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
+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
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 ]
--- /dev/null
+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
+
--- /dev/null
+<!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>
"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"
}
"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"
}
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+<!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>
--- /dev/null
+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
+
--- /dev/null
+<!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/🤔?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>
--- /dev/null
+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
+
--- /dev/null
+<!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/🤔?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>
--- 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."
{
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",
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",
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",
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",
--- /dev/null
+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
+
--- /dev/null
+<!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>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<button onclick="runTest()">1. Copy</button>
+<div><br></div>
+<div id="source" oncopy="copy(event)" contenteditable>http://webkit.org/b/🤔?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>
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 ]
[ 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
+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
#include "Pasteboard.h"
#include "Settings.h"
#include "StaticPasteboard.h"
+#include "URLParser.h"
#include "WebCorePasteboardFileReader.h"
namespace WebCore {
#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()
return lowercaseType;
}
-static bool shouldReadOrWriteTypeAsCustomData(const String& type)
-{
- return Settings::customPasteboardDataEnabled() && !Pasteboard::isSafeTypeForDOMToReadAndWrite(type);
-}
-
void DataTransfer::clearData(const String& type)
{
if (!canWriteData())
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
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()
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;
}
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
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;
}
#endif
auto dataTransfer = adoptRef(*new DataTransfer(mode, WTFMove(pasteboard), draggingFiles ? Type::DragAndDropFiles : Type::DragAndDropData));
dataTransfer->setSourceOperation(sourceOperation);
+ dataTransfer->m_originIdentifier = originIdentifierForDocument(document);
return dataTransfer;
}
// 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;
// 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();
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"; }
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;
#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>
#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)
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);
uint64_t m_domTreeVersion;
static uint64_t s_globalTreeVersion;
-
+
+ String m_uniqueIdentifier;
+
HashSet<NodeIterator*> m_nodeIterators;
HashSet<Range*> m_ranges;
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
if (!target)
return true;
- auto dataTransfer = createDataTransferForClipboardEvent(kind);
+ auto dataTransfer = createDataTransferForClipboardEvent(target->document(), kind);
ClipboardEvent::Init init;
init.bubbles = true;
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();
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));
}
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();
}
if (!m_mouseDownMayStartDrag)
return !mouseDownMayStartSelect() && !m_mouseDownMayStartAutoscroll;
-
+ ASSERT(dragState().source);
+
if (!ExactlyOneBitSet(dragState().type)) {
ASSERT((dragState().type & DragSourceActionSelection));
#if ENABLE(ATTACHMENT_ELEMENT)
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));
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);
virtual void write(const PasteboardImage&);
virtual void write(const PasteboardWebContent&);
+ virtual void writeCustomData(const PasteboardCustomData&);
+
virtual bool containsFiles();
virtual bool canSmartReplace();
WEBCORE_EXPORT static NSArray *supportedFileUploadPasteboardTypes();
const String& name() const { return m_pasteboardName; }
long changeCount() const;
+ const PasteboardCustomData& readCustomData();
#endif
#if PLATFORM(WIN)
void writeImageToDataObject(Element&, const URL&); // FIXME: Layering violation.
#endif
- void writeCustomData(const PasteboardCustomData&);
-
private:
#if PLATFORM(IOS)
bool respectsUTIFidelities() const;
#if PLATFORM(COCOA)
String m_pasteboardName;
long m_changeCount;
+ std::optional<PasteboardCustomData> m_customDataCache;
#endif
#if PLATFORM(WIN)
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)
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);
#include "config.h"
#include "StaticPasteboard.h"
-#include "Settings.h"
-#include "SharedBuffer.h"
-
namespace WebCore {
StaticPasteboard::StaticPasteboard()
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) };
}
}
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;
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; }
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.
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)
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 { };
return types;
}
+String Pasteboard::readOrigin()
+{
+ notImplemented(); // webkit.org/b/177633: [GTK] Move to new Pasteboard API
+ return { };
+}
+
String Pasteboard::readString(const String& type)
{
readFromClipboard();
return selection;
}
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
{
return { };
}
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]) {
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;
}
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]) {
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())];
}
}
{
}
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
{
return { };
}
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)
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];
results.add("text/plain");
}
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String&)
{
notImplemented();
return { };
return vector;
}
+String Pasteboard::readOrigin()
+{
+ notImplemented();
+ return { };
+}
+
String Pasteboard::readString(const String& type)
{
if (!m_dataObject && m_dragDataMap.isEmpty())
return !types.isEmpty();
}
-Vector<String> Pasteboard::typesSafeForBindings()
+Vector<String> Pasteboard::typesSafeForBindings(const String&)
{
notImplemented();
return { };
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);
wpe_pasteboard_string_free(&pairs[0].string);
}
-Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite() const
+Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
{
return { };
}
+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
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)
#if !PLATFORM(COCOA)
-void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String&, Vector<String>& types)
+void WebPasteboardProxy::typesSafeForDOMToReadAndWrite(const String&, const String&, Vector<String>& types)
{
types = { };
}
#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&);
#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.
#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;
}
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;
};
+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
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;
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)
+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
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 */,
--- /dev/null
+/*
+ * 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)
+
--- /dev/null
+<!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>
@"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"
}
});
});
}
+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