summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/cors
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fetch/api/cors')
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-basic.any.js43
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-cookies-redirect.any.js49
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-cookies.any.js56
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-expose-star.sub.any.js41
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-filtering.sub.any.js69
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-keepalive.any.js116
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-multiple-origins.sub.any.js22
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-no-preflight.any.js41
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-origin.any.js51
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-cache.any.js46
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js19
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-redirect.any.js37
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-referrer.any.js51
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-response-validation.any.js33
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-star.any.js86
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight-status.any.js37
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-preflight.any.js62
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-redirect-credentials.any.js52
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-redirect-preflight.any.js46
-rw-r--r--testing/web-platform/tests/fetch/api/cors/cors-redirect.any.js42
-rw-r--r--testing/web-platform/tests/fetch/api/cors/data-url-iframe.html58
-rw-r--r--testing/web-platform/tests/fetch/api/cors/data-url-shared-worker.html53
-rw-r--r--testing/web-platform/tests/fetch/api/cors/data-url-worker.html50
-rw-r--r--testing/web-platform/tests/fetch/api/cors/resources/corspreflight.js58
-rw-r--r--testing/web-platform/tests/fetch/api/cors/resources/not-cors-safelisted.json13
-rw-r--r--testing/web-platform/tests/fetch/api/cors/sandboxed-iframe.html14
26 files changed, 1245 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-basic.any.js b/testing/web-platform/tests/fetch/api/cors/cors-basic.any.js
new file mode 100644
index 0000000000..95de0af2d8
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-basic.any.js
@@ -0,0 +1,43 @@
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+const {
+ HTTPS_ORIGIN,
+ HTTP_ORIGIN_WITH_DIFFERENT_PORT,
+ HTTP_REMOTE_ORIGIN,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT,
+ HTTPS_REMOTE_ORIGIN,
+} = get_host_info();
+
+function cors(desc, origin) {
+ const url = `${origin}${dirname(location.pathname)}${RESOURCES_DIR}top.txt`;
+ const urlAllowCors = `${url}?pipe=header(Access-Control-Allow-Origin,*)`;
+
+ promise_test((test) => {
+ return fetch(urlAllowCors, {'mode': 'no-cors'}).then((resp) => {
+ assert_equals(resp.status, 0, "Opaque filter: status is 0");
+ assert_equals(resp.statusText, "", "Opaque filter: statusText is \"\"");
+ assert_equals(resp.type , "opaque", "Opaque filter: response's type is opaque");
+ return resp.text().then((value) => {
+ assert_equals(value, "", "Opaque response should have an empty body");
+ });
+ });
+ }, `${desc} [no-cors mode]`);
+
+ promise_test((test) => {
+ return promise_rejects_js(test, TypeError, fetch(url, {'mode': 'cors'}));
+ }, `${desc} [server forbid CORS]`);
+
+ promise_test((test) => {
+ return fetch(urlAllowCors, {'mode': 'cors'}).then((resp) => {
+ assert_equals(resp.status, 200, "Fetch's response's status is 200");
+ assert_equals(resp.type , "cors", "CORS response's type is cors");
+ });
+ }, `${desc} [cors mode]`);
+}
+
+cors('Same domain different port', HTTP_ORIGIN_WITH_DIFFERENT_PORT);
+cors('Same domain different protocol different port', HTTPS_ORIGIN);
+cors('Cross domain basic usage', HTTP_REMOTE_ORIGIN);
+cors('Cross domain different port', HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT);
+cors('Cross domain different protocol', HTTPS_REMOTE_ORIGIN);
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-cookies-redirect.any.js b/testing/web-platform/tests/fetch/api/cors/cors-cookies-redirect.any.js
new file mode 100644
index 0000000000..f5217b4246
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-cookies-redirect.any.js
@@ -0,0 +1,49 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+var redirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var urlSetCookies1 = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+var urlSetCookies2 = get_host_info().HTTP_ORIGIN_WITH_DIFFERENT_PORT + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+var urlCheckCookies = get_host_info().HTTP_ORIGIN_WITH_DIFFERENT_PORT + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=cookie";
+
+var urlSetCookiesParameters = "?pipe=header(Access-Control-Allow-Origin," + location.origin + ")";
+urlSetCookiesParameters += "|header(Access-Control-Allow-Credentials,true)";
+
+urlSetCookiesParameters1 = urlSetCookiesParameters + "|header(Set-Cookie,a=1)";
+urlSetCookiesParameters2 = urlSetCookiesParameters + "|header(Set-Cookie,a=2)";
+
+urlClearCookiesParameters1 = urlSetCookiesParameters + "|header(Set-Cookie,a=1%3B%20max-age=0)";
+urlClearCookiesParameters2 = urlSetCookiesParameters + "|header(Set-Cookie,a=2%3B%20max-age=0)";
+
+promise_test(async (test) => {
+ await fetch(urlSetCookies1 + urlSetCookiesParameters1, {"credentials": "include", "mode": "cors"});
+ await fetch(urlSetCookies2 + urlSetCookiesParameters2, {"credentials": "include", "mode": "cors"});
+}, "Set cookies");
+
+function doTest(usePreflight) {
+ promise_test(async (test) => {
+ var url = redirectUrl;
+ var uuid_token = token();
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ urlParameters += "&redirect_status=301";
+ urlParameters += "&location=" + encodeURIComponent(urlCheckCookies);
+ urlParameters += "&allow_headers=a&headers=Cookie";
+ headers = [];
+ if (usePreflight)
+ headers.push(["a", "b"]);
+
+ var requestInit = {"credentials": "include", "mode": "cors", "headers": headers};
+ var response = await fetch(url + urlParameters, requestInit);
+
+ assert_equals(response.headers.get("x-request-cookie") , "a=2", "Request includes cookie(s)");
+ }, "Testing credentials after cross-origin redirection with CORS and " + (usePreflight ? "" : "no ") + "preflight");
+}
+
+doTest(false);
+doTest(true);
+
+promise_test(async (test) => {
+ await fetch(urlSetCookies1 + urlClearCookiesParameters1, {"credentials": "include", "mode": "cors"});
+ await fetch(urlSetCookies2 + urlClearCookiesParameters2, {"credentials": "include", "mode": "cors"});
+}, "Clean cookies");
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-cookies.any.js b/testing/web-platform/tests/fetch/api/cors/cors-cookies.any.js
new file mode 100644
index 0000000000..8c666e4782
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-cookies.any.js
@@ -0,0 +1,56 @@
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsCookies(desc, baseURL1, baseURL2, credentialsMode, cookies) {
+ var urlSetCookie = baseURL1 + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+ var urlCheckCookies = baseURL2 + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=cookie";
+ //enable cors with credentials
+ var urlParameters = "?pipe=header(Access-Control-Allow-Origin," + location.origin + ")";
+ urlParameters += "|header(Access-Control-Allow-Credentials,true)";
+
+ var urlCleanParameters = "?pipe=header(Access-Control-Allow-Origin," + location.origin + ")";
+ urlCleanParameters += "|header(Access-Control-Allow-Credentials,true)";
+ if (cookies) {
+ urlParameters += "|header(Set-Cookie,";
+ urlParameters += cookies.join(",True)|header(Set-Cookie,") + ",True)";
+ urlCleanParameters += "|header(Set-Cookie,";
+ urlCleanParameters += cookies.join("%3B%20max-age=0,True)|header(Set-Cookie,") + "%3B%20max-age=0,True)";
+ }
+
+ var requestInit = {"credentials": credentialsMode, "mode": "cors"};
+
+ promise_test(function(test){
+ return fetch(urlSetCookie + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ //check cookies sent
+ return fetch(urlCheckCookies, requestInit);
+ }).then(function(resp) {
+ assert_equals(resp.status, 200, "HTTP status is 200");
+ assert_false(resp.headers.has("Cookie") , "Cookie header is not exposed in response");
+ if (credentialsMode === "include" && baseURL1 === baseURL2) {
+ assert_equals(resp.headers.get("x-request-cookie") , cookies.join("; "), "Request includes cookie(s)");
+ }
+ else {
+ assert_false(resp.headers.has("x-request-cookie") , "Request should have no cookie");
+ }
+ //clean cookies
+ return fetch(urlSetCookie + urlCleanParameters, {"credentials": "include"});
+ }).catch(function(e) {
+ return fetch(urlSetCookie + urlCleanParameters, {"credentials": "include"}).then(function(resp) {
+ throw e;
+ })
+ });
+ }, desc);
+}
+
+var local = get_host_info().HTTP_ORIGIN;
+var remote = get_host_info().HTTP_REMOTE_ORIGIN;
+// FIXME: otherRemote might not be accessible on some test environments.
+var otherRemote = local.replace("http://", "http://www.");
+
+corsCookies("Omit mode: no cookie sent", local, local, "omit", ["g=7"]);
+corsCookies("Include mode: 1 cookie", remote, remote, "include", ["a=1"]);
+corsCookies("Include mode: local cookies are not sent with remote request", local, remote, "include", ["c=3"]);
+corsCookies("Include mode: remote cookies are not sent with local request", remote, local, "include", ["d=4"]);
+corsCookies("Same-origin mode: cookies are discarded in cors request", remote, remote, "same-origin", ["f=6"]);
+corsCookies("Include mode: remote cookies are not sent with other remote request", remote, otherRemote, "include", ["e=5"]);
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-expose-star.sub.any.js b/testing/web-platform/tests/fetch/api/cors/cors-expose-star.sub.any.js
new file mode 100644
index 0000000000..340e99ab5f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-expose-star.sub.any.js
@@ -0,0 +1,41 @@
+// META: script=../resources/utils.js
+
+const url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt",
+ sharedHeaders = "?pipe=header(Access-Control-Expose-Headers,*)|header(Test,X)|header(Set-Cookie,X)|header(*,whoa)|"
+
+promise_test(() => {
+ const headers = "header(Access-Control-Allow-Origin,*)"
+ return fetch(url + sharedHeaders + headers).then(resp => {
+ assert_equals(resp.status, 200)
+ assert_equals(resp.type , "cors")
+ assert_equals(resp.headers.get("test"), "X")
+ assert_equals(resp.headers.get("set-cookie"), null)
+ assert_equals(resp.headers.get("*"), "whoa")
+ })
+}, "Basic Access-Control-Expose-Headers: * support")
+
+promise_test(() => {
+ const origin = location.origin, // assuming an ASCII origin
+ headers = "header(Access-Control-Allow-Origin," + origin + ")|header(Access-Control-Allow-Credentials,true)"
+ return fetch(url + sharedHeaders + headers, { credentials:"include" }).then(resp => {
+ assert_equals(resp.status, 200)
+ assert_equals(resp.type , "cors")
+ assert_equals(resp.headers.get("content-type"), "text/plain") // safelisted
+ assert_equals(resp.headers.get("test"), null)
+ assert_equals(resp.headers.get("set-cookie"), null)
+ assert_equals(resp.headers.get("*"), "whoa")
+ })
+}, "* for credentialed fetches only matches literally")
+
+promise_test(() => {
+ const headers = "header(Access-Control-Allow-Origin,*)|header(Access-Control-Expose-Headers,set-cookie\\,*)"
+ return fetch(url + sharedHeaders + headers).then(resp => {
+ assert_equals(resp.status, 200)
+ assert_equals(resp.type , "cors")
+ assert_equals(resp.headers.get("test"), "X")
+ assert_equals(resp.headers.get("set-cookie"), null)
+ assert_equals(resp.headers.get("*"), "whoa")
+ })
+}, "* can be one of several values")
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-filtering.sub.any.js b/testing/web-platform/tests/fetch/api/cors/cors-filtering.sub.any.js
new file mode 100644
index 0000000000..a26eaccf2a
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-filtering.sub.any.js
@@ -0,0 +1,69 @@
+// META: script=../resources/utils.js
+
+function corsFilter(corsUrl, headerName, headerValue, isFiltered) {
+ var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|header(Access-Control-Allow-Origin,*)";
+ promise_test(function(test) {
+ return fetch(url).then(function(resp) {
+ assert_equals(resp.status, 200, "Fetch success with code 200");
+ assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
+ if (!isFiltered) {
+ assert_equals(resp.headers.get(headerName), headerValue,
+ headerName + " header should be included in response with value: " + headerValue);
+ } else {
+ assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
+ }
+ test.done();
+ });
+ }, "CORS filter on " + headerName + " header");
+}
+
+function corsExposeFilter(corsUrl, headerName, headerValue, isForbidden, withCredentials) {
+ var url = corsUrl + "?pipe=header(" + headerName + "," + encodeURIComponent(headerValue) +")|" +
+ "header(Access-Control-Allow-Origin, http://{{host}}:{{ports[http][0]}})" +
+ "header(Access-Control-Allow-Credentials, true)" +
+ "header(Access-Control-Expose-Headers," + headerName + ")";
+
+ var title = "CORS filter on " + headerName + " header, header is " + (isForbidden ? "forbidden" : "exposed");
+ if (withCredentials)
+ title+= "(credentials = include)";
+ promise_test(function(test) {
+ return fetch(new Request(url, { credentials: withCredentials ? "include" : "omit" })).then(function(resp) {
+ assert_equals(resp.status, 200, "Fetch success with code 200");
+ assert_equals(resp.type , "cors", "CORS fetch's response has cors type");
+ if (!isForbidden) {
+ assert_equals(resp.headers.get(headerName), headerValue,
+ headerName + " header should be included in response with value: " + headerValue);
+ } else {
+ assert_false(resp.headers.has(headerName), "UA should exclude " + headerName + " header from response");
+ }
+ test.done();
+ });
+ }, title);
+}
+
+var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "top.txt";
+
+corsFilter(url, "Cache-Control", "no-cache", false);
+corsFilter(url, "Content-Language", "fr", false);
+corsFilter(url, "Content-Type", "text/html", false);
+corsFilter(url, "Expires","04 May 1988 22:22:22 GMT" , false);
+corsFilter(url, "Last-Modified", "04 May 1988 22:22:22 GMT", false);
+corsFilter(url, "Pragma", "no-cache", false);
+corsFilter(url, "Content-Length", "3" , false); // top.txt contains "top"
+
+corsFilter(url, "Age", "27", true);
+corsFilter(url, "Server", "wptServe" , true);
+corsFilter(url, "Warning", "Mind the gap" , true);
+corsFilter(url, "Set-Cookie", "name=value" , true);
+corsFilter(url, "Set-Cookie2", "name=value" , true);
+
+corsExposeFilter(url, "Age", "27", false);
+corsExposeFilter(url, "Server", "wptServe" , false);
+corsExposeFilter(url, "Warning", "Mind the gap" , false);
+
+corsExposeFilter(url, "Set-Cookie", "name=value" , true);
+corsExposeFilter(url, "Set-Cookie2", "name=value" , true);
+corsExposeFilter(url, "Set-Cookie", "name=value" , true, true);
+corsExposeFilter(url, "Set-Cookie2", "name=value" , true, true);
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-keepalive.any.js b/testing/web-platform/tests/fetch/api/cors/cors-keepalive.any.js
new file mode 100644
index 0000000000..f54bf4f9b6
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-keepalive.any.js
@@ -0,0 +1,116 @@
+// META: global=window
+// META: timeout=long
+// META: title=Fetch API: keepalive handling
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=../resources/keepalive-helper.js
+// META: script=../resources/utils.js
+
+'use strict';
+
+const {
+ HTTP_NOTSAMESITE_ORIGIN,
+ HTTPS_ORIGIN,
+ HTTP_ORIGIN_WITH_DIFFERENT_PORT,
+ HTTP_REMOTE_ORIGIN,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT,
+ HTTPS_REMOTE_ORIGIN,
+} = get_host_info();
+
+/**
+ * Tests to cover the basic behaviors of keepalive + cors/no-cors mode requests
+ * to different `origin` when the initiator document is still alive. They should
+ * behave the same as without setting keepalive.
+ */
+function keepaliveCorsBasicTest(desc, origin) {
+ const url = `${origin}${dirname(location.pathname)}${RESOURCES_DIR}top.txt`;
+ const urlAllowCors = `${url}?pipe=header(Access-Control-Allow-Origin,*)`;
+
+ promise_test((test) => {
+ return fetch(urlAllowCors, {keepalive: true, 'mode': 'no-cors'})
+ .then((resp) => {
+ assert_equals(resp.status, 0, 'Opaque filter: status is 0');
+ assert_equals(resp.statusText, '', 'Opaque filter: statusText is ""');
+ assert_equals(
+ resp.type, 'opaque', 'Opaque filter: response\'s type is opaque');
+ return resp.text().then((value) => {
+ assert_equals(
+ value, '', 'Opaque response should have an empty body');
+ });
+ });
+ }, `${desc} [no-cors mode]`);
+
+ promise_test((test) => {
+ return promise_rejects_js(
+ test, TypeError, fetch(url, {keepalive: true, 'mode': 'cors'}));
+ }, `${desc} [cors mode, server forbid CORS]`);
+
+ promise_test((test) => {
+ return fetch(urlAllowCors, {keepalive: true, 'mode': 'cors'})
+ .then((resp) => {
+ assert_equals(resp.status, 200, 'Fetch\'s response\'s status is 200');
+ assert_equals(resp.type, 'cors', 'CORS response\'s type is cors');
+ });
+ }, `${desc} [cors mode]`);
+}
+
+keepaliveCorsBasicTest(
+ `[keepalive] Same domain different port`, HTTP_ORIGIN_WITH_DIFFERENT_PORT);
+keepaliveCorsBasicTest(
+ `[keepalive] Same domain different protocol different port`, HTTPS_ORIGIN);
+keepaliveCorsBasicTest(
+ `[keepalive] Cross domain basic usage`, HTTP_REMOTE_ORIGIN);
+keepaliveCorsBasicTest(
+ `[keepalive] Cross domain different port`,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT);
+keepaliveCorsBasicTest(
+ `[keepalive] Cross domain different protocol`, HTTPS_REMOTE_ORIGIN);
+
+/**
+ * In a same-site iframe, and in `unload` event handler, test to fetch
+ * a keepalive URL that involves in different cors modes.
+ */
+function keepaliveCorsInUnloadTest(description, origin, method) {
+ const evt = 'unload';
+ for (const mode of ['no-cors', 'cors']) {
+ for (const disallowCrossOrigin of [false, true]) {
+ const desc = `${description} ${method} request in ${evt} [${mode} mode` +
+ (disallowCrossOrigin ? ']' : ', server forbid CORS]');
+ const expectTokenExist = !disallowCrossOrigin || mode === 'no-cors';
+ promise_test(async (test) => {
+ const token1 = token();
+ const iframe = document.createElement('iframe');
+ iframe.src = getKeepAliveIframeUrl(token1, method, {
+ frameOrigin: '',
+ requestOrigin: origin,
+ sendOn: evt,
+ mode: mode,
+ disallowCrossOrigin
+ });
+ document.body.appendChild(iframe);
+ await iframeLoaded(iframe);
+ iframe.remove();
+ assert_equals(await getTokenFromMessage(), token1);
+
+ assertStashedTokenAsync(desc, token1, {expectTokenExist});
+ }, `${desc}; setting up`);
+ }
+ }
+}
+
+for (const method of ['GET', 'POST']) {
+ keepaliveCorsInUnloadTest(
+ '[keepalive] Same domain different port', HTTP_ORIGIN_WITH_DIFFERENT_PORT,
+ method);
+ keepaliveCorsInUnloadTest(
+ `[keepalive] Same domain different protocol different port`, HTTPS_ORIGIN,
+ method);
+ keepaliveCorsInUnloadTest(
+ `[keepalive] Cross domain basic usage`, HTTP_REMOTE_ORIGIN, method);
+ keepaliveCorsInUnloadTest(
+ `[keepalive] Cross domain different port`,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, method);
+ keepaliveCorsInUnloadTest(
+ `[keepalive] Cross domain different protocol`, HTTPS_REMOTE_ORIGIN,
+ method);
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-multiple-origins.sub.any.js b/testing/web-platform/tests/fetch/api/cors/cors-multiple-origins.sub.any.js
new file mode 100644
index 0000000000..b3abb92284
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-multiple-origins.sub.any.js
@@ -0,0 +1,22 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+function corsMultipleOrigins(originList) {
+ var urlParameters = "?origin=" + encodeURIComponent(originList.join(", "));
+ var url = "http://{{host}}:{{ports[http][1]}}" + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+ promise_test(function(test) {
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters));
+ }, "Listing multiple origins is illegal: " + originList);
+}
+/* Actual origin */
+var origin = "http://{{host}}:{{ports[http][0]}}";
+
+corsMultipleOrigins(["\"\"", "http://example.com", origin]);
+corsMultipleOrigins(["\"\"", "http://example.com", "*"]);
+corsMultipleOrigins(["\"\"", origin, origin]);
+corsMultipleOrigins(["*", "http://example.com", "*"]);
+corsMultipleOrigins(["*", "http://example.com", origin]);
+corsMultipleOrigins(["", "http://example.com", "https://example2.com"]);
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-no-preflight.any.js b/testing/web-platform/tests/fetch/api/cors/cors-no-preflight.any.js
new file mode 100644
index 0000000000..7a0269aae4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-no-preflight.any.js
@@ -0,0 +1,41 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsNoPreflight(desc, baseURL, method, headerName, headerValue) {
+
+ var uuid_token = token();
+ var url = baseURL + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ var requestInit = {"mode": "cors", "method": method, "headers":{}};
+ if (headerName)
+ requestInit["headers"][headerName] = headerValue;
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "0", "No preflight request has been made");
+ });
+ });
+ }, desc);
+}
+
+var host_info = get_host_info();
+
+corsNoPreflight("Cross domain basic usage [GET]", host_info.HTTP_REMOTE_ORIGIN, "GET");
+corsNoPreflight("Same domain different port [GET]", host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT, "GET");
+corsNoPreflight("Cross domain different port [GET]", host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, "GET");
+corsNoPreflight("Cross domain different protocol [GET]", host_info.HTTPS_REMOTE_ORIGIN, "GET");
+corsNoPreflight("Same domain different protocol different port [GET]", host_info.HTTPS_ORIGIN, "GET");
+corsNoPreflight("Cross domain [POST]", host_info.HTTP_REMOTE_ORIGIN, "POST");
+corsNoPreflight("Cross domain [HEAD]", host_info.HTTP_REMOTE_ORIGIN, "HEAD");
+corsNoPreflight("Cross domain [GET] [Accept: */*]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Accept", "*/*");
+corsNoPreflight("Cross domain [GET] [Accept-Language: fr]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Accept-Language", "fr");
+corsNoPreflight("Cross domain [GET] [Content-Language: fr]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Language", "fr");
+corsNoPreflight("Cross domain [GET] [Content-Type: application/x-www-form-urlencoded]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "application/x-www-form-urlencoded");
+corsNoPreflight("Cross domain [GET] [Content-Type: multipart/form-data]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "multipart/form-data");
+corsNoPreflight("Cross domain [GET] [Content-Type: text/plain]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "text/plain");
+corsNoPreflight("Cross domain [GET] [Content-Type: text/plain;charset=utf-8]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "text/plain;charset=utf-8");
+corsNoPreflight("Cross domain [GET] [Content-Type: Text/Plain;charset=utf-8]", host_info.HTTP_REMOTE_ORIGIN, "GET" , "Content-Type", "Text/Plain;charset=utf-8");
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-origin.any.js b/testing/web-platform/tests/fetch/api/cors/cors-origin.any.js
new file mode 100644
index 0000000000..30a02d910f
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-origin.any.js
@@ -0,0 +1,51 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+/* If origin is undefined, it is set to fetched url's origin*/
+function corsOrigin(desc, baseURL, method, origin, shouldPass) {
+ if (!origin)
+ origin = baseURL;
+
+ var uuid_token = token();
+ var urlParameters = "?token=" + uuid_token + "&max_age=0&origin=" + encodeURIComponent(origin) + "&allow_methods=" + method;
+ var url = baseURL + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+ var requestInit = {"mode": "cors", "method": method};
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ if (shouldPass) {
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ });
+ } else {
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+ }
+ });
+ }, desc);
+
+}
+
+var host_info = get_host_info();
+
+/* Actual origin */
+var origin = host_info.HTTP_ORIGIN;
+
+corsOrigin("Cross domain different subdomain [origin OK]", host_info.HTTP_REMOTE_ORIGIN, "GET", origin, true);
+corsOrigin("Cross domain different subdomain [origin KO]", host_info.HTTP_REMOTE_ORIGIN, "GET", undefined, false);
+corsOrigin("Same domain different port [origin OK]", host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT, "GET", origin, true);
+corsOrigin("Same domain different port [origin KO]", host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT, "GET", undefined, false);
+corsOrigin("Cross domain different port [origin OK]", host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, "GET", origin, true);
+corsOrigin("Cross domain different port [origin KO]", host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, "GET", undefined, false);
+corsOrigin("Cross domain different protocol [origin OK]", host_info.HTTPS_REMOTE_ORIGIN, "GET", origin, true);
+corsOrigin("Cross domain different protocol [origin KO]", host_info.HTTPS_REMOTE_ORIGIN, "GET", undefined, false);
+corsOrigin("Same domain different protocol different port [origin OK]", host_info.HTTPS_ORIGIN, "GET", origin, true);
+corsOrigin("Same domain different protocol different port [origin KO]", host_info.HTTPS_ORIGIN, "GET", undefined, false);
+corsOrigin("Cross domain [POST] [origin OK]", host_info.HTTP_REMOTE_ORIGIN, "POST", origin, true);
+corsOrigin("Cross domain [POST] [origin KO]", host_info.HTTP_REMOTE_ORIGIN, "POST", undefined, false);
+corsOrigin("Cross domain [HEAD] [origin OK]", host_info.HTTP_REMOTE_ORIGIN, "HEAD", origin, true);
+corsOrigin("Cross domain [HEAD] [origin KO]", host_info.HTTP_REMOTE_ORIGIN, "HEAD", undefined, false);
+corsOrigin("CORS preflight [PUT] [origin OK]", host_info.HTTP_REMOTE_ORIGIN, "PUT", origin, true);
+corsOrigin("CORS preflight [PUT] [origin KO]", host_info.HTTP_REMOTE_ORIGIN, "PUT", undefined, false);
+corsOrigin("Allowed origin: \"\" [origin KO]", host_info.HTTP_REMOTE_ORIGIN, "GET", "" , false);
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-cache.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-cache.any.js
new file mode 100644
index 0000000000..ce6a169d81
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-cache.any.js
@@ -0,0 +1,46 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+var cors_url = get_host_info().HTTP_REMOTE_ORIGIN +
+ dirname(location.pathname) +
+ RESOURCES_DIR +
+ "preflight.py";
+
+promise_test((test) => {
+ var uuid_token = token();
+ var request_url =
+ cors_url + "?token=" + uuid_token + "&max_age=12000&allow_methods=POST" +
+ "&allow_headers=x-test-header";
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash")
+ .then(() => {
+ return fetch(
+ new Request(request_url,
+ {
+ mode: "cors",
+ method: "POST",
+ headers: [["x-test-header", "test1"]]
+ }));
+ })
+ .then((resp) => {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
+ })
+ .then((res) => res.text())
+ .then((txt) => {
+ assert_equals(txt, "1", "Server stash must be cleared.");
+ return fetch(
+ new Request(request_url,
+ {
+ mode: "cors",
+ method: "POST",
+ headers: [["x-test-header", "test2"]]
+ }));
+ })
+ .then((resp) => {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "0", "Preflight request has not been made");
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
+ });
+});
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js
new file mode 100644
index 0000000000..b2747ccd5b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js
@@ -0,0 +1,19 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=resources/corspreflight.js
+
+const corsURL = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+promise_test(() => fetch("resources/not-cors-safelisted.json").then(res => res.json().then(runTests)), "Loading data…");
+
+function runTests(testArray) {
+ testArray.forEach(testItem => {
+ const [headerName, headerValue] = testItem;
+ corsPreflight("Need CORS-preflight for " + headerName + "/" + headerValue + " header",
+ corsURL,
+ "GET",
+ true,
+ [[headerName, headerValue]]);
+ });
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-redirect.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-redirect.any.js
new file mode 100644
index 0000000000..15f7659abd
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-redirect.any.js
@@ -0,0 +1,37 @@
+// META: global=window,worker
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsPreflightRedirect(desc, redirectUrl, redirectLocation, redirectStatus, redirectPreflight) {
+ var uuid_token = token();
+ var url = redirectUrl;
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ urlParameters += "&redirect_status=" + redirectStatus;
+ urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+ if (redirectPreflight)
+ urlParameters += "&redirect_preflight";
+ var requestInit = {"mode": "cors", "redirect": "follow"};
+
+ /* Force preflight */
+ requestInit["headers"] = {"x-force-preflight": ""};
+ urlParameters += "&allow_headers=x-force-preflight";
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+ });
+ }, desc);
+}
+
+var redirectUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+for (var code of [301, 302, 303, 307, 308]) {
+ /* preflight should not follow the redirection */
+ corsPreflightRedirect("Redirection " + code + " on preflight failed", redirectUrl, locationUrl, code, true);
+ /* preflight is done before redirection: preflight force redirect to error */
+ corsPreflightRedirect("Redirection " + code + " after preflight failed", redirectUrl, locationUrl, code, false);
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-referrer.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-referrer.any.js
new file mode 100644
index 0000000000..5df9fcf142
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-referrer.any.js
@@ -0,0 +1,51 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsPreflightReferrer(desc, corsUrl, referrerPolicy, referrer, expectedReferrer) {
+ var uuid_token = token();
+ var url = corsUrl;
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ var requestInit = {"mode": "cors", "referrerPolicy": referrerPolicy};
+
+ if (referrer)
+ requestInit.referrer = referrer;
+
+ /* Force preflight */
+ requestInit["headers"] = {"x-force-preflight": ""};
+ urlParameters += "&allow_headers=x-force-preflight";
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ assert_equals(resp.headers.get("x-preflight-referrer"), expectedReferrer, "Preflight's referrer is correct");
+ assert_equals(resp.headers.get("x-referrer"), expectedReferrer, "Request's referrer is correct");
+ assert_equals(resp.headers.get("x-control-request-headers"), "", "Access-Control-Allow-Headers value");
+ });
+ });
+ }, desc + " and referrer: " + (referrer ? "'" + referrer + "'" : "default"));
+}
+
+var corsUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+var origin = get_host_info().HTTP_ORIGIN + "/";
+
+corsPreflightReferrer("Referrer policy: no-referrer", corsUrl, "no-referrer", undefined, "");
+corsPreflightReferrer("Referrer policy: no-referrer", corsUrl, "no-referrer", "myreferrer", "");
+
+corsPreflightReferrer("Referrer policy: \"\"", corsUrl, "", undefined, origin);
+corsPreflightReferrer("Referrer policy: \"\"", corsUrl, "", "myreferrer", origin);
+
+corsPreflightReferrer("Referrer policy: no-referrer-when-downgrade", corsUrl, "no-referrer-when-downgrade", undefined, location.toString())
+corsPreflightReferrer("Referrer policy: no-referrer-when-downgrade", corsUrl, "no-referrer-when-downgrade", "myreferrer", new URL("myreferrer", location).toString());
+
+corsPreflightReferrer("Referrer policy: origin", corsUrl, "origin", undefined, origin);
+corsPreflightReferrer("Referrer policy: origin", corsUrl, "origin", "myreferrer", origin);
+
+corsPreflightReferrer("Referrer policy: origin-when-cross-origin", corsUrl, "origin-when-cross-origin", undefined, origin);
+corsPreflightReferrer("Referrer policy: origin-when-cross-origin", corsUrl, "origin-when-cross-origin", "myreferrer", origin);
+
+corsPreflightReferrer("Referrer policy: unsafe-url", corsUrl, "unsafe-url", undefined, location.toString());
+corsPreflightReferrer("Referrer policy: unsafe-url", corsUrl, "unsafe-url", "myreferrer", new URL("myreferrer", location).toString());
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-response-validation.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-response-validation.any.js
new file mode 100644
index 0000000000..718e351c1d
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-response-validation.any.js
@@ -0,0 +1,33 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsPreflightResponseValidation(desc, corsUrl, allowHeaders, allowMethods) {
+ var uuid_token = token();
+ var url = corsUrl;
+ var requestInit = {"mode": "cors"};
+ /* Force preflight */
+ requestInit["headers"] = {"x-force-preflight": ""};
+
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ urlParameters += "&allow_headers=x-force-preflight";
+ if (allowHeaders)
+ urlParameters += "," + allowHeaders;
+ if (allowMethods)
+ urlParameters += "&allow_methods="+ allowMethods;
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(async function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ await promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+
+ return fetch(url + urlParameters).then(function(resp) {
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ });
+ });
+ }, desc);
+}
+
+var corsUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+corsPreflightResponseValidation("Preflight response with a bad Access-Control-Allow-Headers", corsUrl, "Bad value", null);
+corsPreflightResponseValidation("Preflight response with a bad Access-Control-Allow-Methods", corsUrl, null, "Bad value");
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-star.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-star.any.js
new file mode 100644
index 0000000000..f9fb20469c
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-star.any.js
@@ -0,0 +1,86 @@
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+const url = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py",
+ origin = location.origin // assuming an ASCII origin
+
+function preflightTest(succeeds, withCredentials, allowMethod, allowHeader, useMethod, useHeader) {
+ return promise_test(t => {
+ let testURL = url + "?",
+ requestInit = {}
+ if (withCredentials) {
+ testURL += "origin=" + origin + "&"
+ testURL += "credentials&"
+ requestInit.credentials = "include"
+ }
+ if (useMethod) {
+ requestInit.method = useMethod
+ }
+ if (useHeader.length > 0) {
+ requestInit.headers = [useHeader]
+ }
+ testURL += "allow_methods=" + allowMethod + "&"
+ testURL += "allow_headers=" + allowHeader + "&"
+
+ if (succeeds) {
+ return fetch(testURL, requestInit).then(resp => {
+ assert_equals(resp.headers.get("x-origin"), origin)
+ })
+ } else {
+ return promise_rejects_js(t, TypeError, fetch(testURL, requestInit))
+ }
+ }, "CORS that " + (succeeds ? "succeeds" : "fails") + " with credentials: " + withCredentials + "; method: " + useMethod + " (allowed: " + allowMethod + "); header: " + useHeader + " (allowed: " + allowHeader + ")")
+}
+
+// "GET" does not pass the case-sensitive method check, but in the safe list.
+preflightTest(true, false, "get", "x-test", "GET", ["X-Test", "1"])
+// Headers check is case-insensitive, and "*" works as any for method.
+preflightTest(true, false, "*", "x-test", "SUPER", ["X-Test", "1"])
+// "*" works as any only without credentials.
+preflightTest(true, false, "*", "*", "OK", ["X-Test", "1"])
+preflightTest(false, true, "*", "*", "OK", ["X-Test", "1"])
+preflightTest(false, true, "*", "", "PUT", [])
+preflightTest(false, true, "get", "*", "GET", ["X-Test", "1"])
+preflightTest(false, true, "*", "*", "GET", ["X-Test", "1"])
+// Exact character match works even for "*" with credentials.
+preflightTest(true, true, "*", "*", "*", ["*", "1"])
+
+// The following methods are upper-cased for init["method"] by
+// https://fetch.spec.whatwg.org/#concept-method-normalize
+// but not in Access-Control-Allow-Methods response.
+// But they are https://fetch.spec.whatwg.org/#cors-safelisted-method,
+// CORS anyway passes regardless of the cases.
+for (const METHOD of ['GET', 'HEAD', 'POST']) {
+ const method = METHOD.toLowerCase();
+ preflightTest(true, true, METHOD, "*", METHOD, [])
+ preflightTest(true, true, METHOD, "*", method, [])
+ preflightTest(true, true, method, "*", METHOD, [])
+ preflightTest(true, true, method, "*", method, [])
+}
+
+// The following methods are upper-cased for init["method"] by
+// https://fetch.spec.whatwg.org/#concept-method-normalize
+// but not in Access-Control-Allow-Methods response.
+// As they are not https://fetch.spec.whatwg.org/#cors-safelisted-method,
+// Access-Control-Allow-Methods should contain upper-cased methods,
+// while init["method"] can be either in upper or lower case.
+for (const METHOD of ['DELETE', 'PUT']) {
+ const method = METHOD.toLowerCase();
+ preflightTest(true, true, METHOD, "*", METHOD, [])
+ preflightTest(true, true, METHOD, "*", method, [])
+ preflightTest(false, true, method, "*", METHOD, [])
+ preflightTest(false, true, method, "*", method, [])
+}
+
+// "PATCH" is NOT upper-cased in both places because it is not listed in
+// https://fetch.spec.whatwg.org/#concept-method-normalize.
+// So Access-Control-Allow-Methods value and init["method"] should match
+// case-sensitively.
+preflightTest(true, true, "PATCH", "*", "PATCH", [])
+preflightTest(false, true, "PATCH", "*", "patch", [])
+preflightTest(false, true, "patch", "*", "PATCH", [])
+preflightTest(true, true, "patch", "*", "patch", [])
+
+// "Authorization" header can't be wildcarded.
+preflightTest(false, false, "*", "*", "POST", ["Authorization", "123"])
+preflightTest(true, false, "*", "*, Authorization", "POST", ["Authorization", "123"])
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight-status.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight-status.any.js
new file mode 100644
index 0000000000..a4467a6087
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight-status.any.js
@@ -0,0 +1,37 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+/* Check preflight is ok if status is ok status (200 to 299)*/
+function corsPreflightStatus(desc, corsUrl, preflightStatus) {
+ var uuid_token = token();
+ var url = corsUrl;
+ var requestInit = {"mode": "cors"};
+ /* Force preflight */
+ requestInit["headers"] = {"x-force-preflight": ""};
+
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ urlParameters += "&allow_headers=x-force-preflight";
+ urlParameters += "&preflight_status=" + preflightStatus;
+
+ promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ assert_equals(resp.status, 200, "Clean stash response's status is 200");
+ if (200 <= preflightStatus && 299 >= preflightStatus) {
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ });
+ } else {
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+ }
+ });
+ }, desc);
+}
+
+var corsUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+for (status of [200, 201, 202, 203, 204, 205, 206,
+ 300, 301, 302, 303, 304, 305, 306, 307, 308,
+ 400, 401, 402, 403, 404, 405,
+ 501, 502, 503, 504, 505])
+ corsPreflightStatus("Preflight answered with status " + status, corsUrl, status);
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-preflight.any.js b/testing/web-platform/tests/fetch/api/cors/cors-preflight.any.js
new file mode 100644
index 0000000000..045422f40b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-preflight.any.js
@@ -0,0 +1,62 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=resources/corspreflight.js
+
+var corsUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+corsPreflight("CORS [DELETE], server allows", corsUrl, "DELETE", true);
+corsPreflight("CORS [DELETE], server refuses", corsUrl, "DELETE", false);
+corsPreflight("CORS [PUT], server allows", corsUrl, "PUT", true);
+corsPreflight("CORS [PUT], server allows, check preflight has user agent", corsUrl + "?checkUserAgentHeaderInPreflight", "PUT", true);
+corsPreflight("CORS [PUT], server refuses", corsUrl, "PUT", false);
+corsPreflight("CORS [PATCH], server allows", corsUrl, "PATCH", true);
+corsPreflight("CORS [PATCH], server refuses", corsUrl, "PATCH", false);
+corsPreflight("CORS [patcH], server allows", corsUrl, "patcH", true);
+corsPreflight("CORS [patcH], server refuses", corsUrl, "patcH", false);
+corsPreflight("CORS [NEW], server allows", corsUrl, "NEW", true);
+corsPreflight("CORS [NEW], server refuses", corsUrl, "NEW", false);
+corsPreflight("CORS [chicken], server allows", corsUrl, "chicken", true);
+corsPreflight("CORS [chicken], server refuses", corsUrl, "chicken", false);
+
+corsPreflight("CORS [GET] [x-test-header: allowed], server allows", corsUrl, "GET", true, [["x-test-header1", "allowed"]]);
+corsPreflight("CORS [GET] [x-test-header: refused], server refuses", corsUrl, "GET", false, [["x-test-header1", "refused"]]);
+
+var headers = [
+ ["x-test-header1", "allowedOrRefused"],
+ ["x-test-header2", "allowedOrRefused"],
+ ["X-test-header3", "allowedOrRefused"],
+ ["x-test-header-b", "allowedOrRefused"],
+ ["x-test-header-D", "allowedOrRefused"],
+ ["x-test-header-C", "allowedOrRefused"],
+ ["x-test-header-a", "allowedOrRefused"],
+ ["Content-Type", "allowedOrRefused"],
+];
+var safeHeaders= [
+ ["Accept", "*"],
+ ["Accept-Language", "bzh"],
+ ["Content-Language", "eu"],
+];
+
+corsPreflight("CORS [GET] [several headers], server allows", corsUrl, "GET", true, headers, safeHeaders);
+corsPreflight("CORS [GET] [several headers], server refuses", corsUrl, "GET", false, headers, safeHeaders);
+corsPreflight("CORS [PUT] [several headers], server allows", corsUrl, "PUT", true, headers, safeHeaders);
+corsPreflight("CORS [PUT] [several headers], server refuses", corsUrl, "PUT", false, headers, safeHeaders);
+
+corsPreflight("CORS [PUT] [only safe headers], server allows", corsUrl, "PUT", true, null, safeHeaders);
+
+promise_test(async t => {
+ const url = `${corsUrl}?allow_headers=*`;
+ await promise_rejects_js(t, TypeError, fetch(url, {
+ headers: {
+ authorization: 'foobar'
+ }
+ }));
+}, '"authorization" should not be covered by the wildcard symbol');
+
+promise_test(async t => {
+ const url = `${corsUrl}?allow_headers=authorization`;
+ await fetch(url, { headers: {
+ authorization: 'foobar'
+ }});
+}, '"authorization" should be covered by "authorization"'); \ No newline at end of file
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-redirect-credentials.any.js b/testing/web-platform/tests/fetch/api/cors/cors-redirect-credentials.any.js
new file mode 100644
index 0000000000..2aff313406
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-redirect-credentials.any.js
@@ -0,0 +1,52 @@
+// META: timeout=long
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsRedirectCredentials(desc, redirectUrl, redirectLocation, redirectStatus, locationCredentials) {
+ var url = redirectUrl
+ var urlParameters = "?redirect_status=" + redirectStatus;
+ urlParameters += "&location=" + redirectLocation.replace("://", "://" + locationCredentials + "@");
+
+ var requestInit = {"mode": "cors", "redirect": "follow"};
+
+ promise_test(t => {
+ const result = fetch(url + urlParameters, requestInit)
+ if(locationCredentials === "") {
+ return result;
+ } else {
+ return promise_rejects_js(t, TypeError, result);
+ }
+ }, desc);
+}
+
+var redirPath = dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var preflightPath = dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+var host_info = get_host_info();
+
+var localRedirect = host_info.HTTP_ORIGIN + redirPath;
+var remoteRedirect = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + redirPath;
+
+var localLocation = host_info.HTTP_ORIGIN + preflightPath;
+var remoteLocation = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + preflightPath;
+var remoteLocation2 = host_info.HTTP_REMOTE_ORIGIN + preflightPath;
+
+for (var code of [301, 302, 303, 307, 308]) {
+ corsRedirectCredentials("Redirect " + code + " from same origin to remote without user and password", localRedirect, remoteLocation, code, "");
+
+ corsRedirectCredentials("Redirect " + code + " from same origin to remote with user and password", localRedirect, remoteLocation, code, "user:password");
+ corsRedirectCredentials("Redirect " + code + " from same origin to remote with user", localRedirect, remoteLocation, code, "user:");
+ corsRedirectCredentials("Redirect " + code + " from same origin to remote with password", localRedirect, remoteLocation, code, ":password");
+
+ corsRedirectCredentials("Redirect " + code + " from remote to same origin with user and password", remoteRedirect, localLocation, code, "user:password");
+ corsRedirectCredentials("Redirect " + code + " from remote to same origin with user", remoteRedirect, localLocation, code, "user:");
+ corsRedirectCredentials("Redirect " + code + " from remote to same origin with password", remoteRedirect, localLocation, code, ":password");
+
+ corsRedirectCredentials("Redirect " + code + " from remote to same remote with user and password", remoteRedirect, remoteLocation, code, "user:password");
+ corsRedirectCredentials("Redirect " + code + " from remote to same remote with user", remoteRedirect, remoteLocation, code, "user:");
+ corsRedirectCredentials("Redirect " + code + " from remote to same remote with password", remoteRedirect, remoteLocation, code, ":password");
+
+ corsRedirectCredentials("Redirect " + code + " from remote to another remote with user and password", remoteRedirect, remoteLocation2, code, "user:password");
+ corsRedirectCredentials("Redirect " + code + " from remote to another remote with user", remoteRedirect, remoteLocation2, code, "user:");
+ corsRedirectCredentials("Redirect " + code + " from remote to another remote with password", remoteRedirect, remoteLocation2, code, ":password");
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-redirect-preflight.any.js b/testing/web-platform/tests/fetch/api/cors/cors-redirect-preflight.any.js
new file mode 100644
index 0000000000..50848170d0
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-redirect-preflight.any.js
@@ -0,0 +1,46 @@
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsRedirect(desc, redirectUrl, redirectLocation, redirectStatus, expectSuccess) {
+ var urlBaseParameters = "&redirect_status=" + redirectStatus;
+ var urlParametersSuccess = urlBaseParameters + "&allow_headers=x-w3c&location=" + encodeURIComponent(redirectLocation + "?allow_headers=x-w3c");
+ var urlParametersFailure = urlBaseParameters + "&location=" + encodeURIComponent(redirectLocation);
+
+ var requestInit = {"mode": "cors", "redirect": "follow", "headers" : [["x-w3c", "test"]]};
+
+ promise_test(function(test) {
+ var uuid_token = token();
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ return fetch(redirectUrl + "?token=" + uuid_token + "&max_age=0" + urlParametersSuccess, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ });
+ });
+ }, desc + " (preflight after redirection success case)");
+ promise_test(function(test) {
+ var uuid_token = token();
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ return promise_rejects_js(test, TypeError, fetch(redirectUrl + "?token=" + uuid_token + "&max_age=0" + urlParametersFailure, requestInit));
+ });
+ }, desc + " (preflight after redirection failure case)");
+}
+
+var redirPath = dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var preflightPath = dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+var host_info = get_host_info();
+
+var localRedirect = host_info.HTTP_ORIGIN + redirPath;
+var remoteRedirect = host_info.HTTP_REMOTE_ORIGIN + redirPath;
+
+var localLocation = host_info.HTTP_ORIGIN + preflightPath;
+var remoteLocation = host_info.HTTP_REMOTE_ORIGIN + preflightPath;
+var remoteLocation2 = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + preflightPath;
+
+for (var code of [301, 302, 303, 307, 308]) {
+ corsRedirect("Redirect " + code + ": same origin to cors", localRedirect, remoteLocation, code);
+ corsRedirect("Redirect " + code + ": cors to same origin", remoteRedirect, localLocation, code);
+ corsRedirect("Redirect " + code + ": cors to another cors", remoteRedirect, remoteLocation2, code);
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/cors-redirect.any.js b/testing/web-platform/tests/fetch/api/cors/cors-redirect.any.js
new file mode 100644
index 0000000000..cdf4097d56
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/cors-redirect.any.js
@@ -0,0 +1,42 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function corsRedirect(desc, redirectUrl, redirectLocation, redirectStatus, expectedOrigin) {
+ var uuid_token = token();
+ var url = redirectUrl;
+ var urlParameters = "?token=" + uuid_token + "&max_age=0";
+ urlParameters += "&redirect_status=" + redirectStatus;
+ urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+ var requestInit = {"mode": "cors", "redirect": "follow"};
+
+ return promise_test(function(test) {
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(resp) {
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "0", "No preflight request has been made");
+ assert_equals(resp.headers.get("x-origin"), expectedOrigin, "Origin is correctly set after redirect");
+ });
+ });
+ }, desc);
+}
+
+var redirPath = dirname(location.pathname) + RESOURCES_DIR + "redirect.py";
+var preflightPath = dirname(location.pathname) + RESOURCES_DIR + "preflight.py";
+
+var host_info = get_host_info();
+
+var localRedirect = host_info.HTTP_ORIGIN + redirPath;
+var remoteRedirect = host_info.HTTP_REMOTE_ORIGIN + redirPath;
+
+var localLocation = host_info.HTTP_ORIGIN + preflightPath;
+var remoteLocation = host_info.HTTP_REMOTE_ORIGIN + preflightPath;
+var remoteLocation2 = host_info.HTTP_ORIGIN_WITH_DIFFERENT_PORT + preflightPath;
+
+for (var code of [301, 302, 303, 307, 308]) {
+ corsRedirect("Redirect " + code + ": cors to same cors", remoteRedirect, remoteLocation, code, location.origin);
+ corsRedirect("Redirect " + code + ": cors to another cors", remoteRedirect, remoteLocation2, code, "null");
+ corsRedirect("Redirect " + code + ": same origin to cors", localRedirect, remoteLocation, code, location.origin);
+ corsRedirect("Redirect " + code + ": cors to same origin", remoteRedirect, localLocation, code, "null");
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/data-url-iframe.html b/testing/web-platform/tests/fetch/api/cors/data-url-iframe.html
new file mode 100644
index 0000000000..217baa3c46
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/data-url-iframe.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body></body>
+<script>
+
+const createDataUrlIframe = (url, cors) => {
+ const iframe = document.createElement("iframe");
+ const fetchURL = new URL(url, location.href) +
+ `${cors === 'null-origin'
+ ? '?pipe=header(Access-Control-Allow-Origin, null)' : ''}`;
+ const tag_name = 'script';
+ iframe.src =
+ `data:text/html, <${tag_name}>` +
+ `async function test() {` +
+ ` let allowed = true;` +
+ ` try {` +
+ ` await fetch('${fetchURL}');` +
+ ` } catch (e) {` +
+ ` allowed = false;` +
+ ` }` +
+ ` parent.postMessage({allowed}, '*');` +
+ `}` +
+ `test(); </${tag_name}>`;
+ return iframe;
+};
+
+const fetch_from_data_url_iframe_test =
+ (url, cors, expectation, description) => {
+ promise_test(async () => {
+ const iframe = createDataUrlIframe(url, cors);
+ document.body.appendChild(iframe);
+ const msgEvent = await new Promise(resolve => window.onmessage = resolve);
+ assert_equals(msgEvent.data.allowed ? 'allowed' : 'rejected', expectation);
+ }, description);
+};
+
+fetch_from_data_url_iframe_test(
+ '../resources/top.txt',
+ 'acao-omitted',
+ 'rejected',
+ 'fetching "top.txt" without ACAO should be rejected.'
+);
+fetch_from_data_url_iframe_test(
+ '../resources/top.txt',
+ 'null-origin',
+ 'allowed',
+ 'fetching "top.txt" with CORS allowing null origin should be allowed.'
+);
+fetch_from_data_url_iframe_test(
+ 'data:text/plain, top',
+ 'acao-omitted',
+ 'allowed',
+ 'fetching data url script should be allowed.'
+);
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/cors/data-url-shared-worker.html b/testing/web-platform/tests/fetch/api/cors/data-url-shared-worker.html
new file mode 100644
index 0000000000..d69748ab26
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/data-url-shared-worker.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const fetch_from_data_url_worker_test =
+ (url, cors, expectation, description) => {
+ promise_test(async () => {
+ const fetchURL = new URL(url, location.href) +
+ `${cors === 'null-origin'
+ ? '?pipe=header(Access-Control-Allow-Origin, null)' : ''}`;
+ const scriptURL =
+ `data:text/javascript,` +
+ `async function test(port) {` +
+ ` let allowed = true;` +
+ ` try {` +
+ ` await fetch('${fetchURL}');` +
+ ` } catch (e) {` +
+ ` allowed = false;` +
+ ` }` +
+ ` port.postMessage({allowed});` +
+ `}` +
+ `onconnect = e => {` +
+ ` test(e.ports[0]);` +
+ `};`;
+ const worker = new SharedWorker(scriptURL);
+ const msgEvent =
+ await new Promise(resolve => worker.port.onmessage = resolve);
+ assert_equals(msgEvent.data.allowed ? 'allowed' : 'rejected', expectation);
+ }, description);
+};
+
+fetch_from_data_url_worker_test(
+ '../resources/top.txt',
+ 'acao-omitted',
+ 'rejected',
+ 'fetching "top.txt" without ACAO should be rejected.'
+);
+fetch_from_data_url_worker_test(
+ '../resources/top.txt',
+ 'null-origin',
+ 'allowed',
+ 'fetching "top.txt" with CORS allowing null origin should be allowed.'
+);
+fetch_from_data_url_worker_test(
+ 'data:text/plain, top',
+ 'acao-omitted',
+ 'allowed',
+ 'fetching data url script should be allowed.'
+);
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/cors/data-url-worker.html b/testing/web-platform/tests/fetch/api/cors/data-url-worker.html
new file mode 100644
index 0000000000..13113e6262
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/data-url-worker.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const fetch_from_data_url_shared_worker_test =
+ (url, cors, expectation, description) => {
+ promise_test(async () => {
+ const fetchURL = new URL(url, location.href) +
+ `${cors === 'null-origin'
+ ? '?pipe=header(Access-Control-Allow-Origin, null)' : ''}`;
+ const scriptURL =
+ `data:text/javascript,` +
+ `async function test() {` +
+ ` let allowed = true;` +
+ ` try {` +
+ ` await fetch('${fetchURL}');` +
+ ` } catch (e) {` +
+ ` allowed = false;` +
+ ` }` +
+ ` postMessage({allowed});` +
+ `}` +
+ `test();`;
+ const worker = new Worker(scriptURL);
+ const msgEvent = await new Promise(resolve => worker.onmessage = resolve);
+ assert_equals(msgEvent.data.allowed ? 'allowed' : 'rejected', expectation);
+ }, description);
+};
+
+fetch_from_data_url_shared_worker_test(
+ '../resources/top.txt',
+ 'acao-omitted',
+ 'rejected',
+ 'fetching "top.txt" without ACAO should be rejected.'
+);
+fetch_from_data_url_shared_worker_test(
+ '../resources/top.txt',
+ 'null-origin',
+ 'allowed',
+ 'fetching "top.txt" with CORS allowing null origin should be allowed.'
+);
+fetch_from_data_url_shared_worker_test(
+ 'data:text/plain, top',
+ 'acao-omitted',
+ 'allowed',
+ 'fetching data url script should be allowed.'
+);
+
+</script>
diff --git a/testing/web-platform/tests/fetch/api/cors/resources/corspreflight.js b/testing/web-platform/tests/fetch/api/cors/resources/corspreflight.js
new file mode 100644
index 0000000000..18b8f6dfa2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/resources/corspreflight.js
@@ -0,0 +1,58 @@
+function headerNames(headers) {
+ let names = [];
+ for (let header of headers) {
+ names.push(header[0].toLowerCase());
+ }
+ return names;
+}
+
+/*
+ Check preflight is done
+ Control if server allows method and headers and check accordingly
+ Check control access headers added by UA (for method and headers)
+*/
+function corsPreflight(desc, corsUrl, method, allowed, headers, safeHeaders) {
+ return promise_test(function(test) {
+ var uuid_token = token();
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token).then(function(response) {
+ var url = corsUrl + (corsUrl.indexOf("?") === -1 ? "?" : "&");
+ var urlParameters = "token=" + uuid_token + "&max_age=0";
+ var requestInit = {"mode": "cors", "method": method};
+ var requestHeaders = [];
+ if (headers)
+ requestHeaders.push.apply(requestHeaders, headers);
+ if (safeHeaders)
+ requestHeaders.push.apply(requestHeaders, safeHeaders);
+ requestInit["headers"] = requestHeaders;
+
+ if (allowed) {
+ urlParameters += "&allow_methods=" + method + "&control_request_headers";
+ if (headers) {
+ //Make the server allow the headers
+ urlParameters += "&allow_headers=" + headerNames(headers).join("%20%2C");
+ }
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ if (headers) {
+ var actualHeaders = resp.headers.get("x-control-request-headers").toLowerCase().split(",");
+ for (var i in actualHeaders)
+ actualHeaders[i] = actualHeaders[i].trim();
+ for (var header of headers)
+ assert_in_array(header[0].toLowerCase(), actualHeaders, "Preflight asked permission for header: " + header);
+
+ let accessControlAllowHeaders = headerNames(headers).sort().join(",");
+ assert_equals(resp.headers.get("x-control-request-headers"), accessControlAllowHeaders, "Access-Control-Allow-Headers value");
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token);
+ } else {
+ assert_equals(resp.headers.get("x-control-request-headers"), null, "Access-Control-Request-Headers should be omitted")
+ }
+ });
+ } else {
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit)).then(function(){
+ return fetch(RESOURCES_DIR + "clean-stash.py?token=" + uuid_token);
+ });
+ }
+ });
+ }, desc);
+}
diff --git a/testing/web-platform/tests/fetch/api/cors/resources/not-cors-safelisted.json b/testing/web-platform/tests/fetch/api/cors/resources/not-cors-safelisted.json
new file mode 100644
index 0000000000..945dc0f93b
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/resources/not-cors-safelisted.json
@@ -0,0 +1,13 @@
+[
+ ["accept", "\""],
+ ["accept", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"],
+ ["accept-language", "\u0001"],
+ ["accept-language", "@"],
+ ["authorization", "basics"],
+ ["content-language", "\u0001"],
+ ["content-language", "@"],
+ ["content-type", "text/html"],
+ ["content-type", "text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"],
+ ["range", "bytes 0-"],
+ ["test", "hi"]
+]
diff --git a/testing/web-platform/tests/fetch/api/cors/sandboxed-iframe.html b/testing/web-platform/tests/fetch/api/cors/sandboxed-iframe.html
new file mode 100644
index 0000000000..feb9f1f2e5
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/cors/sandboxed-iframe.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe sandbox="allow-scripts" src="../resources/sandboxed-iframe.html"></iframe>
+<script>
+promise_test(async (t) => {
+ const message = await new Promise((resolve) => {
+ window.addEventListener('message', e => resolve(e.data));
+ });
+ assert_equals(message, 'PASS');
+}, 'CORS with sandboxed iframe');
+</script>
+</html>