Commit 060b7f1b authored by Eric Willigers's avatar Eric Willigers Committed by Commit Bot

Web Share: restrict URL scheme to http and https

We now follow the recent spec change limiting the permitted scheme
for shared urls to http and https - see
https://github.com/w3c/web-share/issues/173
https://github.com/w3c/web-share/pull/174
https://github.com/w3c/web-share/pull/177

We make an exception if the page performing the share it itself loaded
from a different scheme (e.g. file) - in that case we allow the same
scheme to be used for the shared url.

Bug: 1131755
Change-Id: I6abf0f9acd40ef79ec49379314e2ef3a81d3467e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2425977
Commit-Queue: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Auto-Submit: Eric Willigers <ericwilligers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810180}
parent 682a8e43
......@@ -81,7 +81,9 @@ bool CanShareInternal(const LocalDOMWindow& window,
if (data.hasUrl()) {
url = window.CompleteURL(data.url());
if (!url.IsValid()) {
if (!url.IsValid() ||
(!url.ProtocolIsInHTTPFamily() &&
url.Protocol() != window.document()->BaseURL().Protocol())) {
if (exception_state) {
exception_state->ThrowTypeError("Invalid URL");
}
......
......@@ -2703,7 +2703,6 @@ crbug.com/626703 [ Mac10.14 ] external/wpt/pointerevents/compat/pointerevent_mou
crbug.com/626703 [ Mac10.15 ] external/wpt/pointerevents/compat/pointerevent_mouse-pointer-preventdefault.html [ Timeout Pass Failure ]
crbug.com/626703 [ Mac10.15 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
crbug.com/626703 [ Mac10.14 ] virtual/off-main-thread-css-paint/external/wpt/css/css-paint-api/valid-image-before-load.https.html [ Failure ]
crbug.com/626703 external/wpt/web-share/share-url-invalid.https.html [ Crash ]
crbug.com/626703 external/wpt/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/pageshow-event.window.html [ Timeout ]
crbug.com/626703 [ Win7 ] external/wpt/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html [ Failure Timeout ]
crbug.com/626703 external/wpt/content-security-policy/frame-src/frame-src-same-document.sub.html [ Timeout ]
......
......@@ -4516,7 +4516,6 @@ crbug.com/1050754 external/wpt/web-share/canShare.tentative.https.html [ Failure
crbug.com/1050754 external/wpt/web-share/idlharness.https.window.html [ Failure ]
crbug.com/1050754 external/wpt/web-share/share-empty.https.html [ Failure ]
crbug.com/1050754 external/wpt/web-share/share-sharePromise-internal-slot.https.html [ Timeout ]
crbug.com/1050754 external/wpt/web-share/share-url-invalid.https.html [ Failure ]
crbug.com/1050754 external/wpt/web-share/share-without-user-gesture.https.html [ Failure ]
crbug.com/1050754 external/wpt/webaudio/idlharness.https.window.html [ Failure ]
crbug.com/1050754 external/wpt/webaudio/the-audio-api/processing-model/cycle-without-delay.html [ Failure ]
......
......@@ -11,74 +11,74 @@
'use strict';
test(() => {
assert_equals(navigator.canShare(), false);
assert_false(navigator.canShare());
}, 'canShare with no arguments (same as empty dictionary)');
test(() => {
assert_equals(navigator.canShare({}), false);
assert_false(navigator.canShare({}));
}, 'canShare with an empty dictionary');
test(() => {
assert_equals(navigator.canShare(undefined), false);
assert_false(navigator.canShare(undefined));
}, 'canShare with a undefined argument (same as empty dictionary)');
test(() => {
assert_equals(navigator.canShare(null), false);
assert_false(navigator.canShare(null));
}, 'canShare with a null argument (same as empty dictionary)');
test(() => {
assert_equals(navigator.canShare({unused: 'unexpected field'}), false);
assert_false(navigator.canShare({unused: 'unexpected field'}));
}, 'canShare with a dictionary containing only surplus fields');
test(() => {
// URL is invalid in that the URL Parser returns failure (port is too
// large).
const url = 'http://example.com:65536';
assert_equals(navigator.canShare({url}), false);
assert_false(navigator.canShare({url}));
}, 'canShare with an invalid URL');
test(() => {
assert_equals(navigator.canShare({title: undefined}), false);
assert_false(navigator.canShare({url: 'data:the url'}));
}, 'canShare with data URL');
test(() => {
assert_false(navigator.canShare({title: undefined}));
}, 'canShare with attribute undefined is equivalent to omitting the attribute');
test(() => {
assert_equals(navigator.canShare({title: 'subject'}), true);
assert_true(navigator.canShare({title: 'subject'}));
}, 'canShare with title');
test(() => {
assert_equals(navigator.canShare({text: 'body'}), true);
assert_true(navigator.canShare({text: 'body'}));
}, 'canShare with text');
test(() => {
assert_equals(navigator.canShare({url: 'https://www.example.com/some/path?some_query#some_fragment'}), true);
assert_true(navigator.canShare({url: 'https://www.example.com/some/path?some_query#some_fragment'}));
}, 'canShare with URL');
test(() => {
assert_equals(navigator.canShare({title: null}), true);
assert_true(navigator.canShare({title: null}));
}, 'canShare with null attribute');
test(() => {
assert_equals(navigator.canShare({text: 123}), true);
assert_true(navigator.canShare({text: 123}));
}, 'canShare with number');
test(() => {
assert_equals(navigator.canShare({url: {toString() { return 'https://example.com/'; }}}), true);
assert_true(navigator.canShare({url: {toString() { return 'https://example.com/'; }}}));
}, 'canShare with object');
test(() => {
assert_equals(navigator.canShare({title: 'subject', text: 'body', url: 'https://example.com/', unused: 'unexpected field'}), true);
assert_true(navigator.canShare({title: 'subject', text: 'body', url: 'https://example.com/', unused: 'unexpected field'}));
}, 'canShare with unexpected field');
test(() => {
assert_equals(navigator.canShare({url: 'data:the url'}), true);
}, 'canShare with data URL');
test(() => {
assert_equals(navigator.canShare({url: ''}), true);
assert_true(navigator.canShare({url: ''}));
}, 'canShare with empty URL');
test(() => {
assert_equals(navigator.canShare({url: '//www.example.com/some/path?some_query#some_fragment'}), true);
assert_true(navigator.canShare({url: '//www.example.com/some/path?some_query#some_fragment'}));
}, 'canShare with URL having no scheme');
</script>
</body>
......
......@@ -16,28 +16,38 @@ async function assertRejectsWithError(promise, name) {
}
share_test(mock => {
mock.pushShareResult('the title', 'the message', 'data:the url',
mock.pushShareResult('the title', 'the message', 'https://example.com/',
blink.mojom.ShareError.CANCELED);
return callWithKeyDown(() => assertRejectsWithError(
navigator.share({
title: 'the title',
text: 'the message',
url: 'data:the url'
url: 'https://example.com/'
}),
'AbortError'));
}, 'share with user cancellation');
share_test(mock => {
mock.pushShareResult('the title', 'the message', 'data:the url',
mock.pushShareResult('the title', 'the message', 'https://example.com/',
blink.mojom.ShareError.INTERNAL_ERROR);
return callWithKeyDown(() => assertRejectsWithError(
navigator.share({
title: 'the title',
text: 'the message',
url: 'data:the url'
url: 'https://example.com/'
}),
'AbortError'));
}, 'share with invalid url template');
}, 'share with internal error');
share_test(mock => {
return callWithKeyDown(() => assertRejectsWithError(
navigator.share({
title: 'the title',
text: 'the message',
url: 'data:foo'
}),
'TypeError'));
}, 'share with data url');
share_test(mock => {
return callWithKeyDown(async () => {
......
......@@ -43,12 +43,6 @@ share_test(mock => {
return callWithKeyDown(() => navigator.share({url: url}));
}, 'successful share with an empty URL');
share_test(mock => {
const url = 'data:foo';
mock.pushShareResult('', '', getAbsoluteUrl(url), blink.mojom.ShareError.OK);
return callWithKeyDown(() => navigator.share({url: url}));
}, 'successful share with a data URL');
share_test(mock => {
const url = 'http://example.com/foo\\ab%63\r\n\t "<>`{}';
// Expect '\' to normalize to '/', "%63" to normalize to 'c', '\r\n\t'
......
......@@ -11,21 +11,22 @@ function getAbsoluteUrl(url) {
}
share_test(mock => {
mock.pushShareResult('true', 'the object', getAbsoluteUrl('384957'),
mock.pushShareResult('true', 'the object', 'http://example.com/',
blink.mojom.ShareError.OK);
const objectWithToString = {toString() { return 'the object'; }};
const textWithToString = {toString() { return 'the object'; }};
const urlWithToString = {toString() { return 'http://example.com/'; }};
return callWithKeyDown(() => navigator.share(
{title: true, text: objectWithToString, url: 384957}));
{title: true, text: textWithToString, url: urlWithToString}));
}, 'share of types other than string (expect implicitly converted to string)');
share_test(mock => {
// null fields should convert into the string 'null' (because the field is
// not nullable, it just converts to a string like any other type).
mock.pushShareResult('null', '', getAbsoluteUrl('null'),
mock.pushShareResult('null', '', '',
blink.mojom.ShareError.OK);
return callWithKeyDown(() => navigator.share(
{title: null, text: undefined, url: null}));
{title: null, text: undefined}));
}, 'share of null/undefined dict values');
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment