From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/tests/mochitest/fetch/test_fetch_cors.js | 1748 ++++++++++++++++++++++++++ 1 file changed, 1748 insertions(+) create mode 100644 dom/tests/mochitest/fetch/test_fetch_cors.js (limited to 'dom/tests/mochitest/fetch/test_fetch_cors.js') diff --git a/dom/tests/mochitest/fetch/test_fetch_cors.js b/dom/tests/mochitest/fetch/test_fetch_cors.js new file mode 100644 index 000000000..ac83d050d --- /dev/null +++ b/dom/tests/mochitest/fetch/test_fetch_cors.js @@ -0,0 +1,1748 @@ +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 "omit". + pass: 1, + noCookie: 1, + }, + { + 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. +} -- cgit v1.2.3