| // 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/functional/bind.h" | 
 | #include "base/functional/callback_helpers.h" | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/observer_list.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/task/thread_pool.h" | 
 | #include "chrome/browser/browser_process.h" | 
 | #include "chrome/browser/download/download_item_warning_data.h" | 
 | #include "chrome/browser/download/download_prefs.h" | 
 | #include "chrome/browser/policy/dm_token_utils.h" | 
 | #include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.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/cloud_content_scanning/file_opening_job.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/browser/ui/browser.h" | 
 | #include "chrome/browser/ui/browser_finder.h" | 
 | #include "chrome/browser/ui/browser_window.h" | 
 | #include "chrome/common/pref_names.h" | 
 | #include "components/download/public/common/download_item.h" | 
 | #include "components/enterprise/common/proto/connectors.pb.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/safe_browsing_ui.h" | 
 | #include "components/safe_browsing/core/common/features.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" | 
 |  | 
 | #if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS) | 
 | #include "chrome/browser/enterprise/connectors/analysis/content_analysis_features.h" | 
 | #include "chrome/browser/enterprise/connectors/common.h" | 
 | #include "chrome/browser/enterprise/connectors/connectors_service.h" | 
 | #endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS) | 
 |  | 
 | using DeepScanTrigger = DownloadItemWarningData::DeepScanTrigger; | 
 |  | 
 | namespace safe_browsing { | 
 |  | 
 | namespace { | 
 |  | 
 | 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::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(); | 
 | } | 
 |  | 
 | void ResponseToDownloadCheckResult( | 
 |     const enterprise_connectors::ContentAnalysisResponse& response, | 
 |     DownloadCheckResult* download_result) { | 
 |   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; | 
 |  | 
 |   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 (malware_action == enterprise_connectors::GetHighestPrecedenceAction( | 
 |                             malware_action, dlp_action)) { | 
 |     switch (malware_action) { | 
 |       case enterprise_connectors::TriggeredRule::BLOCK: | 
 |         *download_result = DownloadCheckResult::DANGEROUS; | 
 |         return; | 
 |       case enterprise_connectors::TriggeredRule::WARN: | 
 |         *download_result = DownloadCheckResult::POTENTIALLY_UNWANTED; | 
 |         return; | 
 |       case enterprise_connectors::TriggeredRule::REPORT_ONLY: | 
 |       case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED: | 
 |         break; | 
 |     } | 
 |   } else { | 
 |     switch (dlp_action) { | 
 |       case enterprise_connectors::TriggeredRule::BLOCK: | 
 |         *download_result = DownloadCheckResult::SENSITIVE_CONTENT_BLOCK; | 
 |         return; | 
 |       case enterprise_connectors::TriggeredRule::WARN: | 
 |         *download_result = DownloadCheckResult::SENSITIVE_CONTENT_WARNING; | 
 |         return; | 
 |       case enterprise_connectors::TriggeredRule::REPORT_ONLY: | 
 |       case enterprise_connectors::TriggeredRule::ACTION_UNSPECIFIED: | 
 |         break; | 
 |     } | 
 |   } | 
 |  | 
 |   if (dlp_scan_failure || malware_scan_failure) { | 
 |     *download_result = DownloadCheckResult::DEEP_SCANNED_FAILED; | 
 |     return; | 
 |   } | 
 |  | 
 |   *download_result = DownloadCheckResult::DEEP_SCANNED_SAFE; | 
 | } | 
 |  | 
 | 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::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; | 
 |     case enterprise_connectors::EventResult::BLOCKED: | 
 |       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(BinaryUploadService::Result 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, | 
 |                           BinaryUploadService::Result result) { | 
 |   const std::string result_info = | 
 |       safe_browsing::BinaryUploadService::ResultToString(result); | 
 |   safe_browsing::WebUIInfoSingleton::GetInstance()->AddToDeepScanRequests( | 
 |       request->per_profile_request(), /*access_token*/ "", | 
 |       /*upload_info*/ base::StrCat({"Skipped - ", result_info}), | 
 |       /*upload_url*/ "", request->content_analysis_request()); | 
 |   safe_browsing::WebUIInfoSingleton::GetInstance()->AddToDeepScanResponses( | 
 |       /*token=*/"", result_info, | 
 |       enterprise_connectors::ContentAnalysisResponse()); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | /* static */ | 
 | std::optional<enterprise_connectors::AnalysisSettings> | 
 | DeepScanningRequest::ShouldUploadBinary(const DeepScanningMetadata& metadata) { | 
 | #if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS) | 
 |   // 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); | 
 | #else | 
 |   return std::nullopt; | 
 | #endif  // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS) | 
 | } | 
 |  | 
 | 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), | 
 |       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), | 
 |       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; | 
 |   IncrementCrashKey(ScanningCrashKey::PENDING_FILE_DOWNLOADS); | 
 |   IncrementCrashKey(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()); | 
 |  | 
 |   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; | 
 |   IncrementCrashKey(ScanningCrashKey::PENDING_FILE_DOWNLOADS, | 
 |                     pending_scan_requests_); | 
 |   IncrementCrashKey(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()); | 
 |  | 
 |   file_opening_job_ = std::make_unique<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 (file_metadata_.count(path) && | 
 |       !file_metadata_.at(path).mime_type.empty()) { | 
 |     request->set_content_type(file_metadata_.at(path).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, | 
 |     BinaryUploadService::Result result, | 
 |     BinaryUploadService::Request::Data data) { | 
 |   file_metadata_.insert({current_path, enterprise_connectors::FileMetadata( | 
 |                                            final_path.AsUTF8Unsafe(), data.hash, | 
 |                                            data.mime_type, data.size)}); | 
 |   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, | 
 |     BinaryUploadService::Result result, | 
 |     BinaryUploadService::Request::Data data) { | 
 |   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()); | 
 |   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, BinaryUploadService::Result::UNKNOWN, | 
 |                    enterprise_connectors::ContentAnalysisResponse()); | 
 |   } | 
 | } | 
 |  | 
 | void DeepScanningRequest::OnScanComplete( | 
 |     const base::FilePath& current_path, | 
 |     BinaryUploadService::Result result, | 
 |     enterprise_connectors::ContentAnalysisResponse response) { | 
 |   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, | 
 |     BinaryUploadService::Result result, | 
 |     enterprise_connectors::ContentAnalysisResponse response) { | 
 |   bool is_invalid_password = | 
 |       result == BinaryUploadService::Result::FILE_ENCRYPTED || | 
 |       (result == BinaryUploadService::Result::SUCCESS && | 
 |        metadata_->IsTopLevelEncryptedArchive() && | 
 |        HasDecryptionFailedResult(response)); | 
 |   bool is_success = | 
 |       result == BinaryUploadService::Result::SUCCESS && !is_invalid_password; | 
 |   CHECK(IsConsumerTriggered()); | 
 |   DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN; | 
 |   if (is_success) { | 
 |     request_tokens_.push_back(response.request_token()); | 
 |     ResponseToDownloadCheckResult(response, &download_result); | 
 |     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, | 
 |     BinaryUploadService::Result result, | 
 |     enterprise_connectors::ContentAnalysisResponse response) { | 
 |   CHECK(IsEnterpriseTriggered()); | 
 |   DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN; | 
 |   if (result == BinaryUploadService::Result::SUCCESS) { | 
 |     request_tokens_.push_back(response.request_token()); | 
 |     ResponseToDownloadCheckResult(response, &download_result); | 
 |   } else if (result == BinaryUploadService::Result::FILE_TOO_LARGE && | 
 |              analysis_settings_.block_large_files) { | 
 |     download_result = DownloadCheckResult::BLOCKED_TOO_LARGE; | 
 |   } else if (result == BinaryUploadService::Result::FILE_ENCRYPTED && | 
 |              analysis_settings_.block_password_protected_files) { | 
 |     download_result = DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED; | 
 |   } else if (enterprise_connectors::ResultIsFailClosed(result) && | 
 |              analysis_settings_.default_action == | 
 |                  enterprise_connectors::DefaultAction::kBlock) { | 
 |     download_result = DownloadCheckResult::BLOCKED_SCAN_FAILED; | 
 |   } | 
 |  | 
 |   LogDeepScanResult(download_result, trigger_, | 
 |                     metadata_->IsTopLevelEncryptedArchive()); | 
 |  | 
 |   Profile* profile = | 
 |       Profile::FromBrowserContext(metadata_->GetBrowserContext()); | 
 |   DCHECK(file_metadata_.count(current_path)); | 
 |   file_metadata_.at(current_path).scan_response = std::move(response); | 
 |   if (profile) { | 
 |     safe_browsing::ReferrerChain referrers = referrer_chain(); | 
 |     const auto& file_metadata = file_metadata_.at(current_path); | 
 |     report_callbacks_.AddUnsafe(base::BindOnce( | 
 |         &MaybeReportDeepScanningVerdict, profile, this, /*source=*/"", | 
 |         /*destination=*/"", file_metadata.filename, file_metadata.sha256, | 
 |         file_metadata.mime_type, | 
 |         enterprise_connectors::kFileDownloadDataTransferEventTrigger, | 
 |         /*content_transfer_method=*/"", GetContentAreaAccountEmail(), | 
 |         file_metadata.size, referrers, result, file_metadata.scan_response)); | 
 |  | 
 |     metadata_->AddScanResultMetadata(file_metadata); | 
 |   } | 
 |  | 
 |   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(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | 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(); | 
 |  | 
 |   // `FinishRequest` always clears the `download_observation` and `metadata`, | 
 |   // 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 ""; | 
 | } | 
 |  | 
 | 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(); | 
 | } | 
 |  | 
 | void DeepScanningRequest::MaybeFinishRequest(DownloadCheckResult result) { | 
 |   download_check_result_ = | 
 |       GetHighestPrecedenceResult(download_check_result_, result); | 
 |   DecrementCrashKey(ScanningCrashKey::PENDING_FILE_DOWNLOADS); | 
 |  | 
 |   if ((--pending_scan_requests_) == 0) { | 
 |     FinishRequest(download_check_result_); | 
 |   } | 
 | } | 
 |  | 
 | void DeepScanningRequest::FinishRequest(DownloadCheckResult result) { | 
 |   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 { | 
 |       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); | 
 | } | 
 |  | 
 | 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()); | 
 |   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<BinaryUploadService::Ack>( | 
 |         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( | 
 |     BinaryUploadService::Result 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 != BinaryUploadService::Result::SUCCESS; | 
 | } | 
 |  | 
 | void DeepScanningRequest::OpenDownload() { | 
 |   metadata_->OpenDownload(); | 
 |   FinishRequest(DownloadCheckResult::UNKNOWN); | 
 | } | 
 |  | 
 | }  // namespace safe_browsing |