// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"

#include <stddef.h>

#include <memory>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profile_resetter/brandcoded_default_settings.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/safe_xml_parser.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"

namespace {

const int kDownloadTimeoutSec = 10;
const char kPostXml[] =
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    "<request"
    "    version=\"chromeprofilereset-1.1\""
    "    protocol=\"3.0\""
    "    installsource=\"profilereset\">"
    "  <app appid=\"{8A69D345-D564-463C-AFF1-A69D9E530F96}\">"
    "    <data name=\"install\" index=\"__BRANDCODE_PLACEHOLDER__\"/>"
    "  </app>"
    "</request>";

// Returns the query to the server which can be used to retrieve the config.
// |brand| is a brand code, it mustn't be empty.
std::string GetUploadData(const std::string& brand) {
  DCHECK(!brand.empty());
  std::string data(kPostXml);
  const std::string placeholder("__BRANDCODE_PLACEHOLDER__");
  size_t placeholder_pos = data.find(placeholder);
  DCHECK(placeholder_pos != std::string::npos);
  data.replace(placeholder_pos, placeholder.size(), brand);
  return data;
}

}  // namespace

BrandcodeConfigFetcher::BrandcodeConfigFetcher(
    network::mojom::URLLoaderFactory* url_loader_factory,
    FetchCallback callback,
    const GURL& url,
    const std::string& brandcode)
    : fetch_callback_(std::move(callback)), weak_ptr_factory_(this) {
  DCHECK(!brandcode.empty());
  net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("brandcode_config", R"(
        semantics {
          sender: "Brandcode Configuration Fetcher"
          description:
            "Chrome installation can be non-organic. That means that Chrome "
            "is distributed by partners and it has a brand code associated "
            "with that partner. For the settings reset operation, Chrome needs "
            "to know the default settings which are partner specific."
          trigger: "'Reset Settings' invocation from Chrome settings."
          data: "Brandcode."
          destination: GOOGLE_OWNED_SERVICE
        }
        policy {
          cookies_allowed: NO
          setting:
            "This feature cannot be disabled and is only invoked by user "
            "request."
          policy_exception_justification:
            "Not implemented, considered not useful as enterprises don't need "
            "to install Chrome in a non-organic fashion."
        })");
  auto resource_request = std::make_unique<network::ResourceRequest>();
  resource_request->url = url;
  resource_request->load_flags = net::LOAD_DISABLE_CACHE;
  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
  resource_request->method = "POST";
  resource_request->headers.SetHeader("Accept", "text/xml");
  simple_url_loader_ = network::SimpleURLLoader::Create(
      std::move(resource_request), traffic_annotation);
  simple_url_loader_->AttachStringForUpload(GetUploadData(brandcode),
                                            "text/xml");
  simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
      url_loader_factory,
      base::BindOnce(&BrandcodeConfigFetcher::OnSimpleLoaderComplete,
                     weak_ptr_factory_.GetWeakPtr()));
  // Abort the download attempt if it takes too long.
  download_timer_.Start(FROM_HERE, base::Seconds(kDownloadTimeoutSec), this,
                        &BrandcodeConfigFetcher::OnDownloadTimeout);
}

BrandcodeConfigFetcher::~BrandcodeConfigFetcher() = default;

void BrandcodeConfigFetcher::SetCallback(FetchCallback callback) {
  fetch_callback_ = std::move(callback);
}

void BrandcodeConfigFetcher::OnSimpleLoaderComplete(
    std::optional<std::string> response_body) {
  const bool is_valid_response =
      response_body && simple_url_loader_->ResponseInfo() &&
      simple_url_loader_->ResponseInfo()->mime_type == "text/xml";

  // Release resources before potentially running the callback.
  simple_url_loader_.reset();
  download_timer_.Stop();

  if (is_valid_response) {
    data_decoder::DataDecoder::ParseXmlIsolated(
        *response_body,
        data_decoder::mojom::XmlParser::WhitespaceBehavior::kIgnore,
        base::BindOnce(&BrandcodeConfigFetcher::OnXmlConfigParsed,
                       weak_ptr_factory_.GetWeakPtr()));
  } else {
    std::move(fetch_callback_).Run();
  }

  // `this` may now be deleted from `fetch_callback_`.
}

void BrandcodeConfigFetcher::OnXmlConfigParsed(
    data_decoder::DataDecoder::ValueOrError value_or_error) {
  // The |fetch_callback_| is called in the case of either success or
  // failure. The difference is whether |default_settings_| is populated.
  base::ScopedClosureRunner scoped_closure(std::move(fetch_callback_));

  if (!value_or_error.has_value())
    return;

  const base::Value* node = &*value_or_error;
  if (!data_decoder::IsXmlElementNamed(*node, "response"))
    return;

  // Descend this tag path to find the node containing the JSON data.
  const char* kDataPath[] = {"app", "data"};
  for (const auto* tag : kDataPath) {
    node = data_decoder::GetXmlElementChildWithTag(*node, tag);
    if (!node)
      return;
  }

  // Extract the text JSON data from the "data" node to specify the new
  // settings.
  std::string master_prefs;
  if (node && data_decoder::GetXmlElementText(*node, &master_prefs)) {
    default_settings_ =
        std::make_unique<BrandcodedDefaultSettings>(master_prefs);
  }
}

void BrandcodeConfigFetcher::OnDownloadTimeout() {
  if (simple_url_loader_) {
    simple_url_loader_.reset();
    std::move(fetch_callback_).Run();
  }
}
