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. }