blob: 5f311fad5942496c893634d555a23952907ac7df [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 "content/browser/frame_host/raw_clipboard_host_impl.h"
#include "base/bind.h"
#include "base/i18n/number_formatting.h"
#include "content/browser/frame_host/clipboard_host_impl.h"
#include "content/browser/permissions/permission_controller_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 "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"
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;
}
// Clipboard implementations do interesting things, like run nested message
// loops. Use manual memory management instead of SelfOwnedReceiver<T> which
// synchronously destroys on failure and can result in some unfortunate
// use-after-frees after the nested message loops exit.
auto* host = new RawClipboardHostImpl(std::move(receiver), render_frame_host);
host->receiver_.set_disconnect_handler(base::BindOnce(
[](RawClipboardHostImpl* host) {
base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, host);
},
host));
}
RawClipboardHostImpl::~RawClipboardHostImpl() {
clipboard_writer_->Reset();
}
RawClipboardHostImpl::RawClipboardHostImpl(
mojo::PendingReceiver<blink::mojom::RawClipboardHost> receiver,
RenderFrameHost* render_frame_host)
: receiver_(this, std::move(receiver)),
clipboard_(ui::Clipboard::GetForCurrentThread()),
clipboard_writer_(
new ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)),
render_frame_host_(render_frame_host) {
DCHECK(render_frame_host);
}
void RawClipboardHostImpl::ReadAvailableFormatNames(
ReadAvailableFormatNamesCallback callback) {
if (!HasTransientUserActivation())
return;
std::vector<base::string16> raw_types =
clipboard_->ReadAvailablePlatformSpecificFormatNames(
ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr);
std::move(callback).Run(raw_types);
}
void RawClipboardHostImpl::Read(const base::string16& format,
ReadCallback callback) {
if (!HasTransientUserActivation())
return;
if (format.size() >= kMaxFormatSize) {
receiver_.ReportBadMessage("Requested format string length too long.");
return;
}
std::string result;
clipboard_->ReadData(
ui::ClipboardFormatType::GetType(base::UTF16ToUTF8(format)),
/* data_dst = */ nullptr, &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 base::string16& format,
mojo_base::BigBuffer data) {
if (!HasTransientUserActivation())
return;
if (format.size() >= kMaxFormatSize) {
receiver_.ReportBadMessage("Target format string length too long.");
return;
}
if (data.size() >= kMaxDataSize) {
receiver_.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<base::string16>> 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_.reset(
new ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste));
}
bool RawClipboardHostImpl::HasTransientUserActivation() const {
// 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