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

#include "extensions/browser/updater/update_data_provider.h"

#include <optional>
#include <utility>

#include "base/base64.h"
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "components/crx_file/crx_verifier.h"
#include "components/update_client/utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/hash.h"
#include "extensions/browser/content_verifier/content_verifier.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/verifier_formats.h"

namespace extensions {

namespace {

using UpdateClientCallback = UpdateDataProvider::UpdateClientCallback;

void PostErrorTasks(const base::FilePath& unpacked_dir,
                    UpdateClientCallback update_client_callback) {
  base::ThreadPool::PostTask(
      FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
      base::GetDeletePathRecursivelyCallback(unpacked_dir));
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(update_client_callback),
                     update_client::CrxInstaller::Result(
                         update_client::InstallError::GENERIC_ERROR)));
}

}  // namespace

UpdateDataProvider::UpdateDataProvider(content::BrowserContext* browser_context)
    : browser_context_(browser_context) {}

UpdateDataProvider::~UpdateDataProvider() = default;

void UpdateDataProvider::Shutdown() {
  browser_context_ = nullptr;
}

void UpdateDataProvider::GetData(
    bool install_immediately,
    const ExtensionUpdateDataMap& update_crx_component,
    const std::vector<std::string>& ids,
    base::OnceCallback<
        void(const std::vector<std::optional<update_client::CrxComponent>>&)>
        callback) {
  std::vector<std::optional<update_client::CrxComponent>> data;
  if (!browser_context_) {
    for (size_t i = 0; i < ids.size(); i++) {
      data.push_back(std::nullopt);
    }
    std::move(callback).Run(data);
    return;
  }
  const ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
  const ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(browser_context_);
  for (const auto& id : ids) {
    const Extension* extension = registry->GetInstalledExtension(id);
    data.push_back(extension ? std::make_optional<update_client::CrxComponent>()
                             : std::nullopt);
    if (!extension) {
      continue;
    }
    DCHECK_NE(0u, update_crx_component.count(id));
    const ExtensionUpdateData& extension_data = update_crx_component.at(id);
    auto& crx_component = data.back();
    auto pubkey = base::Base64Decode(extension->public_key());
    if (!pubkey) {
      continue;
    }
    crx_component->pk_hash = base::ToVector(crypto::hash::Sha256(*pubkey));
    crx_component->app_id =
        update_client::GetCrxIdFromPublicKeyHash(crx_component->pk_hash);
    if (extension_data.is_corrupt_reinstall) {
      crx_component->version = base::Version("0.0.0.0");
    } else {
      crx_component->version =
          extension_data.pending_version
              ? base::Version(*extension_data.pending_version)
              : extension->version();
    }
    bool allow_dev = extension_urls::GetWebstoreUpdateUrl() !=
                     extension_urls::GetDefaultWebstoreUpdateUrl();
    crx_component->requires_network_encryption = !allow_dev;
    crx_component->crx_format_requirement =
        extension->from_webstore() ? GetWebstoreVerifierFormat(allow_dev)
                                   : GetPolicyVerifierFormat();
    crx_component->installer = base::MakeRefCounted<ExtensionInstaller>(
        id, install_immediately,
        base::BindRepeating(&UpdateDataProvider::RunInstallCallback, this));
    if (!ExtensionsBrowserClient::Get()->IsExtensionEnabled(id,
                                                            browser_context_)) {
      DisableReasonSet disable_reasons = extension_prefs->GetDisableReasons(id);

      if (disable_reasons.empty() ||
          disable_reasons.contains(disable_reason::DISABLE_UNKNOWN)) {
        crx_component->disabled_reasons.push_back(0);
      }

      // We are only interested in valid disable reasons from here.
      disable_reasons.erase(disable_reason::DISABLE_UNKNOWN);

      for (int reason : disable_reasons) {
        crx_component->disabled_reasons.push_back(reason);
      }
    }
    crx_component->install_source = extension_data.is_corrupt_reinstall
                                        ? "reinstall"
                                        : extension_data.install_source;
    crx_component->install_location =
        ManifestFetchData::GetSimpleLocationString(extension->location());
  }
  std::move(callback).Run(data);
}

void UpdateDataProvider::RunInstallCallback(
    const ExtensionId& extension_id,
    const std::string& public_key,
    const base::FilePath& unpacked_dir,
    bool install_immediately,
    UpdateClientCallback update_client_callback) {
  VLOG(3) << "UpdateDataProvider::RunInstallCallback " << extension_id << " "
          << public_key;

  if (!browser_context_) {
    PostErrorTasks(unpacked_dir, std::move(update_client_callback));
    return;
  }

  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&UpdateDataProvider::InstallUpdateCallback, this,
                     extension_id, public_key, unpacked_dir,
                     install_immediately, std::move(update_client_callback)));
}

void UpdateDataProvider::InstallUpdateCallback(
    const ExtensionId& extension_id,
    const std::string& public_key,
    const base::FilePath& unpacked_dir,
    bool install_immediately,
    UpdateClientCallback update_client_callback) {
  if (!browser_context_) {
    PostErrorTasks(unpacked_dir, std::move(update_client_callback));
    return;
  }

  // Error codes are converted into integers and may collide with codes from
  // other embedders. However, for any given extension ID, there should be only
  // one embedder, so the server should be able to figure it out.
  ExtensionSystem::Get(browser_context_)
      ->InstallUpdate(
          extension_id, public_key, unpacked_dir, install_immediately,
          base::BindOnce(
              [](UpdateClientCallback callback,
                 const std::optional<CrxInstallError>& error) {
                DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
                update_client::CrxInstaller::Result result(0);
                if (error.has_value()) {
                  int detail =
                      error->type() ==
                              CrxInstallErrorType::SANDBOXED_UNPACKER_FAILURE
                          ? static_cast<int>(error->sandbox_failure_detail())
                          : static_cast<int>(error->detail());
                  result = update_client::CrxInstaller::Result(
                      static_cast<int>(error->type()), detail);
                }
                std::move(callback).Run(result);
              },
              std::move(update_client_callback)));
}

}  // namespace extensions
