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

#include "chrome/browser/safe_browsing/download_protection/deep_scanning_request.h"

#include <memory>
#include <optional>
#include <vector>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/download/download_item_warning_data.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/enterprise/connectors/reporting/reporting_event_router_factory.h"
#include "chrome/browser/policy/dm_token_utils.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/download_protection_service.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#include "chrome/browser/safe_browsing/download_protection/download_request_maker.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_item.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/binary_upload_service.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/deep_scanning_utils.h"
#include "components/enterprise/connectors/core/cloud_content_scanning/file_opening_job.h"
#include "components/enterprise/connectors/core/common.h"
#include "components/enterprise/connectors/core/features.h"
#include "components/enterprise/connectors/core/reporting_constants.h"
#include "components/enterprise/connectors/core/reporting_utils.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/content/browser/web_ui/web_ui_content_info_singleton.h"
#include "components/safe_browsing/core/browser/download_check_result.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/url_matcher/url_matcher.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/common/content_switches.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_controller.h"
#include "chrome/browser/enterprise/data_protection/data_protection_features.h"
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#endif  // !BUILDFLAG(IS_ANDROID)

using DeepScanTrigger = DownloadItemWarningData::DeepScanTrigger;

namespace safe_browsing {

namespace {

using ::enterprise_connectors::BinaryUploadRequest;
using ::enterprise_connectors::TriggeredRule;

TriggeredRule::ForceSaveToCloudDestination
GetHighestPrecedenceForceSaveToCloudDestination(
    TriggeredRule::ForceSaveToCloudDestination destination_1,
    TriggeredRule::ForceSaveToCloudDestination destination_2) {
  // Force download to Google Drive/One Drive is only applicable to desktop.
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  // Prefer G_DRIVE over ONEDRIVE.
  if (base::FeatureList::IsEnabled(
          enterprise_data_protection::kEnableForceDownloadToCloud) &&
      (destination_1 == TriggeredRule::CORP_G_DRIVE ||
       destination_2 == TriggeredRule::CORP_G_DRIVE)) {
    return TriggeredRule::CORP_G_DRIVE;
  } else if (destination_1 == TriggeredRule::CORP_ONEDRIVE ||
             destination_2 == TriggeredRule::CORP_ONEDRIVE) {
    return TriggeredRule::CORP_ONEDRIVE;
  }
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  return TriggeredRule::UNSPECIFIED;
}

TriggeredRule::CustomRuleMessage GetForceSaveToCloudCustomRuleMessage(
    const enterprise_connectors::ContentAnalysisResponse& response) {
  for (const auto& result : response.results()) {
    for (const auto& rule : result.triggered_rules()) {
      if (rule.action() == TriggeredRule::FORCE_SAVE_TO_CLOUD &&
          rule.has_custom_rule_message()) {
        return rule.custom_rule_message();
      }
    }
  }
  return TriggeredRule::CustomRuleMessage();
}

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
bool CanBypassForceSaveDialogForAutomation() {
  return base::CommandLine::ForCurrentProcess()->HasSwitch(
      switches::kEnableAutomation);
}
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

DownloadCheckResult GetHighestPrecedenceResult(DownloadCheckResult result_1,
                                               DownloadCheckResult result_2) {
  // Don't use the enum's int values to determine precedence since that
  // may introduce bugs for new actions later.
  //
  // The current precedence is
  //   blocking > bypassable warning > scan failure > safe
  // with malware verdicts having precedence over DLP verdicts.
  //
  // This matches the precedence established in ResponseToDownloadCheckResult.
  constexpr DownloadCheckResult kDownloadCheckResultPrecedence[] = {
      DownloadCheckResult::DANGEROUS,
      DownloadCheckResult::SENSITIVE_CONTENT_BLOCK,
      DownloadCheckResult::BLOCKED_TOO_LARGE,
      DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED,
      DownloadCheckResult::BLOCKED_SCAN_FAILED,
      DownloadCheckResult::FORCE_SAVE_TO_GDRIVE,
      DownloadCheckResult::FORCE_SAVE_TO_ONEDRIVE,
      DownloadCheckResult::POTENTIALLY_UNWANTED,
      DownloadCheckResult::SENSITIVE_CONTENT_WARNING,
      DownloadCheckResult::PROMPT_FOR_SCANNING,
      DownloadCheckResult::DEEP_SCANNED_FAILED,
      DownloadCheckResult::UNKNOWN,
      DownloadCheckResult::DEEP_SCANNED_SAFE};

  for (DownloadCheckResult result : kDownloadCheckResultPrecedence) {
    if (result_1 == result || result_2 == result) {
      return result;
    }
  }

  NOTREACHED();
}

std::string GetDestinationString(DownloadCheckResult result) {
  switch (result) {
    case DownloadCheckResult::FORCE_SAVE_TO_GDRIVE:
      return "Google Drive";
    case DownloadCheckResult::FORCE_SAVE_TO_ONEDRIVE:
      return "OneDrive";
    default:
      return "";
  }
}

enterprise_connectors::EventResult GetEventResult(
    DownloadCheckResult download_result,
    Profile* profile) {
  auto download_restriction =
      profile ? static_cast<policy::DownloadRestriction>(
                    profile->GetPrefs()->GetInteger(
                        policy::policy_prefs::kDownloadRestrictions))
              : policy::DownloadRestriction::NONE;
  switch (download_result) {
    case DownloadCheckResult::UNKNOWN:
    case DownloadCheckResult::SAFE:
    case DownloadCheckResult::ALLOWLISTED_BY_POLICY:
    case DownloadCheckResult::DEEP_SCANNED_SAFE:
      return enterprise_connectors::EventResult::ALLOWED;

    // The following results return WARNED or BLOCKED depending on
    // |download_restriction|.
    case DownloadCheckResult::DANGEROUS:
    case DownloadCheckResult::DANGEROUS_HOST:
    case DownloadCheckResult::DANGEROUS_ACCOUNT_COMPROMISE:
      switch (download_restriction) {
        case policy::DownloadRestriction::ALL_FILES:
        case policy::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES:
        case policy::DownloadRestriction::DANGEROUS_FILES:
        case policy::DownloadRestriction::MALICIOUS_FILES:
          return enterprise_connectors::EventResult::BLOCKED;
        case policy::DownloadRestriction::NONE:
          return enterprise_connectors::EventResult::WARNED;
      }

    case DownloadCheckResult::UNCOMMON:
    case DownloadCheckResult::POTENTIALLY_UNWANTED:
    case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
      return enterprise_connectors::EventResult::WARNED;

    case DownloadCheckResult::FORCE_SAVE_TO_GDRIVE:
      return enterprise_connectors::EventResult::FORCED_SAVE_TO_CLOUD;
    case DownloadCheckResult::FORCE_SAVE_TO_ONEDRIVE:
      return enterprise_connectors::EventResult::FORCED_SAVE_TO_CLOUD;
    case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
    case DownloadCheckResult::BLOCKED_TOO_LARGE:
    case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
    case DownloadCheckResult::BLOCKED_SCAN_FAILED:
      return enterprise_connectors::EventResult::BLOCKED;

    default:
      NOTREACHED() << "Should never be final result";
  }
}

std::string GetTriggerName(DeepScanTrigger trigger) {
  switch (trigger) {
    case DeepScanTrigger::TRIGGER_UNKNOWN:
      return "Unknown";
    case DeepScanTrigger::TRIGGER_CONSUMER_PROMPT:
      return "ConsumerPrompt";
    case DeepScanTrigger::TRIGGER_POLICY:
      return "Policy";
    case DeepScanTrigger::TRIGGER_ENCRYPTED_CONSUMER_PROMPT:
      return "EncryptedConsumerPrompt";
    case DeepScanTrigger::TRIGGER_IMMEDIATE_DEEP_SCAN:
      return "ImmediateDeepScan";
  }
}

enterprise_connectors::ContentAnalysisAcknowledgement::FinalAction
GetFinalAction(enterprise_connectors::EventResult event_result) {
  auto final_action =
      enterprise_connectors::ContentAnalysisAcknowledgement::ALLOW;
  switch (event_result) {
    case enterprise_connectors::EventResult::UNKNOWN:
    case enterprise_connectors::EventResult::ALLOWED:
    case enterprise_connectors::EventResult::BYPASSED:
      break;
    case enterprise_connectors::EventResult::WARNED:
      final_action =
          enterprise_connectors::ContentAnalysisAcknowledgement::WARN;
      break;
    // TODO(alshawwa): handle FORCE_SAVE_TO_CLOUD case. Currently defaults to
    // BLOCKED behaviour.
    case enterprise_connectors::EventResult::FORCED_SAVE_TO_CLOUD:
    case enterprise_connectors::EventResult::BLOCKED:
    case enterprise_connectors::EventResult::CANCELLED:
      final_action =
          enterprise_connectors::ContentAnalysisAcknowledgement::BLOCK;
      break;
  }

  return final_action;
}

void LogDeepScanResult(DownloadCheckResult download_result,
                       DeepScanTrigger trigger,
                       bool is_encrypted_archive) {
  base::UmaHistogramEnumeration(
      "SBClientDownload.MalwareDeepScanResult2." + GetTriggerName(trigger),
      download_result);
  if (is_encrypted_archive) {
    base::UmaHistogramEnumeration(
        "SBClientDownload.PasswordProtectedMalwareDeepScanResult2." +
            GetTriggerName(trigger),
        download_result);
  }
}

bool HasDecryptionFailedResult(
    enterprise_connectors::ContentAnalysisResponse response) {
  for (const auto& result : response.results()) {
    if (result.tag() != "malware") {
      continue;
    }

    if (result.status_error_message() ==
        enterprise_connectors::ContentAnalysisResponse::Result::
            DECRYPTION_FAILED) {
      return true;
    }
  }

  return false;
}

bool EnterpriseResultIsFailure(
    enterprise_connectors::ScanRequestUploadResult result,
    bool block_large_files,
    bool block_password_protected_files) {
  return enterprise_connectors::CloudResumableResultIsFailure(
      result, block_large_files, block_password_protected_files);
}

void RecordEnterpriseScan(
    std::unique_ptr<FileAnalysisRequest> request,
    enterprise_connectors::ScanRequestUploadResult result) {
  const std::string result_info = base::StrCat(
      {"Skipped - ",
       enterprise_connectors::ScanRequestUploadResultToString(result)});
  safe_browsing::WebUIContentInfoSingleton::GetInstance()
      ->AddToDeepScanRequests(
          request->per_profile_request(), /*access_token*/ "",
          /*upload_info*/ result_info,
          /*upload_url*/ "", request->content_analysis_request());
  safe_browsing::WebUIContentInfoSingleton::GetInstance()
      ->AddToDeepScanResponses(
          /*token=*/"", result_info,
          enterprise_connectors::ContentAnalysisResponse());
}

}  // namespace

DownloadCheckResult ResponseToDownloadCheckResult(
    const enterprise_connectors::ContentAnalysisResponse& response) {
  bool malware_scan_failure = false;
  bool dlp_scan_failure = false;
  auto malware_action =
      enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED;
  auto dlp_action = enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED;
  auto force_save_to_cloud_destination =
      enterprise_connectors::TriggeredRule::UNSPECIFIED;

  for (const auto& result : response.results()) {
    if (result.tag() == "malware") {
      if (result.status() !=
          enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS) {
        malware_scan_failure = true;
        continue;
      }
      for (const auto& rule : result.triggered_rules()) {
        malware_action = enterprise_connectors::GetHighestPrecedenceAction(
            malware_action, rule.action());
      }
    }
    if (result.tag() == "dlp") {
      if (result.status() !=
          enterprise_connectors::ContentAnalysisResponse::Result::SUCCESS) {
        dlp_scan_failure = true;
        continue;
      }
      for (const auto& rule : result.triggered_rules()) {
        dlp_action = enterprise_connectors::GetHighestPrecedenceAction(
            dlp_action, rule.action());

        if (rule.has_force_save_to_cloud_destination()) {
          DCHECK(rule.action() == TriggeredRule::FORCE_SAVE_TO_CLOUD);
          force_save_to_cloud_destination =
              GetHighestPrecedenceForceSaveToCloudDestination(
                  force_save_to_cloud_destination,
                  rule.force_save_to_cloud_destination());
        }
      }
    }
  }

  CHECK(malware_action !=
        enterprise_connectors::TriggeredRule::FORCE_SAVE_TO_CLOUD);

  if (malware_action == enterprise_connectors::GetHighestPrecedenceAction(
                            malware_action, dlp_action)) {
    switch (malware_action) {
      case enterprise_connectors::TriggeredRule::BLOCK:
        return DownloadCheckResult::DANGEROUS;
      case enterprise_connectors::TriggeredRule::WARN:
        return DownloadCheckResult::POTENTIALLY_UNWANTED;
      case enterprise_connectors::TriggeredRule::REPORT_ONLY:
      case enterprise_connectors::TriggeredRule::KEEP_IN_MANAGED_CHROME:
      case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED:
        break;
      case enterprise_connectors::TriggeredRule::JUSTIFICATION_REQUIRED:
      case enterprise_connectors::TriggeredRule::FORCE_SAVE_TO_CLOUD:
        NOTREACHED();
    }
  } else {
    switch (dlp_action) {
      case enterprise_connectors::TriggeredRule::FORCE_SAVE_TO_CLOUD:
        switch (force_save_to_cloud_destination) {
          case enterprise_connectors::TriggeredRule::CORP_G_DRIVE:
            return DownloadCheckResult::FORCE_SAVE_TO_GDRIVE;
          case enterprise_connectors::TriggeredRule::CORP_ONEDRIVE:
            return DownloadCheckResult::FORCE_SAVE_TO_ONEDRIVE;
          case enterprise_connectors::TriggeredRule::UNSPECIFIED:
            // If the destination is unspecified, we default to blocking the
            // download. This should never happen unless both finch flag for
            // gdrive and onedrive are disabled.
            return DownloadCheckResult::SENSITIVE_CONTENT_BLOCK;
        }
      case enterprise_connectors::TriggeredRule::BLOCK:
        return DownloadCheckResult::SENSITIVE_CONTENT_BLOCK;
      case enterprise_connectors::TriggeredRule::WARN:
      case enterprise_connectors::TriggeredRule::JUSTIFICATION_REQUIRED:
        return DownloadCheckResult::SENSITIVE_CONTENT_WARNING;
      case enterprise_connectors::TriggeredRule::REPORT_ONLY:
      case enterprise_connectors::TriggeredRule::KEEP_IN_MANAGED_CHROME:
      case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED:
        break;
    }
  }

  if (dlp_scan_failure || malware_scan_failure) {
    return DownloadCheckResult::DEEP_SCANNED_FAILED;
  }

  return DownloadCheckResult::DEEP_SCANNED_SAFE;
}

/* static */
std::optional<enterprise_connectors::AnalysisSettings>
DeepScanningRequest::ShouldUploadBinary(const DeepScanningMetadata& metadata) {
  // Files already on the disk shouldn't be uploaded for scanning.
  if (metadata.GetURL().SchemeIsFile()) {
    return std::nullopt;
  }

  auto* service =
      enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
          metadata.GetBrowserContext());

  // If the download Connector is not enabled, don't scan.
  if (!service ||
      !service->IsConnectorEnabled(
          enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED)) {
    return std::nullopt;
  }

  // Check that the scanned item's URL matches the appropriate URL patterns by
  // getting settings. No settings means no matches were found and that the
  // downloaded file shouldn't be uploaded.
  return service->GetAnalysisSettings(
      metadata.GetURL(),
      enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED);
}

DeepScanningRequest::DeepScanningRequest(
    std::unique_ptr<DeepScanningMetadata> metadata,
    DeepScanTrigger trigger,
    DownloadCheckResult pre_scan_download_check_result,
    CheckDownloadRepeatingCallback callback,
    DownloadProtectionService* download_service,
    enterprise_connectors::AnalysisSettings settings,
    base::optional_ref<const std::string> password)
    : metadata_(std::move(metadata)),
      trigger_(trigger),
      callback_(callback),
      download_service_(download_service),
      analysis_settings_(std::move(settings)),
      pending_scan_requests_(1),
      pre_scan_download_check_result_(pre_scan_download_check_result),
      password_(password.CopyAsOptional()),
      reason_(enterprise_connectors::ContentAnalysisRequest::NORMAL_DOWNLOAD),
      user_action_id_(base::HexEncode(base::RandBytesAsVector(128))),
      weak_ptr_factory_(this) {
  base::UmaHistogramEnumeration("SBClientDownload.DeepScanType",
                                DeepScanType::NORMAL);
  CHECK(metadata_);
  download_observation_ = metadata_->GetDownloadObservation(this);
}

DeepScanningRequest::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)
    : metadata_(std::move(metadata)),
      trigger_(DeepScanTrigger::TRIGGER_POLICY),
      callback_(callback),
      download_service_(download_service),
      analysis_settings_(std::move(settings)),
      save_package_files_(std::move(save_package_files)),
      pending_scan_requests_(save_package_files_.size()),
      pre_scan_download_check_result_(pre_scan_download_check_result),
      reason_(enterprise_connectors::ContentAnalysisRequest::SAVE_AS_DOWNLOAD),
      user_action_id_(base::HexEncode(base::RandBytesAsVector(128))),
      weak_ptr_factory_(this) {
  base::UmaHistogramEnumeration("SBClientDownload.DeepScanType",
                                DeepScanType::SAVE_PACKAGE);
  base::UmaHistogramCounts10000("SBClientDownload.SavePackageFileCount",
                                save_package_files_.size());
  CHECK(metadata_);
  download_observation_ = metadata_->GetDownloadObservation(this);
}

DeepScanningRequest::~DeepScanningRequest() = default;

void DeepScanningRequest::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void DeepScanningRequest::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void DeepScanningRequest::Start() {
  // Indicate we're now scanning the file.
  pre_scan_danger_type_ = metadata_->GetDangerType();

  if (ReportOnlyScan()) {
    // In non-blocking mode, run `callback_` immediately so the download
    // completes, then start the scanning process only after the downloaded
    // files have been renamed. `callback_` is also reset so that the download
    // is no longer updated after scanning finishes.
    callback_.Run(pre_scan_download_check_result_);
    callback_.Reset();
    return;
  }

  metadata_->SetDeepScanTrigger(trigger_);
  callback_.Run(DownloadCheckResult::ASYNC_SCANNING);
  if (save_package_files_.empty()) {
    StartSingleFileScan();
  } else {
    StartSavePackageScan();
  }
}

void DeepScanningRequest::StartSingleFileScan() {
  DCHECK(!scanning_started_);
  scanning_started_ = true;
  enterprise_connectors::IncrementCrashKey(
      enterprise_connectors::ScanningCrashKey::PENDING_FILE_DOWNLOADS);
  enterprise_connectors::IncrementCrashKey(
      enterprise_connectors::ScanningCrashKey::TOTAL_FILE_DOWNLOADS);

  auto request = std::make_unique<FileAnalysisRequest>(
      analysis_settings_, metadata_->GetFullPath(),
      metadata_->GetTargetFilePath().BaseName(), metadata_->GetMimeType(),
      /* delay_opening_file */ false,
      base::BindOnce(&DeepScanningRequest::OnScanComplete,
                     weak_ptr_factory_.GetWeakPtr(), metadata_->GetFullPath()),
      base::DoNothing(), metadata_->IsObfuscated(),
      /* force_sync_hash_computation */ false);

  request->set_filename(metadata_->GetTargetFilePath().AsUTF8Unsafe());

  std::string sha256 = base::HexEncode(metadata_->GetHash());
  request->set_digest(sha256);

  if (password_) {
    request->set_password(*password_);
  }

  file_metadata_.insert(
      {metadata_->GetFullPath(),
       enterprise_connectors::FileMetadata(
           metadata_->GetTargetFilePath().AsUTF8Unsafe(), sha256,
           metadata_->GetMimeType(), metadata_->GetTotalBytes())});

  Profile* profile =
      Profile::FromBrowserContext(metadata_->GetBrowserContext());

  base::UmaHistogramEnumeration("SBClientDownload.DeepScanTrigger", trigger_);

  FileAnalysisRequest* request_raw = request.get();
  PopulateRequest(request_raw, profile, metadata_->GetFullPath());
  request_raw->GetRequestData(
      base::BindOnce(&DeepScanningRequest::OnGetFileRequestData,
                     weak_ptr_factory_.GetWeakPtr(), metadata_->GetFullPath(),
                     std::move(request)));
}

void DeepScanningRequest::StartSavePackageScan() {
  DCHECK(!scanning_started_);
  scanning_started_ = true;
  enterprise_connectors::IncrementCrashKey(
      enterprise_connectors::ScanningCrashKey::PENDING_FILE_DOWNLOADS,
      pending_scan_requests_);
  enterprise_connectors::IncrementCrashKey(
      enterprise_connectors::ScanningCrashKey::TOTAL_FILE_DOWNLOADS,
      pending_scan_requests_);

  Profile* profile =
      Profile::FromBrowserContext(metadata_->GetBrowserContext());
  std::vector<FileOpeningJob::FileOpeningTask> tasks(pending_scan_requests_);
  size_t i = 0;
  for (const auto& tmp_path_and_final_path : save_package_files_) {
    base::FilePath file_location = ReportOnlyScan()
                                       ? tmp_path_and_final_path.second
                                       : tmp_path_and_final_path.first;
    auto request = std::make_unique<FileAnalysisRequest>(
        analysis_settings_, file_location,
        tmp_path_and_final_path.second.BaseName(), /*mimetype*/ "",
        /* delay_opening_file */ true,
        base::BindOnce(&DeepScanningRequest::OnScanComplete,
                       weak_ptr_factory_.GetWeakPtr(), file_location));
    request->set_filename(tmp_path_and_final_path.second.AsUTF8Unsafe());

    FileAnalysisRequest* request_raw = request.get();
    PopulateRequest(request_raw, profile, file_location);
    request_raw->GetRequestData(base::BindOnce(
        &DeepScanningRequest::OnGetPackageFileRequestData,
        weak_ptr_factory_.GetWeakPtr(), tmp_path_and_final_path.second,
        file_location, std::move(request)));
    DCHECK_LT(i, tasks.size());
    tasks[i++].request = request_raw;
  }
  DCHECK_EQ(i, tasks.size());

  // Keep a reference to `file_opening_job` in each task to ensure
  // its lifetime will be longer than the request.
  file_opening_job_ = base::MakeRefCounted<FileOpeningJob>(std::move(tasks));
}

void DeepScanningRequest::PopulateRequest(FileAnalysisRequest* request,
                                          Profile* profile,
                                          const base::FilePath& path) {
  InitializeRequest(request, IsEnterpriseTriggered());
  request->set_analysis_connector(enterprise_connectors::FILE_DOWNLOADED);
  if (auto it = file_metadata_.find(path);
      it != file_metadata_.end() && !it->second.mime_type.empty()) {
    request->set_content_type(it->second.mime_type);
  }
}

void DeepScanningRequest::PrepareClientDownloadRequest(
    const base::FilePath& current_path,
    std::unique_ptr<FileAnalysisRequest> request) {
  if (IsEnterpriseTriggered()) {
    download_request_maker_ = metadata_->CreateDownloadRequestFromMetadata(
        new BinaryFeatureExtractor());
    download_request_maker_->Start(base::BindOnce(
        &DeepScanningRequest::OnDownloadRequestReady,
        weak_ptr_factory_.GetWeakPtr(), current_path, std::move(request)));
  } else {
    OnDownloadRequestReady(current_path, std::move(request), nullptr);
  }
}

void DeepScanningRequest::OnGetPackageFileRequestData(
    const base::FilePath& final_path,
    const base::FilePath& current_path,
    std::unique_ptr<FileAnalysisRequest> request,
    enterprise_connectors::ScanRequestUploadResult result,
    BinaryUploadRequest::Data data) {
  file_metadata_.insert({current_path, enterprise_connectors::FileMetadata(
                                           final_path.AsUTF8Unsafe(), data.hash,
                                           data.mime_type, data.size)});

  // If block large files is enabled, then the file is too large if it exceeds
  // the max upload size limit which is currently 50MB.
  if (result == enterprise_connectors::ScanRequestUploadResult::kSuccess &&
      analysis_settings_.block_large_files &&
      data.size >
          enterprise_connectors::BinaryUploadService::kMaxUploadSizeBytes) {
    result = enterprise_connectors::ScanRequestUploadResult::kFileTooLarge;
  }

  if (ShouldTerminateEarly(result)) {
    // We record the scan here because the request is terminated early and won't
    // be uploaded to CloudBinaryUploadService.
    if (IsEnterpriseTriggered()) {
      RecordEnterpriseScan(std::move(request), result);
    }
    OnScanComplete(current_path, result,
                   enterprise_connectors::ContentAnalysisResponse());
    return;
  }

  OnDownloadRequestReady(current_path, std::move(request), nullptr);
}

void DeepScanningRequest::OnGetFileRequestData(
    const base::FilePath& file_path,
    std::unique_ptr<FileAnalysisRequest> request,
    enterprise_connectors::ScanRequestUploadResult result,
    BinaryUploadRequest::Data data) {
  if (result == enterprise_connectors::ScanRequestUploadResult::kSuccess &&
      analysis_settings_.block_large_files &&
      data.size >
          enterprise_connectors::BinaryUploadService::kMaxUploadSizeBytes) {
    result = enterprise_connectors::ScanRequestUploadResult::kFileTooLarge;
  }

  if (ShouldTerminateEarly(result)) {
    // We record the scan here because the request is terminated early and won't
    // be uploaded to CloudBinaryUploadService.
    if (IsEnterpriseTriggered()) {
      RecordEnterpriseScan(std::move(request), result);
    }
    OnScanComplete(file_path, result,
                   enterprise_connectors::ContentAnalysisResponse());
    return;
  }

  PrepareClientDownloadRequest(file_path, std::move(request));
}

void DeepScanningRequest::OnDownloadRequestReady(
    const base::FilePath& current_path,
    std::unique_ptr<FileAnalysisRequest> deep_scan_request,
    std::unique_ptr<ClientDownloadRequest> download_request) {
  if (download_request) {
    deep_scan_request->set_csd(*download_request);
  }

  upload_start_times_[current_path] = base::TimeTicks::Now();
  Profile* profile =
      Profile::FromBrowserContext(metadata_->GetBrowserContext());
  enterprise_connectors::BinaryUploadService* binary_upload_service =
      download_service_->GetBinaryUploadService(profile, analysis_settings_);
  if (binary_upload_service) {
    binary_upload_service->MaybeUploadForDeepScanning(
        std::move(deep_scan_request));
  } else {
    OnScanComplete(current_path,
                   enterprise_connectors::ScanRequestUploadResult::kUnknown,
                   enterprise_connectors::ContentAnalysisResponse());
  }
}

void DeepScanningRequest::OnScanComplete(
    const base::FilePath& current_path,
    enterprise_connectors::ScanRequestUploadResult result,
    enterprise_connectors::ContentAnalysisResponse response) {
  enterprise_connectors::RecordDeepScanMetrics(
      analysis_settings_.cloud_or_local_settings.is_cloud_analysis(),
      /*access_point=*/enterprise_connectors::DeepScanAccessPoint::DOWNLOAD,
      /*duration=*/base::TimeTicks::Now() - upload_start_times_[current_path],
      /*total_bytes=*/metadata_->GetTotalBytes(), /*result=*/result,
      /*response=*/response);

  if (IsConsumerTriggered()) {
    OnConsumerScanComplete(current_path, result, response);
  } else if (IsEnterpriseTriggered()) {
    OnEnterpriseScanComplete(current_path, result, response);
  } else {
    NOTREACHED();
  }
}

void DeepScanningRequest::OnConsumerScanComplete(
    const base::FilePath& current_path,
    enterprise_connectors::ScanRequestUploadResult result,
    enterprise_connectors::ContentAnalysisResponse response) {
  bool is_invalid_password =
      result ==
          enterprise_connectors::ScanRequestUploadResult::kFileEncrypted ||
      (result == enterprise_connectors::ScanRequestUploadResult::kSuccess &&
       metadata_->IsTopLevelEncryptedArchive() &&
       HasDecryptionFailedResult(response));
  bool is_success =
      result == enterprise_connectors::ScanRequestUploadResult::kSuccess &&
      !is_invalid_password;
  CHECK(IsConsumerTriggered());
  DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN;
  if (is_success) {
    request_tokens_.push_back(response.request_token());
    download_result = ResponseToDownloadCheckResult(response);
    LogDeepScanEvent(*metadata_, DeepScanEvent::kScanCompleted);
  } else if (is_invalid_password) {
    // Since we now prompt the user for a password for `DownloadItem` scans,
    // FILE_ENCRYPTED indicates the password was not correct. Instead of
    // failing, ask the user to correct the issue.
    metadata_->SetHasIncorrectPassword(true);
    metadata_->PromptForPassword();
    download_result = DownloadCheckResult::PROMPT_FOR_SCANNING;
    LogDeepScanEvent(*metadata_, DeepScanEvent::kIncorrectPassword);
  } else {
    download_result = DownloadCheckResult::DEEP_SCANNED_FAILED;
    LogDeepScanEvent(*metadata_, DeepScanEvent::kScanFailed);
  }

  LogDeepScanResult(download_result, trigger_,
                    metadata_->IsTopLevelEncryptedArchive());

  DCHECK(file_metadata_.count(current_path));
  file_metadata_.at(current_path).scan_response = std::move(response);
  MaybeFinishRequest(download_result);
}

void DeepScanningRequest::OnEnterpriseScanComplete(
    const base::FilePath& current_path,
    enterprise_connectors::ScanRequestUploadResult result,
    enterprise_connectors::ContentAnalysisResponse response) {
  CHECK(IsEnterpriseTriggered());

  DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN;

  enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
      CustomRuleMessage custom_message =
          GetForceSaveToCloudCustomRuleMessage(response);

  if (result == enterprise_connectors::ScanRequestUploadResult::kFileTooLarge &&
      analysis_settings_.block_large_files) {
    download_result = DownloadCheckResult::BLOCKED_TOO_LARGE;
  } else if (result == enterprise_connectors::ScanRequestUploadResult::
                           kFileEncrypted &&
             analysis_settings_.block_password_protected_files) {
    download_result = DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED;
    // WebProtect could still issue a block or warn verdict based on the
    // metadata of large or encrypted files. Therefore we should check the
    // `response` for these two cases as well.
  } else if (result == enterprise_connectors::ScanRequestUploadResult::
                           kFileTooLarge ||
             result == enterprise_connectors::ScanRequestUploadResult::
                           kFileEncrypted) {
    MaybeUpdateDownloadCheckResult(response, download_result);
  } else if (result ==
             enterprise_connectors::ScanRequestUploadResult::kSuccess) {
    request_tokens_.push_back(response.request_token());
    download_result = ResponseToDownloadCheckResult(response);
  } else if (enterprise_connectors::ResultIsFailClosed(result) &&
             analysis_settings_.default_action ==
                 enterprise_connectors::DefaultAction::kBlock) {
    download_result = DownloadCheckResult::BLOCKED_SCAN_FAILED;
  }

  content::WebContents* force_save_web_contents =
      MaybeGetWebContentsForForceSave(download_result);

  // Reporting happens unconditionally and does not depend on
  // DownloadCheckResult. Can be kept here.
  Profile* profile =
      Profile::FromBrowserContext(metadata_->GetBrowserContext());
  DCHECK(file_metadata_.count(current_path));
  file_metadata_.at(current_path).scan_response = std::move(response);
  if (profile) {
    const auto& file_metadata = file_metadata_.at(current_path);
    auto* reporting_event_router = enterprise_connectors::
        ReportingEventRouterFactory::GetForBrowserContext(profile);
    report_callbacks_.AddUnsafe(base::BindOnce(
        &enterprise_connectors::MaybeReportDeepScanningVerdict,
        reporting_event_router, this, /*source=*/"",
        /*destination=*/GetDestinationString(download_result),
        file_metadata.filename, file_metadata.sha256, file_metadata.mime_type,
        enterprise_connectors::kFileDownloadDataTransferEventTrigger,
        /*content_transfer_method=*/"", GetContentAreaAccountEmail(),
        file_metadata.size, result, file_metadata.scan_response));

    metadata_->AddScanResultMetadata(file_metadata);
  }

  if (force_save_web_contents) {
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
    if (CanBypassForceSaveDialogForAutomation()) {
      ProcessEnterpriseDownloadResult(download_result);
      return;
    }
    if (save_package_files_.empty()) {
      // ProcessEnterpriseDownloadResult will run via
      // dialog callback.
      base::OnceClosure keep_closure =
          base::BindOnce(&DeepScanningRequest::ProcessEnterpriseDownloadResult,
                         weak_ptr_factory_.GetWeakPtr(), download_result);
      base::OnceClosure discard_closure =
          base::BindOnce(&DeepScanningRequest::ProcessEnterpriseDownloadResult,
                         weak_ptr_factory_.GetWeakPtr(),
                         DownloadCheckResult::SENSITIVE_CONTENT_BLOCK);

      ShowForceSaveToCloudDialog(
          std::move(keep_closure), std::move(discard_closure),
          force_save_web_contents, custom_message, /*file_count=*/1);
      return;
    }
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
  }

  ProcessEnterpriseDownloadResult(download_result);
}

void DeepScanningRequest::ProcessEnterpriseDownloadResult(
    DownloadCheckResult download_result) {
  LogDeepScanResult(download_result, trigger_,
                    metadata_->IsTopLevelEncryptedArchive());
  MaybeFinishRequest(download_result);
}

void DeepScanningRequest::OnDownloadUpdated(download::DownloadItem* download) {
  CHECK(metadata_->IsForDownloadItem(download));

  if (ReportOnlyScan() &&
      download->GetState() == download::DownloadItem::COMPLETE &&
      !scanning_started_) {
    // Now that the download is complete in non-blocking mode, scanning can
    // start since the files have moved to their final destination.
    if (save_package_files_.empty()) {
      StartSingleFileScan();
    } else {
      StartSavePackageScan();
    }
  }

  if (download->GetState() == download::DownloadItem::CANCELLED) {
    is_cancelled_ = true;
  }
}

void DeepScanningRequest::OnDownloadDestroyed(
    download::DownloadItem* download) {
  CHECK(metadata_->IsForDownloadItem(download));

  if (download->IsSavePackageDownload()) {
    enterprise_connectors::RunSavePackageScanningCallback(download, false);
  }

  // We can't safely return a verdict for this download because it's already
  // been destroyed, so reset the callback here. We still need to run
  // `FinishRequest` to notify the DownloadProtectionService that this deep
  // scan has finished.
  callback_.Reset();

  // Store a weak pointer to `this` before executing cancellation. If the cancel
  // request processes synchronously, it might execute request callbacks that
  // synchronously trigger `CallbackAndCleanup()` and destroy this instance.
  base::WeakPtr<DeepScanningRequest> weak_this = weak_ptr_factory_.GetWeakPtr();

  content::BrowserContext* browser_context =
      metadata_ ? metadata_->GetBrowserContext() : nullptr;
  if (browser_context) {
    Profile* profile = Profile::FromBrowserContext(browser_context);
    enterprise_connectors::BinaryUploadService* upload_service =
        download_service_->GetBinaryUploadService(profile, analysis_settings_);

    if (upload_service) {
      auto cancel =
          std::make_unique<enterprise_connectors::BinaryUploadCancelRequests>(
              analysis_settings_.cloud_or_local_settings);
      cancel->set_user_action_id(user_action_id_);

      // We do the best effort to cancel the requests in upload service.
      // 'This' may be destroyed in the call below.
      upload_service->MaybeCancelRequests(std::move(cancel));
    }
  }

  // Only proceed to finish the request and clean up if this instance has not
  // been synchronously destroyed during the cancel call above.
  if (weak_this) {
    // `FinishRequest` always clears the `download_observation` and `metadata`
    // (if it was not cleared yet), preventing use-after-free issues for
    // `download_item` after it's been destroyed.
    FinishRequest(DownloadCheckResult::UNKNOWN);
  }
}

const enterprise_connectors::AnalysisSettings& DeepScanningRequest::settings()
    const {
  return analysis_settings_;
}

signin::IdentityManager* DeepScanningRequest::identity_manager() const {
  return IdentityManagerFactory::GetForProfile(
      Profile::FromBrowserContext(metadata_->GetBrowserContext()));
}

int DeepScanningRequest::user_action_requests_count() const {
  if (!save_package_files_.empty()) {
    return save_package_files_.size();
  }
  return 1;
}

std::string DeepScanningRequest::tab_title() const {
  return "";
}

std::string DeepScanningRequest::user_action_id() const {
  return user_action_id_;
}

std::string DeepScanningRequest::email() const {
  return enterprise_connectors::GetProfileEmail(
      Profile::FromBrowserContext(metadata_->GetBrowserContext()));
}

const GURL& DeepScanningRequest::url() const {
  if (metadata_->GetURL().is_valid()) {
    return metadata_->GetURL();
  }
  return GURL::EmptyGURL();
}

const GURL& DeepScanningRequest::tab_url() const {
  if (metadata_->GetTabUrl().is_valid()) {
    return metadata_->GetTabUrl();
  }
  return GURL::EmptyGURL();
}

enterprise_connectors::ContentAnalysisRequest::Reason
DeepScanningRequest::reason() const {
  return reason_;
}

safe_browsing::ReferrerChain DeepScanningRequest::referrer_chain() const {
  return metadata_->GetReferrerChain();
}

google::protobuf::RepeatedPtrField<std::string>
DeepScanningRequest::frame_url_chain() const {
  return metadata_->CollectFrameUrls();
}

content::WebContents* DeepScanningRequest::web_contents() const {
  return metadata_->web_contents();
}

void DeepScanningRequest::MaybeFinishRequest(DownloadCheckResult result) {
  download_check_result_ =
      GetHighestPrecedenceResult(download_check_result_, result);
  enterprise_connectors::DecrementCrashKey(
      enterprise_connectors::ScanningCrashKey::PENDING_FILE_DOWNLOADS);

  if ((--pending_scan_requests_) == 0) {
    if (!save_package_files_.empty()) {
      content::WebContents* force_save_web_contents =
          MaybeGetWebContentsForForceSave(download_check_result_);
      if (force_save_web_contents) {
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
        if (CanBypassForceSaveDialogForAutomation()) {
          FinishRequest(download_check_result_);
          return;
        }
        base::OnceClosure keep_closure = base::BindOnce(
            &DeepScanningRequest::FinishRequest, weak_ptr_factory_.GetWeakPtr(),
            download_check_result_);
        base::OnceClosure discard_closure = base::BindOnce(
            &DeepScanningRequest::FinishRequest, weak_ptr_factory_.GetWeakPtr(),
            DownloadCheckResult::SENSITIVE_CONTENT_BLOCK);

        enterprise_connectors::ContentAnalysisResponse::Result::TriggeredRule::
            CustomRuleMessage custom_message;
        for (const auto& metadata : file_metadata_) {
          custom_message = GetForceSaveToCloudCustomRuleMessage(
              metadata.second.scan_response);
          if (custom_message.message_segments_size() > 0) {
            break;
          }
        }

        ShowForceSaveToCloudDialog(std::move(keep_closure),
                                   std::move(discard_closure),
                                   force_save_web_contents, custom_message,
                                   save_package_files_.size());
        return;
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
      }
    }
    FinishRequest(download_check_result_);
  }
}

void DeepScanningRequest::FinishRequest(DownloadCheckResult result) {
  // Metadata could be already destroyed in the previous call to
  // `MaybeFinishRequest`.
  if (!metadata_ || !metadata_->GetBrowserContext()) {
    return;
  }

  enterprise_connectors::EventResult event_result =
      enterprise_connectors::EventResult::UNKNOWN;

  if (!report_callbacks_.empty()) {
    DCHECK(IsEnterpriseTriggered());

    if (ReportOnlyScan()) {
      // The event result in report-only will always match whatever danger
      // type known before deep scanning since the UI will never be updated
      // based on `result`.
      event_result = metadata_->GetPreScanEventResult(pre_scan_danger_type_);
    } else if (is_cancelled_ && base::FeatureList::IsEnabled(
                                    enterprise_connectors::
                                        kEnableCancelUploadOnContentAnalysis)) {
      event_result = enterprise_connectors::EventResult::CANCELLED;
    } else {
      Profile* profile =
          Profile::FromBrowserContext(metadata_->GetBrowserContext());
      // If FinishRequest is reached with an unknown `result` or an explicit
      // failure, then it means no scanning request ever completed
      // successfully, so `event_result` needs to reflect whatever danger type
      // was known pre-deep scanning.
      event_result =
          (result == DownloadCheckResult::DEEP_SCANNED_FAILED ||
           result == DownloadCheckResult::UNKNOWN)
              ? metadata_->GetPreScanEventResult(pre_scan_danger_type_)
              : GetEventResult(result, profile);
    }

    report_callbacks_.Notify(event_result);
  }

  // If the deep-scanning result is unknown for whatever reason, `callback_`
  // should be called with whatever SB result was known prior to deep
  // scanning.
  if ((result == DownloadCheckResult::UNKNOWN ||
       result == DownloadCheckResult::DEEP_SCANNED_FAILED) &&
      IsEnterpriseTriggered()) {
    result = pre_scan_download_check_result_;
  }

  for (auto& observer : observers_) {
    observer.OnFinish(this);
  }

  AcknowledgeRequest(event_result);

  // Bypassed verdicts are given when a user continues a download after being
  // warned by WP, so it is considered safe here.
  // For obfuscated download files, deobfuscate it if the scan returns a safe
  // verdict or result is unknown.
  // TODO(crbug.com/378490429): Add support in obfuscation module for skipping
  // malware scan for password protected files.
  if ((event_result == enterprise_connectors::EventResult::ALLOWED ||
       event_result == enterprise_connectors::EventResult::BYPASSED ||
       result == DownloadCheckResult::UNKNOWN) &&
      metadata_->IsObfuscated()) {
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
        base::BindOnce(&enterprise_obfuscation::DeobfuscateFileInPlace,
                       metadata_->GetFullPath()),
        base::BindOnce(&DeepScanningRequest::OnDeobfuscationComplete,
                       weak_ptr_factory_.GetWeakPtr(), result));

    // Reset to prevent use-after-free issues.
    download_observation_.reset();
    metadata_.reset();

    return;
  }

  CallbackAndCleanup(result);
}

void DeepScanningRequest::OnDeobfuscationComplete(
    DownloadCheckResult result,
    base::expected<void, enterprise_obfuscation::Error> deobfuscation_result) {
  if (!deobfuscation_result.has_value()) {
    // TODO(b/367259664): Add better error handling for deobfuscation.
    DVLOG(1) << "Failed to deobfuscate downloaded file.";
  }

  CallbackAndCleanup(result);
}

void DeepScanningRequest::CallbackAndCleanup(DownloadCheckResult result) {
  if (!callback_.is_null()) {
    callback_.Run(result);
  }
  weak_ptr_factory_.InvalidateWeakPtrs();
  download_observation_.reset();
  metadata_.reset();
  download_service_->RequestFinished(this);
}

content::WebContents* DeepScanningRequest::MaybeGetWebContentsForForceSave(
    DownloadCheckResult& result) {
#if BUILDFLAG(IS_ANDROID)
  return nullptr;
#else
  if (result != DownloadCheckResult::FORCE_SAVE_TO_GDRIVE &&
      result != DownloadCheckResult::FORCE_SAVE_TO_ONEDRIVE) {
    return nullptr;
  }

  if (result == DownloadCheckResult::FORCE_SAVE_TO_GDRIVE &&
      !base::FeatureList::IsEnabled(
          enterprise_data_protection::kEnableForceDownloadToCloud)) {
    result = DownloadCheckResult::SENSITIVE_CONTENT_BLOCK;
    return nullptr;
  }

  content::WebContents* force_save_web_contents = web_contents();

  // `web_contents()` may be nullptr in several cases, if the tab owning
  // the download was opened by a download link, a restored page, or an
  // external application. For those cases, try to find the active web
  // contents of the browser to show the dialog on.
  if (!force_save_web_contents) {
    BrowserWindowInterface* browser =
        ProfileBrowserCollection::GetForProfile(
            Profile::FromBrowserContext(metadata_->GetBrowserContext()))
            ->GetLastActiveBrowser();
    if (browser) {
      force_save_web_contents =
          browser->GetTabStripModel()->GetActiveWebContents();
    }
  }

  return force_save_web_contents;
#endif  // BUILDFLAG(IS_ANDROID)
}

#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
void DeepScanningRequest::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) {
  new enterprise_connectors::ContentAnalysisDialogController(
      std::make_unique<enterprise_connectors::ContentAnalysisDownloadsDelegate>(
          metadata_->GetTargetFilePath().BaseName().AsUTF16Unsafe(), u"",
          GURL(), false, std::move(keep_closure), std::move(discard_closure),
          nullptr, custom_message,
          l10n_util::GetStringFUTF16(
              IDS_DEEP_SCANNING_DIALOG_SAVE_TO_CLOUD_STORAGE_MESSAGE,
              metadata_->GetTargetFilePath().BaseName().AsUTF16Unsafe())),
      true,  // Downloads are always cloud-based for now.
      web_contents, enterprise_connectors::DeepScanAccessPoint::DOWNLOAD,
      file_count,
      enterprise_connectors::FinalContentAnalysisResult::FORCE_SAVE_TO_CLOUD,
      nullptr);
}
#endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)

void DeepScanningRequest::MaybeUpdateDownloadCheckResult(
    const enterprise_connectors::ContentAnalysisResponse& response,
    DownloadCheckResult& download_result) {
  auto result_from_response = ResponseToDownloadCheckResult(response);
  if (result_from_response != DownloadCheckResult::DEEP_SCANNED_SAFE) {
    request_tokens_.push_back(response.request_token());
    download_result = result_from_response;
  }
}

bool DeepScanningRequest::ReportOnlyScan() {
  if (IsConsumerTriggered()) {
    return false;
  }

  return analysis_settings_.block_until_verdict ==
         enterprise_connectors::BlockUntilVerdict::kNoBlock;
}

void DeepScanningRequest::AcknowledgeRequest(
    enterprise_connectors::EventResult event_result) {
  Profile* profile =
      Profile::FromBrowserContext(metadata_->GetBrowserContext());
  enterprise_connectors::BinaryUploadService* binary_upload_service =
      download_service_->GetBinaryUploadService(profile, analysis_settings_);
  if (!binary_upload_service) {
    return;
  }

  // Calculate final action applied to all requests.
  auto final_action = GetFinalAction(event_result);

  for (auto& token : request_tokens_) {
    auto ack = std::make_unique<enterprise_connectors::BinaryUploadAck>(
        analysis_settings_.cloud_or_local_settings);
    ack->set_request_token(token);
    ack->set_status(
        enterprise_connectors::ContentAnalysisAcknowledgement::SUCCESS);
    ack->set_final_action(final_action);
    binary_upload_service->MaybeAcknowledge(std::move(ack));
  }
}

bool DeepScanningRequest::IsConsumerTriggered() const {
  switch (trigger_) {
    case DeepScanTrigger::TRIGGER_UNKNOWN:
    case DeepScanTrigger::TRIGGER_POLICY:
      return false;
    case DeepScanTrigger::TRIGGER_CONSUMER_PROMPT:
    case DeepScanTrigger::TRIGGER_ENCRYPTED_CONSUMER_PROMPT:
    case DeepScanTrigger::TRIGGER_IMMEDIATE_DEEP_SCAN:
      return true;
  }
}

bool DeepScanningRequest::IsEnterpriseTriggered() const {
  switch (trigger_) {
    case DeepScanTrigger::TRIGGER_UNKNOWN:
    case DeepScanTrigger::TRIGGER_CONSUMER_PROMPT:
    case DeepScanTrigger::TRIGGER_ENCRYPTED_CONSUMER_PROMPT:
    case DeepScanTrigger::TRIGGER_IMMEDIATE_DEEP_SCAN:
      return false;
    case DeepScanTrigger::TRIGGER_POLICY:
      return true;
  }
}

bool DeepScanningRequest::ShouldTerminateEarly(
    enterprise_connectors::ScanRequestUploadResult result) {
  CHECK(analysis_settings_.cloud_or_local_settings.is_cloud_analysis());

  return IsEnterpriseTriggered()
             ? EnterpriseResultIsFailure(
                   result, analysis_settings_.block_large_files,
                   analysis_settings_.block_password_protected_files)
             : result !=
                   enterprise_connectors::ScanRequestUploadResult::kSuccess;
}

void DeepScanningRequest::OpenDownload() {
  metadata_->OpenDownload();
  FinishRequest(DownloadCheckResult::UNKNOWN);
}

}  // namespace safe_browsing
