Sanitize URL in pasteboard for other applications and cross origin content
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Oct 2017 19:01:10 +0000 (19:01 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Oct 2017 19:01:10 +0000 (19:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=178060
<rdar://problem/34874518>

Reviewed by Wenson Hsieh.

Source/WebCore:

This patch introduces the sanitization of URL when written from a web content to prevent web content from
exploiting the URL parser of other applications in the system particularly of those that actively monitor
system pasteboard (a.k.a. clipboard on non-Cocoa platforms) and decode or otherwise process URLs.

Because the Web compatibility requires that DataTransfer exposes the original URL to any document in the
same origin as the one which wrote the URL into the pasteboard, we store a string which uniquely identifies
the origin of an originating document into our custom pasteboard data. Note that we expose any URL which
didn't come from WebKit since we don't expect URLs to reveal privacy sensitive information. We use UUID for
the origin identifier of a null origin document.

An alternative approach is to store the pasteboard data from the same origin into the document and invalidate
it when the system pasteboard changes. However, Pasteboard object cannot know about Document (as Pasteboard
is a platform object and Document is a WebCore object), this turns out be quite tricky as there are multiple
places where we create Pasteboard objects, and they all need to be aware of this special same origin
Pasteboard object that hangs off of Document. Also, this approach would result in the same origin code paths
to diverge between null origin and non-null origin documents.

Tests: editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html
       editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html
       editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html
       editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html
       http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html
       CopyURL.ValidURL
       CopyURL.UnescapedURL
       CopyURL.MalformedURL
       DataInteractionTests.DataTransferSetDataValidURL
       DataInteractionTests.DataTransferSetDataUnescapedURL
       DataInteractionTests.DataTransferSetDataInvalidURL

* dom/DataTransfer.cpp:
(WebCore::originForDocument): Extracted from createForCopyAndPaste.
(WebCore::DataTransfer::createForCopyAndPaste):
(WebCore::DataTransfer::getDataForItem const): Read the URL from the custom data when the originating content
is of the same origin. When the originating content is cross origin, or there is no custom data (e.g. written
by another native application; or sanitization didn't result in any difference), then callback to native value.
(WebCore::DataTransfer::setDataFromItemList): Sanitize the URL before writing it to the native pasteboard.
Store the original value if the sanitization resulted in any difference.
(WebCore::DataTransfer::types const):
(WebCore::DataTransfer::commitToPasteboard): Moved the code to write custom data to Pasteboard since we need
to write the origin string with it.
(WebCore::DataTransfer::createForDragStartEvent): Added Document as an argument to compute the origin string.
(WebCore::DataTransfer::createForDrop): Ditto.
(WebCore::DataTransfer::createForUpdatingDropTarget):
(WebCore::DataTransfer::moveDragState):
* dom/DataTransfer.h:
* dom/Document.cpp:
(WebCore::Document::uniqueIdentifier): Added. See above.
* dom/Document.h:
* editing/Editor.cpp:
(WebCore::createDataTransferForClipboardEvent):
(WebCore::dispatchClipboardEvent):
* page/DragController.cpp:
(WebCore::DragController::dispatchTextInputEventFor):
* page/EventHandler.cpp:
(WebCore::EventHandler::performDragAndDrop):
(WebCore::EventHandler::handleDrag):
* platform/Pasteboard.h:
* platform/PasteboardStrategy.h:
* platform/PlatformPasteboard.h:
* platform/StaticPasteboard.cpp:
(WebCore::StaticPasteboard::takeCustomData): Moved the logic to write to native pasteboard to DataTransfer.
* platform/StaticPasteboard.h:
* platform/cocoa/PasteboardCocoa.mm:
(WebCore::Pasteboard::typesSafeForBindings):
(WebCore::Pasteboard::readStringInCustomData): Rewritten using readCustomData. See below.
(WebCore::Pasteboard::readOrigin): Added.
(WebCore::Pasteboard::readCustomData): Added. Populates the cache. Because a single Pasteboard object is never
allowed to read values once its content is updated by other applications, we can permanently cache the result.
* platform/gtk/PasteboardGtk.cpp:
(WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
(WebCore::Pasteboard::readOrigin): Added.
* platform/gtk/PlatformPasteboardGtk.cpp:
(WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Now takes the unused origin string.
* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::originKeyKeyForTeamData): Added.
(WebCore::customTypesKeyForTeamData): Added. Replaces the use of PasteboardCustomData::cocoaType() in the team
data for clarity since the team data key isn't same as the pasteboard type. We don't have to worry about the
backwards compatibility since drag & drop session doesn't persist across iOS upgrades, and there is no publicly
released iOS with this team data support.
(WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Read the origin string and the custom data
off the team data. Don't expose custom types that are written by cross origin documents.
(WebCore::PlatformPasteboard::write): Add the orign string with custom pasteboard types in the team data.
(WebCore::PlatformPasteboard::readURL): Fixed a bug that this function was not reading NSURL when UIPasteboard
serializes NSURL as a plist. This code is exercised by CopyURL.ValidURL.
* platform/mac/PlatformPasteboardMac.mm:
(WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Don't add custom pasteboard types that are
added by cross origin documents.
* platform/win/PasteboardWin.cpp:
(WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
(WebCore::Pasteboard::readOrigin): Added.
* platform/wpe/PasteboardWPE.cpp:
(WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
(WebCore::Pasteboard::readOrigin): Added.
* platform/wpe/PlatformPasteboardWPE.cpp:
(WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Now takes the unused origin string.

Source/WebKit:

Plubmed the origin identifier through IPC from Pasteboard in WebContent process to PlatformPasteboard in UIProcess.

* UIProcess/Cocoa/WebPasteboardProxyCocoa.mm:
(WebKit::WebPasteboardProxy::typesSafeForDOMToReadAndWrite):
* UIProcess/WebPasteboardProxy.cpp:
(WebKit::WebPasteboardProxy::typesSafeForDOMToReadAndWrite):
* UIProcess/WebPasteboardProxy.h:
* UIProcess/WebPasteboardProxy.messages.in:
* WebProcess/WebCoreSupport/WebPlatformStrategies.cpp:
(WebKit::WebPlatformStrategies::typesSafeForDOMToReadAndWrite):
* WebProcess/WebCoreSupport/WebPlatformStrategies.h:

Source/WebKitLegacy/mac:

* WebCoreSupport/WebPlatformStrategies.h:
* WebCoreSupport/WebPlatformStrategies.mm:
(WebPlatformStrategies::typesSafeForDOMToReadAndWrite):

Tools:

Added API tests for sanitizing URLs copied from web content, and that the original URL is exposed to the web content.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm: Added.
(readURLFromPasteboard): A helper function.
* TestWebKitAPI/Tests/WebKitCocoa/copy-url.html: Added.
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(DataInteractionTests.DataTransferGetDataWhenDroppingCustomData): Rebaselined. https://www.apple.com is no longer
normalized to https://www.apple.com/ by NSURL / UIPasteboard as expected.
(DataInteractionTests.DataTransferSetDataValidURL): Added.
(DataInteractionTests.DataTransferSetDataUnescapedURL): Added.
(DataInteractionTests.qDataTransferSetDataInvalidURL): Added.

LayoutTests:

Added tests for copying & pasting URLs. URLs should be %-escaped and any invalid URL should be stripped away and
invisible to a cross-origin content or a null origin document but the same origin content should have access to
its original form.

* TestExpectations:
* editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document-expected.txt: Added.
* editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html: Added.
* editing/pasteboard/data-transfer-get-data-on-drop-custom-expected.txt: Rebaselined. We no longer normalize
"https://www.apple.com" into "https://www.apple.com/" by NSURL / UIPasteboard within the same origin content.
* editing/pasteboard/data-transfer-get-data-on-paste-custom-expected.txt: Ditto.
* editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html: Added.
* editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin-expected.txt: Added.
* editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html: Added.
* editing/pasteboard/dataTransfer-setData-getData-expected.txt: Rebaselined. More test cases are passing.
* editing/pasteboard/dataTransfer-setData-getData.html: Updated expectations as the original URL is now preserved.
* http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url-expected.txt: Added.
* http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html: Added.
* http/tests/security/clipboard/resources/copy.html: Added.
* platform/mac-wk1/TestExpectations:

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

56 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-get-data-on-drop-custom-expected.txt
LayoutTests/editing/pasteboard/data-transfer-get-data-on-paste-custom-expected.txt
LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/dataTransfer-setData-getData-expected.txt
LayoutTests/editing/pasteboard/dataTransfer-setData-getData.html
LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html [new file with mode: 0644]
LayoutTests/http/tests/security/clipboard/resources/copy.html [new file with mode: 0644]
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/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/page/DragController.cpp
Source/WebCore/page/EventHandler.cpp
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/PasteboardStrategy.h
Source/WebCore/platform/PlatformPasteboard.h
Source/WebCore/platform/StaticPasteboard.cpp
Source/WebCore/platform/StaticPasteboard.h
Source/WebCore/platform/cocoa/PasteboardCocoa.mm
Source/WebCore/platform/gtk/PasteboardGtk.cpp
Source/WebCore/platform/gtk/PlatformPasteboardGtk.cpp
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/mac/PlatformPasteboardMac.mm
Source/WebCore/platform/win/PasteboardWin.cpp
Source/WebCore/platform/wpe/PasteboardWPE.cpp
Source/WebCore/platform/wpe/PlatformPasteboardWPE.cpp
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm
Source/WebKit/UIProcess/WebPasteboardProxy.cpp
Source/WebKit/UIProcess/WebPasteboardProxy.h
Source/WebKit/UIProcess/WebPasteboardProxy.messages.in
Source/WebKit/WebProcess/WebCoreSupport/WebPlatformStrategies.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebPlatformStrategies.h
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebPlatformStrategies.h
Source/WebKitLegacy/mac/WebCoreSupport/WebPlatformStrategies.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-url.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

index 11dc66f..2d391c7 100644 (file)
@@ -1,3 +1,35 @@
+2017-10-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Sanitize URL in pasteboard for other applications and cross origin content
+        https://bugs.webkit.org/show_bug.cgi?id=178060
+        <rdar://problem/34874518>
+
+        Reviewed by Wenson Hsieh.
+
+        Added tests for copying & pasting URLs. URLs should be %-escaped and any invalid URL should be stripped away and
+        invisible to a cross-origin content or a null origin document but the same origin content should have access to
+        its original form.
+
+        * TestExpectations:
+        * editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document-expected.txt: Added.
+        * editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html: Added.
+        * editing/pasteboard/data-transfer-get-data-on-drop-custom-expected.txt: Rebaselined. We no longer normalize
+        "https://www.apple.com" into "https://www.apple.com/" by NSURL / UIPasteboard within the same origin content.
+        * editing/pasteboard/data-transfer-get-data-on-paste-custom-expected.txt: Ditto.
+        * editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin-expected.txt: Added.
+        * editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html: Added.
+        * editing/pasteboard/dataTransfer-setData-getData-expected.txt: Rebaselined. More test cases are passing.
+        * editing/pasteboard/dataTransfer-setData-getData.html: Updated expectations as the original URL is now preserved.
+        * http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url-expected.txt: Added.
+        * http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html: Added.
+        * http/tests/security/clipboard/resources/copy.html: Added.
+        * platform/mac-wk1/TestExpectations:
+
 2017-10-11  Andy Estes  <aestes@apple.com>
 
         [Payment Request] Implement Apple Pay merchant validation
index 7295066..593bd65 100644 (file)
@@ -74,6 +74,7 @@ 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-sanitlize-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 ]
diff --git a/LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document-expected.txt
new file mode 100644 (file)
index 0000000..209e77a
--- /dev/null
@@ -0,0 +1,12 @@
+This tests calling getData returns the same malformed URL string set by setData
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS event.clipboardData.getData("text/URI-list") is "some bad URL"
+PASS JSON.stringify(event.clipboardData.types) is "[\"text/uri-list\"]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html b/LayoutTests/editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html
new file mode 100644 (file)
index 0000000..ff8f5ae
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<div id="container">
+<button onclick="runTest()">1. Copy</button>
+<div id="source" oncopy="copy(event)" contenteditable>some text</div>
+<div id="destination" onpaste="paste(event)" contenteditable>2. Paste here</div>
+</div>
+<script>
+
+description('This tests calling getData returns the same malformed URL string set by setData');
+jsTestIsAsync = true;
+
+function runTest() {
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function copy(event) {
+    event.clipboardData.setData('url', 'some bad URL');
+    event.preventDefault();
+}
+
+function paste(event) {
+    shouldBeEqualToString('event.clipboardData.getData("text/URI-list")', 'some bad URL');
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["text/uri-list"]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))',
+        '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('container').remove();
+    finishJSTest();
+}
+
+if (window.testRunner && window.internals) {
+    internals.settings.setCustomPasteboardDataEnabled(true);
+    runTest();
+}
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 9991433..de0c14e 100644 (file)
@@ -14,7 +14,7 @@ Rich text
         "foo/🤔👌🙃": "🤔👌🙃",
         "text/html": "<b>年年年</b>",
         "bar/מקור השם עברית": "<i>מקור השם עברית</i>",
-        "text/uri-list": "https://www.apple.com/",
+        "text/uri-list": "https://www.apple.com",
         "baz/年年年": "https://www.webkit.org",
         "text/rtf": "AAAAAAAAAAA"
     }
index 8607a4a..bacb4aa 100644 (file)
@@ -5,7 +5,7 @@ Rich text
         "foo/🤔👌🙃": "🤔👌🙃",
         "text/html": "<b>年年年</b>",
         "bar/מקור השם עברית": "<i>מקור השם עברית</i>",
-        "text/uri-list": "https://www.apple.com/",
+        "text/uri-list": "https://www.apple.com",
         "baz/年年年": "https://www.webkit.org",
         "text/rtf": "AAAAAAAAAAA"
     }
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-expected.txt
new file mode 100644 (file)
index 0000000..31bc32e
--- /dev/null
@@ -0,0 +1,15 @@
+This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS urlReadInSameDocument is "hello, world"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS event.clipboardData.getData("url") is ""
+PASS JSON.stringify(event.clipboardData.types) is "[]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin-expected.txt
new file mode 100644 (file)
index 0000000..31bc32e
--- /dev/null
@@ -0,0 +1,15 @@
+This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS urlReadInSameDocument is "hello, world"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS event.clipboardData.getData("url") is ""
+PASS JSON.stringify(event.clipboardData.types) is "[]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html
new file mode 100644 (file)
index 0000000..d8fdb9c
--- /dev/null
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+description('This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+<button onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="copy(event)" contenteditable>some text</div>
+<div id="destination" onpaste="paste(event)" contenteditable>2. Paste here</div>
+<script>
+
+function copy(event) {
+    event.clipboardData.setData('url', 'hello, world');
+    event.preventDefault();
+}
+
+function paste(event) {
+    parent.postMessage({
+        url: event.clipboardData.getData('url'),
+        types: event.clipboardData.types,
+        items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+}
+
+function runTest() {
+    document.body.getBoundingClientRect();
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+if (window.testRunner)
+    setTimeout(runTest, 10);
+
+</scri` + 'pt>';
+
+onmessage = (event) => {
+    urlReadInSameDocument = event.data.url;
+    shouldBeEqualToString('urlReadInSameDocument', 'hello, world');
+    typesInSameDocument = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+    itemsInSameDocument = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    shouldBeEqualToString('event.clipboardData.getData("url")', '');
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '[]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))', '[]');
+    document.getElementById('destination').remove();
+    finishJSTest();
+}
+
+</script>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin-expected.txt
new file mode 100644 (file)
index 0000000..6f11d5e
--- /dev/null
@@ -0,0 +1,15 @@
+This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6<"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS event.clipboardData.getData("url") is "http://webkit.org/b/%F0%9F%A4%94?x=8%20+%206%3C"
+PASS JSON.stringify(event.clipboardData.types) is "[\"text/uri-list\"]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html
new file mode 100644 (file)
index 0000000..d867630
--- /dev/null
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+description('This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+iframe.src = `data:text/html;charset=utf-8,<!DOCTYPE html>
+<button onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="copy(event)" contenteditable>http://webkit.org/b/&#x1F914;?x=8 + 6<</div>
+<div id="destination" onpaste="paste(event)" contenteditable>2. Paste here</div>
+<script>
+
+const originalURL = document.getElementById('source').textContent;
+function copy(event) {
+    event.clipboardData.setData('url', originalURL);
+    event.preventDefault();
+}
+
+function paste(event) {
+    parent.postMessage({
+        originalURL,
+        url: event.clipboardData.getData('url'),
+        types: event.clipboardData.types,
+        items: Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})),
+    }, '*');
+}
+
+function runTest() {
+    document.body.getBoundingClientRect();
+    document.getElementById('source').focus();
+    document.execCommand('selectAll');
+    document.execCommand('copy');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+if (window.testRunner)
+    runTest();
+
+</scri` + 'pt>';
+
+onmessage = (event) => {
+    originalURL = event.data.originalURL;
+    urlReadInSameDocument = event.data.url;
+    shouldBeEqualToString('urlReadInSameDocument', originalURL);
+    typesInSameDocument = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+    itemsInSameDocument = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    shouldBeEqualToString('event.clipboardData.getData("url")', (new URL(originalURL)).href);
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["text/uri-list"]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))',
+        '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('destination').remove();
+    finishJSTest();
+}
+
+</script>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin-expected.txt
new file mode 100644 (file)
index 0000000..6d31e36
--- /dev/null
@@ -0,0 +1,38 @@
+This tests calling setData to set a URL 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 urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+
+dragover in the null origin document:
+PASS urlReadInSameDocument is ""
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+
+drop in the null origin document:
+PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+
+dragstart in the null origin document:
+PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+
+dragover in the file URL document:
+PASS urlReadInSameDocument is ""
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+
+drop in the file URL document:
+PASS urlReadInSameDocument is "http://webkit.org/b/%F0%9F%A4%94?x=8%20+%206"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html b/LayoutTests/editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html
new file mode 100644 (file)
index 0000000..fb71280
--- /dev/null
@@ -0,0 +1,157 @@
+<!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 URL 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" data-value="http://webkit.org/b/&#x1F914;?x=8 + 6">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 originalURL = source.dataset.value;
+parent.postMessage({kind: 'originalURL', originalURL}, '*');
+
+function postContent(kind, dataTransfer) {
+    parent.postMessage({
+        kind,
+        documentLabel: 'the null origin document',
+        url: dataTransfer.getData('url'),
+        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('url', originalURL);
+    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 == 'originalURL') {
+        originalURL = event.data.originalURL;
+        return;
+    }
+    if (kind == 'secondDrop')
+        return doSecondDrop(event.data.postContent);
+    debug(`${kind} in ${event.data.documentLabel}:`);
+    switch (kind) {
+    case 'dragstart':
+        urlReadInSameDocument = event.data.url;
+        shouldBeEqualToString('urlReadInSameDocument', originalURL);
+        typesInSameDocument = event.data.types;
+        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+        itemsInSameDocument = event.data.items;
+        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+        break;
+    case 'dragover':
+        urlReadInSameDocument = event.data.url;
+        shouldBeEqualToString('urlReadInSameDocument', '');
+        typesInSameDocument = event.data.types;
+        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+        itemsInSameDocument = event.data.items;
+        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+        break;
+    case 'drop':
+        urlReadInSameDocument = event.data.url;
+        shouldBeEqualToString('urlReadInSameDocument', event.data.documentLabel.includes('null') ? originalURL : (new URL(originalURL)).href);
+        typesInSameDocument = event.data.types;
+        shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+        itemsInSameDocument = event.data.items;
+        shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+        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',
+        url: dataTransfer.getData('url'),
+        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 f0da65e..238ca1c 100644 (file)
@@ -5,39 +5,41 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
 
 --- Test set/get 'URL':
 PASS getDataResultType is "string"
-PASS getDataResult is "http://test.com/"
+PASS getDataResult is "http://test.com"
 --- Test set/get 'URL' with multiple URLs:
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com/. Was .
+FAIL getDataResult should be http://test.com. Was http://test.com
+http://check.com.
 --- Test set/get 'text/uri-list':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com
-http://check.com. Was .
+PASS getDataResult is "http://test.com\r\nhttp://check.com"
 --- Test set/get 'text/uri-list' using '\n':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com
-http://check.com. Was .
+PASS getDataResult is "http://test.com\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com/. Was .
+FAIL getDataResult should be http://test.com. Was http://test.com
+http://check.com.
 --- Test set 'URL', get 'text/uri-list':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com
-http://check.com. Was .
+PASS getDataResult is "http://test.com\r\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL', using only '\n':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com/. Was .
+FAIL getDataResult should be http://test.com. Was http://test.com
+http://check.com.
 --- Test set/get 'text/uri-list' with comments:
 PASS getDataResultType is "string"
-FAIL getDataResult should be # comment
-http://test.com
-http://check.com. Was .
+PASS getDataResult is "# comment\r\nhttp://test.com\r\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL' with comments:
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com/. Was .
+FAIL getDataResult should be http://test.com. Was # comment
+http://test.com
+http://check.com.
 --- Test set 'text/uri-list', get 'URL' with only comments:
 PASS getDataResultType is "string"
-PASS getDataResult is ""
+FAIL getDataResult should be . Was # comment
+# comment 2
+# comment 3.
 --- Test set/get 'text/plain':
 PASS getDataResultType is "string"
 PASS getDataResult is "Lorem ipsum dolor sit amet."
index 49eca66..865fc39 100644 (file)
@@ -85,11 +85,11 @@ function runTest()
 {
     debug("--- Test set/get 'URL':");
     test("URL", "http://test.com", 
-         "URL", "http://test.com/");
+         "URL", "http://test.com");
 
     debug("--- Test set/get 'URL' with multiple URLs:");
     test("URL", "http://test.com\r\nhttp://check.com", 
-         "URL", "http://test.com/");
+         "URL", "http://test.com");
 
     debug("--- Test set/get 'text/uri-list':");
     test("text/uri-list", "http://test.com\r\nhttp://check.com", 
@@ -101,7 +101,7 @@ function runTest()
 
     debug("--- Test set 'text/uri-list', get 'URL':");
     test("text/uri-list", "http://test.com\r\nhttp://check.com", 
-         "URL", "http://test.com/");
+         "URL", "http://test.com");
 
     debug("--- Test set 'URL', get 'text/uri-list':");
     test("URL", "http://test.com\r\nhttp://check.com", 
@@ -109,7 +109,7 @@ function runTest()
 
     debug("--- Test set 'text/uri-list', get 'URL', using only '\\n':");
     test("text/uri-list", "http://test.com\nhttp://check.com",
-         "URL", "http://test.com/");
+         "URL", "http://test.com");
 
     debug("--- Test set/get 'text/uri-list' with comments:");
     test("text/uri-list", "# comment\r\nhttp://test.com\r\nhttp://check.com", 
@@ -117,7 +117,7 @@ function runTest()
 
     debug("--- Test set 'text/uri-list', get 'URL' with comments:");
     test("text/uri-list", "# comment\r\nhttp://test.com\r\nhttp://check.com", 
-         "URL", "http://test.com/");
+         "URL", "http://test.com");
 
     debug("--- Test set 'text/uri-list', get 'URL' with only comments:");
     test("text/uri-list", "# comment\r\n# comment 2\r\n# comment 3", 
diff --git a/LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url-expected.txt b/LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url-expected.txt
new file mode 100644 (file)
index 0000000..2db3c68
--- /dev/null
@@ -0,0 +1,15 @@
+This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS urlReadInSameDocument is "http://webkit.org/b/🤔?x=8 + 6"
+PASS JSON.stringify(typesInSameDocument) is "[\"text/uri-list\"]"
+PASS JSON.stringify(itemsInSameDocument) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS event.clipboardData.getData("url") is "http://webkit.org/b/%F0%9F%A4%94?x=8%20+%206"
+PASS JSON.stringify(event.clipboardData.types) is "[\"text/uri-list\"]"
+PASS JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type}))) is "[{\"kind\":\"string\",\"type\":\"text/uri-list\"}]"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html b/LayoutTests/http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html
new file mode 100644 (file)
index 0000000..87d5f94
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/resources/js-test-pre.js"></script>
+<script>
+
+description('This tests calling setData with a malformed URL in a null origin document. The malformed value should not be readable in another document');
+jsTestIsAsync = true;
+
+if (window.internals)
+    internals.settings.setCustomPasteboardDataEnabled(true);
+
+onmessage = (event) => {
+    originalURL = event.data.originalURL;
+    urlReadInSameDocument = event.data.url;
+    shouldBeEqualToString('urlReadInSameDocument', originalURL);
+    typesInSameDocument = event.data.types;
+    shouldBeEqualToString('JSON.stringify(typesInSameDocument)', '["text/uri-list"]');
+    itemsInSameDocument = event.data.items;
+    shouldBeEqualToString('JSON.stringify(itemsInSameDocument)', '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('destination').focus();
+    if (window.testRunner)
+        document.execCommand('paste');
+}
+
+function doPaste(event) {
+    shouldBeEqualToString('event.clipboardData.getData("url")', (new URL(originalURL)).href);
+    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["text/uri-list"]');
+    shouldBeEqualToString('JSON.stringify(Array.from(event.clipboardData.items).map((item) => ({kind: item.kind, type: item.type})))',
+        '[{"kind":"string","type":"text/uri-list"}]');
+    document.getElementById('destination').remove();
+    finishJSTest();
+}
+
+</script>
+<iframe src="http://localhost:8000/security/clipboard/resources/copy.html"></iframe>
+<div id="destination" onpaste="doPaste(event)" contenteditable>3. Paste here</div>
+<script src="/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/clipboard/resources/copy.html b/LayoutTests/http/tests/security/clipboard/resources/copy.html
new file mode 100644 (file)
index 0000000..2368247
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<body>
+<button onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="copy(event)" contenteditable>http://webkit.org/b/&#x1F914;?x=8 + 6</div>
+<div id="destination" onpaste="paste(event)" contenteditable>2. Paste here</div>
+<script>
+
+const originalURL = document.getElementById('source').textContent;
+function copy(event) {
+    event.clipboardData.setData('url', originalURL);
+    event.preventDefault();
+}
+
+function paste(event) {
+    parent.postMessage({
+        originalURL,
+        url: event.clipboardData.getData('url'),
+        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>
index 678cc93..6ec65ba 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-sanitlize-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 ]
 editing/pasteboard/data-transfer-items-drop-file.html [ Pass ]
index 0e0be98..349fb28 100644 (file)
@@ -1133,6 +1133,9 @@ editing/pasteboard/ [ Pass Failure ]
 [ Debug ] editing/selection/4983858.html [ Skip ] # Debug Assertion
 [ 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
+
 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
 webkit.org/b/140786 editing/pasteboard/copy-backslash-with-euc.html [ Skip ] # Causes later tests to fail
index 4946c6b..2950194 100644 (file)
@@ -1,3 +1,107 @@
+2017-10-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Sanitize URL in pasteboard for other applications and cross origin content
+        https://bugs.webkit.org/show_bug.cgi?id=178060
+        <rdar://problem/34874518>
+
+        Reviewed by Wenson Hsieh.
+
+        This patch introduces the sanitization of URL when written from a web content to prevent web content from
+        exploiting the URL parser of other applications in the system particularly of those that actively monitor
+        system pasteboard (a.k.a. clipboard on non-Cocoa platforms) and decode or otherwise process URLs.
+
+        Because the Web compatibility requires that DataTransfer exposes the original URL to any document in the
+        same origin as the one which wrote the URL into the pasteboard, we store a string which uniquely identifies
+        the origin of an originating document into our custom pasteboard data. Note that we expose any URL which
+        didn't come from WebKit since we don't expect URLs to reveal privacy sensitive information. We use UUID for
+        the origin identifier of a null origin document.
+
+        An alternative approach is to store the pasteboard data from the same origin into the document and invalidate
+        it when the system pasteboard changes. However, Pasteboard object cannot know about Document (as Pasteboard
+        is a platform object and Document is a WebCore object), this turns out be quite tricky as there are multiple
+        places where we create Pasteboard objects, and they all need to be aware of this special same origin
+        Pasteboard object that hangs off of Document. Also, this approach would result in the same origin code paths
+        to diverge between null origin and non-null origin documents.
+
+        Tests: editing/pasteboard/data-transfer-get-data-on-copying-pasting-malformed-url-in-same-document.html
+               editing/pasteboard/data-transfer-set-data-ignore-copied-walformed-url-in-null-origin.html
+               editing/pasteboard/data-transfer-set-data-sanitlize-url-when-copying-in-null-origin.html
+               editing/pasteboard/data-transfer-set-data-sanitlize-url-when-dragging-in-null-origin.html
+               http/tests/security/clipboard/copy-paste-url-across-origin-sanitizes-url.html
+               CopyURL.ValidURL
+               CopyURL.UnescapedURL
+               CopyURL.MalformedURL
+               DataInteractionTests.DataTransferSetDataValidURL
+               DataInteractionTests.DataTransferSetDataUnescapedURL
+               DataInteractionTests.DataTransferSetDataInvalidURL
+
+        * dom/DataTransfer.cpp:
+        (WebCore::originForDocument): Extracted from createForCopyAndPaste.
+        (WebCore::DataTransfer::createForCopyAndPaste):
+        (WebCore::DataTransfer::getDataForItem const): Read the URL from the custom data when the originating content
+        is of the same origin. When the originating content is cross origin, or there is no custom data (e.g. written
+        by another native application; or sanitization didn't result in any difference), then callback to native value.
+        (WebCore::DataTransfer::setDataFromItemList): Sanitize the URL before writing it to the native pasteboard.
+        Store the original value if the sanitization resulted in any difference.
+        (WebCore::DataTransfer::types const):
+        (WebCore::DataTransfer::commitToPasteboard): Moved the code to write custom data to Pasteboard since we need
+        to write the origin string with it.
+        (WebCore::DataTransfer::createForDragStartEvent): Added Document as an argument to compute the origin string.
+        (WebCore::DataTransfer::createForDrop): Ditto.
+        (WebCore::DataTransfer::createForUpdatingDropTarget):
+        (WebCore::DataTransfer::moveDragState):
+        * dom/DataTransfer.h:
+        * dom/Document.cpp:
+        (WebCore::Document::uniqueIdentifier): Added. See above.
+        * dom/Document.h:
+        * editing/Editor.cpp:
+        (WebCore::createDataTransferForClipboardEvent):
+        (WebCore::dispatchClipboardEvent):
+        * page/DragController.cpp:
+        (WebCore::DragController::dispatchTextInputEventFor):
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::performDragAndDrop):
+        (WebCore::EventHandler::handleDrag):
+        * platform/Pasteboard.h:
+        * platform/PasteboardStrategy.h:
+        * platform/PlatformPasteboard.h:
+        * platform/StaticPasteboard.cpp:
+        (WebCore::StaticPasteboard::takeCustomData): Moved the logic to write to native pasteboard to DataTransfer.
+        * platform/StaticPasteboard.h:
+        * platform/cocoa/PasteboardCocoa.mm:
+        (WebCore::Pasteboard::typesSafeForBindings):
+        (WebCore::Pasteboard::readStringInCustomData): Rewritten using readCustomData. See below.
+        (WebCore::Pasteboard::readOrigin): Added.
+        (WebCore::Pasteboard::readCustomData): Added. Populates the cache. Because a single Pasteboard object is never
+        allowed to read values once its content is updated by other applications, we can permanently cache the result.
+        * platform/gtk/PasteboardGtk.cpp:
+        (WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
+        (WebCore::Pasteboard::readOrigin): Added.
+        * platform/gtk/PlatformPasteboardGtk.cpp:
+        (WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Now takes the unused origin string.
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::originKeyKeyForTeamData): Added.
+        (WebCore::customTypesKeyForTeamData): Added. Replaces the use of PasteboardCustomData::cocoaType() in the team
+        data for clarity since the team data key isn't same as the pasteboard type. We don't have to worry about the
+        backwards compatibility since drag & drop session doesn't persist across iOS upgrades, and there is no publicly
+        released iOS with this team data support.
+        (WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Read the origin string and the custom data
+        off the team data. Don't expose custom types that are written by cross origin documents.
+        (WebCore::PlatformPasteboard::write): Add the orign string with custom pasteboard types in the team data.
+        (WebCore::PlatformPasteboard::readURL): Fixed a bug that this function was not reading NSURL when UIPasteboard
+        serializes NSURL as a plist. This code is exercised by CopyURL.ValidURL.
+        * platform/mac/PlatformPasteboardMac.mm:
+        (WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Don't add custom pasteboard types that are
+        added by cross origin documents.
+        * platform/win/PasteboardWin.cpp:
+        (WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
+        (WebCore::Pasteboard::readOrigin): Added.
+        * platform/wpe/PasteboardWPE.cpp:
+        (WebCore::Pasteboard::typesSafeForBindings): Now takes the unused origin string.
+        (WebCore::Pasteboard::readOrigin): Added.
+        * platform/wpe/PlatformPasteboardWPE.cpp:
+        (WebCore::PlatformPasteboard::typesSafeForDOMToReadAndWrite const): Now takes the unused origin string.
+
 2017-10-11  Antti Koivisto  <antti@apple.com>
 
         Remove some obsolete layout assertions
index 5c6b227..4567084 100644 (file)
@@ -41,6 +41,7 @@
 #include "Pasteboard.h"
 #include "Settings.h"
 #include "StaticPasteboard.h"
+#include "URLParser.h"
 #include "WebCorePasteboardFileReader.h"
 
 namespace WebCore {
@@ -77,9 +78,19 @@ DataTransfer::DataTransfer(StoreMode mode, std::unique_ptr<Pasteboard> pasteboar
 #endif
 }
 
-Ref<DataTransfer> DataTransfer::createForCopyAndPaste(StoreMode storeMode, std::unique_ptr<Pasteboard>&& pasteboard)
+static String originIdentifierForDocument(Document& document)
 {
-    return adoptRef(*new DataTransfer(storeMode, WTFMove(pasteboard)));
+    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);
+    return dataTransfer;
 }
 
 DataTransfer::~DataTransfer()
@@ -121,11 +132,6 @@ static String normalizeType(const String& type)
     return lowercaseType;
 }
 
-static bool shouldReadOrWriteTypeAsCustomData(const String& type)
-{
-    return Settings::customPasteboardDataEnabled() && !Pasteboard::isSafeTypeForDOMToReadAndWrite(type);
-}
-
 void DataTransfer::clearData(const String& type)
 {
     if (!canWriteData())
@@ -149,9 +155,28 @@ String DataTransfer::getDataForItem(const String& type) const
         return { };
 
     auto lowercaseType = stripLeadingAndTrailingHTMLSpaces(type).convertToASCIILowercase();
-    if (shouldReadOrWriteTypeAsCustomData(lowercaseType))
-        return m_pasteboard->readStringInCustomData(lowercaseType);
-    return m_pasteboard->readString(lowercaseType);
+    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;
+    }
+
+    if (!isSameOrigin) {
+        if (!Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
+            return { };
+        return m_pasteboard->readString(lowercaseType);
+    }
+
+    String value = m_pasteboard->readStringInCustomData(lowercaseType);
+    if (value.isNull() && Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
+        value = m_pasteboard->readString(lowercaseType);
+    return value;
 }
 
 String DataTransfer::getData(const String& type) const
@@ -178,10 +203,24 @@ void DataTransfer::setDataFromItemList(const String& type, const String& data)
     ASSERT(canWriteData());
     RELEASE_ASSERT(is<StaticPasteboard>(*m_pasteboard));
 
-    if (shouldReadOrWriteTypeAsCustomData(type))
-        downcast<StaticPasteboard>(*m_pasteboard).writeStringInCustomData(type, data);
-    else
+    if (!Settings::customPasteboardDataEnabled()) {
         m_pasteboard->writeString(type, data);
+        return;
+    }
+
+    String sanitizedData;
+    if (type == "text/uri-list") {
+        auto url = URLParser(data).result();
+        if (url.isValid())
+            sanitizedData = url.string();
+    } else if (type == "text/plain")
+        sanitizedData = data; // Nothing to sanitize.
+
+    if (sanitizedData != data)
+        downcast<StaticPasteboard>(*m_pasteboard).writeStringInCustomData(type, data);
+
+    if (Pasteboard::isSafeTypeForDOMToReadAndWrite(type) && !sanitizedData.isNull())
+        m_pasteboard->writeString(type, sanitizedData);
 }
 
 void DataTransfer::updateFileList()
@@ -236,11 +275,11 @@ Vector<String> DataTransfer::types(AddFilesType addFilesType) const
         return addFilesType == AddFilesType::Yes ? Vector<String> { ASCIILiteral("Files") } : Vector<String> { };
 
     if (m_pasteboard->containsFiles()) {
-        ASSERT(!m_pasteboard->typesSafeForBindings().contains("Files"));
+        ASSERT(!m_pasteboard->typesSafeForBindings(m_originIdentifier).contains("Files"));
         return addFilesType == AddFilesType::Yes ? Vector<String> { ASCIILiteral("Files") } : Vector<String> { };
     }
 
-    auto types = m_pasteboard->typesSafeForBindings();
+    auto types = m_pasteboard->typesSafeForBindings(m_originIdentifier);
     ASSERT(!types.contains("Files"));
     return types;
 }
@@ -322,6 +361,22 @@ Ref<DataTransfer> DataTransfer::createForInputEvent(const String& plainText, con
     return adoptRef(*new DataTransfer(StoreMode::Readonly, WTFMove(pasteboard), Type::InputEvent));
 }
 
+void DataTransfer::commitToPasteboard(Pasteboard& nativePasteboard)
+{
+    ASSERT(is<StaticPasteboard>(*m_pasteboard) && !is<StaticPasteboard>(nativePasteboard));
+    PasteboardCustomData customData = downcast<StaticPasteboard>(*m_pasteboard).takeCustomData();
+    if (Settings::customPasteboardDataEnabled()) {
+        customData.origin = m_originIdentifier;
+        nativePasteboard.writeCustomData(customData);
+        return;
+    }
+
+    for (auto& entry : customData.platformData)
+        nativePasteboard.writeString(entry.key, entry.value);
+    for (auto& entry : customData.sameOriginCustomData)
+        nativePasteboard.writeString(entry.key, entry.value);
+}
+
 #if !ENABLE(DRAG_SUPPORT)
 
 String DataTransfer::dropEffect() const
@@ -353,15 +408,18 @@ Ref<DataTransfer> DataTransfer::createForDrag()
     return adoptRef(*new DataTransfer(StoreMode::ReadWrite, Pasteboard::createForDragAndDrop(), Type::DragAndDropData));
 }
 
-Ref<DataTransfer> DataTransfer::createForDragStartEvent()
+Ref<DataTransfer> DataTransfer::createForDragStartEvent(Document& document)
 {
-    return adoptRef(*new DataTransfer(StoreMode::ReadWrite, std::make_unique<StaticPasteboard>(), Type::DragAndDropData));
+    auto dataTransfer = adoptRef(*new DataTransfer(StoreMode::ReadWrite, std::make_unique<StaticPasteboard>(), Type::DragAndDropData));
+    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
+    return dataTransfer;
 }
 
-Ref<DataTransfer> DataTransfer::createForDrop(std::unique_ptr<Pasteboard>&& pasteboard, DragOperation sourceOperation, bool draggingFiles)
+Ref<DataTransfer> DataTransfer::createForDrop(Document& document, std::unique_ptr<Pasteboard>&& pasteboard, DragOperation sourceOperation, bool draggingFiles)
 {
     auto dataTransfer = adoptRef(*new DataTransfer(DataTransfer::StoreMode::Readonly, WTFMove(pasteboard), draggingFiles ? Type::DragAndDropFiles : Type::DragAndDropData));
     dataTransfer->setSourceOperation(sourceOperation);
+    dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
     return dataTransfer;
 }
 
@@ -376,6 +434,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);
     return dataTransfer;
 }
 
@@ -589,7 +648,7 @@ void DataTransfer::moveDragState(Ref<DataTransfer>&& other)
     // After pushing the static pasteboard's contents to the platform, the pasteboard should only
     // contain data that was in the static pasteboard.
     m_pasteboard->clear();
-    downcast<StaticPasteboard>(other->pasteboard()).commitToPasteboard(*m_pasteboard);
+    other->commitToPasteboard(*m_pasteboard);
 
     m_dropEffect = other->m_dropEffect;
     m_effectAllowed = other->m_effectAllowed;
index 818bbff..0f3f209 100644 (file)
@@ -45,7 +45,7 @@ public:
     // https://html.spec.whatwg.org/multipage/dnd.html#drag-data-store-mode
     enum class StoreMode { Invalid, ReadWrite, Readonly, Protected };
 
-    static Ref<DataTransfer> createForCopyAndPaste(StoreMode, std::unique_ptr<Pasteboard>&&);
+    static Ref<DataTransfer> createForCopyAndPaste(Document&, StoreMode, std::unique_ptr<Pasteboard>&&);
     static Ref<DataTransfer> createForInputEvent(const String& plainText, const String& htmlText);
 
     WEBCORE_EXPORT ~DataTransfer();
@@ -82,11 +82,12 @@ public:
     bool hasStringOfType(const String&);
 
     Pasteboard& pasteboard() { return *m_pasteboard; }
+    void commitToPasteboard(Pasteboard&);
 
 #if ENABLE(DRAG_SUPPORT)
     static Ref<DataTransfer> createForDrag();
-    static Ref<DataTransfer> createForDragStartEvent();
-    static Ref<DataTransfer> createForDrop(std::unique_ptr<Pasteboard>&&, DragOperation, bool draggingFiles);
+    static Ref<DataTransfer> createForDragStartEvent(Document&);
+    static Ref<DataTransfer> createForDrop(Document&, std::unique_ptr<Pasteboard>&&, DragOperation, bool draggingFiles);
     static Ref<DataTransfer> createForUpdatingDropTarget(Document&, std::unique_ptr<Pasteboard>&&, DragOperation, bool draggingFiles);
 
     bool dropEffectIsUninitialized() const { return m_dropEffect == "uninitialized"; }
@@ -124,6 +125,7 @@ private:
     Vector<String> types(AddFilesType) const;
     Vector<Ref<File>> filesFromPasteboardAndItemList() const;
 
+    String m_originIdentifier;
     StoreMode m_storeMode;
     std::unique_ptr<Pasteboard> m_pasteboard;
     std::unique_ptr<DataTransferItemList> m_itemList;
index 35399f6..40414c5 100644 (file)
 #include <wtf/NeverDestroyed.h>
 #include <wtf/SetForScope.h>
 #include <wtf/SystemTracing.h>
+#include <wtf/UUID.h>
 #include <wtf/text/StringBuffer.h>
 #include <yarr/RegularExpression.h>
 
@@ -5303,6 +5304,13 @@ bool Document::isTelephoneNumberParsingAllowed() const
 
 #endif
 
+String Document::uniqueIdentifier()
+{
+    if (!m_uniqueIdentifier)
+        m_uniqueIdentifier = "null:" + createCanonicalUUIDString();
+    return m_uniqueIdentifier;
+}
+
 ExceptionOr<Ref<XPathExpression>> Document::createExpression(const String& expression, RefPtr<XPathNSResolver>&& resolver)
 {
     if (!m_xpathEvaluator)
index d1ba38f..4cc6947 100644 (file)
@@ -957,6 +957,8 @@ public:
     void incDOMTreeVersion() { m_domTreeVersion = ++s_globalTreeVersion; }
     uint64_t domTreeVersion() const { return m_domTreeVersion; }
 
+    String uniqueIdentifier();
+
     // XPathEvaluator methods
     WEBCORE_EXPORT ExceptionOr<Ref<XPathExpression>> createExpression(const String& expression, RefPtr<XPathNSResolver>&&);
     WEBCORE_EXPORT Ref<XPathNSResolver> createNSResolver(Node* nodeResolver);
@@ -1517,7 +1519,9 @@ private:
 
     uint64_t m_domTreeVersion;
     static uint64_t s_globalTreeVersion;
-    
+
+    String m_uniqueIdentifier;
+
     HashSet<NodeIterator*> m_nodeIterators;
     HashSet<Range*> m_ranges;
 
index c4c25ab..4ca0a77 100644 (file)
@@ -345,30 +345,30 @@ static AtomicString eventNameForClipboardEvent(ClipboardEventKind kind)
     return { };
 }
 
-static Ref<DataTransfer> createDataTransferForClipboardEvent(ClipboardEventKind kind)
+static Ref<DataTransfer> createDataTransferForClipboardEvent(Document& document, ClipboardEventKind kind)
 {
     switch (kind) {
     case ClipboardEventKind::Copy:
     case ClipboardEventKind::Cut:
-        return DataTransfer::createForCopyAndPaste(DataTransfer::StoreMode::ReadWrite, std::make_unique<StaticPasteboard>());
+        return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::ReadWrite, std::make_unique<StaticPasteboard>());
     case ClipboardEventKind::PasteAsPlainText:
         if (Settings::customPasteboardDataEnabled()) {
             auto plainTextType = ASCIILiteral("text/plain");
             auto plainText = Pasteboard::createForCopyAndPaste()->readString(plainTextType);
             auto pasteboard = std::make_unique<StaticPasteboard>();
             pasteboard->writeString(plainTextType, plainText);
-            return DataTransfer::createForCopyAndPaste(DataTransfer::StoreMode::Readonly, WTFMove(pasteboard));
+            return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, WTFMove(pasteboard));
         }
         FALLTHROUGH;
     case ClipboardEventKind::Paste:
-        return DataTransfer::createForCopyAndPaste(DataTransfer::StoreMode::Readonly, Pasteboard::createForCopyAndPaste());
+        return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, Pasteboard::createForCopyAndPaste());
     case ClipboardEventKind::BeforeCopy:
     case ClipboardEventKind::BeforeCut:
     case ClipboardEventKind::BeforePaste:
-        return DataTransfer::createForCopyAndPaste(DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>());
+        return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>());
     }
     ASSERT_NOT_REACHED();
-    return DataTransfer::createForCopyAndPaste(DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>());
+    return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>());
 }
 
 // Returns whether caller should continue with "the default processing", which is the same as
@@ -380,7 +380,7 @@ static bool dispatchClipboardEvent(RefPtr<Element>&& target, ClipboardEventKind
     if (!target)
         return true;
 
-    auto dataTransfer = createDataTransferForClipboardEvent(kind);
+    auto dataTransfer = createDataTransferForClipboardEvent(target->document(), kind);
 
     ClipboardEvent::Init init;
     init.bubbles = true;
@@ -393,7 +393,7 @@ static bool dispatchClipboardEvent(RefPtr<Element>&& target, ClipboardEventKind
     if (noDefaultProcessing && (kind == ClipboardEventKind::Copy || kind == ClipboardEventKind::Cut)) {
         auto pasteboard = Pasteboard::createForCopyAndPaste();
         pasteboard->clear();
-        downcast<StaticPasteboard>(dataTransfer->pasteboard()).commitToPasteboard(*pasteboard);
+        dataTransfer->commitToPasteboard(*pasteboard);
     }
 
     dataTransfer->makeInvalidForSecurity();
index a47e139..0da3bc4 100644 (file)
@@ -506,6 +506,7 @@ bool DragController::dispatchTextInputEventFor(Frame* innerFrame, const DragData
     ASSERT(m_page.dragCaretController().hasCaret());
     String text = m_page.dragCaretController().isContentRichlyEditable() ? emptyString() : dragData.asPlainText();
     Element* target = innerFrame->editor().findEventTargetFrom(m_page.dragCaretController().caretPosition());
+    // FIXME: What guarantees target is not null?
     return target->dispatchEvent(TextEvent::createForDrop(innerFrame->document()->domWindow(), text));
 }
 
index 4173979..24abc2f 100644 (file)
@@ -2389,7 +2389,7 @@ bool EventHandler::performDragAndDrop(const PlatformMouseEvent& event, std::uniq
         if (targetFrame)
             preventedDefault = targetFrame->eventHandler().performDragAndDrop(event, WTFMove(pasteboard), sourceOperation, draggingFiles);
     } else if (m_dragTarget) {
-        auto dataTransfer = DataTransfer::createForDrop(WTFMove(pasteboard), sourceOperation, draggingFiles);
+        auto dataTransfer = DataTransfer::createForDrop(m_dragTarget->document(), WTFMove(pasteboard), sourceOperation, draggingFiles);
         preventedDefault = dispatchDragEvent(eventNames().dropEvent, *m_dragTarget, event, dataTransfer);
         dataTransfer->makeInvalidForSecurity();
     }
@@ -3640,7 +3640,8 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr
     
     if (!m_mouseDownMayStartDrag)
         return !mouseDownMayStartSelect() && !m_mouseDownMayStartAutoscroll;
-    
+    ASSERT(dragState().source);
+
     if (!ExactlyOneBitSet(dragState().type)) {
         ASSERT((dragState().type & DragSourceActionSelection));
 #if ENABLE(ATTACHMENT_ELEMENT)
@@ -3677,7 +3678,8 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr
     HasNonDefaultPasteboardData hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No;
     
     if (dragState().shouldDispatchEvents) {
-        auto dragStartDataTransfer = DataTransfer::createForDragStartEvent();
+        ASSERT(dragState().source);
+        auto dragStartDataTransfer = DataTransfer::createForDragStartEvent(dragState().source->document());
         m_mouseDownMayStartDrag = dispatchDragStartEventOnSourceElement(dragStartDataTransfer);
         hasNonDefaultPasteboardData = dragStartDataTransfer->pasteboard().hasData() ? HasNonDefaultPasteboardData::Yes : HasNonDefaultPasteboardData::No;
         dragState().dataTransfer->moveDragState(WTFMove(dragStartDataTransfer));
index 14d089b..620a504 100644 (file)
@@ -193,8 +193,9 @@ public:
     virtual bool isStatic() const { return false; }
 
     virtual bool hasData();
-    virtual Vector<String> typesSafeForBindings();
+    virtual Vector<String> typesSafeForBindings(const String& origin);
     virtual Vector<String> typesForLegacyUnsafeBindings();
+    virtual String readOrigin();
     virtual String readString(const String& type);
     virtual String readStringInCustomData(const String& type);
 
@@ -211,6 +212,8 @@ public:
     virtual void write(const PasteboardImage&);
     virtual void write(const PasteboardWebContent&);
 
+    virtual void writeCustomData(const PasteboardCustomData&);
+
     virtual bool containsFiles();
     virtual bool canSmartReplace();
 
@@ -250,6 +253,7 @@ public:
     WEBCORE_EXPORT static NSArray *supportedFileUploadPasteboardTypes();
     const String& name() const { return m_pasteboardName; }
     long changeCount() const;
+    const PasteboardCustomData& readCustomData();
 #endif
 
 #if PLATFORM(WIN)
@@ -261,8 +265,6 @@ public:
     void writeImageToDataObject(Element&, const URL&); // FIXME: Layering violation.
 #endif
 
-    void writeCustomData(const PasteboardCustomData&);
-
 private:
 #if PLATFORM(IOS)
     bool respectsUTIFidelities() const;
@@ -295,6 +297,7 @@ private:
 #if PLATFORM(COCOA)
     String m_pasteboardName;
     long m_changeCount;
+    std::optional<PasteboardCustomData> m_customDataCache;
 #endif
 
 #if PLATFORM(WIN)
index b2684c9..c064ad0 100644 (file)
@@ -72,7 +72,7 @@ public:
     virtual long setStringForType(const String&, const String& pasteboardType, const String& pasteboardName) = 0;
 #endif
 
-    virtual Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName) = 0;
+    virtual Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin) = 0;
     virtual long writeCustomData(const PasteboardCustomData&, const String& pasteboardName) = 0;
 
 #if PLATFORM(GTK)
index 3954c21..4f4ab0a 100644 (file)
@@ -95,7 +95,7 @@ public:
     WEBCORE_EXPORT int numberOfFiles() const;
 
     WEBCORE_EXPORT long write(const PasteboardCustomData&);
-    WEBCORE_EXPORT Vector<String> typesSafeForDOMToReadAndWrite() const;
+    WEBCORE_EXPORT Vector<String> typesSafeForDOMToReadAndWrite(const String& origin) const;
 
 #if PLATFORM(GTK)
     WEBCORE_EXPORT void writeToClipboard(const SelectionData&, WTF::Function<void()>&& primarySelectionCleared);
index 7c0aa3d..17313e4 100644 (file)
@@ -26,9 +26,6 @@
 #include "config.h"
 #include "StaticPasteboard.h"
 
-#include "Settings.h"
-#include "SharedBuffer.h"
-
 namespace WebCore {
 
 StaticPasteboard::StaticPasteboard()
@@ -85,20 +82,9 @@ void StaticPasteboard::clear(const String& type)
     ASSERT(!m_types.contains(type));
 }
 
-void StaticPasteboard::commitToPasteboard(Pasteboard& pasteboard)
+PasteboardCustomData StaticPasteboard::takeCustomData()
 {
-    if (m_platformData.isEmpty() && m_customData.isEmpty())
-        return;
-
-    if (Settings::customPasteboardDataEnabled()) {
-        pasteboard.writeCustomData({ { }, WTFMove(m_types), WTFMove(m_platformData), WTFMove(m_customData) });
-        return;
-    }
-
-    for (auto& entry : m_platformData)
-        pasteboard.writeString(entry.key, entry.value);
-    for (auto& entry : m_customData)
-        pasteboard.writeString(entry.key, entry.value);
+    return { { }, WTFMove(m_types), WTFMove(m_platformData), WTFMove(m_customData) };
 }
 
 }
index 9cfcc72..6a3934d 100644 (file)
@@ -36,13 +36,14 @@ class StaticPasteboard final : public Pasteboard {
 public:
     StaticPasteboard();
 
-    void commitToPasteboard(Pasteboard&);
+    PasteboardCustomData takeCustomData();
 
     bool isStatic() const final { return true; }
 
     bool hasData() final;
-    Vector<String> typesSafeForBindings() final { return m_types; }
+    Vector<String> typesSafeForBindings(const String&) final { return m_types; }
     Vector<String> typesForLegacyUnsafeBindings() final { return m_types; }
+    String readOrigin() final { return { }; }
     String readString(const String& type) final;
     String readStringInCustomData(const String& type) final;
 
@@ -58,6 +59,8 @@ public:
     void write(const PasteboardImage&) final { }
     void write(const PasteboardWebContent&) final { }
 
+    void writeCustomData(const PasteboardCustomData&) final { }
+
     bool containsFiles() final { return false; }
     bool canSmartReplace() final { return false; }
 
index 0777e48..598318a 100644 (file)
@@ -143,9 +143,9 @@ bool Pasteboard::containsFiles()
     return true;
 }
 
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String& origin)
 {
-    Vector<String> types = platformStrategies()->pasteboardStrategy()->typesSafeForDOMToReadAndWrite(m_pasteboardName);
+    Vector<String> types = platformStrategies()->pasteboardStrategy()->typesSafeForDOMToReadAndWrite(m_pasteboardName, origin);
 
     // Enforce changeCount ourselves for security. We check after reading instead of before to be
     // sure it doesn't change between our testing the change count and accessing the data.
@@ -216,16 +216,24 @@ String Pasteboard::readString(const String& type)
 
 String Pasteboard::readStringInCustomData(const String& type)
 {
-    auto buffer = readBufferForTypeWithSecurityCheck(PasteboardCustomData::cocoaType());
-    if (!buffer)
-        return { };
+    return readCustomData().sameOriginCustomData.get(type);
+}
 
-    // Enforce changeCount ourselves for security. We check after reading instead of before to be
-    // sure it doesn't change between our testing the change count and accessing the data.
-    if (m_changeCount != platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
-        return { };
+String Pasteboard::readOrigin()
+{
+    return readCustomData().origin;
+}
 
-    return PasteboardCustomData::fromSharedBuffer(*buffer).sameOriginCustomData.get(type);
+const PasteboardCustomData& Pasteboard::readCustomData()
+{
+    if (m_customDataCache)
+        return *m_customDataCache;
+
+    if (auto buffer = readBufferForTypeWithSecurityCheck(PasteboardCustomData::cocoaType()))
+        m_customDataCache = PasteboardCustomData::fromSharedBuffer(*buffer);
+    else
+        m_customDataCache = PasteboardCustomData { };
+    return *m_customDataCache; 
 }
 
 void Pasteboard::writeCustomData(const PasteboardCustomData& data)
index c89d75b..ac26a7f 100644 (file)
@@ -255,7 +255,7 @@ bool Pasteboard::hasData()
     return m_selectionData->hasText() || m_selectionData->hasMarkup() || m_selectionData->hasURIList() || m_selectionData->hasImage() || m_selectionData->hasUnknownTypeData();
 }
 
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String&)
 {
     notImplemented(); // webkit.org/b/177633: [GTK] Move to new Pasteboard API
     return { };
@@ -286,6 +286,12 @@ Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
     return types;
 }
 
+String Pasteboard::readOrigin()
+{
+    notImplemented(); // webkit.org/b/177633: [GTK] Move to new Pasteboard API
+    return { };
+}
+
 String Pasteboard::readString(const String& type)
 {
     readFromClipboard();
index b9334da..f308c2b 100644 (file)
@@ -46,7 +46,7 @@ Ref<SelectionData> PlatformPasteboard::readFromClipboard()
     return selection;
 }
 
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
 {
     return { };
 }
index bfcecaf..f30903f 100644 (file)
@@ -343,7 +343,10 @@ static const char *safeTypeForDOMToReadAndWriteForPlatformType(const String& pla
     return nullptr;
 }
 
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+static const char originKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.origin";
+static const char customTypesKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.custom-types";
+
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String& origin) const
 {
     ListHashSet<String> domPasteboardTypes;
     for (NSItemProvider *provider in [m_pasteboard itemProviders]) {
@@ -354,7 +357,13 @@ Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
         if (!teamDataObject || ![teamDataObject isKindOfClass:[NSDictionary class]])
             continue;
 
-        id customTypes = [(NSDictionary *)teamDataObject objectForKey:@(PasteboardCustomData::cocoaType())];
+        id originInTeamData = [(NSDictionary *)teamDataObject objectForKey:@(originKeyForTeamData)];
+        if (![originInTeamData isKindOfClass:[NSString class]])
+            continue;
+        if (String((NSString *)originInTeamData) != origin)
+            continue;
+
+        id customTypes = [(NSDictionary *)teamDataObject objectForKey:@(customTypesKeyForTeamData)];
         if (![customTypes isKindOfClass:[NSArray class]])
             continue;
 
@@ -363,9 +372,11 @@ Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
     }
 
     if (NSData *serializedCustomData = [m_pasteboard dataForPasteboardType:@(PasteboardCustomData::cocoaType())]) {
-        auto buffer = SharedBuffer::create(serializedCustomData);
-        for (auto& type : PasteboardCustomData::fromSharedBuffer(buffer.get()).orderedTypes)
-            domPasteboardTypes.add(type);
+        auto data = PasteboardCustomData::fromSharedBuffer(SharedBuffer::create(serializedCustomData).get());
+        if (data.origin == origin) {
+            for (auto& type : data.orderedTypes)
+                domPasteboardTypes.add(type);
+        }
     }
 
     for (NSString *type in [m_pasteboard pasteboardTypes]) {
@@ -402,7 +413,8 @@ long PlatformPasteboard::write(const PasteboardCustomData& data)
             NSMutableArray<NSString *> *typesAsNSArray = [NSMutableArray array];
             for (auto& type : data.orderedTypes)
                 [typesAsNSArray addObject:type];
-            [representationsToRegister setTeamData:[NSKeyedArchiver archivedDataWithRootObject:@{ @(PasteboardCustomData::cocoaType()) : typesAsNSArray }]];
+            [representationsToRegister setTeamData:[NSKeyedArchiver archivedDataWithRootObject:@{
+                @(originKeyForTeamData) : data.origin, @(customTypesKeyForTeamData) : typesAsNSArray }]];
             [representationsToRegister addData:serializedSharedBuffer.get() forType:@(PasteboardCustomData::cocoaType())];
         }
     }
@@ -448,7 +460,7 @@ void PlatformPasteboard::write(const PasteboardURL&)
 {
 }
 
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
 {
     return { };
 }
@@ -516,27 +528,41 @@ String PlatformPasteboard::readString(int index, const String& type)
 URL PlatformPasteboard::readURL(int index, const String& type, String& title)
 {
     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
-
     RetainPtr<NSArray> pasteboardItem = [m_pasteboard valuesForPasteboardType:type inItemSet:indexSet];
 
     if (![pasteboardItem count])
-        return URL();
+        return { };
 
     id value = [pasteboardItem objectAtIndex:0];
-    ASSERT([value isKindOfClass:[NSURL class]]);
-    if (![value isKindOfClass:[NSURL class]])
-        return URL();
+    NSURL *url = nil;
+    if ([value isKindOfClass:[NSData class]]) {
+        id plist = [NSPropertyListSerialization propertyListWithData:(NSData *)value options:NSPropertyListImmutable format:NULL error:NULL];
+        if (![plist isKindOfClass:[NSArray class]])
+            return { };
+        NSArray *plistArray = (NSArray *)plist;
+        if (plistArray.count < 2)
+            return { };
+        if (plistArray.count == 2)
+            url = [NSURL URLWithString:plistArray[0]];
+        else // The first string is the relative URL.
+            url = [NSURL URLWithString:plistArray[0] relativeToURL:[NSURL URLWithString:plistArray[1]]];
+    } else {
+        ASSERT([value isKindOfClass:[NSURL class]]);
+        if (![value isKindOfClass:[NSURL class]])
+            return { };
+        url = (NSURL *)value;
+    }
 
-    if (!allowReadingURLAtIndex((NSURL *)value, index))
+    if (!allowReadingURLAtIndex(url, index))
         return { };
 
 #if PLATFORM(IOS) && !(PLATFORM(WATCHOS) || PLATFORM(APPLETV))
-    title = [value _title];
+    title = [url _title];
 #else
     UNUSED_PARAM(title);
 #endif
 
-    return (NSURL *)value;
+    return url;
 }
 
 void PlatformPasteboard::updateSupportedTypeIdentifiers(const Vector<String>& types)
index 4010f30..ff022d4 100644 (file)
@@ -107,13 +107,15 @@ static const char* safeTypeForDOMToReadAndWriteForPlatformType(const String& pla
     return nullptr;
 }
 
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String& origin) const
 {
     ListHashSet<String> domPasteboardTypes;
     if (NSData *serializedCustomData = [m_pasteboard dataForType:@(PasteboardCustomData::cocoaType())]) {
-        auto buffer = SharedBuffer::create(serializedCustomData);
-        for (auto& type : PasteboardCustomData::fromSharedBuffer(buffer.get()).orderedTypes)
-            domPasteboardTypes.add(type);
+        auto data = PasteboardCustomData::fromSharedBuffer(SharedBuffer::create(serializedCustomData).get());
+        if (data.origin == origin) {
+            for (auto& type : data.orderedTypes)
+                domPasteboardTypes.add(type);
+        }
     }
 
     NSArray<NSString *> *allTypes = [m_pasteboard types];
index af15ed5..336e550 100644 (file)
@@ -237,7 +237,7 @@ static void addMimeTypesForFormat(ListHashSet<String>& results, const FORMATETC&
         results.add("text/plain");
 }
 
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String&)
 {
     notImplemented();
     return { };
@@ -277,6 +277,12 @@ Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
     return vector;
 }
 
+String Pasteboard::readOrigin()
+{
+    notImplemented();
+    return { };
+}
+
 String Pasteboard::readString(const String& type)
 {
     if (!m_dataObject && m_dragDataMap.isEmpty())
index f232dff..247af55 100644 (file)
@@ -49,7 +49,7 @@ bool Pasteboard::hasData()
     return !types.isEmpty();
 }
 
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String&)
 {
     notImplemented();
     return { };
@@ -62,6 +62,12 @@ Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
     return types;
 }
 
+String Pasteboard::readOrigin()
+{
+    notImplemented(); // webkit.org/b/177633: [GTK] Move to new Pasteboard API
+    return { };
+}
+
 String Pasteboard::readString(const String& type)
 {
     return platformStrategies()->pasteboardStrategy()->readStringFromPasteboard(0, type);
index 3bded13..610f2fd 100644 (file)
@@ -116,7 +116,7 @@ void PlatformPasteboard::write(const String& type, const String& string)
     wpe_pasteboard_string_free(&pairs[0].string);
 }
 
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
 {
     return { };
 }
index 6fad9ac..58ebcd7 100644 (file)
@@ -1,3 +1,23 @@
+2017-10-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Sanitize URL in pasteboard for other applications and cross origin content
+        https://bugs.webkit.org/show_bug.cgi?id=178060
+        <rdar://problem/34874518>
+
+        Reviewed by Wenson Hsieh.
+
+        Plubmed the origin identifier through IPC from Pasteboard in WebContent process to PlatformPasteboard in UIProcess.
+
+        * UIProcess/Cocoa/WebPasteboardProxyCocoa.mm:
+        (WebKit::WebPasteboardProxy::typesSafeForDOMToReadAndWrite):
+        * UIProcess/WebPasteboardProxy.cpp:
+        (WebKit::WebPasteboardProxy::typesSafeForDOMToReadAndWrite):
+        * UIProcess/WebPasteboardProxy.h:
+        * UIProcess/WebPasteboardProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebPlatformStrategies.cpp:
+        (WebKit::WebPlatformStrategies::typesSafeForDOMToReadAndWrite):
+        * WebProcess/WebCoreSupport/WebPlatformStrategies.h:
+
 2017-10-11  Chris Dumez  <cdumez@apple.com>
 
         Modernize Geolocation code
index 576d243..63254d9 100644 (file)
@@ -159,9 +159,9 @@ void WebPasteboardProxy::getNumberOfFiles(const String& pasteboardName, uint64_t
     numberOfFiles = PlatformPasteboard(pasteboardName).numberOfFiles();
 }
 
-void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String& pasteboardName, Vector<String>& types)
+void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin, Vector<String>& types)
 {
-    types = PlatformPasteboard(pasteboardName).typesSafeForDOMToReadAndWrite();
+    types = PlatformPasteboard(pasteboardName).typesSafeForDOMToReadAndWrite(origin);
 }
 
 void WebPasteboardProxy::writeCustomData(const WebCore::PasteboardCustomData& data, const String& pasteboardName, uint64_t& newChangeCount)
index 108d3dd..0ee9945 100644 (file)
@@ -63,7 +63,7 @@ void WebPasteboardProxy::removeWebProcessProxy(WebProcessProxy& webProcessProxy)
 
 #if !PLATFORM(COCOA)
 
-void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String&, Vector<String>& types)
+void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String&, const String&, Vector<String>& types)
 {
     types = { };
 }
index 770930a..6cede05 100644 (file)
@@ -99,7 +99,7 @@ private:
 #endif
 
     void writeCustomData(const WebCore::PasteboardCustomData&, const String& pasteboardName, uint64_t& newChangeCount);
-    void typesSafeForDOMToReadAndWrite(const String& pasteboardName, Vector<String>& types);
+    void typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin, Vector<String>& types);
 
 #if PLATFORM(GTK)
     void writeToClipboard(const String& pasteboardName, const WebSelectionData&);
index cd2841f..be16184 100644 (file)
@@ -36,7 +36,7 @@ messages -> WebPasteboardProxy {
 #endif
 
     WriteCustomData(struct WebCore::PasteboardCustomData data, String pasteboardName) -> (uint64_t changeCount)
-    TypesSafeForDOMToReadAndWrite(String pasteboardName) -> (Vector<String> types)
+    TypesSafeForDOMToReadAndWrite(String pasteboardName, String origin) -> (Vector<String> types)
 
 #if PLATFORM(COCOA)
     # Pasteboard messages.
index 84f73f0..56474ba 100644 (file)
@@ -405,10 +405,10 @@ void WebPlatformStrategies::writeToPasteboard(const String& pasteboardType, cons
 
 #endif // PLATFORM(WPE)
 
-Vector<String> WebPlatformStrategies::typesSafeForDOMToReadAndWrite(const String& pasteboardName)
+Vector<String> WebPlatformStrategies::typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin)
 {
     Vector<String> types;
-    WebProcess::singleton().parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::TypesSafeForDOMToReadAndWrite(pasteboardName), Messages::WebPasteboardProxy::TypesSafeForDOMToReadAndWrite::Reply(types), 0);
+    WebProcess::singleton().parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::TypesSafeForDOMToReadAndWrite(pasteboardName, origin), Messages::WebPasteboardProxy::TypesSafeForDOMToReadAndWrite::Reply(types), 0);
     return types;
 }
 
index d0eaa39..20ac6db 100644 (file)
@@ -97,7 +97,7 @@ private:
     void writeToPasteboard(const String& pasteboardType, const String&) override;
 #endif
 
-    Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName) override;
+    Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin) override;
     long writeCustomData(const WebCore::PasteboardCustomData&, const String&) override;
 };
 
index 4f28c5e..ffba3d5 100644 (file)
@@ -1,3 +1,15 @@
+2017-10-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Sanitize URL in pasteboard for other applications and cross origin content
+        https://bugs.webkit.org/show_bug.cgi?id=178060
+        <rdar://problem/34874518>
+
+        Reviewed by Wenson Hsieh.
+
+        * WebCoreSupport/WebPlatformStrategies.h:
+        * WebCoreSupport/WebPlatformStrategies.mm:
+        (WebPlatformStrategies::typesSafeForDOMToReadAndWrite):
+
 2017-10-11  Chris Dumez  <cdumez@apple.com>
 
         Modernize Geolocation code
index b783abd..a03eeac 100644 (file)
@@ -81,7 +81,7 @@ private:
     WebCore::URL url(const String& pasteboardName) override;
 
     long writeCustomData(const WebCore::PasteboardCustomData&, const String& pasteboardName) override;
-    Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName) override;
+    Vector<String> typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin) override;
 
     long addTypes(const Vector<String>& pasteboardTypes, const String& pasteboardName) override;
     long setTypes(const Vector<String>& pasteboardTypes, const String& pasteboardName) override;
index caa046b..5fbec38 100644 (file)
@@ -178,9 +178,9 @@ int WebPlatformStrategies::getNumberOfFiles(const String& pasteboardName)
     return PlatformPasteboard(pasteboardName).numberOfFiles();
 }
 
-Vector<String> WebPlatformStrategies::typesSafeForDOMToReadAndWrite(const String& pasteboardName)
+Vector<String> WebPlatformStrategies::typesSafeForDOMToReadAndWrite(const String& pasteboardName, const String& origin)
 {
-    return PlatformPasteboard(pasteboardName).typesSafeForDOMToReadAndWrite();
+    return PlatformPasteboard(pasteboardName).typesSafeForDOMToReadAndWrite(origin);
 }
 
 long WebPlatformStrategies::writeCustomData(const WebCore::PasteboardCustomData& data, const String& pasteboardName)
index a4ca806..27f192e 100644 (file)
@@ -1,3 +1,24 @@
+2017-10-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Sanitize URL in pasteboard for other applications and cross origin content
+        https://bugs.webkit.org/show_bug.cgi?id=178060
+        <rdar://problem/34874518>
+
+        Reviewed by Wenson Hsieh.
+
+        Added API tests for sanitizing URLs copied from web content, and that the original URL is exposed to the web content.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm: Added.
+        (readURLFromPasteboard): A helper function.
+        * TestWebKitAPI/Tests/WebKitCocoa/copy-url.html: Added.
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (DataInteractionTests.DataTransferGetDataWhenDroppingCustomData): Rebaselined. https://www.apple.com is no longer
+        normalized to https://www.apple.com/ by NSURL / UIPasteboard as expected.
+        (DataInteractionTests.DataTransferSetDataValidURL): Added.
+        (DataInteractionTests.DataTransferSetDataUnescapedURL): Added.
+        (DataInteractionTests.qDataTransferSetDataInvalidURL): Added.
+
 2017-10-11  Chris Dumez  <cdumez@apple.com>
 
         Modernize Geolocation code
index 52c19df..30165c7 100644 (file)
                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 */; };
+               9B62630C1F8C25C8007EE29B /* copy-url.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B62630B1F8C2510007EE29B /* copy-url.html */; };
+               9B7A37C41F8AEBA5004AA228 /* CopyURL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */; };
                9B7D740F1F8378770006C432 /* paste-rtfd.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9B7D740E1F8377E60006C432 /* paste-rtfd.html */; };
                9BD4239A1E04BD9800200395 /* AttributedSubstringForProposedRangeWithImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9BD423991E04BD9800200395 /* AttributedSubstringForProposedRangeWithImage.mm */; };
                9BD4239C1E04C01C00200395 /* chinese-character-with-image.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9BD4239B1E04BFD000200395 /* chinese-character-with-image.html */; };
                                5142B2731517C8C800C32B19 /* ContextMenuCanCopyURL.html in Copy Resources */,
                                CD0BD0A81F79982D001AB2CF /* ContextMenuImgWithVideo.html in Copy Resources */,
                                5C2936961D5C00ED00DEAB1E /* CookieMessage.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 */,
                                290F4275172A221C00939FF0 /* custom-protocol-sync-xhr.html in Copy Resources */,
                9B270FED1DDC25FD002D53F3 /* closed-shadow-tree-test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "closed-shadow-tree-test.html"; sourceTree = "<group>"; };
                9B4F8FA3159D52B1002D9F94 /* HTMLCollectionNamedItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HTMLCollectionNamedItem.mm; sourceTree = "<group>"; };
                9B4F8FA6159D52CA002D9F94 /* HTMLCollectionNamedItem.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = HTMLCollectionNamedItem.html; sourceTree = "<group>"; };
+               9B62630B1F8C2510007EE29B /* copy-url.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "copy-url.html"; sourceTree = "<group>"; };
                9B79164F1BD89D0D00D50B8F /* FirstResponderScrollingPosition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FirstResponderScrollingPosition.mm; sourceTree = "<group>"; };
+               9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CopyURL.mm; sourceTree = "<group>"; };
                9B7D740E1F8377E60006C432 /* paste-rtfd.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "paste-rtfd.html"; sourceTree = "<group>"; };
                9BD423991E04BD9800200395 /* AttributedSubstringForProposedRangeWithImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AttributedSubstringForProposedRangeWithImage.mm; sourceTree = "<group>"; };
                9BD4239B1E04BFD000200395 /* chinese-character-with-image.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "chinese-character-with-image.html"; sourceTree = "<group>"; };
                                5CA1DED81F74A87100E71BD3 /* ContentRuleListNotification.mm */,
                                5C2936911D5BF63E00DEAB1E /* CookieAcceptPolicy.mm */,
                                9999108A1F393C8B008AD455 /* Copying.mm */,
+                               9B7A37C21F8AEBA5004AA228 /* CopyURL.mm */,
                                2DC4CF761D2D9DD800ECCC94 /* DataDetection.mm */,
                                CEC16EA41EE863BF00DE479A /* DecidePolicyForNavigationAction.mm */,
                                2DC60E221E79F88C00FA6C7D /* DoAfterNextPresentationUpdateAfterCrash.mm */,
                                F4A32ECA1F0642F40047C544 /* contenteditable-in-iframe.html */,
                                A16F66B91C40EA2000BD4D24 /* ContentFiltering.html */,
                                5C2936941D5BFD1900DEAB1E /* CookieMessage.html */,
+                               9B62630B1F8C2510007EE29B /* copy-url.html */,
                                F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */,
                                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */,
                                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */,
                                5C2936931D5BF70D00DEAB1E /* CookieAcceptPolicy.mm in Sources */,
                                51D1249B1E785425002B2820 /* CookieManager.cpp in Sources */,
                                9999108B1F393C96008AD455 /* Copying.mm in Sources */,
+                               9B7A37C41F8AEBA5004AA228 /* CopyURL.mm in Sources */,
                                7CCE7EAC1A411A3400447C4C /* Counters.cpp in Sources */,
                                7AEAD47F1E20116C00416EFE /* CrossPartitionFileSchemeAccess.mm in Sources */,
                                7CCE7EDB1A411A9200447C4C /* CSSParser.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/CopyURL.mm
new file mode 100644 (file)
index 0000000..5edbf43
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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 *readURLFromPasteboard()
+{
+    if (NSURL *url = [NSURL URLFromPasteboard:[NSPasteboard generalPasteboard]])
+        return url.absoluteString;
+    NSURL *url = [NSURL URLWithString:[[NSPasteboard generalPasteboard] stringForType:NSURLPboardType]];
+    return url.absoluteString;
+}
+#else
+NSString *readURLFromPasteboard()
+{
+    return [UIPasteboard generalPasteboard].URL.absoluteString;
+}
+#endif
+
+TEST(CopyURL, ValidURL)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView synchronouslyLoadTestPageNamed:@"copy-url"];
+    [webView stringByEvaluatingJavaScript:@"URLToCopy = 'http://webkit.org/b/123';"];
+    [webView copy:nil];
+    [webView paste:nil];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didCopy"].boolValue);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didPaste"].boolValue);
+    EXPECT_WK_STREQ(@"http://webkit.org/b/123", [webView stringByEvaluatingJavaScript:@"window.pastedURL"]);
+    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)]);
+    [webView synchronouslyLoadTestPageNamed:@"copy-url"];
+    [webView stringByEvaluatingJavaScript:@"URLToCopy = 'http://webkit.org/b/\u4F60\u597D;?x=8 + 6';"];
+    [webView copy:nil];
+    [webView paste:nil];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didCopy"].boolValue);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didPaste"].boolValue);
+    EXPECT_WK_STREQ(@"http://webkit.org/b/\u4F60\u597D;?x=8 + 6", [webView stringByEvaluatingJavaScript:@"window.pastedURL"]);
+    EXPECT_WK_STREQ(@"http://webkit.org/b/%E4%BD%A0%E5%A5%BD;?x=8%20+%206", readURLFromPasteboard());
+}
+
+TEST(CopyURL, MalformedURL)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView synchronouslyLoadTestPageNamed:@"copy-url"];
+    [webView stringByEvaluatingJavaScript:@"URLToCopy = 'bad url';"];
+    [webView copy:nil];
+    [webView paste:nil];
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didCopy"].boolValue);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"window.didPaste"].boolValue);
+    EXPECT_WK_STREQ(@"bad url", [webView stringByEvaluatingJavaScript:@"window.pastedURL"]);
+    EXPECT_WK_STREQ(@"", readURLFromPasteboard());
+}
+
+#endif
+
+#endif // WK_API_ENABLED && PLATFORM(MAC)
+
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-url.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/copy-url.html
new file mode 100644 (file)
index 0000000..d84191d
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="editor" oncopy="copy(event)" onpaste="paste(event)" contenteditable>some text</div>
+<script>
+
+var URLToCopy = '';
+
+function copy(event) {
+    event.clipboardData.setData(`url`, URLToCopy);
+    event.preventDefault();
+    didCopy = true;
+}
+
+function paste(event) {
+    pastedURL = event.clipboardData.getData(`url`);
+    didPaste = true;
+}
+
+editor.focus();
+</script>
+</body>
+</html>
index a747824..4c9dc0f 100644 (file)
@@ -1521,7 +1521,7 @@ TEST(DataInteractionTests, DataTransferGetDataWhenDroppingCustomData)
             @"foo/plain" : @"eva lu ator",
             @"text/html" : @"<b>bold text</b>",
             @"bar/html" : @"<i>italic text</i>",
-            @"text/uri-list" : @"https://www.apple.com/",
+            @"text/uri-list" : @"https://www.apple.com",
             @"baz/uri-list" : @"https://www.webkit.org"
         }
     });
@@ -1645,6 +1645,103 @@ TEST(DataInteractionTests, DataTransferGetDataCannotReadPrivateArbitraryTypes)
     });
 }
 
+TEST(DataInteractionTests, DataTransferSetDataValidURL)
+{
+    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 = { 'url' : 'https://webkit.org/b/123' }"];
+    [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 *)kUTTypeURL]);
+        EXPECT_TRUE([item canLoadObjectOfClass: [NSURL class]]);
+        [item loadObjectOfClass:[NSURL class] completionHandler:^(id<NSItemProviderReading> url, NSError *error) {
+            EXPECT_TRUE([url isKindOfClass: [NSURL class]]);
+            EXPECT_WK_STREQ([(NSURL *)url absoluteString], @"https://webkit.org/b/123");
+            done = true;
+        }];
+        return session.items;
+    }];
+    [simulator runFrom:CGPointMake(50, 225) to:CGPointMake(50, 375)];
+
+    checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{
+        @"dragover": @{
+            @"text/uri-list": @"",
+        },
+        @"drop": @{
+            @"text/uri-list": @"https://webkit.org/b/123",
+        }
+    });
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(DataInteractionTests, DataTransferSetDataUnescapedURL)
+{
+    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 = { 'url' : 'http://webkit.org/b/\u4F60\u597D;?x=8 + 6' }"];
+    [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 *)kUTTypeURL]);
+        EXPECT_TRUE([item canLoadObjectOfClass: [NSURL class]]);
+        [item loadObjectOfClass:[NSURL class] completionHandler:^(id<NSItemProviderReading> url, NSError *error) {
+            EXPECT_TRUE([url isKindOfClass: [NSURL class]]);
+            EXPECT_WK_STREQ([(NSURL *)url absoluteString], @"http://webkit.org/b/%E4%BD%A0%E5%A5%BD;?x=8%20+%206");
+            done = true;
+        }];
+        return session.items;
+    }];
+    [simulator runFrom:CGPointMake(50, 225) to:CGPointMake(50, 375)];
+
+    checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{
+        @"dragover": @{
+            @"text/uri-list": @"",
+        },
+        @"drop": @{
+            @"text/uri-list": @"http://webkit.org/b/\u4F60\u597D;?x=8 + 6",
+        }
+    });
+    TestWebKitAPI::Util::run(&done);
+}
+
+TEST(DataInteractionTests, DataTransferSetDataInvalidURL)
+{
+    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 = { 'url' : 'some random string' }"];
+    [webView stringByEvaluatingJavaScript:@"writeCustomData = true"];
+
+    [simulator runFrom:CGPointMake(50, 225) to:CGPointMake(50, 375)];
+    NSArray *registeredTypes = [simulator.get().sourceItemProviders.firstObject registeredTypeIdentifiers];
+    EXPECT_FALSE([registeredTypes containsObject:(NSString *)kUTTypeURL]);
+    checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{
+        @"dragover": @{
+            @"text/uri-list": @"",
+        },
+        @"drop": @{
+            @"text/uri-list": @"some random string",
+        }
+    });
+}
+
 #endif // __IPHONE_OS_VERSION_MIN_REQUIRED >= 110300
 
 } // namespace TestWebKitAPI