blob: fc97014651a955f8a70b0236af03626290db7d2a [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router.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/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/safe_browsing/deep_scanning_failure_modal_dialog.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/policy/core/common/cloud/dm_token.h"
#include "components/prefs/pref_service.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/url_matcher/url_matcher.h"
#include "content/public/browser/download_item_utils.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
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_UNSUPPORTED_FILE_TYPE,
DownloadCheckResult::POTENTIALLY_UNWANTED,
DownloadCheckResult::SENSITIVE_CONTENT_WARNING,
DownloadCheckResult::UNKNOWN,
DownloadCheckResult::DEEP_SCANNED_SAFE};
for (DownloadCheckResult result : kDownloadCheckResultPrecedence) {
if (result_1 == result || result_2 == result)
return result;
}
NOTREACHED();
return DownloadCheckResult::UNKNOWN;
}
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::UNKNOWN;
return;
}
*download_result = DownloadCheckResult::DEEP_SCANNED_SAFE;
}
EventResult GetEventResult(download::DownloadDangerType danger_type,
download::DownloadItem* item) {
DownloadCoreService* download_core_service =
DownloadCoreServiceFactory::GetForBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item));
if (download_core_service) {
ChromeDownloadManagerDelegate* delegate =
download_core_service->GetDownloadManagerDelegate();
if (delegate && delegate->ShouldBlockFile(item, danger_type)) {
return EventResult::BLOCKED;
}
}
switch (danger_type) {
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE:
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_ACCOUNT_COMPROMISE:
case download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_WARNING:
case download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT:
case download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
case download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
return EventResult::WARNED;
case download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
case download::DOWNLOAD_DANGER_TYPE_ALLOWLISTED_BY_POLICY:
return EventResult::ALLOWED;
case download::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_OPENED_DANGEROUS:
return EventResult::BYPASSED;
case download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_SENSITIVE_CONTENT_BLOCK:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_TOO_LARGE:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_PASSWORD_PROTECTED:
case download::DOWNLOAD_DANGER_TYPE_BLOCKED_UNSUPPORTED_FILETYPE:
case download::DOWNLOAD_DANGER_TYPE_DEEP_SCANNED_SAFE:
case download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING:
case download::DOWNLOAD_DANGER_TYPE_MAX:
NOTREACHED();
return EventResult::UNKNOWN;
}
}
EventResult GetEventResult(DownloadCheckResult download_result,
Profile* profile) {
auto download_restriction =
profile
? static_cast<DownloadPrefs::DownloadRestriction>(
profile->GetPrefs()->GetInteger(prefs::kDownloadRestrictions))
: DownloadPrefs::DownloadRestriction::NONE;
switch (download_result) {
case DownloadCheckResult::UNKNOWN:
case DownloadCheckResult::SAFE:
case DownloadCheckResult::ALLOWLISTED_BY_POLICY:
case DownloadCheckResult::DEEP_SCANNED_SAFE:
return 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 DownloadPrefs::DownloadRestriction::ALL_FILES:
case DownloadPrefs::DownloadRestriction::POTENTIALLY_DANGEROUS_FILES:
case DownloadPrefs::DownloadRestriction::DANGEROUS_FILES:
case DownloadPrefs::DownloadRestriction::MALICIOUS_FILES:
return EventResult::BLOCKED;
case DownloadPrefs::DownloadRestriction::NONE:
return EventResult::WARNED;
}
case DownloadCheckResult::UNCOMMON:
case DownloadCheckResult::POTENTIALLY_UNWANTED:
case DownloadCheckResult::SENSITIVE_CONTENT_WARNING:
return EventResult::WARNED;
case DownloadCheckResult::BLOCKED_PASSWORD_PROTECTED:
case DownloadCheckResult::BLOCKED_TOO_LARGE:
case DownloadCheckResult::SENSITIVE_CONTENT_BLOCK:
case DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE:
return EventResult::BLOCKED;
default:
NOTREACHED() << "Should never be final result";
break;
}
return EventResult::UNKNOWN;
}
std::string GetTriggerName(DeepScanningRequest::DeepScanTrigger trigger) {
switch (trigger) {
case DeepScanningRequest::DeepScanTrigger::TRIGGER_UNKNOWN:
return "Unknown";
case DeepScanningRequest::DeepScanTrigger::TRIGGER_APP_PROMPT:
return "AdvancedProtectionPrompt";
case DeepScanningRequest::DeepScanTrigger::TRIGGER_POLICY:
return "Policy";
}
}
bool ResultIsRetriable(BinaryUploadService::Result result) {
switch (result) {
case BinaryUploadService::Result::UNKNOWN:
case BinaryUploadService::Result::SUCCESS:
case BinaryUploadService::Result::UNAUTHORIZED:
case BinaryUploadService::Result::FILE_TOO_LARGE:
case BinaryUploadService::Result::FILE_ENCRYPTED:
case BinaryUploadService::Result::DLP_SCAN_UNSUPPORTED_FILE_TYPE:
return false;
case BinaryUploadService::Result::UPLOAD_FAILURE:
case BinaryUploadService::Result::TIMEOUT:
case BinaryUploadService::Result::FAILED_TO_GET_TOKEN:
case BinaryUploadService::Result::TOO_MANY_REQUESTS:
return true;
}
}
} // namespace
/* static */
absl::optional<enterprise_connectors::AnalysisSettings>
DeepScanningRequest::ShouldUploadBinary(download::DownloadItem* item) {
// Files already on the disk shouldn't be uploaded for scanning.
if (item->GetURL().SchemeIsFile())
return absl::nullopt;
auto* service =
enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item));
// If the download Connector is not enabled, don't scan.
if (!service ||
!service->IsConnectorEnabled(
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED)) {
return absl::nullopt;
}
// Check that item->GetURL() 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(
item->GetURL(),
enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED);
}
DeepScanningRequest::DeepScanningRequest(
download::DownloadItem* item,
DeepScanTrigger trigger,
DownloadCheckResult pre_scan_download_check_result,
CheckDownloadRepeatingCallback callback,
DownloadProtectionService* download_service,
enterprise_connectors::AnalysisSettings settings)
: item_(item),
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),
weak_ptr_factory_(this) {
base::UmaHistogramEnumeration("SBClientDownload.DeepScanType",
DeepScanType::NORMAL);
item_->AddObserver(this);
}
DeepScanningRequest::DeepScanningRequest(
download::DownloadItem* item,
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)
: item_(item),
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),
weak_ptr_factory_(this) {
base::UmaHistogramEnumeration("SBClientDownload.DeepScanType",
DeepScanType::SAVE_PACKAGE);
base::UmaHistogramCounts10000("SBClientDownload.SavePackageFileCount",
save_package_files_.size());
item_->AddObserver(this);
}
DeepScanningRequest::~DeepScanningRequest() {
item_->RemoveObserver(this);
}
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_ = item_->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;
}
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_, item_->GetFullPath(),
item_->GetTargetFilePath().BaseName(), item_->GetMimeType(),
/* delay_opening_file */ false,
base::BindOnce(&DeepScanningRequest::OnScanComplete,
weak_ptr_factory_.GetWeakPtr(), item_->GetFullPath()));
request->set_filename(item_->GetTargetFilePath().AsUTF8Unsafe());
std::string raw_digest_sha256 = item_->GetHash();
std::string sha256 =
base::HexEncode(raw_digest_sha256.data(), raw_digest_sha256.size());
request->set_digest(sha256);
file_metadata_.insert({item_->GetFullPath(),
enterprise_connectors::FileMetadata(
item_->GetTargetFilePath().AsUTF8Unsafe(), sha256,
item_->GetMimeType(), item_->GetTotalBytes())});
Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item_));
base::UmaHistogramEnumeration("SBClientDownload.DeepScanTrigger", trigger_);
PopulateRequest(request.get(), profile, item_->GetFullPath());
PrepareClientDownloadRequest(item_->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(
content::DownloadItemUtils::GetBrowserContext(item_));
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::OnGotRequestData, 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) {
if (trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
if (analysis_settings_.cloud_or_local_settings.is_cloud_analysis()) {
request->set_device_token(
analysis_settings_.cloud_or_local_settings.dm_token());
}
request->set_per_profile_request(analysis_settings_.per_profile);
if (analysis_settings_.client_metadata)
request->set_client_metadata(*analysis_settings_.client_metadata);
}
request->set_analysis_connector(enterprise_connectors::FILE_DOWNLOADED);
request->set_email(GetProfileEmail(profile));
if (item_->GetURL().is_valid())
request->set_url(item_->GetURL().spec());
if (item_->GetTabUrl().is_valid())
request->set_tab_url(item_->GetTabUrl());
if (file_metadata_.count(path) &&
!file_metadata_.at(path).mime_type.empty()) {
request->set_content_type(file_metadata_.at(path).mime_type);
}
for (const auto& tag : analysis_settings_.tags)
request->add_tag(tag.first);
}
void DeepScanningRequest::PrepareClientDownloadRequest(
const base::FilePath& current_path,
std::unique_ptr<FileAnalysisRequest> request) {
if (base::FeatureList::IsEnabled(kSafeBrowsingEnterpriseCsd) &&
trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
download_request_maker_ = DownloadRequestMaker::CreateFromDownloadItem(
new BinaryFeatureExtractor(), item_);
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::OnGotRequestData(
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 (result == BinaryUploadService::Result::SUCCESS) {
OnDownloadRequestReady(current_path, std::move(request), nullptr);
return;
}
OnScanComplete(current_path, result,
enterprise_connectors::ContentAnalysisResponse());
}
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(
content::DownloadItemUtils::GetBrowserContext(item_));
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(
/*access_point=*/DeepScanAccessPoint::DOWNLOAD,
/*duration=*/base::TimeTicks::Now() - upload_start_times_[current_path],
/*total_size=*/item_->GetTotalBytes(), /*result=*/result,
/*response=*/response);
DownloadCheckResult download_result = DownloadCheckResult::UNKNOWN;
if (result == BinaryUploadService::Result::SUCCESS) {
request_tokens_.push_back(response.request_token());
ResponseToDownloadCheckResult(response, &download_result);
base::UmaHistogramEnumeration(
"SBClientDownload.MalwareDeepScanResult." + GetTriggerName(trigger_),
download_result);
} else if (trigger_ == DeepScanTrigger::TRIGGER_APP_PROMPT &&
ResultIsRetriable(result) &&
MaybeShowDeepScanFailureModalDialog(
base::BindOnce(&DeepScanningRequest::Start,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DeepScanningRequest::FinishRequest,
weak_ptr_factory_.GetWeakPtr(),
DownloadCheckResult::UNKNOWN),
base::BindOnce(&DeepScanningRequest::FinishRequest,
weak_ptr_factory_.GetWeakPtr(),
DownloadCheckResult::UNKNOWN),
base::BindOnce(&DeepScanningRequest::OpenDownload,
weak_ptr_factory_.GetWeakPtr()))) {
for (auto& observer : observers_)
observer.OnModalShown(this);
return;
} 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 (result ==
BinaryUploadService::Result::DLP_SCAN_UNSUPPORTED_FILE_TYPE &&
analysis_settings_.block_unsupported_file_types) {
download_result = DownloadCheckResult::BLOCKED_UNSUPPORTED_FILE_TYPE;
}
Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item_));
DCHECK(file_metadata_.count(current_path));
file_metadata_.at(current_path).scan_response = std::move(response);
if (profile && trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
const auto& file_metadata = file_metadata_.at(current_path);
report_callbacks_.AddUnsafe(base::BindOnce(
&MaybeReportDeepScanningVerdict, profile, item_->GetURL(),
file_metadata.filename, file_metadata.sha256, file_metadata.mime_type,
extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
DeepScanAccessPoint::DOWNLOAD, file_metadata.size, result,
file_metadata.scan_response));
enterprise_connectors::ScanResult* stored_result =
static_cast<enterprise_connectors::ScanResult*>(
item_->GetUserData(enterprise_connectors::ScanResult::kKey));
if (stored_result) {
stored_result->file_metadata.push_back(file_metadata);
} else {
auto scan_result =
std::make_unique<enterprise_connectors::ScanResult>(file_metadata);
item_->SetUserData(enterprise_connectors::ScanResult::kKey,
std::move(scan_result));
}
}
MaybeFinishRequest(download_result);
}
void DeepScanningRequest::OnDownloadUpdated(download::DownloadItem* download) {
DCHECK_EQ(download, item_);
if (ReportOnlyScan() &&
item_->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) {
DCHECK_EQ(download, item_);
if (download->IsSavePackageDownload()) {
enterprise_connectors::RunSavePackageScanningCallback(download, false);
}
FinishRequest(DownloadCheckResult::UNKNOWN);
}
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) {
if (!report_callbacks_.empty()) {
DCHECK_EQ(trigger_, DeepScanTrigger::TRIGGER_POLICY);
EventResult event_result;
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 = GetEventResult(pre_scan_danger_type_, item_);
} else {
Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item_));
// If FinishRequest is reached with an unknown `result`, 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::UNKNOWN
? GetEventResult(pre_scan_danger_type_, item_)
: 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 &&
trigger_ == DeepScanTrigger::TRIGGER_POLICY) {
result = pre_scan_download_check_result_;
}
for (auto& observer : observers_)
observer.OnFinish(this);
AcknowledgeRequest();
if (!callback_.is_null())
callback_.Run(result);
weak_ptr_factory_.InvalidateWeakPtrs();
item_->RemoveObserver(this);
download_service_->RequestFinished(this);
}
bool DeepScanningRequest::MaybeShowDeepScanFailureModalDialog(
base::OnceClosure accept_callback,
base::OnceClosure cancel_callback,
base::OnceClosure close_callback,
base::OnceClosure open_now_callback) {
Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item_));
if (!profile)
return false;
Browser* browser =
chrome::FindTabbedBrowser(profile, /*match_original_profiles=*/false);
if (!browser)
return false;
DeepScanningFailureModalDialog::ShowForWebContents(
browser->tab_strip_model()->GetActiveWebContents(),
std::move(accept_callback), std::move(cancel_callback),
std::move(close_callback), std::move(open_now_callback));
return true;
}
void DeepScanningRequest::OpenDownload() {
item_->OpenDownload();
FinishRequest(DownloadCheckResult::UNKNOWN);
}
bool DeepScanningRequest::ReportOnlyScan() {
if (trigger_ == DeepScanTrigger::TRIGGER_APP_PROMPT)
return false;
return base::FeatureList::IsEnabled(kConnectorsScanningReportOnlyUI) &&
analysis_settings_.block_until_verdict ==
enterprise_connectors::BlockUntilVerdict::kNoBlock;
}
void DeepScanningRequest::AcknowledgeRequest() {
Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(item_));
BinaryUploadService* binary_upload_service =
download_service_->GetBinaryUploadService(profile, analysis_settings_);
if (!binary_upload_service)
return;
// Calculate overall status for all requests.
// TODO(b/240629222): Calculate status based on final result.
auto status = enterprise_connectors::ContentAnalysisAcknowledgement::SUCCESS;
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(status);
binary_upload_service->MaybeAcknowledge(std::move(ack));
}
}
} // namespace safe_browsing