summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fetch/api/redirect
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/redirect
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/redirect')
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-back-to-original-origin.any.js38
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-count.any.js51
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-empty-location.any.js21
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.any.js35
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.https.any.js18
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-location-escape.tentative.any.js46
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-location.any.js73
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-method.any.js112
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-mode.any.js59
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-origin.any.js68
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-referrer-override.any.js104
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-referrer.any.js66
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-schemes.any.js19
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-to-dataurl.any.js28
-rw-r--r--testing/web-platform/tests/fetch/api/redirect/redirect-upload.h2.any.js33
15 files changed, 771 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-back-to-original-origin.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-back-to-original-origin.any.js
new file mode 100644
index 0000000000..74d731f242
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-back-to-original-origin.any.js
@@ -0,0 +1,38 @@
+// META: global=window,worker
+// META: script=/common/get-host-info.sub.js
+
+const BASE = location.href;
+const IS_HTTPS = new URL(BASE).protocol === 'https:';
+const REMOTE_HOST = get_host_info()['REMOTE_HOST'];
+const REMOTE_PORT =
+ IS_HTTPS ? get_host_info()['HTTPS_PORT'] : get_host_info()['HTTP_PORT'];
+
+const REMOTE_ORIGIN =
+ new URL(`//${REMOTE_HOST}:${REMOTE_PORT}`, BASE).origin;
+const DESTINATION = new URL('../resources/cors-top.txt', BASE);
+
+function CreateURL(url, BASE, params) {
+ const u = new URL(url, BASE);
+ for (const {name, value} of params) {
+ u.searchParams.append(name, value);
+ }
+ return u;
+}
+
+const redirect =
+ CreateURL('/fetch/api/resources/redirect.py', REMOTE_ORIGIN,
+ [{name: 'redirect_status', value: 303},
+ {name: 'location', value: DESTINATION.href}]);
+
+promise_test(async (test) => {
+ const res = await fetch(redirect.href, {mode: 'no-cors'});
+ // This is discussed at https://github.com/whatwg/fetch/issues/737.
+ assert_equals(res.type, 'opaque');
+}, 'original => remote => original with mode: "no-cors"');
+
+promise_test(async (test) => {
+ const res = await fetch(redirect.href, {mode: 'cors'});
+ assert_equals(res.type, 'cors');
+}, 'original => remote => original with mode: "cors"');
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-count.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-count.any.js
new file mode 100644
index 0000000000..420f9c0dfc
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-count.any.js
@@ -0,0 +1,51 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+// META: timeout=long
+
+/**
+ * Fetches a target that returns response with HTTP status code `statusCode` to
+ * redirect `maxCount` times.
+ */
+function redirectCountTest(maxCount, {statusCode, shouldPass = true} = {}) {
+ const desc = `Redirect ${statusCode} ${maxCount} times`;
+
+ const fromUrl = `${RESOURCES_DIR}redirect.py`;
+ const toUrl = fromUrl;
+ const token1 = token();
+ const url = `${fromUrl}?token=${token1}` +
+ `&max_age=0` +
+ `&redirect_status=${statusCode}` +
+ `&max_count=${maxCount}` +
+ `&location=${encodeURIComponent(toUrl)}`;
+
+ const requestInit = {'redirect': 'follow'};
+
+ promise_test((test) => {
+ return fetch(`${RESOURCES_DIR}clean-stash.py?token=${token1}`)
+ .then((resp) => {
+ assert_equals(
+ resp.status, 200, 'Clean stash response\'s status is 200');
+
+ if (!shouldPass)
+ return promise_rejects_js(test, TypeError, fetch(url, requestInit));
+
+ return fetch(url, requestInit)
+ .then((resp) => {
+ assert_equals(resp.status, 200, 'Response\'s status is 200');
+ return resp.text();
+ })
+ .then((body) => {
+ assert_equals(
+ body, maxCount.toString(), `Redirected ${maxCount} times`);
+ });
+ });
+ }, desc);
+}
+
+for (const statusCode of [301, 302, 303, 307, 308]) {
+ redirectCountTest(20, {statusCode});
+ redirectCountTest(21, {statusCode, shouldPass: false});
+}
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-empty-location.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-empty-location.any.js
new file mode 100644
index 0000000000..487f4d42e9
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-empty-location.any.js
@@ -0,0 +1,21 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+// Tests receiving a redirect response with a Location header with an empty
+// value.
+
+const url = RESOURCES_DIR + 'redirect-empty-location.py';
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, fetch(url, {redirect:'follow'}));
+}, 'redirect response with empty Location, follow mode');
+
+promise_test(t => {
+ return fetch(url, {redirect:'manual'})
+ .then(resp => {
+ assert_equals(resp.type, 'opaqueredirect');
+ assert_equals(resp.status, 0);
+ });
+}, 'redirect response with empty Location, manual mode');
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.any.js
new file mode 100644
index 0000000000..c9ac13f3db
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.any.js
@@ -0,0 +1,35 @@
+// 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
+
+'use strict';
+
+const {
+ HTTP_NOTSAMESITE_ORIGIN,
+ HTTP_REMOTE_ORIGIN,
+ HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT
+} = get_host_info();
+
+
+keepaliveRedirectInUnloadTest('same-origin redirect');
+keepaliveRedirectInUnloadTest(
+ 'same-origin redirect + preflight', {withPreflight: true});
+keepaliveRedirectInUnloadTest('cross-origin redirect', {
+ origin1: HTTP_REMOTE_ORIGIN,
+ origin2: HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT
+});
+keepaliveRedirectInUnloadTest('cross-origin redirect + preflight', {
+ origin1: HTTP_REMOTE_ORIGIN,
+ origin2: HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT,
+ withPreflight: true
+});
+keepaliveRedirectInUnloadTest(
+ 'redirect to file URL',
+ {url2: 'file://tmp/bar.txt', expectFetchSucceed: false});
+keepaliveRedirectInUnloadTest('redirect to data URL', {
+ url2: 'data:text/plain;base64,cmVzcG9uc2UncyBib2R5',
+ expectFetchSucceed: false
+});
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.https.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.https.any.js
new file mode 100644
index 0000000000..54e4bc31fa
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-keepalive.https.any.js
@@ -0,0 +1,18 @@
+// META: global=window
+// 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
+
+'use strict';
+
+const {
+ HTTP_NOTSAMESITE_ORIGIN,
+ HTTPS_NOTSAMESITE_ORIGIN,
+} = get_host_info();
+
+keepaliveRedirectTest(`mixed content redirect`, {
+ origin1: HTTPS_NOTSAMESITE_ORIGIN,
+ origin2: HTTP_NOTSAMESITE_ORIGIN,
+ expectFetchSucceed: false
+});
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-location-escape.tentative.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-location-escape.tentative.any.js
new file mode 100644
index 0000000000..779ad70579
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-location-escape.tentative.any.js
@@ -0,0 +1,46 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+// See https://github.com/whatwg/fetch/issues/883 for the behavior covered by
+// this test. As of writing, the Fetch spec has not been updated to cover these.
+
+// redirectLocation tests that a Location header of |locationHeader| is resolved
+// to a URL which ends in |expectedUrlSuffix|. |locationHeader| is interpreted
+// as a byte sequence via isomorphic encode, as described in [INFRA]. This
+// allows the caller to specify byte sequences which are not valid UTF-8.
+// However, this means, e.g., U+2603 must be passed in as "\xe2\x98\x83", its
+// UTF-8 encoding, not "\u2603".
+//
+// [INFRA] https://infra.spec.whatwg.org/#isomorphic-encode
+function redirectLocation(
+ desc, redirectUrl, locationHeader, expectedUrlSuffix) {
+ promise_test(function(test) {
+ // Note we use escape() instead of encodeURIComponent(), so that characters
+ // are escaped as bytes in the isomorphic encoding.
+ var url = redirectUrl + '?simple=1&location=' + escape(locationHeader);
+
+ return fetch(url, {'redirect': 'follow'}).then(function(resp) {
+ assert_true(
+ resp.url.endsWith(expectedUrlSuffix),
+ resp.url + ' ends with ' + expectedUrlSuffix);
+ });
+ }, desc);
+}
+
+var redirUrl = RESOURCES_DIR + 'redirect.py';
+redirectLocation(
+ 'Redirect to escaped UTF-8', redirUrl, 'top.txt?%E2%98%83%e2%98%83',
+ 'top.txt?%E2%98%83%e2%98%83');
+redirectLocation(
+ 'Redirect to unescaped UTF-8', redirUrl, 'top.txt?\xe2\x98\x83',
+ 'top.txt?%E2%98%83');
+redirectLocation(
+ 'Redirect to escaped and unescaped UTF-8', redirUrl,
+ 'top.txt?\xe2\x98\x83%e2%98%83', 'top.txt?%E2%98%83%e2%98%83');
+redirectLocation(
+ 'Escaping produces double-percent', redirUrl, 'top.txt?%\xe2\x98\x83',
+ 'top.txt?%%E2%98%83');
+redirectLocation(
+ 'Redirect to invalid UTF-8', redirUrl, 'top.txt?\xff', 'top.txt?%FF');
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-location.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-location.any.js
new file mode 100644
index 0000000000..3d483bdcd4
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-location.any.js
@@ -0,0 +1,73 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+const VALID_URL = 'top.txt';
+const INVALID_URL = 'invalidurl:';
+const DATA_URL = 'data:text/plain;base64,cmVzcG9uc2UncyBib2R5';
+
+/**
+ * A test to fetch a URL that returns response redirecting to `toUrl` with
+ * `status` as its HTTP status code. `expectStatus` can be set to test the
+ * status code in fetch's Promise response.
+ */
+function redirectLocationTest(toUrlDesc, {
+ toUrl = undefined,
+ status,
+ expectStatus = undefined,
+ mode,
+ shouldPass = true
+} = {}) {
+ toUrlDesc = toUrl ? `with ${toUrlDesc}` : `without`;
+ const desc = `Redirect ${status} in "${mode}" mode ${toUrlDesc} location`;
+ const url = `${RESOURCES_DIR}redirect.py?redirect_status=${status}` +
+ (toUrl ? `&location=${encodeURIComponent(toUrl)}` : '');
+ const requestInit = {'redirect': mode};
+ if (!expectStatus)
+ expectStatus = status;
+
+ promise_test((test) => {
+ if (mode === 'error' || !shouldPass)
+ return promise_rejects_js(test, TypeError, fetch(url, requestInit));
+ if (mode === 'manual')
+ return fetch(url, requestInit).then((resp) => {
+ assert_equals(resp.status, 0, "Response's status is 0");
+ assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
+ assert_equals(resp.statusText, '', `Response's statusText is ""`);
+ assert_true(resp.headers.entries().next().done, "Headers should be empty");
+ });
+
+ if (mode === 'follow')
+ return fetch(url, requestInit).then((resp) => {
+ assert_equals(
+ resp.status, expectStatus, `Response's status is ${expectStatus}`);
+ });
+ assert_unreached(`${mode} is not a valid redirect mode`);
+ }, desc);
+}
+
+// FIXME: We may want to mix redirect-mode and cors-mode.
+for (const status of [301, 302, 303, 307, 308]) {
+ redirectLocationTest('without location', {status, mode: 'follow'});
+ redirectLocationTest('without location', {status, mode: 'manual'});
+ // FIXME: Add tests for "error" redirect-mode without location.
+
+ // When succeeded, `follow` mode should have followed all redirects.
+ redirectLocationTest(
+ 'valid', {toUrl: VALID_URL, status, expectStatus: 200, mode: 'follow'});
+ redirectLocationTest('valid', {toUrl: VALID_URL, status, mode: 'manual'});
+ redirectLocationTest('valid', {toUrl: VALID_URL, status, mode: 'error'});
+
+ redirectLocationTest(
+ 'invalid',
+ {toUrl: INVALID_URL, status, mode: 'follow', shouldPass: false});
+ redirectLocationTest('invalid', {toUrl: INVALID_URL, status, mode: 'manual'});
+ redirectLocationTest('invalid', {toUrl: INVALID_URL, status, mode: 'error'});
+
+ redirectLocationTest(
+ 'data', {toUrl: DATA_URL, status, mode: 'follow', shouldPass: false});
+ // FIXME: Should this pass?
+ redirectLocationTest('data', {toUrl: DATA_URL, status, mode: 'manual'});
+ redirectLocationTest('data', {toUrl: DATA_URL, status, mode: 'error'});
+}
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-method.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-method.any.js
new file mode 100644
index 0000000000..9fe086a9db
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-method.any.js
@@ -0,0 +1,112 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+
+// Creates a promise_test that fetches a URL that returns a redirect response.
+//
+// |opts| has additional options:
+// |opts.body|: the request body as a string or blob (default is empty body)
+// |opts.expectedBodyAsString|: the expected response body as a string. The
+// server is expected to echo the request body. The default is the empty string
+// if the request after redirection isn't POST; otherwise it's |opts.body|.
+// |opts.expectedRequestContentType|: the expected Content-Type of redirected
+// request.
+function redirectMethod(desc, redirectUrl, redirectLocation, redirectStatus, method, expectedMethod, opts) {
+ let url = redirectUrl;
+ let urlParameters = "?redirect_status=" + redirectStatus;
+ urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+ let requestHeaders = {
+ "Content-Encoding": "Identity",
+ "Content-Language": "en-US",
+ "Content-Location": "foo",
+ };
+ let requestInit = {"method": method, "redirect": "follow", "headers" : requestHeaders};
+ opts = opts || {};
+ if (opts.body) {
+ requestInit.body = opts.body;
+ }
+
+ promise_test(function(test) {
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ let expectedRequestContentType = "NO";
+ if (opts.expectedRequestContentType) {
+ expectedRequestContentType = opts.expectedRequestContentType;
+ }
+
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.type, "basic", "Response's type basic");
+ assert_equals(
+ resp.headers.get("x-request-method"),
+ expectedMethod,
+ "Request method after redirection is " + expectedMethod);
+ let hasRequestBodyHeader = true;
+ if (opts.expectedStripRequestBodyHeader) {
+ hasRequestBodyHeader = !opts.expectedStripRequestBodyHeader;
+ }
+ assert_equals(
+ resp.headers.get("x-request-content-type"),
+ expectedRequestContentType,
+ "Request Content-Type after redirection is " + expectedRequestContentType);
+ [
+ "Content-Encoding",
+ "Content-Language",
+ "Content-Location"
+ ].forEach(header => {
+ let xHeader = "x-request-" + header.toLowerCase();
+ let expectedValue = hasRequestBodyHeader ? requestHeaders[header] : "NO";
+ assert_equals(
+ resp.headers.get(xHeader),
+ expectedValue,
+ "Request " + header + " after redirection is " + expectedValue);
+ });
+ assert_true(resp.redirected);
+ return resp.text().then(function(text) {
+ let expectedBody = "";
+ if (expectedMethod == "POST") {
+ expectedBody = opts.expectedBodyAsString || requestInit.body;
+ }
+ let expectedContentLength = expectedBody ? expectedBody.length.toString() : "NO";
+ assert_equals(text, expectedBody, "request body");
+ assert_equals(
+ resp.headers.get("x-request-content-length"),
+ expectedContentLength,
+ "Request Content-Length after redirection is " + expectedContentLength);
+ });
+ });
+ }, desc);
+}
+
+promise_test(function(test) {
+ assert_false(new Response().redirected);
+ return fetch(RESOURCES_DIR + "method.py").then(function(resp) {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_false(resp.redirected);
+ });
+}, "Response.redirected should be false on not-redirected responses");
+
+var redirUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = "method.py";
+
+const stringBody = "this is my body";
+const blobBody = new Blob(["it's me the blob!", " ", "and more blob!"]);
+const blobBodyAsString = "it's me the blob! and more blob!";
+
+redirectMethod("Redirect 301 with GET", redirUrl, locationUrl, 301, "GET", "GET");
+redirectMethod("Redirect 301 with POST", redirUrl, locationUrl, 301, "POST", "GET", { body: stringBody, expectedStripRequestBodyHeader: true });
+redirectMethod("Redirect 301 with HEAD", redirUrl, locationUrl, 301, "HEAD", "HEAD");
+
+redirectMethod("Redirect 302 with GET", redirUrl, locationUrl, 302, "GET", "GET");
+redirectMethod("Redirect 302 with POST", redirUrl, locationUrl, 302, "POST", "GET", { body: stringBody, expectedStripRequestBodyHeader: true });
+redirectMethod("Redirect 302 with HEAD", redirUrl, locationUrl, 302, "HEAD", "HEAD");
+
+redirectMethod("Redirect 303 with GET", redirUrl, locationUrl, 303, "GET", "GET");
+redirectMethod("Redirect 303 with POST", redirUrl, locationUrl, 303, "POST", "GET", { body: stringBody, expectedStripRequestBodyHeader: true });
+redirectMethod("Redirect 303 with HEAD", redirUrl, locationUrl, 303, "HEAD", "HEAD");
+redirectMethod("Redirect 303 with TESTING", redirUrl, locationUrl, 303, "TESTING", "GET", { expectedStripRequestBodyHeader: true });
+
+redirectMethod("Redirect 307 with GET", redirUrl, locationUrl, 307, "GET", "GET");
+redirectMethod("Redirect 307 with POST (string body)", redirUrl, locationUrl, 307, "POST", "POST", { body: stringBody , expectedRequestContentType: "text/plain;charset=UTF-8"});
+redirectMethod("Redirect 307 with POST (blob body)", redirUrl, locationUrl, 307, "POST", "POST", { body: blobBody, expectedBodyAsString: blobBodyAsString });
+redirectMethod("Redirect 307 with HEAD", redirUrl, locationUrl, 307, "HEAD", "HEAD");
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-mode.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-mode.any.js
new file mode 100644
index 0000000000..9f1ff98c65
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-mode.any.js
@@ -0,0 +1,59 @@
+// META: script=/common/get-host-info.sub.js
+
+var redirectLocation = "cors-top.txt";
+const { ORIGIN, REMOTE_ORIGIN } = get_host_info();
+
+function testRedirect(origin, redirectStatus, redirectMode, corsMode) {
+ var url = new URL("../resources/redirect.py", self.location);
+ if (origin === "cross-origin") {
+ url.host = get_host_info().REMOTE_HOST;
+ url.port = get_host_info().HTTP_PORT;
+ }
+
+ var urlParameters = "?redirect_status=" + redirectStatus;
+ urlParameters += "&location=" + encodeURIComponent(redirectLocation);
+
+ var requestInit = {redirect: redirectMode, mode: corsMode};
+
+ promise_test(function(test) {
+ if (redirectMode === "error" ||
+ (corsMode === "no-cors" && redirectMode !== "follow" && origin !== "same-origin"))
+ return promise_rejects_js(test, TypeError, fetch(url + urlParameters, requestInit));
+ if (redirectMode === "manual")
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ assert_equals(resp.status, 0, "Response's status is 0");
+ assert_equals(resp.type, "opaqueredirect", "Response's type is opaqueredirect");
+ assert_equals(resp.statusText, "", "Response's statusText is \"\"");
+ assert_equals(resp.url, url + urlParameters, "Response URL should be the original one");
+ });
+ if (redirectMode === "follow")
+ return fetch(url + urlParameters, requestInit).then(function(resp) {
+ if (corsMode !== "no-cors" || origin === "same-origin") {
+ assert_true(new URL(resp.url).pathname.endsWith(redirectLocation), "Response's url should be the redirected one");
+ assert_equals(resp.status, 200, "Response's status is 200");
+ } else {
+ assert_equals(resp.type, "opaque", "Response is opaque");
+ }
+ });
+ assert_unreached(redirectMode + " is no a valid redirect mode");
+ }, origin + " redirect " + redirectStatus + " in " + redirectMode + " redirect and " + corsMode + " mode");
+}
+
+for (var origin of ["same-origin", "cross-origin"]) {
+ for (var statusCode of [301, 302, 303, 307, 308]) {
+ for (var redirect of ["error", "manual", "follow"]) {
+ for (var mode of ["cors", "no-cors"])
+ testRedirect(origin, statusCode, redirect, mode);
+ }
+ }
+}
+
+promise_test(async (t) => {
+ const destination = `${ORIGIN}/common/blank.html`;
+ // We use /common/redirect.py intentionally, as we want a CORS error.
+ const url =
+ `${REMOTE_ORIGIN}/common/redirect.py?location=${destination}`;
+ await promise_rejects_js(t, TypeError, fetch(url, { redirect: "manual" }));
+}, "manual redirect with a CORS error should be rejected");
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-origin.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-origin.any.js
new file mode 100644
index 0000000000..6001c509b1
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-origin.any.js
@@ -0,0 +1,68 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+const {
+ HTTP_ORIGIN,
+ HTTP_REMOTE_ORIGIN,
+} = get_host_info();
+
+/**
+ * Fetches `fromUrl` with 'cors' and 'follow' modes that returns response to
+ * redirect to `toUrl`.
+ */
+function testOriginAfterRedirection(
+ desc, method, fromUrl, toUrl, statusCode, expectedOrigin) {
+ desc = `[${method}] Redirect ${statusCode} ${desc}`;
+ const token1 = token();
+ const url = `${fromUrl}?token=${token1}&max_age=0` +
+ `&redirect_status=${statusCode}` +
+ `&location=${encodeURIComponent(toUrl)}`;
+
+ const requestInit = {method, 'mode': 'cors', 'redirect': 'follow'};
+
+ promise_test(function(test) {
+ return fetch(`${RESOURCES_DIR}clean-stash.py?token=${token1}`)
+ .then((cleanResponse) => {
+ assert_equals(
+ cleanResponse.status, 200,
+ `Clean stash response's status is 200`);
+ return fetch(url, requestInit).then((redirectResponse) => {
+ assert_equals(
+ redirectResponse.status, 200,
+ `Inspect header response's status is 200`);
+ assert_equals(
+ redirectResponse.headers.get('x-request-origin'),
+ expectedOrigin, 'Check origin header');
+ });
+ });
+ }, desc);
+}
+
+const FROM_URL = `${RESOURCES_DIR}redirect.py`;
+const CORS_FROM_URL =
+ `${HTTP_REMOTE_ORIGIN}${dirname(location.pathname)}${FROM_URL}`;
+const TO_URL = `${HTTP_ORIGIN}${dirname(location.pathname)}${
+ RESOURCES_DIR}inspect-headers.py?headers=origin`;
+const CORS_TO_URL = `${HTTP_REMOTE_ORIGIN}${dirname(location.pathname)}${
+ RESOURCES_DIR}inspect-headers.py?cors&headers=origin`;
+
+for (const statusCode of [301, 302, 303, 307, 308]) {
+ for (const method of ['GET', 'POST']) {
+ testOriginAfterRedirection(
+ 'Same origin to same origin', method, FROM_URL, TO_URL, statusCode,
+ null);
+ testOriginAfterRedirection(
+ 'Same origin to other origin', method, FROM_URL, CORS_TO_URL,
+ statusCode, HTTP_ORIGIN);
+ testOriginAfterRedirection(
+ 'Other origin to other origin', method, CORS_FROM_URL, CORS_TO_URL,
+ statusCode, HTTP_ORIGIN);
+ // TODO(crbug.com/1432059): Fix broken tests.
+ testOriginAfterRedirection(
+ 'Other origin to same origin', method, CORS_FROM_URL, `${TO_URL}&cors`,
+ statusCode, 'null');
+ }
+}
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-referrer-override.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-referrer-override.any.js
new file mode 100644
index 0000000000..56e55d79e1
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-referrer-override.any.js
@@ -0,0 +1,104 @@
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function getExpectation(expectations, init, initScenario, redirectPolicy, redirectScenario) {
+ let policies = [
+ expectations[initPolicy][initScenario],
+ expectations[redirectPolicy][redirectScenario]
+ ];
+
+ if (policies.includes("omitted")) {
+ return null;
+ } else if (policies.includes("origin")) {
+ return referrerOrigin;
+ } else {
+ // "stripped-referrer"
+ return referrerUrl;
+ }
+}
+
+function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
+ var url = redirectUrl;
+ var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
+ var description = desc + ", " + referrerPolicy + " init, " + redirectReferrerPolicy + " redirect header ";
+
+ if (redirectReferrerPolicy)
+ urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
+
+ var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
+ promise_test(function(test) {
+ return fetch(url + urlParameters, requestInit).then(function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
+ });
+ }, description);
+}
+
+var referrerOrigin = get_host_info().HTTP_ORIGIN + "/";
+var referrerUrl = location.href;
+
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=referer";
+var crossLocationUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
+
+var expectations = {
+ "no-referrer": {
+ "same-origin": "omitted",
+ "cross-origin": "omitted"
+ },
+ "no-referrer-when-downgrade": {
+ "same-origin": "stripped-referrer",
+ "cross-origin": "stripped-referrer"
+ },
+ "origin": {
+ "same-origin": "origin",
+ "cross-origin": "origin"
+ },
+ "origin-when-cross-origin": {
+ "same-origin": "stripped-referrer",
+ "cross-origin": "origin",
+ },
+ "same-origin": {
+ "same-origin": "stripped-referrer",
+ "cross-origin": "omitted"
+ },
+ "strict-origin": {
+ "same-origin": "origin",
+ "cross-origin": "origin"
+ },
+ "strict-origin-when-cross-origin": {
+ "same-origin": "stripped-referrer",
+ "cross-origin": "origin"
+ },
+ "unsafe-url": {
+ "same-origin": "stripped-referrer",
+ "cross-origin": "stripped-referrer"
+ }
+};
+
+for (var initPolicy in expectations) {
+ for (var redirectPolicy in expectations) {
+
+ // Redirect to same-origin URL
+ testReferrerAfterRedirection(
+ "Same origin redirection",
+ redirectUrl,
+ locationUrl,
+ initPolicy,
+ redirectPolicy,
+ getExpectation(expectations, initPolicy, "same-origin", redirectPolicy, "same-origin"));
+
+ // Redirect to cross-origin URL
+ testReferrerAfterRedirection(
+ "Cross origin redirection",
+ redirectUrl,
+ crossLocationUrl,
+ initPolicy,
+ redirectPolicy,
+ getExpectation(expectations, initPolicy, "same-origin", redirectPolicy, "cross-origin"));
+ }
+}
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-referrer.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-referrer.any.js
new file mode 100644
index 0000000000..99fda42e69
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-referrer.any.js
@@ -0,0 +1,66 @@
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+function testReferrerAfterRedirection(desc, redirectUrl, redirectLocation, referrerPolicy, redirectReferrerPolicy, expectedReferrer) {
+ var url = redirectUrl;
+ var urlParameters = "?location=" + encodeURIComponent(redirectLocation);
+
+ if (redirectReferrerPolicy)
+ urlParameters += "&redirect_referrerpolicy=" + redirectReferrerPolicy;
+
+ var requestInit = {"redirect": "follow", "referrerPolicy": referrerPolicy};
+
+ promise_test(function(test) {
+ return fetch(url + urlParameters, requestInit).then(function(response) {
+ assert_equals(response.status, 200, "Inspect header response's status is 200");
+ assert_equals(response.headers.get("x-request-referer"), expectedReferrer ? expectedReferrer : null, "Check referrer header");
+ });
+ }, desc);
+}
+
+var referrerOrigin = get_host_info().HTTP_ORIGIN + "/";
+var referrerUrl = location.href;
+
+var redirectUrl = RESOURCES_DIR + "redirect.py";
+var locationUrl = get_host_info().HTTP_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?headers=referer";
+var crossLocationUrl = get_host_info().HTTP_REMOTE_ORIGIN + dirname(location.pathname) + RESOURCES_DIR + "inspect-headers.py?cors&headers=referer";
+
+testReferrerAfterRedirection("Same origin redirection, empty init, unsafe-url redirect header ", redirectUrl, locationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, locationUrl, "", "no-referrer-when-downgrade", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, same-origin redirect header ", redirectUrl, locationUrl, "", "same-origin", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, origin redirect header ", redirectUrl, locationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "origin-when-cross-origin", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty init, no-referrer redirect header ", redirectUrl, locationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, locationUrl, "", "strict-origin-when-cross-origin", referrerUrl);
+
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, unsafe-url init ", redirectUrl, locationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, locationUrl, "no-referrer-when-downgrade", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, same-origin init ", redirectUrl, locationUrl, "same-origin", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin init ", redirectUrl, locationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, locationUrl, "origin-when-cross-origin", "", referrerUrl);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, no-referrer init ", redirectUrl, locationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin init ", redirectUrl, locationUrl, "strict-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Same origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, locationUrl, "strict-origin-when-cross-origin", "", referrerUrl);
+
+testReferrerAfterRedirection("Cross origin redirection, empty init, unsafe-url redirect header ", redirectUrl, crossLocationUrl, "", "unsafe-url", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer-when-downgrade redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer-when-downgrade", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty init, same-origin redirect header ", redirectUrl, crossLocationUrl, "", "same-origin", null);
+testReferrerAfterRedirection("Cross origin redirection, empty init, origin redirect header ", redirectUrl, crossLocationUrl, "", "origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "origin-when-cross-origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, no-referrer redirect header ", redirectUrl, crossLocationUrl, "", "no-referrer", null);
+testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty init, strict-origin-when-cross-origin redirect header ", redirectUrl, crossLocationUrl, "", "strict-origin-when-cross-origin", referrerOrigin);
+
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, unsafe-url init ", redirectUrl, crossLocationUrl, "unsafe-url", "", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer-when-downgrade init ", redirectUrl, crossLocationUrl, "no-referrer-when-downgrade", "", referrerUrl);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, same-origin init ", redirectUrl, crossLocationUrl, "same-origin", "", null);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin init ", redirectUrl, crossLocationUrl, "origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "origin-when-cross-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, no-referrer init ", redirectUrl, crossLocationUrl, "no-referrer", "", null);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin init ", redirectUrl, crossLocationUrl, "strict-origin", "", referrerOrigin);
+testReferrerAfterRedirection("Cross origin redirection, empty redirect header, strict-origin-when-cross-origin init ", redirectUrl, crossLocationUrl, "strict-origin-when-cross-origin", "", referrerOrigin);
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-schemes.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-schemes.any.js
new file mode 100644
index 0000000000..31ec124fd6
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-schemes.any.js
@@ -0,0 +1,19 @@
+// META: title=Fetch: handling different schemes in redirects
+// META: global=window,worker
+// META: script=/common/get-host-info.sub.js
+
+// All non-HTTP(S) schemes cannot survive redirects
+var url = "../resources/redirect.py?location=";
+var tests = [
+ url + "mailto:a@a.com",
+ url + "data:,HI",
+ url + "facetime:a@a.org",
+ url + "about:blank",
+ url + "about:unicorn",
+ url + "blob:djfksfjs"
+];
+tests.forEach(function(url) {
+ promise_test(function(test) {
+ return promise_rejects_js(test, TypeError, fetch(url))
+ })
+})
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-to-dataurl.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-to-dataurl.any.js
new file mode 100644
index 0000000000..9d0f147349
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-to-dataurl.any.js
@@ -0,0 +1,28 @@
+// META: global=window,worker
+// META: script=/common/get-host-info.sub.js
+
+var dataURL = "data:text/plain;base64,cmVzcG9uc2UncyBib2R5";
+var body = "response's body";
+var contentType = "text/plain";
+
+function redirectDataURL(desc, redirectUrl, mode) {
+ var url = redirectUrl + "?cors&location=" + encodeURIComponent(dataURL);
+
+ var requestInit = {"mode": mode};
+
+ promise_test(function(test) {
+ return promise_rejects_js(test, TypeError, fetch(url, requestInit));
+ }, desc);
+}
+
+var redirUrl = get_host_info().HTTP_ORIGIN + "/fetch/api/resources/redirect.py";
+var corsRedirUrl = get_host_info().HTTP_REMOTE_ORIGIN + "/fetch/api/resources/redirect.py";
+
+redirectDataURL("Testing data URL loading after same-origin redirection (cors mode)", redirUrl, "cors");
+redirectDataURL("Testing data URL loading after same-origin redirection (no-cors mode)", redirUrl, "no-cors");
+redirectDataURL("Testing data URL loading after same-origin redirection (same-origin mode)", redirUrl, "same-origin");
+
+redirectDataURL("Testing data URL loading after cross-origin redirection (cors mode)", corsRedirUrl, "cors");
+redirectDataURL("Testing data URL loading after cross-origin redirection (no-cors mode)", corsRedirUrl, "no-cors");
+
+done();
diff --git a/testing/web-platform/tests/fetch/api/redirect/redirect-upload.h2.any.js b/testing/web-platform/tests/fetch/api/redirect/redirect-upload.h2.any.js
new file mode 100644
index 0000000000..521bd3adc2
--- /dev/null
+++ b/testing/web-platform/tests/fetch/api/redirect/redirect-upload.h2.any.js
@@ -0,0 +1,33 @@
+// META: global=window,worker
+// META: script=../resources/utils.js
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+
+const redirectUrl = RESOURCES_DIR + "redirect.h2.py";
+const redirectLocation = "top.txt";
+
+async function fetchStreamRedirect(statusCode) {
+ const url = RESOURCES_DIR + "redirect.h2.py" +
+ `?redirect_status=${statusCode}&location=${redirectLocation}`;
+ const requestInit = {method: "POST"};
+ requestInit["body"] = new ReadableStream({start: controller => {
+ const encoder = new TextEncoder();
+ controller.enqueue(encoder.encode("Test"));
+ controller.close();
+ }});
+ requestInit.duplex = "half";
+ return fetch(url, requestInit);
+}
+
+promise_test(async () => {
+ const resp = await fetchStreamRedirect(303);
+ assert_equals(resp.status, 200);
+ assert_true(new URL(resp.url).pathname.endsWith(redirectLocation),
+ "Response's url should be the redirected one");
+}, "Fetch upload streaming should be accepted on 303");
+
+for (const statusCode of [301, 302, 307, 308]) {
+ promise_test(t => {
+ return promise_rejects_js(t, TypeError, fetchStreamRedirect(statusCode));
+ }, `Fetch upload streaming should fail on ${statusCode}`);
+}