// 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 CHROME_BROWSER_EXTENSIONS_API_DOWNLOADS_DOWNLOADS_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_DOWNLOADS_DOWNLOADS_API_H_

#include <deque>
#include <memory>
#include <set>
#include <string>

#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/common/extensions/api/downloads.h"
#include "components/download/content/public/all_download_item_notifier.h"
#include "components/download/public/common/download_path_reservation_tracker.h"
#include "content/public/browser/download_manager.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_observer.h"
#include "extensions/browser/warning_set.h"
#include "extensions/buildflags/buildflags.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

class DownloadFileIconExtractor;
class Profile;

// Functions in the chrome.downloads namespace facilitate
// controlling downloads from extensions. See the full API doc at
// https://docs.google.com/document/d/12rNimeeGaA8jEV60PPKtT4pmJYmY9ae_edl3hJyoXYE/

namespace extensions {

class DownloadedByExtension : public base::SupportsUserData::Data {
 public:
  static DownloadedByExtension* Get(download::DownloadItem* item);

  DownloadedByExtension(download::DownloadItem* item,
                        const std::string& id,
                        const std::string& name);

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

  const std::string& id() const { return id_; }
  const std::string& name() const { return name_; }

 private:
  static const char kKey[];

  std::string id_;
  std::string name_;
};

class DownloadsFunction : public ExtensionFunction {
 public:
  DownloadsFunction();

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

  // ExtensionFunction:
  ResponseAction Run() override;
  void ExecuteFromQueue();
  void OnManagerGoingDown();

 protected:
  ~DownloadsFunction() override;

  // Subclasses should implement this instead of Run().
  virtual ResponseAction RunInternal() = 0;
};

class DownloadsDownloadFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.download", DOWNLOADS_DOWNLOAD)
  DownloadsDownloadFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsDownloadFunction() override;

 private:
  void OnStarted(const base::FilePath& creator_suggested_filename,
                 extensions::api::downloads::FilenameConflictAction
                     creator_conflict_action,
                 download::DownloadItem* item,
                 download::DownloadInterruptReason interrupt_reason);
};

class DownloadsSearchFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.search", DOWNLOADS_SEARCH)
  DownloadsSearchFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsSearchFunction() override;
};

class DownloadsPauseFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.pause", DOWNLOADS_PAUSE)
  DownloadsPauseFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsPauseFunction() override;
};

class DownloadsResumeFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.resume", DOWNLOADS_RESUME)
  DownloadsResumeFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsResumeFunction() override;
};

class DownloadsCancelFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.cancel", DOWNLOADS_CANCEL)
  DownloadsCancelFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsCancelFunction() override;
};

class DownloadsEraseFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.erase", DOWNLOADS_ERASE)
  DownloadsEraseFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsEraseFunction() override;
};

class DownloadsRemoveFileFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.removeFile", DOWNLOADS_REMOVEFILE)
  DownloadsRemoveFileFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsRemoveFileFunction() override;

 private:
  void Done(bool success);
};

class DownloadsAcceptDangerFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.acceptDanger", DOWNLOADS_ACCEPTDANGER)
  DownloadsAcceptDangerFunction();

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

  ResponseAction RunInternal() override;

  // Sets the action to take when a danger prompt is shown in tests. The
  // returned AutoReset restores the previous value, which is an empty
  // optional if it hasn't been set.
  [[nodiscard]] static base::AutoReset<
      std::optional<DownloadDangerPrompt::Action>>
  TriggerDangerPromptActionForTesting(DownloadDangerPrompt::Action action);

 protected:
  ~DownloadsAcceptDangerFunction() override;
  void DangerPromptCallback(int download_id,
                            DownloadDangerPrompt::Action action);

 private:
  void PromptOrWait(int download_id, int retries);
};

class DownloadsShowFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.show", DOWNLOADS_SHOW)
  DownloadsShowFunction();

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

  ResponseAction RunInternal() override;

 protected:
  ~DownloadsShowFunction() override;
};

class DownloadsShowDefaultFolderFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION(
      "downloads.showDefaultFolder", DOWNLOADS_SHOWDEFAULTFOLDER)
  DownloadsShowDefaultFolderFunction();

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

  ResponseAction Run() override;

 protected:
  ~DownloadsShowDefaultFolderFunction() override;
};

class DownloadsOpenFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.open", DOWNLOADS_OPEN)
  DownloadsOpenFunction();

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

  ResponseAction RunInternal() override;

  [[nodiscard]] static base::AutoReset<bool> AcceptDialogForTesting();

 protected:
  ~DownloadsOpenFunction() override;

 private:
  void OpenPromptDone(int download_id, bool accept);
};

// TODO(crbug.com/40858206): Remove this deprecated function.
class DownloadsSetShelfEnabledFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.setShelfEnabled",
                             DOWNLOADS_SETSHELFENABLED)
  DownloadsSetShelfEnabledFunction();

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

  ResponseAction Run() override;

 protected:
  ~DownloadsSetShelfEnabledFunction() override;
};

class DownloadsSetUiOptionsFunction : public ExtensionFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.setUiOptions", DOWNLOADS_SETUIOPTIONS)
  DownloadsSetUiOptionsFunction();

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

  ResponseAction Run() override;

 protected:
  ~DownloadsSetUiOptionsFunction() override;
};

class DownloadsGetFileIconFunction : public DownloadsFunction {
 public:
  DECLARE_EXTENSION_FUNCTION("downloads.getFileIcon", DOWNLOADS_GETFILEICON)
  DownloadsGetFileIconFunction();

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

  ResponseAction RunInternal() override;
  void SetIconExtractorForTesting(DownloadFileIconExtractor* extractor);

 protected:
  ~DownloadsGetFileIconFunction() override;

 private:
  void OnIconURLExtracted(const std::string& url);
  base::FilePath path_;
  std::unique_ptr<DownloadFileIconExtractor> icon_extractor_;
};

// Observes a single DownloadManager and many DownloadItems and dispatches
// onCreated and onErased events.
class ExtensionDownloadsEventRouter
    : public extensions::EventRouter::Observer,
      public extensions::ExtensionRegistryObserver,
      public download::AllDownloadItemNotifier::Observer {
 public:
  typedef base::OnceCallback<void(
      const base::FilePath& changed_filename,
      download::DownloadPathReservationTracker::FilenameConflictAction)>
      FilenameChangedCallback;

  static void SetDetermineFilenameTimeoutForTesting(base::TimeDelta timeout);

  // The logic for how to handle conflicting filename suggestions from multiple
  // extensions is split out here for testing.
  static void DetermineFilenameInternal(
      const base::FilePath& filename,
      extensions::api::downloads::FilenameConflictAction conflict_action,
      const std::string& suggesting_extension_id,
      const base::Time& suggesting_install_time,
      const std::string& incumbent_extension_id,
      const base::Time& incumbent_install_time,
      std::string* winner_extension_id,
      base::FilePath* determined_filename,
      extensions::api::downloads::FilenameConflictAction*
        determined_conflict_action,
      extensions::WarningSet* warnings);

  // A downloads.onDeterminingFilename listener has returned. If the extension
  // wishes to override the download's filename, then |filename| will be
  // non-empty. |filename| will be interpreted as a relative path, appended to
  // the default downloads directory. If the extension wishes to overwrite any
  // existing files, then |overwrite| will be true. Returns true on success,
  // false otherwise.
  static bool DetermineFilename(
      content::BrowserContext* browser_context,
      bool include_incognito,
      const std::string& ext_id,
      int download_id,
      const base::FilePath& filename,
      extensions::api::downloads::FilenameConflictAction conflict_action,
      std::string* error);

  explicit ExtensionDownloadsEventRouter(
      Profile* profile, content::DownloadManager* manager);

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

  void QueuePendingFunction(scoped_refptr<DownloadsFunction> function);

  ~ExtensionDownloadsEventRouter() override;

  void SetUiEnabled(const extensions::Extension* extension, bool enabled);
  bool IsUiEnabled() const;

  // Called by ChromeDownloadManagerDelegate during the filename determination
  // process, allows extensions to change the item's target filename. If no
  // extension wants to change the target filename, then |filename_changed| will
  // be called with an empty filename and the filename determination process
  // will continue as normal. If an extension wants to change the target
  // filename, then |filename_changed| will be called with the new filename and
  // a flag indicating whether the new file should overwrite any old files of
  // the same name.
  void OnDeterminingFilename(download::DownloadItem* item,
                             const base::FilePath& suggested_path,
                             FilenameChangedCallback filename_changed);

  // AllDownloadItemNotifier::Observer.
  void OnDownloadCreated(content::DownloadManager* manager,
                         download::DownloadItem* download_item) override;
  void OnDownloadUpdated(content::DownloadManager* manager,
                         download::DownloadItem* download_item) override;
  void OnDownloadRemoved(content::DownloadManager* manager,
                         download::DownloadItem* download_item) override;
  void OnManagerInitialized(content::DownloadManager* manager) override;
  void OnManagerGoingDown(content::DownloadManager* manager) override;

  // extensions::EventRouter::Observer.
  void OnListenerRemoved(const extensions::EventListenerInfo& details) override;

  void CheckForHistoryFilesRemoval();

 private:
  void DispatchEvent(events::HistogramValue histogram_value,
                     const std::string& event_name,
                     bool include_incognito,
                     Event::WillDispatchCallback will_dispatch_callback,
                     base::Value json_arg);

  // extensions::ExtensionRegistryObserver.
  void OnExtensionUnloaded(content::BrowserContext* browser_context,
                           const extensions::Extension* extension,
                           extensions::UnloadedExtensionReason reason) override;

  std::deque<scoped_refptr<DownloadsFunction>> pending_functions_;
  raw_ptr<Profile> profile_;
  download::AllDownloadItemNotifier notifier_;
  std::set<raw_ptr<const extensions::Extension, SetExperimental>>
      ui_disabling_extensions_;

  base::Time last_checked_removal_;

  // Listen to extension unloaded notifications.
  base::ScopedObservation<extensions::ExtensionRegistry,
                          extensions::ExtensionRegistryObserver>
      extension_registry_observation_{this};
};

}  // namespace extensions

#endif  // CHROME_BROWSER_EXTENSIONS_API_DOWNLOADS_DOWNLOADS_API_H_
