summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/fetch/api/resources
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/api/resources')
-rw-r--r--testing/web-platform/tests/fetch/api/resources/authentication.py14
-rw-r--r--testing/web-platform/tests/fetch/api/resources/bad-chunk-encoding.py13
-rw-r--r--testing/web-platform/tests/fetch/api/resources/basic.html5
-rw-r--r--testing/web-platform/tests/fetch/api/resources/cache.py18
-rw-r--r--testing/web-platform/tests/fetch/api/resources/clean-stash.py6
-rw-r--r--testing/web-platform/tests/fetch/api/resources/cors-top.txt1
-rw-r--r--testing/web-platform/tests/fetch/api/resources/cors-top.txt.headers1
-rw-r--r--testing/web-platform/tests/fetch/api/resources/data.json1
-rw-r--r--testing/web-platform/tests/fetch/api/resources/dump-authorization-header.py14
-rw-r--r--testing/web-platform/tests/fetch/api/resources/echo-content.h2.py7
-rw-r--r--testing/web-platform/tests/fetch/api/resources/echo-content.py12
-rw-r--r--testing/web-platform/tests/fetch/api/resources/empty.txt0
-rw-r--r--testing/web-platform/tests/fetch/api/resources/infinite-slow-response.py35
-rw-r--r--testing/web-platform/tests/fetch/api/resources/inspect-headers.py24
-rw-r--r--testing/web-platform/tests/fetch/api/resources/keepalive-helper.js176
-rw-r--r--testing/web-platform/tests/fetch/api/resources/keepalive-iframe.html22
-rw-r--r--testing/web-platform/tests/fetch/api/resources/keepalive-redirect-iframe.html23
-rw-r--r--testing/web-platform/tests/fetch/api/resources/keepalive-redirect-window.html42
-rw-r--r--testing/web-platform/tests/fetch/api/resources/method.py18
-rw-r--r--testing/web-platform/tests/fetch/api/resources/preflight.py78
-rw-r--r--testing/web-platform/tests/fetch/api/resources/redirect-empty-location.py3
-rw-r--r--testing/web-platform/tests/fetch/api/resources/redirect.h2.py14
-rw-r--r--testing/web-platform/tests/fetch/api/resources/redirect.py73
-rw-r--r--testing/web-platform/tests/fetch/api/resources/sandboxed-iframe.html34
-rw-r--r--testing/web-platform/tests/fetch/api/resources/script-with-header.py7
-rw-r--r--testing/web-platform/tests/fetch/api/resources/stash-put.py41
-rw-r--r--testing/web-platform/tests/fetch/api/resources/stash-take.py9
-rw-r--r--testing/web-platform/tests/fetch/api/resources/status.py11
-rw-r--r--testing/web-platform/tests/fetch/api/resources/sw-intercept-abort.js19
-rw-r--r--testing/web-platform/tests/fetch/api/resources/sw-intercept.js10
-rw-r--r--testing/web-platform/tests/fetch/api/resources/top.txt1
-rw-r--r--testing/web-platform/tests/fetch/api/resources/trickle.py15
-rw-r--r--testing/web-platform/tests/fetch/api/resources/utils.js120
33 files changed, 867 insertions, 0 deletions
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 @@
+<!DOCTYPE html>
+<!--
+ Duplicating /common/blank.html to make service worker scoping simpler in
+ ../abort/serviceworker-intercepted.https.html
+-->
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
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/resources/empty.txt
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 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<script>
+const SEARCH_PARAMS = new URL(location.href).searchParams;
+const ORIGIN = SEARCH_PARAMS.get('origin') || '';
+const FRAME_ORIGIN = new URL(location.href).origin;
+const TOKEN = SEARCH_PARAMS.get('token') || '';
+const METHOD = SEARCH_PARAMS.get('method') || 'GET';
+const SEND_ON_EVENT = SEARCH_PARAMS.get('sendOn') || 'load';
+const MODE = SEARCH_PARAMS.get('mode') || 'cors';
+const DISALLOW_CROSS_ORIGIN = SEARCH_PARAMS.get('disallowCrossOrigin') || '';
+// CORS requests are allowed by this URL by default.
+const url = `${ORIGIN}/fetch/api/resources/stash-put.py?key=${TOKEN}&value=on&mode=${MODE}`
+ + `&frame_origin=${FRAME_ORIGIN}` + (DISALLOW_CROSS_ORIGIN ? `&disallow_cross_origin=1` : '');
+
+addEventListener(SEND_ON_EVENT, () => {
+ let p = fetch(url, {keepalive: true, method: METHOD, mode: MODE});
+ window.parent.postMessage(TOKEN, '*');
+});
+</script>
+</html>
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 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<script>
+const SEARCH_PARAMS = new URL(location.href).searchParams;
+const ORIGIN1 = SEARCH_PARAMS.get('origin1') || '';
+const ORIGIN2 = SEARCH_PARAMS.get('origin2') || '';
+const WITH_HEADERS = !!SEARCH_PARAMS.has('with-headers');
+const TOKEN = SEARCH_PARAMS.get('token') || '';
+
+const url =
+ `${ORIGIN1}/fetch/api/resources/redirect.py?` +
+ `delay=500&` +
+ `allow_headers=foo&` +
+ `location=${ORIGIN2}/fetch/api/resources/stash-put.py?key=${TOKEN}%26value=on`;
+
+addEventListener('load', () => {
+ const headers = WITH_HEADERS ? {'foo': 'bar'} : undefined;
+ let p = fetch(url, {keepalive: true, headers});
+ window.parent.postMessage(TOKEN, '*');
+});
+</script>
+</html>
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 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+const TOKEN = token();
+const {
+ HTTP_NOTSAMESITE_ORIGIN,
+ HTTP_REMOTE_ORIGIN,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT
+} = get_host_info();
+
+const SEARCH_PARAMS = new URL(location.href).searchParams;
+const WITH_HEADERS = !!SEARCH_PARAMS.has('with-headers');
+const ORIGIN1 = SEARCH_PARAMS.get('origin1') || '';
+const ORIGIN2 = SEARCH_PARAMS.get('origin2') || '';
+const URL2 = SEARCH_PARAMS.get('url2') || '';
+
+const REDIRECT_DESTINATION = URL2 ? URL2 :
+ `${ORIGIN2}/fetch/api/resources/stash-put.py` +
+ `?key=${TOKEN}&value=on`;
+const FROM_URL =
+ `${ORIGIN1}/fetch/api/resources/redirect.py?` +
+ `delay=500&` +
+ `allow_headers=foo&` +
+ `location=${encodeURIComponent(REDIRECT_DESTINATION)}`;
+
+addEventListener('load', () => {
+ const headers = WITH_HEADERS ? {'foo': 'bar'} : undefined;
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ iframe.contentWindow.addEventListener('unload', () => {
+ iframe.contentWindow.fetch(FROM_URL, {keepalive: true, headers});
+ });
+
+ window.opener.postMessage(TOKEN, '*');
+ // Do NOT remove `iframe` here. We want to check the case where the nested
+ // frame is implicitly closed by window closure.
+});
+</script>
+</html>
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 @@
+<!doctype html>
+<html>
+<script>
+async function no_cors_should_be_rejected() {
+ let thrown = false;
+ try {
+ const resp = await fetch('top.txt');
+ } catch (e) {
+ thrown = true;
+ }
+ if (!thrown) {
+ throw Error('fetching "top.txt" should be rejected.');
+ }
+}
+
+async function null_origin_should_be_accepted() {
+ const url = 'top.txt?pipe=header(access-control-allow-origin,null)|' +
+ 'header(cache-control,no-store)';
+ const resp = await fetch(url);
+}
+
+async function test() {
+ try {
+ await no_cors_should_be_rejected();
+ await null_origin_should_be_accepted();
+ parent.postMessage('PASS', '*');
+ } catch (e) {
+ parent.postMessage(e.message, '*');
+ }
+}
+
+test();
+</script>
+</html>
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);
+}