| // 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/data_protection_clipboard_utils.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <queue> |
| #include <variant> |
| |
| #include "base/no_destructor.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/enterprise/data_controls/chrome_rules_service.h" |
| #include "chrome/browser/enterprise/data_protection/paste_allowed_request.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/enterprise/common/files_scan_data.h" |
| #include "components/enterprise/connectors/core/connectors_prefs.h" |
| #include "components/enterprise/content/clipboard_restriction_service.h" |
| #include "components/enterprise/data_controls/content/browser/last_replaced_clipboard_data.h" |
| #include "components/enterprise/data_controls/core/browser/data_controls_dialog_factory.h" |
| #include "components/enterprise/data_controls/core/browser/prefs.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/safe_browsing/buildflags.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/clipboard_types.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/clipboard/clipboard_monitor.h" |
| #include "ui/base/clipboard/clipboard_observer.h" |
| #include "ui/base/clipboard/clipboard_sequence_number_token.h" |
| #include "ui/base/clipboard/clipboard_util.h" |
| #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| #include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h" |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| #if BUILDFLAG(ENTERPRISE_DATA_CONTROLS) |
| #include "chrome/browser/enterprise/data_controls/desktop_data_controls_dialog_factory.h" |
| #endif // BUILDFLAG(ENTERPRISE_DATA_PROTECTION) |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/enterprise/data_controls/android_data_controls_dialog.h" |
| #include "chrome/browser/enterprise/data_controls/android_data_controls_dialog_factory.h" |
| #include "components/enterprise/data_controls/core/browser/features.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| #if BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| #include "chrome/browser/enterprise/data_controls/reporting_service.h" |
| #endif // BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| |
| namespace enterprise_data_protection { |
| |
| namespace { |
| |
| // Returns an empty URL if `endpoint` doesn't hold a DTE, or a non-URL DTE. |
| GURL GetUrlFromEndpoint(const content::ClipboardEndpoint& endpoint) { |
| if (!endpoint.data_transfer_endpoint() || |
| !endpoint.data_transfer_endpoint()->IsUrlType() || |
| !endpoint.data_transfer_endpoint()->GetURL()) { |
| return GURL(); |
| } |
| return *endpoint.data_transfer_endpoint()->GetURL(); |
| } |
| |
| bool SkipDataControlOrContentAnalysisChecks( |
| const content::ClipboardEndpoint& main_endpoint) { |
| // Data Controls and content analysis copy/paste checks require an active tab |
| // to be meaningful, so if it's gone they can be skipped. |
| auto* web_contents = main_endpoint.web_contents(); |
| if (!web_contents) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| #if BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| void HandleFileData( |
| content::WebContents* web_contents, |
| enterprise_connectors::ContentAnalysisDelegate::Data dialog_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback) { |
| enterprise_connectors::ContentAnalysisDelegate::CreateForFilesInWebContents( |
| web_contents, std::move(dialog_data), |
| base::BindOnce( |
| [](content::ContentBrowserClient::IsClipboardPasteAllowedCallback |
| callback, |
| std::vector<base::FilePath> paths, std::vector<bool> results) { |
| std::optional<content::ClipboardPasteData> clipboard_paste_data; |
| bool all_blocked = |
| std::all_of(results.begin(), results.end(), |
| [](bool allowed) { return !allowed; }); |
| if (!all_blocked) { |
| std::vector<base::FilePath> allowed_paths; |
| allowed_paths.reserve(paths.size()); |
| for (size_t i = 0; i < paths.size(); ++i) { |
| if (results[i]) { |
| allowed_paths.emplace_back(std::move(paths[i])); |
| } |
| } |
| clipboard_paste_data = content::ClipboardPasteData(); |
| clipboard_paste_data->file_paths = std::move(allowed_paths); |
| } |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| }, |
| std::move(callback)), |
| safe_browsing::DeepScanAccessPoint::PASTE); |
| } |
| |
| void HandleStringData( |
| content::WebContents* web_contents, |
| content::ClipboardPasteData clipboard_paste_data, |
| enterprise_connectors::ContentAnalysisDelegate::Data dialog_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback) { |
| enterprise_connectors::ContentAnalysisDelegate::CreateForWebContents( |
| web_contents, std::move(dialog_data), |
| base::BindOnce( |
| [](content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback |
| callback, |
| const enterprise_connectors::ContentAnalysisDelegate::Data& data, |
| enterprise_connectors::ContentAnalysisDelegate::Result& result) { |
| // TODO(b/318664590): Since the `data` argument is forwarded to |
| // `callback`, changing the type from `const Data&` to just `Data` |
| // would avoid a copy. |
| |
| bool text_blocked = |
| !result.text_results.empty() && !result.text_results[0]; |
| bool image_blocked = |
| !clipboard_paste_data.png.empty() && !result.image_result; |
| if (text_blocked || image_blocked) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| }, |
| std::move(clipboard_paste_data), std::move(callback)), |
| safe_browsing::DeepScanAccessPoint::PASTE); |
| } |
| |
| void PasteIfAllowedByContentAnalysis( |
| content::WebContents* web_contents, |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback) { |
| DCHECK(web_contents); |
| DCHECK(!SkipDataControlOrContentAnalysisChecks(destination)); |
| |
| // Always allow if the source of the last clipboard commit was this host. |
| if (destination.web_contents()->GetPrimaryMainFrame()->IsClipboardOwner( |
| metadata.seqno)) { |
| ReplaceSameTabClipboardDataIfRequiredByPolicy(metadata.seqno, |
| clipboard_paste_data); |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| return; |
| } |
| |
| Profile* profile = Profile::FromBrowserContext(destination.browser_context()); |
| if (!profile) { |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| return; |
| } |
| |
| bool is_files = |
| metadata.format_type == ui::ClipboardFormatType::FilenamesType(); |
| enterprise_connectors::AnalysisConnector connector = |
| is_files ? enterprise_connectors::AnalysisConnector::FILE_ATTACHED |
| : enterprise_connectors::AnalysisConnector::BULK_DATA_ENTRY; |
| enterprise_connectors::ContentAnalysisDelegate::Data dialog_data; |
| |
| if (!enterprise_connectors::ContentAnalysisDelegate::IsEnabled( |
| profile, GetUrlFromEndpoint(destination), &dialog_data, connector)) { |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| return; |
| } |
| |
| dialog_data.reason = |
| enterprise_connectors::ContentAnalysisRequest::CLIPBOARD_PASTE; |
| dialog_data.clipboard_source = |
| data_controls::ReportingService::GetClipboardSource( |
| source, destination, |
| enterprise_connectors::kOnBulkDataEntryScopePref); |
| |
| if (is_files) { |
| dialog_data.paths = std::move(clipboard_paste_data.file_paths); |
| HandleFileData(web_contents, std::move(dialog_data), std::move(callback)); |
| } else { |
| dialog_data.AddClipboardData(clipboard_paste_data); |
| HandleStringData(web_contents, std::move(clipboard_paste_data), |
| std::move(dialog_data), std::move(callback)); |
| } |
| } |
| #endif // BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| |
| data_controls::DataControlsDialogFactory* GetDialogFactory() { |
| #if BUILDFLAG(IS_ANDROID) |
| return data_controls::AndroidDataControlsDialogFactory::GetInstance(); |
| #elif BUILDFLAG(ENTERPRISE_DATA_CONTROLS) |
| return data_controls::DesktopDataControlsDialogFactory::GetInstance(); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| void MaybeReportDataControlsPaste(const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| const data_controls::Verdict& verdict, |
| bool bypassed = false) { |
| #if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| auto* reporting_service = |
| data_controls::ReportingServiceFactory::GetInstance() |
| ->GetForBrowserContext(destination.browser_context()); |
| |
| // `reporting_service` can be null for incognito browser contexts, so since |
| // there's no reporting in that case we just return early. |
| if (!reporting_service) { |
| return; |
| } |
| |
| if (bypassed) { |
| reporting_service->ReportPasteWarningBypassed(source, destination, metadata, |
| verdict); |
| } else { |
| reporting_service->ReportPaste(source, destination, metadata, verdict); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| } |
| |
| void MaybeReportDataControlsCopy(const content::ClipboardEndpoint& source, |
| const content::ClipboardMetadata& metadata, |
| const data_controls::Verdict& verdict, |
| bool bypassed = false) { |
| #if !BUILDFLAG(IS_ANDROID) && BUILDFLAG(SAFE_BROWSING_AVAILABLE) |
| auto* reporting_service = |
| data_controls::ReportingServiceFactory::GetInstance() |
| ->GetForBrowserContext(source.browser_context()); |
| |
| // `reporting_service` can be null for incognito browser contexts, so since |
| // there's no reporting in that case we just return early. |
| if (!reporting_service) { |
| return; |
| } |
| |
| if (bypassed) { |
| reporting_service->ReportCopyWarningBypassed(source, metadata, verdict); |
| } else { |
| reporting_service->ReportCopy(source, metadata, verdict); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| } |
| |
| void OnDataControlsPasteWarning( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| data_controls::Verdict verdict, |
| content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback, |
| bool bypassed) { |
| if (!bypassed || SkipDataControlOrContentAnalysisChecks(destination)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| if (bypassed && verdict.level() == data_controls::Rule::Level::kWarn) { |
| MaybeReportDataControlsPaste(source, destination, metadata, verdict, |
| /*bypassed=*/true); |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) || !BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| #else |
| PasteIfAllowedByContentAnalysis( |
| destination.web_contents(), source, destination, metadata, |
| std::move(clipboard_paste_data), std::move(callback)); |
| #endif // BUILDFLAG(IS_ANDROID) || !BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| } |
| |
| void PasteIfAllowedByDataControls( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback) { |
| DCHECK(!SkipDataControlOrContentAnalysisChecks(destination)); |
| |
| auto verdict = data_controls::ChromeRulesServiceFactory::GetInstance() |
| ->GetForBrowserContext(destination.browser_context()) |
| ->GetPasteVerdict(source, destination, metadata); |
| if (source.browser_context() && |
| source.browser_context() != destination.browser_context()) { |
| verdict = data_controls::Verdict::MergePasteVerdicts( |
| data_controls::ChromeRulesServiceFactory::GetInstance() |
| ->GetForBrowserContext(source.browser_context()) |
| ->GetPasteVerdict(source, destination, metadata), |
| std::move(verdict)); |
| } |
| |
| auto* factory = GetDialogFactory(); |
| if (verdict.level() == data_controls::Rule::Level::kBlock) { |
| MaybeReportDataControlsPaste(source, destination, metadata, verdict); |
| if (factory) { |
| factory->ShowDialogIfNeeded( |
| destination.web_contents(), |
| data_controls::DataControlsDialog::Type::kClipboardPasteBlock); |
| } |
| std::move(callback).Run(std::nullopt); |
| return; |
| } else if (verdict.level() == data_controls::Rule::Level::kWarn) { |
| MaybeReportDataControlsPaste(source, destination, metadata, verdict); |
| if (factory) { |
| factory->ShowDialogIfNeeded( |
| destination.web_contents(), |
| data_controls::DataControlsDialog::Type::kClipboardPasteWarn, |
| base::BindOnce(&OnDataControlsPasteWarning, source, destination, |
| metadata, std::move(verdict), |
| std::move(clipboard_paste_data), std::move(callback))); |
| } else { |
| std::move(callback).Run(std::nullopt); |
| } |
| return; |
| } else if (verdict.level() == data_controls::Rule::Level::kReport) { |
| MaybeReportDataControlsPaste(source, destination, metadata, verdict); |
| } |
| |
| // If the data currently being pasted was replaced when it was initially |
| // copied from Chrome, replace it back since it hasn't triggered a Data |
| // Controls rule when pasting. Only do this if `source` has a known browser |
| // context to ensure we're not letting through data that was replaced by |
| // policies that are no longer applicable due to the profile being closed. |
| if (source.browser_context() && |
| metadata.seqno == data_controls::GetLastReplacedClipboardData().seqno) { |
| clipboard_paste_data = |
| data_controls::GetLastReplacedClipboardData().clipboard_paste_data; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) || !BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| #else |
| PasteIfAllowedByContentAnalysis( |
| destination.web_contents(), source, destination, metadata, |
| std::move(clipboard_paste_data), std::move(callback)); |
| #endif // BUILDFLAG(IS_ANDROID) || !BUILDFLAG(ENTERPRISE_CONTENT_ANALYSIS) |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| void OnDlpRulesCheckDone( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback, |
| bool allowed) { |
| // If DLP rules blocked the action or if there are no further policy checks |
| // required, return null to indicate the pasting is blocked or no longer |
| // applicable. |
| if (!allowed || SkipDataControlOrContentAnalysisChecks(destination)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } |
| |
| PasteIfAllowedByDataControls(source, destination, metadata, |
| std::move(clipboard_paste_data), |
| std::move(callback)); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| void IsCopyToOSClipboardRestricted( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardMetadata& metadata, |
| const content::ClipboardPasteData& data, |
| content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback) { |
| if (SkipDataControlOrContentAnalysisChecks(source)) { |
| std::move(callback).Run(metadata.format_type, data, std::nullopt); |
| return; |
| } |
| |
| auto verdict = data_controls::ChromeRulesServiceFactory::GetInstance() |
| ->GetForBrowserContext(source.browser_context()) |
| ->GetCopyToOSClipboardVerdict(GetUrlFromEndpoint(source)); |
| |
| if (verdict.level() == data_controls::Rule::Level::kBlock) { |
| // Before calling `callback`, we remember `data` will correspond to the next |
| // clipboard sequence number so that it can be potentially replaced again at |
| // paste time. |
| data_controls::LastReplacedClipboardDataObserver::GetInstance() |
| ->AddDataToNextSeqno(data); |
| std::move(callback).Run( |
| metadata.format_type, data, /*replacement_data=*/ |
| l10n_util::GetStringUTF16( |
| IDS_ENTERPRISE_DATA_CONTROLS_COPY_PREVENTION_WARNING_MESSAGE)); |
| |
| return; |
| } |
| |
| std::move(callback).Run(metadata.format_type, data, std::nullopt); |
| } |
| |
| void OnDataControlsCopyWarning( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardMetadata& metadata, |
| const content::ClipboardPasteData& data, |
| data_controls::Verdict verdict, |
| content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback, |
| bool bypassed) { |
| if (bypassed) { |
| MaybeReportDataControlsCopy(source, metadata, verdict, /*bypassed=*/true); |
| IsCopyToOSClipboardRestricted(source, metadata, data, std::move(callback)); |
| return; |
| } |
| |
| // Once a pending write has been initiated, something must be written to the |
| // clipboard to avoid being stuck in a pending write state, which would |
| // prevent future writes to the clipboard. Since copying was not allowed, |
| // the callback should be run with empty data instead. |
| std::move(callback).Run(metadata.format_type, content::ClipboardPasteData(), |
| /*replacement_data=*/std::nullopt); |
| } |
| |
| void IsCopyRestrictedByDialog( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardMetadata& metadata, |
| const content::ClipboardPasteData& data, |
| content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback) { |
| if (SkipDataControlOrContentAnalysisChecks(source)) { |
| std::move(callback).Run(metadata.format_type, data, std::nullopt); |
| return; |
| } |
| |
| auto source_only_verdict = |
| data_controls::ChromeRulesServiceFactory::GetInstance() |
| ->GetForBrowserContext(source.browser_context()) |
| ->GetCopyRestrictedBySourceVerdict(GetUrlFromEndpoint(source)); |
| |
| auto* factory = GetDialogFactory(); |
| if (source_only_verdict.level() == data_controls::Rule::Level::kBlock) { |
| MaybeReportDataControlsCopy(source, metadata, source_only_verdict); |
| if (factory) { |
| factory->ShowDialogIfNeeded( |
| source.web_contents(), |
| data_controls::DataControlsDialog::Type::kClipboardCopyBlock); |
| } |
| return; |
| } |
| |
| // The "warn" level of copying to the OS clipboard is to show a warning |
| // dialog, not to do a string replacement. |
| auto os_clipboard_verdict = |
| data_controls::ChromeRulesServiceFactory::GetInstance() |
| ->GetForBrowserContext(source.browser_context()) |
| ->GetCopyToOSClipboardVerdict(GetUrlFromEndpoint(source)); |
| |
| if (source_only_verdict.level() == data_controls::Rule::Level::kWarn || |
| os_clipboard_verdict.level() == data_controls::Rule::Level::kWarn) { |
| auto verdict = data_controls::Verdict::MergeCopyWarningVerdicts( |
| std::move(source_only_verdict), std::move(os_clipboard_verdict)); |
| MaybeReportDataControlsCopy(source, metadata, verdict); |
| if (factory) { |
| factory->ShowDialogIfNeeded( |
| source.web_contents(), |
| data_controls::DataControlsDialog::Type::kClipboardCopyWarn, |
| base::BindOnce(&OnDataControlsCopyWarning, source, metadata, data, |
| std::move(verdict), std::move(callback))); |
| } |
| return; |
| } |
| |
| if (source_only_verdict.level() == data_controls::Rule::Level::kReport) { |
| MaybeReportDataControlsCopy(source, metadata, source_only_verdict); |
| } |
| |
| IsCopyToOSClipboardRestricted(source, metadata, data, std::move(callback)); |
| } |
| |
| } // namespace |
| |
| void PasteIfAllowedByPolicy( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardEndpoint& destination, |
| const content::ClipboardMetadata& metadata, |
| content::ClipboardPasteData clipboard_paste_data, |
| content::ContentBrowserClient::IsClipboardPasteAllowedCallback callback) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (SkipDataControlOrContentAnalysisChecks(destination)) { |
| std::move(callback).Run(std::nullopt); |
| return; |
| } else if (base::FeatureList::IsEnabled( |
| data_controls::kEnableClipboardDataControlsAndroid)) { |
| // Call PasteIfAllowedByDataControls directly as |
| // DataTransferPolicyController::PasteIfAllowed contains logic that isn't |
| // relevant to Clank. |
| PasteIfAllowedByDataControls(source, destination, metadata, |
| std::move(clipboard_paste_data), |
| std::move(callback)); |
| return; |
| } else { |
| std::move(callback).Run(std::move(clipboard_paste_data)); |
| return; |
| } |
| #else |
| if (ui::DataTransferPolicyController::HasInstance()) { |
| std::variant<size_t, std::vector<base::FilePath>> pasted_content; |
| if (clipboard_paste_data.file_paths.empty()) { |
| DCHECK(metadata.size.has_value()); |
| pasted_content = *metadata.size; |
| } else { |
| pasted_content = clipboard_paste_data.file_paths; |
| } |
| |
| std::optional<ui::DataTransferEndpoint> destination_endpoint = std::nullopt; |
| if (destination.browser_context() && |
| !destination.browser_context()->IsOffTheRecord()) { |
| destination_endpoint = destination.data_transfer_endpoint(); |
| } |
| |
| ui::DataTransferPolicyController::Get()->PasteIfAllowed( |
| source.data_transfer_endpoint(), destination_endpoint, |
| std::move(pasted_content), |
| destination.web_contents() |
| ? destination.web_contents()->GetPrimaryMainFrame() |
| : nullptr, |
| base::BindOnce(&OnDlpRulesCheckDone, source, destination, metadata, |
| std::move(clipboard_paste_data), std::move(callback))); |
| return; |
| } |
| |
| OnDlpRulesCheckDone(source, destination, metadata, |
| std::move(clipboard_paste_data), std::move(callback), |
| /*allowed=*/true); |
| #endif // BUILDFLAG(IS_ANDROID) |
| } |
| |
| void IsClipboardCopyAllowedByPolicy( |
| const content::ClipboardEndpoint& source, |
| const content::ClipboardMetadata& metadata, |
| const content::ClipboardPasteData& data, |
| content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback) { |
| #if BUILDFLAG(IS_ANDROID) |
| if (!base::FeatureList::IsEnabled( |
| data_controls::kEnableClipboardDataControlsAndroid)) { |
| std::move(callback).Run(metadata.format_type, data, std::nullopt); |
| return; |
| } |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| if (SkipDataControlOrContentAnalysisChecks(source)) { |
| std::move(callback).Run(metadata.format_type, data, std::nullopt); |
| return; |
| } |
| |
| DCHECK(source.web_contents()); |
| DCHECK(source.browser_context()); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| // IsUrlAllowedToCopy checks a deprecated CopyPreventionSettings that isn't |
| // applicable on Clank. |
| std::u16string replacement_data; |
| ClipboardRestrictionService* service = |
| ClipboardRestrictionServiceFactory::GetInstance()->GetForBrowserContext( |
| source.browser_context()); |
| if (!service->IsUrlAllowedToCopy(GetUrlFromEndpoint(source), |
| metadata.size.value_or(0), |
| &replacement_data)) { |
| std::move(callback).Run(metadata.format_type, data, |
| std::move(replacement_data)); |
| return; |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| IsCopyRestrictedByDialog(source, metadata, data, std::move(callback)); |
| } |
| |
| void ReplaceSameTabClipboardDataIfRequiredByPolicy( |
| ui::ClipboardSequenceNumberToken seqno, |
| content::ClipboardPasteData& data) { |
| if (seqno == data_controls::GetLastReplacedClipboardData().seqno) { |
| data = data_controls::GetLastReplacedClipboardData().clipboard_paste_data; |
| } |
| } |
| |
| } // namespace enterprise_data_protection |