// Copyright 2020 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_SAFE_BROWSING_DOWNLOAD_PROTECTION_DEEP_SCANNING_REQUEST_H_
#define CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_DEEP_SCANNING_REQUEST_H_

#include <memory>

#include "base/callback_list.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "chrome/browser/download/download_item_warning_data.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_info.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.h"
#include "chrome/browser/safe_browsing/download_protection/deep_scanning_metadata.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/common.h"
#include "components/enterprise/obfuscation/core/download_obfuscator.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"

namespace download {
class DownloadItem;
}

namespace safe_browsing {

class DownloadProtectionService;
class DownloadRequestMaker;
class FileOpeningJob;

// This class encapsulates the process of uploading a file to Safe Browsing for
// deep scanning and reporting the result.
// Deep scanning is not supported on Android.
class DeepScanningRequest : public download::DownloadItem::Observer,
                            public enterprise_connectors::ContentAnalysisInfo {
 public:
  // Enum representing the type of constructor that initiated scanning.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class DeepScanType {
    // Scanning was initiated by a normal download from a web page.
    NORMAL = 0,

    // Scanning was initiated by a save package being saved on disk.
    SAVE_PACKAGE = 1,

    kMaxValue = SAVE_PACKAGE,
  };

  class Observer : public base::CheckedObserver {
   public:
    ~Observer() override = default;

    // Called when the DeepScanningRequest finishes.
    virtual void OnFinish(DeepScanningRequest* request) {}
  };

  // Checks the current policies to determine whether files must be uploaded by
  // policy. Returns the settings to apply to this analysis if it should happen
  // or std::nullopt if no analysis should happen.
  static std::optional<enterprise_connectors::AnalysisSettings>
  ShouldUploadBinary(const DeepScanningMetadata& metadata);

  // Scan the given `metadata item`, with the given `trigger`. The result of the
  // scanning will be provided through `callback`. Take a references to the
  // owning `download_service`.
  DeepScanningRequest(std::unique_ptr<DeepScanningMetadata> metadata,
                      DownloadItemWarningData::DeepScanTrigger trigger,
                      DownloadCheckResult pre_scan_download_check_result,
                      CheckDownloadRepeatingCallback callback,
                      DownloadProtectionService* download_service,
                      enterprise_connectors::AnalysisSettings settings,
                      base::optional_ref<const std::string> password);

  // Scan the given `metadata item` that corresponds to a save package, with
  // `save_package_page` mapping every currently on-disk file part of that
  // package to their final target path. The result of the scanning is provided
  // through `callback` once every file has been scanned, and the given result
  // is the highest severity one. Takes a reference to the owning
  // `download_service`.
  DeepScanningRequest(
      std::unique_ptr<DeepScanningMetadata> metadata,
      DownloadCheckResult pre_scan_download_check_result,
      CheckDownloadRepeatingCallback callback,
      DownloadProtectionService* download_service,
      enterprise_connectors::AnalysisSettings settings,
      base::flat_map<base::FilePath, base::FilePath> save_package_files);

  ~DeepScanningRequest() override;

  // Begin the deep scanning request. This must be called on the UI thread.
  void Start();

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // download::DownloadItem::Observer:
  void OnDownloadUpdated(download::DownloadItem* download) override;
  void OnDownloadDestroyed(download::DownloadItem* download) override;

  // enterprise_connectors::ContentAnalysisInfo:
  const enterprise_connectors::AnalysisSettings& settings() const override;
  signin::IdentityManager* identity_manager() const override;
  int user_action_requests_count() const override;
  std::string tab_title() const override;
  std::string user_action_id() const override;
  std::string email() const override;
  const GURL& url() const override;
  const GURL& tab_url() const override;
  enterprise_connectors::ContentAnalysisRequest::Reason reason() const override;
  google::protobuf::RepeatedPtrField<::safe_browsing::ReferrerChainEntry>
  referrer_chain() const override;
  google::protobuf::RepeatedPtrField<std::string> frame_url_chain()
      const override;
  content::WebContents* web_contents() const override;

 private:
  // Starts the deep scanning request when there is a one-to-one mapping from
  // the download item to a file.
  void StartSingleFileScan();

  // Starts the deep scanning requests when there is a one-to-many mapping from
  // the download item to multiple files being scanned as a part of a save
  // package.
  void StartSavePackageScan();

  // Callback when the |download_request_maker_| is finished assembling the
  // download metadata request.
  void OnDownloadRequestReady(
      const base::FilePath& current_path,
      std::unique_ptr<FileAnalysisRequest> deep_scan_request,
      std::unique_ptr<ClientDownloadRequest> download_request);

  // Callbacks for when |binary_upload_service_| finishes uploading.
  void OnScanComplete(const base::FilePath& current_path,
                      enterprise_connectors::ScanRequestUploadResult result,
                      enterprise_connectors::ContentAnalysisResponse response);
  void OnConsumerScanComplete(
      const base::FilePath& current_path,
      enterprise_connectors::ScanRequestUploadResult result,
      enterprise_connectors::ContentAnalysisResponse response);
  void OnEnterpriseScanComplete(
      const base::FilePath& current_path,
      enterprise_connectors::ScanRequestUploadResult result,
      enterprise_connectors::ContentAnalysisResponse response);
  void ProcessEnterpriseDownloadResult(DownloadCheckResult download_result);

  // Called when a single file scanning request has completed. Calls
  // FinishRequest if it was the last required one.
  void MaybeFinishRequest(DownloadCheckResult result);

  // Finishes the request, providing the result through |callback_| and
  // notifying |download_service_|.
  void FinishRequest(DownloadCheckResult result);

  // Called to verify if `result` is considered as a failure and the scan should
  // end early.
  bool ShouldTerminateEarly(
      enterprise_connectors::ScanRequestUploadResult result);

  // Called to open the download. This is triggered by the timeout modal dialog.
  void OpenDownload();

  // Populates a request's proto fields with the appropriate data.
  void PopulateRequest(FileAnalysisRequest* request,
                       Profile* profile,
                       const base::FilePath& path);

  // Creates a ClientDownloadRequest asynchronously to attach to
  // `deep_scan_request`. Once it is obtained, OnDownloadRequestReady is called
  // to upload the request for deep scanning.
  void PrepareClientDownloadRequest(
      const base::FilePath& current_path,
      std::unique_ptr<FileAnalysisRequest> deep_scan_request);

  // Callback invoked in `StartSingleFileScan` to check if `data` has been
  // successfully fetched and ready for deep scanning if needed.
  void OnGetFileRequestData(
      const base::FilePath& file_path,
      std::unique_ptr<FileAnalysisRequest> request,
      enterprise_connectors::ScanRequestUploadResult result,
      enterprise_connectors::BinaryUploadRequest::Data data);

  // Callback invoked in `StartSavePackageScan` to check if `data` of a file in
  // package has been successfully fetched and ready for deep scanning if
  // needed.
  void OnGetPackageFileRequestData(
      const base::FilePath& final_path,
      const base::FilePath& current_path,
      std::unique_ptr<FileAnalysisRequest> request,
      enterprise_connectors::ScanRequestUploadResult result,
      enterprise_connectors::BinaryUploadRequest::Data data);

  // Helper function to simplify checking if the report-only feature is set in
  // conjunction with the corresponding policy value.
  bool ReportOnlyScan();

  // Acknowledge the request's handling to the service provider.
  void AcknowledgeRequest(enterprise_connectors::EventResult event_result);

  bool IsEnterpriseTriggered() const;
  bool IsConsumerTriggered() const;

  // Callback for when deobfuscation of the file is completed.
  void OnDeobfuscationComplete(
      DownloadCheckResult download_result,
      base::expected<void, enterprise_obfuscation::Error> result);

  // Provides scan result to `callback_` and clean up.
  void CallbackAndCleanup(DownloadCheckResult result);

  // If enterprise scan finds something, update the download check result for
  // large or encrypted files.
  void MaybeUpdateDownloadCheckResult(
      const enterprise_connectors::ContentAnalysisResponse& response,
      DownloadCheckResult& result);

  // Evaluates whether force save to cloud is enabled, and if so, finds the
  // correct web contents. Modifies `result` to SENSITIVE_CONTENT_BLOCK if the
  // feature is disabled. Returns the WebContents to use for the dialog, or
  // nullptr if no dialog should be shown (either because it's not a force save,
  // the feature is disabled, or no web contents could be found).
  content::WebContents* MaybeGetWebContentsForForceSave(
      DownloadCheckResult& result);

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  // Shows the force save to cloud dialog. `file_count` should be 1 for single
  // file scans. For save package scans, it should be the number of files in
  // the package.
  void ShowForceSaveToCloudDialog(
      base::OnceClosure keep_closure,
      base::OnceClosure discard_closure,
      content::WebContents* web_contents,
      const enterprise_connectors::ContentAnalysisResponse::Result::
          TriggeredRule::CustomRuleMessage& custom_message,
      size_t file_count);
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

  // Metadata for the item being scanned. This is owned by `DeepScanningRequest`
  // and provides an abstraction layer over different types of scan sources
  // (download items, file system access writes).
  std::unique_ptr<DeepScanningMetadata> metadata_;

  // ScopedObservation to manage `DownloadItem` observation lifetime. Must be
  // cleared before `metadata_` is.
  std::unique_ptr<DeepScanningMetadata::DownloadScopedObservation>
      download_observation_;

  // The reason for deep scanning.
  DownloadItemWarningData::DeepScanTrigger trigger_;

  // The callback to provide the scan result to.
  CheckDownloadRepeatingCallback callback_;

  // The download protection service that initiated this upload. The
  // |download_service_| owns this class.
  raw_ptr<DownloadProtectionService> download_service_;

  // The time when uploading starts. Keyed with the file's current path.
  base::flat_map<base::FilePath, base::TimeTicks> upload_start_times_;

  // The settings to apply to this scan.
  enterprise_connectors::AnalysisSettings analysis_settings_;

  // Used to assemble the download metadata.
  std::unique_ptr<DownloadRequestMaker> download_request_maker_;

  // This list of observers of this request.
  base::ObserverList<Observer> observers_;

  // Stores a mapping of temporary paths to final paths for save package files.
  // This is empty on non-page save scanning requests.
  base::flat_map<base::FilePath, base::FilePath> save_package_files_;

  // Stores a mapping of a file's current path to its metadata so it can be used
  // in reporting events. This is populated from opening the file for save
  // package scans, or populated from `item_` for single file scans.
  base::flat_map<base::FilePath, enterprise_connectors::FileMetadata>
      file_metadata_;

  // Owner of the FileOpeningJob used to safely open multiple files in parallel
  // for save package scans. Always nullptr for non-save package scans.
  scoped_refptr<FileOpeningJob> file_opening_job_;

  // The total number of files beings scanned for which OnScanComplete hasn't
  // been called. Once this is 0, FinishRequest should be called and `this`
  // should be destroyed.
  size_t pending_scan_requests_;

  // The highest precedence DownloadCheckResult obtained from scanning verdicts.
  // This should be updated when a scan completes.
  DownloadCheckResult download_check_result_ =
      DownloadCheckResult::DEEP_SCANNED_SAFE;

  // Cached SB result for the download to be used if deep scanning fails.
  DownloadCheckResult pre_scan_download_check_result_;

  // Cached danger type for the download to be used by reporting in case
  // scanning is skipped for any reason.
  download::DownloadDangerType pre_scan_danger_type_;

  // Set to true when StartSingleFileScan or StartSavePackageScan is called and
  // that scanning has started. This is used so that calls to OnDownloadUpdated
  // only ever start the scanning process once.
  bool scanning_started_ = false;

  // Cached callbacks to report scanning results until the final `event_result_`
  // is known. The callbacks in this list should be called in FinishRequest.
  base::OnceCallbackList<void(enterprise_connectors::EventResult result)>
      report_callbacks_;

  // The request tokens of all the requests that make up the user action
  // represented by this ContentAnalysisDelegate instance.
  std::vector<std::string> request_tokens_;

  // Password for the file, if it's an archive.
  std::optional<std::string> password_;

  // Reason the scanning took place. Used to populate enterprise requests to
  // give more context on what user action lead to a scan.
  enterprise_connectors::ContentAnalysisRequest::Reason reason_ =
      enterprise_connectors::ContentAnalysisRequest::UNKNOWN;

  // User action id for the scanning.
  std::string user_action_id_;

  // Set to true if the download has been destroyed, indicating that the deep
  // scan request has been cancelled.
  bool is_cancelled_ = false;

  base::WeakPtrFactory<DeepScanningRequest> weak_ptr_factory_;
};

// Revealed for testing.
DownloadCheckResult ResponseToDownloadCheckResult(
    const enterprise_connectors::ContentAnalysisResponse& response);

}  // namespace safe_browsing

#endif  // CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_DEEP_SCANNING_REQUEST_H_
