diff options
Diffstat (limited to 'dom/security/test/sec-fetch')
16 files changed, 1004 insertions, 0 deletions
diff --git a/dom/security/test/sec-fetch/browser.ini b/dom/security/test/sec-fetch/browser.ini new file mode 100644 index 0000000000..ab9c0b1a7d --- /dev/null +++ b/dom/security/test/sec-fetch/browser.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = file_no_cache.sjs + +[browser_navigation.js] +[browser_external_loads.js] +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +support-files = + file_dummy_link.html + file_dummy_link_location.html diff --git a/dom/security/test/sec-fetch/browser_external_loads.js b/dom/security/test/sec-fetch/browser_external_loads.js new file mode 100644 index 0000000000..0340b46899 --- /dev/null +++ b/dom/security/test/sec-fetch/browser_external_loads.js @@ -0,0 +1,176 @@ +"use strict"; + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +var gExpectedHeader = {}; + +function checkSecFetchUser(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com")) { + return; + } + + info(`testing headers for load of ${channel.URI.spec}`); + + const secFetchHeaders = [ + "sec-fetch-mode", + "sec-fetch-dest", + "sec-fetch-user", + "sec-fetch-site", + ]; + + secFetchHeaders.forEach(header => { + const expectedValue = gExpectedHeader[header]; + try { + is( + channel.getRequestHeader(header), + expectedValue, + `${header} is set to ${expectedValue}` + ); + } catch (e) { + if (expectedValue) { + ok(false, `${header} should be set`); + } else { + ok(true, `${header} should not be set`); + } + } + }); +} + +add_task(async function external_load() { + waitForExplicitFinish(); + Services.obs.addObserver(checkSecFetchUser, "http-on-stop-request"); + + let headersChecked = new Promise(resolve => { + let reqStopped = async (subject, topic, data) => { + Services.obs.removeObserver(reqStopped, "http-on-stop-request"); + resolve(); + }; + Services.obs.addObserver(reqStopped, "http-on-stop-request"); + }); + + // System fetch. Shouldn't use Sec- headers for that. + gExpectedHeader = { + "sec-fetch-site": null, + "sec-fetch-mode": null, + "sec-fetch-dest": null, + "sec-fetch-user": null, + }; + await window.fetch(`${TEST_PATH}file_dummy_link.html?sysfetch`); + await headersChecked; + + // Simulate an external load in the *current* window with + // Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL and the system principal. + gExpectedHeader = { + "sec-fetch-site": "none", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-user": "?1", + }; + + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + window.browserDOMWindow.openURI( + makeURI(`${TEST_PATH}file_dummy_link.html`), + null, + Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL, + Services.scriptSecurityManager.getSystemPrincipal() + ); + await loaded; + + // Open a link in a *new* window through the context menu. + gExpectedHeader = { + "sec-fetch-site": "same-origin", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-user": "?1", + }; + + loaded = BrowserTestUtils.waitForNewWindow({ + url: `${TEST_PATH}file_dummy_link_location.html`, + }); + BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + document.getElementById("context-openlink").doCommand(); + event.target.hidePopup(); + return true; + }); + BrowserTestUtils.synthesizeMouseAtCenter( + "#dummylink", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + + let win = await loaded; + win.close(); + + // Simulate an external load in a *new* window with + // Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL and the system principal. + gExpectedHeader = { + "sec-fetch-site": "none", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-user": "?1", + }; + + loaded = BrowserTestUtils.waitForNewWindow({ + url: "https://example.com/newwindow", + }); + window.browserDOMWindow.openURI( + makeURI("https://example.com/newwindow"), + null, + Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW, + Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL, + Services.scriptSecurityManager.getSystemPrincipal() + ); + win = await loaded; + win.close(); + + // Open a *new* window through window.open without user activation. + gExpectedHeader = { + "sec-fetch-site": "same-origin", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + }; + + loaded = BrowserTestUtils.waitForNewWindow({ + url: "https://example.com/windowopen", + }); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.window.open( + "https://example.com/windowopen", + "_blank", + "height=500,width=500" + ); + }); + win = await loaded; + win.close(); + + // Open a *new* window through window.open with user activation. + gExpectedHeader = { + "sec-fetch-site": "same-origin", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-user": "?1", + }; + + loaded = BrowserTestUtils.waitForNewWindow({ + url: "https://example.com/windowopen_withactivation", + }); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + content.document.notifyUserGestureActivation(); + content.window.open( + "https://example.com/windowopen_withactivation", + "_blank", + "height=500,width=500" + ); + content.document.clearUserGestureActivation(); + }); + win = await loaded; + win.close(); + + Services.obs.removeObserver(checkSecFetchUser, "http-on-stop-request"); + finish(); +}); diff --git a/dom/security/test/sec-fetch/browser_navigation.js b/dom/security/test/sec-fetch/browser_navigation.js new file mode 100644 index 0000000000..642dcab76e --- /dev/null +++ b/dom/security/test/sec-fetch/browser_navigation.js @@ -0,0 +1,178 @@ +"use strict"; + +const REQUEST_URL = + "https://example.com/browser/dom/security/test/sec-fetch/file_no_cache.sjs"; + +let gTestCounter = 0; +let gExpectedHeader = {}; + +async function setup() { + waitForExplicitFinish(); +} + +function checkSecFetchUser(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com/")) { + return; + } + + info(`testing headers for load of ${channel.URI.spec}`); + + const secFetchHeaders = [ + "sec-fetch-mode", + "sec-fetch-dest", + "sec-fetch-user", + "sec-fetch-site", + ]; + + secFetchHeaders.forEach(header => { + const expectedValue = gExpectedHeader[header]; + try { + is( + channel.getRequestHeader(header), + expectedValue, + `${header} is set to ${expectedValue}` + ); + } catch (e) { + if (expectedValue) { + ok(false, "required headers are set"); + } else { + ok(true, `${header} should not be set`); + } + } + }); + + gTestCounter++; +} + +async function testNavigations() { + gTestCounter = 0; + + // Load initial site + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser, REQUEST_URL + "?test1"); + await loaded; + + // Load another site + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.notifyUserGestureActivation(); // simulate user activation + let test2Button = content.document.getElementById("test2_button"); + test2Button.click(); + content.document.clearUserGestureActivation(); + }); + await loaded; + // Load another site + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.notifyUserGestureActivation(); // simulate user activation + let test3Button = content.document.getElementById("test3_button"); + test3Button.click(); + content.document.clearUserGestureActivation(); + }); + await loaded; + + gExpectedHeader = { + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-site": "same-origin", + "sec-fetch-user": "?1", + }; + + // Register the http request observer. + // All following actions should cause requests with the sec-fetch-user header + // set. + Services.obs.addObserver(checkSecFetchUser, "http-on-stop-request"); + + // Go back one site by clicking the back button + info("Clicking back button"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + document.notifyUserGestureActivation(); // simulate user activation + let backButton = document.getElementById("back-button"); + backButton.click(); + document.clearUserGestureActivation(); + await loaded; + + // Reload the site by clicking the reload button + info("Clicking reload button"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + document.notifyUserGestureActivation(); // simulate user activation + let reloadButton = document.getElementById("reload-button"); + await TestUtils.waitForCondition(() => { + return !reloadButton.disabled; + }); + reloadButton.click(); + document.clearUserGestureActivation(); + await loaded; + + // Go forward one site by clicking the forward button + info("Clicking forward button"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + document.notifyUserGestureActivation(); // simulate user activation + let forwardButton = document.getElementById("forward-button"); + forwardButton.click(); + document.clearUserGestureActivation(); + await loaded; + + // Testing history.back/forward... + + info("going back with history.back"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.notifyUserGestureActivation(); // simulate user activation + content.history.back(); + content.document.clearUserGestureActivation(); + }); + await loaded; + + info("going forward with history.forward"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.document.notifyUserGestureActivation(); // simulate user activation + content.history.forward(); + content.document.clearUserGestureActivation(); + }); + await loaded; + + gExpectedHeader = { + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "document", + "sec-fetch-site": "same-origin", + }; + + info("going back with history.back without user activation"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.history.back(); + }); + await loaded; + + info("going forward with history.forward without user activation"); + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () { + content.history.forward(); + }); + await loaded; + + ok(gTestCounter === 7, "testing that all five actions have been tested."); + + Services.obs.removeObserver(checkSecFetchUser, "http-on-stop-request"); +} + +add_task(async function () { + waitForExplicitFinish(); + + await testNavigations(); + + // If fission is enabled we also want to test the navigations with the bfcache + // in the parent. + if (SpecialPowers.getBoolPref("fission.autostart")) { + await SpecialPowers.pushPrefEnv({ + set: [["fission.bfcacheInParent", true]], + }); + + await testNavigations(); + } + + finish(); +}); diff --git a/dom/security/test/sec-fetch/file_dummy_link.html b/dom/security/test/sec-fetch/file_dummy_link.html new file mode 100644 index 0000000000..2150054226 --- /dev/null +++ b/dom/security/test/sec-fetch/file_dummy_link.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1738694 - Sec-Fetch-User header is missing when opening a link in a new window</title> +</head> +<body> + <a id="dummylink" href="file_dummy_link_location.html">Open</a> +</body> +</html> diff --git a/dom/security/test/sec-fetch/file_dummy_link_location.html b/dom/security/test/sec-fetch/file_dummy_link_location.html new file mode 100644 index 0000000000..9f9400e1c3 --- /dev/null +++ b/dom/security/test/sec-fetch/file_dummy_link_location.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1738694 - Sec-Fetch-User header is missing when opening a link in a new window</title> +</head> +<body> + <h1>file_dummy_link_location.html</h1> +</body> +</html> diff --git a/dom/security/test/sec-fetch/file_no_cache.sjs b/dom/security/test/sec-fetch/file_no_cache.sjs new file mode 100644 index 0000000000..9e75209e44 --- /dev/null +++ b/dom/security/test/sec-fetch/file_no_cache.sjs @@ -0,0 +1,28 @@ +const MESSAGE_PAGE = function (msg) { + return ` +<html> +<script type="text/javascript"> +window.parent.postMessage({test : "${msg}"},"*"); +</script> +<script> + addEventListener("back", () => { + history.back(); + }); + addEventListener("forward", () => { + history.forward(); + }); +</script> +<body> + <a id="test2_button" href="https://example.com/browser/dom/security/test/sec-fetch/file_no_cache.sjs?test2">Click me</a> + <a id="test3_button" href="https://example.com/browser/dom/security/test/sec-fetch/file_no_cache.sjs?test3">Click me</a> +<body> +</html> +`; +}; + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-store"); + response.setHeader("Content-Type", "text/html"); + + response.write(MESSAGE_PAGE(request.queryString)); +} diff --git a/dom/security/test/sec-fetch/file_redirect.sjs b/dom/security/test/sec-fetch/file_redirect.sjs new file mode 100644 index 0000000000..84c2c39913 --- /dev/null +++ b/dom/security/test/sec-fetch/file_redirect.sjs @@ -0,0 +1,34 @@ +const SITE_META_REDIRECT = ` +<html> + <head> + <meta http-equiv="refresh" content="0; url='https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs?redirect302'"> + </head> + <body> + META REDIRECT + </body> +</html> +`; + +const REDIRECT_302 = + "https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs?pageC"; + +function handleRequest(req, res) { + // avoid confusing cache behaviour + res.setHeader("Cache-Control", "no-cache", false); + res.setHeader("Content-Type", "text/html", false); + + switch (req.queryString) { + case "meta": + res.write(SITE_META_REDIRECT); + return; + case "redirect302": + res.setStatusLine("1.1", 302, "Found"); + res.setHeader("Location", REDIRECT_302, false); + return; + case "pageC": + res.write("<html><body>PAGE C</body></html>"); + return; + } + + res.write(`<html><body>THIS SHOULD NEVER BE DISPLAYED</body></html>`); +} diff --git a/dom/security/test/sec-fetch/file_trustworthy_loopback.html b/dom/security/test/sec-fetch/file_trustworthy_loopback.html new file mode 100644 index 0000000000..88f9242650 --- /dev/null +++ b/dom/security/test/sec-fetch/file_trustworthy_loopback.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1732069: Sec-Fetch-Site inconsistent on localhost/IPs</title> +</head> +<body> + <iframe src="http://localhost:9898/foo"></iframe> + <iframe src="http://localhost:9899/foo"></iframe> + <iframe src="http://sub.localhost/foo"></iframe> +</body> +</html> diff --git a/dom/security/test/sec-fetch/file_websocket_wsh.py b/dom/security/test/sec-fetch/file_websocket_wsh.py new file mode 100644 index 0000000000..b7159c742b --- /dev/null +++ b/dom/security/test/sec-fetch/file_websocket_wsh.py @@ -0,0 +1,6 @@ +def web_socket_do_extra_handshake(request): + pass + + +def web_socket_transfer_data(request): + pass diff --git a/dom/security/test/sec-fetch/mochitest.ini b/dom/security/test/sec-fetch/mochitest.ini new file mode 100644 index 0000000000..4c27cdb2fe --- /dev/null +++ b/dom/security/test/sec-fetch/mochitest.ini @@ -0,0 +1,19 @@ +[DEFAULT] +support-files = + file_no_cache.sjs + file_redirect.sjs + +[test_iframe_history_manipulation.html] +[test_iframe_src_metaRedirect.html] +[test_iframe_srcdoc_metaRedirect.html] +[test_iframe_window_open_metaRedirect.html] +[test_trustworthy_loopback.html] +skip-if = + os == "linux" && !fission # Bug 1805760 + http3 +support-files = file_trustworthy_loopback.html +[test_websocket.html] +skip-if = + toolkit == 'android' # no websocket support Bug 982828 + http3 +support-files = file_websocket_wsh.py diff --git a/dom/security/test/sec-fetch/test_iframe_history_manipulation.html b/dom/security/test/sec-fetch/test_iframe_history_manipulation.html new file mode 100644 index 0000000000..5ec749bf4d --- /dev/null +++ b/dom/security/test/sec-fetch/test_iframe_history_manipulation.html @@ -0,0 +1,85 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1648825 - Fetch Metadata Headers contain invalid value for Sec-Fetch-Site for history manipulation</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<script class="testbody" type="text/javascript"> + +const REQUEST_PATH = 'tests/dom/security/test/sec-fetch/file_no_cache.sjs' +let sendHome = true; +let testCounter = 0; +let testFrame; + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + info("request observed: " + channel.URI.spec); + if (!channel.URI.spec.startsWith("https://example.org")) { + return; + } + let headerPresent = false; + try { + is(channel.getRequestHeader("Sec-Fetch-Site"), "cross-site", "testing sec-fetch-site is cross-site"); + + // This should fail and cause the catch clause to be executed. + channel.getRequestHeader("Sec-Fetch-User"); + headerPresent = true; + } catch (e) { + headerPresent = false; + } + + ok(!headerPresent, "testing sec-fetch-user header is not set"); + + sendAsyncMessage("test-pass"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-pass", () => { + testCounter++; + if(testCounter == 2) { + SimpleTest.finish(); + } +}); + +window.addEventListener("message", function (event) { + iframeAction(event.data.test); +}); + +function iframeAction(test) { + info("received message " + test); + + switch (test) { + case 'test': + testFrame.contentWindow.location = `https://example.org/${REQUEST_PATH}?test#bypass`; + if(sendHome) { + // We need to send the message manually here because there is no request send to the server. + window.postMessage({test: "home"}, "*"); + sendHome = false; + } + + break; + case 'home': + testFrame.contentWindow.location = `/${REQUEST_PATH}?back`; + break; + case 'back': + testFrame.contentWindow.history.back(); + break; + } +} + +SimpleTest.waitForExplicitFinish(); + +testFrame = document.createElement('iframe'); +testFrame.src = `https://example.org/${REQUEST_PATH}?test`; +document.body.appendChild(testFrame); + +</script> +</body> +</html> diff --git a/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html new file mode 100644 index 0000000000..28eae80226 --- /dev/null +++ b/dom/security/test/sec-fetch/test_iframe_src_metaRedirect.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1647128 - Fetch Metadata Headers contain invalid value for Sec-Fetch-Site for meta redirects</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<script class="testbody" type="text/javascript"> +/* + * Description of the test: + * We load site A that redirects to site B using a meta refresh, + * finally site B redirects to site C via a 302 redirect. + * The first load of site A is made by an iframe: frame.src = "...". + * We check that all requests have the Sec-Fetch-* headers set appropriately. + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = "https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs"; +let testPassCounter = 0; + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { + return; + } + + // The redirection flow is the following: + // http://mochi.test:8888 -> https://example.com?meta -> https://example.com?redirect302 -> https://example.com?pageC + // So the Sec-Fetch-* headers for each request should be: + const expectedHeaders = { + "?meta": { + "Sec-Fetch-Site": "cross-site", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + "?redirect302": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + "?pageC": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + }; + + let matchedOne = false; + for (const [query, headers] of Object.entries(expectedHeaders)) { + if (!channel.URI.spec.endsWith(query)) { + continue; + } + matchedOne = true; + + for (const [header, value] of Object.entries(headers)) { + try { + is(channel.getRequestHeader(header), value, `testing ${header} for the ${query} query`); + } catch (e) { + is(header, "Sec-Fetch-User", "testing Sec-Fetch-User"); + } + } + } + ok(matchedOne, "testing expectedHeaders"); + + sendAsyncMessage("test-pass"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-pass", async () => { + testPassCounter++; + if (testPassCounter < 3) { + return; + } + + // If we received "test-pass" 3 times we know that all loads had Sec-Fetch-* headers set appropriately. + SimpleTest.finish(); +}); + +let frame = document.createElement("iframe"); +frame.src = REQUEST_URL + "?meta"; +document.body.appendChild(frame); + +</script> +</body> +</html> diff --git a/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html new file mode 100644 index 0000000000..adee5afe84 --- /dev/null +++ b/dom/security/test/sec-fetch/test_iframe_srcdoc_metaRedirect.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1647128 - Fetch Metadata Headers contain invalid value for Sec-Fetch-Site for meta redirects</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<script class="testbody" type="text/javascript"> +/* + * Description of the test: + * We load site A that redirects to site B using a meta refresh, + * finally site B redirects to site C via a 302 redirect. + * The first load of site A is made by an iframe: frame.srcdoc = "<meta ...". + * We check that all requests have the Sec-Fetch-* headers set appropriately. + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = "https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs"; +let testPassCounter = 0; + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { + return; + } + + // The redirection flow is the following: + // http://mochi.test:8888 -> https://example.com?meta -> https://example.com?redirect302 -> https://example.com?pageC + // So the Sec-Fetch-* headers for each request should be: + const expectedHeaders = { + "?meta": { + "Sec-Fetch-Site": "cross-site", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + "?redirect302": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + "?pageC": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "iframe", + "Sec-Fetch-User": null, + }, + }; + + let matchedOne = false; + for (const [query, headers] of Object.entries(expectedHeaders)) { + if (!channel.URI.spec.endsWith(query)) { + continue; + } + matchedOne = true; + + for (const [header, value] of Object.entries(headers)) { + try { + is(channel.getRequestHeader(header), value, `testing ${header} for the ${query} query`); + } catch (e) { + is(header, "Sec-Fetch-User", "testing Sec-Fetch-User"); + } + } + } + ok(matchedOne, "testing expectedHeaders"); + + sendAsyncMessage("test-pass"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-pass", async () => { + testPassCounter++; + if (testPassCounter < 3) { + return; + } + + // If we received "test-pass" 3 times we know that all loads had Sec-Fetch-* headers set appropriately. + SimpleTest.finish(); +}); + +let frame = document.createElement("iframe"); +frame.srcdoc = `<meta http-equiv="refresh" content="0; url='${REQUEST_URL}?meta'">`; +document.body.appendChild(frame); + +</script> +</body> +</html> diff --git a/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html b/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html new file mode 100644 index 0000000000..b532baeb5e --- /dev/null +++ b/dom/security/test/sec-fetch/test_iframe_window_open_metaRedirect.html @@ -0,0 +1,98 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1647128 - Fetch Metadata Headers contain invalid value for Sec-Fetch-Site for meta redirects</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + +<script class="testbody" type="text/javascript"> +/* + * Description of the test: + * We load site A that redirects to site B using a meta refresh, + * finally site B redirects to site C via a 302 redirect. + * The first load of site A is made through window.open. + * We check that all requests have the Sec-Fetch-* headers set appropriately. + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = "https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs"; +let testPassCounter = 0; +let testWindow; + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_redirect.sjs")) { + return; + } + + // The redirection flow is the following: + // http://mochi.test:8888 -> https://example.com?meta -> https://example.com?redirect302 -> https://example.com?pageC + // So the Sec-Fetch-* headers for each request should be: + const expectedHeaders = { + "?meta": { + "Sec-Fetch-Site": "cross-site", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-User": null, + }, + "?redirect302": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-User": null, + }, + "?pageC": { + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-User": null, + }, + }; + + let matchedOne = false; + for (const [query, headers] of Object.entries(expectedHeaders)) { + if (!channel.URI.spec.endsWith(query)) { + continue; + } + matchedOne = true; + + for (const [header, value] of Object.entries(headers)) { + try { + is(channel.getRequestHeader(header), value, `testing ${header} for the ${query} query`); + } catch (e) { + is(header, "Sec-Fetch-User", "testing Sec-Fetch-User"); + } + } + } + ok(matchedOne, "testing expectedHeaders"); + + sendAsyncMessage("test-pass"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-pass", async () => { + testPassCounter++; + if (testPassCounter < 3) { + return; + } + + if (testWindow) { + testWindow.close(); + } + + // If we received "test-pass" 3 times we know that all loads had Sec-Fetch-* headers set appropriately. + SimpleTest.finish(); +}); + +testWindow = window.open(REQUEST_URL + "?meta"); + +</script> +</body> +</html> diff --git a/dom/security/test/sec-fetch/test_trustworthy_loopback.html b/dom/security/test/sec-fetch/test_trustworthy_loopback.html new file mode 100644 index 0000000000..95ecac17ed --- /dev/null +++ b/dom/security/test/sec-fetch/test_trustworthy_loopback.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1732069: Sec-Fetch-Site inconsistent on localhost/IPs</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +let testsSucceeded = 0; + +let win; +function checkTestsDone() { + testsSucceeded++; + if (testsSucceeded == 3) { + win.close(); + SimpleTest.finish(); + } +} + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.includes("localhost") || + channel.URI.spec.startsWith("http://localhost:9898/tests/dom/security/test/sec-fetch/file_trustworthy_loopback.html")) { + return; + } + + const expectedHeaders = { + "localhost:9898": { + "sec-fetch-site": "same-origin", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "iframe", + }, + "sub.localhost:-1": { + "sec-fetch-site": "cross-site", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "iframe", + }, + "localhost:9899": { + "sec-fetch-site": "same-site", + "sec-fetch-mode": "navigate", + "sec-fetch-dest": "iframe", + }, + }; + + info(`checking headers for request to ${channel.URI.spec}`); + const expected = expectedHeaders[channel.URI.host + ":" + channel.URI.port]; + for (let key in expected) { + try { + is(channel.getRequestHeader(key), expected[key], `${key} header matches`); + } catch (e) { + ok(false, "failed to check headers"); + } + } + sendAsyncMessage("test-end"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-end", () => { + checkTestsDone(); +}); + +SpecialPowers.pushPrefEnv({set: [ + ["network.proxy.allow_hijacking_localhost", true], + ["network.proxy.testing_localhost_is_secure_when_hijacked", true], +]}).then(function() { + win = window.open("http://localhost:9898/tests/dom/security/test/sec-fetch/file_trustworthy_loopback.html"); +}); + +</script> +</body> +</html> diff --git a/dom/security/test/sec-fetch/test_websocket.html b/dom/security/test/sec-fetch/test_websocket.html new file mode 100644 index 0000000000..33053ff20a --- /dev/null +++ b/dom/security/test/sec-fetch/test_websocket.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1628605: Test Sec-Fetch-* header for websockets</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +let testsSucceeded = 0; + +function checkTestsDone() { + testsSucceeded++; + if (testsSucceeded == 2) { + SimpleTest.finish(); + } +} + +var script = SpecialPowers.loadChromeScript(() => { + /* eslint-env mozilla/chrome-script */ + Services.obs.addObserver(function onExamResp(subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (!channel.URI.spec.startsWith("https://example.com/tests/dom/security/test/sec-fetch/file_websocket")) { + return; + } + + // Sec-Fetch-* Headers should be present for Dest, Mode, Site + try { + let secFetchDest = channel.getRequestHeader("Sec-Fetch-Dest"); + is(secFetchDest, "websocket", "testing sec-fetch-dest"); + + let secFetchMode = channel.getRequestHeader("Sec-Fetch-Mode"); + is(secFetchMode, "websocket", "testing sec-fetch-mode"); + + let secFetchSite = channel.getRequestHeader("Sec-Fetch-Site"); + is(secFetchSite, "cross-site", "testing sec-fetch-site"); + } + catch (e) { + ok(false, "testing sec-fetch-*"); + } + + // Sec-Fetch-User should not be present + try { + channel.getRequestHeader("Sec-Fetch-User"); + ok(false, "testing sec-fetch-user"); + } + catch (e) { + ok(true, "testing sec-fetch-user"); + } + Services.obs.removeObserver(onExamResp, "http-on-stop-request"); + + sendAsyncMessage("test-end"); + }, "http-on-stop-request"); +}); + +script.addMessageListener("test-end", () => { + checkTestsDone(); +}); + +var wssSocket = new WebSocket("wss://example.com/tests/dom/security/test/sec-fetch/file_websocket"); +wssSocket.onopen = function(e) { + ok(true, "sanity: wssSocket onopen"); + checkTestsDone(); +}; +wssSocket.onerror = function(e) { + ok(false, "sanity: wssSocket onerror"); +}; + +</script> +</body> +</html> |