blob: 8ab6754afe3ff4af7998486293204c32ecbc018b [file] [log] [blame]
// Copyright 2019 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/enterprise/connectors/analysis/content_analysis_delegate.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <string>
#include <utility>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/connectors/analysis/analysis_settings.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.h"
#include "chrome/browser/enterprise/connectors/analysis/files_request_handler.h"
#include "chrome/browser/enterprise/connectors/analysis/page_print_analysis_request.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/file_util_service.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/profiles/profile.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/download_protection/check_client_download_request.h"
#include "chrome/grit/generated_resources.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "components/policy/core/common/chrome_schema.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/url_matcher/url_matcher.h"
#include "content/public/browser/web_contents.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "net/base/mime_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
using safe_browsing::BinaryUploadService;
namespace enterprise_connectors {
namespace {
// Global pointer of factory function (RepeatingCallback) used to create
// instances of ContentAnalysisDelegate in tests. !is_null() only in tests.
ContentAnalysisDelegate::Factory* GetFactoryStorage() {
static base::NoDestructor<ContentAnalysisDelegate::Factory> factory;
return factory.get();
}
bool* UIEnabledStorage() {
static bool enabled = true;
return &enabled;
}
ContentAnalysisDelegate::OnAckAllRequestsCallback* OnAckAllRequestsStorage() {
static base::NoDestructor<ContentAnalysisDelegate::OnAckAllRequestsCallback>
callback;
return callback.get();
}
} // namespace
StringAnalysisRequest::StringAnalysisRequest(
CloudOrLocalAnalysisSettings settings,
std::string text,
BinaryUploadService::ContentAnalysisCallback callback)
: Request(std::move(callback), std::move(settings)) {
data_.size = text.size();
// Only remember strings less than the maximum allowed.
if (text.size() < BinaryUploadService::kMaxUploadSizeBytes) {
data_.contents = std::move(text);
result_ = BinaryUploadService::Result::SUCCESS;
}
safe_browsing::IncrementCrashKey(
safe_browsing::ScanningCrashKey::PENDING_TEXT_UPLOADS);
safe_browsing::IncrementCrashKey(
safe_browsing::ScanningCrashKey::TOTAL_TEXT_UPLOADS);
}
StringAnalysisRequest::~StringAnalysisRequest() {
safe_browsing::DecrementCrashKey(
safe_browsing::ScanningCrashKey::PENDING_TEXT_UPLOADS);
}
void StringAnalysisRequest::GetRequestData(DataCallback callback) {
std::move(callback).Run(result_, data_);
}
ContentAnalysisDelegate::Data::Data() = default;
ContentAnalysisDelegate::Data::Data(Data&& other) = default;
ContentAnalysisDelegate::Data& ContentAnalysisDelegate::Data::operator=(
ContentAnalysisDelegate::Data&& other) = default;
ContentAnalysisDelegate::Data::~Data() = default;
ContentAnalysisDelegate::Result::Result() = default;
ContentAnalysisDelegate::Result::Result(Result&& other) = default;
ContentAnalysisDelegate::Result::~Result() = default;
ContentAnalysisDelegate::~ContentAnalysisDelegate() = default;
void ContentAnalysisDelegate::BypassWarnings(
absl::optional<std::u16string> user_justification) {
if (callback_.is_null())
return;
// Mark the full text as complying and report a warning bypass.
if (text_warning_) {
std::fill(result_.text_results.begin(), result_.text_results.end(), true);
int64_t content_size = 0;
for (const std::string& entry : data_.text)
content_size += entry.size();
ReportAnalysisConnectorWarningBypass(
profile_, url_, "", "", "Text data", std::string(), "text/plain",
extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
access_point_, content_size, text_response_, user_justification);
}
// Mark the full image as complying and report a warning bypass.
if (image_warning_) {
result_.image_result = true;
ReportAnalysisConnectorWarningBypass(
profile_, url_, "", "", "Image data", std::string(),
/*mime_type*/ std::string(),
extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
access_point_, data_.image.size(), image_response_, user_justification);
}
if (!warned_file_indices_.empty()) {
files_request_handler_->ReportWarningBypass(user_justification);
// Mark every warned file as complying.
for (size_t index : warned_file_indices_) {
result_.paths_results[index] = true;
}
}
// Mark the printed page as complying and report a warning bypass.
if (page_warning_) {
result_.page_result = true;
ReportAnalysisConnectorWarningBypass(
profile_, url_, "", "", title_, /*sha256*/ std::string(),
/*mime_type*/ std::string(),
extensions::SafeBrowsingPrivateEventRouter::kTriggerPagePrint,
access_point_, /*content_size*/ -1, page_response_, user_justification);
}
RunCallback();
}
void ContentAnalysisDelegate::Cancel(bool warning) {
if (callback_.is_null())
return;
// Don't report this upload as cancelled if the user didn't bypass the
// warning.
if (!warning) {
RecordDeepScanMetrics(
data_.settings.cloud_or_local_settings.is_cloud_analysis(),
access_point_, base::TimeTicks::Now() - upload_start_time_, 0,
"CancelledByUser", false);
}
// Ask the binary upload service to cancel requests if it can.
auto cancel = std::make_unique<BinaryUploadService::CancelRequests>(
data_.settings.cloud_or_local_settings);
cancel->set_user_action_id(user_action_id_);
BinaryUploadService* upload_service = GetBinaryUploadService();
if (upload_service)
upload_service->MaybeCancelRequests(std::move(cancel));
// Make sure to reject everything.
FillAllResultsWith(false);
RunCallback();
}
absl::optional<std::u16string> ContentAnalysisDelegate::GetCustomMessage()
const {
auto element = data_.settings.tags.find(final_result_tag_);
if (element != data_.settings.tags.end() &&
!element->second.custom_message.message.empty()) {
return l10n_util::GetStringFUTF16(IDS_DEEP_SCANNING_DIALOG_CUSTOM_MESSAGE,
element->second.custom_message.message);
}
return absl::nullopt;
}
absl::optional<GURL> ContentAnalysisDelegate::GetCustomLearnMoreUrl() const {
auto element = data_.settings.tags.find(final_result_tag_);
if (element != data_.settings.tags.end() &&
element->second.custom_message.learn_more_url.is_valid() &&
!element->second.custom_message.learn_more_url.is_empty()) {
return element->second.custom_message.learn_more_url;
}
return absl::nullopt;
}
bool ContentAnalysisDelegate::BypassRequiresJustification() const {
if (!base::FeatureList::IsEnabled(kBypassJustificationEnabled))
return false;
return data_.settings.tags.count(final_result_tag_) &&
data_.settings.tags.at(final_result_tag_).requires_justification;
}
std::u16string ContentAnalysisDelegate::GetBypassJustificationLabel() const {
return l10n_util::GetStringUTF16(
IDS_DEEP_SCANNING_DIALOG_UPLOAD_BYPASS_JUSTIFICATION_LABEL);
}
absl::optional<std::u16string>
ContentAnalysisDelegate::OverrideCancelButtonText() const {
return absl::nullopt;
}
// static
bool ContentAnalysisDelegate::IsEnabled(Profile* profile,
GURL url,
Data* data,
AnalysisConnector connector) {
auto* service = ConnectorsServiceFactory::GetForBrowserContext(profile);
// If the corresponding Connector policy isn't set, don't perform scans.
if (!service || !service->IsConnectorEnabled(connector))
return false;
// Check that |url| matches the appropriate URL patterns by getting settings.
// No settings means no matches were found.
auto settings = service->GetAnalysisSettings(url, connector);
if (!settings.has_value()) {
return false;
}
data->settings = std::move(settings.value());
if (url.is_valid())
data->url = url;
return true;
}
// static
void ContentAnalysisDelegate::CreateForWebContents(
content::WebContents* web_contents,
Data data,
CompletionCallback callback,
safe_browsing::DeepScanAccessPoint access_point) {
Factory* testing_factory = GetFactoryStorage();
bool wait_for_verdict =
data.settings.block_until_verdict == BlockUntilVerdict::kBlock;
// Using new instead of std::make_unique<> to access non public constructor.
auto delegate = testing_factory->is_null()
? base::WrapUnique(new ContentAnalysisDelegate(
web_contents, std::move(data), std::move(callback),
access_point))
: testing_factory->Run(web_contents, std::move(data),
std::move(callback));
bool work_being_done = delegate->UploadData();
// Only show UI if work is being done in the background, the user must
// wait for a verdict.
bool show_ui = work_being_done && wait_for_verdict && (*UIEnabledStorage());
// If the UI is enabled, create the modal dialog.
if (show_ui) {
ContentAnalysisDelegate* delegate_ptr = delegate.get();
int files_count = delegate_ptr->data_.paths.size();
// This dialog is owned by the constrained_window code.
delegate_ptr->dialog_ = new ContentAnalysisDialog(
std::move(delegate),
delegate_ptr->data_.settings.cloud_or_local_settings
.is_cloud_analysis(),
web_contents, access_point, files_count);
return;
}
if (!wait_for_verdict || !work_being_done) {
// The UI will not be shown but the policy is set to not wait for the
// verdict, or no scans need to be performed. Inform the caller that they
// may proceed.
//
// Supporting "wait for verdict" while not showing a UI makes writing tests
// for callers of this code easier.
delegate->FillAllResultsWith(true);
delegate->RunCallback();
}
// If all requests are already done, just let `delegate` go out of scope.
if (delegate->all_work_done_) {
return;
}
// ... otherwise, let the last response from the upload service callback
// delete the delegate when there is no more work.
if (work_being_done) {
delegate.release();
}
}
// static
void ContentAnalysisDelegate::SetFactoryForTesting(Factory factory) {
*GetFactoryStorage() = factory;
}
// static
void ContentAnalysisDelegate::ResetFactoryForTesting() {
if (GetFactoryStorage())
GetFactoryStorage()->Reset();
}
// static
void ContentAnalysisDelegate::DisableUIForTesting() {
*UIEnabledStorage() = false;
}
// static
void ContentAnalysisDelegate::SetOnAckAllRequestsCallbackForTesting(
OnAckAllRequestsCallback callback) {
*OnAckAllRequestsStorage() = std::move(callback);
}
ContentAnalysisDelegate::ContentAnalysisDelegate(
content::WebContents* web_contents,
Data data,
CompletionCallback callback,
safe_browsing::DeepScanAccessPoint access_point)
: data_(std::move(data)),
callback_(std::move(callback)),
access_point_(access_point) {
DCHECK(web_contents);
profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
url_ = web_contents->GetLastCommittedURL();
title_ = base::UTF16ToUTF8(web_contents->GetTitle());
std::string user_action_token = base::RandBytesAsString(128);
user_action_id_ =
base::HexEncode(user_action_token.data(), user_action_token.size());
page_content_type_ = web_contents->GetContentsMimeType();
result_.text_results.resize(data_.text.size(), false);
result_.image_result = false;
result_.paths_results.resize(data_.paths.size(), false);
result_.page_result = false;
}
void ContentAnalysisDelegate::StringRequestCallback(
BinaryUploadService::Result result,
ContentAnalysisResponse response) {
// Remember to send an ack for this response.
if (result == safe_browsing::BinaryUploadService::Result::SUCCESS)
final_actions_[response.request_token()] = GetAckFinalAction(response);
int64_t content_size = 0;
for (const std::string& entry : data_.text)
content_size += entry.size();
RecordDeepScanMetrics(
data_.settings.cloud_or_local_settings.is_cloud_analysis(), access_point_,
base::TimeTicks::Now() - upload_start_time_, content_size, result,
response);
text_request_complete_ = true;
string_request_result_ =
CalculateRequestHandlerResult(data_.settings, result, response);
bool text_complies = string_request_result_.complies;
bool should_warn = string_request_result_.final_result ==
FinalContentAnalysisResult::WARNING;
std::fill(result_.text_results.begin(), result_.text_results.end(),
text_complies);
MaybeReportDeepScanningVerdict(
profile_, url_, "", "", "Text data", std::string(), "text/plain",
extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
access_point_, content_size, result, response,
CalculateEventResult(data_.settings, text_complies, should_warn));
UpdateFinalResult(string_request_result_.final_result,
string_request_result_.tag);
if (should_warn) {
text_warning_ = true;
text_response_ = std::move(response);
}
MaybeCompleteScanRequest();
}
void ContentAnalysisDelegate::ImageRequestCallback(
BinaryUploadService::Result result,
ContentAnalysisResponse response) {
// Remember to send an ack for this response.
if (result == safe_browsing::BinaryUploadService::Result::SUCCESS) {
final_actions_[response.request_token()] = GetAckFinalAction(response);
}
RecordDeepScanMetrics(
data_.settings.cloud_or_local_settings.is_cloud_analysis(), access_point_,
base::TimeTicks::Now() - upload_start_time_, data_.image.size(), result,
response);
image_request_complete_ = true;
image_request_result_ =
CalculateRequestHandlerResult(data_.settings, result, response);
bool image_complies = image_request_result_.complies;
bool should_warn =
image_request_result_.final_result == FinalContentAnalysisResult::WARNING;
result_.image_result = image_complies;
MaybeReportDeepScanningVerdict(
profile_, url_, "", "", "Image data", std::string(),
/*mime_type*/ std::string(),
extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
access_point_, data_.image.size(), result, response,
CalculateEventResult(data_.settings, image_complies, should_warn));
UpdateFinalResult(image_request_result_.final_result,
image_request_result_.tag);
if (should_warn) {
image_warning_ = true;
image_response_ = std::move(response);
}
MaybeCompleteScanRequest();
}
void ContentAnalysisDelegate::FilesRequestCallback(
std::vector<RequestHandlerResult> results) {
// Remember to send acks for any responses.
files_request_handler_->AppendFinalActionsTo(&final_actions_);
// No reporting here, because the MultiFileRequestHandler does that.
DCHECK_EQ(results.size(), result_.paths_results.size());
for (size_t index = 0; index < results.size(); ++index) {
FinalContentAnalysisResult result = results[index].final_result;
result_.paths_results[index] = results[index].complies;
if (result == FinalContentAnalysisResult::WARNING) {
warned_file_indices_.push_back(index);
}
UpdateFinalResult(result, results[index].tag);
}
files_request_results_ = std::move(results);
files_request_complete_ = true;
MaybeCompleteScanRequest();
}
FilesRequestHandler*
ContentAnalysisDelegate::GetFilesRequestHandlerForTesting() {
return files_request_handler_.get();
}
bool ContentAnalysisDelegate::ShowFinalResultInDialog() {
if (!dialog_)
return false;
dialog_->ShowResult(final_result_);
return true;
}
bool ContentAnalysisDelegate::CancelDialog() {
if (!dialog_)
return false;
return dialog_->CancelDialogAndDelete();
}
void ContentAnalysisDelegate::PageRequestCallback(
BinaryUploadService::Result result,
ContentAnalysisResponse response) {
// Remember to send an ack for this response.
if (result == safe_browsing::BinaryUploadService::Result::SUCCESS)
final_actions_[response.request_token()] = GetAckFinalAction(response);
RecordDeepScanMetrics(
data_.settings.cloud_or_local_settings.is_cloud_analysis(), access_point_,
base::TimeTicks::Now() - upload_start_time_, page_size_bytes_, result,
response);
page_request_complete_ = true;
RequestHandlerResult request_handler_result =
CalculateRequestHandlerResult(data_.settings, result, response);
result_.page_result = request_handler_result.complies;
bool should_warn = request_handler_result.final_result ==
FinalContentAnalysisResult::WARNING;
MaybeReportDeepScanningVerdict(
profile_, url_, "", "", title_, /*sha256*/ std::string(),
/*mime_type*/ std::string(),
extensions::SafeBrowsingPrivateEventRouter::kTriggerPagePrint,
access_point_, /*content_size*/ -1, result, response,
CalculateEventResult(data_.settings, result_.page_result, should_warn));
UpdateFinalResult(request_handler_result.final_result,
request_handler_result.tag);
if (should_warn) {
page_warning_ = true;
page_response_ = std::move(response);
}
MaybeCompleteScanRequest();
}
bool ContentAnalysisDelegate::UploadData() {
upload_start_time_ = base::TimeTicks::Now();
// Create a text request, an image request, a page request and a file request
// for each file.
PrepareTextRequest();
PrepareImageRequest();
PreparePageRequest();
if (!data_.paths.empty()) {
// Passing the settings using a reference is safe here, because
// MultiFileRequestHandler is owned by this class.
files_request_handler_ = FilesRequestHandler::Create(
GetBinaryUploadService(), profile_, data_.settings, url_, "", "",
user_action_id_, title_, access_point_, data_.paths,
base::BindOnce(&ContentAnalysisDelegate::FilesRequestCallback,
GetWeakPtr()));
files_request_complete_ = !files_request_handler_->UploadData();
} else {
// If no files should be uploaded, the file request is complete.
files_request_complete_ = true;
}
data_uploaded_ = true;
// Do not add code under this comment. The above line should be the last thing
// this function does before the return statement.
return !text_request_complete_ || !image_request_complete_ ||
!files_request_complete_ || !page_request_complete_;
}
void ContentAnalysisDelegate::PrepareTextRequest() {
std::string full_text;
for (const std::string& text : data_.text)
full_text.append(text);
// The request is considered complete if there is no text or if the text is
// too small compared to the minimum size. This means a minimum_data_size of
// 0 is equivalent to no minimum, as the second part of the "or" will always
// be false.
text_request_complete_ =
full_text.empty() || full_text.size() < data_.settings.minimum_data_size;
if (!full_text.empty()) {
base::UmaHistogramCustomCounts("Enterprise.OnBulkDataEntry.DataSize",
full_text.size(),
/*min=*/1,
/*max=*/51 * 1024 * 1024,
/*buckets=*/50);
}
if (!text_request_complete_) {
auto request = std::make_unique<StringAnalysisRequest>(
data_.settings.cloud_or_local_settings, std::move(full_text),
base::BindOnce(&ContentAnalysisDelegate::StringRequestCallback,
weak_ptr_factory_.GetWeakPtr()));
PrepareRequest(BULK_DATA_ENTRY, request.get());
UploadTextForDeepScanning(std::move(request));
}
}
void ContentAnalysisDelegate::PrepareImageRequest() {
// The request is considered complete if there is no image or if the image is
// too large compared to the maximum size.
image_request_complete_ =
data_.image.empty() ||
data_.image.size() >
data_.settings.cloud_or_local_settings.max_file_size();
if (!data_.image.empty()) {
base::UmaHistogramCustomCounts("Enterprise.OnBulkDataEntry.DataSize",
data_.image.size(),
/*min=*/1,
/*max=*/51 * 1024 * 1024,
/*buckets=*/50);
}
if (!image_request_complete_) {
auto request = std::make_unique<StringAnalysisRequest>(
data_.settings.cloud_or_local_settings, data_.image,
base::BindOnce(&ContentAnalysisDelegate::ImageRequestCallback,
weak_ptr_factory_.GetWeakPtr()));
PrepareRequest(BULK_DATA_ENTRY, request.get());
UploadImageForDeepScanning(std::move(request));
}
}
void ContentAnalysisDelegate::PreparePageRequest() {
// The request is considered complete if the mapped region is invalid since it
// prevents scanning.
page_request_complete_ = !data_.page.IsValid();
if (!page_request_complete_) {
page_size_bytes_ = data_.page.GetSize();
auto request = std::make_unique<PagePrintAnalysisRequest>(
data_.settings, std::move(data_.page),
base::BindOnce(&ContentAnalysisDelegate::PageRequestCallback,
weak_ptr_factory_.GetWeakPtr()));
PrepareRequest(PRINT, request.get());
request->set_filename(title_);
request->set_printer_name(data_.printer_name);
request->set_printer_type(data_.printer_type);
if (!page_content_type_.empty()) {
request->set_content_type(page_content_type_);
}
UploadPageForDeepScanning(std::move(request));
}
}
// This method only prepares requests for print and paste events. File events
// are handled by
// chrome/browser/enterprise/connectors/analysis/files_request_handler.h
void ContentAnalysisDelegate::PrepareRequest(
AnalysisConnector connector,
BinaryUploadService::Request* request) {
if (data_.settings.cloud_or_local_settings.is_cloud_analysis()) {
request->set_device_token(
data_.settings.cloud_or_local_settings.dm_token());
}
// Include tab page title, user action id, and count of requests per user
// action in local content analysis requests.
if (data_.settings.cloud_or_local_settings.is_local_analysis()) {
// Increment the total number of user action requests by 1.
total_requests_count_++;
request->set_tab_title(title_);
request->set_user_action_id(user_action_id_);
}
request->set_analysis_connector(connector);
request->set_email(safe_browsing::GetProfileEmail(profile_));
request->set_url(data_.url.spec());
request->set_tab_url(data_.url);
request->set_per_profile_request(data_.settings.per_profile);
for (const auto& tag : data_.settings.tags)
request->add_tag(tag.first);
if (data_.settings.client_metadata)
request->set_client_metadata(*data_.settings.client_metadata);
}
void ContentAnalysisDelegate::FillAllResultsWith(bool status) {
std::fill(result_.text_results.begin(), result_.text_results.end(), status);
result_.image_result = status;
std::fill(result_.paths_results.begin(), result_.paths_results.end(), status);
result_.page_result = status;
}
BinaryUploadService* ContentAnalysisDelegate::GetBinaryUploadService() {
return safe_browsing::BinaryUploadService::GetForProfile(profile_,
data_.settings);
}
void ContentAnalysisDelegate::UploadTextForDeepScanning(
std::unique_ptr<BinaryUploadService::Request> request) {
request->set_user_action_requests_count(total_requests_count_);
BinaryUploadService* upload_service = GetBinaryUploadService();
if (upload_service) {
upload_service->MaybeUploadForDeepScanning(std::move(request));
}
}
void ContentAnalysisDelegate::UploadImageForDeepScanning(
std::unique_ptr<BinaryUploadService::Request> request) {
BinaryUploadService* upload_service = GetBinaryUploadService();
request->set_user_action_requests_count(total_requests_count_);
if (upload_service)
upload_service->MaybeUploadForDeepScanning(std::move(request));
}
void ContentAnalysisDelegate::UploadPageForDeepScanning(
std::unique_ptr<BinaryUploadService::Request> request) {
BinaryUploadService* upload_service = GetBinaryUploadService();
request->set_user_action_requests_count(total_requests_count_);
if (upload_service)
upload_service->MaybeUploadForDeepScanning(std::move(request));
}
bool ContentAnalysisDelegate::UpdateDialog() {
// Only show final result UI in the case of a cloud analysis.
// In the local case, the local agent does that.
return data_.settings.cloud_or_local_settings.is_cloud_analysis()
? ShowFinalResultInDialog()
: CancelDialog();
}
void ContentAnalysisDelegate::MaybeCompleteScanRequest() {
if (!text_request_complete_ || !image_request_complete_ ||
!files_request_complete_ || !page_request_complete_) {
return;
}
// If showing the warning message, wait before running the callback. The
// callback will be called either in BypassWarnings or Cancel.
if (final_result_ != FinalContentAnalysisResult::WARNING)
RunCallback();
AckAllRequests();
if (callback_running_ && !dialog_ && *UIEnabledStorage()) {
// This code path implies that RunCallback has already been called,
// and that we are racing against a non-blocking scan. In such a
// case, we let the other caller handle deletion of `this`, and let
// them know no more work is needed.
all_work_done_ = true;
return;
}
if (!UpdateDialog() && data_uploaded_) {
// No UI was shown. Delete |this| to cleanup, unless UploadData isn't done
// yet.
delete this;
}
}
void ContentAnalysisDelegate::RunCallback() {
DCHECK(!callback_running_);
if (callback_.is_null()) {
return;
}
callback_running_ = true;
std::move(callback_).Run(data_, result_);
callback_running_ = false;
// Since `result_` might have been tweaked by `callback_`, `final_actions_`
// need to be updated before Acks are sent.
for (size_t i = 0; i < result_.paths_results.size(); ++i) {
if (!result_.paths_results[i] && files_request_results_.size() > i &&
final_actions_.count(files_request_results_[i].request_token) &&
final_actions_[files_request_results_[i].request_token] ==
ContentAnalysisAcknowledgement::ALLOW) {
final_actions_[files_request_results_[i].request_token] =
ContentAnalysisAcknowledgement::BLOCK;
}
}
// If both image and text are present, synchronize their ack statuses if
// needed.
if (!string_request_result_.request_token.empty() &&
!image_request_result_.request_token.empty()) {
if (!result_.image_result) {
final_actions_[string_request_result_.request_token] =
ContentAnalysisAcknowledgement::BLOCK;
}
// text_results is uniformly updated in StringRequestCallback(), so the
// values should be consistent.
if (!result_.text_results[0]) {
final_actions_[image_request_result_.request_token] =
ContentAnalysisAcknowledgement::BLOCK;
}
}
}
void ContentAnalysisDelegate::UpdateFinalResult(
FinalContentAnalysisResult result,
const std::string& tag) {
if (result < final_result_) {
final_result_ = result;
final_result_tag_ = tag;
}
}
void ContentAnalysisDelegate::AckAllRequests() {
if (!OnAckAllRequestsStorage()->is_null())
std::move(*OnAckAllRequestsStorage()).Run(final_actions_);
BinaryUploadService* upload_service = GetBinaryUploadService();
if (!upload_service)
return;
for (const auto& token_and_action : final_actions_) {
// Only have files that have a request token. Not having one implies that
// the agent never received the request for some reason (size, encryption,
// etc.) so it doesn't make sense to send an ack.
if (!token_and_action.first.empty()) {
auto ack = std::make_unique<safe_browsing::BinaryUploadService::Ack>(
data_.settings.cloud_or_local_settings);
ack->set_request_token(token_and_action.first);
ack->set_status(ContentAnalysisAcknowledgement::SUCCESS);
ack->set_final_action(token_and_action.second);
upload_service->MaybeAcknowledge(std::move(ack));
}
}
}
} // namespace enterprise_connectors