// 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.

#ifndef EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
#define EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_

#include <memory>
#include <optional>
#include <string>
#include <string_view>

#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/scoped_refptr.h"
#include "base/types/expected.h"
#include "base/values.h"
#include "extensions/browser/api/declarative_net_request/install_index_helper.h"
#include "extensions/browser/content_verifier/content_verifier_key.h"
#include "extensions/browser/crx_file_info.h"
#include "extensions/browser/image_sanitizer.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/browser/json_file_sanitizer.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "services/data_decoder/public/cpp/data_decoder.h"

class SkBitmap;

namespace base {
class SequencedTaskRunner;
}

namespace crx_file {
enum class VerifierFormat;
}

namespace extensions {
class Extension;
enum class SandboxedUnpackerFailureReason;
enum class InstallationStage;
struct RulesetParseResult;

namespace declarative_net_request {
struct IndexAndPersistJSONRulesetResult;
}

class SandboxedUnpackerClient
    : public base::RefCountedDeleteOnSequence<SandboxedUnpackerClient> {
 public:
  // Initialize the ref-counted base to always delete on the UI thread. Note
  // the constructor call must also happen on the UI thread.
  SandboxedUnpackerClient();

  // Determines whether `extension` requires computing and storing
  // computed_hashes.json and returns the result through `callback`.
  // Currently we do this only for force-installed extensions outside of Chrome
  // Web Store, and that is reflected in method's name.
  virtual void ShouldComputeHashesForOffWebstoreExtension(
      scoped_refptr<const Extension> extension,
      base::OnceCallback<void(bool)> callback);

  // Since data for content verification (verifier_contents.json) may be present
  // in the CRX header, we need to verify it against public key. Normally it is
  // Chrome Web Store public key, but may be overridden for tests.
  virtual void GetContentVerifierKey(
      base::OnceCallback<void(ContentVerifierKey)> callback);

  // temp_dir - A temporary directory containing the results of the extension
  // unpacking. The client is responsible for deleting this directory.
  //
  // extension_root - The path to the extension root inside of temp_dir.
  //
  // original_manifest - The parsed but unmodified version of the manifest,
  // with no modifications such as localization, etc.
  //
  // extension - The extension that was unpacked. The client is responsible
  // for deleting this memory.
  //
  // install_icon - The icon we will display in the installation UI, if any.
  //
  // ruleset_install_prefs - Install prefs needed for the Declarative Net
  // Request API.
  //
  // Note: OnUnpackSuccess/Failure may be called either synchronously or
  // asynchronously from SandboxedUnpacker::StartWithCrx/Directory.
  virtual void OnUnpackSuccess(
      const base::FilePath& temp_dir,
      const base::FilePath& extension_root,
      std::unique_ptr<base::DictValue> original_manifest,
      const Extension* extension,
      const SkBitmap& install_icon,
      base::DictValue ruleset_install_prefs) = 0;
  virtual void OnUnpackFailure(const CrxInstallError& error) = 0;

  // Called after stage of installation is changed.
  virtual void OnStageChanged(InstallationStage stage) {}

 protected:
  friend class base::RefCountedDeleteOnSequence<SandboxedUnpackerClient>;
  friend class base::DeleteHelper<SandboxedUnpackerClient>;

  virtual ~SandboxedUnpackerClient() = default;
};

// SandboxedUnpacker does work to optionally unpack and then validate/sanitize
// an extension, either starting from a crx file, or else an already unzipped
// directory (eg., from a differential update). The parsing of complex data
// formats like JPEG or JSON is performed in specific, sandboxed services.
//
// Unpacking an extension using this class makes changes to its source, such as
// transcoding all images to PNG, parsing all message catalogs, and rewriting
// the manifest JSON. As such, it should not be used when the output is not
// intended to be given back to the author.
class SandboxedUnpacker : public ImageSanitizer::Client {
 public:
  // Overrides the required verifier format for testing purposes. Only one
  // ScopedVerifierFormatOverrideForTest may exist at a time.
  class ScopedVerifierFormatOverrideForTest {
   public:
    explicit ScopedVerifierFormatOverrideForTest(
        crx_file::VerifierFormat format);
    ~ScopedVerifierFormatOverrideForTest();

   private:
    THREAD_CHECKER(thread_checker_);
  };

  // Creates a SandboxedUnpacker that will do work to unpack an extension,
  // passing the `location` and `creation_flags` to Extension::Create. The
  // `extensions_dir` parameter should specify the directory under which we'll
  // create a subdirectory to write the unpacked extension contents.
  // Note: Because this requires disk I/O, the task runner passed should use
  // TaskShutdownBehavior::SKIP_ON_SHUTDOWN to ensure that either the task is
  // fully run (if initiated before shutdown) or not run at all (if shutdown is
  // initiated first). See crbug.com/41009306.
  // TODO(devlin): SKIP_ON_SHUTDOWN is also not quite sufficient for this. We
  // should probably instead be using base::ImportantFileWriter or similar.
  SandboxedUnpacker(
      mojom::ManifestLocation location,
      int creation_flags,
      const base::FilePath& extensions_dir,
      const scoped_refptr<base::SequencedTaskRunner>& unpacker_io_task_runner,
      SandboxedUnpackerClient* client);

  SandboxedUnpacker(const SandboxedUnpacker&) = delete;
  SandboxedUnpacker& operator=(const SandboxedUnpacker&) = delete;

  // Start processing the extension, either from a CRX file or already unzipped
  // in a directory. The client is called with the results. The directory form
  // requires the id and base64-encoded public key (for insertion into the
  // 'key' field of the manifest.json file).
  void StartWithCrx(const CRXFileInfo& crx_info);
  void StartWithDirectory(const ExtensionId& extension_id,
                          const std::string& public_key_base64,
                          const base::FilePath& directory);

 private:
  friend class SandboxedUnpackerTest;
  class IOThreadState;

  ~SandboxedUnpacker() override;

  // Create `temp_dir_` used to unzip or unpack the extension in.
  bool CreateTempDirectory();

  // Helper functions to simplify calling ReportFailure.
  std::u16string FailureReasonToString16(
      const SandboxedUnpackerFailureReason reason);
  void FailWithPackageError(const SandboxedUnpackerFailureReason reason);

  // Validates the signature of the extension and extract the key to
  // `public_key_`. True if the signature validates, false otherwise.
  bool ValidateSignature(const base::FilePath& crx_path,
                         const std::string& expected_hash,
                         const crx_file::VerifierFormat required_format);

  // Unzips the extension into directory.
  void Unzip(const base::FilePath& crx_path,
             const base::FilePath& unzipped_dir);
  void UnzipDone(const base::FilePath& zip_file,
                 const base::FilePath& unzip_dir,
                 const std::string& error);

  // Callback which is called after the verified contents are uncompressed.
  void OnVerifiedContentsUncompressed(
      const base::FilePath& unzip_dir,
      base::expected<mojo_base::BigBuffer, std::string> result);

  // Verifies the decompressed verified contents fetched from the header of CRX
  // and stores them if the verification of these contents is successful.
  void StoreVerifiedContentsInExtensionDir(
      const base::FilePath& unzip_dir,
      base::span<const uint8_t> verified_contents,
      ContentVerifierKey content_verifier_key);

  // Unpacks the extension in directory and returns the manifest.
  void Unpack(const base::FilePath& directory);
  void ReadManifestDone(base::expected<base::Value, std::u16string> result);
  void UnpackExtensionSucceeded(base::DictValue manifest);

  // Helper which calls ReportFailure.
  void ReportUnpackExtensionFailed(const std::u16string& error);

  // Implementation of ImageSanitizer::Client:
  data_decoder::DataDecoder* GetDataDecoder() override;
  void OnImageSanitizationDone(ImageSanitizer::Status status,
                               const base::FilePath& path) override;
  void OnImageDecoded(const base::FilePath& path, SkBitmap image) override;

  void ReadMessageCatalogs();

  void SanitizeMessageCatalogs(
      const std::set<base::FilePath>& message_catalog_paths);

  void MessageCatalogsSanitized(
      base::expected<void, JsonFileSanitizer::Error> result);

  // Reports unpack success or failure, or unzip failure.
  void ReportSuccess();

  // Puts a sanboxed unpacker failure in histogram
  // Extensions.SandboxUnpackFailureReason.
  void ReportFailure(const SandboxedUnpackerFailureReason reason,
                     const std::u16string& error);

  // Overwrites original manifest with safe result from utility process.
  // Returns nullopt on error.
  std::optional<base::DictValue> RewriteManifestFile(
      const base::DictValue& manifest);

  // Cleans up temp directory artifacts.
  void Cleanup();

  // If a Declarative Net Request JSON ruleset is present, parses the JSON
  // rulesets for the Declarative Net Request API and persists the indexed
  // rulesets.
  void IndexAndPersistJSONRulesetsIfNeeded();

  void OnJSONRulesetsIndexed(RulesetParseResult result);

  // Computed hashes: if requested (via ShouldComputeHashes callback in
  // SandbloxedUnpackerClient), calculate hashes of all extensions' resources
  // and writes them in _metadata/computed_hashes.json. This is used by content
  // verification system for extensions outside of Chrome Web Store.
  void CheckComputeHashes();

  void MaybeComputeHashes(bool should_compute_hashes);

  // Parses the JSON file at `path` and invokes `ReadManifestDone()` with the
  // result.
  // This must be called from the `unpacker_io_task_runner_`.
  void ParseJsonFile(const base::FilePath& path);

  // If we unpacked a CRX file, we hold on to the path name for use
  // in various histograms.
  base::FilePath crx_path_for_histograms_;

  // Our unpacker client.
  scoped_refptr<SandboxedUnpackerClient> client_;

  // The Extensions directory inside the profile.
  base::FilePath extensions_dir_;

  // Temporary directory to use for unpacking.
  base::ScopedTempDir temp_dir_;

  // Root directory of the unpacked extension (a child of temp_dir_).
  base::FilePath extension_root_;

  // Parsed original manifest of the extension. Set after unpacking the
  // extension and working with its manifest, so after UnpackExtensionSucceeded
  // is called.
  std::optional<base::DictValue> manifest_;

  // Install prefs needed for the Declarative Net Request API.
  base::DictValue ruleset_install_prefs_;

  // Represents the extension we're unpacking.
  scoped_refptr<Extension> extension_;

  // The compressed verified contents extracted from the CRX header.
  std::vector<uint8_t> compressed_verified_contents_;

  // The public key that was extracted from the CRX header.
  std::string public_key_;

  // The extension's ID. This will be calculated from the public key
  // in the CRX header.
  ExtensionId extension_id_;

  // Location to use for the unpacked extension.
  mojom::ManifestLocation location_;

  // Creation flags to use for the extension. These flags will be used
  // when calling Extension::Create() by the CRX installer.
  int creation_flags_;

  // Overridden value of VerifierFormat that is used from StartWithCrx().
  std::optional<crx_file::VerifierFormat> format_verifier_override_;

  // Sequenced task runner where file I/O operations will be performed.
  scoped_refptr<base::SequencedTaskRunner> unpacker_io_task_runner_;

  // The normalized path of the install icon path, retrieved from the manifest.
  base::FilePath install_icon_path_;

  // The decoded install icon.
  SkBitmap install_icon_;

  // TODO(crbug.com/40232388): Consider to wrap it in base::SequenceBound
  std::unique_ptr<IOThreadState> io_thread_state_;
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_SANDBOXED_UNPACKER_H_
