Cannot access images included in the content pasted from Microsoft Word
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Oct 2017 21:44:28 +0000 (21:44 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Oct 2017 21:44:28 +0000 (21:44 +0000)
https://bugs.webkit.org/show_bug.cgi?id=124391
<rdar://problem/26862741>

Reviewed by Antti Koivisto.

Source/WebCore:

The bug is caused by the fact Microsoft Word generates HTML content which references an image using file URL.
Because the websites don't have access to arbtirary file URLs, this prevents editors such as TinyMCE to save
those images.

This patch fixes the problem by converting file URLs for images and all other subresources in the web archive
generated by Microsoft Word by blob URLs like r222839 for RTF/RTFD and r222119 for images.

To avoid revealing privacy sensitive information such as the absolute local file path to the user's home directory
Microsoft Word and other applications in the system includes in the web archive placed in the system pasteboard,
this patch also introduces the mechanism to sanitize when the HTML content is read by DataTransfer's getData.

This patch also introduces the sanitization for when writing HTML into the pasteboard since other applications
in the syste which is capable to processing web archives are not necessarily equipped to pretect itself and the
rest of the system from potentially dangerous JavaScript included in the web archive placed in the system pasteboard.

Finally, this patch expands the list of clipboard types that are exposed as "text/html" to the Web platform by
adding the capability to convert RTF, RTFD, and web archive into HTML markup by introducing WebContentMarkupReader,
a new subclass of PasteboardWebContentReader which creates a HTML markup instead of a document fragment. Most of
the sanitization process happens in this new class, and will be expanded to WebContentReader to make pasting safer.

Tests: editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html
       editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html
       editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html
       editing/pasteboard/data-transfer-set-data-sanitlize-html-when-dragging-in-null-origin.html
       http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html
       CopyHTML.Sanitizes
       DataInteractionTests.DataTransferSanitizeHTML
       PasteRTF.ExposesHTMLTypeInDataTransfer
       PasteRTFD.ExposesHTMLTypeInDataTransfer
       PasteRTFD.ImageElementUsesBlobURLInHTML
       PasteWebArchive.ExposesHTMLTypeInDataTransfer

* dom/DataTransfer.cpp:
(WebCore::originIdentifierForDocument): Moved to Document::originIdentifierForPasteboard.
(WebCore::DataTransfer::createForCopyAndPaste):
(WebCore::DataTransfer::getDataForItem const): Use WebContentMarkupReader read HTMl content so that we can read
web arhive, RTF, and RTFD as text/html.
(WebCore::DataTransfer::getData const):
(WebCore::DataTransfer::setData):
(WebCore::DataTransfer::setDataFromItemList): Sanitize the HTML before placing into the system pasteboard.
(WebCore::DataTransfer::createForDragStartEvent):
(WebCore::DataTransfer::createForDrop):
(WebCore::DataTransfer::createForUpdatingDropTarget):
* dom/DataTransfer.h:
* dom/DataTransfer.idl:
* dom/DataTransferItem.cpp:
(WebCore::DataTransferItem::getAsString const):
* dom/Document.cpp:
(WebCore::Document::originIdentifierForPasteboard): Renamed from uniqueIdentifier. Moved the code to use the origin
string and then falling back to the UUID here from originIdentifierForDocument in DataTransfer.cpp.
* dom/Document.h:
* editing/WebContentReader.cpp:
(WebCore::WebContentMarkupReader::shouldSanitize const): Added.
* editing/WebContentReader.h:
(WebCore::WebContentMarkupReader): Added.
(WebCore::WebContentMarkupReader::WebContentMarkupReader):
* editing/cocoa/WebContentReaderCocoa.mm:
(WebCore::createFragmentFromWebArchive): Extracted out of WebContentReader::readWebArchive to share code.
(WebCore::WebContentReader::readWebArchive):
(WebCore::WebContentMarkupReader::readWebArchive): Added. Reads the web archive, replace all subresource URLs by
blob URLs, and re-generate the markup using our copy & paste code. The last step is requied to strip away any privacy
sensitive information as well as potentially dangerous JavaScript code.
(WebCore::stripMicrosoftPrefix): Extracted out of WebContentReader::readHTML to share code.
(WebCore::WebContentReader::readHTML):
(WebCore::WebContentMarkupReader::readHTML): Added. Only sanitize the markup when it comes from a different origin.
(WebCore::WebContentReader::readRTFD): Added a nullity check for frame.document().
(WebCore::WebContentMarkupReader::readRTFD): Added.
(WebCore::WebContentMarkupReader::readRTF): Added.
* editing/markup.h:
* editing/markup.cpp:
(WebCore::createPageForSanitizingWebContent): Added.
(WebCore::sanitizeMarkup): Added. This function "pastes" the markup into a new isolated document then reserializes
using our serialization code for copy. It strips away all invisible information such as comments, and strips away
event handlers and script elements to remove potentially dangerous scripts.
* platform/Pasteboard.h:
* platform/ios/PasteboardIOS.mm:
(WebCore::Pasteboard::readPasteboardWebContentDataForType): Now that this code can be called by DataTransfer, added
the checks for the change count to make sure we stop letting web content read if the pasteboard had been changed by
some other applications. To do this, turned this function into a member of Pasteboard. Also changed the return type
to an enum with tri-state to exist the loop early in the call sites.
(WebCore::Pasteboard::read):
(WebCore::Pasteboard::readRespectingUTIFidelities):
* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::safeTypeForDOMToReadAndWriteForPlatformType): Treat RTF, RTFD, and web archive as HTML.
* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::read): Add the change count checks now that this code can be called by DataTransfer.
* platform/mac/PlatformPasteboardMac.mm:
(WebCore::safeTypeForDOMToReadAndWriteForPlatformType): Treat RTF, RTFD, and web archive as HTML.

Tools:

Added tests for sanitizing HTML contents for copy & paste and drag & drop.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/CopyHTML.mm: Added.
(readHTMLFromPasteboard): Added.
(createWebViewWithCustomPasteboardDataEnabled): Added.
(CopyHTML.Sanitizes): Added.

* TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm:
(createWebViewWithCustomPasteboardDataEnabled): Added to enable more tests on bots.

* TestWebKitAPI/Tests/WebKitCocoa/PasteRTFD.mm:
(writeRTFToPasteboard): Added.
(createWebViewWithCustomPasteboardDataEnabled): Added.
(createHelloWorldString): Added.
(PasteRTF.ExposesHTMLTypeInDataTransfer): Added.
(PasteRTFD.ExposesHTMLTypeInDataTransfer): Added.
(PasteRTFD.ImageElementUsesBlobURLInHTML): Added.

* TestWebKitAPI/Tests/WebKitCocoa/copy-html.html: Added.
* TestWebKitAPI/Tests/WebKitCocoa/paste-rtfd.html: Store the clipboardData contents for
PasteRTF.ExposesHTMLTypeInDataTransfer and PasteRTFD.ExposesHTMLTypeInDataTransfer.

* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(DataInteractionTests.DataTransferSanitizeHTML):

LayoutTests:

Added tests for copying & pasting and dragging & dropping HTML contents.

* TestExpectations:
* editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt: Rebaselined.
* editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Ditto.
* editing/pasteboard/data-transfer-get-data-on-paste-rich-text.html: Modified the test to strip away platform specific
inline style properties.
* editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url-expected.txt: Added.
* editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-dragging-in-null-origin-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-html-when-dragging-in-null-origin.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitizes-url-when-dragging-in-null-origin.html: Removed the superflous
call to setTimeout that was errornously added during debugging. Also updated the test to not claim all URL and
HTML values are read in the same origin, and updated the assertion for cross-origin case as it's now sanitized.
* editing/pasteboard/onpaste-text-html-expected.txt: Rebaselined. The order of CSS properties have changed.
* http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html-expected.txt: Added.
* http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html: Added.
* http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html:
* http/tests/security/clipboard/resources/copy-html.html: Added.
* http/tests/security/clipboard/resources/copy-url.html: Renamed from copy.html.
* platform/ios-wk2/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Remoevd.
* platform/ios-wk1/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Remoevd.
* platform/mac-wk1/TestExpectations:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@223440 268f45cc-cd09-0410-ab3c-d52691b4dbfc

54 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt
LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt
LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-rich-text.html
LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin-expected.txt
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin.html
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/onpaste-text-html-expected.txt
LayoutTests/fast/events/ondrop-text-html-expected.txt
LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html [new file with mode: 0644]
LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html
LayoutTests/http/tests/security/clipboard/resources/copy-html.html [new file with mode: 0644]
LayoutTests/http/tests/security/clipboard/resources/copy-url.html [moved from LayoutTests/http/tests/security/clipboard/resources/copy.html with 100% similarity]
LayoutTests/platform/ios-wk1/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt [deleted file]
LayoutTests/platform/ios-wk2/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt [deleted file]
LayoutTests/platform/mac-wk1/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/dom/DataTransfer.cpp
Source/WebCore/dom/DataTransfer.h
Source/WebCore/dom/DataTransfer.idl
Source/WebCore/dom/DataTransferItem.cpp
Source/WebCore/dom/DataTransferItem.h
Source/WebCore/dom/DataTransferItem.idl
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/editing/WebContentReader.cpp
Source/WebCore/editing/WebContentReader.h
Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm
Source/WebCore/editing/markup.cpp
Source/WebCore/editing/markup.h
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/ios/PasteboardIOS.mm
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/mac/PasteboardMac.mm
Source/WebCore/platform/mac/PlatformPasteboardMac.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyHTML.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteRTFD.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-html.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/paste-rtfd.html
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

index f97e8f9..d4787cf 100644 (file)
@@ -1,3 +1,39 @@
+2017-10-15  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Cannot access images included in the content pasted from Microsoft Word
+        https://bugs.webkit.org/show_bug.cgi?id=124391
+        <rdar://problem/26862741>
+
+        Reviewed by Antti Koivisto.
+
+        Added tests for copying & pasting and dragging & dropping HTML contents.
+
+        * TestExpectations:
+        * editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt: Rebaselined.
+        * editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Ditto.
+        * editing/pasteboard/data-transfer-get-data-on-paste-rich-text.html: Modified the test to strip away platform specific
+        inline style properties.
+        * editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url-expected.txt: Added.
+        * editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-dragging-in-null-origin-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-html-when-dragging-in-null-origin.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitizes-url-when-dragging-in-null-origin.html: Removed the superflous
+        call to setTimeout that was errornously added during debugging. Also updated the test to not claim all URL and
+        HTML values are read in the same origin, and updated the assertion for cross-origin case as it's now sanitized.
+        * editing/pasteboard/onpaste-text-html-expected.txt: Rebaselined. The order of CSS properties have changed.
+        * http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html-expected.txt: Added.
+        * http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html: Added.
+        * http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html:
+        * http/tests/security/clipboard/resources/copy-html.html: Added.
+        * http/tests/security/clipboard/resources/copy-url.html: Renamed from copy.html.
+        * platform/ios-wk2/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Remoevd.
+        * platform/ios-wk1/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Remoevd.
+        * platform/mac-wk1/TestExpectations:
+
 2017-10-16  Ross Kirsling  <ross.kirsling@sony.com>
 
         Web Inspector: Layers tab mistakenly throws out the root element's layer.
index 81f5228..1930a37 100644 (file)
@@ -74,7 +74,9 @@ editing/pasteboard/data-transfer-get-data-on-drop-plain-text.html [ Skip ]
 editing/pasteboard/data-transfer-get-data-on-drop-rich-text.html [ Skip ]
 editing/pasteboard/data-transfer-get-data-on-drop-url.html [ Skip ]
 editing/pasteboard/data-transfer-is-unique-for-dragenter-and-dragleave.html [ Skip ]
+editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html [ Skip ]
 editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin.html [ Skip ]
+
 editing/pasteboard/drag-end-crash-accessing-item-list.html [ Skip ]
 editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Skip ]
 editing/pasteboard/data-transfer-items-drop-file.html [ Skip ]
index cec5b62..c9bf475 100644 (file)
@@ -5,7 +5,7 @@ Rich text
         "text/plain": ""
     },
     "drop": {
-        "text/html": "<strong style=\"font-family: -apple-system; font-size: 150px; font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: nowrap; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; color: purple;\">Rich text</strong>",
+        "text/html": "<strong style=\"font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-family: -apple-system; font-size: 150px; white-space: nowrap; color: purple;\">Rich text</strong>",
         "text/plain": "Rich text"
     }
 }
index d4b004f..13d2c86 100644 (file)
@@ -1,7 +1,7 @@
 Rich text
 {
     "paste": {
-        "text/html": "<span style=\"caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -apple-system; font-size: 150px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: nowrap; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; display: inline !important; float: none;\">Rich text</span>",
+        "text/html": "<span style=\"...\"\">Rich text</span>",
         "text/plain": "Rich text"
     }
 }
index 69bb7c0..d63f2b9 100644 (file)
@@ -35,7 +35,7 @@ result = {};
 function updateResultWithEvent(event) {
     const eventData = {};
     for (const type of event.clipboardData.types)
-        eventData[type] = event.clipboardData.getData(type);
+        eventData[type] = event.clipboardData.getData(type).replace(/style="[^"]+"/g, 'style="...""');
     result[event.type] = eventData;
     output.textContent = JSON.stringify(result, null, "    ");
     event.preventDefault();
diff --git a/LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url-expected.txt
new file mode 100644 (file)
index 0000000..3a64585
--- /dev/null
@@ -0,0 +1,25 @@
+This tests getData strips away secrets and dangerous code when copying inside a null origin document.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS typesInNullOrigin.includes("text/html") is true
+PASS htmlInNullOrigin.includes("secret") is false
+PASS htmlInNullOrigin.includes("dangerousCode") is false
+PASS parsedTree = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html"); !!parsedTree.querySelector("b"); is true
+PASS parsedTree.querySelector("b").textContent is "16th President of the United States:"
+PASS (new URL(parsedTree.querySelector("img").src)).protocol is "blob:"
+PASS parsedTree.querySelector("img").src.includes("resources/abe.png") is false
+PASS itemsInNullOrigin.some((item) => item.kind == "string" && item.type == "text/html") is true
+PASS typesInNullOrigin.includes("text/html") is true
+PASS htmlInSameDocument.includes("secret") is false
+PASS htmlInSameDocument.includes("dangerousCode") is false
+PASS parsedTree = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html"); !!parsedTree.querySelector("b"); is true
+PASS parsedTree.querySelector("b").textContent is "16th President of the United States:"
+FAIL (new URL(parsedTree.querySelector("img").src)).protocol should be file:. Was blob:.
+FAIL parsedTree.querySelector("img").src.includes("resources/abe.png") should be true. Was false.
+PASS itemsInSameDocument.some((item) => item.kind == "string" && item.type == "text/html") is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html b/LayoutTests/editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html
new file mode 100644 (file)
index 0000000..146f90d
--- /dev/null
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<div id="container">
+<button id="copy" onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="doCopy(event)" contenteditable>
+    <b>16th President of the United States:</b>
+    <!-- secret-->
+    <img src="../resources/abe.png" alt="Abraham Lincoln" onmouseover="dangerousCode()">
+    <script>function dangerousCode() { } dangerousCode();</script>
+</div>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div></div>
+<script>
+
+description('This tests getData strips away secrets and dangerous code when copying inside a null origin document.');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+}
+
+const container = document.getElementById('container');
+function doCopy(event) {
+    const iframe = document.createElement('iframe');
+    container.insertBefore(iframe, container.lastChild);
+    iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+    <div id="destination" onpaste="doPaste(event)" contenteditable>2. Paste here</div>
+    <script>
+
+    function doPaste(event) {
+        event.preventDefault();
+        parent.postMessage({
+            html: event.clipboardData.getData('text/html'),
+            types: event.clipboardData.types,
+            items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+        }, '*');
+    };
+
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+
+    </scri` + 'pt>';
+}
+
+onmessage = (event) => {
+    typesInNullOrigin = event.data.types;
+    shouldBeTrue('typesInNullOrigin.includes("text/html")');
+
+    htmlInNullOrigin = event.data.html;
+    shouldBeFalse('htmlInNullOrigin.includes("secret")');
+    shouldBeFalse('htmlInNullOrigin.includes("dangerousCode")');
+    shouldBeTrue('parsedTree = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html"); !!parsedTree.querySelector("b");');
+    shouldBeEqualToString('parsedTree.querySelector("b").textContent', '16th President of the United States:');
+    shouldBeEqualToString('(new URL(parsedTree.querySelector("img").src)).protocol', 'blob:');
+    shouldBeFalse('parsedTree.querySelector("img").src.includes("resources/abe.png")');
+
+    itemsInNullOrigin = event.data.items;
+    shouldBeTrue('itemsInNullOrigin.some((item) => item.kind == "string" && item.type == "text/html")');
+
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    event.preventDefault();
+
+    typesInSameDocument = event.clipboardData.types;
+    shouldBeTrue('typesInNullOrigin.includes("text/html")');
+
+    htmlInSameDocument = event.clipboardData.getData('text/html');
+    shouldBeFalse('htmlInSameDocument.includes("secret")');
+    shouldBeFalse('htmlInSameDocument.includes("dangerousCode")');
+    shouldBeTrue('parsedTree = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html"); !!parsedTree.querySelector("b");');
+    shouldBeEqualToString('parsedTree.querySelector("b").textContent', '16th President of the United States:');
+
+    // FIXME: These two test cases fail.
+    shouldBeEqualToString('(new URL(parsedTree.querySelector("img").src)).protocol', 'file:');
+    shouldBeTrue('parsedTree.querySelector("img").src.includes("resources/abe.png")');
+
+    itemsInSameDocument = Array.from(event.clipboardData.items);
+    shouldBeTrue('itemsInSameDocument.some((item) => item.kind == "string" && item.type == "text/html")');
+
+    container.remove();
+    finishJSTest();
+}
+
+if (window.testRunner)
+    window.onload = runTest;
+
+successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin-expected.txt
new file mode 100644 (file)
index 0000000..dd773fe
--- /dev/null
@@ -0,0 +1,41 @@
+This tests calling setData to set a html in a null origin document. The URL should be sanitized when reading in another document.
+To manually test, drag and drop the "1. Drag this" above to "2. Drop here" and "3. Drop here".
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+dragstart in the null origin document:
+PASS html is "<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>"
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+
+dragover in the null origin document:
+PASS html is ""
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+
+drop in the null origin document:
+PASS html is "<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>"
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+
+dragstart in the null origin document:
+PASS html is "<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>"
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+
+dragover in the file URL document:
+PASS html is ""
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+
+drop in the file URL document:
+PASS html.includes("hello") is true
+PASS html.includes(", world") is true
+PASS html.includes("secret") is false
+PASS html.includes("dangerousCode") is false
+PASS types.includes("text/html") is true
+PASS items.some((item) => item.kind == "string" && item.type == "text/html") is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html
new file mode 100644 (file)
index 0000000..9c26e74
--- /dev/null
@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container">
+<div id="destination">3. Drop here</div>
+</div>
+<div id="description"></div>
+<div id="console"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+description('This tests calling setData to set a html in a null origin document. The URL should be sanitized when reading in another document.<br>'
+    + 'To manually test, drag and drop the "1. Drag this" above to "2. Drop here" and "3. Drop here".');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+const iframe = document.createElement('iframe');
+const destination = document.getElementById('destination');
+document.getElementById('container').prepend(iframe);
+
+iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+<div id="source" draggable="true">1. Drag this</div>
+<div id="destination" onpaste="paste(event)">2. Drop here</div>
+<script>
+
+const source = document.getElementById('source');
+const destination = document.getElementById('destination');
+const originalHTML = '<meta content="secret"><b onmouseover="dangerousCode()">hello</b><!-- secret-->, world<script>dangerousCode()</scr' + 'ipt>';
+parent.postMessage({kind: 'originalHTML', originalHTML}, '*');
+
+function postContent(kind, dataTransfer) {
+    parent.postMessage({
+        kind,
+        documentLabel: 'the null origin document',
+        html: dataTransfer.getData('text/html'),
+        types: dataTransfer.types,
+        items: Array.from(dataTransfer.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+}
+
+let postDragOver;
+source.addEventListener("dragstart", (event) => {
+    postDragOver = false;
+    event.dataTransfer.setData('text/html', originalHTML);
+    postContent('dragstart', event.dataTransfer);
+});
+destination.addEventListener("dragover", (event) => {
+    event.preventDefault();
+    if (postDragOver)
+        return;
+    postDragOver = true;
+    postContent('dragover', event.dataTransfer);
+});
+destination.addEventListener("drop", (event) => {
+    postContent('drop', event.dataTransfer);
+    destination.remove();
+    top.postMessage({kind: 'secondDrop'}, '*');
+});
+
+if (window.eventSender) {
+    const iframeOffset = {x: 10, y: 10};
+    eventSender.mouseMoveTo(iframeOffset.x + source.offsetLeft + 5, iframeOffset.y + source.offsetTop + 5);
+    eventSender.mouseDown();
+    eventSender.leapForward(500);
+    eventSender.mouseMoveTo(iframeOffset.x + destination.offsetLeft + 5, iframeOffset.y + destination.offsetTop + 5);
+    eventSender.mouseUp();
+}
+
+</scri` + 'pt>';
+
+onmessage = (event) => {
+    const kind = event.data.kind;
+    if (kind == 'originalHTML') {
+        originalHTML = event.data.originalHTML;
+        return;
+    }
+    if (kind == 'secondDrop')
+        return doSecondDrop(event.data.postContent);
+    debug(`${kind} in ${event.data.documentLabel}:`);
+    switch (kind) {
+    case 'dragstart':
+        html = event.data.html;
+        shouldBeEqualToString('html', originalHTML);
+        types = event.data.types;
+        shouldBeTrue('types.includes("text/html")');
+        items = event.data.items;
+        shouldBeTrue('items.some((item) => item.kind == "string" && item.type == "text/html")');
+        break;
+    case 'dragover':
+        html = event.data.html;
+        shouldBeEqualToString('html', '');
+        types = event.data.types;
+        shouldBeTrue('types.includes("text/html")');
+        items = event.data.items;
+        shouldBeTrue('items.some((item) => item.kind == "string" && item.type == "text/html")');
+        break;
+    case 'drop':
+        html = event.data.html;
+        if (event.data.documentLabel.includes('null'))
+            shouldBeEqualToString('html', originalHTML);
+        else {
+            shouldBeTrue('html.includes("hello")');
+            shouldBeTrue('html.includes(", world")');
+            shouldBeFalse('html.includes("secret")');
+            shouldBeFalse('html.includes("dangerousCode")');
+        }
+        types = event.data.types;
+        shouldBeTrue('types.includes("text/html")');
+        items = event.data.items;
+        shouldBeTrue('items.some((item) => item.kind == "string" && item.type == "text/html")');
+        if (!event.data.documentLabel.includes('null')) {
+            document.getElementById('container').remove();
+            finishJSTest();
+        }
+        break;
+    }
+    debug('');
+}
+
+function postContent(kind, dataTransfer) {
+    window.postMessage({
+        kind,
+        documentLabel: 'the file URL document',
+        html: dataTransfer.getData('text/html'),
+        types: dataTransfer.types,
+        items: Array.from(dataTransfer.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+}
+
+let postDragOver = false;
+destination.addEventListener("dragover", (event) => {
+    event.preventDefault();
+    if (postDragOver)
+        return;
+    postDragOver = true;
+    postContent('dragover', event.dataTransfer);
+});
+destination.addEventListener("drop", (event) => {
+    postContent('drop', event.dataTransfer);
+});
+
+function doSecondDrop(postContent) {
+    postDragOver = false;
+    if (!window.eventSender)
+        return;
+
+    eventSender.leapForward(500);
+    eventSender.mouseMoveTo(iframe.offsetLeft + 10, iframe.offsetTop + 10);
+    eventSender.mouseDown();
+    eventSender.leapForward(500);
+
+    const destinationRect = destination.getBoundingClientRect();
+    eventSender.mouseMoveTo(destination.offsetLeft + 5, destination.offsetTop + 5);
+    eventSender.mouseUp();
+}
+
+setTimeout(finishJSTest, 3000);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 5d3e118..6920f75 100644 (file)
@@ -4,40 +4,40 @@ To manually test, drag and drop the "1. Drag this" above to "2. Drop here" and "
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 dragstart in the null origin document:
-PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
-PASS htmlReadInSameDocument is "testing"
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is "http://webkit.org/b/🤔?x=8 + 6"
+PASS html is "testing"
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 
 dragover in the null origin document:
-PASS urlReadInSameDocument is ""
-PASS htmlReadInSameDocument is ""
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is ""
+PASS html is ""
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 
 drop in the null origin document:
-PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
-PASS htmlReadInSameDocument is "testing"
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is "http://webkit.org/b/🤔?x=8 + 6"
+PASS html is "testing"
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 
 dragstart in the null origin document:
-PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
-PASS htmlReadInSameDocument is "testing"
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is "http://webkit.org/b/🤔?x=8 + 6"
+PASS html is "testing"
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 
 dragover in the file URL document:
-PASS urlReadInSameDocument is ""
-PASS htmlReadInSameDocument is ""
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is ""
+PASS html is ""
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 
 drop in the file URL document:
-PASS urlReadInSameDocument is "http://webkit.org/b/%F0%9F%A4%94?x=8%20+%206"
-PASS htmlReadInSameDocument is "testing"
-PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\",\"text/html\"]"
-PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS url is "http://webkit.org/b/%F0%9F%A4%94?x=8%20+%206"
+PASS html.includes("testing") is true
+PASS JSON.stringify(types) is "[\"text/uri-list\",\"text/html\"]"
+PASS JSON.stringify(items) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"},{\"kind\":\"string\",\"type\":\"text/html\"}]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 700c95d..aa1ccdc 100644 (file)
@@ -83,34 +83,38 @@ onmessage = (event) => {
     debug(`${kind} in ${event.data.documentLabel}:`);
     switch (kind) {
     case 'dragstart':
-        urlReadInSameDocument = event.data.url;
-        shouldBeEqualToString('urlReadInSameDocument', originalURL);
-        htmlReadInSameDocument = event.data.html;
-        shouldBeEqualToString('htmlReadInSameDocument', "testing");
-        typesInSameDocument = event.data.types;
-        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list","text/html"]');
-        itemsInSameDocument = event.data.items;
-        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
+        url = event.data.url;
+        shouldBeEqualToString('url', originalURL);
+        html = event.data.html;
+        shouldBeEqualToString('html', "testing");
+        types = event.data.types;
+        shouldBeEqualToString('JSON.stringify(types)', '["text/uri-list","text/html"]');
+        items = event.data.items;
+        shouldBeEqualToString('JSON.stringify(items)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
         break;
     case 'dragover':
-        urlReadInSameDocument = event.data.url;
-        shouldBeEqualToString('urlReadInSameDocument', '');
-        htmlReadInSameDocument = event.data.html;
-        shouldBeEqualToString('htmlReadInSameDocument', '');
-        typesInSameDocument = event.data.types;
-        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list","text/html"]');
-        itemsInSameDocument = event.data.items;
-        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
+        url = event.data.url;
+        shouldBeEqualToString('url', '');
+        html = event.data.html;
+        shouldBeEqualToString('html', '');
+        types = event.data.types;
+        shouldBeEqualToString('JSON.stringify(types)', '["text/uri-list","text/html"]');
+        items = event.data.items;
+        shouldBeEqualToString('JSON.stringify(items)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
         break;
     case 'drop':
-        urlReadInSameDocument = event.data.url;
-        shouldBeEqualToString('urlReadInSameDocument', event.data.documentLabel.includes('null') ? originalURL : (new URL(originalURL)).href);
-        htmlReadInSameDocument = event.data.html;
-        shouldBeEqualToString('htmlReadInSameDocument', "testing");
-        typesInSameDocument = event.data.types;
-        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list","text/html"]');
-        itemsInSameDocument = event.data.items;
-        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
+        url = event.data.url;
+        isSameOrigin = event.data.documentLabel.includes('null');
+        shouldBeEqualToString('url', isSameOrigin ? originalURL : (new URL(originalURL)).href);
+        html = event.data.html;
+        if (isSameOrigin)
+            shouldBeEqualToString('html', 'testing');
+        else
+            shouldBeTrue('html.includes("testing")');
+        types = event.data.types;
+        shouldBeEqualToString('JSON.stringify(types)', '["text/uri-list","text/html"]');
+        items = event.data.items;
+        shouldBeEqualToString('JSON.stringify(items)', '[{"kind":"string","type":"text/uri-list"},{"kind":"string","type":"text/html"}]');
         if (!event.data.documentLabel.includes('null')) {
             document.getElementById('container').remove();
             finishJSTest();
@@ -158,8 +162,6 @@ function doSecondDrop(postContent) {
     eventSender.mouseUp();
 }
 
-setTimeout(finishJSTest, 3000);
-
 </script>
 <script src="../../resources/js-test-post.js"></script>
 </body>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-expected.txt
new file mode 100644 (file)
index 0000000..fde94b0
--- /dev/null
@@ -0,0 +1,16 @@
+This tests getData strips away secrets and dangerous code when copying HTML in a regular origin and pasting inside a null origin document.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS JSON.stringify(typesInSameDocument) is "[\"text/html\"]"
+PASS htmlInSameDocument is "<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS htmlInAnotherDocument.includes("secret") is false
+PASS htmlInAnotherDocument.includes("dangerousCode") is false
+PASS b = (new DOMParser).parseFromString(htmlInAnotherDocument, "text/html").querySelector("b"); b.textContent is "hello"
+PASS b.parentNode.textContent is "hello, world"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin-expected.txt
new file mode 100644 (file)
index 0000000..f540bef
--- /dev/null
@@ -0,0 +1,18 @@
+This tests getData strips away secrets and dangerous code when copying inside a null origin document.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS JSON.stringify(typesInNullOrigin) is "[\"text/html\"]"
+PASS htmlInNullOrigin.includes("secret") is false
+PASS htmlInNullOrigin.includes("dangerousCode") is false
+PASS b = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html").querySelector("b"); b.textContent is "hello"
+PASS b.parentNode.textContent is "hello, world"
+PASS JSON.stringify(itemsInNullOrigin) is "[{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS event.clipboardData.getData("text/html") is "<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>"
+PASS JSON.stringify(event.clipboardData.types) is "[\"text/html\"]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html
new file mode 100644 (file)
index 0000000..94ab014
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<div id="container">
+<button id="copy" onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="doCopy(event)" contenteditable>some text</div>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+</div>
+<script>
+
+description('This tests getData strips away secrets and dangerous code when copying inside a null origin document.');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+}
+
+var originalHTML = '<meta content="secret"><b onmouseover="dangerousCode()">hello</b><!-- secret-->, world<script>dangerousCode()</scr' + 'ipt>';
+function doCopy(event) {
+    event.clipboardData.setData('text/html', originalHTML);
+    event.preventDefault();
+
+    const iframe = document.createElement('iframe');
+    document.getElementById('container').insertBefore(iframe, iframe.lastChild);
+    iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+    <div id="destination" onpaste="doPaste(event)" contenteditable>2. Paste here</div>
+    <script>
+
+    function doPaste(event) {
+        event.preventDefault();
+        parent.postMessage({
+            html: event.clipboardData.getData('text/html'),
+            types: event.clipboardData.types,
+            items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+        }, '*');
+    };
+
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+
+    </scri` + 'pt>';
+}
+
+onmessage = (event) => {
+    typesInNullOrigin = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInNullOrigin)', '["text/html"]');
+
+    htmlInNullOrigin = event.data.html;
+    shouldBeFalse('htmlInNullOrigin.includes("secret")');
+    shouldBeFalse('htmlInNullOrigin.includes("dangerousCode")');
+    shouldBeEqualToString('b = (new DOMParser).parseFromString(htmlInNullOrigin, "text/html").querySelector("b"); b.textContent', 'hello');
+    shouldBeEqualToString('b.parentNode.textContent', 'hello, world');
+
+    itemsInNullOrigin = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInNullOrigin)', '[{"kind":"string","type":"text/html"}]');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    shouldBeEqualToString('event.clipboardData.getData("text/html")', originalHTML);
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["text/html"]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))',
+        '[{"kind":"string","type":"text/html"}]');
+    document.getElementById('container').remove();
+    finishJSTest();
+}
+
+if (window.testRunner)
+    window.onload = runTest;
+
+successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html
new file mode 100644 (file)
index 0000000..570bbeb
--- /dev/null
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<div id="container">
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+</div>
+<script>
+
+description('This tests getData strips away secrets and dangerous code when copying HTML in a regular origin and pasting inside a null origin document.');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+const iframe = document.createElement('iframe');
+document.getElementById('container').prepend(iframe);
+iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+<button id="copy" onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="doCopy(event)" contenteditable>some text</div>
+<div id="destination" onpaste="doPaste(event)" contenteditable>2. Paste here</div>
+<script>
+
+const originalHTML = '<meta content="secret"><b onmouseover="dangerousCode()">hello</b><!-- secret-->, world<script>dangerousCode()</scr' + 'ipt>';
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+}
+
+function doCopy(event) {
+    event.clipboardData.setData('text/html', originalHTML);
+    event.preventDefault();
+    setTimeout(() => {
+        document.getElementById('destination').focus();
+        if (window.testRunner)
+            document.execCommand('paste');
+    }, 0);
+};
+
+function doPaste(event) {
+    event.preventDefault();
+    parent.postMessage({
+        originalHTML,
+        html: event.clipboardData.getData('text/html'),
+        types: event.clipboardData.types,
+        items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+};
+
+document.getElementById('destination').focus();
+if (window.testRunner)
+    runTest();
+    document.execCommand('paste');
+
+</scri` + 'pt>';
+
+function doCopy(event) {
+    event.clipboardData.setData('text/html', originalHTML);
+    event.preventDefault();
+}
+
+onmessage = (event) => {
+    typesInSameDocument = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/html"]');
+    htmlInSameDocument = event.data.html;
+    originalHTML = event.data.originalHTML;
+    shouldBeEqualToString('htmlInSameDocument', originalHTML);
+
+    itemsInSameDocument = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/html"}]');
+
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    htmlInAnotherDocument = event.clipboardData.getData("text/html");
+    shouldBeFalse('htmlInAnotherDocument.includes("secret")');
+    shouldBeFalse('htmlInAnotherDocument.includes("dangerousCode")');
+    shouldBeEqualToString('b = (new DOMParser).parseFromString(htmlInAnotherDocument, "text/html").querySelector("b"); b.textContent', 'hello');
+    shouldBeEqualToString('b.parentNode.textContent', 'hello, world');
+    document.getElementById('container').remove();
+    finishJSTest();
+}
+
+successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 8c45c64..2801f4c 100644 (file)
@@ -1,5 +1,5 @@
 CONSOLE MESSAGE: line 21: text/plain: This test verifies that we can get text/html from the clipboard during an onpaste event. 
-CONSOLE MESSAGE: line 23: text/html: <span style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; display: inline !important; float: none;">This test verifies that we can get text/html from the clipboard during an onpaste event.<span class="Apple-converted-space"> </span></span>
+CONSOLE MESSAGE: line 23: text/html: <span style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium; float: none; display: inline !important;">This test verifies that we can get text/html from the clipboard during an onpaste event.<span class="Apple-converted-space"> </span></span>
 This test verifies that we can get text/html from the clipboard during an onpaste event. This test requires DRT.
 Paste content in this div.This test verifies that we can get text/html from the clipboard during an onpaste event. 
 PASS
index 47616bc..b3eb51b 100644 (file)
@@ -1,4 +1,4 @@
 CONSOLE MESSAGE: line 21: text/plain: This test verifies that we can get text/html from the drag object during an ondrop event. 
-CONSOLE MESSAGE: line 23: text/html: <span style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-size: medium; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; display: inline !important; float: none;">This test verifies that we can get text/html from the drag object during an ondrop event.<span class="Apple-converted-space"> </span></span>
+CONSOLE MESSAGE: line 23: text/html: <span style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium; float: none; display: inline !important;">This test verifies that we can get text/html from the drag object during an ondrop event.<span class="Apple-converted-space"> </span></span>
 This test verifies that we can get text/html from the drag object during an ondrop event. This test requires DRT.
 PASS
diff --git a/LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html-expected.txt b/LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html-expected.txt
new file mode 100644 (file)
index 0000000..1e84542
--- /dev/null
@@ -0,0 +1,18 @@
+This tests calling setData with a HTML in a null origin document. The HTML should be sanitized in another document
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS htmlReadInSameDocument is "\n    <meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world\n    <script>function dangerousCode() { }</script>\n"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/html\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS html = event.clipboardData.getData("text/html"); html.includes("hello") is true
+PASS html.includes(", world") is true
+PASS html.includes("secret") is false
+PASS html.includes("dangerousCode") is false
+PASS JSON.stringify(event.clipboardData.types) is "[\"text/html\"]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[{\"kind\":\"string\",\"type\":\"text/html\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html b/LayoutTests/http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html
new file mode 100644 (file)
index 0000000..996b0a2
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/js-test-pre.js"></script>
+<script>
+
+description('This tests calling setData with a HTML in a null origin document. The HTML should be sanitized in another document');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+onmessage = (event) => {
+    originalHTML = event.data.originalHTML;
+    htmlReadInSameDocument = event.data.html;
+    shouldBeEqualToString('htmlReadInSameDocument', originalHTML);
+    typesInSameDocument = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/html"]');
+    itemsInSameDocument = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/html"}]');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    shouldBeTrue('html = event.clipboardData.getData("text/html"); html.includes("hello")');
+    shouldBeTrue('html.includes(", world")');
+    shouldBeFalse('html.includes("secret")');
+    shouldBeFalse('html.includes("dangerousCode")');
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["text/html"]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))',
+        '[{"kind":"string","type":"text/html"}]');
+    document.getElementById('destination').remove();
+    finishJSTest();
+}
+
+</script>
+<iframe src="http://localhost:8000/security/clipboard/resources/copy-html.html"></iframe>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+<script src="/resources/js-test-post.js"></script>
+</body>
+</html>
index 87d5f94..56423c3 100644 (file)
@@ -33,7 +33,7 @@ function doPaste(event) {
 }
 
 </script>
-<iframe src="http://localhost:8000/security/clipboard/resources/copy.html"></iframe>
+<iframe src="http://localhost:8000/security/clipboard/resources/copy-url.html"></iframe>
 <div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
 <script src="/resources/js-test-post.js"></script>
 </body>
diff --git a/LayoutTests/http/tests/security/clipboard/resources/copy-html.html b/LayoutTests/http/tests/security/clipboard/resources/copy-html.html
new file mode 100644 (file)
index 0000000..f51bb4c
--- /dev/null
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<body>
+<button onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="copy(event)" contenteditable>
+    <meta content="secret"><b onmouseover="dangerousCode()">hello</b><!-- secret-->, world
+    <script>function dangerousCode() { }</script>
+</div>
+<div id="destination" onpaste="paste(event)" contenteditable>2. Paste here</div>
+<script>
+
+const originalHTML = document.getElementById('source').innerHTML;
+function copy(event) {
+    event.clipboardData.setData('text/html', originalHTML);
+    event.preventDefault();
+}
+
+function paste(event) {
+    parent.postMessage({
+        originalHTML,
+        html: event.clipboardData.getData('text/html'),
+        types: event.clipboardData.types,
+        items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+}
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+if (window.testRunner)
+    runTest();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/platform/ios-wk1/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt b/LayoutTests/platform/ios-wk1/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt
deleted file mode 100644 (file)
index b3de879..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Rich text
-{
-    "paste": {
-        "text/plain": "Rich text",
-        "text/html": "<span style=\"caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -apple-system; font-size: 150px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: nowrap; widows: auto; word-spacing: 0px; -webkit-tap-highlight-color: rgba(26, 26, 26, 0.301961); -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; display: inline !important; float: none;\">Rich text</span>"
-    }
-}
diff --git a/LayoutTests/platform/ios-wk2/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt b/LayoutTests/platform/ios-wk2/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt
deleted file mode 100644 (file)
index 263144b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Rich text
-{
-    "paste": {
-        "text/plain": "Rich text",
-        "text/html": "<span style=\"caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: -apple-system; font-size: 150px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: nowrap; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; display: inline !important; float: none;\">Rich text</span>"
-    }
-}
index c70fa20..43a1c4a 100644 (file)
@@ -12,6 +12,7 @@ editing/pasteboard/data-transfer-get-data-on-drop-plain-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-rich-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-url.html [ Pass ]
 editing/pasteboard/data-transfer-is-unique-for-dragenter-and-dragleave.html [ Pass ]
+editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html [ Pass ]
 editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin.html [ Pass ]
 editing/pasteboard/drag-end-crash-accessing-item-list.html [ Pass ]
 editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Pass ]
index 2837f8c..73e620c 100644 (file)
@@ -1135,7 +1135,8 @@ editing/pasteboard/ [ Pass Failure ]
 [ Debug ] editing/selection/4975120.html [ Skip ] # Debug Assertion
 
 # Custom pasteboard data is not supported on Windows.
-http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html
+http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html [ Skip ]
+http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.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
index 946c480..aa94618 100644 (file)
@@ -1,3 +1,100 @@
+2017-10-15  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Cannot access images included in the content pasted from Microsoft Word
+        https://bugs.webkit.org/show_bug.cgi?id=124391
+        <rdar://problem/26862741>
+
+        Reviewed by Antti Koivisto.
+
+        The bug is caused by the fact Microsoft Word generates HTML content which references an image using file URL.
+        Because the websites don't have access to arbtirary file URLs, this prevents editors such as TinyMCE to save
+        those images.
+
+        This patch fixes the problem by converting file URLs for images and all other subresources in the web archive
+        generated by Microsoft Word by blob URLs like r222839 for RTF/RTFD and r222119 for images.
+
+        To avoid revealing privacy sensitive information such as the absolute local file path to the user's home directory
+        Microsoft Word and other applications in the system includes in the web archive placed in the system pasteboard,
+        this patch also introduces the mechanism to sanitize when the HTML content is read by DataTransfer's getData.
+
+        This patch also introduces the sanitization for when writing HTML into the pasteboard since other applications
+        in the syste which is capable to processing web archives are not necessarily equipped to pretect itself and the
+        rest of the system from potentially dangerous JavaScript included in the web archive placed in the system pasteboard.
+
+        Finally, this patch expands the list of clipboard types that are exposed as "text/html" to the Web platform by
+        adding the capability to convert RTF, RTFD, and web archive into HTML markup by introducing WebContentMarkupReader,
+        a new subclass of PasteboardWebContentReader which creates a HTML markup instead of a document fragment. Most of
+        the sanitization process happens in this new class, and will be expanded to WebContentReader to make pasting safer.
+
+        Tests: editing/pasteboard/data-transfer-get-data-on-pasting-html-uses-blob-url.html
+               editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying-in-null-origin.html
+               editing/pasteboard/data-transfer-set-data-sanitizes-html-when-copying.html
+               editing/pasteboard/data-transfer-set-data-sanitlize-html-when-dragging-in-null-origin.html
+               http/tests/security/clipboard/copy-paste-html-across-origin-sanitizes-html.html
+               CopyHTML.Sanitizes
+               DataInteractionTests.DataTransferSanitizeHTML
+               PasteRTF.ExposesHTMLTypeInDataTransfer
+               PasteRTFD.ExposesHTMLTypeInDataTransfer
+               PasteRTFD.ImageElementUsesBlobURLInHTML
+               PasteWebArchive.ExposesHTMLTypeInDataTransfer
+
+        * dom/DataTransfer.cpp:
+        (WebCore::originIdentifierForDocument): Moved to Document::originIdentifierForPasteboard.
+        (WebCore::DataTransfer::createForCopyAndPaste):
+        (WebCore::DataTransfer::getDataForItem const): Use WebContentMarkupReader read HTMl content so that we can read
+        web arhive, RTF, and RTFD as text/html.
+        (WebCore::DataTransfer::getData const):
+        (WebCore::DataTransfer::setData):
+        (WebCore::DataTransfer::setDataFromItemList): Sanitize the HTML before placing into the system pasteboard.
+        (WebCore::DataTransfer::createForDragStartEvent):
+        (WebCore::DataTransfer::createForDrop):
+        (WebCore::DataTransfer::createForUpdatingDropTarget):
+        * dom/DataTransfer.h:
+        * dom/DataTransfer.idl:
+        * dom/DataTransferItem.cpp:
+        (WebCore::DataTransferItem::getAsString const):
+        * dom/Document.cpp:
+        (WebCore::Document::originIdentifierForPasteboard): Renamed from uniqueIdentifier. Moved the code to use the origin
+        string and then falling back to the UUID here from originIdentifierForDocument in DataTransfer.cpp.
+        * dom/Document.h:
+        * editing/WebContentReader.cpp:
+        (WebCore::WebContentMarkupReader::shouldSanitize const): Added.
+        * editing/WebContentReader.h:
+        (WebCore::WebContentMarkupReader): Added.
+        (WebCore::WebContentMarkupReader::WebContentMarkupReader):
+        * editing/cocoa/WebContentReaderCocoa.mm:
+        (WebCore::createFragmentFromWebArchive): Extracted out of WebContentReader::readWebArchive to share code.
+        (WebCore::WebContentReader::readWebArchive):
+        (WebCore::WebContentMarkupReader::readWebArchive): Added. Reads the web archive, replace all subresource URLs by
+        blob URLs, and re-generate the markup using our copy & paste code. The last step is requied to strip away any privacy
+        sensitive information as well as potentially dangerous JavaScript code.
+        (WebCore::stripMicrosoftPrefix): Extracted out of WebContentReader::readHTML to share code.
+        (WebCore::WebContentReader::readHTML):
+        (WebCore::WebContentMarkupReader::readHTML): Added. Only sanitize the markup when it comes from a different origin.
+        (WebCore::WebContentReader::readRTFD): Added a nullity check for frame.document().
+        (WebCore::WebContentMarkupReader::readRTFD): Added.
+        (WebCore::WebContentMarkupReader::readRTF): Added.
+        * editing/markup.h:
+        * editing/markup.cpp:
+        (WebCore::createPageForSanitizingWebContent): Added.
+        (WebCore::sanitizeMarkup): Added. This function "pastes" the markup into a new isolated document then reserializes
+        using our serialization code for copy. It strips away all invisible information such as comments, and strips away
+        event handlers and script elements to remove potentially dangerous scripts.
+        * platform/Pasteboard.h:
+        * platform/ios/PasteboardIOS.mm:
+        (WebCore::Pasteboard::readPasteboardWebContentDataForType): Now that this code can be called by DataTransfer, added
+        the checks for the change count to make sure we stop letting web content read if the pasteboard had been changed by
+        some other applications. To do this, turned this function into a member of Pasteboard. Also changed the return type
+        to an enum with tri-state to exist the loop early in the call sites.
+        (WebCore::Pasteboard::read):
+        (WebCore::Pasteboard::readRespectingUTIFidelities):
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::safeTypeForDOMToReadAndWriteForPlatformType): Treat RTF, RTFD, and web archive as HTML.
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::read): Add the change count checks now that this code can be called by DataTransfer.
+        * platform/mac/PlatformPasteboardMac.mm:
+        (WebCore::safeTypeForDOMToReadAndWriteForPlatformType): Treat RTF, RTFD, and web archive as HTML.
+
 2017-10-16  Ryan Haddad  <ryanhaddad@apple.com>
 
         Unreviewed attempt to fix the Windows debug build.
index 7d79cc2..5471d8f 100644 (file)
@@ -30,6 +30,7 @@
 #include "CachedImageClient.h"
 #include "DataTransferItem.h"
 #include "DataTransferItemList.h"
+#include "DocumentFragment.h"
 #include "DragData.h"
 #include "Editor.h"
 #include "FileList.h"
@@ -42,7 +43,9 @@
 #include "Settings.h"
 #include "StaticPasteboard.h"
 #include "URLParser.h"
+#include "WebContentReader.h"
 #include "WebCorePasteboardFileReader.h"
+#include "markup.h"
 
 namespace WebCore {
 
@@ -78,18 +81,10 @@ DataTransfer::DataTransfer(StoreMode mode, std::unique_ptr<Pasteboard> pasteboar
 #endif
 }
 
-static String originIdentifierForDocument(Document& document)
-{
-    auto origin = document.securityOrigin().toString();
-    if (origin == "null")
-        return document.uniqueIdentifier();
-    return origin;
-}
-
 Ref<DataTransfer> DataTransfer::createForCopyAndPaste(Document& document, StoreMode storeMode, std::unique_ptr<Pasteboard>&& pasteboard)
 {
     auto dataTransfer = adoptRef(*new DataTransfer(storeMode, WTFMove(pasteboard)));
-    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
+    dataTransfer->m_originIdentifier = document.originIdentifierForPasteboard();
     return dataTransfer;
 }
 
@@ -146,7 +141,7 @@ void DataTransfer::clearData(const String& type)
         m_itemList->didClearStringData(normalizedType);
 }
 
-String DataTransfer::getDataForItem(const String& type) const
+String DataTransfer::getDataForItem(Document& document, const String& type) const
 {
     if (!canReadData())
         return { };
@@ -164,30 +159,30 @@ String DataTransfer::getDataForItem(const String& type) const
     if (!Settings::customPasteboardDataEnabled())
         return m_pasteboard->readString(lowercaseType);
 
-    bool isSameOrigin = false;
-    if (is<StaticPasteboard>(*m_pasteboard)) {
-        // StaticPasteboard is only used to stage data written by websites before being committed to the system pasteboard.
-        isSameOrigin = true;
-    } else if (!m_originIdentifier.isNull()) {
-        String originOfPasteboard = m_pasteboard->readOrigin();
-        isSameOrigin = m_originIdentifier == originOfPasteboard;
+    // StaticPasteboard is only used to stage data written by websites before being committed to the system pasteboard.
+    bool isSameOrigin = is<StaticPasteboard>(*m_pasteboard) || (!m_originIdentifier.isNull() && m_originIdentifier == m_pasteboard->readOrigin());
+    if (isSameOrigin) {
+        String value = m_pasteboard->readStringInCustomData(lowercaseType);
+        if (!value.isNull())
+            return value;
     }
+    if (!Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
+        return { };
 
-    if (!isSameOrigin) {
-        if (!Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
+    if (!is<StaticPasteboard>(*m_pasteboard) && type == "text/html") {
+        if (!document.frame())
             return { };
-        return m_pasteboard->readString(lowercaseType);
+        WebContentMarkupReader reader { *document.frame() };
+        m_pasteboard->read(reader);
+        return reader.markup;
     }
 
-    String value = m_pasteboard->readStringInCustomData(lowercaseType);
-    if (value.isNull() && Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
-        value = m_pasteboard->readString(lowercaseType);
-    return value;
+    return m_pasteboard->readString(type);
 }
 
-String DataTransfer::getData(const String& type) const
+String DataTransfer::getData(Document& document, const String& type) const
 {
-    return getDataForItem(normalizeType(type));
+    return getDataForItem(document, normalizeType(type));
 }
 
 bool DataTransfer::shouldSuppressGetAndSetDataToAvoidExposingFilePaths() const
@@ -222,11 +217,13 @@ void DataTransfer::setDataFromItemList(const String& type, const String& data)
     }
 
     String sanitizedData;
-    if (type == "text/uri-list") {
+    if (type == "text/html")
+        sanitizedData = sanitizeMarkup(data);
+    else if (type == "text/uri-list") {
         auto url = URLParser(data).result();
         if (url.isValid())
             sanitizedData = url.string();
-    } else if (type == "text/plain" || type == "text/html")
+    } else if (type == "text/plain")
         sanitizedData = data; // Nothing to sanitize.
 
     if (sanitizedData != data)
@@ -430,7 +427,7 @@ Ref<DataTransfer> DataTransfer::createForDrag()
 Ref<DataTransfer> DataTransfer::createForDragStartEvent(Document& document)
 {
     auto dataTransfer = adoptRef(*new DataTransfer(StoreMode::ReadWrite, std::make_unique<StaticPasteboard>(), Type::DragAndDropData));
-    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
+    dataTransfer->m_originIdentifier = document.originIdentifierForPasteboard();
     return dataTransfer;
 }
 
@@ -438,7 +435,7 @@ Ref<DataTransfer> DataTransfer::createForDrop(Document& document, std::unique_pt
 {
     auto dataTransfer = adoptRef(*new DataTransfer(DataTransfer::StoreMode::Readonly, WTFMove(pasteboard), draggingFiles ? Type::DragAndDropFiles : Type::DragAndDropData));
     dataTransfer->setSourceOperation(sourceOperation);
-    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
+    dataTransfer->m_originIdentifier = document.originIdentifierForPasteboard();
     return dataTransfer;
 }
 
@@ -453,7 +450,7 @@ Ref<DataTransfer> DataTransfer::createForUpdatingDropTarget(Document& document,
 #endif
     auto dataTransfer = adoptRef(*new DataTransfer(mode, WTFMove(pasteboard), draggingFiles ? Type::DragAndDropFiles : Type::DragAndDropData));
     dataTransfer->setSourceOperation(sourceOperation);
-    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
+    dataTransfer->m_originIdentifier = document.originIdentifierForPasteboard();
     return dataTransfer;
 }
 
index 65b4bc6..31dc799 100644 (file)
@@ -64,8 +64,8 @@ public:
 
     void clearData(const String& type = String());
 
-    String getData(const String& type) const;
-    String getDataForItem(const String& type) const;
+    String getData(Document&, const String& type) const;
+    String getDataForItem(Document&, const String& type) const;
 
     void setData(const String& type, const String& data);
     void setDataFromItemList(const String& type, const String& data);
index c7057ea..0c677c1 100644 (file)
@@ -38,7 +38,7 @@
     void setDragImage(Element? image, long x, long y); // FIXME: Element argument is not nullable in the HTML standard.
 
     readonly attribute FrozenArray<DOMString> types;
-    DOMString getData(DOMString format);
+    [CallWith=Document] DOMString getData(DOMString format);
     void setData(DOMString format, DOMString data);
     void clearData(optional DOMString format);
     // FIXME: files should use [SameObject] once that is supported.
index 7c8cd52..6783486 100644 (file)
@@ -34,6 +34,7 @@
 
 #include "DOMFileSystem.h"
 #include "DataTransferItemList.h"
+#include "Document.h"
 #include "File.h"
 #include "FileSystem.h"
 #include "FileSystemDirectoryEntry.h"
@@ -85,7 +86,7 @@ String DataTransferItem::type() const
     return isInDisabledMode() ? String() : m_type;
 }
 
-void DataTransferItem::getAsString(ScriptExecutionContext& context, RefPtr<StringCallback>&& callback) const
+void DataTransferItem::getAsString(Document& document, RefPtr<StringCallback>&& callback) const
 {
     if (!callback || !m_list || m_file)
         return;
@@ -95,7 +96,7 @@ void DataTransferItem::getAsString(ScriptExecutionContext& context, RefPtr<Strin
         return;
 
     // FIXME: Make this async.
-    callback->scheduleCallback(context, dataTransfer.getDataForItem(m_type));
+    callback->scheduleCallback(document, dataTransfer.getDataForItem(document, m_type));
 }
 
 RefPtr<File> DataTransferItem::getAsFile() const
index 1f2809f..91ebda1 100644 (file)
@@ -60,7 +60,7 @@ public:
     bool isFile() const { return m_file; }
     String kind() const;
     String type() const;
-    void getAsString(ScriptExecutionContext&, RefPtr<StringCallback>&&) const;
+    void getAsString(Document&, RefPtr<StringCallback>&&) const;
     RefPtr<File> getAsFile() const;
     RefPtr<FileSystemEntry> getAsEntry(ScriptExecutionContext&) const;
 
index e7d1e03..5393581 100644 (file)
@@ -36,7 +36,7 @@
     readonly attribute DOMString kind;
     readonly attribute DOMString type;
 
-    [CallWith=ScriptExecutionContext] void getAsString(StringCallback? callback);
+    [CallWith=Document] void getAsString(StringCallback? callback);
     File getAsFile();
 
     // https://wicg.github.io/entries-api/#html-data
index 6312b31..231c201 100644 (file)
@@ -5300,8 +5300,11 @@ bool Document::isTelephoneNumberParsingAllowed() const
 
 #endif
 
-String Document::uniqueIdentifier()
+String Document::originIdentifierForPasteboard()
 {
+    auto origin = securityOrigin().toString();
+    if (origin != "null")
+        return origin;
     if (!m_uniqueIdentifier)
         m_uniqueIdentifier = "null:" + createCanonicalUUIDString();
     return m_uniqueIdentifier;
index 1723ba4..824cf7f 100644 (file)
@@ -956,7 +956,7 @@ public:
     void incDOMTreeVersion() { m_domTreeVersion = ++s_globalTreeVersion; }
     uint64_t domTreeVersion() const { return m_domTreeVersion; }
 
-    String uniqueIdentifier();
+    String originIdentifierForPasteboard();
 
     // XPathEvaluator methods
     WEBCORE_EXPORT ExceptionOr<Ref<XPathExpression>> createExpression(const String& expression, RefPtr<XPathNSResolver>&&);
index 72c679a..e6bc262 100644 (file)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "WebContentReader.h"
 
+#include "Document.h"
 #include "DocumentFragment.h"
 
 namespace WebCore {
@@ -38,5 +39,10 @@ void WebContentReader::addFragment(Ref<DocumentFragment>&& newFragment)
         fragment->appendChild(newFragment.get());
 }
 
+bool WebContentMarkupReader::shouldSanitize() const
+{
+    return frame.document() && frame.document()->originIdentifierForPasteboard() != contentOrigin;
+}
+
 }
 
index d0cecfa..843ac20 100644 (file)
@@ -65,6 +65,31 @@ private:
     bool readPlainText(const String&) override;
 };
 
+class WebContentMarkupReader final : public PasteboardWebContentReader {
+public:
+    Frame& frame;
+    String markup;
+
+    explicit WebContentMarkupReader(Frame& frame)
+        : frame(frame)
+    {
+    }
+
+private:
+    bool shouldSanitize() const;
+
+#if PLATFORM(COCOA)
+    bool readWebArchive(SharedBuffer&) override;
+    bool readFilenames(const Vector<String>&) override { return false; }
+    bool readHTML(const String&) override;
+    bool readRTFD(SharedBuffer&) override;
+    bool readRTF(SharedBuffer&) override;
+    bool readImage(Ref<SharedBuffer>&&, const String&) override { return false; }
+    bool readURL(const URL&, const String&) override { return false; }
+#endif
+    bool readPlainText(const String&) override { return false; }
+};
+
 #if PLATFORM(COCOA) && defined(__OBJC__)
 struct FragmentAndResources {
     RefPtr<DocumentFragment> fragment;
index f256556..ff63eb3 100644 (file)
 #import "HTMLBodyElement.h"
 #import "HTMLImageElement.h"
 #import "LegacyWebArchive.h"
+#import "MainFrame.h"
 #import "Page.h"
 #import "Settings.h"
+#import "SocketProvider.h"
 #import "WebArchiveResourceFromNSAttributedString.h"
 #import "WebArchiveResourceWebResourceHandler.h"
 #import "WebNSAttributedStringExtras.h"
@@ -179,37 +181,84 @@ RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttribute
     return WTFMove(fragmentAndResources.fragment);
 }
 
-bool WebContentReader::readWebArchive(SharedBuffer& buffer)
-{
-    if (frame.settings().preferMIMETypeForImages() || !frame.document())
-        return false;
+struct FragmentAndArchive {
+    Ref<DocumentFragment> fragment;
+    Ref<Archive> archive;
+};
 
+static std::optional<FragmentAndArchive> createFragmentFromWebArchive(Document& document, SharedBuffer& buffer, const std::function<bool(const String)>& canShowMIMETypeAsHTML)
+{
     auto archive = LegacyWebArchive::create(URL(), buffer);
     if (!archive)
-        return false;
+        return std::nullopt;
 
     RefPtr<ArchiveResource> mainResource = archive->mainResource();
     if (!mainResource)
-        return false;
+        return std::nullopt;
 
     auto type = mainResource->mimeType();
-    if (!frame.loader().client().canShowMIMETypeAsHTML(type))
+    if (!canShowMIMETypeAsHTML(type))
+        return std::nullopt;
+
+    auto markupString = String::fromUTF8(mainResource->data().data(), mainResource->data().size());
+    auto fragment = createFragmentFromMarkup(document, markupString, mainResource->url(), DisallowScriptingAndPluginContent);
+
+    return FragmentAndArchive { WTFMove(fragment), archive.releaseNonNull() };
+}
+
+bool WebContentReader::readWebArchive(SharedBuffer& buffer)
+{
+    if (frame.settings().preferMIMETypeForImages() || !frame.document())
         return false;
 
     DeferredLoadingScope scope(frame);
-    auto markupString = String::fromUTF8(mainResource->data().data(), mainResource->data().size());
-    addFragment(createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent));
+    auto result = createFragmentFromWebArchive(*frame.document(), buffer, [&] (const String& type) {
+        return frame.loader().client().canShowMIMETypeAsHTML(type);
+    });
+    if (!result)
+        return false;
 
+    fragment = WTFMove(result->fragment);
     if (DocumentLoader* loader = frame.loader().documentLoader())
-        loader->addAllArchiveResources(*archive);
+        loader->addAllArchiveResources(result->archive.get());
 
     return true;
 }
 
-bool WebContentReader::readHTML(const String& string)
+bool WebContentMarkupReader::readWebArchive(SharedBuffer& buffer)
+{
+    auto page = createPageForSanitizingWebContent();
+    Document* stagingDocument = page->mainFrame().document();
+    ASSERT(stagingDocument);
+
+    DeferredLoadingScope scope(frame);
+    auto result = createFragmentFromWebArchive(*stagingDocument, buffer, [&] (const String& type) {
+        return frame.loader().client().canShowMIMETypeAsHTML(type);
+    });
+    if (!result)
+        return false;
+
+    HashMap<AtomicString, AtomicString> blobURLMap;
+    for (const Ref<ArchiveResource>& subresource : result->archive->subresources()) {
+        auto blob = Blob::create(subresource->data(), subresource->mimeType());
+        String blobURL = DOMURL::createObjectURL(*frame.document(), blob);
+        blobURLMap.set(subresource->url().string(), blobURL);
+    }
+    replaceSubresourceURLs(result->fragment.get(), WTFMove(blobURLMap));
+
+    auto* bodyElement = stagingDocument->body();
+    ASSERT(bodyElement);
+    bodyElement->appendChild(result->fragment);
+
+    auto range = Range::create(*stagingDocument);
+    range->selectNodeContents(*bodyElement);
+    markup = createMarkup(range.get(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
+
+    return true;
+}
+
+static String stripMicrosoftPrefix(const String& string)
 {
-    String stringOmittingMicrosoftPrefix = string;
-    
 #if PLATFORM(MAC)
     // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
     // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
@@ -217,24 +266,43 @@ bool WebContentReader::readHTML(const String& string)
     if (string.startsWith("Version:")) {
         size_t location = string.findIgnoringCase("<html");
         if (location != notFound)
-            stringOmittingMicrosoftPrefix = string.substring(location);
+            return string.substring(location);
     }
 #endif
+    return string;
+}
 
-    if (stringOmittingMicrosoftPrefix.isEmpty())
+bool WebContentReader::readHTML(const String& string)
+{
+    if (frame.settings().preferMIMETypeForImages() || !frame.document())
         return false;
+    Document& document = *frame.document();
 
-    if (!frame.document())
+    String stringOmittingMicrosoftPrefix = stripMicrosoftPrefix(string);
+    if (stringOmittingMicrosoftPrefix.isEmpty())
         return false;
-    Document& document = *frame.document();
 
     addFragment(createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent));
     return true;
 }
 
+bool WebContentMarkupReader::readHTML(const String& string)
+{
+    if (!frame.document())
+        return false;
+
+    String rawHTML = stripMicrosoftPrefix(string);
+    if (shouldSanitize())
+        markup = sanitizeMarkup(rawHTML);
+    else
+        markup = rawHTML;
+
+    return !markup.isEmpty();
+}
+
 bool WebContentReader::readRTFD(SharedBuffer& buffer)
 {
-    if (frame.settings().preferMIMETypeForImages())
+    if (frame.settings().preferMIMETypeForImages() || !frame.document())
         return false;
 
     auto fragment = createFragmentAndAddResources(frame, adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]).get());
@@ -245,6 +313,15 @@ bool WebContentReader::readRTFD(SharedBuffer& buffer)
     return true;
 }
 
+bool WebContentMarkupReader::readRTFD(SharedBuffer& buffer)
+{
+    if (!frame.document())
+        return false;
+    auto fragment = createFragmentAndAddResources(frame, adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]).get());
+    markup = createMarkup(*fragment);
+    return true;
+}
+
 bool WebContentReader::readRTF(SharedBuffer& buffer)
 {
     if (frame.settings().preferMIMETypeForImages())
@@ -258,6 +335,17 @@ bool WebContentReader::readRTF(SharedBuffer& buffer)
     return true;
 }
 
+bool WebContentMarkupReader::readRTF(SharedBuffer& buffer)
+{
+    if (!frame.document())
+        return false;
+    auto fragment = createFragmentAndAddResources(frame, adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]).get());
+    if (!fragment)
+        return false;
+    markup = createMarkup(*fragment);
+    return true;
+}
+
 bool WebContentReader::readPlainText(const String& text)
 {
     if (!allowPlainText)
index e738656..a86c3e5 100644 (file)
 #include "CSSPropertyNames.h"
 #include "CSSValue.h"
 #include "CSSValueKeywords.h"
+#include "CacheStorageProvider.h"
 #include "ChildListMutationScope.h"
 #include "DocumentFragment.h"
 #include "DocumentLoader.h"
 #include "DocumentType.h"
 #include "Editing.h"
 #include "Editor.h"
+#include "EditorClient.h"
 #include "ElementIterator.h"
+#include "EmptyClients.h"
 #include "File.h"
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "HTMLTableElement.h"
 #include "HTMLTextAreaElement.h"
 #include "HTMLTextFormControlElement.h"
-#include "URL.h"
+#include "LibWebRTCProvider.h"
+#include "MainFrame.h"
 #include "MarkupAccumulator.h"
 #include "NodeList.h"
+#include "Page.h"
+#include "PageConfiguration.h"
 #include "Range.h"
 #include "RenderBlock.h"
 #include "Settings.h"
+#include "SocketProvider.h"
 #include "StyleProperties.h"
 #include "TextIterator.h"
 #include "TypedElementDescendantIterator.h"
+#include "URL.h"
 #include "VisibleSelection.h"
 #include "VisibleUnits.h"
 #include <wtf/StdLibExtras.h>
@@ -135,6 +143,51 @@ void replaceSubresourceURLs(Ref<DocumentFragment>&& fragment, HashMap<AtomicStri
     for (auto& change : changes)
         change.apply();
 }
+
+std::unique_ptr<Page> createPageForSanitizingWebContent()
+{
+    PageConfiguration pageConfiguration(createEmptyEditorClient(), SocketProvider::create(), LibWebRTCProvider::create(), CacheStorageProvider::create());
+
+    fillWithEmptyClients(pageConfiguration);
+    
+    auto page = std::make_unique<Page>(WTFMove(pageConfiguration));
+    page->settings().setMediaEnabled(false);
+    page->settings().setScriptEnabled(false);
+    page->settings().setPluginsEnabled(false);
+    page->settings().setAcceleratedCompositingEnabled(false);
+
+    Frame& frame = page->mainFrame();
+    frame.setView(FrameView::create(frame));
+    frame.init();
+
+    FrameLoader& loader = frame.loader();
+    static char markup[] = "<!DOCTYPE html><html><body></body></html>";
+    ASSERT(loader.activeDocumentLoader());
+    loader.activeDocumentLoader()->writer().setMIMEType("text/html");
+    loader.activeDocumentLoader()->writer().begin();
+    loader.activeDocumentLoader()->writer().addData(markup, sizeof(markup));
+    loader.activeDocumentLoader()->writer().end();
+
+    return page;
+}
+
+
+String sanitizeMarkup(const String& rawHTML)
+{
+    auto page = createPageForSanitizingWebContent();
+    Document* stagingDocument = page->mainFrame().document();
+    ASSERT(stagingDocument);
+    auto* bodyElement = stagingDocument->body();
+    ASSERT(bodyElement);
+
+    auto fragment = createFragmentFromMarkup(*stagingDocument, rawHTML, emptyString(), DisallowScriptingAndPluginContent);
+    bodyElement->appendChild(fragment.get());
+
+    auto range = Range::create(*stagingDocument);
+    range->selectNodeContents(*bodyElement);
+    return createMarkup(range.get(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
+}
+
     
 class StyledMarkupAccumulator final : public MarkupAccumulator {
 public:
index 2c7b303..6a7f6ff 100644 (file)
@@ -42,10 +42,13 @@ class Frame;
 class HTMLElement;
 class URL;
 class Node;
+class Page;
 class QualifiedName;
 class Range;
 
 void replaceSubresourceURLs(Ref<DocumentFragment>&&, HashMap<AtomicString, AtomicString>&&);
+std::unique_ptr<Page> createPageForSanitizingWebContent();
+String sanitizeMarkup(const String&);
 
 enum EChildrenOnly { IncludeNode, ChildrenOnly };
 enum EAbsoluteURLs { DoNotResolveURLs, ResolveAllURLs, ResolveNonLocalURLs };
index 473f264..b1e4474 100644 (file)
@@ -127,9 +127,11 @@ struct PasteboardImage {
 
 class PasteboardWebContentReader {
 public:
+    String contentOrigin;
+
     virtual ~PasteboardWebContentReader() { }
 
-#if !(PLATFORM(GTK) || PLATFORM(WIN))
+#if PLATFORM(COCOA)
     virtual bool readWebArchive(SharedBuffer&) = 0;
     virtual bool readFilenames(const Vector<String>&) = 0;
     virtual bool readHTML(const String&) = 0;
@@ -270,6 +272,13 @@ private:
 #if PLATFORM(IOS)
     bool respectsUTIFidelities() const;
     void readRespectingUTIFidelities(PasteboardWebContentReader&);
+
+    enum class ReaderResult {
+        ReadType,
+        DidNotReadType,
+        PasteboardWasChangedExternally
+    };
+    ReaderResult readPasteboardWebContentDataForType(PasteboardWebContentReader&, PasteboardStrategy&, NSString *type, int itemIndex);
 #endif
 
 #if PLATFORM(WIN)
index 18c0b50..2e0a976 100644 (file)
@@ -162,50 +162,66 @@ static NSArray* supportedImageTypes()
     return @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
 }
 
-static bool readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex, const String& pasteboardName)
+Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex)
 {
     if ([type isEqualToString:WebArchivePboardType]) {
-        auto buffer = strategy.readBufferFromPasteboard(itemIndex, WebArchivePboardType, pasteboardName);
-        return buffer && reader.readWebArchive(*buffer);
+        auto buffer = strategy.readBufferFromPasteboard(itemIndex, WebArchivePboardType, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return buffer && reader.readWebArchive(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
-        String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, pasteboardName);
-        return !htmlString.isNull() && reader.readHTML(htmlString);
+        String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return !htmlString.isNull() && reader.readHTML(htmlString) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if ([type isEqualToString:(NSString *)kUTTypeFlatRTFD]) {
-        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, pasteboardName);
-        return buffer && reader.readRTFD(*buffer);
+        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return buffer && reader.readRTFD(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if ([type isEqualToString:(NSString *)kUTTypeRTF]) {
-        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, pasteboardName);
-        return buffer && reader.readRTF(*buffer);
+        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return buffer && reader.readRTF(*buffer) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if ([supportedImageTypes() containsObject:type]) {
-        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, pasteboardName);
-        return buffer && reader.readImage(buffer.releaseNonNull(), type);
+        RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return buffer && reader.readImage(buffer.releaseNonNull(), type) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if ([type isEqualToString:(NSString *)kUTTypeURL]) {
         String title;
-        URL url = strategy.readURLFromPasteboard(itemIndex, kUTTypeURL, pasteboardName, title);
-        return !url.isNull() && reader.readURL(url, title);
+        URL url = strategy.readURLFromPasteboard(itemIndex, kUTTypeURL, m_pasteboardName, title);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return !url.isNull() && reader.readURL(url, title) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if (UTTypeConformsTo((CFStringRef)type, kUTTypePlainText)) {
-        String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, pasteboardName);
-        return !string.isNull() && reader.readPlainText(string);
+        String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
     if (UTTypeConformsTo((CFStringRef)type, kUTTypeText)) {
-        String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, pasteboardName);
-        return !string.isNull() && reader.readPlainText(string);
+        String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, m_pasteboardName);
+        if (m_changeCount != changeCount())
+            return ReaderResult::PasteboardWasChangedExternally;
+        return !string.isNull() && reader.readPlainText(string) ? ReaderResult::ReadType : ReaderResult::DidNotReadType;
     }
 
-    return false;
+    return ReaderResult::DidNotReadType;
 }
 
 void Pasteboard::read(PasteboardWebContentReader& reader)
@@ -222,12 +238,17 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
     if (!numberOfItems)
         return;
 
+    reader.contentOrigin = readOrigin();
+
     NSArray *types = supportedWebContentPasteboardTypes();
     int numberOfTypes = [types count];
 
     for (int i = 0; i < numberOfItems; i++) {
         for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
-            if (readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i, m_pasteboardName))
+            auto result = readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i);
+            if (result == ReaderResult::PasteboardWasChangedExternally)
+                return;
+            if (result == ReaderResult::ReadType)
                 break;
         }
     }
@@ -251,7 +272,10 @@ void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
         Vector<String> typesForItemInOrderOfFidelity;
         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
         for (auto& type : typesForItemInOrderOfFidelity) {
-            if (readPasteboardWebContentDataForType(reader, strategy, type, index, m_pasteboardName))
+            auto result = readPasteboardWebContentDataForType(reader, strategy, type, index);
+            if (result == ReaderResult::PasteboardWasChangedExternally)
+                return;
+            if (result == ReaderResult::ReadType)
                 break;
         }
     }
index a4e8bf9..6d7e8ae 100644 (file)
@@ -355,7 +355,8 @@ static const char *safeTypeForDOMToReadAndWriteForPlatformType(const String& pla
     if (UTTypeConformsTo(cfType.get(), kUTTypePlainText))
         return ASCIILiteral("text/plain");
 
-    if (UTTypeConformsTo(cfType.get(), kUTTypeHTML))
+    if (UTTypeConformsTo(cfType.get(), kUTTypeHTML) || UTTypeConformsTo(cfType.get(), (CFStringRef)WebArchivePboardType)
+        || UTTypeConformsTo(cfType.get(), kUTTypeRTF) || UTTypeConformsTo(cfType.get(), kUTTypeFlatRTFD))
         return ASCIILiteral("text/html");
 
     if (UTTypeConformsTo(cfType.get(), kUTTypeURL))
index 2d30c6c..5a5d54e 100644 (file)
@@ -332,9 +332,11 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
     Vector<String> types;
     strategy.getTypes(types, m_pasteboardName);
 
+    reader.contentOrigin = readOrigin();
+
     if (types.contains(WebArchivePboardType)) {
         if (auto buffer = strategy.bufferForType(WebArchivePboardType, m_pasteboardName)) {
-            if (reader.readWebArchive(*buffer))
+            if (m_changeCount != changeCount() || reader.readWebArchive(*buffer))
                 return;
         }
     }
@@ -342,54 +344,54 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
     if (types.contains(String(NSFilenamesPboardType))) {
         Vector<String> paths;
         strategy.getPathnamesForType(paths, NSFilenamesPboardType, m_pasteboardName);
-        if (reader.readFilenames(paths))
+        if (m_changeCount != changeCount() || reader.readFilenames(paths))
             return;
     }
 
     if (types.contains(String(NSHTMLPboardType))) {
         String string = strategy.stringForType(NSHTMLPboardType, m_pasteboardName);
-        if (!string.isNull() && reader.readHTML(string))
+        if (m_changeCount != changeCount() || (!string.isNull() && reader.readHTML(string)))
             return;
     }
 
     if (types.contains(String(NSRTFDPboardType))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(NSRTFDPboardType, m_pasteboardName)) {
-            if (reader.readRTFD(*buffer))
+            if (m_changeCount != changeCount() || reader.readRTFD(*buffer))
                 return;
         }
     }
 
     if (types.contains(String(NSRTFPboardType))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(NSRTFPboardType, m_pasteboardName)) {
-            if (reader.readRTF(*buffer))
+            if (m_changeCount != changeCount() || reader.readRTF(*buffer))
                 return;
         }
     }
 
     if (types.contains(String(NSTIFFPboardType))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(NSTIFFPboardType, m_pasteboardName)) {
-            if (reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/tiff")))
+            if (m_changeCount != changeCount() || reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/tiff")))
                 return;
         }
     }
 
     if (types.contains(String(NSPDFPboardType))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(NSPDFPboardType, m_pasteboardName)) {
-            if (reader.readImage(buffer.releaseNonNull(), ASCIILiteral("application/pdf")))
+            if (m_changeCount != changeCount() || reader.readImage(buffer.releaseNonNull(), ASCIILiteral("application/pdf")))
                 return;
         }
     }
 
     if (types.contains(String(kUTTypePNG))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(kUTTypePNG, m_pasteboardName)) {
-            if (reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/png")))
+            if (m_changeCount != changeCount() || reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/png")))
                 return;
         }
     }
 
     if (types.contains(String(kUTTypeJPEG))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(kUTTypeJPEG, m_pasteboardName)) {
-            if (reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/jpeg")))
+            if (m_changeCount != changeCount() || reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/jpeg")))
                 return;
         }
     }
@@ -397,19 +399,19 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
     if (types.contains(String(NSURLPboardType))) {
         URL url = strategy.url(m_pasteboardName);
         String title = strategy.stringForType(WebURLNamePboardType, m_pasteboardName);
-        if (!url.isNull() && reader.readURL(url, title))
+        if (m_changeCount != changeCount() || (!url.isNull() && reader.readURL(url, title)))
             return;
     }
 
     if (types.contains(String(NSStringPboardType))) {
         String string = strategy.stringForType(NSStringPboardType, m_pasteboardName);
-        if (!string.isNull() && reader.readPlainText(string))
+        if (m_changeCount != changeCount() || (!string.isNull() && reader.readPlainText(string)))
             return;
     }
 
     if (types.contains(String(kUTTypeUTF8PlainText))) {
         String string = strategy.stringForType(kUTTypeUTF8PlainText, m_pasteboardName);
-        if (!string.isNull() && reader.readPlainText(string))
+        if (m_changeCount != changeCount() || (!string.isNull() && reader.readPlainText(string)))
             return;
     }
 }
index 22ab2cb..f650e05 100644 (file)
@@ -107,7 +107,8 @@ static const char* safeTypeForDOMToReadAndWriteForPlatformType(const String& pla
     if (platformType == String(NSURLPboardType))
         return ASCIILiteral("text/uri-list");
 
-    if (platformType == String(NSHTMLPboardType))
+    if (platformType == String(NSHTMLPboardType) || platformType == String(WebArchivePboardType)
+        || platformType == String(NSRTFDPboardType) || platformType == String(NSRTFPboardType))
         return ASCIILiteral("text/html");
 
     return nullptr;
index 8c4d502..6b6a1b7 100644 (file)
@@ -1,3 +1,37 @@
+2017-10-15  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Cannot access images included in the content pasted from Microsoft Word
+        https://bugs.webkit.org/show_bug.cgi?id=124391
+        <rdar://problem/26862741>
+
+        Reviewed by Antti Koivisto.
+
+        Added tests for sanitizing HTML contents for copy & paste and drag & drop.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/CopyHTML.mm: Added.
+        (readHTMLFromPasteboard): Added.
+        (createWebViewWithCustomPasteboardDataEnabled): Added.
+        (CopyHTML.Sanitizes): Added.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm:
+        (createWebViewWithCustomPasteboardDataEnabled): Added to enable more tests on bots.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteRTFD.mm:
+        (writeRTFToPasteboard): Added.
+        (createWebViewWithCustomPasteboardDataEnabled): Added.
+        (createHelloWorldString): Added.
+        (PasteRTF.ExposesHTMLTypeInDataTransfer): Added.
+        (PasteRTFD.ExposesHTMLTypeInDataTransfer): Added.
+        (PasteRTFD.ImageElementUsesBlobURLInHTML): Added.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/copy-html.html: Added.
+        * TestWebKitAPI/Tests/WebKitCocoa/paste-rtfd.html: Store the clipboardData contents for
+        PasteRTF.ExposesHTMLTypeInDataTransfer and PasteRTFD.ExposesHTMLTypeInDataTransfer.
+
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (DataInteractionTests.DataTransferSanitizeHTML):
+
 2017-10-16  Youenn Fablet  <youenn@apple.com>
 
         Activate Cache API by default
index 88b4d7c..79e00a1 100644 (file)
                9999108B1F393C96008AD455 /* Copying.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9999108A1F393C8B008AD455 /* Copying.mm */; };
                9B0786A51C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B0786A41C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp */; };
                9B19CDA01F06DFE3000548DD /* NetworkProcessCrashWithPendingConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B19CD9E1F06DFE3000548DD /* NetworkProcessCrashWithPendingConnection.mm */; };
+               9B1F6F781F90558400B55744 /* CopyHTML.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B1056411F9045C700D5583F /* CopyHTML.mm */; };
+               9B1F6F791F90559E00B55744 /* copy-html.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B1056421F9047CC00D5583F /* copy-html.html */; };
+               9B2346421F943E2700DB1D23 /* PasteWebArchive.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B2346411F943A2400DB1D23 /* PasteWebArchive.mm */; };
                9B26FCCA159D16DE00CC3765 /* HTMLFormCollectionNamedItem.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B26FCB4159D15E700CC3765 /* HTMLFormCollectionNamedItem.html */; };
                9B270FEE1DDC2C0B002D53F3 /* closed-shadow-tree-test.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B270FED1DDC25FD002D53F3 /* closed-shadow-tree-test.html */; };
                9B4F8FA7159D52DD002D9F94 /* HTMLCollectionNamedItem.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B4F8FA6159D52CA002D9F94 /* HTMLCollectionNamedItem.html */; };
                                5142B2731517C8C800C32B19 /* ContextMenuCanCopyURL.html in Copy Resources */,
                                CD0BD0A81F79982D001AB2CF /* ContextMenuImgWithVideo.html in Copy Resources */,
                                5C2936961D5C00ED00DEAB1E /* CookieMessage.html in Copy Resources */,
+                               9B1F6F791F90559E00B55744 /* copy-html.html in Copy Resources */,
                                9B62630C1F8C25C8007EE29B /* copy-url.html in Copy Resources */,
                                7AEAD4811E20122700416EFE /* CrossPartitionFileSchemeAccess.html in Copy Resources */,
                                F4AB578A1F65165400DB0DA1 /* custom-draggable-div.html in Copy Resources */,
                9999108A1F393C8B008AD455 /* Copying.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Copying.mm; sourceTree = "<group>"; };
                9B0786A21C58830F00D159E3 /* InjectedBundleMakeAllShadowRootsOpen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InjectedBundleMakeAllShadowRootsOpen.cpp; sourceTree = "<group>"; };
                9B0786A41C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp; sourceTree = "<group>"; };
+               9B1056411F9045C700D5583F /* CopyHTML.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CopyHTML.mm; sourceTree = "<group>"; };
+               9B1056421F9047CC00D5583F /* copy-html.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "copy-html.html"; sourceTree = "<group>"; };
                9B19CD9E1F06DFE3000548DD /* NetworkProcessCrashWithPendingConnection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = NetworkProcessCrashWithPendingConnection.mm; path = WebKit/NetworkProcessCrashWithPendingConnection.mm; sourceTree = "<group>"; };
+               9B2346411F943A2400DB1D23 /* PasteWebArchive.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PasteWebArchive.mm; sourceTree = "<group>"; };
                9B26FC6B159D061000CC3765 /* HTMLFormCollectionNamedItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HTMLFormCollectionNamedItem.mm; sourceTree = "<group>"; };
                9B26FCB4159D15E700CC3765 /* HTMLFormCollectionNamedItem.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = HTMLFormCollectionNamedItem.html; sourceTree = "<group>"; };
                9B270FED1DDC25FD002D53F3 /* closed-shadow-tree-test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "closed-shadow-tree-test.html"; sourceTree = "<group>"; };
                                A14FC5891B89927100D107EB /* ContentFilteringPlugIn.mm */,
                                5CA1DED81F74A87100E71BD3 /* ContentRuleListNotification.mm */,
                                5C2936911D5BF63E00DEAB1E /* CookieAcceptPolicy.mm */,
+                               9B1056411F9045C700D5583F /* CopyHTML.mm */,
                                9999108A1F393C8B008AD455 /* Copying.mm */,
                                9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */,
                                2DC4CF761D2D9DD800ECCC94 /* DataDetection.mm */,
                                CEBCA12E1E3A660100C73293 /* OverrideContentSecurityPolicy.mm */,
                                9BDCCD851F7D0B0700009A18 /* PasteImage.mm */,
                                9BDD95561F83683600D20C60 /* PasteRTFD.mm */,
+                               9B2346411F943A2400DB1D23 /* PasteWebArchive.mm */,
                                3FCC4FE41EC4E8520076E37C /* PictureInPictureDelegate.mm */,
                                83BAEE8C1EF4625500DDE894 /* PluginLoadClientPolicies.mm */,
                                C95501BE19AD2FAF0049BE3E /* Preferences.mm */,
                                F4A32ECA1F0642F40047C544 /* contenteditable-in-iframe.html */,
                                A16F66B91C40EA2000BD4D24 /* ContentFiltering.html */,
                                5C2936941D5BFD1900DEAB1E /* CookieMessage.html */,
+                               9B1056421F9047CC00D5583F /* copy-html.html */,
                                9B62630B1F8C2510007EE29B /* copy-url.html */,
                                F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */,
                                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */,
                                CD0BD0A61F79924D001AB2CF /* ContextMenuImgWithVideo.mm in Sources */,
                                5C2936931D5BF70D00DEAB1E /* CookieAcceptPolicy.mm in Sources */,
                                51D1249B1E785425002B2820 /* CookieManager.cpp in Sources */,
+                               9B1F6F781F90558400B55744 /* CopyHTML.mm in Sources */,
                                9999108B1F393C96008AD455 /* Copying.mm in Sources */,
                                9B7A37C41F8AEBA5004AA228 /* CopyURL.mm in Sources */,
                                7CCE7EAC1A411A3400447C4C /* Counters.cpp in Sources */,
                                7CCE7F0A1A411AE600447C4C /* PasteboardNotifications.mm in Sources */,
                                9BDCCD871F7D0B0700009A18 /* PasteImage.mm in Sources */,
                                9BDD95581F83683600D20C60 /* PasteRTFD.mm in Sources */,
+                               9B2346421F943E2700DB1D23 /* PasteWebArchive.mm in Sources */,
                                7C83E0531D0A643A00FEBCF3 /* PendingAPIRequestURL.cpp in Sources */,
                                3FCC4FE51EC4E8520076E37C /* PictureInPictureDelegate.mm in Sources */,
                                7CCE7EAF1A411A3800447C4C /* PlatformUtilities.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyHTML.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyHTML.mm
new file mode 100644 (file)
index 0000000..631fbea
--- /dev/null
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if WK_API_ENABLED && PLATFORM(COCOA)
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/text/WTFString.h>
+
+#if PLATFORM(IOS)
+#include <MobileCoreServices/MobileCoreServices.h>
+#endif
+
+@interface WKWebView ()
+- (void)copy:(id)sender;
+- (void)paste:(id)sender;
+@end
+
+#if PLATFORM(MAC)
+NSString *readHTMLFromPasteboard()
+{
+    return [[NSPasteboard generalPasteboard] stringForType:NSHTMLPboardType];
+}
+#else
+NSString *readHTMLFromPasteboard()
+{
+    id value = [[UIPasteboard generalPasteboard] valueForPasteboardType:(NSString *)kUTTypeHTML];
+    if ([value isKindOfClass:[NSData class]])
+        value = [[[NSString alloc] initWithData:(NSData *)value encoding:NSUTF8StringEncoding] autorelease];
+    ASSERT([value isKindOfClass:[NSString class]]);
+    return (NSString *)value;
+}
+#endif
+
+static RetainPtr<TestWKWebView> createWebViewWithCustomPasteboardDataEnabled()
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto preferences = (WKPreferencesRef)[[webView configuration] preferences];
+    WKPreferencesSetDataTransferItemsEnabled(preferences, true);
+    WKPreferencesSetCustomPasteboardDataEnabled(preferences, true);
+    return webView;
+}
+
+TEST(CopyHTML, Sanitizes)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
+    [webView synchronouslyLoadTestPageNamed:@"copy-html"];
+    [webView stringByEvaluatingJavaScript:@"HTMLToCopy = '<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b>"
+        "<!-- secret-->, world<script>dangerousCode()</script>';"];
+    [webView copy:nil];
+    [webView paste:nil];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"didCopy"].boolValue);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"didPaste"].boolValue);
+    EXPECT_WK_STREQ("<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>",
+        [webView stringByEvaluatingJavaScript:@"pastedHTML"]);
+    String htmlInNativePasteboard = readHTMLFromPasteboard();
+    EXPECT_TRUE(htmlInNativePasteboard.contains("hello"));
+    EXPECT_TRUE(htmlInNativePasteboard.contains(", world"));
+    EXPECT_FALSE(htmlInNativePasteboard.contains("secret"));
+    EXPECT_FALSE(htmlInNativePasteboard.contains("dangerousCode"));
+}
+
+#endif // WK_API_ENABLED && PLATFORM(MAC)
index 5edbf43..d88fa54 100644 (file)
@@ -57,9 +57,18 @@ NSString *readURLFromPasteboard()
 }
 #endif
 
-TEST(CopyURL, ValidURL)
+static RetainPtr<TestWKWebView> createWebViewWithCustomPasteboardDataEnabled()
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto preferences = (WKPreferencesRef)[[webView configuration] preferences];
+    WKPreferencesSetDataTransferItemsEnabled(preferences, true);
+    WKPreferencesSetCustomPasteboardDataEnabled(preferences, true);
+    return webView;
+}
+
+TEST(CopyURL, ValidURL)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
     [webView synchronouslyLoadTestPageNamed:@"copy-url"];
     [webView stringByEvaluatingJavaScript:@"URLToCopy = 'http://webkit.org/b/123';"];
     [webView copy:nil];
@@ -70,12 +79,9 @@ TEST(CopyURL, ValidURL)
     EXPECT_WK_STREQ(@"http://webkit.org/b/123", readURLFromPasteboard());
 }
 
-// FIXME: We should add a mechanism to enable custom pasteboard data in older OS for testing purposes.
-#if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110300) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED > 101300)
-
 TEST(CopyURL, UnescapedURL)
 {
-    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
     [webView synchronouslyLoadTestPageNamed:@"copy-url"];
     [webView stringByEvaluatingJavaScript:@"URLToCopy = 'http://webkit.org/b/\u4F60\u597D;?x=8 + 6';"];
     [webView copy:nil];
@@ -88,7 +94,7 @@ TEST(CopyURL, UnescapedURL)
 
 TEST(CopyURL, MalformedURL)
 {
-    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
     [webView synchronouslyLoadTestPageNamed:@"copy-url"];
     [webView stringByEvaluatingJavaScript:@"URLToCopy = 'bad url';"];
     [webView copy:nil];
@@ -99,7 +105,5 @@ TEST(CopyURL, MalformedURL)
     EXPECT_WK_STREQ(@"", readURLFromPasteboard());
 }
 
-#endif
-
 #endif // WK_API_ENABLED && PLATFORM(MAC)
 
index 2de5aa6..0c54183 100644 (file)
@@ -29,6 +29,9 @@
 
 #import "PlatformUtilities.h"
 #import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKPreferencesRefPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/text/WTFString.h>
 
 @end
 
 #if PLATFORM(MAC)
+void writeRTFToPasteboard(NSData *data)
+{
+    [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeRTF] owner:nil];
+    [[NSPasteboard generalPasteboard] setData:data forType:NSPasteboardTypeRTF];
+}
+
 void writeRTFDToPasteboard(NSData *data)
 {
     [[NSPasteboard generalPasteboard] declareTypes:@[NSRTFDPboardType] owner:nil];
@@ -56,15 +65,56 @@ void writeRTFDToPasteboard(NSData *data)
 - (BOOL)containsAttachments;
 @end
 
+void writeRTFToPasteboard(NSData *data)
+{
+    [[UIPasteboard generalPasteboard] setItems:@[@{ (NSString *)kUTTypeRTF : data}]];
+}
+
 void writeRTFDToPasteboard(NSData *data)
 {
     [[UIPasteboard generalPasteboard] setItems:@[@{ (NSString *)kUTTypeFlatRTFD : data}]];
 }
 #endif
 
-TEST(PasteRTFD, EmptyRTFD)
+static RetainPtr<TestWKWebView> createWebViewWithCustomPasteboardDataEnabled()
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto preferences = (WKPreferencesRef)[[webView configuration] preferences];
+    WKPreferencesSetDataTransferItemsEnabled(preferences, true);
+    WKPreferencesSetCustomPasteboardDataEnabled(preferences, true);
+    return webView;
+}
+
+static RetainPtr<NSAttributedString> createHelloWorldString()
+{
+    auto hello = adoptNS([[NSAttributedString alloc] initWithString:@"hello" attributes:@{ NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle) }]);
+    auto world = adoptNS([[NSAttributedString alloc] initWithString:@", world" attributes:@{ }]);
+    auto string = adoptNS([[NSMutableAttributedString alloc] init]);
+    [string appendAttributedString:hello.get()];
+    [string appendAttributedString:world.get()];
+    return string;
+}
+
+TEST(PasteRTF, ExposesHTMLTypeInDataTransfer)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
+    [webView synchronouslyLoadTestPageNamed:@"paste-rtfd"];
+
+    auto string = createHelloWorldString();
+    writeRTFToPasteboard([string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:@{ }]);
+    [webView paste:nil];
+
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"clipboardData.types.includes('text/html')"]);
+    [webView stringByEvaluatingJavaScript:@"editor.innerHTML = clipboardData.values[0]; editor.focus()"];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('underline')"].boolValue);
+    [webView stringByEvaluatingJavaScript:@"getSelection().modify('move', 'forward', 'lineboundary')"];
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('underline')"].boolValue);
+    EXPECT_WK_STREQ("hello, world", [webView stringByEvaluatingJavaScript:@"editor.textContent"]);
+}
+
+TEST(PasteRTFD, EmptyRTFD)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
     [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html><html><body><div id='editor' contenteditable></div></body></html>"];
 
     writeRTFDToPasteboard([NSData data]);
@@ -72,9 +122,26 @@ TEST(PasteRTFD, EmptyRTFD)
     [webView paste:nil];
 }
 
-TEST(PasteRTFD, ImageElementsUseBlobURL)
+TEST(PasteRTFD, ExposesHTMLTypeInDataTransfer)
 {
-    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
+    [webView synchronouslyLoadTestPageNamed:@"paste-rtfd"];
+
+    auto string = createHelloWorldString();
+    writeRTFDToPasteboard([string RTFDFromRange:NSMakeRange(0, [string length]) documentAttributes:@{ }]);
+    [webView paste:nil];
+
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"clipboardData.types.includes('text/html')"]);
+    [webView stringByEvaluatingJavaScript:@"editor.innerHTML = clipboardData.values[0]; editor.focus()"];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('underline')"].boolValue);
+    [webView stringByEvaluatingJavaScript:@"getSelection().modify('move', 'forward', 'lineboundary')"];
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('underline')"].boolValue);
+    EXPECT_WK_STREQ("hello, world", [webView stringByEvaluatingJavaScript:@"editor.textContent"]);
+}
+
+TEST(PasteRTFD, ImageElementUsesBlobURL)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
     [webView synchronouslyLoadTestPageNamed:@"paste-rtfd"];
 
     auto *pngData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sunset-in-cupertino-200px" ofType:@"png" inDirectory:@"TestWebKitAPI.resources"]];
@@ -90,6 +157,25 @@ TEST(PasteRTFD, ImageElementsUseBlobURL)
     EXPECT_WK_STREQ("blob:", [webView stringByEvaluatingJavaScript:@"url = new URL(imageElement.src).protocol"]);
 }
 
+TEST(PasteRTFD, ImageElementUsesBlobURLInHTML)
+{
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
+    [webView synchronouslyLoadTestPageNamed:@"paste-rtfd"];
+
+    auto *pngData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"sunset-in-cupertino-200px" ofType:@"png" inDirectory:@"TestWebKitAPI.resources"]];
+    auto attachment = adoptNS([[NSTextAttachment alloc] initWithData:pngData ofType:(NSString *)kUTTypePNG]);
+    NSAttributedString *string = [NSAttributedString attributedStringWithAttachment:attachment.get()];
+    NSData *RTFDData = [string RTFDFromRange:NSMakeRange(0, [string length]) documentAttributes:@{ }];
+
+    writeRTFDToPasteboard(RTFDData);
+    [webView paste:nil];
+
+    [webView waitForMessage:@"loaded"];
+    EXPECT_WK_STREQ("[\"text/html\"]", [webView stringByEvaluatingJavaScript:@"JSON.stringify(clipboardData.types)"]);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"imageElement = (new DOMParser).parseFromString(clipboardData.values[0], 'text/html').querySelector('img'); !!imageElement"].boolValue);
+    EXPECT_WK_STREQ("blob:", [webView stringByEvaluatingJavaScript:@"new URL(imageElement.src).protocol"]);
+}
+
 #endif // WK_API_ENABLED && PLATFORM(MAC)
 
 
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm
new file mode 100644 (file)
index 0000000..f4374b4
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if WK_API_ENABLED && PLATFORM(MAC)
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKPreferencesRefPrivate.h>
+#import <WebKit/WKWebViewConfigurationPrivate.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/text/WTFString.h>
+
+@interface WKWebView ()
+- (void)paste:(id)sender;
+@end
+
+static RetainPtr<TestWKWebView> createWebViewWithCustomPasteboardDataEnabled()
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    auto preferences = (WKPreferencesRef)[[webView configuration] preferences];
+    WKPreferencesSetDataTransferItemsEnabled(preferences, true);
+    WKPreferencesSetCustomPasteboardDataEnabled(preferences, true);
+    return webView;
+}
+
+TEST(PasteWebArchive, ExposesHTMLTypeInDataTransfer)
+{
+    auto* url = [NSURL URLWithString:@"file:///some-file.html"];
+    auto* markup = [@"<!DOCTYPE html><html><body><meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b>"
+        "<!-- secret-->, world<script>dangerousCode()</script></html>" dataUsingEncoding:NSUTF8StringEncoding];
+    auto mainResource = adoptNS([[WebResource alloc] initWithData:markup URL:url MIMEType:@"text/html" textEncodingName:@"utf-8" frameName:nil]);
+    auto archive = adoptNS([[WebArchive alloc] initWithMainResource:mainResource.get() subresources:nil subframeArchives:nil]);
+
+    [[NSPasteboard generalPasteboard] declareTypes:@[WebArchivePboardType] owner:nil];
+    [[NSPasteboard generalPasteboard] setData:[archive data] forType:WebArchivePboardType];
+
+    auto webView = createWebViewWithCustomPasteboardDataEnabled();
+    [webView synchronouslyLoadTestPageNamed:@"paste-rtfd"];
+    [webView paste:nil];
+
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"clipboardData.types.includes('text/html')"]);
+    [webView stringByEvaluatingJavaScript:@"html = clipboardData.values[0]"];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"html.includes('hello')"].boolValue);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"html.includes(', world')"].boolValue);
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"html.includes('secret')"].boolValue);
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"html.includes('dangerousCode')"].boolValue);
+
+    [webView stringByEvaluatingJavaScript:@"editor.innerHTML = html; editor.focus(); getSelection().setPosition(editor, 0)"];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('bold')"].boolValue);
+    [webView stringByEvaluatingJavaScript:@"getSelection().modify('move', 'forward', 'lineboundary')"];
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"document.queryCommandState('bold')"].boolValue);
+    EXPECT_WK_STREQ("hello, world", [webView stringByEvaluatingJavaScript:@"editor.textContent"]);
+}
+
+#endif // WK_API_ENABLED && PLATFORM(MAC)
+
+
+
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-html.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-html.html
new file mode 100644 (file)
index 0000000..419c8b0
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="editor" oncopy="copy(event)" onpaste="paste(event)" contenteditable>some text</div>
+<script>
+
+var HTMLToCopy = null;
+var didCopy = false;
+var didPaste = false;
+
+function copy(event) {
+    if (HTMLToCopy) {
+        event.clipboardData.setData('text/html', HTMLToCopy);
+        event.preventDefault();
+    }
+    didCopy = true;
+}
+
+function paste(event) {
+    pastedHTML = event.clipboardData.getData('text/html');
+    didPaste = true;
+}
+
+editor.focus();
+</script>
+</body>
+</html>
index 58b6098..2bdb24f 100644 (file)
@@ -6,7 +6,13 @@
 const editor = document.getElementById('editor');
 editor.focus();
 
+var clipboardData = {};
 editor.addEventListener('paste', (event) => {
+    clipboardData.types = Array.from(event.clipboardData.types);
+    clipboardData.items = Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}));
+    clipboardData.values = clipboardData.types.map((type) => event.clipboardData.getData(type));
+    clipboardData.files = Array.from(event.clipboardData.files);
+
     setTimeout(() => {
        let img = document.querySelector('img');
        if (img.complete)
index 548f387..85a11a4 100644 (file)
@@ -1764,6 +1764,46 @@ TEST(DataInteractionTests, DataTransferSetDataInvalidURL)
     });
 }
 
+TEST(DataInteractionTests, DataTransferSanitizeHTML)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"dump-datatransfer-types"];
+    auto simulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+
+    [webView stringByEvaluatingJavaScript:@"select(rich)"];
+    [webView stringByEvaluatingJavaScript:@"customData = { 'text/html' : '<meta content=\"secret\">"
+        "<b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>' }"];
+    [webView stringByEvaluatingJavaScript:@"writeCustomData = true"];
+
+    __block bool done = false;
+    [simulator.get() setOverridePerformDropBlock:^NSArray<UIDragItem *> *(id <UIDropSession> session)
+    {
+        EXPECT_EQ(1UL, session.items.count);
+        auto *item = session.items[0].itemProvider;
+        EXPECT_TRUE([item.registeredTypeIdentifiers containsObject:(NSString *)kUTTypeHTML]);
+        [item loadDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML completionHandler:^(NSData *data, NSError *error) {
+            NSString *markup = [[[NSString alloc] initWithData:(NSData *)data encoding:NSUTF8StringEncoding] autorelease];
+            EXPECT_TRUE([markup containsString:@"hello"]);
+            EXPECT_TRUE([markup containsString:@", world"]);
+            EXPECT_FALSE([markup containsString:@"secret"]);
+            EXPECT_FALSE([markup containsString:@"dangerousCode"]);
+            done = true;
+        }];
+        return session.items;
+    }];
+    [simulator runFrom:CGPointMake(50, 225) to:CGPointMake(50, 375)];
+
+    checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{
+        @"dragover": @{
+            @"text/html": @"",
+        },
+        @"drop": @{
+            @"text/html": @"<meta content=\"secret\"><b onmouseover=\"dangerousCode()\">hello</b><!-- secret-->, world<script>dangerousCode()</script>",
+        }
+    });
+    TestWebKitAPI::Util::run(&done);
+}
+
 #endif // __IPHONE_OS_VERSION_MIN_REQUIRED >= 110300
 
 } // namespace TestWebKitAPI