// Copyright 2012 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/sandboxed_unpacker.h"

#include <stddef.h>
#include <stdint.h>

#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/containers/to_vector.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/crx_file/crx_verifier.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/declarative_net_request/file_backed_ruleset_source.h"
#include "extensions/browser/api/declarative_net_request/install_index_helper.h"
#include "extensions/browser/api/declarative_net_request/ruleset_source.h"
#include "extensions/browser/computed_hashes.h"
#include "extensions/browser/content_verifier/content_verifier_key.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/browser/install/sandboxed_unpacker_failure_reason.h"
#include "extensions/browser/install_stage.h"
#include "extensions/browser/json_file_sanitizer.h"
#include "extensions/browser/ruleset_parse_result.h"
#include "extensions/browser/verified_contents.h"
#include "extensions/browser/zipfile_installer.h"
#include "extensions/common/api/declarative_net_request/dnr_manifest_data.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_l10n_util.h"
#include "extensions/common/extension_resource_path_normalizer.h"
#include "extensions/common/extension_utility_types.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/default_locale_handler.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/switches.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/codec/png_codec.h"

using base::ASCIIToUTF16;
using content::BrowserThread;

namespace extensions {
namespace {

// Normalize the file path. If the call to base::NormalizeFilePath fails then we
// return the original path.
base::FilePath NormalizeFilePath(const base::FilePath& path) {
  base::FilePath normalized;
  if (!base::NormalizeFilePath(path, &normalized)) {
    LOG(WARNING) << path.value() << " couldn't be normalized.";
    return path;
  }
  return normalized;
}

// Work horse for FindWritableTempLocation. Creates a temp file in the folder
// and tries to normalize the path.
bool VerifyWritableTempLocation(base::FilePath* temp_dir) {
  if (temp_dir->empty()) {
    return false;
  }

  base::FilePath temp_file;
  if (!base::CreateTemporaryFileInDir(*temp_dir, &temp_file)) {
    LOG(ERROR) << temp_dir->value() << " is not writable";
    return false;
  }

  // Clean up the temp file.
  base::DeleteFile(temp_file);

  *temp_dir = NormalizeFilePath(*temp_dir);
  return true;
}

// This function tries to find a location for unpacking the extension archive
// that is writable. If no such location exists we can not proceed and should
// fail.
// The result will be written to |temp_dir|. The function will write to this
// parameter even if it returns false.
bool FindWritableTempLocation(const base::FilePath& extensions_dir,
                              base::FilePath* temp_dir) {
// On ChromeOS, we will only attempt to unpack extension in cryptohome (profile)
// directory to provide additional security/privacy and speed up the rest of
// the extension install process.
#if !BUILDFLAG(IS_CHROMEOS)
  base::PathService::Get(base::DIR_TEMP, temp_dir);
  if (VerifyWritableTempLocation(temp_dir)) {
    return true;
  }
#endif

  *temp_dir = file_util::GetInstallTempDir(extensions_dir);
  if (VerifyWritableTempLocation(temp_dir)) {
    return true;
  }
  // Neither path is writable, installation will fail.
  LOG(ERROR) << "Both the %TEMP% folder and the profile seem to be read-only. "
                "Installation can not complete!";
  return false;
}

std::set<base::FilePath> GetMessageCatalogPathsToBeSanitized(
    const base::FilePath& locales_path) {
  // Not all folders under _locales have to be valid locales.
  base::FileEnumerator locales(locales_path, /*recursive=*/false,
                               base::FileEnumerator::DIRECTORIES);

  std::set<base::FilePath> message_catalog_paths;
  std::set<std::string> all_locales;
  extension_l10n_util::GetAllLocales(&all_locales);
  base::FilePath locale_path;
  while (!(locale_path = locales.Next()).empty()) {
    if (!extension_l10n_util::ShouldSkipValidation(locales_path, locale_path,
                                                   all_locales)) {
      message_catalog_paths.insert(locale_path.Append(kMessagesFilename));
    }
  }
  return message_catalog_paths;
}

// Callback for ComputedHashes::Create, compute hashes for all files except
// _metadata directory (e.g. computed_hashes.json itself).
bool ShouldComputeHashesForResource(
    const base::FilePath& relative_resource_path) {
  std::vector<base::FilePath::StringType> components =
      relative_resource_path.GetComponents();
  return !components.empty() && components[0] != kMetadataFolder;
}

std::optional<crx_file::VerifierFormat> g_verifier_format_override_for_test;

}  // namespace

class SandboxedUnpacker::IOThreadState {
 public:
  IOThreadState() = default;
  IOThreadState(const IOThreadState&) = delete;
  IOThreadState& operator=(const IOThreadState&) = delete;
  ~IOThreadState() = default;

  void CleanUp() {
    image_sanitizer_.reset();
    json_file_sanitizer_.reset();
  }

  data_decoder::DataDecoder* GetDataDecoder() { return &data_decoder_; }

  void CreateImangeSanitizer(
      const extensions::Extension* extension,
      const base::FilePath& extension_root,
      scoped_refptr<ImageSanitizer::Client> client,
      const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner) {
    DCHECK(!image_sanitizer_);
    std::set<base::FilePath> image_paths =
        ExtensionsClient::Get()->GetBrowserImagePaths(extension);
    image_sanitizer_ = ImageSanitizer::CreateAndStart(
        client, extension_root, image_paths, unpacker_io_task_runner);
  }

  void CreateJsonFileSanitizer(
      std::set<base::FilePath> message_catalog_paths,
      JsonFileSanitizer::Callback callback,
      const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner) {
    json_file_sanitizer_ = JsonFileSanitizer::CreateAndStart(
        std::move(message_catalog_paths), std::move(callback),
        unpacker_io_task_runner);
  }

 private:
  // Controls our own lazily started, isolated instance of the Data Decoder
  // service so that multiple decode operations related to this
  // SandboxedUnpacker can share a single instance. Only used for image
  // sanitization.
  data_decoder::DataDecoder data_decoder_;

  // The ImageSanitizer used to clean-up images.
  std::unique_ptr<ImageSanitizer> image_sanitizer_;

  std::unique_ptr<JsonFileSanitizer> json_file_sanitizer_;
};

SandboxedUnpackerClient::SandboxedUnpackerClient()
    : RefCountedDeleteOnSequence<SandboxedUnpackerClient>(
          content::GetUIThreadTaskRunner({})) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void SandboxedUnpackerClient::ShouldComputeHashesForOffWebstoreExtension(
    scoped_refptr<const Extension> extension,
    base::OnceCallback<void(bool)> callback) {
  std::move(callback).Run(false);
}

void SandboxedUnpackerClient::GetContentVerifierKey(
    base::OnceCallback<void(ContentVerifierKey)> callback) {
  std::move(callback).Run(kWebstoreSignaturesPublicKey);
}

SandboxedUnpacker::ScopedVerifierFormatOverrideForTest::
    ScopedVerifierFormatOverrideForTest(crx_file::VerifierFormat format) {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  DCHECK(!g_verifier_format_override_for_test.has_value());
  g_verifier_format_override_for_test = format;
}

SandboxedUnpacker::ScopedVerifierFormatOverrideForTest::
    ~ScopedVerifierFormatOverrideForTest() {
  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
  g_verifier_format_override_for_test.reset();
}

SandboxedUnpacker::SandboxedUnpacker(
    mojom::ManifestLocation location,
    int creation_flags,
    const base::FilePath& extensions_dir,
    const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
    SandboxedUnpackerClient* client)
    : client_(client),
      extensions_dir_(extensions_dir),
      location_(location),
      creation_flags_(creation_flags),
      format_verifier_override_(g_verifier_format_override_for_test),
      unpacker_io_task_runner_(unpacker_io_task_runner),
      io_thread_state_(std::make_unique<IOThreadState>()) {
  // Tracking for crbug.com/41301792. The location must be valid. If it's
  // invalid, the utility process kills itself for a bad IPC.
  CHECK_GT(location, mojom::ManifestLocation::kInvalidLocation);
  CHECK_LE(location, mojom::ManifestLocation::kMaxValue);
}

bool SandboxedUnpacker::CreateTempDirectory() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  base::FilePath temp_dir;
  if (!FindWritableTempLocation(extensions_dir_, &temp_dir)) {
    ReportFailure(
        SandboxedUnpackerFailureReason::COULD_NOT_GET_TEMP_DIRECTORY,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"COULD_NOT_GET_TEMP_DIRECTORY"));
    return false;
  }

  if (!temp_dir_.CreateUniqueTempDirUnderPath(temp_dir)) {
    ReportFailure(
        SandboxedUnpackerFailureReason::COULD_NOT_CREATE_TEMP_DIRECTORY,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"COULD_NOT_CREATE_TEMP_DIRECTORY"));
    return false;
  }

  return true;
}

void SandboxedUnpacker::StartWithCrx(const CRXFileInfo& crx_info) {
  // We assume that we are started on the thread that the client wants us
  // to do file IO on.
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  std::string expected_hash;
  if (!crx_info.expected_hash.empty() &&
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          extensions::switches::kEnableCrxHashCheck)) {
    expected_hash = base::ToLowerASCII(crx_info.expected_hash);
  }

  if (!CreateTempDirectory()) {
    return;  // ReportFailure() already called.
  }

  // Initialize the path that will eventually contain the unpacked extension.
  extension_root_ = temp_dir_.GetPath().AppendASCII(kTempExtensionName);

  client_->OnStageChanged(InstallationStage::kCopying);
  // Copy the crx file into our working directory before validating its
  // signature so that the bytes that are validated are the same bytes that
  // are subsequently unzipped, even if the source path is modified
  // concurrently.
  // Note: Copying prior to signature validation incurs a file copy for invalid
  // or corrupt CRX files, but guarantees that validation operates on the
  // isolated working copy. Since verification failing is an uncommon case,
  // this isn't a major performance concern.
  base::FilePath temp_crx_path =
      temp_dir_.GetPath().Append(crx_info.path.BaseName());

  if (!base::CopyFile(crx_info.path, temp_crx_path)) {
    // Failed to copy extension file to temporary directory.
    ReportFailure(SandboxedUnpackerFailureReason::
                      FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY,
                  l10n_util::GetStringFUTF16(
                      IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                      u"FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY"));
    return;
  }

  client_->OnStageChanged(InstallationStage::kVerification);
  // Extract the public key and validate the package.
  if (!ValidateSignature(
          temp_crx_path, expected_hash,
          format_verifier_override_.value_or(crx_info.required_format))) {
    return;  // ValidateSignature() already reported the error.
  }

  base::FilePath normalized_crx_path = NormalizeFilePath(temp_crx_path);
  client_->OnStageChanged(InstallationStage::kUnpacking);
  // Make sure to create the directory where the extension will be unzipped, as
  // the unzipper service requires it.
  base::FilePath unzipped_dir =
      normalized_crx_path.DirName().AppendASCII(kTempExtensionName);
  base::File::Error error;
  if (!base::CreateDirectoryAndGetError(unzipped_dir, &error)) {
    LOG(ERROR) << "Failed to created directory " << unzipped_dir.value()
               << " with error " << error;
    ReportFailure(SandboxedUnpackerFailureReason::UNZIP_FAILED,
                  l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
    return;
  }

  Unzip(normalized_crx_path, unzipped_dir);
}

void SandboxedUnpacker::StartWithDirectory(const ExtensionId& extension_id,
                                           const std::string& public_key,
                                           const base::FilePath& directory) {
  // We assume that we are started on the thread that the client wants us
  // to do file IO on.
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  extension_id_ = extension_id;
  public_key_ = public_key;
  if (!CreateTempDirectory()) {
    return;  // ReportFailure() already called.
  }

  extension_root_ = temp_dir_.GetPath().AppendASCII(kTempExtensionName);

  if (!base::Move(directory, extension_root_)) {
    LOG(ERROR) << "Could not move " << directory.value() << " to "
               << extension_root_.value();
    ReportFailure(
        SandboxedUnpackerFailureReason::DIRECTORY_MOVE_FAILED,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"DIRECTORY_MOVE_FAILED"));
    return;
  }

  Unpack(extension_root_);
}

SandboxedUnpacker::~SandboxedUnpacker() {
  // To avoid blocking shutdown, don't delete temporary directory here if it
  // hasn't been cleaned up or passed on to another owner yet.
  // This is OK because ExtensionGarbageCollector will take care of the leaked
  // |temp_dir_| eventually.
  temp_dir_.Take();

  unpacker_io_task_runner_->DeleteSoon(FROM_HERE, std::move(io_thread_state_));
}

void SandboxedUnpacker::Unzip(const base::FilePath& crx_path,
                              const base::FilePath& unzipped_dir) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  DCHECK(crx_path.DirName() == temp_dir_.GetPath());

  ZipFileInstaller::Create(unpacker_io_task_runner_,
                           base::BindOnce(&SandboxedUnpacker::UnzipDone, this))
      ->LoadFromZipFileInDir(crx_path, unzipped_dir);
}

void SandboxedUnpacker::UnzipDone(const base::FilePath& zip_file,
                                  const base::FilePath& unzip_dir,
                                  const std::string& error) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  if (!error.empty()) {
    ReportFailure(SandboxedUnpackerFailureReason::UNZIP_FAILED,
                  l10n_util::GetStringUTF16(IDS_EXTENSION_PACKAGE_UNZIP_ERROR));
    return;
  }
  base::FilePath verified_contents_path =
      file_util::GetVerifiedContentsPath(extension_root_);
  // If the verified contents are already present in the _metadata folder, we
  // can ignore the verified contents in the header.
  if (compressed_verified_contents_.empty() ||
      base::PathExists(verified_contents_path)) {
    Unpack(unzip_dir);
    return;
  }
  GetDataDecoder()->GzipUncompress(
      compressed_verified_contents_,
      base::BindOnce(&SandboxedUnpacker::OnVerifiedContentsUncompressed, this,
                     unzip_dir));
}

void SandboxedUnpacker::OnVerifiedContentsUncompressed(
    const base::FilePath& unzip_dir,
    base::expected<mojo_base::BigBuffer, std::string> result) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (!result.has_value()) {
    ReportFailure(SandboxedUnpackerFailureReason::
                      CRX_HEADER_VERIFIED_CONTENTS_UNCOMPRESSING_FAILURE,
                  l10n_util::GetStringFUTF16(
                      IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                      u"CRX_HEADER_VERIFIED_CONTENTS_UNCOMPRESSING_FAILURE"));
    return;
  }
  // Make a copy, since |result| may store data in shared memory, accessible by
  // some other processes.
  std::vector<uint8_t> verified_contents = base::ToVector(*result);

  client_->GetContentVerifierKey(
      base::BindOnce(&SandboxedUnpacker::StoreVerifiedContentsInExtensionDir,
                     this, unzip_dir, std::move(verified_contents)));
}

void SandboxedUnpacker::StoreVerifiedContentsInExtensionDir(
    const base::FilePath& unzip_dir,
    base::span<const uint8_t> verified_contents,
    ContentVerifierKey content_verifier_key) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  if (!VerifiedContents::Create(
          content_verifier_key,
          {reinterpret_cast<const char*>(verified_contents.data()),
           verified_contents.size()})) {
    ReportFailure(
        SandboxedUnpackerFailureReason::MALFORMED_VERIFIED_CONTENTS,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"MALFORMED_VERIFIED_CONTENTS"));
    return;
  }

  base::FilePath metadata_path = extension_root_.Append(kMetadataFolder);
  if (!base::CreateDirectory(metadata_path)) {
    ReportFailure(
        SandboxedUnpackerFailureReason::COULD_NOT_CREATE_METADATA_DIRECTORY,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"COULD_NOT_CREATE_METADATA_DIRECTORY"));
    return;
  }

  base::FilePath verified_contents_path =
      file_util::GetVerifiedContentsPath(extension_root_);

  // Cannot write the verified contents file.
  if (!base::WriteFile(verified_contents_path, verified_contents)) {
    ReportFailure(SandboxedUnpackerFailureReason::
                      COULD_NOT_WRITE_VERIFIED_CONTENTS_INTO_FILE,
                  l10n_util::GetStringFUTF16(
                      IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                      u"COULD_NOT_WRITE_VERIFIED_CONTENTS_INTO_FILE"));
    return;
  }

  Unpack(unzip_dir);
}

void SandboxedUnpacker::Unpack(const base::FilePath& directory) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(directory.DirName() == temp_dir_.GetPath());

  base::FilePath manifest_path = extension_root_.Append(kManifestFilename);

  // This calls `ReadManifestDone()` on completion.
  ParseJsonFile(manifest_path);
}

void SandboxedUnpacker::ReadManifestDone(
    base::expected<base::Value, std::u16string> result) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (!result.has_value()) {
    ReportUnpackExtensionFailed(result.error());
    return;
  }
  const base::DictValue* dict = result->GetIfDict();
  if (!dict) {
    ReportUnpackExtensionFailed(manifest_errors::kInvalidManifest);
    return;
  }

  std::u16string error;
  scoped_refptr<Extension> extension(
      Extension::Create(extension_root_, location_, *dict, creation_flags_,
                        extension_id_, &error));
  if (!extension) {
    ReportUnpackExtensionFailed(error);
    return;
  }

  std::vector<InstallWarning> warnings;
  if (!file_util::ValidateExtension(extension.get(), &error, &warnings)) {
    ReportUnpackExtensionFailed(error);
    return;
  }
  extension->AddInstallWarnings(std::move(warnings));

  UnpackExtensionSucceeded(std::move(result).value().TakeDict());
}

void SandboxedUnpacker::UnpackExtensionSucceeded(base::DictValue manifest) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  std::optional<base::DictValue> final_manifest(RewriteManifestFile(manifest));
  if (!final_manifest) {
    return;
  }

  // Create an extension object that refers to the temporary location the
  // extension was unpacked to. We use this until the extension is finally
  // installed. For example, the install UI shows images from inside the
  // extension.

  // Localize manifest now, so confirm UI gets correct extension name.

  // TODO(rdevlin.cronin): Continue removing std::string errors and replacing
  // with std::u16string
  std::string utf8_error;
  if (!extension_l10n_util::LocalizeExtension(
          extension_root_, &final_manifest.value(),
          extension_l10n_util::GzippedMessagesPermission::kDisallow,
          &utf8_error)) {
    ReportFailure(
        SandboxedUnpackerFailureReason::COULD_NOT_LOCALIZE_EXTENSION,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE,
                                   base::UTF8ToUTF16(utf8_error)));
    return;
  }

  std::u16string utf16_error;
  extension_ =
      Extension::Create(extension_root_, location_, final_manifest.value(),
                        Extension::REQUIRE_KEY | creation_flags_, &utf16_error);

  if (!extension_.get()) {
    ReportFailure(SandboxedUnpackerFailureReason::INVALID_MANIFEST,
                  u"Manifest is invalid: " + utf16_error);
    return;
  }

  // The install icon path may be empty, which is OK, but if it is not it should
  // be normalized successfully.
  const std::string& original_install_icon_path =
      IconsInfo::GetIcons(extension_.get())
          .Get(extension_misc::EXTENSION_ICON_LARGE,
               ExtensionIconSet::Match::kBigger);
  if (!original_install_icon_path.empty() &&
      !NormalizeExtensionResourcePath(
          base::FilePath::FromUTF8Unsafe(original_install_icon_path),
          &install_icon_path_)) {
    // Invalid path for browser image.
    ReportFailure(
        SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"INVALID_PATH_FOR_BROWSER_IMAGE"));
    return;
  }

  manifest_ = std::move(manifest);

  io_thread_state_->CreateImangeSanitizer(extension_.get(), extension_root_,
                                          this, unpacker_io_task_runner_);
}

data_decoder::DataDecoder* SandboxedUnpacker::GetDataDecoder() {
  return io_thread_state_->GetDataDecoder();
}

void SandboxedUnpacker::OnImageDecoded(const base::FilePath& path,
                                       SkBitmap image) {
  if (path == install_icon_path_) {
    install_icon_ = image;
  }
}

void SandboxedUnpacker::OnImageSanitizationDone(
    ImageSanitizer::Status status,
    const base::FilePath& file_path_for_error) {
  if (status == ImageSanitizer::Status::kSuccess) {
    // Next step is to sanitize the message catalogs.
    ReadMessageCatalogs();
    return;
  }

  SandboxedUnpackerFailureReason failure_reason =
      SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED;
  std::u16string error;
  switch (status) {
    case ImageSanitizer::Status::kImagePathError:
      failure_reason =
          SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"INVALID_PATH_FOR_BROWSER_IMAGE");
      break;
    case ImageSanitizer::Status::kFileReadError:
    case ImageSanitizer::Status::kDecodingError:
      error = l10n_util::GetStringFUTF16(
          IDS_EXTENSION_PACKAGE_IMAGE_ERROR,
          base::i18n::GetDisplayStringInLTRDirectionality(
              file_path_for_error.BaseName().LossyDisplayName()));
      break;
    case ImageSanitizer::Status::kFileDeleteError:
      failure_reason =
          SandboxedUnpackerFailureReason::ERROR_REMOVING_OLD_IMAGE_FILE;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"ERROR_REMOVING_OLD_IMAGE_FILE");
      break;
    case ImageSanitizer::Status::kEncodingError:
      failure_reason =
          SandboxedUnpackerFailureReason::ERROR_RE_ENCODING_THEME_IMAGE;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"ERROR_RE_ENCODING_THEME_IMAGE");
      break;
    case ImageSanitizer::Status::kFileWriteError:
      failure_reason = SandboxedUnpackerFailureReason::ERROR_SAVING_THEME_IMAGE;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"ERROR_SAVING_THEME_IMAGE");
      break;
    default:
      NOTREACHED();
  }

  ReportFailure(failure_reason, error);
}

void SandboxedUnpacker::ReadMessageCatalogs() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (LocaleInfo::GetDefaultLocale(extension_.get()).empty()) {
    MessageCatalogsSanitized(base::ok());
    return;
  }

  // Get the paths to the message catalogs we should sanitize on the file task
  // runner.
  base::FilePath locales_path = extension_root_.Append(kLocaleFolder);

  extensions::GetExtensionFileTaskRunner()->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&GetMessageCatalogPathsToBeSanitized, locales_path),
      base::BindOnce(&SandboxedUnpacker::SanitizeMessageCatalogs, this));
}

void SandboxedUnpacker::SanitizeMessageCatalogs(
    const std::set<base::FilePath>& message_catalog_paths) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  io_thread_state_->CreateJsonFileSanitizer(
      message_catalog_paths,
      base::BindOnce(&SandboxedUnpacker::MessageCatalogsSanitized, this),
      unpacker_io_task_runner_);
}

void SandboxedUnpacker::MessageCatalogsSanitized(
    base::expected<void, JsonFileSanitizer::Error> result) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (result.has_value()) {
    IndexAndPersistJSONRulesetsIfNeeded();
    return;
  }

  SandboxedUnpackerFailureReason failure_reason =
      SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED;
  std::u16string error;
  switch (result.error()) {
    case JsonFileSanitizer::Error::kFileReadError:
    case JsonFileSanitizer::Error::kDecodingError:
      failure_reason = SandboxedUnpackerFailureReason::INVALID_CATALOG_DATA;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"INVALID_CATALOG_DATA");
      break;
    case JsonFileSanitizer::Error::kSerializingError:
      failure_reason =
          SandboxedUnpackerFailureReason::ERROR_SERIALIZING_CATALOG;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"ERROR_SERIALIZING_CATALOG");
      break;
    case JsonFileSanitizer::Error::kFileDeleteError:
    case JsonFileSanitizer::Error::kFileWriteError:
      failure_reason = SandboxedUnpackerFailureReason::ERROR_SAVING_CATALOG;
      error = l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                         u"ERROR_SAVING_CATALOG");
      break;
  }

  ReportFailure(failure_reason, error);
}

void SandboxedUnpacker::IndexAndPersistJSONRulesetsIfNeeded() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  DCHECK(extension_);

  // Defer ruleset indexing for disabled rulesets to speed up extension
  // installation.
  auto ruleset_filter = declarative_net_request::FileBackedRulesetSource::
      RulesetFilter::kIncludeManifestEnabled;

  // Ignore rule parsing errors since ruleset indexing (and therefore rule
  // parsing) is deferred until the ruleset is enabled for packed extensions.
  auto parse_flags = declarative_net_request::RulesetSource::kNone;

  declarative_net_request::InstallIndexHelper::IndexStaticRulesets(
      *extension_, ruleset_filter, parse_flags,
      base::BindOnce(&SandboxedUnpacker::OnJSONRulesetsIndexed, this));
}

void SandboxedUnpacker::OnJSONRulesetsIndexed(RulesetParseResult result) {
  if (result.error) {
    ReportFailure(
        SandboxedUnpackerFailureReason::ERROR_INDEXING_DNR_RULESET,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE,
                                   base::UTF8ToUTF16(*result.error)));
    return;
  }

  if (!result.warnings.empty()) {
    extension_->AddInstallWarnings(std::move(result.warnings));
  }

  ruleset_install_prefs_ = std::move(result.ruleset_install_prefs);

  CheckComputeHashes();
}

void SandboxedUnpacker::CheckComputeHashes() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  client_->ShouldComputeHashesForOffWebstoreExtension(
      extension_, base::BindOnce(&SandboxedUnpacker::MaybeComputeHashes, this));
}

void SandboxedUnpacker::MaybeComputeHashes(bool should_compute) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (!should_compute) {
    ReportSuccess();
    return;
  }

  base::ElapsedTimer timer;

  std::optional<ComputedHashes::Data> computed_hashes_data =
      ComputedHashes::Compute(
          extension_->path(),
          extension_misc::kContentVerificationDefaultBlockSize,
          IsCancelledCallback(),
          base::BindRepeating(&ShouldComputeHashesForResource));
  bool success =
      computed_hashes_data &&
      ComputedHashes(std::move(*computed_hashes_data))
          .WriteToFile(file_util::GetComputedHashesPath(extension_->path()));
  UMA_HISTOGRAM_BOOLEAN(
      "Extensions.ContentVerification.ComputeHashesOnInstallResult", success);
  if (success) {
    UMA_HISTOGRAM_TIMES(
        "Extensions.ContentVerification.ComputeHashesOnInstallTime",
        timer.Elapsed());
  } else {
    LOG(ERROR) << "[extension " << extension_->id()
               << "] Failed to create computed_hashes.json";
  }

  ReportSuccess();
}

void SandboxedUnpacker::ReportUnpackExtensionFailed(
    const std::u16string& error) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  ReportFailure(
      SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED,
      l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_MESSAGE, error));
}

std::u16string SandboxedUnpacker::FailureReasonToString16(
    const SandboxedUnpackerFailureReason reason) {
  switch (reason) {
    case SandboxedUnpackerFailureReason::COULD_NOT_GET_TEMP_DIRECTORY:
      return u"COULD_NOT_GET_TEMP_DIRECTORY";
    case SandboxedUnpackerFailureReason::COULD_NOT_CREATE_TEMP_DIRECTORY:
      return u"COULD_NOT_CREATE_TEMP_DIRECTORY";
    case SandboxedUnpackerFailureReason::
        FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY:
      return u"FAILED_TO_COPY_EXTENSION_FILE_TO_TEMP_DIRECTORY";
    case SandboxedUnpackerFailureReason::COULD_NOT_GET_SANDBOX_FRIENDLY_PATH:
      return u"COULD_NOT_GET_SANDBOX_FRIENDLY_PATH";
    case SandboxedUnpackerFailureReason::COULD_NOT_LOCALIZE_EXTENSION:
      return u"COULD_NOT_LOCALIZE_EXTENSION";
    case SandboxedUnpackerFailureReason::INVALID_MANIFEST:
      return u"INVALID_MANIFEST";
    case SandboxedUnpackerFailureReason::UNPACKER_CLIENT_FAILED:
      return u"UNPACKER_CLIENT_FAILED";
    case SandboxedUnpackerFailureReason::
        UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL:
      return u"UTILITY_PROCESS_CRASHED_WHILE_TRYING_TO_INSTALL";

    case SandboxedUnpackerFailureReason::CRX_FILE_NOT_READABLE:
      return u"CRX_FILE_NOT_READABLE";
    case SandboxedUnpackerFailureReason::CRX_HEADER_INVALID:
      return u"CRX_HEADER_INVALID";
    case SandboxedUnpackerFailureReason::CRX_MAGIC_NUMBER_INVALID:
      return u"CRX_MAGIC_NUMBER_INVALID";
    case SandboxedUnpackerFailureReason::CRX_VERSION_NUMBER_INVALID:
      return u"CRX_VERSION_NUMBER_INVALID";
    case SandboxedUnpackerFailureReason::CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE:
      return u"CRX_EXCESSIVELY_LARGE_KEY_OR_SIGNATURE";
    case SandboxedUnpackerFailureReason::CRX_ZERO_KEY_LENGTH:
      return u"CRX_ZERO_KEY_LENGTH";
    case SandboxedUnpackerFailureReason::CRX_ZERO_SIGNATURE_LENGTH:
      return u"CRX_ZERO_SIGNATURE_LENGTH";
    case SandboxedUnpackerFailureReason::CRX_PUBLIC_KEY_INVALID:
      return u"CRX_PUBLIC_KEY_INVALID";
    case SandboxedUnpackerFailureReason::CRX_SIGNATURE_INVALID:
      return u"CRX_SIGNATURE_INVALID";
    case SandboxedUnpackerFailureReason::
        CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED:
      return u"CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED";
    case SandboxedUnpackerFailureReason::CRX_SIGNATURE_VERIFICATION_FAILED:
      return u"CRX_SIGNATURE_VERIFICATION_FAILED";
    case SandboxedUnpackerFailureReason::CRX_FILE_IS_DELTA_UPDATE:
      return u"CRX_FILE_IS_DELTA_UPDATE";
    case SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID:
      return u"CRX_EXPECTED_HASH_INVALID";

    case SandboxedUnpackerFailureReason::ERROR_SERIALIZING_MANIFEST_JSON:
      return u"ERROR_SERIALIZING_MANIFEST_JSON";
    case SandboxedUnpackerFailureReason::ERROR_SAVING_MANIFEST_JSON:
      return u"ERROR_SAVING_MANIFEST_JSON";

    case SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BROWSER_IMAGE:
      return u"INVALID_PATH_FOR_BROWSER_IMAGE";
    case SandboxedUnpackerFailureReason::ERROR_REMOVING_OLD_IMAGE_FILE:
      return u"ERROR_REMOVING_OLD_IMAGE_FILE";
    case SandboxedUnpackerFailureReason::INVALID_PATH_FOR_BITMAP_IMAGE:
      return u"INVALID_PATH_FOR_BITMAP_IMAGE";
    case SandboxedUnpackerFailureReason::ERROR_RE_ENCODING_THEME_IMAGE:
      return u"ERROR_RE_ENCODING_THEME_IMAGE";
    case SandboxedUnpackerFailureReason::ERROR_SAVING_THEME_IMAGE:
      return u"ERROR_SAVING_THEME_IMAGE";

    case SandboxedUnpackerFailureReason::INVALID_CATALOG_DATA:
      return u"INVALID_CATALOG_DATA";
    case SandboxedUnpackerFailureReason::ERROR_SERIALIZING_CATALOG:
      return u"ERROR_SERIALIZING_CATALOG";
    case SandboxedUnpackerFailureReason::ERROR_SAVING_CATALOG:
      return u"ERROR_SAVING_CATALOG";

    case SandboxedUnpackerFailureReason::CRX_HASH_VERIFICATION_FAILED:
      return u"CRX_HASH_VERIFICATION_FAILED";

    case SandboxedUnpackerFailureReason::UNZIP_FAILED:
      return u"UNZIP_FAILED";
    case SandboxedUnpackerFailureReason::DIRECTORY_MOVE_FAILED:
      return u"DIRECTORY_MOVE_FAILED";

    case SandboxedUnpackerFailureReason::ERROR_INDEXING_DNR_RULESET:
      return u"ERROR_INDEXING_DNR_RULESET";

    case SandboxedUnpackerFailureReason::CRX_REQUIRED_PROOF_MISSING:
      return u"CRX_REQUIRED_PROOF_MISSING";

    case SandboxedUnpackerFailureReason::DEPRECATED_ABORTED_DUE_TO_SHUTDOWN:
    case SandboxedUnpackerFailureReason::DEPRECATED_ERROR_PARSING_DNR_RULESET:
    case SandboxedUnpackerFailureReason::NUM_FAILURE_REASONS:
    default:
      NOTREACHED();
  }
}

void SandboxedUnpacker::FailWithPackageError(
    const SandboxedUnpackerFailureReason reason) {
  ReportFailure(reason,
                l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_ERROR_CODE,
                                           FailureReasonToString16(reason)));
}

bool SandboxedUnpacker::ValidateSignature(
    const base::FilePath& crx_path,
    const std::string& expected_hash,
    const crx_file::VerifierFormat required_format) {
  std::vector<uint8_t> hash;
  if (!expected_hash.empty()) {
    if (!base::HexStringToBytes(expected_hash, &hash)) {
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID);
      return false;
    }
  }

  const crx_file::VerifierResult result = crx_file::Verify(
      crx_path, required_format, std::vector<std::vector<uint8_t>>(), hash,
      &public_key_, &extension_id_, &compressed_verified_contents_);

  switch (result) {
    case crx_file::VerifierResult::OK_FULL: {
      return true;
    }
    case crx_file::VerifierResult::OK_DELTA:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_FILE_IS_DELTA_UPDATE);
      break;
    case crx_file::VerifierResult::ERROR_FILE_NOT_READABLE:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_FILE_NOT_READABLE);
      break;
    case crx_file::VerifierResult::ERROR_HEADER_INVALID:
      FailWithPackageError(SandboxedUnpackerFailureReason::CRX_HEADER_INVALID);
      break;
    case crx_file::VerifierResult::ERROR_SIGNATURE_INITIALIZATION_FAILED:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::
              CRX_SIGNATURE_VERIFICATION_INITIALIZATION_FAILED);
      break;
    case crx_file::VerifierResult::ERROR_SIGNATURE_VERIFICATION_FAILED:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_SIGNATURE_VERIFICATION_FAILED);
      break;
    case crx_file::VerifierResult::ERROR_EXPECTED_HASH_INVALID:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_EXPECTED_HASH_INVALID);
      break;
    case crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING:
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_REQUIRED_PROOF_MISSING);
      break;
    case crx_file::VerifierResult::ERROR_FILE_HASH_FAILED:
      // We should never get this result unless we had specifically asked for
      // verification of the crx file's hash.
      CHECK(!expected_hash.empty());
      FailWithPackageError(
          SandboxedUnpackerFailureReason::CRX_HASH_VERIFICATION_FAILED);
      break;
  }

  return false;
}

void SandboxedUnpacker::ReportFailure(
    const SandboxedUnpackerFailureReason reason,
    const std::u16string& error) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  UMA_HISTOGRAM_ENUMERATION(
      "Extensions.SandboxUnpackFailureReason2", reason,
      SandboxedUnpackerFailureReason::NUM_FAILURE_REASONS);
  Cleanup();

  client_->OnUnpackFailure(CrxInstallError(reason, error));
}

void SandboxedUnpacker::ReportSuccess() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());

  UMA_HISTOGRAM_COUNTS_1M("Extensions.SandboxUnpackSuccess", 1);

  DCHECK(!temp_dir_.GetPath().empty());

  // Client takes ownership of temporary directory, manifest, and extension.
  client_->OnUnpackSuccess(
      temp_dir_.Take(), extension_root_,
      std::make_unique<base::DictValue>(std::move(manifest_.value())),
      extension_.get(), install_icon_, std::move(ruleset_install_prefs_));

  // Interestingly, the C++ standard doesn't guarantee that a moved-from vector
  // is empty.
  ruleset_install_prefs_.clear();

  extension_.reset();

  Cleanup();
}

std::optional<base::DictValue> SandboxedUnpacker::RewriteManifestFile(
    const base::DictValue& manifest) {
  constexpr int64_t kMaxFingerprintSize = 1024;

  // Add the public key extracted earlier to the parsed manifest and overwrite
  // the original manifest. We do this to ensure the manifest doesn't contain an
  // exploitable bug that could be used to compromise the browser.
  DCHECK(!public_key_.empty());
  base::DictValue final_manifest = manifest.Clone();
  final_manifest.Set(manifest_keys::kPublicKey, public_key_);

  {
    std::string differential_fingerprint;
    if (base::ReadFileToStringWithMaxSize(
            extension_root_.Append(kDifferentialFingerprintFilename),
            &differential_fingerprint, kMaxFingerprintSize)) {
      final_manifest.Set(manifest_keys::kDifferentialFingerprint,
                         std::move(differential_fingerprint));
    }
  }

  std::optional<std::string> manifest_json = base::WriteJsonWithOptions(
      final_manifest, base::JSONWriter::OPTIONS_PRETTY_PRINT);
  if (!manifest_json) {
    // Error serializing manifest.json.
    ReportFailure(
        SandboxedUnpackerFailureReason::ERROR_SERIALIZING_MANIFEST_JSON,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"ERROR_SERIALIZING_MANIFEST_JSON"));
    return std::nullopt;
  }

  base::FilePath manifest_path = extension_root_.Append(kManifestFilename);
  if (!base::WriteFile(manifest_path, *manifest_json)) {
    // Error saving manifest.json.
    ReportFailure(
        SandboxedUnpackerFailureReason::ERROR_SAVING_MANIFEST_JSON,
        l10n_util::GetStringFUTF16(IDS_EXTENSION_PACKAGE_INSTALL_ERROR,
                                   u"ERROR_SAVING_MANIFEST_JSON"));
    return std::nullopt;
  }

  return std::move(final_manifest);
}

void SandboxedUnpacker::Cleanup() {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  if (temp_dir_.IsValid() && !temp_dir_.Delete()) {
    LOG(WARNING) << "Can not delete temp directory at "
                 << temp_dir_.GetPath().value();
  }

  io_thread_state_->CleanUp();
}

void SandboxedUnpacker::ParseJsonFile(const base::FilePath& path) {
  DCHECK(unpacker_io_task_runner_->RunsTasksInCurrentSequence());
  std::string contents;
  if (!base::ReadFileToString(path, &contents)) {
    ReadManifestDone(base::unexpected(u"File doesn't exist."));
    return;
  }

  base::JSONReader::Result result =
      base::JSONReader::ReadAndReturnValueWithError(
          contents, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  ReadManifestDone(std::move(result).transform_error(
      [](const base::JSONReader::Error& error) {
        return base::UTF8ToUTF16(error.ToString());
      }));
}

}  // namespace extensions
