Source code
Revision control
Copy as Markdown
Other Tools
var path = "/tests/dom/base/test/";
function isOpaqueResponse(response) {
return (
response.type == "opaque" &&
response.status === 0 &&
response.statusText === ""
);
}
function testModeSameOrigin() {
// Fetch spec Section 4, step 4, "request's mode is same-origin".
return fetch(req).then(
function (res) {
ok(
false,
"Attempting to fetch a resource from a different origin with mode same-origin should fail."
);
},
function (e) {
ok(
e instanceof TypeError,
"Attempting to fetch a resource from a different origin with mode same-origin should fail."
);
}
);
}
function testNoCorsCtor() {
// Request constructor Step 19.1
var simpleMethods = ["GET", "HEAD", "POST"];
for (var i = 0; i < simpleMethods.length; ++i) {
method: simpleMethods[i],
mode: "no-cors",
});
ok(
true,
"no-cors Request with simple method " + simpleMethods[i] + " is allowed."
);
}
var otherMethods = ["DELETE", "OPTIONS", "PUT"];
for (var i = 0; i < otherMethods.length; ++i) {
try {
method: otherMethods[i],
mode: "no-cors",
});
ok(
false,
"no-cors Request with non-simple method " +
otherMethods[i] +
" is not allowed."
);
} catch (e) {
ok(
true,
"no-cors Request with non-simple method " +
otherMethods[i] +
" is not allowed."
);
}
}
// Request constructor Step 19.2, check guarded headers.
var r = new Request(".", { mode: "no-cors" });
r.headers.append("Content-Type", "multipart/form-data");
is(
r.headers.get("content-type"),
"multipart/form-data",
"Appending simple header should succeed"
);
r.headers.append("custom", "value");
ok(!r.headers.has("custom"), "Appending custom header should fail");
r.headers.append("DNT", "value");
ok(!r.headers.has("DNT"), "Appending forbidden header should fail");
}
var corsServerPath =
"/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?";
function testModeNoCors() {
// Fetch spec, section 4, step 4, response tainting should be set opaque, so
// that fetching leads to an opaque filtered response in step 8.
mode: "no-cors",
});
return fetch(r).then(
function (res) {
ok(
isOpaqueResponse(res),
"no-cors Request fetch should result in opaque response"
);
},
function (e) {
ok(false, "no-cors Request fetch should not error");
}
);
}
function testSameOriginCredentials() {
var cookieStr = "type=chocolatechip";
var tests = [
{
// Initialize by setting a cookie.
pass: 1,
setCookie: cookieStr + "; Partitioned; Secure; SameSite=None",
withCred: "same-origin",
},
{
// Default mode is "same-origin".
pass: 1,
cookie: cookieStr,
},
{
pass: 1,
noCookie: 1,
withCred: "omit",
},
{
pass: 1,
cookie: cookieStr,
withCred: "same-origin",
},
{
pass: 1,
cookie: cookieStr,
withCred: "include",
},
];
var finalPromiseResolve, finalPromiseReject;
var finalPromise = new Promise(function (res, rej) {
finalPromiseResolve = res;
finalPromiseReject = rej;
});
function makeRequest(test) {
req = {
// Add a default query param just to make formatting the actual params
// easier.
url: corsServerPath + "a=b",
method: test.method,
headers: test.headers,
withCred: test.withCred,
};
if (test.setCookie) {
req.url += "&setCookie=" + escape(test.setCookie);
}
if (test.cookie) {
req.url += "&cookie=" + escape(test.cookie);
}
if (test.noCookie) {
req.url += "&noCookie";
}
return new Request(req.url, {
method: req.method,
headers: req.headers,
credentials: req.withCred,
});
}
function testResponse(res, test) {
ok(test.pass, "Expected test to pass " + JSON.stringify(test));
is(res.status, 200, "wrong status in test for " + JSON.stringify(test));
is(res.statusText, "OK", "wrong status text for " + JSON.stringify(test));
return res.text().then(function (v) {
is(
v,
"<res>hello pass</res>\n",
"wrong text in test for " + JSON.stringify(test)
);
});
}
function runATest(tests, i) {
var test = tests[i];
var request = makeRequest(test);
console.log(request.url);
fetch(request).then(
function (res) {
testResponse(res, test).then(function () {
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
});
},
function (e) {
ok(!test.pass, "Expected test to fail " + JSON.stringify(test));
ok(e instanceof TypeError, "Test should fail " + JSON.stringify(test));
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
}
);
}
runATest(tests, 0);
return finalPromise;
}
function testModeCors() {
var tests = [
// Plain request
{ pass: 1, method: "GET", noAllowPreflight: 1 },
// undefined username
{ pass: 1, method: "GET", noAllowPreflight: 1, username: undefined },
// undefined username and password
{
pass: 1,
method: "GET",
noAllowPreflight: 1,
username: undefined,
password: undefined,
},
// nonempty username
{ pass: 0, method: "GET", noAllowPreflight: 1, username: "user" },
// nonempty password
{ pass: 0, method: "GET", noAllowPreflight: 1, password: "password" },
// Default allowed headers
{
pass: 1,
method: "GET",
headers: {
"Content-Type": "text/plain",
Accept: "foo/bar",
"Accept-Language": "sv-SE",
},
noAllowPreflight: 1,
},
{
pass: 0,
method: "GET",
headers: {
"Content-Type": "foo/bar",
Accept: "foo/bar",
"Accept-Language": "sv-SE",
},
noAllowPreflight: 1,
},
{
pass: 0,
method: "GET",
headers: { "Content-Type": "foo/bar, text/plain" },
noAllowPreflight: 1,
},
{
pass: 0,
method: "GET",
headers: { "Content-Type": "foo/bar, text/plain, garbage" },
noAllowPreflight: 1,
},
// Custom headers
{
pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "X-My-Header",
},
{
pass: 1,
method: "GET",
headers: {
"x-my-header": "myValue",
"long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header":
"secondValue",
},
allowHeaders:
"x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
},
{
pass: 1,
method: "GET",
headers: { "x-my%-header": "myValue" },
allowHeaders: "x-my%-header",
},
{ pass: 0, method: "GET", headers: { "x-my-header": "myValue" } },
{ pass: 0, method: "GET", headers: { "x-my-header": "" } },
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "y-my-header",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header y-my-header",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, y-my-header z",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, y-my-he(ader",
},
{
pass: 0,
method: "GET",
headers: { myheader: "" },
allowMethods: "myheader",
},
{
pass: 1,
method: "GET",
headers: { "User-Agent": "myValue" },
allowHeaders: "User-Agent",
},
{ pass: 0, method: "GET", headers: { "User-Agent": "myValue" } },
// Multiple custom headers
{
pass: 1,
method: "GET",
headers: {
"x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue",
},
allowHeaders: "x-my-header, second-header, third-header",
},
{
pass: 1,
method: "GET",
headers: {
"x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue",
},
allowHeaders: "x-my-header,second-header,third-header",
},
{
pass: 1,
method: "GET",
headers: {
"x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue",
},
allowHeaders: "x-my-header ,second-header ,third-header",
},
{
pass: 1,
method: "GET",
headers: {
"x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue",
},
allowHeaders: "x-my-header , second-header , third-header",
},
{
pass: 1,
method: "GET",
headers: { "x-my-header": "myValue", "second-header": "secondValue" },
allowHeaders: ", x-my-header, , ,, second-header, , ",
},
{
pass: 1,
method: "GET",
headers: { "x-my-header": "myValue", "second-header": "secondValue" },
allowHeaders: "x-my-header, second-header, unused-header",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "myValue", "y-my-header": "secondValue" },
allowHeaders: "x-my-header",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "", "y-my-header": "" },
allowHeaders: "x-my-header",
},
// HEAD requests
{ pass: 1, method: "HEAD", noAllowPreflight: 1 },
// HEAD with safe headers
{
pass: 1,
method: "HEAD",
headers: {
"Content-Type": "text/plain",
Accept: "foo/bar",
"Accept-Language": "sv-SE",
},
noAllowPreflight: 1,
},
{
pass: 0,
method: "HEAD",
headers: {
"Content-Type": "foo/bar",
Accept: "foo/bar",
"Accept-Language": "sv-SE",
},
noAllowPreflight: 1,
},
{
pass: 0,
method: "HEAD",
headers: { "Content-Type": "foo/bar, text/plain" },
noAllowPreflight: 1,
},
{
pass: 0,
method: "HEAD",
headers: { "Content-Type": "foo/bar, text/plain, garbage" },
noAllowPreflight: 1,
},
// HEAD with custom headers
{
pass: 1,
method: "HEAD",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ pass: 0, method: "HEAD", headers: { "x-my-header": "myValue" } },
{
pass: 0,
method: "HEAD",
headers: { "x-my-header": "myValue" },
allowHeaders: "",
},
{
pass: 0,
method: "HEAD",
headers: { "x-my-header": "myValue" },
allowHeaders: "y-my-header",
},
{
pass: 0,
method: "HEAD",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header y-my-header",
},
// POST tests
{ pass: 1, method: "POST", body: "hi there", noAllowPreflight: 1 },
{ pass: 1, method: "POST" },
{ pass: 1, method: "POST", noAllowPreflight: 1 },
// POST with standard headers
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain" },
noAllowPreflight: 1,
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "multipart/form-data" },
noAllowPreflight: 1,
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
noAllowPreflight: 1,
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar" },
},
{ pass: 0, method: "POST", headers: { "Content-Type": "foo/bar" } },
{
pass: 1,
method: "POST",
body: "hi there",
headers: {
"Content-Type": "text/plain",
Accept: "foo/bar",
"Accept-Language": "sv-SE",
},
noAllowPreflight: 1,
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar, text/plain" },
noAllowPreflight: 1,
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar, text/plain, garbage" },
noAllowPreflight: 1,
},
// POST with custom headers
{
pass: 1,
method: "POST",
body: "hi there",
headers: {
Accept: "foo/bar",
"Accept-Language": "sv-SE",
"x-my-header": "myValue",
},
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "POST",
headers: { "Content-Type": "text/plain", "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar", "x-my-header": "myValue" },
allowHeaders: "x-my-header, content-type",
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar" },
noAllowPreflight: 1,
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar", "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "POST",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, $_%",
},
// Other methods
{ pass: 1, method: "DELETE", allowMethods: "DELETE" },
{ pass: 0, method: "DELETE", allowHeaders: "DELETE" },
{ pass: 0, method: "DELETE" },
{ pass: 0, method: "DELETE", allowMethods: "" },
{ pass: 1, method: "DELETE", allowMethods: "POST, PUT, DELETE" },
{ pass: 1, method: "DELETE", allowMethods: "POST, DELETE, PUT" },
{ pass: 1, method: "DELETE", allowMethods: "DELETE, POST, PUT" },
{ pass: 1, method: "DELETE", allowMethods: "POST ,PUT ,DELETE" },
{ pass: 1, method: "DELETE", allowMethods: "POST,PUT,DELETE" },
{ pass: 1, method: "DELETE", allowMethods: "POST , PUT , DELETE" },
{
pass: 1,
method: "DELETE",
allowMethods: " ,, PUT ,, , , DELETE , ,",
},
{ pass: 0, method: "DELETE", allowMethods: "PUT" },
{ pass: 0, method: "DELETE", allowMethods: "DELETEZ" },
{ pass: 0, method: "DELETE", allowMethods: "DELETE PUT" },
{ pass: 0, method: "DELETE", allowMethods: "DELETE, PUT Z" },
{ pass: 0, method: "DELETE", allowMethods: "DELETE, PU(T" },
{ pass: 0, method: "DELETE", allowMethods: "PUT DELETE" },
{ pass: 0, method: "DELETE", allowMethods: "PUT Z, DELETE" },
{ pass: 0, method: "DELETE", allowMethods: "PU(T, DELETE" },
{ pass: 0, method: "PUT", allowMethods: "put" },
// Status messages
{
pass: 1,
method: "GET",
noAllowPreflight: 1,
status: 404,
statusMessage: "nothin' here",
},
{
pass: 1,
method: "GET",
noAllowPreflight: 1,
status: 401,
statusMessage: "no can do",
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "foo/bar" },
allowHeaders: "content-type",
status: 500,
statusMessage: "server boo",
},
{
pass: 1,
method: "GET",
noAllowPreflight: 1,
status: 200,
statusMessage: "Yes!!",
},
{
pass: 0,
method: "GET",
headers: { "x-my-header": "header value" },
allowHeaders: "x-my-header",
preflightStatus: 400,
},
{
pass: 1,
method: "GET",
headers: { "x-my-header": "header value" },
allowHeaders: "x-my-header",
preflightStatus: 200,
},
{
pass: 1,
method: "GET",
headers: { "x-my-header": "header value" },
allowHeaders: "x-my-header",
preflightStatus: 204,
},
// exposed headers
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "x-my-header",
expectedResponseHeaders: ["x-my-header"],
},
{
pass: 0,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "x-my-header",
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "x-my-header y",
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "y x-my-header",
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "x-my-header, y-my-header z",
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header" },
exposeHeaders: "x-my-header, y-my-hea(er",
expectedResponseHeaders: [],
},
{
pass: 1,
method: "GET",
responseHeaders: { "x-my-header": "x header", "y-my-header": "y header" },
exposeHeaders: " , ,,y-my-header,z-my-header, ",
expectedResponseHeaders: ["y-my-header"],
},
{
pass: 1,
method: "GET",
responseHeaders: {
"Cache-Control": "cacheControl header",
"Content-Language": "contentLanguage header",
Expires: "expires header",
"Last-Modified": "lastModified header",
Pragma: "pragma header",
Unexpected: "unexpected header",
},
expectedResponseHeaders: [
"Cache-Control",
"Content-Language",
"Content-Type",
"Expires",
"Last-Modified",
"Pragma",
],
},
// Check that sending a body in the OPTIONS response works
{
pass: 1,
method: "DELETE",
allowMethods: "DELETE",
preflightBody: "I'm a preflight response body",
},
];
var origin = self.location.origin;
var baseURL =
baseURL += corsServerPath;
var fetches = [];
for (test of tests) {
var req = {
url: baseURL + "allowOrigin=" + escape(test.origin || origin),
method: test.method,
headers: test.headers,
uploadProgress: test.uploadProgress,
body: test.body,
responseHeaders: test.responseHeaders,
};
if (test.pass) {
req.url += "&origin=" + escape(origin) + "&requestMethod=" + test.method;
}
if ("username" in test) {
var u = new URL(req.url);
u.username = test.username || "";
req.url = u.href;
}
if ("password" in test) {
var u = new URL(req.url);
u.password = test.password || "";
req.url = u.href;
}
if (test.noAllowPreflight) {
req.url += "&noAllowPreflight";
}
if (test.pass && "headers" in test) {
function isUnsafeHeader(name) {
lName = name.toLowerCase();
return (
lName != "accept" &&
lName != "accept-language" &&
(lName != "content-type" ||
![
"text/plain",
"multipart/form-data",
"application/x-www-form-urlencoded",
].includes(test.headers[name].toLowerCase()))
);
}
req.url += "&headers=" + escape(JSON.stringify(test.headers));
reqHeaders = escape(
Object.keys(test.headers)
.filter(isUnsafeHeader)
.map(s => s.toLowerCase())
.sort()
.join(",")
);
req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
}
if ("allowHeaders" in test) {
req.url += "&allowHeaders=" + escape(test.allowHeaders);
}
if ("allowMethods" in test) {
req.url += "&allowMethods=" + escape(test.allowMethods);
}
if (test.body) {
req.url += "&body=" + escape(test.body);
}
if (test.status) {
req.url += "&status=" + test.status;
req.url += "&statusMessage=" + escape(test.statusMessage);
}
if (test.preflightStatus) {
req.url += "&preflightStatus=" + test.preflightStatus;
}
if (test.responseHeaders) {
req.url +=
"&responseHeaders=" + escape(JSON.stringify(test.responseHeaders));
}
if (test.exposeHeaders) {
req.url += "&exposeHeaders=" + escape(test.exposeHeaders);
}
if (test.preflightBody) {
req.url += "&preflightBody=" + escape(test.preflightBody);
}
fetches.push(
(function (test) {
return new Promise(function (resolve) {
resolve(
new Request(req.url, {
method: req.method,
mode: "cors",
headers: req.headers,
body: req.body,
})
);
})
.then(function (request) {
return fetch(request);
})
.then(function (res) {
ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
if (test.status) {
is(
res.status,
test.status,
"wrong status in test for " + JSON.stringify(test)
);
is(
res.statusText,
test.statusMessage,
"wrong status text for " + JSON.stringify(test)
);
} else {
is(
res.status,
200,
"wrong status in test for " + JSON.stringify(test)
);
is(
res.statusText,
"OK",
"wrong status text for " + JSON.stringify(test)
);
}
if (test.responseHeaders) {
for (header in test.responseHeaders) {
if (!test.expectedResponseHeaders.includes(header)) {
is(
res.headers.has(header),
false,
"|Headers.has()|wrong response header (" +
header +
") in test for " +
JSON.stringify(test)
);
} else {
is(
res.headers.get(header),
test.responseHeaders[header],
"|Headers.get()|wrong response header (" +
header +
") in test for " +
JSON.stringify(test)
);
}
}
}
return res.text();
})
.then(function (v) {
if (test.method !== "HEAD") {
is(
v,
"<res>hello pass</res>\n",
"wrong responseText in test for " + JSON.stringify(test)
);
} else {
is(
v,
"",
"wrong responseText in HEAD test for " + JSON.stringify(test)
);
}
})
.catch(function (e) {
ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
ok(
e instanceof TypeError,
"Exception should be TypeError for " + JSON.stringify(test)
);
});
})(test)
);
}
return Promise.all(fetches);
}
function testCrossOriginCredentials() {
var origin = self.location.origin;
var tests = [
{ pass: 1, method: "GET", withCred: "include", allowCred: 1 },
{ pass: 0, method: "GET", withCred: "include", allowCred: 0 },
{ pass: 0, method: "GET", withCred: "include", allowCred: 1, origin: "*" },
{ pass: 1, method: "GET", withCred: "omit", allowCred: 1, origin: "*" },
{
pass: 1,
method: "GET",
setCookie: "a=1; Partitioned; Secure; SameSite=None",
withCred: "include",
allowCred: 1,
},
{
pass: 1,
method: "GET",
cookie: "a=1",
withCred: "include",
allowCred: 1,
},
{ pass: 1, method: "GET", noCookie: 1, withCred: "omit", allowCred: 1 },
{ pass: 0, method: "GET", noCookie: 1, withCred: "include", allowCred: 1 },
{
pass: 1,
method: "GET",
setCookie: "a=2; Partitioned; Secure; SameSite=None",
withCred: "omit",
allowCred: 1,
},
{
pass: 1,
method: "GET",
cookie: "a=1",
withCred: "include",
allowCred: 1,
},
{
pass: 1,
method: "GET",
setCookie: "a=2; Partitioned; Secure; SameSite=None",
withCred: "include",
allowCred: 1,
},
{
pass: 1,
method: "GET",
cookie: "a=2",
withCred: "include",
allowCred: 1,
},
{
// When credentials mode is same-origin, but mode is cors, no
// cookie should be sent cross origin.
pass: 0,
method: "GET",
cookie: "a=2",
withCred: "same-origin",
allowCred: 1,
},
{
// When credentials mode is same-origin, but mode is cors, no
// cookie should be sent cross origin. This test checks the same
// thing as above, but uses the noCookie check on the server
// instead, and expects a valid response.
pass: 1,
method: "GET",
noCookie: 1,
withCred: "same-origin",
},
{
// Initialize by setting a cookies for same- and cross- origins.
pass: 1,
hops: [
{
server: origin,
setCookie: escape("a=1; Partitioned; Secure; SameSite=None"),
},
{
allowOrigin: origin,
allowCred: 1,
setCookie: escape("a=2; Partitioned; Secure; SameSite=None"),
},
],
withCred: "include",
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin, cookie: escape("a=1") },
{ server: origin, cookie: escape("a=1") },
],
withCred: "same-origin",
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin, cookie: escape("a=1") },
{ server: origin, cookie: escape("a=1") },
{
allowOrigin: origin,
allowCred: 1,
cookie: escape("a=2"),
},
],
withCred: "include",
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin, cookie: escape("a=1") },
{ server: origin, cookie: escape("a=1") },
],
withCred: "same-origin",
},
{
pass: 0,
method: "GET",
hops: [
{ server: origin, cookie: escape("a=1") },
{ server: origin, cookie: escape("a=1") },
{
allowOrigin: "*",
allowCred: 1,
cookie: escape("a=2"),
},
],
withCred: "include",
},
// fails because allow-credentials CORS header is not set by server
{
pass: 0,
method: "GET",
hops: [
{ server: origin, cookie: escape("a=1") },
{ server: origin, cookie: escape("a=1") },
{
allowOrigin: origin,
cookie: escape("a=2"),
},
],
withCred: "include",
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin, noCookie: 1 },
{ server: origin, noCookie: 1 },
],
withCred: "omit",
},
];
// Xorigin with https has .org origin, default .com
var baseURL =
baseURL += corsServerPath;
var finalPromiseResolve, finalPromiseReject;
var finalPromise = new Promise(function (res, rej) {
finalPromiseResolve = res;
finalPromiseReject = rej;
});
function makeRequest(test) {
var url;
if (test.hops) {
url =
test.hops[0].server +
corsServerPath +
"hop=1&hops=" +
escape(JSON.stringify(test.hops));
} else {
url = baseURL + "allowOrigin=" + escape(test.origin || origin);
}
req = {
url,
method: test.method,
headers: test.headers,
withCred: test.withCred,
};
if (test.allowCred) {
req.url += "&allowCred";
}
if (test.setCookie) {
req.url += "&setCookie=" + escape(test.setCookie);
}
if (test.cookie) {
req.url += "&cookie=" + escape(test.cookie);
}
if (test.noCookie) {
req.url += "&noCookie";
}
if ("allowHeaders" in test) {
req.url += "&allowHeaders=" + escape(test.allowHeaders);
}
if ("allowMethods" in test) {
req.url += "&allowMethods=" + escape(test.allowMethods);
}
return new Request(req.url, {
method: req.method,
headers: req.headers,
credentials: req.withCred,
});
}
function testResponse(res, test) {
ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
is(res.status, 200, "wrong status in test for " + JSON.stringify(test));
is(res.statusText, "OK", "wrong status text for " + JSON.stringify(test));
return res.text().then(function (v) {
is(
v,
"<res>hello pass</res>\n",
"wrong text in test for " + JSON.stringify(test)
);
});
}
function runATest(tests, i) {
var test = tests[i];
var request = makeRequest(test);
fetch(request).then(
function (res) {
testResponse(res, test).then(function () {
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
});
},
function (e) {
ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
ok(
e instanceof TypeError,
"Exception should be TypeError for " + JSON.stringify(test)
);
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
}
);
}
runATest(tests, 0);
return finalPromise;
}
function testModeNoCorsCredentials() {
var cookieStr = "type=chocolatechip";
var tests = [
{
// Initialize by setting a cookie.
pass: 1,
setCookie: cookieStr + "; Partitioned; Secure; SameSite=None",
withCred: "include",
},
{
pass: 1,
noCookie: 1,
withCred: "omit",
},
{
pass: 1,
noCookie: 1,
withCred: "same-origin",
},
{
pass: 1,
cookie: cookieStr,
withCred: "include",
},
{
pass: 1,
cookie: cookieStr,
withCred: "omit",
status: 500,
},
{
pass: 1,
cookie: cookieStr,
withCred: "same-origin",
status: 500,
},
{
pass: 1,
noCookie: 1,
withCred: "include",
status: 500,
},
];
var finalPromiseResolve, finalPromiseReject;
var finalPromise = new Promise(function (res, rej) {
finalPromiseResolve = res;
finalPromiseReject = rej;
});
function makeRequest(test) {
req = {
withCred: test.withCred,
};
if (test.setCookie) {
req.url += "&setCookie=" + escape(test.setCookie);
}
if (test.cookie) {
req.url += "&cookie=" + escape(test.cookie);
}
if (test.noCookie) {
req.url += "&noCookie";
}
return new Request(req.url, {
method: "GET",
mode: "no-cors",
credentials: req.withCred,
});
}
function testResponse(res, test) {
is(res.type, "opaque", "wrong response type for " + JSON.stringify(test));
// Get unfiltered response
var chromeResponse = SpecialPowers.wrap(res);
var unfiltered = chromeResponse.cloneUnfiltered();
var status = test.status ? test.status : 200;
is(
unfiltered.status,
status,
"wrong status in test for " + JSON.stringify(test)
);
return unfiltered.text().then(function (v) {
if (test.status === 200) {
const expected =
SpecialPowers.getIntPref(
"browser.opaqueResponseBlocking.filterFetchResponse"
) > 0
? ""
: "<res>hello pass</res>\n";
is(v, expected, "wrong text in test for " + JSON.stringify(test));
}
});
}
function runATest(tests, i) {
if (typeof SpecialPowers !== "object") {
finalPromiseResolve();
return;
}
var test = tests[i];
var request = makeRequest(test);
fetch(request).then(
function (res) {
ok(test.pass, "Expected test to pass " + JSON.stringify(test));
testResponse(res, test).then(function () {
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
});
},
function (e) {
ok(!test.pass, "Expected test to fail " + JSON.stringify(test));
ok(e instanceof TypeError, "Test should fail " + JSON.stringify(test));
if (i < tests.length - 1) {
runATest(tests, i + 1);
} else {
finalPromiseResolve();
}
}
);
}
runATest(tests, 0);
return finalPromise;
}
function testCORSRedirects() {
var origin = self.location.origin;
var host = self.location.hostname;
var protocol = self.location.protocol;
var originSubSub1 = protocol + "//sub1.test1." + host;
var originSubSub2 = protocol + "//sub2.test2." + host;
var originSub = protocol + "//test3." + host;
var foreignHost = host === "example.com" ? "example.org" : "example.com";
var foreignSub1 = protocol + "//test1." + foreignHost;
var foreignSub2 = protocol + "//test2." + foreignHost;
var tests = [
{
pass: 1,
method: "GET",
},
{
pass: 0,
method: "GET",
hops: [
{ server: origin, allowOrigin: origin },
],
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin, allowOrigin: "*" },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: origin },
],
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin },
{ server: origin },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: origin },
{ server: origin },
{ server: origin },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: originSub, allowOrigin: origin },
{ server: originSubSub1, allowOrigin: origin },
{ server: originSubSub2, allowOrigin: origin },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: originSub, allowOrigin: origin },
{ server: originSubSub1, allowOrigin: "*" },
{ server: originSubSub2, allowOrigin: "*" },
],
},
{
pass: 1,
method: "GET",
hops: [
{ server: originSub, allowOrigin: "*" },
{ server: originSubSub1, allowOrigin: "*" },
{ server: originSubSub2, allowOrigin: "*" },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: originSub, allowOrigin: origin },
{ server: originSubSub1, allowOrigin: "x" },
{ server: originSubSub2, allowOrigin: origin },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: originSub, allowOrigin: origin },
{ server: originSubSub1, allowOrigin: "*" },
{ server: originSubSub2, allowOrigin: origin },
],
},
{
pass: 0,
method: "GET",
hops: [
{ server: originSub, allowOrigin: origin },
{ server: originSubSub1, allowOrigin: "*" },
{ server: originSubSub2 },
],
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain" },
hops: [
{ server: origin },
],
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{ server: origin },
{
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{ server: origin },
{
allowOrigin: origin,
allowHeaders: "my-header",
noAllowPreflight: 1,
},
],
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{ server: origin },
{
server: foreignSub1,
allowOrigin: origin,
allowHeaders: "my-header",
},
{
server: foreignSub2,
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
{
pass: 1,
method: "DELETE",
hops: [
{ server: origin },
{
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{
pass: 0,
method: "DELETE",
hops: [
{ server: origin },
{
allowOrigin: origin,
allowMethods: "DELETE",
noAllowPreflight: 1,
},
],
},
{
pass: 0,
method: "DELETE",
hops: [
{ server: origin },
{
server: foreignSub1,
allowOrigin: origin,
allowMethods: "DELETE",
},
{
server: foreignSub2,
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{ server: originSubSub1, allowOrigin: origin },
],
},
{
pass: 0,
method: "DELETE",
hops: [
{
allowOrigin: origin,
allowMethods: "DELETE",
},
{
server: originSubSub1,
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{
server: originSubSub1,
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
{
pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain" },
hops: [
{ server: origin },
],
},
{
pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain", "my-header": "myValue" },
hops: [
{
allowOrigin: origin,
allowHeaders: "my-header",
},
{
server: origin,
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
];
var fetches = [];
for (test of tests) {
req = {
url:
test.hops[0].server +
corsServerPath +
"hop=1&hops=" +
escape(JSON.stringify(test.hops)),
method: test.method,
headers: test.headers,
body: test.body,
};
if (test.headers) {
req.url += "&headers=" + escape(JSON.stringify(test.headers));
}
if (test.pass) {
if (test.body) {
req.url += "&body=" + escape(test.body);
}
}
var request = new Request(req.url, {
method: req.method,
headers: req.headers,
body: req.body,
});
fetches.push(
(function (request, test) {
return fetch(request).then(
function (res) {
ok(test.pass, "Expected test to pass for " + JSON.stringify(test));
is(
res.status,
200,
"wrong status in test for " + JSON.stringify(test)
);
is(
res.statusText,
"OK",
"wrong status text for " + JSON.stringify(test)
);
is(
res.type,
"cors",
"wrong response type for " + JSON.stringify(test)
);
var reqHost = new URL(req.url).host;
// If there is a service worker present, the redirections will be
// transparent, assuming that the original request is to the current
// site and would be intercepted.
if (isSWPresent) {
if (reqHost === location.host) {
is(
new URL(res.url).host,
reqHost,
"Response URL should be original URL with a SW present"
);
}
} else {
is(
new URL(res.url).host,
new URL(test.hops[test.hops.length - 1].server).host,
"Response URL should be redirected URL"
);
}
return res.text().then(function (v) {
is(
v,
"<res>hello pass</res>\n",
"wrong responseText in test for " + JSON.stringify(test)
);
});
},
function (e) {
ok(!test.pass, "Expected test failure for " + JSON.stringify(test));
ok(
e instanceof TypeError,
"Exception should be TypeError for " + JSON.stringify(test)
);
}
);
})(request, test)
);
}
return Promise.all(fetches);
}
function testNoCORSRedirects() {
var origin = self.location.origin;
var tests = [
{
pass: 1,
method: "GET",
},
{
pass: 1,
method: "GET",
// Must use a simple header due to no-cors header restrictions.
headers: { "accept-language": "en-us" },
},
{
pass: 1,
method: "GET",
hops: [
{ server: origin },
{ server: origin },
],
},
{
pass: 1,
method: "POST",
body: "upload body here",
},
{
pass: 0,
method: "DELETE",
},
];
var fetches = [];
for (test of tests) {
req = {
url:
test.hops[0].server +
corsServerPath +
"hop=1&hops=" +
escape(JSON.stringify(test.hops)),
method: test.method,
headers: test.headers,
body: test.body,
};
if (test.headers) {
req.url += "&headers=" + escape(JSON.stringify(test.headers));
}
if (test.pass) {
if (test.body) {
req.url += "&body=" + escape(test.body);
}
}
fetches.push(
(function (req, test) {
return new Promise(function (resolve, reject) {
resolve(
new Request(req.url, {
mode: "no-cors",
method: req.method,
headers: req.headers,
body: req.body,
})
);
})
.then(function (request) {
return fetch(request);
})
.then(
function (res) {
ok(
test.pass,
"Expected test to pass for " + JSON.stringify(test)
);
// All requests are cross-origin no-cors, we should always have
// an opaque response here. All values on the opaque response
// should be hidden.
is(
res.type,
"opaque",
"wrong response type for " + JSON.stringify(test)
);
is(
res.status,
0,
"wrong status in test for " + JSON.stringify(test)
);
is(
res.statusText,
"",
"wrong status text for " + JSON.stringify(test)
);
is(res.url, "", "wrong response url for " + JSON.stringify(test));
return res.text().then(function (v) {
is(
v,
"",
"wrong responseText in test for " + JSON.stringify(test)
);
});
},
function (e) {
ok(
!test.pass,
"Expected test failure for " + JSON.stringify(test)
);
ok(
e instanceof TypeError,
"Exception should be TypeError for " + JSON.stringify(test)
);
}
);
})(req, test)
);
}
return Promise.all(fetches);
}
function testReferrer() {
var referrer;
if (self && self.location) {
referrer = self.location.href;
} else {
referrer = document.documentURI;
}
var dict = {
Referer: referrer,
};
return fetch(
corsServerPath + "headers=" + encodeURIComponent(JSON.stringify(dict))
).then(
function (res) {
is(res.status, 200, "expected correct referrer header to be sent");
dump(res.statusText);
},
function (e) {
ok(false, "expected correct referrer header to be sent");
}
);
}
function runTest() {
testNoCorsCtor();
let promise = Promise.resolve();
if (typeof SpecialPowers === "object") {
promise = SpecialPowers.pushPrefEnv({
set: [["network.cookie.sameSite.laxByDefault", false]],
});
}
return promise
.then(testModeSameOrigin)
.then(testModeNoCors)
.then(testModeCors)
.then(testSameOriginCredentials)
.then(testCrossOriginCredentials)
.then(testModeNoCorsCredentials)
.then(testCORSRedirects)
.then(testNoCORSRedirects)
.then(testReferrer);
// Put more promise based tests here.
}