// 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_CRX_INSTALLER_H_
#define EXTENSIONS_BROWSER_CRX_INSTALLER_H_

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/scoped_observation.h"
#include "base/values.h"
#include "base/version.h"
#include "components/sync/model/string_ordinal.h"
#include "extensions/browser/extension_install_prompt_client.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install_flag.h"
#include "extensions/browser/manifest_check_level.h"
#include "extensions/browser/preload_check.h"
#include "extensions/browser/sandboxed_unpacker.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class SkBitmap;

namespace base {
class SequencedTaskRunner;
}

namespace content {
class BrowserContext;
}

namespace extensions {
FORWARD_DECLARE_TEST(ExtensionServiceTest, InstallExtensionDuringShutdown);

class CrxInstallError;
class ExtensionRegistrar;
class ExtensionUpdaterTest;
struct InstallApproval;
enum class InstallationStage;
class MockCrxInstaller;
class PreloadCheckGroup;
class ScopedBrowserContextKeepAlive;

// This class installs a crx file into a browser context.
//
// Installing a CRX is a multi-step process, including unpacking the crx,
// validating it, prompting the user, and installing. Since many of these
// steps must occur on the file thread, this class contains a copy of all data
// necessary to do its job. (This also minimizes external dependencies for
// easier testing).
//
// Lifetime management:
//
// This class is ref-counted by each call it makes to itself on another thread,
// and by UtilityProcessHost.
//
// Additionally, we hold a reference to our own client so that it lives at least
// long enough to receive the result of unpacking.
//
// IMPORTANT: Callers should keep a reference to a CrxInstaller while they are
// working with it, eg:
//
// scoped_refptr<CrxInstaller> installer(new CrxInstaller(...));
// installer->set_foo();
// installer->set_bar();
// installer->InstallCrx(...);
//
// Installation is aborted if the CrxInstaller object learns that Chrome is
// terminating during the install.
class CrxInstaller : public SandboxedUnpackerClient {
 public:
  // A callback to be executed when the install finishes.
  using InstallerResultCallback = ExtensionSystem::InstallUpdateCallback;

  using ExpectationsVerifiedCallback = base::OnceClosure;

  // Used in histograms; do not change order.
  enum OffStoreInstallAllowReason {
    OffStoreInstallDisallowed,
    OffStoreInstallAllowedFromSettingsPage,
    OffStoreInstallAllowedBecausePref,
    OffStoreInstallAllowedInTest,
    NumOffStoreInstallAllowReasons
  };

  // Used to indicate if host permissions should be withheld during
  // installation.
  enum WithholdingBehavior { kWithholdPermissions, kDontWithholdPermissions };

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

  static void EnsureShutdownNotifierFactoryBuilt();

  // Extensions will be installed into the default install directory, then
  // registered with the extensions system. This does a silent install - see
  // below for other options.
  static scoped_refptr<CrxInstaller> CreateSilent(
      content::BrowserContext* context);

  // Same as above, but use `client` to generate a confirmation prompt.
  static scoped_refptr<CrxInstaller> Create(
      content::BrowserContext* context,
      std::unique_ptr<ExtensionInstallPromptClient> client);

  // Same as the previous method, except use the `approval` to bypass the
  // prompt. Note that the caller retains ownership of `approval`.
  static scoped_refptr<CrxInstaller> Create(
      content::BrowserContext* context,
      std::unique_ptr<ExtensionInstallPromptClient> client,
      const InstallApproval* approval);

  // Install the crx in `source_file`. The file must be a CRX3. A publisher
  // proof in the file is required unless off-webstore installation is allowed.
  void InstallCrx(const base::FilePath& source_file);

  // Install the crx in `source_file`.
  virtual void InstallCrxFile(const CRXFileInfo& source_file);

  // Install the unpacked crx in `unpacked_dir`.
  // If `delete_source_` is true, `unpacked_dir` will be removed at the end of
  // the installation.
  void InstallUnpackedCrx(const ExtensionId& extension_id,
                          const std::string& public_key,
                          const base::FilePath& unpacked_dir);

  // Update the extension `extension_id` with the unpacked crx in
  // `unpacked_dir`.
  // If `delete_source_` is true, `unpacked_dir` will be removed at the end of
  // the update.
  void UpdateExtensionFromUnpackedCrx(const ExtensionId& extension_id,
                                      const std::string& public_key,
                                      const base::FilePath& unpacked_dir);

  void OnInstallPromptDone(
      ExtensionInstallPromptClient::DoneCallbackPayload payload);

  void InitializeCreationFlagsForUpdate(const Extension* extension,
                                        const int initial_flags);

  // Adds a callback that will be run once the installation finishes
  // (successfully or not).
  // The added callbacks will be run in the order in which they were added
  // (FIFO).
  // Virtual for testing.
  virtual void AddInstallerCallback(InstallerResultCallback callback);

  int creation_flags() const { return creation_flags_; }
  void set_creation_flags(int val) { creation_flags_ = val; }

  const base::FilePath& source_file() const { return source_file_; }

  mojom::ManifestLocation install_source() const { return install_source_; }
  void set_install_source(mojom::ManifestLocation source) {
    install_source_ = source;
  }

  const ExtensionId& expected_id() const { return expected_id_; }
  void set_expected_id(const ExtensionId& val) { expected_id_ = val; }

  // Expected SHA256 hash sum for the package.
  const std::string& expected_hash() const { return expected_hash_; }
  void set_expected_hash(const std::string& val) { expected_hash_ = val; }

  // Set the exact version the installed extension should have. If
  // `fail_install_if_unexpected` is true, installation will fail if the actual
  // version doesn't match. If it is false, the installation will still
  // be performed, but the extension will not be granted any permissions.
  void set_expected_version(const base::Version& val,
                            bool fail_install_if_unexpected) {
    expected_version_ = val;
    fail_install_if_unexpected_version_ = fail_install_if_unexpected;
  }

  bool delete_source() const { return delete_source_; }
  void set_delete_source(bool val) { delete_source_ = val; }

  bool allow_silent_install() const { return allow_silent_install_; }
  void set_allow_silent_install(bool val) { allow_silent_install_ = val; }

  bool grant_permissions() const { return grant_permissions_; }
  void set_grant_permissions(bool val) { grant_permissions_ = val; }

  bool is_update() const { return is_update_; }
  void set_is_update(bool val) { is_update_ = val; }

  bool is_gallery_install() const {
    return (creation_flags_ & Extension::FROM_WEBSTORE) > 0;
  }
  void set_is_gallery_install(bool val) {
    if (val) {
      creation_flags_ |= Extension::FROM_WEBSTORE;
    } else {
      creation_flags_ &= ~Extension::FROM_WEBSTORE;
    }
  }
  void set_withhold_permissions();

  // If `apps_require_extension_mime_type_` is set to true, be sure to set
  // `original_mime_type_` as well.
  void set_apps_require_extension_mime_type(
      bool apps_require_extension_mime_type) {
    apps_require_extension_mime_type_ = apps_require_extension_mime_type;
  }

  void set_original_mime_type(const std::string& original_mime_type) {
    original_mime_type_ = original_mime_type;
  }

  bool was_triggered_by_user_download() const {
    return was_triggered_by_user_download_;
  }
  void set_was_triggered_by_user_download() {
    was_triggered_by_user_download_ = true;
  }

  OffStoreInstallAllowReason off_store_install_allow_reason() const {
    return off_store_install_allow_reason_;
  }
  void set_off_store_install_allow_reason(OffStoreInstallAllowReason reason) {
    off_store_install_allow_reason_ = reason;
  }

  void set_page_ordinal(const syncer::StringOrdinal& page_ordinal) {
    page_ordinal_ = page_ordinal;
  }

  void set_error_on_unsupported_requirements(bool val) {
    error_on_unsupported_requirements_ = val;
  }

  void set_install_immediately(bool val) {
    set_install_flag(kInstallFlagInstallImmediately, val);
  }
  void set_do_not_sync(bool val) {
    set_install_flag(kInstallFlagDoNotSync, val);
  }
  void set_bypassed_safebrowsing_friction_for_testing(bool val) {
    set_install_flag(kInstallFlagBypassedSafeBrowsingFriction, val);
  }

  // Callback to be invoked when the crx file has passed the expectations check
  // after unpack success and the ownership of the crx file lies with the
  // installer. The callback is passed the ownership of the crx file.
  void set_expectations_verified_callback(
      ExpectationsVerifiedCallback callback);

  bool did_handle_successfully() const { return did_handle_successfully_; }

  content::BrowserContext* browser_context() { return browser_context_; }

  const Extension* extension() const { return extension_.get(); }

  // The currently installed version of the extension, for updates. Will be
  // invalid if this isn't an update.
  const base::Version& current_version() const { return current_version_; }

 protected:
  // Run all callbacks received in AddInstallerCallback with the given error.
  // Protected so that FakeCrxInstaller can expose it.
  void RunInstallerCallbacks(const std::optional<CrxInstallError>& error);

 private:
  FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
                           InstallExtensionDuringShutdown);
  friend class BookmarkAppInstallFinalizerTest;
  friend class ExtensionUpdaterTest;
  friend class FakeCrxInstaller;
  friend class MockCrxInstaller;

  CrxInstaller(content::BrowserContext* context,
               std::unique_ptr<ExtensionInstallPromptClient> client,
               const InstallApproval* approval);
  ~CrxInstaller() override;

  // Called after OnUnpackSuccess check to see whether the install expectations
  // are met and the install process should continue.
  std::optional<CrxInstallError> CheckExpectations(const Extension* extension);

  // Called after OnUnpackSuccess as a last check to see whether the install
  // should complete.
  std::optional<CrxInstallError> AllowInstall(const Extension* extension);

  // To check whether we need to compute hashes or not, we have to make a query
  // to ContentVerifier, and that should be done on the UI thread.
  void ShouldComputeHashesOnUI(scoped_refptr<const Extension> extension,
                               base::OnceCallback<void(bool)> callback);

  // To provide content verifier key to the unpacker.
  void GetContentVerifierKeyOnUI(
      base::OnceCallback<void(ContentVerifierKey)> callback);

  // SandboxedUnpackerClient
  void GetContentVerifierKey(
      base::OnceCallback<void(ContentVerifierKey)> callback) override;
  void ShouldComputeHashesForOffWebstoreExtension(
      scoped_refptr<const Extension> extension,
      base::OnceCallback<void(bool)> callback) override;
  void OnUnpackFailure(const CrxInstallError& error) override;
  void OnUnpackSuccess(const base::FilePath& temp_dir,
                       const base::FilePath& extension_dir,
                       std::unique_ptr<base::DictValue> original_manifest,
                       const Extension* extension,
                       const SkBitmap& install_icon,
                       base::DictValue ruleset_install_prefs) override;
  void OnStageChanged(InstallationStage stage) override;

  // Called on BrowserContext shutdown.
  void Shutdown();

  // Called on the UI thread to start the requirements, policy and blocklist
  // checks on the extension.
  void CheckInstall();

  // Runs on the UI thread. Callback from PreloadCheckGroup.
  void OnInstallChecksComplete(const PreloadCheck::Errors& errors);

  // Runs on the UI thread. Confirms the installation to the ExtensionService.
  void ConfirmInstall();

  // Runs on the UI thread. Updates the creation flags for the extension and
  // calls CompleteInstall().
  void UpdateCreationFlagsAndCompleteInstall(
      WithholdingBehavior withholding_behavior);

  // Runs on File thread. Install the unpacked extension into the browser
  // context and notify the frontend.
  void CompleteInstall(bool updates_from_webstore);

  // Reloads extension on File thread and reports installation result back
  // to UI thread.
  void ReloadExtensionAfterInstall(const base::FilePath& version_dir);

  // Result reporting.
  void ReportFailureFromSharedFileThread(const CrxInstallError& error);
  void ReportFailureFromUIThread(const CrxInstallError& error);
  void ReportSuccessFromSharedFileThread();
  void ReportSuccessFromUIThread();
  // Always report from the UI thread.
  void ReportInstallationStage(InstallationStage stage);
  // Returns false if it cannot acquire resources.
  bool AcquireKeepAlive();
  void NotifyCrxInstallBegin();
  void NotifyCrxInstallComplete(const std::optional<CrxInstallError>& error);

  // Deletes temporary directory and crx file if needed.
  void CleanupTempFiles();

  // Checks whether the current installation is initiated by the user from
  // the extension settings page to update an existing extension or app.
  void CheckUpdateFromSettingsPage();

  // Show re-enable prompt if the update is initiated from the settings page
  // and needs additional permissions.
  void ConfirmReEnable();

  // OnUnpackSuccess() gets called on the unpacker sequence. It calls this
  // method on the shared file sequence, to avoid race conditions.
  virtual void OnUnpackSuccessOnSharedFileThread(
      base::FilePath temp_dir,
      base::FilePath extension_dir,
      std::unique_ptr<base::DictValue> original_manifest,
      scoped_refptr<const Extension> extension,
      SkBitmap install_icon,
      base::DictValue ruleset_install_prefs);

  void set_install_flag(int flag, bool val) {
    if (val) {
      install_flags_ |= flag;
    } else {
      install_flags_ &= ~flag;
    }
  }

  // Returns `unpacker_task_runner_`. Initializes it if it's still nullptr.
  base::SequencedTaskRunner* GetUnpackerTaskRunner();

  // Called when the browser is terminating.
  void OnBrowserTerminating();

  // Get the effective update URL for the extension. Normally this URL comes
  // from the extension manifest, but may be overridden by policies.
  GURL GetEffectiveUpdateURL(const Extension& extension);

  // Returns true if this extension's update URL is from webstore, including any
  // policy overrides.
  bool UpdatesFromWebstore(const Extension& extension);

  // The BrowserContext the extension is being installed in.
  raw_ptr<content::BrowserContext> browser_context_;

  // Prevent Profile destruction until the CrxInstaller is done.
  std::unique_ptr<ScopedBrowserContextKeepAlive> profile_keep_alive_;

  // Cached for convenience.
  raw_ptr<ExtensionRegistrar> registrar_;

  // The extension being installed.
  scoped_refptr<const Extension> extension_;

  // The file we're installing.
  base::FilePath source_file_;

  // The URL the file was downloaded from.
  GURL download_url_;

  // The directory extensions are installed to.
  const base::FilePath install_directory_;

  // The location the installation came from (bundled with Chromium, registry,
  // manual install, etc). This metadata is saved with the installation if
  // successful. Defaults to INTERNAL.
  mojom::ManifestLocation install_source_;

  // Indicates whether the user has already approved the extension to be
  // installed. If true, `expected_manifest_` and `expected_id_` must match
  // those of the CRX.
  bool approved_;

  // For updates, external and webstore installs we have an ID we're expecting
  // the extension to contain.
  ExtensionId expected_id_;

  // An expected hash sum for the .crx file.
  std::string expected_hash_;

  // A copy of the expected manifest, before any transformations like
  // localization have taken place. If `approved_` is true, then the extension's
  // manifest must match this for the install to proceed.
  std::unique_ptr<base::DictValue> expected_manifest_;

  // The level of checking when comparing the actual manifest against
  // the `expected_manifest_`.
  ManifestCheckLevel expected_manifest_check_level_ =
      ManifestCheckLevel::kStrict;

  // If valid, specifies the minimum version we'll install. Installation will
  // fail if the actual version is smaller.
  base::Version minimum_version_;

  // If valid, contains the expected version of the extension we're installing.
  // Important for external sources, where claiming the wrong version could
  // cause unnecessary unpacking of an extension at every restart.
  // See also `fail_install_if_unexpected_version_`!
  base::Version expected_version_;

  // If true, installation will fail if the actual version doesn't match
  // `expected_version_`. If false, the extension will still be installed, but
  // not granted any permissions.
  bool fail_install_if_unexpected_version_;

  // Whether manual extension installation is enabled. We can't just check this
  // before trying to install because themes and bookmark apps are special-cased
  // to always be allowed.
  bool extensions_enabled_;

  // Whether we're supposed to delete the source file on destruction. Defaults
  // to false.
  bool delete_source_;

  // The ordinal of the NTP apps page `extension_` will be shown on.
  syncer::StringOrdinal page_ordinal_;

  // A copy of the unmodified original manifest, before any transformations like
  // localization have taken place.
  std::unique_ptr<base::DictValue> original_manifest_;

  // If valid, contains the current version of the extension we're
  // installing (for upgrades).
  base::Version current_version_;

  // The icon we will display in the installation UI, if any.
  std::unique_ptr<SkBitmap> install_icon_;

  // The temp directory extension resources were unpacked to. We own this and
  // must delete it when we are done with it.
  base::FilePath temp_dir_;

  // The client we will work with to do the installation. This can be NULL, in
  // which case the install is silent.
  std::unique_ptr<ExtensionInstallPromptClient> client_;

  // The root of the unpacked extension directory. This is a subdirectory of
  // temp_dir_, so we don't have to delete it explicitly.
  base::FilePath unpacked_extension_root_;

  // True when the CRX being installed was just downloaded.
  // Used to trigger extra checks before installing.
  bool apps_require_extension_mime_type_;

  // Allows for the possibility of a normal install (one in which a `client`
  // is provided in the ctor) to proceed without showing the permissions prompt
  // dialog.
  bool allow_silent_install_;

  // Allows for the possibility of an installation without granting any
  // permissions to the extension.
  bool grant_permissions_;

  // The value of the content type header sent with the CRX.
  // Ignored unless `require_extension_mime_type_` is true.
  std::string original_mime_type_;

  // Was the extension install initiated by a user downloading the extension?
  bool was_triggered_by_user_download_ = false;

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

  // Whether to allow off store installation.
  OffStoreInstallAllowReason off_store_install_allow_reason_;

  // Whether the installation was handled successfully. This is used to
  // indicate to the client whether the file should be removed and any UI
  // initiating the installation can be removed. This is different than whether
  // there was an error; if there was an error that rejects installation we
  // still consider the installation 'handled'.
  bool did_handle_successfully_;

  // Whether we should produce an error if the manifest declares requirements
  // that are not met. If false and there is an unmet requirement, the install
  // will continue but the extension will be disabled.
  bool error_on_unsupported_requirements_;

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

  // Sequenced task runner where the SandboxedUnpacker will run. Because the
  // unpacker uses its own temp dir, it won't hit race conditions, and can use a
  // separate task runner per instance (for better performance).
  //
  // Lazily initialized by GetUnpackerTaskRunner().
  scoped_refptr<base::SequencedTaskRunner> unpacker_task_runner_;

  // Whether the update is initiated by the user from the extension settings
  // page.
  bool update_from_settings_page_;

  // Whether the operation is an extension update.
  bool is_update_ = false;

  // The flags for ExtensionService::OnExtensionInstalled.
  int install_flags_;

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

  // Checks that may run before installing the extension.
  std::unique_ptr<PreloadCheck> policy_check_;
  std::unique_ptr<PreloadCheck> requirements_check_;
  std::unique_ptr<PreloadCheck> blocklist_check_;

  // Runs the above checks.
  std::unique_ptr<PreloadCheckGroup> check_group_;

  // Invoked when the install is completed.
  std::vector<InstallerResultCallback> installer_callbacks_;

  // Invoked when the expectations from CRXFileInfo match with the crx file
  // after unpack success.
  ExpectationsVerifiedCallback expectations_verified_callback_;

  // Subscription for a callback that runs when the BrowserContext is
  // destroyed.
  base::CallbackListSubscription browser_context_shutdown_subscription_;
};

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_CRX_INSTALLER_H_
