// 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_DOWNLOAD_CHROME_DOWNLOAD_MANAGER_DELEGATE_H_
#define CHROME_BROWSER_DOWNLOAD_CHROME_DOWNLOAD_MANAGER_DELEGATE_H_

#include <stdint.h>

#include <deque>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "chrome/browser/download/download_completion_blocker.h"
#include "chrome/browser/download/download_target_determiner_delegate.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_item.h"
#include "components/download/public/common/download_path_reservation_tracker.h"
#include "components/download/public/common/download_target_info.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/safe_browsing/buildflags.h"
#include "components/safe_browsing/content/common/proto/download_file_types.pb.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_manager_delegate.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/shell_dialogs/selected_file_info.h"

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/download/android/download_dialog_bridge.h"  // nogncheck crbug.com/40147906
#include "chrome/browser/download/android/download_message_bridge.h"  // nogncheck crbug.com/40147906
#endif

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) || BUILDFLAG(IS_ANDROID)
#include "base/types/expected.h"
#endif

#if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
#include "chrome/browser/download/download_completion_blocker.h"
#endif

class DownloadPrefs;
class Profile;

namespace content {
class DownloadManager;
}

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
namespace extensions {
class CrxInstaller;
class CrxInstallError;
}
#endif

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) || BUILDFLAG(IS_ANDROID)
namespace enterprise_obfuscation {
enum class Error;
}
#endif

#if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
namespace safe_browsing {
class DownloadProtectionService;
enum class DownloadCheckResult;
}
#endif

// This is the Chrome side helper for the download system.
class ChromeDownloadManagerDelegate
    : public content::DownloadManagerDelegate,
      public DownloadTargetDeterminerDelegate,
      public content::DownloadManager::Observer {
 public:
  explicit ChromeDownloadManagerDelegate(Profile* profile);

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

  ~ChromeDownloadManagerDelegate() override;

  // Should be called before the first call to ShouldCompleteDownload() to
  // disable SafeBrowsing checks for |item|.
  static void DisableSafeBrowsing(download::DownloadItem* item);

  // True when |danger_type| is one that is blocked for policy reasons (e.g.
  // "file too large") as opposed to malicious content reasons.
  static bool IsDangerTypeBlocked(download::DownloadDangerType danger_type);

  void SetDownloadManager(content::DownloadManager* dm);

#if BUILDFLAG(IS_ANDROID)
  void ShowDownloadDialog(gfx::NativeWindow native_window,
                          int64_t total_bytes,
                          DownloadLocationDialogType dialog_type,
                          const base::FilePath& suggested_path,
                          DownloadDialogBridge::DialogCallback callback);

  void SetDownloadDialogBridgeForTesting(DownloadDialogBridge* bridge);
  void SetDownloadMessageBridgeForTesting(DownloadMessageBridge* bridge);
#endif

  // Callbacks passed to GetNextId() will not be called until the returned
  // callback is called.
  content::DownloadIdCallback GetDownloadIdReceiverCallback();

  // content::DownloadManagerDelegate
  void Shutdown() override;
  void OnDownloadCanceledAtShutdown(download::DownloadItem* item) override;
  void GetNextId(content::DownloadIdCallback callback) override;
  bool DetermineDownloadTarget(
      download::DownloadItem* item,
      download::DownloadTargetCallback* callback) override;
  bool ShouldAutomaticallyOpenFile(const GURL& url,
                                   const base::FilePath& path) override;
  bool ShouldAutomaticallyOpenFileByPolicy(const GURL& url,
                                           const base::FilePath& path) override;
  bool ShouldCompleteDownload(download::DownloadItem* item,
                              base::OnceClosure complete_callback) override;
  bool ShouldOpenDownload(
      download::DownloadItem* item,
      content::DownloadOpenDelayedCallback callback) override;
  bool ShouldObfuscateDownload(download::DownloadItem* item) override;
  bool InterceptDownloadIfApplicable(
      const GURL& url,
      const std::string& user_agent,
      const std::string& content_disposition,
      const std::string& mime_type,
      const std::string& request_origin,
      int64_t content_length,
      bool is_transient,
      bool is_content_initiated,
      content::WebContents* web_contents) override;
  void GetSaveDir(content::BrowserContext* browser_context,
                  base::FilePath* website_save_dir,
                  base::FilePath* download_save_dir) override;
  void ChooseSavePath(content::WebContents* web_contents,
                      const base::FilePath& suggested_path,
                      const base::FilePath::StringType& default_extension,
                      bool can_save_as_complete,
                      content::SavePackagePathPickedCallback callback) override;
  void SanitizeSavePackageResourceName(base::FilePath* filename,
                                       const GURL& source_url) override;
  void SanitizeDownloadParameters(
      download::DownloadUrlParameters* params) override;
  void OpenDownload(download::DownloadItem* download) override;
  void ShowDownloadInShell(download::DownloadItem* download) override;
  std::string ApplicationClientIdForFileScanning() override;
  void CheckDownloadAllowed(
      const content::WebContents::Getter& web_contents_getter,
      const GURL& url,
      const std::string& request_method,
      std::optional<url::Origin> request_initiator,
      bool from_download_cross_origin_redirect,
      bool content_initiated,
      const std::string& mime_type,
      std::optional<ui::PageTransition> page_transition,
      content::CheckDownloadAllowedCallback check_download_allowed_cb) override;
  download::QuarantineConnectionCallback GetQuarantineConnectionCallback()
      override;
  std::unique_ptr<download::DownloadItemRenameHandler>
  GetRenameHandlerForDownload(download::DownloadItem* download_item) override;
  void CheckSavePackageAllowed(
      download::DownloadItem* download_item,
      base::flat_map<base::FilePath, base::FilePath> save_package_files,
      content::SavePackageAllowedCallback callback) override;
#if BUILDFLAG(IS_ANDROID)
  bool IsFromExternalApp(download::DownloadItem* item) override;
  bool ShouldOpenPdfInline() override;
  bool IsDownloadRestrictedByPolicy() override;
#else
  void AttachExtraInfo(download::DownloadItem* item) override;
#endif  // BUILDFLAG(IS_ANDROID)
  bool SupportsHistoryLoading() override;

  // Opens a download using the platform handler. DownloadItem::OpenDownload,
  // which ends up being handled by OpenDownload(), will open a download in the
  // browser if doing so is preferred.
  void OpenDownloadUsingPlatformHandler(download::DownloadItem* download);

  DownloadPrefs* download_prefs() { return download_prefs_.get(); }

#if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
  // The state of a safebrowsing check.
  class SafeBrowsingState : public DownloadCompletionBlocker {
   public:
    SafeBrowsingState() = default;

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

    ~SafeBrowsingState() override;

    // String pointer used for identifying safebrowsing data associated with
    // a download item.
    static const char kSafeBrowsingUserDataKey[];
  };

  // Callback function after the DownloadProtectionService completes.
  void CheckClientDownloadDone(uint32_t download_id,
                               safe_browsing::DownloadCheckResult result);

  // Callback function after scanning completes for a save package.
  void CheckSavePackageScanningDone(uint32_t download_id,
                                    safe_browsing::DownloadCheckResult result);
#endif  // SAFE_BROWSING_DOWNLOAD_PROTECTION

  base::WeakPtr<ChromeDownloadManagerDelegate> GetWeakPtr();

  static void ConnectToQuarantineService(
      mojo::PendingReceiver<quarantine::mojom::Quarantine> receiver);

  // Return true if the downloaded file should be blocked based on the current
  // download restriction pref, the file type, and |danger_type|.
  bool ShouldBlockFile(download::DownloadItem* item,
                       download::DownloadDangerType danger_type) const;

  // Schedules an ephemeral warning download to be canceled. It will only be
  // canceled if it continues to be an ephemeral warning that hasn't been acted
  // on when the scheduled time arrives. This should be scheduled after the user
  // sees the warning in the UI, i.e. should not be scheduled before the user
  // has a chance to see the warning.
  // Note: Any scheduled cancellations will be abandoned at browser shutdown,
  // but the cancellation should occur when the browser next starts up, in
  // CancelAllEphemeralWarnings().
  void ScheduleCancelForEphemeralWarning(const std::string& guid);

  // Returns true if |path| should open in the browser.
  virtual bool IsOpenInBrowserPreferredForFile(const base::FilePath& path);

 protected:
#if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
  virtual safe_browsing::DownloadProtectionService*
      GetDownloadProtectionService();
#endif

  // Show file picker for |download|.
  virtual void ShowFilePickerForDownload(
      download::DownloadItem* download,
      const base::FilePath& suggested_path,
      DownloadTargetDeterminerDelegate::ConfirmationCallback callback);

  // DownloadTargetDeterminerDelegate. Protected for testing.
  void GetInsecureDownloadStatus(
      download::DownloadItem* download,
      const base::FilePath& virtual_path,
      GetInsecureDownloadStatusCallback callback) override;
  void NotifyExtensions(download::DownloadItem* download,
                        const base::FilePath& suggested_virtual_path,
                        NotifyExtensionsCallback callback) override;
  void ReserveVirtualPath(
      download::DownloadItem* download,
      const base::FilePath& virtual_path,
      bool create_directory,
      download::DownloadPathReservationTracker::FilenameConflictAction
          conflict_action,
      const base::FilePath& containment_directory,
      ReservedPathCallback callback) override;
#if BUILDFLAG(IS_ANDROID)
  void RequestIncognitoWarningConfirmation(
      content::WebContents* web_contents,
      IncognitoWarningConfirmationCallback) override;
#endif
  void RequestConfirmation(download::DownloadItem* download,
                           const base::FilePath& suggested_virtual_path,
                           DownloadConfirmationReason reason,
                           ConfirmationCallback callback) override;
  void DetermineLocalPath(download::DownloadItem* download,
                          const base::FilePath& virtual_path,
                          download::LocalPathCallback callback) override;
  void CheckDownloadUrl(download::DownloadItem* download,
                        const base::FilePath& suggested_virtual_path,
                        CheckDownloadUrlCallback callback) override;
  void GetFileMimeType(const base::FilePath& path,
                       GetFileMimeTypeCallback callback) override;

#if BUILDFLAG(IS_ANDROID)
  virtual void OnDownloadCanceled(download::DownloadItem* download,
                                  bool has_no_external_storage);
#endif

  // Called when the file picker returns the confirmation result.
  void OnConfirmationCallbackComplete(
      DownloadTargetDeterminerDelegate::ConfirmationCallback callback,
      DownloadConfirmationResult result,
      const ui::SelectedFileInfo& selected_file_info);

  // So that test classes that inherit from this for override purposes
  // can call back into the DownloadManager.
  raw_ptr<content::DownloadManager> download_manager_ = nullptr;

 private:
  friend class base::RefCountedThreadSafe<ChromeDownloadManagerDelegate>;
  FRIEND_TEST_ALL_PREFIXES(ChromeDownloadManagerDelegateTest,
                           RequestConfirmation_Android);
  FRIEND_TEST_ALL_PREFIXES(ChromeDownloadManagerDelegateTest,
                           CancelAllEphemeralWarnings);

  using IdCallbackVector = std::vector<content::DownloadIdCallback>;

  // Called to show a file picker for download with |guid|
  void ShowFilePicker(
      const std::string& guid,
      const base::FilePath& suggested_path,
      DownloadTargetDeterminerDelegate::ConfirmationCallback callback);

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // Called when CrxInstaller in running_crx_installs_ finishes installation.
  void OnInstallerDone(const base::UnguessableToken& token,
                       content::DownloadOpenDelayedCallback callback,
                       const std::optional<extensions::CrxInstallError>& error);
#endif

  // Internal gateways for ShouldCompleteDownload().
  bool IsDownloadReadyForCompletion(
      download::DownloadItem* item,
      base::OnceClosure internal_complete_callback);
  void ShouldCompleteDownloadInternal(uint32_t download_id,
                                      base::OnceClosure user_complete_callback);

#if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) || BUILDFLAG(IS_ANDROID)
  // Called when obfuscated download files are deobfuscated.
  void OnDeobfuscationComplete(
      uint32_t download_id,
      base::OnceClosure callback,
      base::expected<void, enterprise_obfuscation::Error> deobfuscation_result);
#endif

  // Sets the next download id based on download database records, and runs all
  // cached id callbacks.
  void SetNextId(uint32_t id);

  // Runs the |callback| with next id. Results in the download being started.
  void ReturnNextId(content::DownloadIdCallback callback);

  void OnDownloadTargetDetermined(
      uint32_t download_id,
      download::DownloadTargetCallback callback,
      download::DownloadTargetInfo target_info,
      safe_browsing::DownloadFileType::DangerLevel danger_level);

  // Sends a download report when the dangerous download is opened. This action
  // can be performed multiple times after the warning is bypassed, so this
  // report can be sent multiple times for a single download.
  void MaybeSendDangerousDownloadOpenedReport(download::DownloadItem* download,
                                              bool show_download_in_folder);

  // Sends a download report when the dangerous download is canceled
  // automatically. This code path is NOT triggered if a user explicitly
  // canceled the download.
  void MaybeSendDangerousDownloadCanceledReport(
      download::DownloadItem* download,
      bool is_shutdown);

  void OnCheckDownloadAllowedComplete(
      content::CheckDownloadAllowedCallback check_download_allowed_cb,
      bool storage_permission_granted,
      bool allow);

  // Returns whether this is the most recent download in the rare event where
  // multiple downloads are associated with the same file path.
  bool IsMostRecentDownloadItemAtFilePath(download::DownloadItem* download);

  // Cancels a download if it's still an ephemeral warning (and has not been
  // acted on by the user).
  void CancelForEphemeralWarning(const std::string& guid);
  // If the browser doesn't shut down cleanly, there can be ephemeral warnings
  // that were not cleaned up. This function cleans them up on startup, when the
  // download manager is initialized.
  void CancelAllEphemeralWarnings();

  // content::DownloadManager::Observer
  void OnManagerInitialized() override;

#if BUILDFLAG(IS_ANDROID)
  // Called after a unique file name is generated in the case that there is a
  // TARGET_CONFLICT and the new file name should be displayed to the user.
  void GenerateUniqueFileNameDone(
      const std::string& download_guid,
      DownloadTargetDeterminerDelegate::ConfirmationCallback callback,
      download::PathValidationResult result,
      const base::FilePath& target_path);
  // Return true if the mime type is pdf and Chrome supports open this pdf.
  bool IsPdfAndSupported(const std::string& mime_type,
                         content::WebContents* web_contents);

  // Called after user interacted on the incognito download confirmation message
  // before proceeding to save a package.
  void RequestIncognitoSavePackageConfirmationDone(
      content::WebContents* web_contents,
      const base::FilePath& suggested_path,
      content::SavePackagePathPickedCallback callback,
      bool accept);

  void OnDetermineSavePackagePathDone(
      base::WeakPtr<content::WebContents> web_contents,
      content::SavePackagePathPickedCallback callback,
      const base::FilePath& file_path,
      const base::FilePath& display_name);
#endif

#if BUILDFLAG(IS_CHROMEOS)
  // Called after attempting to obtain a temporary directory for deobfuscating
  // virtual files.
  void OnCreateDeobfuscationTempFile(uint32_t download_id,
                                     const base::FilePath& virtual_path,
                                     download::LocalPathCallback callback,
                                     base::FilePath temp_file_path);
#endif

  raw_ptr<Profile, DanglingUntriaged> profile_;

#if BUILDFLAG(IS_ANDROID)
  std::unique_ptr<DownloadDialogBridge> download_dialog_bridge_;
  std::unique_ptr<DownloadMessageBridge> download_message_bridge_;
#endif

  // If history database fails to initialize, this will always be kInvalidId.
  // Otherwise, the first available download id is assigned from history
  // database, and incremented by one for each download.
  uint32_t next_download_id_ = download::DownloadItem::kInvalidId;

  // Whether |next_download_id_| is retrieved from history db.
  bool next_id_retrieved_ = false;

  // The |GetNextId| callbacks that may be cached before loading the download
  // database.
  IdCallbackVector id_callbacks_;
  std::unique_ptr<DownloadPrefs> download_prefs_;

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // CRX installs that are currently in progress.
  std::map<base::UnguessableToken, scoped_refptr<extensions::CrxInstaller>>
      running_crx_installs_;
#endif

  // Outstanding callbacks to open file selection dialog.
  std::deque<base::OnceClosure> file_picker_callbacks_;

  // Whether a file picker dialog is showing.
  bool is_file_picker_showing_ = false;

  base::WeakPtrFactory<ChromeDownloadManagerDelegate> weak_ptr_factory_{this};
};

#endif  // CHROME_BROWSER_DOWNLOAD_CHROME_DOWNLOAD_MANAGER_DELEGATE_H_
