Diff
Modified: trunk/LayoutTests/ChangeLog (253485 => 253486)
--- trunk/LayoutTests/ChangeLog 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/LayoutTests/ChangeLog 2019-12-13 18:39:40 UTC (rev 253486)
@@ -1,3 +1,25 @@
+2019-12-13 Wenson Hsieh <[email protected]>
+
+ [Clipboard API] Sanitize HTML and image data written using clipboard.write
+ https://bugs.webkit.org/show_bug.cgi?id=205188
+ <rdar://problem/57612968>
+
+ Reviewed by Darin Adler.
+
+ * editing/async-clipboard/sanitize-when-reading-markup-expected.txt: Added.
+ * editing/async-clipboard/sanitize-when-reading-markup.html: Added.
+
+ Add a test to verify that markup is sanitized when copying and pasting across different security origins.
+
+ * editing/async-clipboard/sanitize-when-writing-image-expected.txt: Added.
+ * editing/async-clipboard/sanitize-when-writing-image.html: Added.
+
+ Add a test to verify that "image/png" data is sanitized, and one or more written image data that cannot be
+ decoded results in the promise being rejected.
+
+ * platform/mac-wk1/TestExpectations:
+ * platform/win/TestExpectations:
+
2019-12-13 Chris Dumez <[email protected]>
Behavior of [[GetOwnProperty]] for cross-origin windows is not spec-compliant
Added: trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup-expected.txt (0 => 253486)
--- trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup-expected.txt (rev 0)
+++ trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup-expected.txt 2019-12-13 18:39:40 UTC (rev 253486)
@@ -0,0 +1,22 @@
+This test verifies that navigator.clipboard.writeText sanitizes 'text/html'. To manually run the test, click the button in the subframe below to copy HTML, and then click the paste button.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Wrote markup to the clipboard.
+PASS finishedCopying became true
+PASS Read items from the clipboard.
+PASS items.length is 2
+PASS items[0].types is ['text/html']
+PASS fragment1.documentElement.textContent is "Hello world 1"
+PASS fragment1.querySelector('p') is non-null.
+PASS fragment1.querySelector('script') is null
+PASS fragment1.querySelector('p').onclick is null
+PASS items[1].types is ['text/html']
+PASS fragment2.documentElement.textContent is "Hello world 2"
+PASS fragment2.querySelector('span') is non-null.
+PASS fragment2.querySelector('p') is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup.html (0 => 253486)
--- trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup.html (rev 0)
+++ trunk/LayoutTests/editing/async-clipboard/sanitize-when-reading-markup.html 2019-12-13 18:39:40 UTC (rev 253486)
@@ -0,0 +1,90 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ domPasteAllowed=false useFlexibleViewport=true experimental:AsyncClipboardAPIEnabled=true ] -->
+<html>
+ <meta charset="utf8">
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+ <script src=""
+ <script src=""
+ <script src=""
+ <style>
+ button {
+ display: block;
+ margin: 40px 0;
+ }
+
+ iframe {
+ width: 240px;
+ height: 120px;
+ }
+ </style>
+ </head>
+ <script>
+ jsTestIsAsync = true;
+ finishedCopying = false;
+
+ async function runTest() {
+ description("This test verifies that navigator.clipboard.writeText sanitizes 'text/html'. To manually run the test, click the button in the subframe below to copy HTML, and then click the paste button.");
+
+ const frame = document.querySelector("iframe");
+ const button = document.querySelector("button");
+ button.addEventListener("click", async () => {
+ try {
+ items = await navigator.clipboard.read();
+ testPassed("Read items from the clipboard.");
+ shouldBe("items.length", "2");
+
+ shouldBe("items[0].types", "['text/html']");
+ fragment1 = await loadDocument(await items[0].getType("text/html"));
+ shouldBeEqualToString("fragment1.documentElement.textContent", "Hello world 1");
+ shouldBeNonNull("fragment1.querySelector('p')");
+ shouldBeNull("fragment1.querySelector('script')");
+ shouldBeNull("fragment1.querySelector('p').onclick");
+
+ shouldBe("items[1].types", "['text/html']");
+ fragment2 = await loadDocument(await items[1].getType("text/html"));
+ shouldBeEqualToString("fragment2.documentElement.textContent", "Hello world 2");
+ shouldBeNonNull("fragment2.querySelector('span')");
+ shouldBeNull("fragment2.querySelector('p')");
+ } catch (exception) {
+ testFailed("Did not write to or read from the clipboard.");
+ } finally {
+ button.remove();
+ frame.remove();
+ finishJSTest();
+ }
+ });
+
+ addEventListener("message", event => {
+ testPassed("Wrote markup to the clipboard.");
+ finishedCopying = event.data ="" "finished-copying";
+ });
+
+ if (!window.testRunner)
+ return;
+
+ await UIHelper.activateElement(document.querySelector("iframe"));
+ await new Promise(resolve => shouldBecomeEqual("finishedCopying", "true", resolve));
+ await triggerProgrammaticPaste(button);
+ }
+
+ addEventListener("load", runTest);
+ </script>
+ <body>
+ <iframe src=""
+ <button id='copy' style='font-size: 40px; text-align: center;'>Click to copy</button>
+ <script>
+ const markup1 = `<script>console.log('This script tag should be sanitized out.')</${'script'}><p _onclick_='_javascript_:void()'>Hello world 1</p>`;
+ const markup2 = `<p style='display: none;'>You should not see this text.</p><span>Hello world 2</span>`;
+ copy.addEventListener('click', async () => {
+ await navigator.clipboard.write([
+ new ClipboardItem({ 'text/html' : markup1 }),
+ new ClipboardItem({ 'text/html' : markup2 })
+ ]);
+ parent.postMessage('finished-copying', '*');
+ });
+ </script>"></iframe>
+ <button>Click to paste</button>
+ <p id="description"></p>
+ <p id="console"></p>
+ </body>
+</html>
Added: trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image-expected.txt (0 => 253486)
--- trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image-expected.txt (rev 0)
+++ trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image-expected.txt 2019-12-13 18:39:40 UTC (rev 253486)
@@ -0,0 +1,14 @@
+This test verifies that navigator.clipboard.writeText sanitizes 'image/png'. To manually run the test, click the buttons labeled (1), (2), and then (3)
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS Wrote a valid image to the clipboard.
+PASS doneWritingFirstImage became true
+PASS Did not write an invalid image to the clipboard.
+PASS doneWritingSecondImage became true
+PASS Did not write an invalid image to the clipboard.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image.html (0 => 253486)
--- trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image.html (rev 0)
+++ trunk/LayoutTests/editing/async-clipboard/sanitize-when-writing-image.html 2019-12-13 18:39:40 UTC (rev 253486)
@@ -0,0 +1,84 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true experimental:AsyncClipboardAPIEnabled=true ] -->
+<html>
+ <meta charset="utf8">
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+ <script src=""
+ <script src=""
+ <script src=""
+ <style>
+ button {
+ display: block;
+ margin: 40px 0;
+ }
+ </style>
+ </head>
+ <script>
+ jsTestIsAsync = true;
+ doneWritingFirstImage = false;
+ doneWritingSecondImage = false;
+
+ async function runTest() {
+ description("This test verifies that navigator.clipboard.writeText sanitizes 'image/png'. To manually run the test, click the buttons labeled (1), (2), and then (3)");
+
+ const writeValidImageButton = document.getElementById("valid");
+ const writeInvalidImageButton = document.getElementById("invalid");
+ const writeBothImagesButton = document.getElementById("both");
+
+ const validImageItem = new ClipboardItem({ "image/png" : imageBlob() });
+ const invalidImageItem = new ClipboardItem({ "image/png" : imageBlob("ThisIsNotActuallyBase64ImageData") });
+
+ writeValidImageButton.addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.write([validImageItem]);
+ testPassed("Wrote a valid image to the clipboard.");
+ } catch (exception) {
+ testFailed("Did not write a valid image to the clipboard.");
+ } finally {
+ doneWritingFirstImage = true;
+ }
+ });
+
+ writeInvalidImageButton.addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.write([invalidImageItem]);
+ testFailed("Should not have written anything to the clipboard.");
+ } catch (exception) {
+ testPassed("Did not write an invalid image to the clipboard.");
+ } finally {
+ doneWritingSecondImage = true;
+ }
+ });
+
+ writeBothImagesButton.addEventListener("click", async () => {
+ try {
+ await navigator.clipboard.write([validImageItem, invalidImageItem]);
+ testFailed("Should not have written anything to the clipboard.");
+ } catch (exception) {
+ testPassed("Did not write an invalid image to the clipboard.");
+ } finally {
+ [writeValidImageButton, writeInvalidImageButton, writeBothImagesButton].map(button => button.remove());
+ finishJSTest();
+ }
+ });
+
+ if (!window.testRunner)
+ return;
+
+ await UIHelper.activateElement(writeValidImageButton);
+ await new Promise(resolve => shouldBecomeEqual("doneWritingFirstImage", "true", resolve));
+ await UIHelper.activateElement(writeInvalidImageButton);
+ await new Promise(resolve => shouldBecomeEqual("doneWritingSecondImage", "true", resolve));
+ await UIHelper.activateElement(writeBothImagesButton);
+ }
+
+ addEventListener("load", runTest);
+ </script>
+ <body>
+ <button id="valid">1. Write valid item</button>
+ <button id="invalid">2. Write invalid item</button>
+ <button id="both">3. Write valid and invalid items</button>
+ <p id="description"></p>
+ <p id="console"></p>
+ </body>
+</html>
Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (253485 => 253486)
--- trunk/LayoutTests/platform/mac-wk1/TestExpectations 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations 2019-12-13 18:39:40 UTC (rev 253486)
@@ -72,6 +72,7 @@
editing/async-clipboard/clipboard-do-not-read-text-from-platform-if-text-changes.html [ Skip ]
editing/async-clipboard/clipboard-read-text-from-platform.html [ Skip ]
editing/async-clipboard/clipboard-read-text-same-origin.html [ Skip ]
+editing/async-clipboard/sanitize-when-reading-markup.html [ Skip ]
imported/w3c/web-platform-tests/websockets/Close-1000-reason.any.html [ Pass Failure ]
imported/w3c/web-platform-tests/websockets/Close-1000-reason.any.worker.html [ Pass Failure ]
Modified: trunk/LayoutTests/platform/win/TestExpectations (253485 => 253486)
--- trunk/LayoutTests/platform/win/TestExpectations 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/LayoutTests/platform/win/TestExpectations 2019-12-13 18:39:40 UTC (rev 253486)
@@ -1200,6 +1200,8 @@
webkit.org/b/203100 editing/async-clipboard/clipboard-read-text-from-platform.html [ Skip ]
webkit.org/b/203100 editing/async-clipboard/clipboard-read-text-same-origin.html [ Skip ]
webkit.org/b/203100 editing/async-clipboard/clipboard-read-text.html [ Skip ]
+webkit.org/b/203100 editing/async-clipboard/sanitize-when-writing-image.html [ Skip ]
+webkit.org/b/203100 editing/async-clipboard/sanitize-when-reading-markup.html [ Skip ]
webkit.org/b/140783 [ Release ] editing/pasteboard/copy-standalone-image.html [ Failure ImageOnlyFailure ]
webkit.org/b/140783 [ Debug ] editing/pasteboard/copy-standalone-image.html [ Skip ] # Debug Assertion
Modified: trunk/Source/WebCore/ChangeLog (253485 => 253486)
--- trunk/Source/WebCore/ChangeLog 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/ChangeLog 2019-12-13 18:39:40 UTC (rev 253486)
@@ -1,3 +1,55 @@
+2019-12-13 Wenson Hsieh <[email protected]>
+
+ [Clipboard API] Sanitize HTML and image data written using clipboard.write
+ https://bugs.webkit.org/show_bug.cgi?id=205188
+ <rdar://problem/57612968>
+
+ Reviewed by Darin Adler.
+
+ Sanitizes HTML ("text/html") and image ("image/png") content when writing to the platform pasteboard using the
+ clipboard API. See below for more details.
+
+ Tests: editing/async-clipboard/sanitize-when-reading-markup.html
+ editing/async-clipboard/sanitize-when-writing-image.html
+ ClipboardTests.WriteSanitizedMarkup
+
+ * Modules/async-clipboard/ClipboardItemBindingsDataSource.cpp:
+ (WebCore::ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::sanitizeDataIfNeeded):
+
+ Add a new helper method to sanitize `m_data` after loading finishes, but before invoking the completion handler
+ to notify the data source about the clipboard data. Currently, we support writing "text/plain", "text/uri-list",
+ "text/html" and "image/png"; we sanitize HTML by stripping away hidden content such as comments, script, and
+ event listeners, and sanitize image data by painting it into a new graphics context and re-encoding the rendered
+ contents as an image.
+
+ (WebCore::ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::invokeCompletionHandler):
+ * Modules/async-clipboard/ClipboardItemBindingsDataSource.h:
+ * platform/PlatformPasteboard.h:
+ * platform/cocoa/PasteboardCocoa.mm:
+ (WebCore::cocoaTypeToImageType):
+ * platform/ios/PlatformPasteboardIOS.mm:
+ (WebCore::PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite):
+
+ Adjust these helpers to map "image/png" to the platform PNG type on iOS ("public.png"). The extra
+ IncludeImageTypes argument was added to avoid considering "image/png" a web-safe type for writing and reading
+ when exercising DataTransfer APIs, which currently don't support reading the "image/png" MIME type. In the
+ future, we should consider adding support for image sanitization when using DataTransfer.setData or
+ DataTransferItemList.add, and then remove this flag.
+
+ (WebCore::createItemProviderRegistrationList):
+ * platform/mac/LegacyNSPasteboardTypes.h:
+
+ Add an entry for legacyPNGPasteboardType on macOS, and replace one use of it in PasteboardCocoa.mm.
+
+ (WebCore::legacyPNGPasteboardType):
+ * platform/mac/PlatformPasteboardMac.mm:
+ (WebCore::PlatformPasteboard::write):
+ (WebCore::PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite):
+
+ Likewise, map "image/png" to legacyPNGPasteboardType() on macOS, which was added above.
+
+ (WebCore::createPasteboardItem):
+
2019-12-13 Chris Dumez <[email protected]>
Behavior of [[GetOwnProperty]] for cross-origin windows is not spec-compliant
Modified: trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.cpp (253485 => 253486)
--- trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.cpp 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.cpp 2019-12-13 18:39:40 UTC (rev 253486)
@@ -26,6 +26,7 @@
#include "config.h"
#include "ClipboardItemBindingsDataSource.h"
+#include "BitmapImage.h"
#include "Blob.h"
#include "Clipboard.h"
#include "ClipboardItem.h"
@@ -32,6 +33,8 @@
#include "Document.h"
#include "FileReaderLoader.h"
#include "Frame.h"
+#include "GraphicsContext.h"
+#include "ImageBuffer.h"
#include "JSBlob.h"
#include "JSDOMPromise.h"
#include "JSDOMPromiseDeferred.h"
@@ -252,10 +255,51 @@
invokeCompletionHandler();
}
+void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::sanitizeDataIfNeeded()
+{
+ if (m_type == "text/html"_s) {
+ String markupToSanitize;
+ if (WTF::holds_alternative<Ref<SharedBuffer>>(m_data)) {
+ auto& buffer = WTF::get<Ref<SharedBuffer>>(m_data);
+ markupToSanitize = String::fromUTF8(buffer->data(), buffer->size());
+ } else if (WTF::holds_alternative<String>(m_data))
+ markupToSanitize = WTF::get<String>(m_data);
+
+ if (markupToSanitize.isEmpty())
+ return;
+
+ m_data = { sanitizeMarkup(markupToSanitize) };
+ }
+
+ if (m_type == "image/png"_s) {
+ RefPtr<SharedBuffer> bufferToSanitize;
+ if (WTF::holds_alternative<Ref<SharedBuffer>>(m_data))
+ bufferToSanitize = WTF::get<Ref<SharedBuffer>>(m_data).ptr();
+ else if (WTF::holds_alternative<String>(m_data))
+ bufferToSanitize = utf8Buffer(WTF::get<String>(m_data));
+
+ if (!bufferToSanitize || bufferToSanitize->isEmpty())
+ return;
+
+ auto bitmapImage = BitmapImage::create();
+ bitmapImage->setData(WTFMove(bufferToSanitize), true);
+ auto imageBuffer = ImageBuffer::create(bitmapImage->size(), Unaccelerated);
+ if (!imageBuffer) {
+ m_data = { nullString() };
+ return;
+ }
+
+ imageBuffer->context().drawImage(bitmapImage.get(), FloatPoint::zero());
+ m_data = { SharedBuffer::create(imageBuffer->toData("image/png"_s)) };
+ }
+}
+
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::invokeCompletionHandler()
{
- if (auto completion = WTFMove(m_completionHandler))
+ if (auto completion = WTFMove(m_completionHandler)) {
+ sanitizeDataIfNeeded();
completion();
+ }
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didResolveToBlob(ScriptExecutionContext& context, Ref<Blob>&& blob)
Modified: trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.h (253485 => 253486)
--- trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.h 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/Modules/async-clipboard/ClipboardItemBindingsDataSource.h 2019-12-13 18:39:40 UTC (rev 253486)
@@ -73,6 +73,7 @@
private:
ClipboardItemTypeLoader(const String& type, CompletionHandler<void()>&&);
+ void sanitizeDataIfNeeded();
void invokeCompletionHandler();
// FileReaderLoaderClient methods.
Modified: trunk/Source/WebCore/platform/PlatformPasteboard.h (253485 => 253486)
--- trunk/Source/WebCore/platform/PlatformPasteboard.h 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/platform/PlatformPasteboard.h 2019-12-13 18:39:40 UTC (rev 253486)
@@ -66,7 +66,8 @@
WEBCORE_EXPORT Optional<Vector<PasteboardItemInfo>> allPasteboardItemInfo(int64_t changeCount);
WEBCORE_EXPORT static String uniqueName();
- WEBCORE_EXPORT static String platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType);
+ enum class IncludeImageTypes : bool { No, Yes };
+ static String platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType, IncludeImageTypes = IncludeImageTypes::No);
WEBCORE_EXPORT void getTypes(Vector<String>& types);
WEBCORE_EXPORT RefPtr<SharedBuffer> bufferForType(const String& pasteboardType);
Modified: trunk/Source/WebCore/platform/cocoa/PasteboardCocoa.mm (253485 => 253486)
--- trunk/Source/WebCore/platform/cocoa/PasteboardCocoa.mm 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/platform/cocoa/PasteboardCocoa.mm 2019-12-13 18:39:40 UTC (rev 253486)
@@ -68,7 +68,7 @@
if (cocoaType == String(kUTTypeTIFF))
return ImageType::TIFF;
#if PLATFORM(MAC)
- if (cocoaType == "Apple PNG pasteboard type") // NSPNGPboardType
+ if (cocoaType == String(legacyPNGPasteboardType())) // NSPNGPboardType
return ImageType::PNG;
#endif
if (cocoaType == String(kUTTypePNG))
Modified: trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm (253485 => 253486)
--- trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm 2019-12-13 18:39:40 UTC (rev 253486)
@@ -322,7 +322,7 @@
return String();
}
-String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType)
+String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType, IncludeImageTypes includeImageTypes)
{
if (domType == "text/plain")
return kUTTypePlainText;
@@ -333,6 +333,9 @@
if (domType == "text/uri-list")
return kUTTypeURL;
+ if (includeImageTypes == IncludeImageTypes::Yes && domType == "image/png")
+ return kUTTypePNG;
+
return { };
}
@@ -603,7 +606,7 @@
return;
NSString *stringValue = value;
- auto cocoaType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type).createCFString();
+ auto cocoaType = PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type, PlatformPasteboard::IncludeImageTypes::Yes).createCFString();
if (UTTypeConformsTo(cocoaType.get(), kUTTypeURL))
[representationsToRegister addRepresentingObject:[NSURL URLWithString:stringValue]];
else if (UTTypeConformsTo(cocoaType.get(), kUTTypePlainText))
Modified: trunk/Source/WebCore/platform/mac/LegacyNSPasteboardTypes.h (253485 => 253486)
--- trunk/Source/WebCore/platform/mac/LegacyNSPasteboardTypes.h 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/platform/mac/LegacyNSPasteboardTypes.h 2019-12-13 18:39:40 UTC (rev 253486)
@@ -86,6 +86,11 @@
return NSFilesPromisePboardType;
}
+inline NSString *legacyPNGPasteboardType()
+{
+ return @"Apple PNG pasteboard type";
+}
+
} // namespace WebCore
ALLOW_DEPRECATED_DECLARATIONS_END
Modified: trunk/Source/WebCore/platform/mac/PlatformPasteboardMac.mm (253485 => 253486)
--- trunk/Source/WebCore/platform/mac/PlatformPasteboardMac.mm 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Source/WebCore/platform/mac/PlatformPasteboardMac.mm 2019-12-13 18:39:40 UTC (rev 253486)
@@ -226,7 +226,7 @@
{
NSMutableArray *types = [NSMutableArray array];
data.forEachType([&] (auto& type) {
- NSString *platformType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type);
+ NSString *platformType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type, IncludeImageTypes::Yes);
if (platformType.length)
[types addObject:platformType];
});
@@ -237,11 +237,19 @@
[m_pasteboard declareTypes:types owner:nil];
- data.forEachPlatformString([&] (auto& type, auto& data) {
- auto platformType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type);
- ASSERT(!platformType.isEmpty());
- if (!platformType.isEmpty())
- [m_pasteboard setString:data forType:platformType];
+ data.forEachPlatformStringOrBuffer([&] (auto& type, auto& stringOrBuffer) {
+ auto platformType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type, IncludeImageTypes::Yes);
+ if (platformType.isEmpty())
+ return;
+
+ if (WTF::holds_alternative<Ref<SharedBuffer>>(stringOrBuffer)) {
+ if (auto platformData = WTF::get<Ref<SharedBuffer>>(stringOrBuffer)->createNSData())
+ [m_pasteboard setData:platformData.get() forType:platformType];
+ } else if (WTF::holds_alternative<String>(stringOrBuffer)) {
+ auto string = WTF::get<String>(stringOrBuffer);
+ if (!!string)
+ [m_pasteboard setString:string forType:platformType];
+ }
});
if (shouldWriteCustomData) {
@@ -257,7 +265,7 @@
return [m_pasteboard.get() changeCount];
}
-String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType)
+String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType, IncludeImageTypes includeImageTypes)
{
if (domType == "text/plain")
return legacyStringPasteboardType();
@@ -268,6 +276,9 @@
if (domType == "text/uri-list")
return legacyURLPasteboardType();
+ if (includeImageTypes == IncludeImageTypes::Yes && domType == "image/png")
+ return legacyPNGPasteboardType();
+
return { };
}
@@ -474,14 +485,13 @@
if (!platformType)
return;
- if (WTF::holds_alternative<String>(stringOrBuffer)) {
- [item setString:WTF::get<String>(stringOrBuffer) forType:platformType];
- return;
- }
-
if (WTF::holds_alternative<Ref<SharedBuffer>>(stringOrBuffer)) {
if (auto platformData = WTF::get<Ref<SharedBuffer>>(stringOrBuffer)->createNSData())
[item setData:platformData.get() forType:platformType];
+ } else if (WTF::holds_alternative<String>(stringOrBuffer)) {
+ auto string = WTF::get<String>(stringOrBuffer);
+ if (!!string)
+ [item setString:string forType:platformType];
}
});
Modified: trunk/Tools/ChangeLog (253485 => 253486)
--- trunk/Tools/ChangeLog 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Tools/ChangeLog 2019-12-13 18:39:40 UTC (rev 253486)
@@ -1,3 +1,19 @@
+2019-12-13 Wenson Hsieh <[email protected]>
+
+ [Clipboard API] Sanitize HTML and image data written using clipboard.write
+ https://bugs.webkit.org/show_bug.cgi?id=205188
+ <rdar://problem/57612968>
+
+ Reviewed by Darin Adler.
+
+ Adds an API test to verify that the markup written to the platform pasteboard on macOS and iOS is sanitized, and
+ does not contain hidden content, such as script elements.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/ClipboardTests.mm:
+ (-[TestWKWebView writeString:toClipboardWithType:]):
+ (readMarkupFromPasteboard):
+ * TestWebKitAPI/Tests/WebKitCocoa/clipboard.html:
+
2019-12-13 Kate Cheney <[email protected]>
Create WebKit API calls for ITP Data
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ClipboardTests.mm (253485 => 253486)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ClipboardTests.mm 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ClipboardTests.mm 2019-12-13 18:39:40 UTC (rev 253486)
@@ -29,6 +29,7 @@
#import "TestWKWebView.h"
#import "UIKitSPI.h"
#import <CoreServices/CoreServices.h>
+#import <WebCore/LegacyNSPasteboardTypes.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/_WKExperimentalFeature.h>
@@ -50,6 +51,16 @@
TestWebKitAPI::Util::run(&done);
}
+- (void)writeString:(NSString *)string toClipboardWithType:(NSString *)type
+{
+ __block bool done = false;
+ [self performAfterReceivingMessage:@"wroteStringToClipboard" action:^{
+ done = true;
+ }];
+ [self evaluateJavaScript:[NSString stringWithFormat:@"writeStringToClipboard(`%@`, `%@`)", type, string] completionHandler:nil];
+ TestWebKitAPI::Util::run(&done);
+}
+
@end
static RetainPtr<TestWKWebView> createWebViewForClipboardTests()
@@ -118,6 +129,16 @@
#endif
}
+static NSString *readMarkupFromPasteboard()
+{
+#if PLATFORM(MAC)
+ NSData *rawData = [NSPasteboard.generalPasteboard dataForType:WebCore::legacyHTMLPasteboardType()];
+#elif PLATFORM(IOS)
+ NSData *rawData = [UIPasteboard.generalPasteboard dataForPasteboardType:(__bridge NSString *)kUTTypeHTML];
+#endif
+ return [[[NSString alloc] initWithData:rawData encoding:NSUTF8StringEncoding] autorelease];
+}
+
TEST(ClipboardTests, ReadMultipleItems)
{
auto webView = createWebViewForClipboardTests();
@@ -133,3 +154,14 @@
EXPECT_WK_STREQ("https://webkit.org/", [webView objectByEvaluatingJavaScript:@"clipboardData[3]['text/uri-list']"]);
EXPECT_WK_STREQ("https://webkit.org/", [webView stringByEvaluatingJavaScript:@"clipboardData[3]['text/html'].querySelector('a').href"]);
}
+
+TEST(ClipboardTests, WriteSanitizedMarkup)
+{
+ auto webView = createWebViewForClipboardTests();
+ [webView writeString:@"<script>/* super secret */</script>This is a test." toClipboardWithType:@"text/html"];
+
+ NSString *writtenMarkup = readMarkupFromPasteboard();
+ EXPECT_TRUE([writtenMarkup containsString:@"This is a test."]);
+ EXPECT_FALSE([writtenMarkup containsString:@"super secret"]);
+ EXPECT_FALSE([writtenMarkup containsString:@"<script>"]);
+}
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/clipboard.html (253485 => 253486)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/clipboard.html 2019-12-13 18:35:06 UTC (rev 253485)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/clipboard.html 2019-12-13 18:39:40 UTC (rev 253486)
@@ -28,6 +28,18 @@
clipboardData = [];
exception = null;
+async function writeStringToClipboard(type, string) {
+ try {
+ const itemData = {};
+ itemData[type] = string;
+ await navigator.clipboard.write([new ClipboardItem(itemData)]);
+ } catch (e) {
+ exception = e;
+ } finally {
+ webkit.messageHandlers.testHandler.postMessage("wroteStringToClipboard");
+ }
+}
+
async function readClipboard() {
try {
const items = await navigator.clipboard.read();