blob: cb424c0d670b3e4fe7b94e5f2a55f6b5006712bf [file] [log] [blame]
// Copyright 2023 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/data_protection/print_utils.h"
#include <cstring>
#include <optional>
#include <utility>
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "chrome/browser/profiles/profile.h"
#include "components/device_event_log/device_event_log.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "content/public/browser/web_contents.h"
#include "printing/printing_features.h"
namespace enterprise_data_protection {
namespace {
bool ShouldScan(PrintScanningContext context) {
switch (context) {
// For "normal" prints, the scanning can happen immediately after the user
// clicks "Print" in the print preview dialog as the preview document is
// representative of what they are printing.
case PrintScanningContext::kNormalPrintAfterPreview:
return true;
case PrintScanningContext::kNormalPrintBeforePrintDocument:
return false;
// For "system dialog" prints, the scanning waits until the user picks
// settings from the system dialog, but starts applying enterprise-logic
// logic at the `kBeforeSystemDialog` context for that to happen.
//
// Scanning also happens right before the document is printed through an
// existing print job when that is triggered after the print preview
// dialog.
case PrintScanningContext::kBeforeSystemDialog:
return true;
case PrintScanningContext::kSystemPrintAfterPreview:
return false;
case PrintScanningContext::kSystemPrintBeforePrintDocument:
return true;
#if BUILDFLAG(IS_MAC)
// For the "Open PDF in Preview" option on Mac, scan right as it happens
// from the print preview dialog.
case PrintScanningContext::kOpenPdfInPreview:
return true;
#endif // BUILDFLAG(IS_MAC)
}
}
} // namespace
void PrintIfAllowedByPolicy(scoped_refptr<base::RefCountedMemory> print_data,
content::WebContents* initiator,
std::string printer_name,
PrintScanningContext context,
base::OnceCallback<void(bool)> on_verdict,
base::OnceClosure hide_preview) {
// In some cases like the web contents closing or the render process crashing,
// it's possible for `initiator` to be null. In that case, printing can simply
// be aborted.
if (!initiator) {
std::move(on_verdict).Run(/*allowed=*/false);
return;
}
// This needs to be done to avoid having an embedded page compare against
// policies and report its URL. This is especially important when Chrome's PDF
// reader is used, as a cryptic extension URL will be sent for
// scanning/reporting in that case.
// TODO(b/289243948): Add browser test coverage for web contents received and
// passed to the delegate.
content::WebContents* web_contents = initiator->GetOutermostWebContents();
auto scanning_data = GetPrintAnalysisData(web_contents, context);
if (!scanning_data) {
std::move(on_verdict).Run(/*allowed=*/true);
return;
}
// Populate print metadata.
scanning_data->printer_name = std::move(printer_name);
// Hide the preview dialog so it doesn't cover the content analysis dialog
// showing the status of the scanning. Since that scanning dialog only appears
// when the policy is set to be blocking, don't run `hide_preview` for other
// policy configurations to avoid races between the dialog being hidden vs
// destroyed.
if (scanning_data->settings.block_until_verdict ==
enterprise_connectors::BlockUntilVerdict::kBlock) {
std::move(hide_preview).Run();
}
PrintIfAllowedByPolicy(print_data, web_contents, std::move(*scanning_data),
std::move(on_verdict));
}
void PrintIfAllowedByPolicy(
scoped_refptr<base::RefCountedMemory> print_data,
content::WebContents* initiator,
enterprise_connectors::ContentAnalysisDelegate::Data scanning_data,
base::OnceCallback<void(bool)> on_verdict) {
// The preview document bytes are copied so that the content analysis code
// can arbitrarily use them without having to handle ownership issues with
// other printing code.
base::MappedReadOnlyRegion region =
base::ReadOnlySharedMemoryRegion::Create(print_data->size());
if (!region.IsValid()) {
// Allow printing if the scan can't happen due to memory failure.
PRINTER_LOG(ERROR) << "Printed without analysis due to memory failure";
std::move(on_verdict).Run(/*allowed=*/true);
return;
}
region.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(*print_data);
scanning_data.page = std::move(region.region);
auto on_scan_result = base::BindOnce(
[](base::OnceCallback<void(bool should_proceed)> callback,
const enterprise_connectors::ContentAnalysisDelegate::Data& data,
enterprise_connectors::ContentAnalysisDelegate::Result& result) {
std::move(callback).Run(result.page_result);
},
std::move(on_verdict));
// This needs to be done to avoid having an embedded page compare against
// policies and report its URL. This is especially important when Chrome's PDF
// reader is used, as a cryptic extension URL will be sent for
// scanning/reporting in that case.
// TODO(b/289243948): Add browser test coverage for web contents received and
// passed to the delegate.
content::WebContents* web_contents = initiator->GetOutermostWebContents();
enterprise_connectors::ContentAnalysisDelegate::CreateForWebContents(
web_contents, std::move(scanning_data), std::move(on_scan_result),
enterprise_connectors::DeepScanAccessPoint::PRINT);
}
std::optional<enterprise_connectors::ContentAnalysisDelegate::Data>
GetPrintAnalysisData(content::WebContents* web_contents,
PrintScanningContext context) {
enterprise_connectors::ContentAnalysisDelegate::Data scanning_data;
bool enabled = enterprise_connectors::ContentAnalysisDelegate::IsEnabled(
Profile::FromBrowserContext(web_contents->GetBrowserContext()),
web_contents->GetOutermostWebContents()->GetLastCommittedURL(),
&scanning_data, enterprise_connectors::AnalysisConnector::PRINT);
if (enabled && ShouldScan(context)) {
switch (context) {
#if BUILDFLAG(IS_MAC)
case PrintScanningContext::kOpenPdfInPreview:
#endif // BUILDFLAG(IS_MAC)
case PrintScanningContext::kNormalPrintAfterPreview:
case PrintScanningContext::kNormalPrintBeforePrintDocument:
scanning_data.reason =
enterprise_connectors::ContentAnalysisRequest::PRINT_PREVIEW_PRINT;
break;
case PrintScanningContext::kBeforeSystemDialog:
case PrintScanningContext::kSystemPrintAfterPreview:
case PrintScanningContext::kSystemPrintBeforePrintDocument:
scanning_data.reason =
enterprise_connectors::ContentAnalysisRequest::SYSTEM_DIALOG_PRINT;
break;
}
return scanning_data;
}
return std::nullopt;
}
} // namespace enterprise_data_protection