var origin = "http://mochi.test:8888"; function fetchXHRWithMethod(name, method, onload, onerror, headers) { expectAsyncResult(); onload = onload || function () { my_ok(false, "XHR load should not complete successfully"); finish(); }; onerror = onerror || function () { my_ok( false, "XHR load for " + name + " should be intercepted successfully" ); finish(); }; var x = new XMLHttpRequest(); x.open(method, name, true); x.onload = function () { onload(x); }; x.onerror = function () { onerror(x); }; headers = headers || []; headers.forEach(function (header) { x.setRequestHeader(header[0], header[1]); }); x.send(); } var corsServerPath = "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs"; var corsServerURL = "http://example.com" + corsServerPath; function redirectURL(hops) { return ( hops[0].server + corsServerPath + "?hop=1&hops=" + encodeURIComponent(JSON.stringify(hops)) ); } function fetchXHR(name, onload, onerror, headers) { return fetchXHRWithMethod(name, "GET", onload, onerror, headers); } fetchXHR("bare-synthesized.txt", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "synthesized response body", "load should have synthesized response" ); finish(); }); fetchXHR("test-respondwith-response.txt", function (xhr) { my_ok( xhr.status == 200, "test-respondwith-response load should be successful" ); my_ok( xhr.responseText == "test-respondwith-response response body", "load should have response" ); finish(); }); fetchXHR("synthesized-404.txt", function (xhr) { my_ok(xhr.status == 404, "load should 404"); my_ok( xhr.responseText == "synthesized response body", "404 load should have synthesized response" ); finish(); }); fetchXHR("synthesized-headers.txt", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.getResponseHeader("X-Custom-Greeting") === "Hello", "custom header should be set" ); my_ok( xhr.responseText == "synthesized response body", "custom header load should have synthesized response" ); finish(); }); fetchXHR("synthesized-redirect-real-file.txt", function (xhr) { dump("Got status AARRGH " + xhr.status + " " + xhr.responseText + "\n"); my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "This is a real file.\n", "Redirect to real file should complete." ); finish(); }); fetchXHR("synthesized-redirect-twice-real-file.txt", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "This is a real file.\n", "Redirect to real file (twice) should complete." ); finish(); }); fetchXHR("synthesized-redirect-synthesized.txt", function (xhr) { my_ok(xhr.status == 200, "synth+redirect+synth load should be successful"); my_ok( xhr.responseText == "synthesized response body", "load should have redirected+synthesized response" ); finish(); }); fetchXHR("synthesized-redirect-twice-synthesized.txt", function (xhr) { my_ok( xhr.status == 200, "synth+redirect+synth (twice) load should be successful" ); my_ok( xhr.responseText == "synthesized response body", "load should have redirected+synthesized (twice) response" ); finish(); }); fetchXHR("redirect.sjs", function (xhr) { my_ok(xhr.status == 404, "redirected load should be uninterrupted"); finish(); }); fetchXHR("ignored.txt", function (xhr) { my_ok(xhr.status == 404, "load should be uninterrupted"); finish(); }); fetchXHR("rejected.txt", null, function (xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); fetchXHR("nonresponse.txt", null, function (xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); fetchXHR("nonresponse2.txt", null, function (xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); fetchXHR("nonpromise.txt", null, function (xhr) { my_ok(xhr.status == 0, "load should not complete"); finish(); }); fetchXHR( "headers.txt", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok(xhr.responseText == "1", "request header checks should have passed"); finish(); }, null, [ ["X-Test1", "header1"], ["X-Test2", "header2"], ] ); fetchXHR("http://user:pass@mochi.test:8888/user-pass", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "http://user:pass@mochi.test:8888/user-pass", "The username and password should be preserved" ); finish(); }); fetchXHR("readable-stream.txt", function (xhr) { my_ok(xhr.status == 200, "loading completed"); my_ok(xhr.responseText == "Hello!", "The message is correct!"); finish(); }); fetchXHR( "readable-stream-locked.txt", function (xhr) { my_ok(false, "This should not be called!"); finish(); }, function () { my_ok(true, "The exception has been correctly handled!"); finish(); } ); fetchXHR( "readable-stream-with-exception.txt", function (xhr) { my_ok(false, "This should not be called!"); finish(); }, function () { my_ok(true, "The exception has been correctly handled!"); finish(); } ); fetchXHR( "readable-stream-with-exception2.txt", function (xhr) { my_ok(false, "This should not be called!"); finish(); }, function () { my_ok(true, "The exception has been correctly handled!"); finish(); } ); fetchXHR( "readable-stream-already-consumed.txt", function (xhr) { my_ok(false, "This should not be called!"); finish(); }, function () { my_ok(true, "The exception has been correctly handled!"); finish(); } ); var expectedUncompressedResponse = ""; for (let i = 0; i < 10; ++i) { expectedUncompressedResponse += "hello"; } expectedUncompressedResponse += "\n"; // ServiceWorker does not intercept, at which point the network request should // be correctly decoded. fetchXHR("deliver-gzip.sjs", function (xhr) { my_ok(xhr.status == 200, "network gzip load should be successful"); my_ok( xhr.responseText == expectedUncompressedResponse, "network gzip load should have synthesized response." ); my_ok( xhr.getResponseHeader("Content-Encoding") == "gzip", "network Content-Encoding should be gzip." ); my_ok( xhr.getResponseHeader("Content-Length") == "35", "network Content-Length should be of original gzipped file." ); finish(); }); fetchXHR("hello.gz", function (xhr) { my_ok(xhr.status == 200, "gzip load should be successful"); my_ok( xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response." ); my_ok( xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip." ); my_ok( xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file." ); finish(); }); fetchXHR("hello-after-extracting.gz", function (xhr) { my_ok(xhr.status == 200, "gzip load after extracting should be successful"); my_ok( xhr.responseText == expectedUncompressedResponse, "gzip load after extracting should have synthesized response." ); my_ok( xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding after extracting should be gzip." ); my_ok( xhr.getResponseHeader("Content-Length") == "35", "Content-Length after extracting should be of original gzipped file." ); finish(); }); fetchXHR(corsServerURL + "?status=200&allowOrigin=*", function (xhr) { my_ok( xhr.status == 200, "cross origin load with correct headers should be successful" ); my_ok( xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out" ); finish(); }); // Verify origin header is sent properly even when we have a no-intercept SW. var uriOrigin = encodeURIComponent(origin); fetchXHR( "http://example.org" + corsServerPath + "?ignore&status=200&origin=" + uriOrigin + "&allowOrigin=" + uriOrigin, function (xhr) { my_ok( xhr.status == 200, "cross origin load with correct headers should be successful" ); my_ok( xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out" ); finish(); } ); // Verify that XHR is considered CORS tainted even when original URL is same-origin // redirected to cross-origin. fetchXHR( redirectURL([ { server: origin }, { server: "http://example.org", allowOrigin: origin }, ]), function (xhr) { my_ok( xhr.status == 200, "cross origin load with correct headers should be successful" ); my_ok( xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out" ); finish(); } ); // Test that CORS preflight requests cannot be intercepted. Performs a // cross-origin XHR that the SW chooses not to intercept. This requires a // preflight request, which the SW must not be allowed to intercept. fetchXHR( corsServerURL + "?status=200&allowOrigin=*", null, function (xhr) { my_ok( xhr.status == 0, "cross origin load with incorrect headers should be a failure" ); finish(); }, [["X-Unsafe", "unsafe"]] ); // Test that CORS preflight requests cannot be intercepted. Performs a // cross-origin XHR that the SW chooses to intercept and respond with a // cross-origin fetch. This requires a preflight request, which the SW must not // be allowed to intercept. fetchXHR( "http://example.org" + corsServerPath + "?status=200&allowOrigin=*", null, function (xhr) { my_ok( xhr.status == 0, "cross origin load with incorrect headers should be a failure" ); finish(); }, [["X-Unsafe", "unsafe"]] ); // Test that when the page fetches a url the controlling SW forces a redirect to // another location. This other location fetch should also be intercepted by // the SW. fetchXHR("something.txt", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "something else response body", "load should have something else" ); finish(); }); // Test fetch will internally get it's SkipServiceWorker flag set. The request is // made from the SW through fetch(). fetch() fetches a server-side JavaScript // file that force a redirect. The redirect location fetch does not go through // the SW. fetchXHR("redirect_serviceworker.sjs", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "// empty worker, always succeed!\n", "load should have redirection content" ); finish(); }); fetchXHR( "empty-header", function (xhr) { my_ok(xhr.status == 200, "load should be successful"); my_ok( xhr.responseText == "emptyheader", "load should have the expected content" ); finish(); }, null, [["emptyheader", ""]] ); expectAsyncResult(); fetch( "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*" ).then( function (res) { my_ok(res.ok, "Valid CORS request should receive valid response"); my_ok(res.type == "cors", "Response type should be CORS"); res.text().then(function (body) { my_ok( body === "hello pass\n", "cors response body should match" ); finish(); }); }, function (e) { my_ok(false, "CORS Fetch failed"); finish(); } ); expectAsyncResult(); fetch( "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200", { mode: "no-cors" } ).then( function (res) { my_ok(res.type == "opaque", "Response type should be opaque"); my_ok(res.status == 0, "Status should be 0"); res.text().then(function (body) { my_ok(body === "", "opaque response body should be empty"); finish(); }); }, function (e) { my_ok(false, "no-cors Fetch failed"); finish(); } ); expectAsyncResult(); fetch("opaque-on-same-origin").then( function (res) { my_ok( false, "intercepted opaque response for non no-cors request should fail." ); finish(); }, function (e) { my_ok( true, "intercepted opaque response for non no-cors request should fail." ); finish(); } ); expectAsyncResult(); fetch("http://example.com/opaque-no-cors", { mode: "no-cors" }).then( function (res) { my_ok( res.type == "opaque", "intercepted opaque response for no-cors request should have type opaque." ); finish(); }, function (e) { my_ok( false, "intercepted opaque response for no-cors request should pass." ); finish(); } ); expectAsyncResult(); fetch("http://example.com/cors-for-no-cors", { mode: "no-cors" }).then( function (res) { my_ok( res.type == "cors", "synthesize CORS response should result in outer CORS response" ); finish(); }, function (e) { my_ok(false, "cors-for-no-cors request should not reject"); finish(); } ); function arrayBufferFromString(str) { var arr = new Uint8Array(str.length); for (let i = 0; i < str.length; ++i) { arr[i] = str.charCodeAt(i); } return arr; } expectAsyncResult(); fetch(new Request("body-simple", { method: "POST", body: "my body" })) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body == "my bodymy body", "the body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); fetch( new Request("body-arraybufferview", { method: "POST", body: arrayBufferFromString("my body"), }) ) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body == "my bodymy body", "the ArrayBufferView body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); fetch( new Request("body-arraybuffer", { method: "POST", body: arrayBufferFromString("my body").buffer, }) ) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body == "my bodymy body", "the ArrayBuffer body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); var usp = new URLSearchParams(); usp.set("foo", "bar"); usp.set("baz", "qux"); fetch(new Request("body-urlsearchparams", { method: "POST", body: usp })) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body == "foo=bar&baz=quxfoo=bar&baz=qux", "the URLSearchParams body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); var fd = new FormData(); fd.set("foo", "bar"); fd.set("baz", "qux"); fetch(new Request("body-formdata", { method: "POST", body: fd })) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body.indexOf('Content-Disposition: form-data; name="foo"\r\n\r\nbar') < body.indexOf('Content-Disposition: form-data; name="baz"\r\n\r\nqux'), "the FormData body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); fetch( new Request("body-blob", { method: "POST", body: new Blob(new String("my body")), }) ) .then(function (res) { return res.text(); }) .then(function (body) { my_ok( body == "my bodymy body", "the Blob body of the intercepted fetch should be visible in the SW" ); finish(); }); expectAsyncResult(); fetch("interrupt.sjs").then( function (res) { my_ok(true, "interrupted fetch succeeded"); res.text().then( function (body) { my_ok(false, "interrupted fetch shouldn't have complete body"); finish(); }, function () { my_ok(true, "interrupted fetch shouldn't have complete body"); finish(); } ); }, function (e) { my_ok(false, "interrupted fetch failed"); finish(); } ); ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"].forEach(function (method) { fetchXHRWithMethod("xhr-method-test.txt", method, function (xhr) { my_ok(xhr.status == 200, method + " load should be successful"); if (method === "HEAD") { my_ok( xhr.responseText == "", method + "load should not have synthesized response" ); } else { my_ok( xhr.responseText == "intercepted " + method, method + " load should have synthesized response" ); } finish(); }); }); expectAsyncResult(); fetch(new Request("empty-header", { headers: { emptyheader: "" } })) .then(function (res) { return res.text(); }) .then( function (body) { my_ok( body == "emptyheader", "The empty header was observed in the fetch event" ); finish(); }, function (err) { my_ok(false, "A promise was rejected with " + err); finish(); } ); expectAsyncResult(); fetch("fetchevent-extendable") .then(function (res) { return res.text(); }) .then( function (body) { my_ok(body == "extendable", "FetchEvent inherits from ExtendableEvent"); finish(); }, function (err) { my_ok(false, "A promise was rejected with " + err); finish(); } ); expectAsyncResult(); fetch("fetchevent-request") .then(function (res) { return res.text(); }) .then( function (body) { my_ok(body == "non-nullable", "FetchEvent.request must be non-nullable"); finish(); }, function (err) { my_ok(false, "A promise was rejected with " + err); finish(); } );