From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../tests/fetch/api/resources/authentication.py | 14 ++ .../fetch/api/resources/bad-chunk-encoding.py | 13 ++ .../tests/fetch/api/resources/basic.html | 5 + .../tests/fetch/api/resources/cache.py | 18 +++ .../tests/fetch/api/resources/clean-stash.py | 6 + .../tests/fetch/api/resources/cors-top.txt | 1 + .../tests/fetch/api/resources/cors-top.txt.headers | 1 + .../tests/fetch/api/resources/data.json | 1 + .../api/resources/dump-authorization-header.py | 14 ++ .../tests/fetch/api/resources/echo-content.h2.py | 7 + .../tests/fetch/api/resources/echo-content.py | 12 ++ .../tests/fetch/api/resources/empty.txt | 0 .../fetch/api/resources/infinite-slow-response.py | 35 ++++ .../tests/fetch/api/resources/inspect-headers.py | 24 +++ .../tests/fetch/api/resources/keepalive-helper.js | 176 +++++++++++++++++++++ .../fetch/api/resources/keepalive-iframe.html | 22 +++ .../api/resources/keepalive-redirect-iframe.html | 23 +++ .../api/resources/keepalive-redirect-window.html | 42 +++++ .../tests/fetch/api/resources/method.py | 18 +++ .../tests/fetch/api/resources/preflight.py | 78 +++++++++ .../fetch/api/resources/redirect-empty-location.py | 3 + .../tests/fetch/api/resources/redirect.h2.py | 14 ++ .../tests/fetch/api/resources/redirect.py | 73 +++++++++ .../fetch/api/resources/sandboxed-iframe.html | 34 ++++ .../fetch/api/resources/script-with-header.py | 7 + .../tests/fetch/api/resources/stash-put.py | 41 +++++ .../tests/fetch/api/resources/stash-take.py | 9 ++ .../tests/fetch/api/resources/status.py | 11 ++ .../fetch/api/resources/sw-intercept-abort.js | 19 +++ .../tests/fetch/api/resources/sw-intercept.js | 10 ++ .../web-platform/tests/fetch/api/resources/top.txt | 1 + .../tests/fetch/api/resources/trickle.py | 15 ++ .../tests/fetch/api/resources/utils.js | 120 ++++++++++++++ 33 files changed, 867 insertions(+) create mode 100644 testing/web-platform/tests/fetch/api/resources/authentication.py create mode 100644 testing/web-platform/tests/fetch/api/resources/bad-chunk-encoding.py create mode 100644 testing/web-platform/tests/fetch/api/resources/basic.html create mode 100644 testing/web-platform/tests/fetch/api/resources/cache.py create mode 100644 testing/web-platform/tests/fetch/api/resources/clean-stash.py create mode 100644 testing/web-platform/tests/fetch/api/resources/cors-top.txt create mode 100644 testing/web-platform/tests/fetch/api/resources/cors-top.txt.headers create mode 100644 testing/web-platform/tests/fetch/api/resources/data.json create mode 100644 testing/web-platform/tests/fetch/api/resources/dump-authorization-header.py create mode 100644 testing/web-platform/tests/fetch/api/resources/echo-content.h2.py create mode 100644 testing/web-platform/tests/fetch/api/resources/echo-content.py create mode 100644 testing/web-platform/tests/fetch/api/resources/empty.txt create mode 100644 testing/web-platform/tests/fetch/api/resources/infinite-slow-response.py create mode 100644 testing/web-platform/tests/fetch/api/resources/inspect-headers.py create mode 100644 testing/web-platform/tests/fetch/api/resources/keepalive-helper.js create mode 100644 testing/web-platform/tests/fetch/api/resources/keepalive-iframe.html create mode 100644 testing/web-platform/tests/fetch/api/resources/keepalive-redirect-iframe.html create mode 100644 testing/web-platform/tests/fetch/api/resources/keepalive-redirect-window.html create mode 100644 testing/web-platform/tests/fetch/api/resources/method.py create mode 100644 testing/web-platform/tests/fetch/api/resources/preflight.py create mode 100644 testing/web-platform/tests/fetch/api/resources/redirect-empty-location.py create mode 100644 testing/web-platform/tests/fetch/api/resources/redirect.h2.py create mode 100644 testing/web-platform/tests/fetch/api/resources/redirect.py create mode 100644 testing/web-platform/tests/fetch/api/resources/sandboxed-iframe.html create mode 100644 testing/web-platform/tests/fetch/api/resources/script-with-header.py create mode 100644 testing/web-platform/tests/fetch/api/resources/stash-put.py create mode 100644 testing/web-platform/tests/fetch/api/resources/stash-take.py create mode 100644 testing/web-platform/tests/fetch/api/resources/status.py create mode 100644 testing/web-platform/tests/fetch/api/resources/sw-intercept-abort.js create mode 100644 testing/web-platform/tests/fetch/api/resources/sw-intercept.js create mode 100644 testing/web-platform/tests/fetch/api/resources/top.txt create mode 100644 testing/web-platform/tests/fetch/api/resources/trickle.py create mode 100644 testing/web-platform/tests/fetch/api/resources/utils.js (limited to 'testing/web-platform/tests/fetch/api/resources') diff --git a/testing/web-platform/tests/fetch/api/resources/authentication.py b/testing/web-platform/tests/fetch/api/resources/authentication.py new file mode 100644 index 0000000000..8b6b00b087 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/authentication.py @@ -0,0 +1,14 @@ +def main(request, response): + user = request.auth.username + password = request.auth.password + + if user == b"user" and password == b"password": + return b"Authentication done" + + realm = b"test" + if b"realm" in request.GET: + realm = request.GET.first(b"realm") + + return ((401, b"Unauthorized"), + [(b"WWW-Authenticate", b'Basic realm="' + realm + b'"')], + b"Please login with credentials 'user' and 'password'") diff --git a/testing/web-platform/tests/fetch/api/resources/bad-chunk-encoding.py b/testing/web-platform/tests/fetch/api/resources/bad-chunk-encoding.py new file mode 100644 index 0000000000..94a77adead --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/bad-chunk-encoding.py @@ -0,0 +1,13 @@ +import time + +def main(request, response): + delay = float(request.GET.first(b"ms", 1000)) / 1E3 + count = int(request.GET.first(b"count", 50)) + time.sleep(delay) + response.headers.set(b"Transfer-Encoding", b"chunked") + response.write_status_headers() + time.sleep(delay) + for i in range(count): + response.writer.write_content(b"a\r\nTEST_CHUNK\r\n") + time.sleep(delay) + response.writer.write_content(b"garbage") diff --git a/testing/web-platform/tests/fetch/api/resources/basic.html b/testing/web-platform/tests/fetch/api/resources/basic.html new file mode 100644 index 0000000000..e23afd4bf6 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/basic.html @@ -0,0 +1,5 @@ + + diff --git a/testing/web-platform/tests/fetch/api/resources/cache.py b/testing/web-platform/tests/fetch/api/resources/cache.py new file mode 100644 index 0000000000..4de751e30b --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/cache.py @@ -0,0 +1,18 @@ +ETAG = b'"123abc"' +CONTENT_TYPE = b"text/plain" +CONTENT = b"lorem ipsum dolor sit amet" + + +def main(request, response): + # let caching kick in if possible (conditional GET) + etag = request.headers.get(b"If-None-Match", None) + if etag == ETAG: + response.headers.set(b"X-HTTP-STATUS", 304) + response.status = (304, b"Not Modified") + return b"" + + # cache miss, so respond with the actual content + response.status = (200, b"OK") + response.headers.set(b"ETag", ETAG) + response.headers.set(b"Content-Type", CONTENT_TYPE) + return CONTENT diff --git a/testing/web-platform/tests/fetch/api/resources/clean-stash.py b/testing/web-platform/tests/fetch/api/resources/clean-stash.py new file mode 100644 index 0000000000..ee8c69ac44 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/clean-stash.py @@ -0,0 +1,6 @@ +def main(request, response): + token = request.GET.first(b"token") + if request.server.stash.take(token) is not None: + return b"1" + else: + return b"0" diff --git a/testing/web-platform/tests/fetch/api/resources/cors-top.txt b/testing/web-platform/tests/fetch/api/resources/cors-top.txt new file mode 100644 index 0000000000..83a3157d14 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/cors-top.txt @@ -0,0 +1 @@ +top \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/api/resources/cors-top.txt.headers b/testing/web-platform/tests/fetch/api/resources/cors-top.txt.headers new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/cors-top.txt.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/fetch/api/resources/data.json b/testing/web-platform/tests/fetch/api/resources/data.json new file mode 100644 index 0000000000..76519fa8cc --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/data.json @@ -0,0 +1 @@ +{"key": "value"} diff --git a/testing/web-platform/tests/fetch/api/resources/dump-authorization-header.py b/testing/web-platform/tests/fetch/api/resources/dump-authorization-header.py new file mode 100644 index 0000000000..a651aeb4e8 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/dump-authorization-header.py @@ -0,0 +1,14 @@ +def main(request, response): + headers = [(b"Content-Type", "text/html"), + (b"Cache-Control", b"no-cache")] + + if b"Origin" in request.headers: + headers.append((b"Access-Control-Allow-Origin", request.headers.get(b"Origin", b""))) + headers.append((b"Access-Control-Allow-Credentials", b"true")) + else: + headers.append((b"Access-Control-Allow-Origin", b"*")) + headers.append((b"Access-Control-Allow-Headers", b'Authorization')) + + if b"authorization" in request.headers: + return 200, headers, request.headers.get(b"Authorization") + return 200, headers, "none" diff --git a/testing/web-platform/tests/fetch/api/resources/echo-content.h2.py b/testing/web-platform/tests/fetch/api/resources/echo-content.h2.py new file mode 100644 index 0000000000..0be3ece4a5 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/echo-content.h2.py @@ -0,0 +1,7 @@ +def handle_headers(frame, request, response): + response.status = 200 + response.headers.update([('Content-Type', 'text/plain')]) + response.write_status_headers() + +def handle_data(frame, request, response): + response.writer.write_data(frame.data) diff --git a/testing/web-platform/tests/fetch/api/resources/echo-content.py b/testing/web-platform/tests/fetch/api/resources/echo-content.py new file mode 100644 index 0000000000..5e137e15d7 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/echo-content.py @@ -0,0 +1,12 @@ +from wptserve.utils import isomorphic_encode + +def main(request, response): + + headers = [(b"X-Request-Method", isomorphic_encode(request.method)), + (b"X-Request-Content-Length", request.headers.get(b"Content-Length", b"NO")), + (b"X-Request-Content-Type", request.headers.get(b"Content-Type", b"NO")), + # Avoid any kind of content sniffing on the response. + (b"Content-Type", b"text/plain")] + content = request.body + + return headers, content diff --git a/testing/web-platform/tests/fetch/api/resources/empty.txt b/testing/web-platform/tests/fetch/api/resources/empty.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/fetch/api/resources/infinite-slow-response.py b/testing/web-platform/tests/fetch/api/resources/infinite-slow-response.py new file mode 100644 index 0000000000..a26cd8064c --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/infinite-slow-response.py @@ -0,0 +1,35 @@ +import time + + +def url_dir(request): + return u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/' + + +def stash_write(request, key, value): + """Write to the stash, overwriting any previous value""" + request.server.stash.take(key, url_dir(request)) + request.server.stash.put(key, value, url_dir(request)) + + +def main(request, response): + stateKey = request.GET.first(b"stateKey", b"") + abortKey = request.GET.first(b"abortKey", b"") + + if stateKey: + stash_write(request, stateKey, 'open') + + response.headers.set(b"Content-type", b"text/plain") + response.write_status_headers() + + # Writing an initial 2k so browsers realise it's there. *shrug* + response.writer.write(b"." * 2048) + + while True: + if not response.writer.write(b"."): + break + if abortKey and request.server.stash.take(abortKey, url_dir(request)): + break + time.sleep(0.01) + + if stateKey: + stash_write(request, stateKey, 'closed') diff --git a/testing/web-platform/tests/fetch/api/resources/inspect-headers.py b/testing/web-platform/tests/fetch/api/resources/inspect-headers.py new file mode 100644 index 0000000000..9ed566e607 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/inspect-headers.py @@ -0,0 +1,24 @@ +def main(request, response): + headers = [] + if b"headers" in request.GET: + checked_headers = request.GET.first(b"headers").split(b"|") + for header in checked_headers: + if header in request.headers: + headers.append((b"x-request-" + header, request.headers.get(header, b""))) + + if b"cors" in request.GET: + if b"Origin" in request.headers: + headers.append((b"Access-Control-Allow-Origin", request.headers.get(b"Origin", b""))) + else: + headers.append((b"Access-Control-Allow-Origin", b"*")) + headers.append((b"Access-Control-Allow-Credentials", b"true")) + headers.append((b"Access-Control-Allow-Methods", b"GET, POST, HEAD")) + exposed_headers = [b"x-request-" + header for header in checked_headers] + headers.append((b"Access-Control-Expose-Headers", b", ".join(exposed_headers))) + if b"allow_headers" in request.GET: + headers.append((b"Access-Control-Allow-Headers", request.GET[b'allow_headers'])) + else: + headers.append((b"Access-Control-Allow-Headers", b", ".join(request.headers))) + + headers.append((b"content-type", b"text/plain")) + return headers, b"" diff --git a/testing/web-platform/tests/fetch/api/resources/keepalive-helper.js b/testing/web-platform/tests/fetch/api/resources/keepalive-helper.js new file mode 100644 index 0000000000..f6f511631e --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/keepalive-helper.js @@ -0,0 +1,176 @@ +// Utility functions to help testing keepalive requests. + +// Returns a URL to an iframe that loads a keepalive URL on iframe loaded. +// +// The keepalive URL points to a target that stores `token`. The token will then +// be posted back on iframe loaded to the parent document. +// `method` defaults to GET. +// `frameOrigin` to specify the origin of the iframe to load. If not set, +// default to a different site origin. +// `requestOrigin` to specify the origin of the fetch request target. +// `sendOn` to specify the name of the event when the keepalive request should +// be sent instead of the default 'load'. +// `mode` to specify the fetch request's CORS mode. +// `disallowCrossOrigin` to ask the iframe to set up a server that disallows +// cross origin requests. +function getKeepAliveIframeUrl(token, method, { + frameOrigin = 'DEFAULT', + requestOrigin = '', + sendOn = 'load', + mode = 'cors', + disallowCrossOrigin = false +} = {}) { + const https = location.protocol.startsWith('https'); + frameOrigin = frameOrigin === 'DEFAULT' ? + get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'] : + frameOrigin; + return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` + + `token=${token}&` + + `method=${method}&` + + `sendOn=${sendOn}&` + + `mode=${mode}&` + (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) + + `origin=${requestOrigin}`; +} + +// Returns a different-site URL to an iframe that loads a keepalive URL. +// +// By default, the keepalive URL points to a target that redirects to another +// same-origin destination storing `token`. The token will then be posted back +// to parent document. +// +// The URL redirects can be customized from `origin1` to `origin2` if provided. +// Sets `withPreflight` to true to get URL enabling preflight. +function getKeepAliveAndRedirectIframeUrl( + token, origin1, origin2, withPreflight) { + const https = location.protocol.startsWith('https'); + const frameOrigin = + get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN']; + return `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` + + `token=${token}&` + + `origin1=${origin1}&` + + `origin2=${origin2}&` + (withPreflight ? `with-headers` : ``); +} + +async function iframeLoaded(iframe) { + return new Promise((resolve) => iframe.addEventListener('load', resolve)); +} + +// Obtains the token from the message posted by iframe after loading +// `getKeepAliveAndRedirectIframeUrl()`. +async function getTokenFromMessage() { + return new Promise((resolve) => { + window.addEventListener('message', (event) => { + resolve(event.data); + }, {once: true}); + }); +} + +// Tells if `token` has been stored in the server. +async function queryToken(token) { + const response = await fetch(`../resources/stash-take.py?key=${token}`); + const json = await response.json(); + return json; +} + +// A helper to assert the existence of `token` that should have been stored in +// the server by fetching ../resources/stash-put.py. +// +// This function simply wait for a custom amount of time before trying to +// retrieve `token` from the server. +// `expectTokenExist` tells if `token` should be present or not. +// +// NOTE: +// In order to parallelize the work, we are going to have an async_test +// for the rest of the work. Note that we want the serialized behavior +// for the steps so far, so we don't want to make the entire test case +// an async_test. +function assertStashedTokenAsync( + testName, token, {expectTokenExist = true} = {}) { + async_test(test => { + new Promise(resolve => test.step_timeout(resolve, 3000 /*ms*/)) + .then(test.step_func(() => { + return queryToken(token); + })) + .then(test.step_func(result => { + if (expectTokenExist) { + assert_equals(result, 'on', `token should be on (stashed).`); + test.done(); + } else { + assert_not_equals( + result, 'on', `token should not be on (stashed).`); + return Promise.reject(`Failed to retrieve token from server`); + } + })) + .catch(test.step_func(e => { + if (expectTokenExist) { + test.unreached_func(e); + } else { + test.done(); + } + })); + }, testName); +} + +/** + * In an iframe, and in `load` event handler, test to fetch a keepalive URL that + * involves in redirect to another URL. + * + * `unloadIframe` to unload the iframe before verifying stashed token to + * simulate the situation that unloads after fetching. Note that this test is + * different from `keepaliveRedirectInUnloadTest()` in that the the latter + * performs fetch() call directly in `unload` event handler, while this test + * does it in `load`. + */ +function keepaliveRedirectTest(desc, { + origin1 = '', + origin2 = '', + withPreflight = false, + unloadIframe = false, + expectFetchSucceed = true, +} = {}) { + desc = `[keepalive][iframe][load] ${desc}` + + (unloadIframe ? ' [unload at end]' : ''); + promise_test(async (test) => { + const tokenToStash = token(); + const iframe = document.createElement('iframe'); + iframe.src = getKeepAliveAndRedirectIframeUrl( + tokenToStash, origin1, origin2, withPreflight); + document.body.appendChild(iframe); + await iframeLoaded(iframe); + assert_equals(await getTokenFromMessage(), tokenToStash); + if (unloadIframe) { + iframe.remove(); + } + + assertStashedTokenAsync( + desc, tokenToStash, {expectTokenExist: expectFetchSucceed}); + }, `${desc}; setting up`); +} + +/** + * Opens a different site window, and in `unload` event handler, test to fetch + * a keepalive URL that involves in redirect to another URL. + */ +function keepaliveRedirectInUnloadTest(desc, { + origin1 = '', + origin2 = '', + url2 = '', + withPreflight = false, + expectFetchSucceed = true +} = {}) { + desc = `[keepalive][new window][unload] ${desc}`; + + promise_test(async (test) => { + const targetUrl = + `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` + + `origin1=${origin1}&` + + `origin2=${origin2}&` + + `url2=${url2}&` + (withPreflight ? `with-headers` : ``); + const w = window.open(targetUrl); + const token = await getTokenFromMessage(); + w.close(); + + assertStashedTokenAsync( + desc, token, {expectTokenExist: expectFetchSucceed}); + }, `${desc}; setting up`); +} diff --git a/testing/web-platform/tests/fetch/api/resources/keepalive-iframe.html b/testing/web-platform/tests/fetch/api/resources/keepalive-iframe.html new file mode 100644 index 0000000000..f9dae5a34e --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/keepalive-iframe.html @@ -0,0 +1,22 @@ + + + + + diff --git a/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-iframe.html b/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-iframe.html new file mode 100644 index 0000000000..fdee00f312 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-iframe.html @@ -0,0 +1,23 @@ + + + + + diff --git a/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-window.html b/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-window.html new file mode 100644 index 0000000000..c18650796c --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/keepalive-redirect-window.html @@ -0,0 +1,42 @@ + + + + + + + diff --git a/testing/web-platform/tests/fetch/api/resources/method.py b/testing/web-platform/tests/fetch/api/resources/method.py new file mode 100644 index 0000000000..c1a111b4cd --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/method.py @@ -0,0 +1,18 @@ +from wptserve.utils import isomorphic_encode + +def main(request, response): + headers = [] + if b"cors" in request.GET: + headers.append((b"Access-Control-Allow-Origin", b"*")) + headers.append((b"Access-Control-Allow-Credentials", b"true")) + headers.append((b"Access-Control-Allow-Methods", b"GET, POST, PUT, FOO")) + headers.append((b"Access-Control-Allow-Headers", b"x-test, x-foo")) + headers.append((b"Access-Control-Expose-Headers", b"x-request-method")) + + headers.append((b"x-request-method", isomorphic_encode(request.method))) + headers.append((b"x-request-content-type", request.headers.get(b"Content-Type", b"NO"))) + headers.append((b"x-request-content-length", request.headers.get(b"Content-Length", b"NO"))) + headers.append((b"x-request-content-encoding", request.headers.get(b"Content-Encoding", b"NO"))) + headers.append((b"x-request-content-language", request.headers.get(b"Content-Language", b"NO"))) + headers.append((b"x-request-content-location", request.headers.get(b"Content-Location", b"NO"))) + return headers, request.body diff --git a/testing/web-platform/tests/fetch/api/resources/preflight.py b/testing/web-platform/tests/fetch/api/resources/preflight.py new file mode 100644 index 0000000000..f983ef9522 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/preflight.py @@ -0,0 +1,78 @@ +def main(request, response): + headers = [(b"Content-Type", b"text/plain")] + stashed_data = {b'control_request_headers': b"", b'preflight': b"0", b'preflight_referrer': b""} + + token = None + if b"token" in request.GET: + token = request.GET.first(b"token") + + if b"origin" in request.GET: + for origin in request.GET[b'origin'].split(b", "): + headers.append((b"Access-Control-Allow-Origin", origin)) + else: + headers.append((b"Access-Control-Allow-Origin", b"*")) + + if b"clear-stash" in request.GET: + if request.server.stash.take(token) is not None: + return headers, b"1" + else: + return headers, b"0" + + if b"credentials" in request.GET: + headers.append((b"Access-Control-Allow-Credentials", b"true")) + + if request.method == u"OPTIONS": + if not b"Access-Control-Request-Method" in request.headers: + response.set_error(400, u"No Access-Control-Request-Method header") + return b"ERROR: No access-control-request-method in preflight!" + + if request.headers.get(b"Accept", b"") != b"*/*": + response.set_error(400, u"Request does not have 'Accept: */*' header") + return b"ERROR: Invalid access in preflight!" + + if b"control_request_headers" in request.GET: + stashed_data[b'control_request_headers'] = request.headers.get(b"Access-Control-Request-Headers", None) + + if b"max_age" in request.GET: + headers.append((b"Access-Control-Max-Age", request.GET[b'max_age'])) + + if b"allow_headers" in request.GET: + headers.append((b"Access-Control-Allow-Headers", request.GET[b'allow_headers'])) + + if b"allow_methods" in request.GET: + headers.append((b"Access-Control-Allow-Methods", request.GET[b'allow_methods'])) + + preflight_status = 200 + if b"preflight_status" in request.GET: + preflight_status = int(request.GET.first(b"preflight_status")) + + stashed_data[b'preflight'] = b"1" + stashed_data[b'preflight_referrer'] = request.headers.get(b"Referer", b"") + stashed_data[b'preflight_user_agent'] = request.headers.get(b"User-Agent", b"") + if token: + request.server.stash.put(token, stashed_data) + + return preflight_status, headers, b"" + + + if token: + data = request.server.stash.take(token) + if data: + stashed_data = data + + if b"checkUserAgentHeaderInPreflight" in request.GET and request.headers.get(b"User-Agent") != stashed_data[b'preflight_user_agent']: + return 400, headers, b"ERROR: No user-agent header in preflight" + + #use x-* headers for returning value to bodyless responses + headers.append((b"Access-Control-Expose-Headers", b"x-did-preflight, x-control-request-headers, x-referrer, x-preflight-referrer, x-origin")) + headers.append((b"x-did-preflight", stashed_data[b'preflight'])) + if stashed_data[b'control_request_headers'] != None: + headers.append((b"x-control-request-headers", stashed_data[b'control_request_headers'])) + headers.append((b"x-preflight-referrer", stashed_data[b'preflight_referrer'])) + headers.append((b"x-referrer", request.headers.get(b"Referer", b""))) + headers.append((b"x-origin", request.headers.get(b"Origin", b""))) + + if token: + request.server.stash.put(token, stashed_data) + + return headers, b"" diff --git a/testing/web-platform/tests/fetch/api/resources/redirect-empty-location.py b/testing/web-platform/tests/fetch/api/resources/redirect-empty-location.py new file mode 100644 index 0000000000..1a5f7feb2a --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/redirect-empty-location.py @@ -0,0 +1,3 @@ +def main(request, response): + headers = [(b"Location", b"")] + return 302, headers, b"" diff --git a/testing/web-platform/tests/fetch/api/resources/redirect.h2.py b/testing/web-platform/tests/fetch/api/resources/redirect.h2.py new file mode 100644 index 0000000000..6937014587 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/redirect.h2.py @@ -0,0 +1,14 @@ +from wptserve.utils import isomorphic_decode, isomorphic_encode + +def handle_headers(frame, request, response): + status = 302 + if b'redirect_status' in request.GET: + status = int(request.GET[b'redirect_status']) + response.status = status + + if b'location' in request.GET: + url = isomorphic_decode(request.GET[b'location']) + response.headers[b'Location'] = isomorphic_encode(url) + + response.headers.update([('Content-Type', 'text/plain')]) + response.write_status_headers() diff --git a/testing/web-platform/tests/fetch/api/resources/redirect.py b/testing/web-platform/tests/fetch/api/resources/redirect.py new file mode 100644 index 0000000000..d52ab5f3ee --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/redirect.py @@ -0,0 +1,73 @@ +import time + +from urllib.parse import urlencode, urlparse + +from wptserve.utils import isomorphic_decode, isomorphic_encode + +def main(request, response): + stashed_data = {b'count': 0, b'preflight': b"0"} + status = 302 + headers = [(b"Content-Type", b"text/plain"), + (b"Cache-Control", b"no-cache"), + (b"Pragma", b"no-cache")] + if b"Origin" in request.headers: + headers.append((b"Access-Control-Allow-Origin", request.headers.get(b"Origin", b""))) + headers.append((b"Access-Control-Allow-Credentials", b"true")) + else: + headers.append((b"Access-Control-Allow-Origin", b"*")) + + token = None + if b"token" in request.GET: + token = request.GET.first(b"token") + data = request.server.stash.take(token) + if data: + stashed_data = data + + if request.method == u"OPTIONS": + if b"allow_headers" in request.GET: + headers.append((b"Access-Control-Allow-Headers", request.GET[b'allow_headers'])) + stashed_data[b'preflight'] = b"1" + #Preflight is not redirected: return 200 + if not b"redirect_preflight" in request.GET: + if token: + request.server.stash.put(request.GET.first(b"token"), stashed_data) + return 200, headers, u"" + + if b"redirect_status" in request.GET: + status = int(request.GET[b'redirect_status']) + elif b"redirect_status" in request.POST: + status = int(request.POST[b'redirect_status']) + + stashed_data[b'count'] += 1 + + if b"location" in request.GET: + url = isomorphic_decode(request.GET[b'location']) + if b"simple" not in request.GET: + scheme = urlparse(url).scheme + if scheme == u"" or scheme == u"http" or scheme == u"https": + url += u"&" if u'?' in url else u"?" + #keep url parameters in location + url_parameters = {} + for item in request.GET.items(): + url_parameters[isomorphic_decode(item[0])] = isomorphic_decode(item[1][0]) + url += urlencode(url_parameters) + #make sure location changes during redirection loop + url += u"&count=" + str(stashed_data[b'count']) + headers.append((b"Location", isomorphic_encode(url))) + + if b"redirect_referrerpolicy" in request.GET: + headers.append((b"Referrer-Policy", request.GET[b'redirect_referrerpolicy'])) + + if b"delay" in request.GET: + time.sleep(float(request.GET.first(b"delay", 0)) / 1E3) + + if token: + request.server.stash.put(request.GET.first(b"token"), stashed_data) + if b"max_count" in request.GET: + max_count = int(request.GET[b'max_count']) + #stop redirecting and return count + if stashed_data[b'count'] > max_count: + # -1 because the last is not a redirection + return str(stashed_data[b'count'] - 1) + + return status, headers, u"" diff --git a/testing/web-platform/tests/fetch/api/resources/sandboxed-iframe.html b/testing/web-platform/tests/fetch/api/resources/sandboxed-iframe.html new file mode 100644 index 0000000000..6e5d506547 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/sandboxed-iframe.html @@ -0,0 +1,34 @@ + + + + diff --git a/testing/web-platform/tests/fetch/api/resources/script-with-header.py b/testing/web-platform/tests/fetch/api/resources/script-with-header.py new file mode 100644 index 0000000000..9a9c70ef5c --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/script-with-header.py @@ -0,0 +1,7 @@ +def main(request, response): + headers = [(b"Content-type", request.GET.first(b"mime"))] + if b"content" in request.GET and request.GET.first(b"content") == b"empty": + content = b'' + else: + content = b"console.log('Script loaded')" + return 200, headers, content diff --git a/testing/web-platform/tests/fetch/api/resources/stash-put.py b/testing/web-platform/tests/fetch/api/resources/stash-put.py new file mode 100644 index 0000000000..91c198abb7 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/stash-put.py @@ -0,0 +1,41 @@ +from wptserve.utils import isomorphic_decode + +def should_be_treated_as_same_origin_request(request): + """Tells whether request should be treated as same-origin request.""" + # In both of the following cases, allow to proceed with handling to simulate + # 'no-cors' mode: response is sent, but browser will make it opaque. + if request.GET.first(b'mode') == b'no-cors': + return True + + # We can't rely on the Origin header field of a fetch request, as it is only + # present for 'cors' mode or methods other than 'GET'/'HEAD' (i.e. present for + # 'POST'). See https://fetch.spec.whatwg.org/#http-origin + assert 'frame_origin ' in request.GET + frame_origin = request.GET.first(b'frame_origin').decode('utf-8') + host_origin = request.url_parts.scheme + '://' + request.url_parts.netloc + return frame_origin == host_origin + +def main(request, response): + if request.method == u'OPTIONS': + # CORS preflight + response.headers.set(b'Access-Control-Allow-Origin', b'*') + response.headers.set(b'Access-Control-Allow-Methods', b'*') + response.headers.set(b'Access-Control-Allow-Headers', b'*') + return 'done' + + if b'disallow_cross_origin' not in request.GET: + response.headers.set(b'Access-Control-Allow-Origin', b'*') + elif not should_be_treated_as_same_origin_request(request): + # As simple requests will not trigger preflight, we have to manually block + # cors requests before making any changes to storage. + # https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests + # https://fetch.spec.whatwg.org/#cors-preflight-fetch + return 'not stashing for cors request' + + url_dir = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/' + key = request.GET.first(b'key') + value = request.GET.first(b'value') + # value here must be a text string. It will be json.dump()'ed in stash-take.py. + request.server.stash.put(key, isomorphic_decode(value), url_dir) + + return 'done' diff --git a/testing/web-platform/tests/fetch/api/resources/stash-take.py b/testing/web-platform/tests/fetch/api/resources/stash-take.py new file mode 100644 index 0000000000..e6db80dd86 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/stash-take.py @@ -0,0 +1,9 @@ +from wptserve.handlers import json_handler + + +@json_handler +def main(request, response): + dir = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/' + key = request.GET.first(b"key") + response.headers.set(b'Access-Control-Allow-Origin', b'*') + return request.server.stash.take(key, dir) diff --git a/testing/web-platform/tests/fetch/api/resources/status.py b/testing/web-platform/tests/fetch/api/resources/status.py new file mode 100644 index 0000000000..05a59d5a63 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/status.py @@ -0,0 +1,11 @@ +from wptserve.utils import isomorphic_encode + +def main(request, response): + code = int(request.GET.first(b"code", 200)) + text = request.GET.first(b"text", b"OMG") + content = request.GET.first(b"content", b"") + type = request.GET.first(b"type", b"") + status = (code, text) + headers = [(b"Content-Type", type), + (b"X-Request-Method", isomorphic_encode(request.method))] + return status, headers, content diff --git a/testing/web-platform/tests/fetch/api/resources/sw-intercept-abort.js b/testing/web-platform/tests/fetch/api/resources/sw-intercept-abort.js new file mode 100644 index 0000000000..19d4b189d8 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/sw-intercept-abort.js @@ -0,0 +1,19 @@ +async function messageClient(clientId, message) { + const client = await clients.get(clientId); + client.postMessage(message); +} + +addEventListener('fetch', event => { + let resolve; + const promise = new Promise(r => resolve = r); + + function onAborted() { + messageClient(event.clientId, event.request.signal.reason); + resolve(); + } + + messageClient(event.clientId, 'fetch event has arrived'); + + event.respondWith(promise.then(() => new Response('hello'))); + event.request.signal.addEventListener('abort', onAborted); +}); diff --git a/testing/web-platform/tests/fetch/api/resources/sw-intercept.js b/testing/web-platform/tests/fetch/api/resources/sw-intercept.js new file mode 100644 index 0000000000..b8166b62a5 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/sw-intercept.js @@ -0,0 +1,10 @@ +async function broadcast(msg) { + for (const client of await clients.matchAll()) { + client.postMessage(msg); + } +} + +addEventListener('fetch', event => { + event.waitUntil(broadcast(event.request.url)); + event.respondWith(fetch(event.request)); +}); diff --git a/testing/web-platform/tests/fetch/api/resources/top.txt b/testing/web-platform/tests/fetch/api/resources/top.txt new file mode 100644 index 0000000000..83a3157d14 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/top.txt @@ -0,0 +1 @@ +top \ No newline at end of file diff --git a/testing/web-platform/tests/fetch/api/resources/trickle.py b/testing/web-platform/tests/fetch/api/resources/trickle.py new file mode 100644 index 0000000000..99833f1b38 --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/trickle.py @@ -0,0 +1,15 @@ +import time + +def main(request, response): + delay = float(request.GET.first(b"ms", 500)) / 1E3 + count = int(request.GET.first(b"count", 50)) + # Read request body + request.body + time.sleep(delay) + if not b"notype" in request.GET: + response.headers.set(b"Content-type", b"text/plain") + response.write_status_headers() + time.sleep(delay) + for i in range(count): + response.writer.write_content(b"TEST_TRICKLE\n") + time.sleep(delay) diff --git a/testing/web-platform/tests/fetch/api/resources/utils.js b/testing/web-platform/tests/fetch/api/resources/utils.js new file mode 100644 index 0000000000..3721d9bf9c --- /dev/null +++ b/testing/web-platform/tests/fetch/api/resources/utils.js @@ -0,0 +1,120 @@ +var RESOURCES_DIR = "../resources/"; + +function dirname(path) { + return path.replace(/\/[^\/]*$/, '/') +} + +function checkRequest(request, ExpectedValuesDict) { + for (var attribute in ExpectedValuesDict) { + switch(attribute) { + case "headers": + for (var key in ExpectedValuesDict["headers"].keys()) { + assert_equals(request["headers"].get(key), ExpectedValuesDict["headers"].get(key), + "Check headers attribute has " + key + ":" + ExpectedValuesDict["headers"].get(key)); + } + break; + + case "body": + //for checking body's content, a dedicated asyncronous/promise test should be used + assert_true(request["headers"].has("Content-Type") , "Check request has body using Content-Type header") + break; + + case "method": + case "referrer": + case "referrerPolicy": + case "credentials": + case "cache": + case "redirect": + case "integrity": + case "url": + case "destination": + assert_equals(request[attribute], ExpectedValuesDict[attribute], "Check " + attribute + " attribute") + break; + + default: + break; + } + } +} + +function stringToArray(str) { + var array = new Uint8Array(str.length); + for (var i=0, strLen = str.length; i < strLen; i++) + array[i] = str.charCodeAt(i); + return array; +} + +function encode_utf8(str) +{ + if (self.TextEncoder) + return (new TextEncoder).encode(str); + return stringToArray(unescape(encodeURIComponent(str))); +} + +function validateBufferFromString(buffer, expectedValue, message) +{ + return assert_array_equals(new Uint8Array(buffer !== undefined ? buffer : []), stringToArray(expectedValue), message); +} + +function validateStreamFromString(reader, expectedValue, retrievedArrayBuffer) { + // Passing Uint8Array for byte streams; non-byte streams will simply ignore it + return reader.read(new Uint8Array(64)).then(function(data) { + if (!data.done) { + assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array"); + var newBuffer; + if (retrievedArrayBuffer) { + newBuffer = new Uint8Array(data.value.length + retrievedArrayBuffer.length); + newBuffer.set(retrievedArrayBuffer, 0); + newBuffer.set(data.value, retrievedArrayBuffer.length); + } else { + newBuffer = data.value; + } + return validateStreamFromString(reader, expectedValue, newBuffer); + } + validateBufferFromString(retrievedArrayBuffer, expectedValue, "Retrieve and verify stream"); + }); +} + +function validateStreamFromPartialString(reader, expectedValue, retrievedArrayBuffer) { + // Passing Uint8Array for byte streams; non-byte streams will simply ignore it + return reader.read(new Uint8Array(64)).then(function(data) { + if (!data.done) { + assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array"); + var newBuffer; + if (retrievedArrayBuffer) { + newBuffer = new Uint8Array(data.value.length + retrievedArrayBuffer.length); + newBuffer.set(retrievedArrayBuffer, 0); + newBuffer.set(data.value, retrievedArrayBuffer.length); + } else { + newBuffer = data.value; + } + return validateStreamFromPartialString(reader, expectedValue, newBuffer); + } + + var string = new TextDecoder("utf-8").decode(retrievedArrayBuffer); + return assert_true(string.search(expectedValue) != -1, "Retrieve and verify stream"); + }); +} + +// From streams tests +function delay(milliseconds) +{ + return new Promise(function(resolve) { + step_timeout(resolve, milliseconds); + }); +} + +function requestForbiddenHeaders(desc, forbiddenHeaders) { + var url = RESOURCES_DIR + "inspect-headers.py"; + var requestInit = {"headers": forbiddenHeaders} + var urlParameters = "?headers=" + Object.keys(forbiddenHeaders).join("|"); + + promise_test(function(test){ + return fetch(url + urlParameters, requestInit).then(function(resp) { + assert_equals(resp.status, 200, "HTTP status is 200"); + assert_equals(resp.type , "basic", "Response's type is basic"); + for (var header in forbiddenHeaders) + assert_not_equals(resp.headers.get("x-request-" + header), forbiddenHeaders[header], header + " does not have the value we defined"); + }); + }, desc); +} -- cgit v1.2.3