diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/components/httpsonlyerror | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/httpsonlyerror')
12 files changed, 742 insertions, 0 deletions
diff --git a/toolkit/components/httpsonlyerror/content/errorpage.html b/toolkit/components/httpsonlyerror/content/errorpage.html new file mode 100644 index 0000000000..186b768041 --- /dev/null +++ b/toolkit/components/httpsonlyerror/content/errorpage.html @@ -0,0 +1,44 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<!DOCTYPE html> + +<html> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" /> + <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css"> + <link rel="stylesheet" href="chrome://global/skin/aboutHttpsOnlyError.css"> + <link rel="localization" href="branding/brand.ftl"/> + <link rel="localization" href="toolkit/about/aboutHttpsOnlyError.ftl"> + <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in + toolkit/components/places/src/nsFaviconService.h should be updated. --> + <link rel="icon" id="favicon" href="chrome://global/skin/icons/warning.svg"/> + <title data-l10n-id="about-httpsonly-title-connection-not-available"></title> + </head> + <body> + <main class="container"> + <div class="title"> + <h2 data-l10n-id="about-httpsonly-title-alert"></h2> + <h1 class="title-text" data-l10n-id="about-httpsonly-title-connection-not-available"></h1> + </div> + <p id="insecure-explanation-unavailable" data-l10n-id="about-httpsonly-explanation-unavailable2"></p> + <p id="learn-more-container"> + <a id="learnMoreLink" target="_blank" data-l10n-id="about-httpsonly-link-learn-more"></a> + </p> + + <b data-l10n-id="about-httpsonly-explanation-question"></b> + <ul> + <li data-l10n-id="about-httpsonly-explanation-nosupport"></li> + <li data-l10n-id="about-httpsonly-explanation-risk"></li> + </ul> + + <p id="explanation-continue" data-l10n-id="about-httpsonly-explanation-continue"></p> + <div class="button-container"> + <button id="goBack" class="primary" data-l10n-id="about-httpsonly-button-go-back"></button> + <button id="openInsecure" data-l10n-id="about-httpsonly-button-continue-to-site"></button> + </div> + </main> + <script src="chrome://global/content/httpsonlyerror/errorpage.js"></script> + </body> + </html> diff --git a/toolkit/components/httpsonlyerror/content/errorpage.js b/toolkit/components/httpsonlyerror/content/errorpage.js new file mode 100644 index 0000000000..a641216c55 --- /dev/null +++ b/toolkit/components/httpsonlyerror/content/errorpage.js @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/frame-script */ + +"use strict"; + +const searchParams = new URLSearchParams(document.documentURI.split("?")[1]); + +function initPage() { + if (!searchParams.get("e")) { + document.getElementById("error").remove(); + } + + const explanation1 = document.getElementById( + "insecure-explanation-unavailable" + ); + + const pageUrl = new URL(window.location.href.replace(/^view-source:/, "")); + + document.l10n.setAttributes( + explanation1, + "about-httpsonly-explanation-unavailable2", + { websiteUrl: pageUrl.host } + ); + + const baseSupportURL = RPMGetFormatURLPref("app.support.baseURL"); + document + .getElementById("learnMoreLink") + .setAttribute("href", baseSupportURL + "https-only-prefs"); + + document + .getElementById("openInsecure") + .addEventListener("click", onOpenInsecureButtonClick); + + if (window.top == window) { + document + .getElementById("goBack") + .addEventListener("click", onReturnButtonClick); + addAutofocus("#goBack", "beforeend"); + } else { + document.getElementById("goBack").remove(); + } +} + +/* Button Events */ + +function onOpenInsecureButtonClick() { + RPMSendAsyncMessage("openInsecure", { + inFrame: window.top != window, + }); +} + +function onReturnButtonClick() { + RPMSendAsyncMessage("goBack"); +} + +/* Utils */ + +function addAutofocus(selector, position = "afterbegin") { + if (window.top != window) { + return; + } + var button = document.querySelector(selector); + var parent = button.parentNode; + button.remove(); + button.setAttribute("autofocus", "true"); + parent.insertAdjacentElement(position, button); +} + +/* Initialize Page */ + +initPage(); +// Dispatch this event so tests can detect that we finished loading the error page. +// We're using the same event name as neterror because BrowserTestUtils.jsm relies on that. +let event = new CustomEvent("AboutNetErrorLoad", { bubbles: true }); +document.dispatchEvent(event); diff --git a/toolkit/components/httpsonlyerror/content/secure-broken.svg b/toolkit/components/httpsonlyerror/content/secure-broken.svg new file mode 100644 index 0000000000..417d17b3cc --- /dev/null +++ b/toolkit/components/httpsonlyerror/content/secure-broken.svg @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="context-fill #424e5a"><path d="M18.75 9.977h-.727L6 22h12.75A2.25 2.25 0 0 0 21 19.75v-7.523a2.25 2.25 0 0 0-2.25-2.25zm-9.75 0V7a3 3 0 0 1 6 0v1.5l2.838-2.838A5.994 5.994 0 0 0 6 7v2.977h-.75A2.25 2.25 0 0 0 3 12.227v7.523a2.224 2.224 0 0 0 .105.645L13.523 9.977z"></path><path d="M2.5 23a1.5 1.5 0 0 1-1.061-2.561l19-19A1.5 1.5 0 0 1 22.56 3.56l-19 19A1.5 1.5 0 0 1 2.5 23z" fill="#ff0039"></path></svg> diff --git a/toolkit/components/httpsonlyerror/jar.mn b/toolkit/components/httpsonlyerror/jar.mn new file mode 100644 index 0000000000..f5b5c42030 --- /dev/null +++ b/toolkit/components/httpsonlyerror/jar.mn @@ -0,0 +1,8 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +toolkit.jar: + content/global/httpsonlyerror/errorpage.html (content/errorpage.html) + content/global/httpsonlyerror/errorpage.js (content/errorpage.js) + content/global/httpsonlyerror/secure-broken.svg (content/secure-broken.svg) diff --git a/toolkit/components/httpsonlyerror/moz.build b/toolkit/components/httpsonlyerror/moz.build new file mode 100644 index 0000000000..2eddec4a60 --- /dev/null +++ b/toolkit/components/httpsonlyerror/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ["jar.mn"] + +BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"] + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Security") diff --git a/toolkit/components/httpsonlyerror/tests/browser/browser.ini b/toolkit/components/httpsonlyerror/tests/browser/browser.ini new file mode 100644 index 0000000000..57611e73e5 --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/browser.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = + head.js +[browser_errorpage.js] +[browser_exception.js] +support-files = + file_upgrade_insecure_server.sjs +[browser_errorpage_timeout.js] +support-files = + file_errorpage_timeout_server.sjs diff --git a/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage.js b/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage.js new file mode 100644 index 0000000000..d0dd565714 --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage.js @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const SECURE_PAGE = "https://example.com/"; +const GOOD_PAGE = "http://example.com/"; +const BAD_CERT = "http://expired.example.com/"; +const UNKNOWN_ISSUER = "http://self-signed.example.com/"; + +const { TabStateFlusher } = ChromeUtils.import( + "resource:///modules/sessionstore/TabStateFlusher.jsm" +); + +add_task(async function() { + info("Check that the error pages shows up"); + + await Promise.all([ + testPageWithURI( + GOOD_PAGE, + "Should not show error page on upgradeable website.", + false + ), + testPageWithURI( + BAD_CERT, + "Should show error page on bad-certificate error.", + true + ), + testPageWithURI( + UNKNOWN_ISSUER, + "Should show error page on unkown-issuer error.", + true + ), + ]); +}); + +add_task(async function() { + info("Check that the go-back button returns to previous page"); + + // Test with and without being in an iFrame + for (let useFrame of [false, true]) { + let tab = await openErrorPage(BAD_CERT, useFrame); + let browser = tab.linkedBrowser; + + is( + browser.webNavigation.canGoBack, + false, + "!webNavigation.canGoBack should be false." + ); + is( + browser.webNavigation.canGoForward, + false, + "webNavigation.canGoForward should be false." + ); + + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + await TabStateFlusher.flush(browser); + let { entries } = JSON.parse(SessionStore.getTabState(tab)); + is(entries.length, 1, "There should be 1 shistory entry."); + + let bc = browser.browsingContext; + if (useFrame) { + bc = bc.children[0]; + } + + if (useFrame) { + await SpecialPowers.spawn(bc, [], async function() { + let returnButton = content.document.getElementById("goBack"); + is( + returnButton, + null, + "Return-button should not be present in iFrame." + ); + }); + } else { + let locationChangePromise = BrowserTestUtils.waitForLocationChange( + gBrowser, + "about:home" + ); + await SpecialPowers.spawn(bc, [], async function() { + let returnButton = content.document.getElementById("goBack"); + is( + returnButton.getAttribute("autofocus"), + "true", + "Return-button should have focus." + ); + returnButton.click(); + }); + + await locationChangePromise; + + is(browser.webNavigation.canGoBack, true, "webNavigation.canGoBack"); + is( + browser.webNavigation.canGoForward, + false, + "!webNavigation.canGoForward" + ); + is(gBrowser.currentURI.spec, "about:home", "Went back"); + } + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } +}); + +add_task(async function() { + info("Check that the go-back button returns to about:home"); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, SECURE_PAGE); + let browser = gBrowser.selectedBrowser; + + let errorPageLoaded = BrowserTestUtils.waitForErrorPage(browser); + BrowserTestUtils.loadURI(browser, BAD_CERT); + await errorPageLoaded; + + is( + browser.webNavigation.canGoBack, + true, + "webNavigation.canGoBack should be true before navigation." + ); + is( + browser.webNavigation.canGoForward, + false, + "webNavigation.canGoForward should be false before navigation." + ); + + // Populate the shistory entries manually, since it happens asynchronously + // and the following tests will be too soon otherwise. + await TabStateFlusher.flush(browser); + let { entries } = JSON.parse(SessionStore.getTabState(tab)); + is(entries.length, 2, "There should be 1 shistory entries."); + + let pageShownPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow", + true + ); + + // Click on "go back" Button + await SpecialPowers.spawn(browser, [], async function() { + let returnButton = content.document.getElementById("goBack"); + returnButton.click(); + }); + await pageShownPromise; + + is( + browser.webNavigation.canGoBack, + false, + "webNavigation.canGoBack should be false after navigation." + ); + is( + browser.webNavigation.canGoForward, + true, + "webNavigation.canGoForward should be true after navigation." + ); + is( + gBrowser.currentURI.spec, + SECURE_PAGE, + "Should go back to previous page after button click." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Utils + +async function testPageWithURI(uri, message, expect) { + // Open new Tab with URI + let tab; + if (expect) { + tab = await openErrorPage(uri, false); + } else { + tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri, true); + } + + // Check if HTTPS-Only Error-Page loaded instead + let browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [message, expect], function( + message, + expect + ) { + const doc = content.document; + let result = doc.documentURI.startsWith("about:httpsonlyerror"); + is(result, expect, message); + }); + + // Close tab again + BrowserTestUtils.removeTab(tab); +} diff --git a/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage_timeout.js b/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage_timeout.js new file mode 100644 index 0000000000..a3ae4a2bba --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/browser_errorpage_timeout.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// We need to request longer timeout because HTTPS-Only Mode sends the +// backround http request with a delay of N milliseconds before the +// actual load gets cancelled. +requestLongerTimeout(5); + +const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); +const TEST_PATH_HTTPS = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const TIMEOUT_PAGE_URI_HTTP = + TEST_PATH_HTTP + "file_errorpage_timeout_server.sjs"; +const TIMEOUT_PAGE_URI_HTTPS = + TEST_PATH_HTTPS + "file_errorpage_timeout_server.sjs"; + +add_task(async function avoid_timeout_and_show_https_only_error_page() { + await BrowserTestUtils.withNewTab("about:blank", async function(browser) { + let loaded = BrowserTestUtils.browserLoaded( + browser, + false, // includeSubFrames = false, no need to includeSubFrames + TIMEOUT_PAGE_URI_HTTPS, // Wait for upgraded page to timeout + true // maybeErrorPage = true, because we need the error page to appear + ); + BrowserTestUtils.loadURI(browser, TIMEOUT_PAGE_URI_HTTP); + await loaded; + + await SpecialPowers.spawn(browser, [], async function() { + const doc = content.document; + let errorPage = doc.body.innerHTML; + // It's possible that fluent has not been translated when running in + // chaos mode, hence let's rather use an element id for verification + // that the https-only mode error page has loaded. + ok( + errorPage.includes("about-httpsonly-button-continue-to-site"), + "Potential time-out in https-only mode should cause error page to appear!" + ); + }); + }); +}); diff --git a/toolkit/components/httpsonlyerror/tests/browser/browser_exception.js b/toolkit/components/httpsonlyerror/tests/browser/browser_exception.js new file mode 100644 index 0000000000..b680c13f42 --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/browser_exception.js @@ -0,0 +1,157 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const ROOT_PATH = getRootDirectory(gTestPath); +const EXPIRED_ROOT_PATH = ROOT_PATH.replace( + "chrome://mochitests/content", + "http://supports-insecure.expired.example.com" +); +const SECURE_ROOT_PATH = ROOT_PATH.replace( + "chrome://mochitests/content", + "http://example.com" +); +const INSECURE_ROOT_PATH = ROOT_PATH.replace( + "chrome://mochitests/content", + "http://example.com" +); + +// This is how this test works: +// +// +----[REQUEST] https://file_upgrade_insecure_server.sjs?queryresult +// | +// | +-[REQUEST] http://file_upgrade_insecure_server.sjs?content +// | | -> Internal HTTPS redirect +// | | +// | +>[RESPONSE] Expired Certificate Response +// | -> HTTPS-Only Mode Error Page shows up +// | -> Click exception button +// | +// | +-[REQUEST] http://file_upgrade_insecure_server.sjs?content +// | | +// | +>[RESPONSE] Webpage with a bunch of sub-resources +// | -> http://file_upgrade_insecure_ser^er.sjs?img +// | -> http://file_upgrade_insecure_server.sjs?xhr +// | -> http://file_upgrade_insecure_server.sjs?iframe +// | -> etc. +// | +// +--->[RESPONSE] List of all recorded requests and whether they were loaded +// with HTTP or not (eg.: img-ok, xhr-ok, iframe-error, ...) + +add_task(async function() { + const testCases = ["default", "private", "firstpartyisolation"]; + for (let i = 0; i < testCases.length; i++) { + // Call sjs-file with setup query-string and store promise + let expectedQueries = new Set([ + "content", + "img", + "iframe", + "xhr", + "nestedimg", + ]); + + const filesLoaded = setupFileServer(); + // Since we don't know when the server has saved all it's variables, + // let's wait a bit before reloading the page. + await new Promise(resolve => executeSoon(resolve)); + + // Create a new private window but reuse the normal one. + let privateWindow = false; + if (testCases[i] === "private") { + privateWindow = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + } else if (testCases[i] === "firstpartyisolation") { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.firstparty.isolate", true]], + }); + } + + // Create new tab with sjs-file requesting content. + // "supports-insecure.expired.example.com" responds to http and https but + // with an expired certificate + let tab = await openErrorPage( + `${EXPIRED_ROOT_PATH}file_upgrade_insecure_server.sjs?content`, + false, + privateWindow + ); + let browser = tab.linkedBrowser; + + let pageShownPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow", + true + ); + + // click on exception-button and wait for page to load + await SpecialPowers.spawn(browser, [], async function() { + let openInsecureButton = content.document.getElementById("openInsecure"); + ok(openInsecureButton != null, "openInsecureButton should exist."); + openInsecureButton.click(); + }); + + await pageShownPromise; + + // Check if the original page got loaded with http this time + await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + ok( + !doc.documentURI.startsWith("http://expired.example.com"), + "Page should load normally after exception button was clicked." + ); + }); + + // Wait for initial sjs request to resolve + let results = await filesLoaded; + + for (let resultIndex in results) { + const response = results[resultIndex]; + // A response looks either like this "iframe-ok" or "[key]-[result]" + const [key, result] = response.split("-", 2); + // try to find the expected result within the results array + if (expectedQueries.has(key)) { + expectedQueries.delete(key); + is(result, "ok", `Request '${key}' should be loaded with HTTP.'`); + } else { + ok(false, `Unexpected response from server (${response})`); + } + } + + // Clean up permissions, tab and potentially preferences + Services.perms.removeAll(); + + if (testCases[i] === "firstpartyisolation") { + await SpecialPowers.popPrefEnv(); + } + + if (privateWindow) { + await BrowserTestUtils.closeWindow(privateWindow); + } else { + gBrowser.removeCurrentTab(); + } + } +}); + +function setupFileServer() { + // We initialize the upgrade-server with the queryresult query-string. + // We'll get a response once all files have been requested and then + // can see if they have been requested with http. + return new Promise((resolve, reject) => { + var xhrRequest = new XMLHttpRequest(); + xhrRequest.open( + "GET", + `${SECURE_ROOT_PATH}file_upgrade_insecure_server.sjs?queryresult=${INSECURE_ROOT_PATH}` + ); + xhrRequest.onload = function(e) { + var results = xhrRequest.responseText.split(","); + resolve(results); + }; + xhrRequest.onerror = e => { + ok(false, "Could not query results from server (" + e.message + ")"); + reject(); + }; + xhrRequest.send(); + }); +} diff --git a/toolkit/components/httpsonlyerror/tests/browser/file_errorpage_timeout_server.sjs b/toolkit/components/httpsonlyerror/tests/browser/file_errorpage_timeout_server.sjs new file mode 100644 index 0000000000..0f22b6e6df --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/file_errorpage_timeout_server.sjs @@ -0,0 +1,16 @@ +// Custom *.sjs file specifically for the needs of Bug 1657348 + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + if (request.scheme === "https") { + // Simulating a timeout by processing the https request + // async and *never* return anything! + response.processAsync(); + return; + } + // we should never get here; just in case, return something unexpected + response.write("do'h"); +} diff --git a/toolkit/components/httpsonlyerror/tests/browser/file_upgrade_insecure_server.sjs b/toolkit/components/httpsonlyerror/tests/browser/file_upgrade_insecure_server.sjs new file mode 100644 index 0000000000..1f3566c3b8 --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/file_upgrade_insecure_server.sjs @@ -0,0 +1,108 @@ +// Serverside Javascript for browser_exception.js +// Bug 1625156 - Error page for HTTPS Only Mode + +const expectedQueries = ["content", "img", "iframe", "xhr", "nestedimg"]; +const TOTAL_EXPECTED_REQUESTS = expectedQueries.length; + +const CONTENT = path => ` +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'> + </head> + <body> + <p>Insecure website</p> + <script type="application/javascript"> + var myXHR = new XMLHttpRequest(); + myXHR.open("GET", "${path}file_upgrade_insecure_server.sjs?xhr"); + myXHR.send(null); + </script> + <img src='${path}file_upgrade_insecure_server.sjs?img'></img> + <iframe src="${path}file_upgrade_insecure_server.sjs?iframe"></iframe> + </body> +</html>`; + +const IFRAME_CONTENT = path => ` +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'> + </head> + <body> + <p>Nested insecure website</p> + <img src='${path}file_upgrade_insecure_server.sjs?nestedimg'></img> + </body> +</html>`; + +function handleRequest(request, response) { + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + var queryString = request.queryString; + + // initialize server variables and save the object state + // of the initial request, which returns async once the + // server has processed all requests. + if (queryString.startsWith("queryresult")) { + response.processAsync(); + setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString()); + setState("receivedQueries", ""); + setState("rootPath", /=(.+)/.exec(queryString)[1]); + setObjectState("queryResult", response); + return; + } + + // just in case error handling for unexpected queries + if (!expectedQueries.includes(queryString)) { + response.write("unexpected-response"); + return; + } + + // make sure all the requested queries are indeed http + const testResult = + queryString + (request.scheme == "http" ? "-ok" : "-error"); + + var receivedQueries = getState("receivedQueries"); + + // images, scripts, etc. get queried twice, do not + // confuse the server by storing the preload as + // well as the actual load. If either the preload + // or the actual load is not https, then we would + // append "-error" in the array and the test would + // fail at the end. + if (receivedQueries.includes(testResult)) { + return; + } + + // append the result to the total query string array + if (receivedQueries != "") { + receivedQueries += ","; + } + receivedQueries += testResult; + setState("receivedQueries", receivedQueries); + + // keep track of how many more requests the server + // is expecting + var totaltests = parseInt(getState("totaltests")); + totaltests -= 1; + setState("totaltests", totaltests.toString()); + + // Respond with html content + if (queryString == "content") { + response.write(CONTENT(getState("rootPath"))); + } else if (queryString == "iframe") { + response.write(IFRAME_CONTENT(getState("rootPath"))); + } + + // if we have received all the requests, we return + // the result back. + if (totaltests == 0) { + getObjectState("queryResult", function(queryResponse) { + if (!queryResponse) { + return; + } + var receivedQueries = getState("receivedQueries"); + queryResponse.write(receivedQueries); + queryResponse.finish(); + }); + } +} diff --git a/toolkit/components/httpsonlyerror/tests/browser/head.js b/toolkit/components/httpsonlyerror/tests/browser/head.js new file mode 100644 index 0000000000..6848401f61 --- /dev/null +++ b/toolkit/components/httpsonlyerror/tests/browser/head.js @@ -0,0 +1,67 @@ +/* eslint-env mozilla/frame-script */ + +// Enable HTTPS-Only Mode +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_only_mode", true]], + }); +}); + +// Copied from: https://searchfox.org/mozilla-central/rev/9f074fab9bf905fad62e7cc32faf121195f4ba46/browser/base/content/test/about/head.js + +async function injectErrorPageFrame(tab, src, sandboxed) { + let loadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + true, + null, + true + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [src, sandboxed], async function( + frameSrc, + frameSandboxed + ) { + let iframe = content.document.createElement("iframe"); + iframe.src = frameSrc; + if (frameSandboxed) { + iframe.setAttribute("sandbox", "allow-scripts"); + } + content.document.body.appendChild(iframe); + }); + + await loadedPromise; +} + +async function openErrorPage(src, useFrame, privateWindow, sandboxed) { + let gb = gBrowser; + if (privateWindow) { + gb = privateWindow.gBrowser; + } + let dummyPage = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "dummy_page.html"; + + let tab; + if (useFrame) { + info("Loading error page in an iframe"); + tab = await BrowserTestUtils.openNewForegroundTab(gb, dummyPage); + await injectErrorPageFrame(tab, src, sandboxed); + } else { + let ErrorPageLoaded; + tab = await BrowserTestUtils.openNewForegroundTab( + gb, + () => { + gb.selectedTab = BrowserTestUtils.addTab(gb, src); + let browser = gb.selectedBrowser; + ErrorPageLoaded = BrowserTestUtils.waitForErrorPage(browser); + }, + false + ); + info("Loading and waiting for the error page"); + await ErrorPageLoaded; + } + + return tab; +} |