| // 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 "content/browser/renderer_host/raw_clipboard_host_impl.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/i18n/number_formatting.h" |
| #include "base/memory/ptr_util.h" |
| #include "content/browser/permissions/permission_controller_impl.h" |
| #include "content/browser/renderer_host/clipboard_host_impl.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/common/child_process_host.h" |
| #include "ipc/ipc_message.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/clipboard/raw_clipboard.mojom.h" |
| #include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/clipboard/clipboard_format_type.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/base/data_transfer_policy/data_transfer_endpoint.h" |
| |
| namespace content { |
| |
| void RawClipboardHostImpl::Create( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<blink::mojom::RawClipboardHost> receiver) { |
| DCHECK(render_frame_host); |
| |
| // Feature flags and permission should already be checked in the renderer |
| // process, but recheck in the browser process in case of a hijacked renderer. |
| if (!base::FeatureList::IsEnabled(blink::features::kRawClipboard)) { |
| mojo::ReportBadMessage("Raw Clipboard is not enabled."); |
| return; |
| } |
| |
| // Renderer process should already check for user activation before sending |
| // this request. Double check in case of compromised renderer. |
| if (!render_frame_host->HasTransientUserActivation()) { |
| // mojo::ReportBadMessage() is not appropriate here, because user |
| // activation may expire after the renderer check but before the browser |
| // check. |
| return; |
| } |
| |
| PermissionControllerImpl* permission_controller = |
| PermissionControllerImpl::FromBrowserContext( |
| render_frame_host->GetProcess()->GetBrowserContext()); |
| |
| blink::mojom::PermissionStatus status = |
| permission_controller->GetPermissionStatusForFrame( |
| PermissionType::CLIPBOARD_READ_WRITE, render_frame_host, |
| render_frame_host->GetLastCommittedOrigin().GetURL()); |
| |
| if (status != blink::mojom::PermissionStatus::GRANTED) { |
| // mojo::ReportBadMessage() is not appropriate here because the permission |
| // may be granted after the renderer check, but revoked before the browser |
| // check. |
| return; |
| } |
| |
| mojo::MakeSelfOwnedReceiver( |
| base::WrapUnique(new RawClipboardHostImpl( |
| static_cast<RenderFrameHostImpl*>(render_frame_host))), |
| std::move(receiver)); |
| } |
| |
| RawClipboardHostImpl::~RawClipboardHostImpl() { |
| clipboard_writer_->Reset(); |
| } |
| |
| RawClipboardHostImpl::RawClipboardHostImpl(RenderFrameHost* render_frame_host) |
| : render_frame_routing_id_( |
| GlobalRenderFrameHostId(render_frame_host->GetProcess()->GetID(), |
| render_frame_host->GetRoutingID())), |
| clipboard_writer_( |
| new ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste, |
| CreateDataEndpoint())) { |
| DCHECK(render_frame_host); |
| } |
| |
| void RawClipboardHostImpl::ReadAvailableFormatNames( |
| ReadAvailableFormatNamesCallback callback) { |
| if (!HasTransientUserActivation()) |
| return; |
| std::vector<std::u16string> raw_types = |
| ui::Clipboard::GetForCurrentThread() |
| ->ReadAvailablePlatformSpecificFormatNames( |
| ui::ClipboardBuffer::kCopyPaste, CreateDataEndpoint().get()); |
| std::move(callback).Run(raw_types); |
| } |
| |
| void RawClipboardHostImpl::Read(const std::u16string& format, |
| ReadCallback callback) { |
| if (!HasTransientUserActivation()) |
| return; |
| if (format.size() >= kMaxFormatSize) { |
| mojo::ReportBadMessage("Requested format string length too long."); |
| return; |
| } |
| |
| std::string result; |
| ui::Clipboard::GetForCurrentThread()->ReadData( |
| ui::ClipboardFormatType::GetType(base::UTF16ToUTF8(format)), |
| CreateDataEndpoint().get(), &result); |
| base::span<const uint8_t> span( |
| reinterpret_cast<const uint8_t*>(result.data()), result.size()); |
| mojo_base::BigBuffer buffer = mojo_base::BigBuffer(span); |
| std::move(callback).Run(std::move(buffer)); |
| } |
| |
| void RawClipboardHostImpl::Write(const std::u16string& format, |
| mojo_base::BigBuffer data) { |
| if (!HasTransientUserActivation()) |
| return; |
| if (format.size() >= kMaxFormatSize) { |
| mojo::ReportBadMessage("Target format string length too long."); |
| return; |
| } |
| if (data.size() >= kMaxDataSize) { |
| mojo::ReportBadMessage("Write data too large."); |
| return; |
| } |
| |
| // Windows / X11 clipboards enter an unrecoverable state after registering |
| // some amount of unique formats, and there's no way to un-register these |
| // formats. For these clipboards, use a conservative limit to avoid |
| // registering too many formats, as: |
| // (1) Other native applications may also register clipboard formats. |
| // (2) |registered_formats| only persists over one Chrome Clipboard session. |
| // (3) Chrome also registers other clipboard formats. |
| // |
| // The limit is based on Windows, which has the smallest limit, at 0x4000. |
| // Windows represents clipboard formats using values in 0xC000 - 0xFFFF. |
| // Therefore, Windows supports at most 0x4000 registered formats. Reference: |
| // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformata |
| static constexpr int kMaxWindowsClipboardFormats = 0x4000; |
| static constexpr int kMaxRegisteredFormats = kMaxWindowsClipboardFormats / 4; |
| static base::NoDestructor<std::set<std::u16string>> registered_formats; |
| if (!base::Contains(*registered_formats, format)) { |
| if (registered_formats->size() >= kMaxRegisteredFormats) |
| return; |
| registered_formats->emplace(format); |
| } |
| |
| clipboard_writer_->WriteData(format, std::move(data)); |
| } |
| |
| void RawClipboardHostImpl::CommitWrite() { |
| clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>( |
| ui::ClipboardBuffer::kCopyPaste, CreateDataEndpoint()); |
| } |
| |
| std::unique_ptr<ui::DataTransferEndpoint> |
| RawClipboardHostImpl::CreateDataEndpoint() { |
| RenderFrameHostImpl* render_frame_host = |
| RenderFrameHostImpl::FromID(render_frame_routing_id_); |
| if (!render_frame_host) |
| return nullptr; |
| |
| return std::make_unique<ui::DataTransferEndpoint>( |
| render_frame_host->GetLastCommittedOrigin(), |
| render_frame_host->HasTransientUserActivation()); |
| } |
| |
| bool RawClipboardHostImpl::HasTransientUserActivation() const { |
| RenderFrameHostImpl* render_frame_host = |
| RenderFrameHostImpl::FromID(render_frame_routing_id_); |
| if (!render_frame_host) |
| return false; |
| |
| // Renderer process should already check for user activation before sending |
| // this request. Double check in case of compromised renderer. |
| // mojo::ReportBadMessage() is not appropriate here, because user activation |
| // may expire after the renderer check but before the browser check. |
| return render_frame_host->HasTransientUserActivation(); |
| } |
| |
| } // namespace content |