0

[Clipboard API] Clipboard Custom Formats implementation Part 5.

In this patch a new web custom format map is introduced while reading
& writing custom format from/to the clipboard. This format is in JSON
type and contains the mapping of custom MIME type to web custom
clipboard format. It also adds a restriction as to how many web
custom formats can be added via pickling async clipboard APIs.
Currently the hard limit is 100 on all platforms.
This was a security feedback in https://chromium-review.googlesource.com/c/chromium/src/+/3002583/comments/75f23531_61662b80
Also this format map is passed via
`WritePortableAndPlatformRepresentations` method to write the custom
format mapping to the clipboard.

i2p: https://groups.google.com/a/chromium.org/g/blink-dev/c/Lo7WBM_v_LY/m/LncCKkXeAwAJ

Bug: 106449, 1217643

Change-Id: I252f9eb5e5c6da7a60baf84e51743eea3f196161
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3039323
Commit-Queue: Anupam Snigdha <snianu@microsoft.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
Reviewed-by: Eric Seckler <eseckler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#908188}
This commit is contained in:
Anupam Snigdha
2021-08-03 23:15:49 +00:00
committed by Chromium LUCI CQ
parent f43582955e
commit 22a48f046a
13 changed files with 262 additions and 186 deletions

@ -76,6 +76,10 @@ HeadlessClipboard::ReadAvailablePlatformSpecificFormatNames(
const auto& data = GetStore(buffer).data;
std::vector<std::u16string> types;
types.reserve(data.size());
std::map<std::string, std::string> custom_format_names =
ExtractCustomPlatformNames(buffer, data_dst);
for (const auto& item : custom_format_names)
types.push_back(base::UTF8ToUTF16(item.first));
for (const auto& it : data) {
std::u16string type = base::UTF8ToUTF16(it.first.GetName());
types.push_back(type);

@ -10,6 +10,7 @@
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/json/json_reader.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "third_party/skia/include/core/SkBitmap.h"
@ -137,6 +138,33 @@ base::Time Clipboard::GetLastModifiedTime() const {
void Clipboard::ClearLastModifiedTime() {}
std::map<std::string, std::string> Clipboard::ExtractCustomPlatformNames(
ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst) const {
// Read the JSON metadata payload.
std::map<std::string, std::string> custom_format_names;
if (IsFormatAvailable(ui::ClipboardFormatType::WebCustomFormatMap(), buffer,
data_dst)) {
std::string custom_format_json;
// Read the custom format map.
ReadData(ui::ClipboardFormatType::WebCustomFormatMap(), data_dst,
&custom_format_json);
if (!custom_format_json.empty()) {
absl::optional<base::Value> json_val =
base::JSONReader::Read(custom_format_json);
if (json_val.has_value()) {
for (const auto it : json_val->DictItems()) {
std::string custom_format_name;
if (it.second.GetAsString(&custom_format_name)) {
custom_format_names.emplace(it.first, custom_format_name);
}
}
}
}
}
return custom_format_names;
}
Clipboard::Clipboard() = default;
Clipboard::~Clipboard() = default;
@ -203,6 +231,11 @@ void Clipboard::DispatchPortableRepresentation(PortableFormat format,
&(params[1].front()), params[1].size());
break;
case PortableFormat::kWebCustomFormatMap:
WriteData(ClipboardFormatType::WebCustomFormatMap(),
&(params[0].front()), params[0].size());
break;
default:
NOTREACHED();
}
@ -211,7 +244,7 @@ void Clipboard::DispatchPortableRepresentation(PortableFormat format,
void Clipboard::DispatchPlatformRepresentations(
std::vector<Clipboard::PlatformRepresentation> platform_representations) {
for (const auto& representation : platform_representations) {
WriteData(ClipboardFormatType::GetCustomPlatformType(representation.format),
WriteData(ClipboardFormatType::CustomPlatformType(representation.format),
reinterpret_cast<const char*>(representation.data.data()),
representation.data.size());
}

@ -8,6 +8,7 @@
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
@ -265,6 +266,15 @@ class COMPONENT_EXPORT(UI_BASE_CLIPBOARD) Clipboard
// Resets the clipboard last modified time to Time::Time().
virtual void ClearLastModifiedTime();
// Reads the web custom format map (which is in JSON format) from the
// clipboard if it's available. Parses the JSON string that has the mapping of
// MIME type to custom format name and fetches the list of custom MIME types.
// e.g. on Windows, the mapping is represented as "text/html":"Web Custom
// Format(0-99)".
std::map<std::string, std::string> ExtractCustomPlatformNames(
ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst) const;
protected:
// PortableFormat designates the type of data to be stored in the clipboard.
// This designation is shared across all OSes. The system-specific designation
@ -291,6 +301,7 @@ class COMPONENT_EXPORT(UI_BASE_CLIPBOARD) Clipboard
kData, // Arbitrary block of bytes.
kSvg,
kFilenames,
kWebCustomFormatMap,
};
// TODO (https://crbug.com/994928): Rename ObjectMap-related types.
@ -314,6 +325,7 @@ class COMPONENT_EXPORT(UI_BASE_CLIPBOARD) Clipboard
// kWebkit none empty vector
// kData format char array
// data byte array
// kWebCustomFormatMap char array
using ObjectMapParam = std::vector<char>;
using ObjectMapParams = std::vector<ObjectMapParam>;
using ObjectMap = base::flat_map<PortableFormat, ObjectMapParams>;

@ -82,19 +82,19 @@ class COMPONENT_EXPORT(UI_BASE_CLIPBOARD_TYPES) ClipboardFormatType {
static const ClipboardFormatType& MozUrlType();
#endif
// Gets the ClipboardFormatType corresponding to an arbitrary format string,
// registering it with the system if needed. Due to Windows/Linux
// limitations, please place limits on the amount of GetType calls with unique
// |format_string| arguments, when ingesting |format_string| from
// untrusted sources, such as renderer processes. In Windows, a failure will
// return an invalid format with Deserialize()'ed value of "0".
// The custom format name is transformed to the appropriate custom platform
// type name.
static ClipboardFormatType GetCustomPlatformType(
// For custom formats we hardcode the web custom format prefix and the index.
// Due to Windows/Linux limitations, please place limits on the amount of
// `WebCustomFormatName` calls with unique `index` argument.
static std::string WebCustomFormatName(int index);
// Gets the ClipboardFormatType corresponding to a format string,
// registering it with the system if needed.
static ClipboardFormatType CustomPlatformType(
const std::string& format_string);
// Returns a custom MIME type from custom format name.
// e.g. On Windows, "Web Text HTML" is returned as "text/html".
std::string GetCustomPlatformName() const;
// Returns the web custom format map that has the mapping of MIME types to
// custom format names.
static const ClipboardFormatType& WebCustomFormatMap();
// Returns the web custom format map name.
static std::string WebCustomFormatMapName();
// ClipboardFormatType can be used in a set on some platforms.
bool operator<(const ClipboardFormatType& other) const;

@ -4,6 +4,9 @@
#include "ui/base/clipboard/clipboard_format_type.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "ui/base/clipboard/clipboard_constants.h"
namespace ui {
@ -38,15 +41,29 @@ bool ClipboardFormatType::operator==(const ClipboardFormatType& other) const {
return data_ == other.data_;
}
// TODO(crbug.com/106449): Support custom formats.
ClipboardFormatType ClipboardFormatType::GetCustomPlatformType(
// static
std::string ClipboardFormatType::WebCustomFormatName(int index) {
return base::StrCat({"application/web;type=\"custom/format",
base::NumberToString(index), "\""});
}
// static
std::string ClipboardFormatType::WebCustomFormatMapName() {
return "application/web;type=\"custom/formatmap\"";
}
// static
ClipboardFormatType ClipboardFormatType::CustomPlatformType(
const std::string& format_string) {
DCHECK(base::IsStringASCII(format_string));
return ClipboardFormatType::Deserialize(format_string);
}
// TODO(crbug.com/106449): Support custom formats.
std::string ClipboardFormatType::GetCustomPlatformName() const {
return Serialize();
// static
const ClipboardFormatType& ClipboardFormatType::WebCustomFormatMap() {
static base::NoDestructor<ClipboardFormatType> type(
ClipboardFormatType::WebCustomFormatMapName());
return *type;
}
// Various predefined ClipboardFormatTypes.

@ -4,6 +4,9 @@
#include "ui/base/clipboard/clipboard_format_type.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "ui/base/clipboard/clipboard_constants.h"
namespace ui {
@ -41,15 +44,29 @@ bool ClipboardFormatType::operator==(const ClipboardFormatType& other) const {
return data_ == other.data_;
}
// TODO(crbug.com/106449): Support custom formats.
ClipboardFormatType ClipboardFormatType::GetCustomPlatformType(
// static
ClipboardFormatType ClipboardFormatType::CustomPlatformType(
const std::string& format_string) {
DCHECK(base::IsStringASCII(format_string));
return ClipboardFormatType::Deserialize(format_string);
}
// TODO(crbug.com/106449): Support custom formats.
std::string ClipboardFormatType::GetCustomPlatformName() const {
return Serialize();
// static
std::string ClipboardFormatType::WebCustomFormatName(int index) {
return base::StrCat({"application/web;type=\"custom/format",
base::NumberToString(index), "\""});
}
// static
std::string ClipboardFormatType::WebCustomFormatMapName() {
return "application/web;type=\"custom/formatmap\"";
}
// static
const ClipboardFormatType& ClipboardFormatType::WebCustomFormatMap() {
static base::NoDestructor<ClipboardFormatType> type(
ClipboardFormatType::WebCustomFormatMapName());
return *type;
}
// Various predefined ClipboardFormatTypes.

@ -6,6 +6,9 @@
#import <Cocoa/Cocoa.h>
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/base/clipboard/clipboard_constants.h"
@ -58,15 +61,27 @@ bool ClipboardFormatType::operator<(const ClipboardFormatType& other) const {
}
// static
// TODO(crbug.com/106449): Support custom formats.
ClipboardFormatType ClipboardFormatType::GetCustomPlatformType(
const std::string& format_string) {
return ClipboardFormatType::Deserialize(format_string);
std::string ClipboardFormatType::WebCustomFormatName(int index) {
return base::StrCat({"com.web.custom.format", base::NumberToString(index)});
}
// TODO(crbug.com/106449): Support custom formats.
std::string ClipboardFormatType::GetCustomPlatformName() const {
return Serialize();
// static
std::string ClipboardFormatType::WebCustomFormatMapName() {
return "com.web.custom.format.map";
}
// static
const ClipboardFormatType& ClipboardFormatType::WebCustomFormatMap() {
static base::NoDestructor<ClipboardFormatType> type(
base::SysUTF8ToNSString(ClipboardFormatType::WebCustomFormatMapName()));
return *type;
}
// static
ClipboardFormatType ClipboardFormatType::CustomPlatformType(
const std::string& format_string) {
DCHECK(base::IsStringASCII(format_string));
return ClipboardFormatType::Deserialize(format_string);
}
// Various predefined ClipboardFormatTypes.

@ -58,74 +58,32 @@ ClipboardFormatType ClipboardFormatType::Deserialize(
}
// static
ClipboardFormatType ClipboardFormatType::GetCustomPlatformType(
const std::string& format_string) {
// For unsanitized custom formats, we add `Web ` prefix and capitalize the
// first letter of each word in the format. e.g. `text/custom` format would be
// converted to `Web Text Custom`. Similarly for `text/html` or any other
// standard formats, the pickled version would be prefixed with `Web ` and
// first letter capitalized. e.g. text/html would be converted to `Web Text
// Html`.
// For security reasons we also check for valid ascii codepoints.
constexpr int kMinNameSize = 3; // Formats need to have at least 3 chars.
if (!base::IsStringASCII(format_string) ||
format_string.size() < kMinNameSize) {
return ClipboardFormatType();
}
size_t index = format_string.find('/');
if (index == std::string::npos || index == 0 ||
index == format_string.size() - 1)
return ClipboardFormatType();
base::StringPiece first_part =
base::StringPiece(format_string).substr(0, index);
base::StringPiece second_part =
base::StringPiece(format_string).substr(index + 1);
std::string web_custom_format_string = base::StrCat(
{"Web ", base::ToUpperASCII(first_part.substr(0, 1)),
first_part.substr(1), " ", base::ToUpperASCII(second_part.substr(0, 1)),
second_part.substr(1)});
return ClipboardFormatType(::RegisterClipboardFormat(
base::ASCIIToWide(web_custom_format_string).c_str()));
std::string ClipboardFormatType::WebCustomFormatName(int index) {
return base::StrCat({"Web Custom Format", base::NumberToString(index)});
}
// static
std::string ClipboardFormatType::GetCustomPlatformName() const {
constexpr size_t kMaxFormatSize = 1024;
static base::NoDestructor<std::vector<wchar_t>> name_buffer(kMaxFormatSize);
int name_size = GetClipboardFormatName(data_.cfFormat, name_buffer->data(),
kMaxFormatSize);
// Custom formats should have at least 7 characters. e.g. "Web x y"
constexpr int kMinNameSize = 7;
if (!name_size || name_size < kMinNameSize) {
// Input format doesn't exist or is predefined.
return std::string();
}
std::string ClipboardFormatType::WebCustomFormatMapName() {
return "Web Custom Format Map";
}
std::string format_name_in_clipboard =
base::WideToASCII(std::wstring(name_buffer->data(), name_size));
// static
ClipboardFormatType ClipboardFormatType::CustomPlatformType(
const std::string& format_string) {
// Once these formats are registered, `RegisterClipboardFormat` just returns
// the `cfFormat` associated with it and doesn't register a new format.
DCHECK(base::IsStringASCII(format_string));
return ClipboardFormatType(
::RegisterClipboardFormat(base::ASCIIToWide(format_string).c_str()));
}
// For security reasons we also check for valid ascii codepoints.
if (!base::IsStringASCII(format_name_in_clipboard))
return std::string();
// For unsanitized custom formats (prefixed with Web) we extract the strings
// and convert it into standard representation. e.g. `Web Text Custom` format
// would be converted to `text/custom`.
if (format_name_in_clipboard.substr(0, 4) == "Web ") {
base::StringPiece format_name =
base::StringPiece(format_name_in_clipboard).substr(4);
size_t space_index = format_name.find(" ");
if (space_index == std::string::npos)
return std::string();
if (format_name.size() < (space_index + 2))
return std::string();
return base::StrCat(
{base::ToLowerASCII(format_name.substr(0, space_index)), "/",
base::ToLowerASCII(format_name.substr(space_index + 1))});
}
return format_name_in_clipboard;
// static
const ClipboardFormatType& ClipboardFormatType::WebCustomFormatMap() {
static base::NoDestructor<ClipboardFormatType> format(
::RegisterClipboardFormat(
base::ASCIIToWide(ClipboardFormatType::WebCustomFormatMapName())
.c_str()));
return *format;
}
std::string ClipboardFormatType::GetName() const {

@ -809,11 +809,11 @@ TYPED_TEST(ClipboardTest, MultiplePickleTest) {
EXPECT_EQ(payload1, unpickled_string1);
}
// TODO(crbug.com/106449): Implement custom formats on other platforms.
#if defined(OS_WIN)
TYPED_TEST(ClipboardTest, DataTest) {
const std::string kFormatString = "chromium/x-test-format";
const std::u16string kFormatString16 = u"chromium/x-test-format";
const ClipboardFormatType kFormat =
ClipboardFormatType::GetCustomPlatformType(kFormatString);
const std::string payload = "test string";
base::span<const uint8_t> payload_span(
reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
@ -824,55 +824,28 @@ TYPED_TEST(ClipboardTest, DataTest) {
mojo_base::BigBuffer(payload_span));
}
ASSERT_TRUE(this->clipboard().IsFormatAvailable(
kFormat, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr));
std::map<std::string, std::string> custom_format_names =
this->clipboard().ExtractCustomPlatformNames(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr);
EXPECT_TRUE(custom_format_names.find(kFormatString) !=
custom_format_names.end());
std::string output;
this->clipboard().ReadData(kFormat, /* data_dst = */ nullptr, &output);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString]),
/* data_dst = */ nullptr, &output);
EXPECT_EQ(payload, output);
}
#if defined(OS_WIN)
TYPED_TEST(ClipboardTest, CustomFormatTypeAndNameTest) {
const std::string kFormatString1 = "a/bc";
const std::string kFormatString2 = "/abc";
const std::string kFormatString3 = "abc/";
const std::string kFormatString4 = "a/";
const std::string kFormatString5 = "/a";
const std::string kFormatString6 = "a/c";
ClipboardFormatType format =
ClipboardFormatType::GetCustomPlatformType(kFormatString1);
EXPECT_EQ("a/bc", format.GetCustomPlatformName());
format = ClipboardFormatType::GetCustomPlatformType(kFormatString2);
EXPECT_EQ("", format.GetCustomPlatformName());
format = ClipboardFormatType::GetCustomPlatformType(kFormatString3);
EXPECT_EQ("", format.GetCustomPlatformName());
format = ClipboardFormatType::GetCustomPlatformType(kFormatString4);
EXPECT_EQ("", format.GetCustomPlatformName());
format = ClipboardFormatType::GetCustomPlatformType(kFormatString5);
EXPECT_EQ("", format.GetCustomPlatformName());
format = ClipboardFormatType::GetCustomPlatformType(kFormatString6);
EXPECT_EQ("a/c", format.GetCustomPlatformName());
}
#endif
// crbug.com/1224904: Flaky on Mac.
#if (!defined(USE_AURA) || defined(OS_WIN) || defined(USE_OZONE) || \
defined(USE_X11)) && \
!BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_MAC)
TYPED_TEST(ClipboardTest, MultipleDataTest) {
const std::string kFormatString1 = "chromium/x-test-format1";
const std::u16string kFormatString116 = u"chromium/x-test-format1";
const ClipboardFormatType kFormat1 =
ClipboardFormatType::GetCustomPlatformType(kFormatString1);
const std::string payload1("test string1");
base::span<const uint8_t> payload_span1(
reinterpret_cast<const uint8_t*>(payload1.data()), payload1.size());
const std::string kFormatString2 = "chromium/x-test-format2";
const std::u16string kFormatString216 = u"chromium/x-test-format2";
const ClipboardFormatType kFormat2 =
ClipboardFormatType::GetCustomPlatformType(kFormatString2);
const std::string payload2("test string2");
base::span<const uint8_t> payload_span2(
reinterpret_cast<const uint8_t*>(payload2.data()), payload2.size());
@ -890,36 +863,42 @@ TYPED_TEST(ClipboardTest, MultipleDataTest) {
EXPECT_THAT(this->clipboard().ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr),
Contains(kFormatString116));
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
kFormat1, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr));
std::string custom_format_json;
this->clipboard().ReadData(ClipboardFormatType::WebCustomFormatMap(),
/* data_dst = */ nullptr, &custom_format_json);
std::map<std::string, std::string> custom_format_names =
this->clipboard().ExtractCustomPlatformNames(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr);
EXPECT_TRUE(custom_format_names.find(kFormatString1) !=
custom_format_names.end());
std::string output1;
this->clipboard().ReadData(kFormat1, /* data_dst = */ nullptr, &output1);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString1]),
/* data_dst = */ nullptr, &output1);
EXPECT_EQ(payload1, output1);
// Check format 2.
EXPECT_THAT(this->clipboard().ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr),
Contains(kFormatString216));
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
kFormat2, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr));
EXPECT_TRUE(custom_format_names.find(kFormatString2) !=
custom_format_names.end());
std::string output2;
this->clipboard().ReadData(kFormat2, /* data_dst = */ nullptr, &output2);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString2]),
/* data_dst = */ nullptr, &output2);
EXPECT_EQ(payload2, output2);
}
TYPED_TEST(ClipboardTest, DataAndPortableFormatTest) {
const std::string kFormatString1 = "chromium/x-test-format1";
const std::u16string kFormatString116 = u"chromium/x-test-format1";
const ClipboardFormatType kFormat1 =
ClipboardFormatType::GetCustomPlatformType(kFormatString1);
const std::string payload1("test string1");
base::span<const uint8_t> payload_span1(
reinterpret_cast<const uint8_t*>(payload1.data()), payload1.size());
const std::string kFormatString2 = "text/plain";
const std::u16string kFormatString216 = u"text/plain";
const ClipboardFormatType kFormat2 =
ClipboardFormatType::GetCustomPlatformType(kFormatString2);
const std::string payload2("test string2");
base::span<const uint8_t> payload_span2(
reinterpret_cast<const uint8_t*>(payload2.data()), payload2.size());
@ -937,20 +916,30 @@ TYPED_TEST(ClipboardTest, DataAndPortableFormatTest) {
EXPECT_THAT(this->clipboard().ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr),
Contains(kFormatString116));
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
kFormat1, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr));
std::string custom_format_json;
this->clipboard().ReadData(ClipboardFormatType::WebCustomFormatMap(),
/* data_dst = */ nullptr, &custom_format_json);
std::map<std::string, std::string> custom_format_names =
this->clipboard().ExtractCustomPlatformNames(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr);
EXPECT_TRUE(custom_format_names.find(kFormatString1) !=
custom_format_names.end());
std::string output1;
this->clipboard().ReadData(kFormat1, /* data_dst = */ nullptr, &output1);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString1]),
/* data_dst = */ nullptr, &output1);
EXPECT_EQ(payload1, output1);
// Check format 2.
EXPECT_THAT(this->clipboard().ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr),
Contains(kFormatString216));
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
kFormat2, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr));
EXPECT_TRUE(custom_format_names.find(kFormatString2) !=
custom_format_names.end());
std::string output2;
this->clipboard().ReadData(kFormat2, /* data_dst = */ nullptr, &output2);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString2]),
/* data_dst = */ nullptr, &output2);
EXPECT_EQ(payload2, output2);
}
#endif
@ -1050,40 +1039,20 @@ TYPED_TEST(ClipboardTest, PlatformSpecificDataTest) {
mojo_base::BigBuffer(text_span));
}
const std::vector<std::u16string> raw_types =
this->clipboard().ReadAvailablePlatformSpecificFormatNames(
ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr);
std::string custom_format_json;
this->clipboard().ReadData(ClipboardFormatType::WebCustomFormatMap(),
/* data_dst = */ nullptr, &custom_format_json);
std::map<std::string, std::string> custom_format_names =
this->clipboard().ExtractCustomPlatformNames(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr);
EXPECT_THAT(raw_types, Contains(ASCIIToUTF16(kFormatString)));
#if defined(OS_WIN)
// Custom format is only available on Windows.
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
ClipboardFormatType::GetCustomPlatformType(kFormatString),
ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr));
#else
// TODO(crbug.com/106449): Support custom formats on other platforms.
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
ClipboardFormatType::PlainTextType(), ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr));
std::string text_result;
this->clipboard().ReadAsciiText(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr, &text_result);
EXPECT_EQ(text_result, text);
// Windows will automatically convert CF_TEXT to its UNICODE version.
EXPECT_TRUE(this->clipboard().IsFormatAvailable(
ClipboardFormatType::PlainTextType(), ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr));
std::u16string text_result16;
this->clipboard().ReadText(ClipboardBuffer::kCopyPaste,
/* data_dst = */ nullptr, &text_result16);
EXPECT_EQ(text_result16, base::ASCIIToUTF16(text));
#endif // defined(OS_WIN)
EXPECT_TRUE(custom_format_names.find(kFormatString) !=
custom_format_names.end());
std::string platform_specific_result;
this->clipboard().ReadData(
ClipboardFormatType::GetCustomPlatformType(kFormatString),
/* data_dst = */ nullptr, &platform_specific_result);
this->clipboard().ReadData(ClipboardFormatType::CustomPlatformType(
custom_format_names[kFormatString]),
/* data_dst = */ nullptr,
&platform_specific_result);
EXPECT_EQ(platform_specific_result, kPlatformSpecificText);
}
#endif // defined(OS_WIN) || defined(USE_X11)

@ -354,13 +354,16 @@ ClipboardWin::ReadAvailablePlatformSpecificFormatNames(
if (!clipboard.Acquire(GetClipboardWindow()))
return {};
// Check if we have any custom formats in the clipboard.
std::map<std::string, std::string> custom_format_names =
ExtractCustomPlatformNames(buffer, data_dst);
for (const auto& items : custom_format_names) {
types.push_back(base::ASCIIToUTF16(items.first));
}
UINT cf_format = 0;
cf_format = ::EnumClipboardFormats(cf_format);
while (cf_format) {
std::string type_name = ClipboardFormatType(cf_format).GetName();
// Search for custom types if we couldn't find a standard format.
if (type_name.empty())
type_name = ClipboardFormatType(cf_format).GetCustomPlatformName();
if (!type_name.empty())
types.push_back(base::ASCIIToUTF16(type_name));
cf_format = ::EnumClipboardFormats(cf_format);

@ -6,8 +6,10 @@
#include <memory>
#include <utility>
#include "base/json/json_writer.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "net/base/escape.h"
#include "ui/base/clipboard/clipboard_format_type.h"
#include "ui/base/clipboard/clipboard_metrics.h"
@ -25,6 +27,19 @@ ScopedClipboardWriter::ScopedClipboardWriter(
ScopedClipboardWriter::~ScopedClipboardWriter() {
static constexpr size_t kMaxRepresentations = 1 << 12;
DCHECK(platform_representations_.size() < kMaxRepresentations);
// If the metadata format type is not empty then create a JSON payload and
// write to the clipboard.
if (!registered_formats_.empty()) {
std::string custom_format_json;
base::Value registered_formats_value(base::Value::Type::DICTIONARY);
for (const auto& item : registered_formats_)
registered_formats_value.SetStringKey(item.first, item.second);
base::JSONWriter::Write(registered_formats_value, &custom_format_json);
Clipboard::ObjectMapParams parameters;
parameters.push_back(Clipboard::ObjectMapParam(custom_format_json.begin(),
custom_format_json.end()));
objects_[Clipboard::PortableFormat::kWebCustomFormatMap] = parameters;
}
if (!objects_.empty() || !platform_representations_.empty()) {
Clipboard::GetForCurrentThread()->WritePortableAndPlatformRepresentations(
buffer_, objects_, std::move(platform_representations_),
@ -171,15 +186,39 @@ void ScopedClipboardWriter::WritePickledData(
void ScopedClipboardWriter::WriteData(const std::u16string& format,
mojo_base::BigBuffer data) {
RecordWrite(ClipboardFormatMetric::kData);
platform_representations_.push_back(
{base::UTF16ToASCII(format), std::move(data)});
// 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) Malicious sites can write more than the hard limit defined on
// Windows(16k). (3) Chrome also registers other clipboard formats.
//
// There will be a custom format map which contains a JSON payload that will
// have a mapping of custom format MIME type to web custom format.
// There can only be 100 custom format per write and it will be
// registered when the web authors request for a custom format.
static constexpr int kMaxRegisteredFormats = 100;
if (counter_ >= kMaxRegisteredFormats)
return;
std::string format_in_ascii = base::UTF16ToASCII(format);
if (registered_formats_.find(format_in_ascii) == registered_formats_.end()) {
std::string web_custom_format_string =
ClipboardFormatType::WebCustomFormatName(counter_);
registered_formats_[format_in_ascii] = web_custom_format_string;
counter_++;
platform_representations_.push_back(
{web_custom_format_string, std::move(data)});
}
}
void ScopedClipboardWriter::Reset() {
objects_.clear();
platform_representations_.clear();
registered_formats_.clear();
bitmap_.reset();
confidential_ = false;
counter_ = 0;
}
} // namespace ui

@ -8,6 +8,7 @@
#include <string>
#include "base/component_export.h"
#include "base/containers/flat_map.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
@ -94,6 +95,9 @@ class COMPONENT_EXPORT(UI_BASE_CLIPBOARD) ScopedClipboardWriter {
Clipboard::ObjectMap objects_;
std::vector<Clipboard::PlatformRepresentation> platform_representations_;
// Keeps track of the unique custom formats registered in the clipboard.
base::flat_map<std::string, std::string> registered_formats_;
int counter_ = 0;
// The type is set at construction, and can be changed before committing.
const ClipboardBuffer buffer_;

@ -142,11 +142,15 @@ TestClipboard::ReadAvailablePlatformSpecificFormatNames(
const auto& data = store.data;
std::vector<std::u16string> types;
types.reserve(data.size());
std::map<std::string, std::string> custom_format_names =
ExtractCustomPlatformNames(buffer, data_dst);
for (const auto& item : custom_format_names)
types.push_back(base::UTF8ToUTF16(item.first));
for (const auto& it : data) {
std::string format_type = it.first.GetName();
if (format_type.empty())
format_type = it.first.GetCustomPlatformName();
types.push_back(base::UTF8ToUTF16(format_type));
if (!format_type.empty()) {
types.push_back(base::UTF8ToUTF16(format_type));
}
}
return types;
@ -314,6 +318,7 @@ void TestClipboard::WritePortableAndPlatformRepresentations(
std::unique_ptr<DataTransferEndpoint> data_src) {
Clear(buffer);
default_store_buffer_ = buffer;
DispatchPlatformRepresentations(std::move(platform_representations));
for (const auto& kv : objects)
DispatchPortableRepresentation(kv.first, kv.second);