Title: [223678] trunk
Revision
223678
Author
[email protected]
Date
2017-10-18 22:44:33 -0700 (Wed, 18 Oct 2017)

Log Message

Don't expose raw HTML in pasteboard to the web content
https://bugs.webkit.org/show_bug.cgi?id=178422
Source/WebCore:

<rdar://problem/34567052>

Reviewed by Wenson Hsieh.

This patch enables HTML sanitization added in r223440 when WebKit pastes & concludes edit drag as opposed to
just when dataTransfer.get is used. This is important to avoid leaking privacy sensitive information such as
local file paths and pasting potentially harmful content such as scripts in event handler serialized by
WebKit prior to r223462. In addition, we start using blob URLs in the pasted content instead of retaining
the original URL and overriding the document loader like r222839 for RTFD and r222119 for image files.

To do this, a new superclass FrameWebContentReader of PasteboardWebContentReader and WebContentMarkupReader
is introduced, and helper functions are extracted out of WebContentMarkupReader in WebContentReaderCocoa.mm
to be also used in WebContentReader.

Tests: http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html
       http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html
       http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html
       PasteWebArchive.SanitizesHTML

* editing/WebContentReader.cpp:
(WebCore::FrameWebContentReader::shouldSanitize const): Moved from WebContentMarkupReader.
* editing/WebContentReader.h:
(WebCore::FrameWebContentReader): Added to share code between WebContentReader and WebContentMarkupReader.
(WebCore::FrameWebContentReader::FrameWebContentReader): Added.
* editing/cocoa/EditorCocoa.mm:
(WebCore::Editor::writeSelectionToPasteboard): Store the content's origin in the pasteboard so that we can
avoid sanitizing the content when pasting into the same document. This is important since converting all URLs
into blob URLs would break editors on the Web which tracks images, etc... in the content using URLs.
(WebCore::Editor::writeSelection): Ditto.
* editing/cocoa/WebContentReaderCocoa.mm:
(WebCore::MarkupAndArchive): Replaced FragmentAndArchive. Now returns the markup string in the archive
instead of the parsed fragment.
(WebCore::extractMarkupAndArchive): Renamed from createFragmentFromWebArchive. Now returns the markup string.
(WebCore::sanitizeMarkupWithArchive): Extracted out of WebContentMarkupReader::readWebArchive to share code
between WebContentReader and WebContentMarkupReader, and added the code to handle subframes recursively.
As inefficient as this code is, we can't delay the conversion of subframes' marksup until later time since
the main frame's markup would contain blob URLs to refer to those subframes. 
(WebCore::WebContentReader::readWebArchive): Use sanitizeMarkupWithArchive when shouldSanitize() is true.
Don't add the subresources to the document loader when the content will be loaded into the same origin since
subresouces are mostly likely available in the document anyway.
(WebCore::WebContentMarkupReader::readWebArchive):
* platform/Pasteboard.h:
(WebCore::PasteboardWebContent): Added contentOrigin.
* platform/PasteboardWriterData.h:
(WebCore::PasteboardWriterData): Ditto.
* platform/ios/PasteboardIOS.mm:
(WebCore::Pasteboard::read): Read the origin before branching out to readRespectingUTIFidelities.
* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::PlatformPasteboard::write): Record the content origin into the pasteboard.
* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::write): Ditto.
* platform/mac/PasteboardWriter.mm:
(WebCore::createPasteboardWriter): Ditto.

Source/WebKit:


Reviewed by Wenson Hsieh.

Encode & decode the origin string of the copied content written into the system pasteboard.

* Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<PasteboardWebContent>::encode):
(IPC::ArgumentCoder<PasteboardWebContent>::decode):

Tools:


Reviewed by Wenson Hsieh.

Added a test case for sanitizing web archive in the system pasteboard to strip privacy sensitive information
such as local file paths and potentially harmful scripts like event handlers serialized by WebKit prior to r223462.

* TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm:
(PasteWebArchive.SanitizesHTML):

LayoutTests:


Reviewed by Wenson Hsieh.

Added tests to copy & paste web contents within the same origin as well as cross origin.

* TestExpectations:
* editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt: Now contains DOCTYPE.
* editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Ditto.
* editing/pasteboard/onpaste-text-html-expected.txt: Rebaselined as now inline styles are stripped.
* editing/pasteboard/onpaste-text-html.html: Strip away the inline style data since they differ on each platform.
* http/tests/misc/copy-resolves-urls-expected.txt:
* http/tests/misc/copy-resolves-urls.html: Now uses blob URL for the pasted image as expected.
* http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin-expected.txt: Added.
* http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html: Added.
* http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin-expected.txt: Added.
* http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html: Added.
* http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin-expected.txt: Added.
* http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html: Added.
* http/tests/security/clipboard/resources/content-to-copy.html: Added.
* http/tests/security/clipboard/resources/subdirectory/paste-html.html: Added.
* platform/ios/TestExpectations: Unskip tests that have started passing.
* platform/mac-wk1/TestExpectations: Unskip the drag & drop test which only works in Mac WK1.
* platform/win/TestExpectations: Skip the newly added tests since we don't support custom pasteboard
data on Windows port.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (223677 => 223678)


--- trunk/LayoutTests/ChangeLog	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/ChangeLog	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1,3 +1,32 @@
+2017-10-18  Ryosuke Niwa  <[email protected]>
+
+        Don't expose raw HTML in pasteboard to the web content
+        https://bugs.webkit.org/show_bug.cgi?id=178422
+
+        Reviewed by Wenson Hsieh.
+
+        Added tests to copy & paste web contents within the same origin as well as cross origin.
+
+        * TestExpectations:
+        * editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt: Now contains DOCTYPE.
+        * editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt: Ditto.
+        * editing/pasteboard/onpaste-text-html-expected.txt: Rebaselined as now inline styles are stripped.
+        * editing/pasteboard/onpaste-text-html.html: Strip away the inline style data since they differ on each platform.
+        * http/tests/misc/copy-resolves-urls-expected.txt:
+        * http/tests/misc/copy-resolves-urls.html: Now uses blob URL for the pasted image as expected.
+        * http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin-expected.txt: Added.
+        * http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html: Added.
+        * http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin-expected.txt: Added.
+        * http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html: Added.
+        * http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin-expected.txt: Added.
+        * http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html: Added.
+        * http/tests/security/clipboard/resources/content-to-copy.html: Added.
+        * http/tests/security/clipboard/resources/subdirectory/paste-html.html: Added.
+        * platform/ios/TestExpectations: Unskip tests that have started passing.
+        * platform/mac-wk1/TestExpectations: Unskip the drag & drop test which only works in Mac WK1.
+        * platform/win/TestExpectations: Skip the newly added tests since we don't support custom pasteboard
+        data on Windows port.
+
 2017-10-18  Chris Dumez  <[email protected]>
 
         Implement ServiceWorkerRegistration.scope / updateViaCache

Modified: trunk/LayoutTests/TestExpectations (223677 => 223678)


--- trunk/LayoutTests/TestExpectations	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/TestExpectations	2017-10-19 05:44:33 UTC (rev 223678)
@@ -76,6 +76,7 @@
 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 ]
+http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-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 ]

Modified: trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt (223677 => 223678)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-drop-rich-text-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -5,7 +5,7 @@
         "text/plain": ""
     },
     "drop": {
-        "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/html": "<!DOCTYPE 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/plain": "Rich text"
     }
 }

Modified: trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt (223677 => 223678)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-rich-text-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1,7 +1,7 @@
 Rich text
 {
     "paste": {
-        "text/html": "<span style=\"...\"\">Rich text</span>",
+        "text/html": "<!DOCTYPE html><span style=\"...\"\">Rich text</span>",
         "text/plain": "Rich text"
     }
 }

Modified: trunk/LayoutTests/editing/pasteboard/onpaste-text-html-expected.txt (223677 => 223678)


--- trunk/LayoutTests/editing/pasteboard/onpaste-text-html-expected.txt	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/editing/pasteboard/onpaste-text-html-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -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-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>
+CONSOLE MESSAGE: line 23: text/html: <span style="...">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

Modified: trunk/LayoutTests/editing/pasteboard/onpaste-text-html.html (223677 => 223678)


--- trunk/LayoutTests/editing/pasteboard/onpaste-text-html.html	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/editing/pasteboard/onpaste-text-html.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -20,7 +20,7 @@
 {
     console.log("text/plain: " + ev.clipboardData.getData("text/plain"));
     // Remove the font name because it varies depending on the platform.
-    console.log("text/html: " + removeFontName(ev.clipboardData.getData("text/html")));
+    console.log("text/html: " + ev.clipboardData.getData("text/html").replace(/style="[^"]+"/, 'style="..."'));
     if (ev.clipboardData.getData("text/html") != undefined)
         document.getElementById("results").innerHTML = "PASS";
 }

Modified: trunk/LayoutTests/fast/events/ondrop-text-html-expected.txt (223677 => 223678)


--- trunk/LayoutTests/fast/events/ondrop-text-html-expected.txt	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/fast/events/ondrop-text-html-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -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-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>
+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>
 This test verifies that we can get text/html from the drag object during an ondrop event. This test requires DRT.
 PASS

Modified: trunk/LayoutTests/http/tests/misc/copy-resolves-urls-expected.txt (223677 => 223678)


--- trunk/LayoutTests/http/tests/misc/copy-resolves-urls-expected.txt	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/http/tests/misc/copy-resolves-urls-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -2,4 +2,4 @@
 
 link
 link
-<a href="" src=""
+<a href="" src=""

Modified: trunk/LayoutTests/http/tests/misc/copy-resolves-urls.html (223677 => 223678)


--- trunk/LayoutTests/http/tests/misc/copy-resolves-urls.html	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/http/tests/misc/copy-resolves-urls.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -42,13 +42,15 @@
 {
     var pasteHere = document.getElementById("pastehere");
     var results = document.getElementById("results");
-    results.appendChild(document.createTextNode(pasteHere.innerHTML));
+    results.appendChild(document.createTextNode(pasteHere.innerHTML.replace(/blob\:http\:\/\/localhost\:8080\/[a-z0-9\-]+/, 'blob://localhost:8080/...')));
     if (window.testRunner)
         testRunner.notifyDone();
 }
 
-if (document.location.search == "?paste")
-    doPaste();
-else
-    test();
+window._onload_ = () => {
+    if (document.location.search == "?paste")
+        doPaste();
+    else
+        test();
+}
 </script>

Added: trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin-expected.txt (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,24 @@
+This tests copying and pasting HTML by the default action. WebKit should sanitize the HTML across origin.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+html in DataTransfer
+PASS html.includes("hello") is true
+PASS fragment = (new DOMParser).parseFromString(html, "text/html"); img = fragment.querySelector("img"); !!img is true
+PASS new URL(img.src).protocol is "blob:"
+PASS new URL(fragment.querySelector(".same-origin-frame").src).protocol is "blob:"
+PASS new URL(fragment.querySelector(".cross-origin-frame").src).protocol is "blob:"
+PASS frames.length is 2
+PASS new URL(frames[0].src).protocol is "blob:"
+PASS frames[0].canAccessContentDocument is true
+PASS frames[0].hasImage is true
+PASS frames[0].imageWidth is 80
+PASS new URL(frames[1].src).protocol is "blob:"
+PASS frames[1].canAccessContentDocument is true
+PASS frames[1].hasImage is true
+PASS frames[1].imageWidth is 80
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<p id="description"></p>
+<div id="console"></div>
+<div id="container">
+<button _onclick_="runTest()">Copy</button>
+<div><br></div>
+<div id="source" contenteditable>
+    hello, <meta content="some secret"><!-- secret -->
+    <img _onclick_="dangerousCode()" src=""
+    <iframe class="same-origin-frame" src="" width=80 height=80></iframe>
+    <iframe class="cross-origin-frame" src="" width="100" height="100"></iframe>
+</div>
+<iframe id="destinationFrame" src=""
+</div>
+<script>
+
+description('This tests copying and pasting HTML by the default action. WebKit should sanitize the HTML across origin.');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    getSelection().removeAllRanges();
+    setTimeout(() => {
+        destinationFrame.postMessage({type: 'paste'}, '*');
+    }, 0);
+}
+
+window._onmessage_ = function (event) {
+    if (event.data.type == 'pasted') {
+        html = event.data.html;
+        debug('html in DataTransfer');
+        shouldBeTrue('html.includes("hello")');
+        shouldBeTrue('fragment = (new DOMParser).parseFromString(html, "text/html"); img = fragment.querySelector("img"); !!img');
+        shouldBeEqualToString('new URL(img.src).protocol', 'blob:');
+        shouldBeEqualToString('new URL(fragment.querySelector(".same-origin-frame").src).protocol', 'blob:');
+        shouldBeEqualToString('new URL(fragment.querySelector(".cross-origin-frame").src).protocol', 'blob:');
+    } else if (event.data.type == 'checkedState') {
+        frames = event.data.frames;
+        shouldBe('frames.length', '2');
+        shouldBeEqualToString('new URL(frames[0].src).protocol', 'blob:');
+        shouldBeTrue('frames[0].canAccessContentDocument');
+        shouldBeTrue('frames[0].hasImage');
+        shouldBe('frames[0].imageWidth', '80');
+        shouldBeEqualToString('new URL(frames[1].src).protocol', 'blob:');
+        shouldBeTrue('frames[1].canAccessContentDocument');
+        shouldBeTrue('frames[1].hasImage');
+        shouldBe('frames[1].imageWidth', '80');
+        if (window.testRunner)
+            container.remove();
+        finishJSTest();
+    }
+}
+
+if (window.testRunner)
+    window._onload_ = runTest;
+
+setTimeout(finishJSTest, 3000);
+
+</script>
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin-expected.txt (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,24 @@
+CONSOLE MESSAGE: line 233: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+CONSOLE MESSAGE: line 233: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+This tests copying and pasting HTML by the default action. WebKit should not sanitize the HTML in the same document.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS html = event.clipboardData.getData("text/html"); html.includes("hello") is true
+PASS destination.innerHTML = html; img = destination.querySelector("img"); !!img is true
+PASS new URL(img.src).protocol is "http:"
+PASS html.includes("http://localhost:8000/security/clipboard/resources/content-to-copy.html") is true
+PASS html.includes("secret") is false
+destination.innerHTML = ""
+PASS destination.textContent.includes("hello") is true
+PASS destination.innerHTML.includes("secret") is false
+PASS destination.innerHTML.includes("dangerousCode") is false
+PASS destination.querySelector("img"); !!img is true
+PASS new URL(img.src).protocol is "http:"
+PASS source.querySelector("iframe").contentDocument is null
+PASS destination.querySelector("iframe").contentDocument is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<p id="description"></p>
+<div id="console"></div>
+<div id="container">
+<button _onclick_="runTest()">Copy</button>
+<div><br></div>
+<div id="source" contenteditable>
+    hello, <meta content="some secret"><!-- secret -->
+    <img _onclick_="dangerousCode()" src=""
+    <iframe src=""
+</div>
+<div id="destination" _onpaste_="doPaste(event)" contenteditable>Paste here</div>
+</div>
+<script>
+
+description('This tests copying and pasting HTML by the default action. WebKit should not sanitize the HTML in the same document.');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    setTimeout(() => {
+        document.getElementById('destination').focus();
+        document.execCommand('selectAll');
+        if (window.testRunner)
+            document.execCommand('paste');
+    }, 0);
+}
+
+function doPaste(event) {
+    shouldBeTrue('html = event.clipboardData.getData("text/html"); html.includes("hello")');
+    shouldBeTrue('destination.innerHTML = html; img = destination.querySelector("img"); !!img');
+    shouldBeEqualToString('new URL(img.src).protocol', 'http:');
+    shouldBeTrue('html.includes("http://localhost:8000/security/clipboard/resources/content-to-copy.html")');
+    shouldBeFalse('html.includes("secret")');
+    evalAndLog('destination.innerHTML = ""');
+
+    const observer = new MutationObserver((recordList) => {
+        for (const record of recordList) {
+            for (const node of record.addedNodes) {
+                if (node.nodeValue === null)
+                    continue;
+                if (node.nodeValue.includes('secret'))
+                    testFailed(`Saw secret in a node ${node}`);
+                if (node.nodeValue.includes('dangerousCode'))
+                    testFailed(`Saw dangerous code in a node ${node}`);
+            }
+        }
+    });
+    observer.observe(destination, {childList: true, subtree: true});
+
+    window._onmessage_ = checkFrameAccess;
+}
+
+function checkFrameAccess() {
+    shouldBeTrue('destination.textContent.includes("hello")');
+    shouldBeFalse('destination.innerHTML.includes("secret")');
+    shouldBeFalse('destination.innerHTML.includes("dangerousCode")');
+    shouldBeTrue('destination.querySelector("img"); !!img');
+    shouldBeEqualToString('new URL(img.src).protocol', 'http:');
+    shouldBeNull('source.querySelector("iframe").contentDocument');
+    shouldBeNull('destination.querySelector("iframe").contentDocument');
+    container.remove();
+    finishJSTest();
+}
+
+if (window.testRunner)
+    window._onload_ = runTest;
+
+var successfullyParsed = true;
+
+</script>
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin-expected.txt (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin-expected.txt	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,24 @@
+CONSOLE MESSAGE: line 233: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+This tests draggin and dropping HTML by the default action. WebKit should not sanitize the HTML in the same document.
+To manually test, drag & drop the content in the block above to the blue box on the right.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS html = event.dataTransfer.getData("text/html"); html.includes("Drag this,") is true
+PASS destination.innerHTML = html; img = destination.querySelector("img"); !!img is true
+PASS new URL(img.src).protocol is "http:"
+PASS html.includes("http://localhost:8000/security/clipboard/resources/content-to-copy.html") is true
+PASS html.includes("secret") is false
+destination.innerHTML = ""
+PASS source.innerHTML is ""
+PASS destination.textContent.includes("Drag this,") is true
+PASS destination.innerHTML.includes("secret") is false
+PASS destination.innerHTML.includes("dangerousCode") is false
+PASS destination.querySelector("img"); !!img is true
+PASS new URL(img.src).protocol is "http:"
+PASS destination.querySelector("iframe").contentDocument is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<div id="container">
+<style>
+    #container { position: relative; }
+    #container > div { position: relative; border: solid 1px black; width: 200px; height: 200px; }
+</style>
+<div id="source" contenteditable>
+    Drag this, <meta content="some secret"><!-- secret -->
+    <img _onclick_="dangerousCode()" src=""
+    <iframe src="" width=100 height=100></iframe>
+</div>
+<div id="destination" style="position: absolute; top: 0px; left: 220px; border-color: blue;" _ondrop_="doDrop(event)" contenteditable></div>
+</div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+
+description('This tests draggin and dropping HTML by the default action. WebKit should not sanitize the HTML in the same document.<br>'
+    + 'To manually test, drag & drop the content in the block above to the blue box on the right.');
+jsTestIsAsync = true;
+
+window._onload_ = () => {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    if (window.testRunner) {
+        internals.settings.setCustomPasteboardDataEnabled(true);
+        const sourceRect = source.getBoundingClientRect();
+        const destinationRect = destination.getBoundingClientRect();
+        eventSender.mouseMoveTo(sourceRect.x + 10, sourceRect.y + 10);
+        eventSender.mouseDown();
+        eventSender.leapForward(500);
+        eventSender.mouseMoveTo(destinationRect.x + 10, destinationRect.y + 10);
+        eventSender.mouseUp();
+    }
+}
+
+function doDrop(event) {
+    shouldBeTrue('html = event.dataTransfer.getData("text/html"); html.includes("Drag this,")');
+    shouldBeTrue('destination.innerHTML = html; img = destination.querySelector("img"); !!img');
+    shouldBeEqualToString('new URL(img.src).protocol', 'http:');
+    shouldBeTrue('html.includes("http://localhost:8000/security/clipboard/resources/content-to-copy.html")');
+    shouldBeFalse('html.includes("secret")');
+    evalAndLog('destination.innerHTML = ""');
+
+    const observer = new MutationObserver((recordList) => {
+        for (const record of recordList) {
+            for (const node of record.addedNodes) {
+                if (node.nodeValue === null)
+                    continue;
+                if (node.nodeValue.includes('secret'))
+                    testFailed(`Saw secret in a node ${node}`);
+                if (node.nodeValue.includes('dangerousCode'))
+                    testFailed(`Saw dangerous code in a node ${node}`);
+            }
+        }
+    });
+    observer.observe(destination, {childList: true, subtree: true});
+
+    window._onmessage_ = checkFrameAccess;
+}
+
+function checkFrameAccess() {
+    shouldBeEqualToString('source.innerHTML', '');
+    shouldBeTrue('destination.textContent.includes("Drag this,")');
+    shouldBeFalse('destination.innerHTML.includes("secret")');
+    shouldBeFalse('destination.innerHTML.includes("dangerousCode")');
+    shouldBeTrue('destination.querySelector("img"); !!img');
+    shouldBeEqualToString('new URL(img.src).protocol', 'http:');
+    shouldBeNull('destination.querySelector("iframe").contentDocument');
+
+    if (window.testRunner)
+        container.remove();
+    finishJSTest();
+}
+
+var successfullyParsed = true;
+
+</script>
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/security/clipboard/resources/content-to-copy.html (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/resources/content-to-copy.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/resources/content-to-copy.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src=""
+<script>
+top.postMessage({type: 'contentLoaded'}, '*');
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/security/clipboard/resources/subdirectory/paste-html.html (0 => 223678)


--- trunk/LayoutTests/http/tests/security/clipboard/resources/subdirectory/paste-html.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/security/clipboard/resources/subdirectory/paste-html.html	2017-10-19 05:44:33 UTC (rev 223678)
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+_onmessage_ = function (event) {
+    if (event.data.type == 'paste') {
+        document.body.focus();
+        document.execCommand('selectAll');
+        if (window.testRunner)
+            document.execCommand('paste');
+    }
+}
+
+let frames = [];
+function doPaste(event) {
+    top.postMessage({type: 'pasted', html: event.clipboardData.getData('text/html')}, '*');
+    setTimeout(() => {
+        frames = Array.from(document.body.querySelectorAll('iframe'));
+
+        Promise.all(frames.map((frame) => {
+            return new Promise((resolve) => {
+                const waitForImage = () => {
+                    const img = frame.contentDocument.querySelector('img');
+                    if (img && !img.complete)
+                        img._onload_ = resolve;
+                    else
+                        resolve();
+                }
+
+                if (frame.contentDocument && frame.contentDocument.body.innerHTML == '')
+                    frame._onload_ = waitForImage;
+                else
+                    waitForImage();
+
+            });
+        })).then(checkState);
+    }, 0);
+}
+
+function checkState() {
+    top.postMessage({
+        type: 'checkedState',
+        html: document.body.innerHTML,
+        frames: frames.map((frame) => {
+            const img = frame.contentDocument ? frame.contentDocument.querySelector('img') : null;
+            return {
+                src: frame.src,
+                class: frame.className,
+                canAccessContentDocument: !!frame.contentDocument,
+                hasImage: !!img,
+                imageWidth: img ? img.width : null,
+            }
+        })}, '*');
+}
+
+</script>
+<body _onpaste_="doPaste(event)" contenteditable>
+Paste here.
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios/TestExpectations (223677 => 223678)


--- trunk/LayoutTests/platform/ios/TestExpectations	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/platform/ios/TestExpectations	2017-10-19 05:44:33 UTC (rev 223678)
@@ -2087,8 +2087,6 @@
 editing/pasteboard/merge-end-5.html [ Failure ]
 editing/pasteboard/merge-end-list-2.html [ Failure ]
 editing/pasteboard/merge-end-table-2.html [ Failure ]
-editing/pasteboard/onpaste-text-html-types.html [ Failure ]
-editing/pasteboard/onpaste-text-html.html [ Failure ]
 editing/pasteboard/page-zoom.html [ Failure ]
 editing/pasteboard/paste-before-tab-span.html [ Failure ]
 editing/pasteboard/paste-blockquote-3.html [ Failure ]
@@ -2099,7 +2097,6 @@
 editing/pasteboard/paste-sanitize-crash-1.html [ Failure ]
 editing/pasteboard/paste-sanitize-crash-2.html [ Failure ]
 editing/pasteboard/paste-text-events.html [ Failure ]
-editing/pasteboard/pasting-empty-html-falls-back-to-text.html [ Failure ]
 editing/pasteboard/smart-paste-001.html [ Failure ]
 editing/pasteboard/smart-paste-002.html [ Failure ]
 editing/pasteboard/smart-paste-003.html [ Failure ]

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (223677 => 223678)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2017-10-19 05:44:33 UTC (rev 223678)
@@ -17,6 +17,7 @@
 editing/pasteboard/drag-end-crash-accessing-item-list.html [ Pass ]
 editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Pass ]
 editing/pasteboard/data-transfer-items-drop-file.html [ Pass ]
+http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html [ Pass ]
 
 #//////////////////////////////////////////////////////////////////////////////////////////
 # End platform-specific directories.

Modified: trunk/LayoutTests/platform/win/TestExpectations (223677 => 223678)


--- trunk/LayoutTests/platform/win/TestExpectations	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/LayoutTests/platform/win/TestExpectations	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1135,8 +1135,11 @@
 [ Debug ] editing/selection/4975120.html [ Skip ] # Debug Assertion
 
 # Custom pasteboard data is not supported on Windows.
+editing/pasteboard/clipboard-customData.html [ Skip ]
 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 ]
+http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html [ Skip ]
+http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.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
@@ -1154,8 +1157,6 @@
 editing/pasteboard/data-transfer-items-drag-drop-file.html [ Skip ]
 editing/pasteboard/data-transfer-items-drag-drop-entry.html [ Skip ]
 editing/pasteboard/data-transfer-items-drag-drop-string.html [ Skip ]
-# TODO Custom MIME type support in DataTransfer is not yet implemented.
-editing/pasteboard/clipboard-customData.html [ Skip ]
 # TODO Windows does not have global selection.
 editing/pasteboard/paste-global-selection.html [ Skip ]
 webkit.org/b/116564 editing/pasteboard/copy-without-selection.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (223677 => 223678)


--- trunk/Source/WebCore/ChangeLog	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/ChangeLog	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1,3 +1,61 @@
+2017-10-18  Ryosuke Niwa  <[email protected]>
+
+        Don't expose raw HTML in pasteboard to the web content
+        https://bugs.webkit.org/show_bug.cgi?id=178422
+        <rdar://problem/34567052>
+
+        Reviewed by Wenson Hsieh.
+
+        This patch enables HTML sanitization added in r223440 when WebKit pastes & concludes edit drag as opposed to
+        just when dataTransfer.get is used. This is important to avoid leaking privacy sensitive information such as
+        local file paths and pasting potentially harmful content such as scripts in event handler serialized by
+        WebKit prior to r223462. In addition, we start using blob URLs in the pasted content instead of retaining
+        the original URL and overriding the document loader like r222839 for RTFD and r222119 for image files.
+
+        To do this, a new superclass FrameWebContentReader of PasteboardWebContentReader and WebContentMarkupReader
+        is introduced, and helper functions are extracted out of WebContentMarkupReader in WebContentReaderCocoa.mm
+        to be also used in WebContentReader.
+
+        Tests: http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-across-origin.html
+               http/tests/security/clipboard/copy-paste-html-cross-origin-iframe-in-same-origin.html
+               http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html
+               PasteWebArchive.SanitizesHTML
+
+        * editing/WebContentReader.cpp:
+        (WebCore::FrameWebContentReader::shouldSanitize const): Moved from WebContentMarkupReader.
+        * editing/WebContentReader.h:
+        (WebCore::FrameWebContentReader): Added to share code between WebContentReader and WebContentMarkupReader.
+        (WebCore::FrameWebContentReader::FrameWebContentReader): Added.
+        * editing/cocoa/EditorCocoa.mm:
+        (WebCore::Editor::writeSelectionToPasteboard): Store the content's origin in the pasteboard so that we can
+        avoid sanitizing the content when pasting into the same document. This is important since converting all URLs
+        into blob URLs would break editors on the Web which tracks images, etc... in the content using URLs.
+        (WebCore::Editor::writeSelection): Ditto.
+        * editing/cocoa/WebContentReaderCocoa.mm:
+        (WebCore::MarkupAndArchive): Replaced FragmentAndArchive. Now returns the markup string in the archive
+        instead of the parsed fragment.
+        (WebCore::extractMarkupAndArchive): Renamed from createFragmentFromWebArchive. Now returns the markup string.
+        (WebCore::sanitizeMarkupWithArchive): Extracted out of WebContentMarkupReader::readWebArchive to share code
+        between WebContentReader and WebContentMarkupReader, and added the code to handle subframes recursively.
+        As inefficient as this code is, we can't delay the conversion of subframes' marksup until later time since
+        the main frame's markup would contain blob URLs to refer to those subframes. 
+        (WebCore::WebContentReader::readWebArchive): Use sanitizeMarkupWithArchive when shouldSanitize() is true.
+        Don't add the subresources to the document loader when the content will be loaded into the same origin since
+        subresouces are mostly likely available in the document anyway.
+        (WebCore::WebContentMarkupReader::readWebArchive):
+        * platform/Pasteboard.h:
+        (WebCore::PasteboardWebContent): Added contentOrigin.
+        * platform/PasteboardWriterData.h:
+        (WebCore::PasteboardWriterData): Ditto.
+        * platform/ios/PasteboardIOS.mm:
+        (WebCore::Pasteboard::read): Read the origin before branching out to readRespectingUTIFidelities.
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::PlatformPasteboard::write): Record the content origin into the pasteboard.
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::write): Ditto.
+        * platform/mac/PasteboardWriter.mm:
+        (WebCore::createPasteboardWriter): Ditto.
+
 2017-10-18  Sam Weinig  <[email protected]>
 
         Another attempt to fix the windows build.

Modified: trunk/Source/WebCore/editing/WebContentReader.cpp (223677 => 223678)


--- trunk/Source/WebCore/editing/WebContentReader.cpp	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/editing/WebContentReader.cpp	2017-10-19 05:44:33 UTC (rev 223678)
@@ -39,9 +39,10 @@
         fragment->appendChild(newFragment.get());
 }
 
-bool WebContentMarkupReader::shouldSanitize() const
+bool FrameWebContentReader::shouldSanitize() const
 {
-    return frame.document() && frame.document()->originIdentifierForPasteboard() != contentOrigin;
+    ASSERT(frame.document());
+    return frame.document()->originIdentifierForPasteboard() != contentOrigin;
 }
 
 }

Modified: trunk/Source/WebCore/editing/WebContentReader.h (223677 => 223678)


--- trunk/Source/WebCore/editing/WebContentReader.h	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/editing/WebContentReader.h	2017-10-19 05:44:33 UTC (rev 223678)
@@ -33,9 +33,21 @@
 
 class ArchiveResource;
 
-class WebContentReader final : public PasteboardWebContentReader {
+class FrameWebContentReader : public PasteboardWebContentReader {
 public:
     Frame& frame;
+
+    FrameWebContentReader(Frame& frame)
+        : frame(frame)
+    {
+    }
+
+protected:
+    bool shouldSanitize() const;
+};
+
+class WebContentReader final : public FrameWebContentReader {
+public:
     Range& context;
     const bool allowPlainText;
 
@@ -43,7 +55,7 @@
     bool madeFragmentFromPlainText;
 
     WebContentReader(Frame& frame, Range& context, bool allowPlainText)
-        : frame(frame)
+        : FrameWebContentReader(frame)
         , context(context)
         , allowPlainText(allowPlainText)
         , madeFragmentFromPlainText(false)
@@ -65,19 +77,16 @@
     bool readPlainText(const String&) override;
 };
 
-class WebContentMarkupReader final : public PasteboardWebContentReader {
+class WebContentMarkupReader final : public FrameWebContentReader {
 public:
-    Frame& frame;
     String markup;
 
     explicit WebContentMarkupReader(Frame& frame)
-        : frame(frame)
+        : FrameWebContentReader(frame)
     {
     }
 
 private:
-    bool shouldSanitize() const;
-
 #if PLATFORM(COCOA)
     bool readWebArchive(SharedBuffer&) override;
     bool readFilenames(const Vector<String>&) override { return false; }

Modified: trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm (223677 => 223678)


--- trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -156,6 +156,7 @@
     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
 
     PasteboardWebContent content;
+    content.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
     content.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
@@ -173,6 +174,7 @@
     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
 
     PasteboardWriterData::WebContent webContent;
+    webContent.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
     webContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
     webContent.dataInWebArchiveFormat = selectionInWebArchiveFormat();
     webContent.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;

Modified: trunk/Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm (223677 => 223678)


--- trunk/Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -28,6 +28,7 @@
 
 #import "ArchiveResource.h"
 #import "Blob.h"
+#import "BlobURL.h"
 #import "CachedResourceLoader.h"
 #import "DOMURL.h"
 #import "Document.h"
@@ -37,10 +38,12 @@
 #import "FrameLoader.h"
 #import "FrameLoaderClient.h"
 #import "HTMLBodyElement.h"
+#import "HTMLIframeElement.h"
 #import "HTMLImageElement.h"
 #import "LegacyWebArchive.h"
 #import "MainFrame.h"
 #import "Page.h"
+#import "PublicURLManager.h"
 #import "Settings.h"
 #import "SocketProvider.h"
 #import "WebArchiveResourceFromNSAttributedString.h"
@@ -181,12 +184,13 @@
     return WTFMove(fragmentAndResources.fragment);
 }
 
-struct FragmentAndArchive {
-    Ref<DocumentFragment> fragment;
+struct MarkupAndArchive {
+    String markup;
+    Ref<ArchiveResource> mainResource;
     Ref<Archive> archive;
 };
 
-static std::optional<FragmentAndArchive> createFragmentFromWebArchive(Document& document, SharedBuffer& buffer, const std::function<bool(const String)>& canShowMIMETypeAsHTML)
+static std::optional<MarkupAndArchive> extractMarkupAndArchive(SharedBuffer& buffer, const std::function<bool(const String)>& canShowMIMETypeAsHTML)
 {
     auto archive = LegacyWebArchive::create(URL(), buffer);
     if (!archive)
@@ -200,10 +204,57 @@
     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 MarkupAndArchive { String::fromUTF8(mainResource->data().data(), mainResource->data().size()), mainResource.releaseNonNull(), archive.releaseNonNull() };
+}
 
-    return FragmentAndArchive { WTFMove(fragment), archive.releaseNonNull() };
+static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAndArchive& markupAndArchive, const std::function<bool(const String)>& canShowMIMETypeAsHTML)
+{
+    auto page = createPageForSanitizingWebContent();
+    Document* stagingDocument = page->mainFrame().document();
+    ASSERT(stagingDocument);
+    auto fragment = createFragmentFromMarkup(*stagingDocument, markupAndArchive.markup, markupAndArchive.mainResource->url(), DisallowScriptingAndPluginContent);
+
+    HashMap<AtomicString, AtomicString> blobURLMap;
+    for (const Ref<ArchiveResource>& subresource : markupAndArchive.archive->subresources()) {
+        auto blob = Blob::create(subresource->data(), subresource->mimeType());
+        String blobURL = DOMURL::createObjectURL(destinationDocument, blob);
+        blobURLMap.set(subresource->url().string(), blobURL);
+    }
+
+    auto contentOrigin = SecurityOrigin::create(markupAndArchive.mainResource->url());
+    for (const Ref<Archive>& subframeArchive : markupAndArchive.archive->subframeArchives()) {
+        RefPtr<ArchiveResource> subframeMainResource = subframeArchive->mainResource();
+        if (!subframeMainResource)
+            continue;
+
+        auto type = subframeMainResource->mimeType();
+        if (!canShowMIMETypeAsHTML(type))
+            continue;
+
+        auto subframeURL = subframeMainResource->url();
+        MarkupAndArchive subframeContent = { String::fromUTF8(subframeMainResource->data().data(), subframeMainResource->data().size()),
+            subframeMainResource.releaseNonNull(), subframeArchive.copyRef() };
+        auto subframeMarkup = sanitizeMarkupWithArchive(destinationDocument, subframeContent, canShowMIMETypeAsHTML);
+
+        CString utf8 = subframeMarkup.utf8();
+        Vector<uint8_t> blobBuffer;
+        blobBuffer.reserveCapacity(utf8.length());
+        blobBuffer.append(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
+        auto blob = Blob::create(WTFMove(blobBuffer), type);
+
+        String subframeBlobURL = DOMURL::createObjectURL(destinationDocument, blob);
+        blobURLMap.set(subframeURL.string(), subframeBlobURL);
+    }
+
+    replaceSubresourceURLs(fragment.get(), WTFMove(blobURLMap));
+
+    auto* bodyElement = stagingDocument->body();
+    ASSERT(bodyElement);
+    bodyElement->appendChild(fragment);
+
+    auto range = Range::create(*stagingDocument);
+    range->selectNodeContents(*bodyElement);
+    return createMarkup(range.get(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
 }
 
 bool WebContentReader::readWebArchive(SharedBuffer& buffer)
@@ -212,48 +263,45 @@
         return false;
 
     DeferredLoadingScope scope(frame);
-    auto result = createFragmentFromWebArchive(*frame.document(), buffer, [&] (const String& type) {
+    auto result = extractMarkupAndArchive(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(result->archive.get());
+    if (!shouldSanitize()) {
+        fragment = createFragmentFromMarkup(*frame.document(), result->markup, result->mainResource->url(), DisallowScriptingAndPluginContent);
+        return true;
+    }
 
+    String sanitizedMarkup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) {
+        return frame.loader().client().canShowMIMETypeAsHTML(type);
+    });
+    fragment = createFragmentFromMarkup(*frame.document(), sanitizedMarkup, blankURL(), DisallowScriptingAndPluginContent);
+
     return true;
 }
 
 bool WebContentMarkupReader::readWebArchive(SharedBuffer& buffer)
 {
-    auto page = createPageForSanitizingWebContent();
-    Document* stagingDocument = page->mainFrame().document();
-    ASSERT(stagingDocument);
+    if (!frame.document())
+        return false;
 
-    DeferredLoadingScope scope(frame);
-    auto result = createFragmentFromWebArchive(*stagingDocument, buffer, [&] (const String& type) {
+    auto result = extractMarkupAndArchive(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);
+    if (!shouldSanitize()) {
+        markup = result->markup;
+        return true;
     }
-    replaceSubresourceURLs(result->fragment.get(), WTFMove(blobURLMap));
 
-    auto* bodyElement = stagingDocument->body();
-    ASSERT(bodyElement);
-    bodyElement->appendChild(result->fragment);
+    markup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) {
+        return frame.loader().client().canShowMIMETypeAsHTML(type);
+    });
 
-    auto range = Range::create(*stagingDocument);
-    range->selectNodeContents(*bodyElement);
-    markup = createMarkup(range.get(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
-
     return true;
 }
 

Modified: trunk/Source/WebCore/platform/Pasteboard.h (223677 => 223678)


--- trunk/Source/WebCore/platform/Pasteboard.h	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/Pasteboard.h	2017-10-19 05:44:33 UTC (rev 223678)
@@ -71,6 +71,7 @@
 #if PLATFORM(COCOA)
     WEBCORE_EXPORT PasteboardWebContent();
     WEBCORE_EXPORT ~PasteboardWebContent();
+    String contentOrigin;
     bool canSmartCopyOrDelete;
     RefPtr<SharedBuffer> dataInWebArchiveFormat;
     RefPtr<SharedBuffer> dataInRTFDFormat;

Modified: trunk/Source/WebCore/platform/PasteboardWriterData.h (223677 => 223678)


--- trunk/Source/WebCore/platform/PasteboardWriterData.h	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/PasteboardWriterData.h	2017-10-19 05:44:33 UTC (rev 223678)
@@ -50,6 +50,7 @@
         ~WebContent();
 
 #if PLATFORM(COCOA)
+        String contentOrigin;
         bool canSmartCopyOrDelete;
         RefPtr<SharedBuffer> dataInWebArchiveFormat;
         RefPtr<SharedBuffer> dataInRTFDFormat;

Modified: trunk/Source/WebCore/platform/ios/PasteboardIOS.mm (223677 => 223678)


--- trunk/Source/WebCore/platform/ios/PasteboardIOS.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/ios/PasteboardIOS.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -226,6 +226,7 @@
 
 void Pasteboard::read(PasteboardWebContentReader& reader)
 {
+    reader.contentOrigin = readOrigin();
     if (respectsUTIFidelities()) {
         readRespectingUTIFidelities(reader);
         return;
@@ -238,8 +239,6 @@
     if (!numberOfItems)
         return;
 
-    reader.contentOrigin = readOrigin();
-
     NSArray *types = supportedWebContentPasteboardTypes();
     int numberOfTypes = [types count];
 

Modified: trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm (223677 => 223678)


--- trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/ios/PlatformPasteboardIOS.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -283,6 +283,10 @@
     if (!content.dataInStringFormat.isEmpty())
         addRepresentationsForPlainText(representationsToRegister.get(), content.dataInStringFormat);
 
+    PasteboardCustomData customData;
+    customData.origin = content.contentOrigin;
+    [representationsToRegister addData:customData.createSharedBuffer()->createNSData().get() forType:@(PasteboardCustomData::cocoaType())];
+
     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
 }
 

Modified: trunk/Source/WebCore/platform/mac/PasteboardMac.mm (223677 => 223678)


--- trunk/Source/WebCore/platform/mac/PasteboardMac.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/mac/PasteboardMac.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -138,6 +138,7 @@
     if (!content.dataInStringFormat.isNull())
         types.append(String(NSStringPboardType));
     types.appendVector(content.clientTypes);
+    types.append(PasteboardCustomData::cocoaType());
 
     m_changeCount = platformStrategies()->pasteboardStrategy()->setTypes(types, m_pasteboardName);
 
@@ -156,6 +157,11 @@
         m_changeCount = platformStrategies()->pasteboardStrategy()->setStringForType(content.dataInHTMLFormat, NSHTMLPboardType, m_pasteboardName);
     if (!content.dataInStringFormat.isNull())
         m_changeCount = platformStrategies()->pasteboardStrategy()->setStringForType(content.dataInStringFormat, NSStringPboardType, m_pasteboardName);
+
+    PasteboardCustomData data;
+    data.origin = content.contentOrigin;
+    m_changeCount = platformStrategies()->pasteboardStrategy()->setBufferForType(data.createSharedBuffer().ptr(), PasteboardCustomData::cocoaType(), m_pasteboardName);
+
 }
 
 void Pasteboard::writePlainText(const String& text, SmartReplaceOption smartReplaceOption)

Modified: trunk/Source/WebCore/platform/mac/PasteboardWriter.mm (223677 => 223678)


--- trunk/Source/WebCore/platform/mac/PasteboardWriter.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebCore/platform/mac/PasteboardWriter.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -28,6 +28,7 @@
 
 #if PLATFORM(MAC)
 
+#import "Pasteboard.h"
 #import "PasteboardWriterData.h"
 #import "SharedBuffer.h"
 #import <pal/spi/mac/NSPasteboardSPI.h>
@@ -114,6 +115,10 @@
 
         for (unsigned i = 0; i < webContent->clientTypes.size(); ++i)
             [pasteboardItem setData:webContent->clientData[i]->createNSData().get() forType:toUTIUnlessAlreadyUTI(webContent->clientTypes[i]).get()];
+
+        PasteboardCustomData customData;
+        customData.origin = webContent->contentOrigin;
+        [pasteboardItem setData:customData.createSharedBuffer()->createNSData().get() forType:toUTIUnlessAlreadyUTI(String(PasteboardCustomData::cocoaType())).get()];
     }
 
     return pasteboardItem;

Modified: trunk/Source/WebKit/ChangeLog (223677 => 223678)


--- trunk/Source/WebKit/ChangeLog	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebKit/ChangeLog	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1,3 +1,16 @@
+2017-10-18  Ryosuke Niwa  <[email protected]>
+
+        Don't expose raw HTML in pasteboard to the web content
+        https://bugs.webkit.org/show_bug.cgi?id=178422
+
+        Reviewed by Wenson Hsieh.
+
+        Encode & decode the origin string of the copied content written into the system pasteboard.
+
+        * Shared/WebCoreArgumentCoders.cpp:
+        (IPC::ArgumentCoder<PasteboardWebContent>::encode):
+        (IPC::ArgumentCoder<PasteboardWebContent>::decode):
+
 2017-10-18  Chris Dumez  <[email protected]>
 
         Implement ServiceWorkerRegistration.scope / updateViaCache

Modified: trunk/Source/WebKit/Shared/WebCoreArgumentCoders.cpp (223677 => 223678)


--- trunk/Source/WebKit/Shared/WebCoreArgumentCoders.cpp	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Source/WebKit/Shared/WebCoreArgumentCoders.cpp	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1689,6 +1689,7 @@
 
 void ArgumentCoder<PasteboardWebContent>::encode(Encoder& encoder, const PasteboardWebContent& content)
 {
+    encoder << content.contentOrigin;
     encoder << content.canSmartCopyOrDelete;
     encoder << content.dataInStringFormat;
     encoder << content.dataInHTMLFormat;
@@ -1703,6 +1704,8 @@
 
 bool ArgumentCoder<PasteboardWebContent>::decode(Decoder& decoder, PasteboardWebContent& content)
 {
+    if (!decoder.decode(content.contentOrigin))
+        return false;
     if (!decoder.decode(content.canSmartCopyOrDelete))
         return false;
     if (!decoder.decode(content.dataInStringFormat))

Modified: trunk/Tools/ChangeLog (223677 => 223678)


--- trunk/Tools/ChangeLog	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Tools/ChangeLog	2017-10-19 05:44:33 UTC (rev 223678)
@@ -1,3 +1,16 @@
+2017-10-18  Ryosuke Niwa  <[email protected]>
+
+        Don't expose raw HTML in pasteboard to the web content
+        https://bugs.webkit.org/show_bug.cgi?id=178422
+
+        Reviewed by Wenson Hsieh.
+
+        Added a test case for sanitizing web archive in the system pasteboard to strip privacy sensitive information
+        such as local file paths and potentially harmful scripts like event handlers serialized by WebKit prior to r223462.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm:
+        (PasteWebArchive.SanitizesHTML):
+
 2017-10-18  Youenn Fablet  <[email protected]>
 
         TestController should clear all fetch caches when resetting its state

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm (223677 => 223678)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm	2017-10-19 05:15:05 UTC (rev 223677)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteWebArchive.mm	2017-10-19 05:44:33 UTC (rev 223678)
@@ -77,6 +77,30 @@
     EXPECT_WK_STREQ("hello, world", [webView stringByEvaluatingJavaScript:@"editor.textContent"]);
 }
 
+TEST(PasteWebArchive, SanitizesHTML)
+{
+    auto* url = "" 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_WK_STREQ("hello, world", [webView stringByEvaluatingJavaScript:@"editor.textContent"]);
+    [webView stringByEvaluatingJavaScript:@"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_FALSE([webView stringByEvaluatingJavaScript:@"editor.innerHTML.includes('secret')"].boolValue);
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"editor.innerHTML.includes('dangerousCode')"].boolValue);
+}
+
 #endif // WK_API_ENABLED && PLATFORM(MAC)
 
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to