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".
var req = new Request("http://example.com", { mode: "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) {
var r = new Request("http://example.com", { 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 {
var r = new Request("http://example.com", { 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.
var r = new Request("http://example.com" + corsServerPath + "status=200", { 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,
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 " + test.toSource());
is(res.status, 200, "wrong status in test for " + test.toSource());
is(res.statusText, "OK", "wrong status text for " + test.toSource());
return res.text().then(function(v) {
is(v, "hello pass\n",
"wrong text in test for " + test.toSource());
});
}
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 " + test.toSource());
ok(e instanceof TypeError, "Test should fail " + test.toSource());
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
// XXXbz this passes for now, because we ignore passwords
// without usernames in most cases.
{ pass: 1,
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",
origin: "http://invalid",
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 baseURL = "http://example.org" + corsServerPath;
var origin = "http://mochi.test:8888";
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"]
.indexOf(test.headers[name].toLowerCase()) == -1);
}
req.url += "&headers=" + escape(test.headers.toSource());
reqHeaders =
escape(Object.keys(test.headers)
.filter(isUnsafeHeader)
.map(String.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(test.responseHeaders.toSource());
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 " + test.toSource());
if (test.status) {
is(res.status, test.status, "wrong status in test for " + test.toSource());
is(res.statusText, test.statusMessage, "wrong status text for " + test.toSource());
}
else {
is(res.status, 200, "wrong status in test for " + test.toSource());
is(res.statusText, "OK", "wrong status text for " + test.toSource());
}
if (test.responseHeaders) {
for (header in test.responseHeaders) {
if (test.expectedResponseHeaders.indexOf(header) == -1) {
is(res.headers.has(header), false,
"|Headers.has()|wrong response header (" + header + ") in test for " +
test.toSource());
}
else {
is(res.headers.get(header), test.responseHeaders[header],
"|Headers.get()|wrong response header (" + header + ") in test for " +
test.toSource());
}
}
}
return res.text();
}).then(function(v) {
if (test.method !== "HEAD") {
is(v, "hello pass\n",
"wrong responseText in test for " + test.toSource());
}
else {
is(v, "",
"wrong responseText in HEAD test for " + test.toSource());
}
}).catch(function(e) {
ok(!test.pass, "Expected test failure for " + test.toSource());
ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
});
})(test));
}
return Promise.all(fetches);
}
function testCrossOriginCredentials() {
var origin = "http://mochi.test:8888";
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",
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",
withCred: "omit",
allowCred: 1,
},
{ pass: 1,
method: "GET",
cookie: "a=1",
withCred: "include",
allowCred: 1,
},
{ pass: 1,
method: "GET",
setCookie: "a=2",
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"),
},
{ server: "http://example.com",
allowOrigin: origin,
allowCred: 1,
setCookie: escape("a=2"),
},
],
withCred: "include",
},
{ pass: 1,
method: "GET",
hops: [{ server: origin,
cookie: escape("a=1"),
},
{ server: origin,
cookie: escape("a=1"),
},
{ server: "http://example.com",
allowOrigin: origin,
noCookie: 1,
},
],
withCred: "same-origin",
},
{ pass: 1,
method: "GET",
hops: [{ server: origin,
cookie: escape("a=1"),
},
{ server: origin,
cookie: escape("a=1"),
},
{ server: "http://example.com",
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"),
},
{ server: "http://example.com",
allowOrigin: '*',
noCookie: 1,
},
],
withCred: "same-origin",
},
{ pass: 0,
method: "GET",
hops: [{ server: origin,
cookie: escape("a=1"),
},
{ server: origin,
cookie: escape("a=1"),
},
{ server: "http://example.com",
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"),
},
{ server: "http://example.com",
allowOrigin: origin,
cookie: escape("a=2"),
},
],
withCred: "include",
},
{ pass: 1,
method: "GET",
hops: [{ server: origin,
noCookie: 1,
},
{ server: origin,
noCookie: 1,
},
{ server: "http://example.com",
allowOrigin: origin,
noCookie: 1,
},
],
withCred: "omit",
},
];
var baseURL = "http://example.org" + corsServerPath;
var origin = "http://mochi.test:8888";
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(test.hops.toSource());
} else {
url = baseURL + "allowOrigin=" + escape(test.origin || origin);
}
req = {
url: 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 " + test.toSource());
is(res.status, 200, "wrong status in test for " + test.toSource());
is(res.statusText, "OK", "wrong status text for " + test.toSource());
return res.text().then(function(v) {
is(v, "hello pass\n",
"wrong text in test for " + test.toSource());
});
}
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 " + test.toSource());
ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
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,
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 = {
url : "http://example.org" + corsServerPath + "a+b",
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 ' + test.toSource());
// 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 " + test.toSource());
return unfiltered.text().then(function(v) {
if (status === 200) {
is(v, "hello pass\n",
"wrong text in test for " + test.toSource());
}
});
}
function runATest(tests, i) {
if (typeof SpecialPowers !== 'object') {
finalPromiseResolve();
return;
}
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 to fail " + test.toSource());
ok(e instanceof TypeError, "Test should fail " + test.toSource());
if (i < tests.length-1) {
runATest(tests, i+1);
} else {
finalPromiseResolve();
}
});
}
runATest(tests, 0);
return finalPromise;
}
function testCORSRedirects() {
var origin = "http://mochi.test:8888";
var tests = [
{ pass: 1,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://mochi.test:8888",
allowOrigin: origin
},
],
},
{ pass: 1,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://mochi.test:8888",
allowOrigin: "*"
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://mochi.test:8888",
},
],
},
{ pass: 1,
method: "GET",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://mochi.test:8888",
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: "*"
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: "*"
},
],
},
{ pass: 1,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: "*"
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: "*"
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: "*"
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: "x"
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: "*"
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin
},
],
},
{ pass: 0,
method: "GET",
hops: [{ server: "http://example.com",
allowOrigin: origin
},
{ server: "http://test2.mochi.test:8888",
allowOrigin: origin
},
{ server: "http://sub2.xn--lt-uia.mochi.test:8888",
allowOrigin: "*"
},
{ server: "http://sub1.test1.mochi.test:8888",
},
],
},
{ pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain" },
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
},
],
},
{ pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
allowHeaders: "my-header",
noAllowPreflight: 1,
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://test1.example.com",
allowOrigin: origin,
allowHeaders: "my-header",
},
{ server: "http://test2.example.com",
allowOrigin: origin,
allowHeaders: "my-header",
}
],
},
{ pass: 1,
method: "DELETE",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{ pass: 0,
method: "DELETE",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
allowMethods: "DELETE",
noAllowPreflight: 1,
},
],
},
{ pass: 0,
method: "DELETE",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://test1.example.com",
allowOrigin: origin,
allowMethods: "DELETE",
},
{ server: "http://test2.example.com",
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://example.com",
allowOrigin: origin,
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin,
},
],
},
{ pass: 0,
method: "DELETE",
hops: [{ server: "http://example.com",
allowOrigin: origin,
allowMethods: "DELETE",
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin,
allowMethods: "DELETE",
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://example.com",
},
{ server: "http://sub1.test1.mochi.test:8888",
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
{ pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain" },
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://example.com",
allowOrigin: origin,
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://example.com",
allowOrigin: origin,
allowHeaders: "my-header",
},
{ server: "http://mochi.test:8888",
allowOrigin: origin,
allowHeaders: "my-header",
},
],
},
];
var fetches = [];
for (test of tests) {
req = {
url: test.hops[0].server + corsServerPath + "hop=1&hops=" +
escape(test.hops.toSource()),
method: test.method,
headers: test.headers,
body: test.body,
};
if (test.headers) {
req.url += "&headers=" + escape(test.headers.toSource());
}
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 " + test.toSource());
is(res.status, 200, "wrong status in test for " + test.toSource());
is(res.statusText, "OK", "wrong status text for " + test.toSource());
is(res.type, 'cors', 'wrong response type for ' + test.toSource());
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, "hello pass\n",
"wrong responseText in test for " + test.toSource());
});
}, function(e) {
ok(!test.pass, "Expected test failure for " + test.toSource());
ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
});
})(request, test));
}
return Promise.all(fetches);
}
function testNoCORSRedirects() {
var origin = "http://mochi.test:8888";
var tests = [
{ pass: 1,
method: "GET",
hops: [{ server: "http://example.com",
},
],
},
{ pass: 1,
method: "GET",
hops: [{ server: origin,
},
{ server: "http://example.com",
},
],
},
{ pass: 1,
method: "GET",
// Must use a simple header due to no-cors header restrictions.
headers: { "accept-language": "en-us",
},
hops: [{ server: origin,
},
{ server: "http://example.com",
},
],
},
{ pass: 1,
method: "GET",
hops: [{ server: origin,
},
{ server: "http://example.com",
},
{ server: origin,
}
],
},
{ pass: 1,
method: "POST",
body: 'upload body here',
hops: [{ server: origin
},
{ server: "http://example.com",
},
],
},
{ pass: 0,
method: "DELETE",
hops: [{ server: origin
},
{ server: "http://example.com",
},
],
},
];
var fetches = [];
for (test of tests) {
req = {
url: test.hops[0].server + corsServerPath + "hop=1&hops=" +
escape(test.hops.toSource()),
method: test.method,
headers: test.headers,
body: test.body,
};
if (test.headers) {
req.url += "&headers=" + escape(test.headers.toSource());
}
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 " + test.toSource());
// 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 ' + test.toSource());
is(res.status, 0, "wrong status in test for " + test.toSource());
is(res.statusText, "", "wrong status text for " + test.toSource());
is(res.url, '', 'wrong response url for ' + test.toSource());
return res.text().then(function(v) {
is(v, "", "wrong responseText in test for " + test.toSource());
});
}, function(e) {
ok(!test.pass, "Expected test failure for " + test.toSource());
ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
});
})(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=" + dict.toSource()).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();
return Promise.resolve()
.then(testModeSameOrigin)
.then(testModeNoCors)
.then(testModeCors)
.then(testSameOriginCredentials)
.then(testCrossOriginCredentials)
.then(testModeNoCorsCredentials)
.then(testCORSRedirects)
.then(testNoCORSRedirects)
.then(testReferrer)
// Put more promise based tests here.
}