summaryrefslogtreecommitdiffstats
path: root/dom/security/test/https-only
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/security/test/https-only/browser.toml62
-rw-r--r--dom/security/test/https-only/browser_background_redirect.js64
-rw-r--r--dom/security/test/https-only/browser_bug1874801.js56
-rw-r--r--dom/security/test/https-only/browser_console_logging.js153
-rw-r--r--dom/security/test/https-only/browser_continue_button_delay.js59
-rw-r--r--dom/security/test/https-only/browser_cors_mixedcontent.js127
-rw-r--r--dom/security/test/https-only/browser_hsts_host.js203
-rw-r--r--dom/security/test/https-only/browser_httpsonly_prefs.js118
-rw-r--r--dom/security/test/https-only/browser_httpsonly_speculative_connect.js71
-rw-r--r--dom/security/test/https-only/browser_iframe_test.js223
-rw-r--r--dom/security/test/https-only/browser_navigation.js94
-rw-r--r--dom/security/test/https-only/browser_redirect_tainting.js39
-rw-r--r--dom/security/test/https-only/browser_save_as.js187
-rw-r--r--dom/security/test/https-only/browser_triggering_principal_exemption.js72
-rw-r--r--dom/security/test/https-only/browser_upgrade_exceptions.js86
-rw-r--r--dom/security/test/https-only/browser_upgrade_exemption.js80
-rw-r--r--dom/security/test/https-only/browser_user_gesture.js55
-rw-r--r--dom/security/test/https-only/browser_websocket_exceptions.js68
-rw-r--r--dom/security/test/https-only/file_background_redirect.sjs32
-rw-r--r--dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs67
-rw-r--r--dom/security/test/https-only/file_bug1874801.html11
-rw-r--r--dom/security/test/https-only/file_bug1874801.sjs17
-rw-r--r--dom/security/test/https-only/file_console_logging.html16
-rw-r--r--dom/security/test/https-only/file_cors_mixedcontent.html42
-rw-r--r--dom/security/test/https-only/file_fragment.html47
-rw-r--r--dom/security/test/https-only/file_fragment_noscript.html9
-rw-r--r--dom/security/test/https-only/file_http_background_auth_request.sjs16
-rw-r--r--dom/security/test/https-only/file_http_background_request.sjs15
-rw-r--r--dom/security/test/https-only/file_httpsonly_speculative_connect.html1
-rw-r--r--dom/security/test/https-only/file_iframe_test.sjs58
-rw-r--r--dom/security/test/https-only/file_insecure_reload.sjs27
-rw-r--r--dom/security/test/https-only/file_redirect.sjs37
-rw-r--r--dom/security/test/https-only/file_redirect_tainting.sjs39
-rw-r--r--dom/security/test/https-only/file_redirect_to_insecure.sjs16
-rw-r--r--dom/security/test/https-only/file_save_as.html10
-rw-r--r--dom/security/test/https-only/file_upgrade_insecure.html91
-rw-r--r--dom/security/test/https-only/file_upgrade_insecure_server.sjs112
-rw-r--r--dom/security/test/https-only/file_upgrade_insecure_wsh.py6
-rw-r--r--dom/security/test/https-only/file_user_gesture.html11
-rw-r--r--dom/security/test/https-only/file_websocket_exceptions.html9
-rw-r--r--dom/security/test/https-only/file_websocket_exceptions_iframe.html30
-rw-r--r--dom/security/test/https-only/hsts_headers.sjs24
-rw-r--r--dom/security/test/https-only/mochitest.toml65
-rw-r--r--dom/security/test/https-only/test_break_endless_upgrade_downgrade_loop.html89
-rw-r--r--dom/security/test/https-only/test_fragment.html72
-rw-r--r--dom/security/test/https-only/test_http_background_auth_request.html113
-rw-r--r--dom/security/test/https-only/test_http_background_request.html125
-rw-r--r--dom/security/test/https-only/test_insecure_reload.html74
-rw-r--r--dom/security/test/https-only/test_redirect_upgrade.html58
-rw-r--r--dom/security/test/https-only/test_resource_upgrade.html122
-rw-r--r--dom/security/test/https-only/test_user_suggestion_box.html81
51 files changed, 3359 insertions, 0 deletions
diff --git a/dom/security/test/https-only/browser.toml b/dom/security/test/https-only/browser.toml
new file mode 100644
index 0000000000..2cba418aff
--- /dev/null
+++ b/dom/security/test/https-only/browser.toml
@@ -0,0 +1,62 @@
+[DEFAULT]
+prefs = ["dom.security.https_first=false"]
+
+["browser_background_redirect.js"]
+support-files = ["file_background_redirect.sjs"]
+
+["browser_bug1874801.js"]
+support-files = [
+ "file_bug1874801.sjs",
+ "file_bug1874801.html",
+]
+
+["browser_console_logging.js"]
+support-files = ["file_console_logging.html"]
+
+["browser_continue_button_delay.js"]
+
+["browser_cors_mixedcontent.js"]
+support-files = ["file_cors_mixedcontent.html"]
+
+["browser_hsts_host.js"]
+support-files = [
+ "hsts_headers.sjs",
+ "file_fragment_noscript.html",
+]
+
+["browser_httpsonly_prefs.js"]
+
+["browser_httpsonly_speculative_connect.js"]
+support-files = ["file_httpsonly_speculative_connect.html"]
+
+["browser_iframe_test.js"]
+skip-if = [
+ "os == 'linux' && bits == 64", # Bug 1735565
+ "os == 'win' && bits == 64", # Bug 1735565
+]
+support-files = ["file_iframe_test.sjs"]
+
+["browser_navigation.js"]
+support-files = ["file_redirect_to_insecure.sjs"]
+
+["browser_redirect_tainting.js"]
+support-files = ["file_redirect_tainting.sjs"]
+
+["browser_save_as.js"]
+support-files = ["file_save_as.html"]
+
+["browser_triggering_principal_exemption.js"]
+
+["browser_upgrade_exceptions.js"]
+
+["browser_upgrade_exemption.js"]
+
+["browser_user_gesture.js"]
+support-files = ["file_user_gesture.html"]
+
+["browser_websocket_exceptions.js"]
+skip-if = ["os == 'android'"] # WebSocket tests are not supported on Android Yet. Bug 1566168.
+support-files = [
+ "file_websocket_exceptions.html",
+ "file_websocket_exceptions_iframe.html",
+]
diff --git a/dom/security/test/https-only/browser_background_redirect.js b/dom/security/test/https-only/browser_background_redirect.js
new file mode 100644
index 0000000000..2907278943
--- /dev/null
+++ b/dom/security/test/https-only/browser_background_redirect.js
@@ -0,0 +1,64 @@
+"use strict";
+/* Description of the test:
+ * We load a page which gets upgraded to https. HTTPS-Only Mode then
+ * sends an 'http' background request which we redirect (using the
+ * web extension API) to 'same-origin' https. We ensure the HTTPS-Only
+ * Error page does not occur, but we https page gets loaded.
+ */
+
+let extension = null;
+
+add_task(async function test_https_only_background_request_redirect() {
+ // A longer timeout is necessary for this test since we have to wait
+ // at least 3 seconds for the https-only background request to happen.
+ requestLongerTimeout(10);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["webRequest", "webRequestBlocking", "http://example.com/*"],
+ },
+ background() {
+ let { browser } = this;
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ if (details.url === "http://example.com/") {
+ browser.test.sendMessage("redir-handled");
+ let redirectUrl = "https://example.com/";
+ return { redirectUrl };
+ }
+ return undefined;
+ },
+ { urls: ["http://example.com/*"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ "http://example.com/browser/dom/security/test/https-only/file_background_redirect.sjs?start"
+ );
+
+ await extension.awaitMessage("redir-handled");
+
+ await loaded;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ let innerHTML = content.document.body.innerHTML;
+ ok(
+ innerHTML.includes("Test Page for Bug 1683015 loaded"),
+ "No https-only error page"
+ );
+ });
+ });
+
+ await extension.unload();
+});
diff --git a/dom/security/test/https-only/browser_bug1874801.js b/dom/security/test/https-only/browser_bug1874801.js
new file mode 100644
index 0000000000..280a072736
--- /dev/null
+++ b/dom/security/test/https-only/browser_bug1874801.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Specifically test https://bugzilla.mozilla.org/show_bug.cgi?id=1874801
+
+const TAB_URL =
+ "https://example.com/browser/dom/security/test/https-only/file_bug1874801.html";
+
+function assertImageLoaded(tab) {
+ return ContentTask.spawn(tab.linkedBrowser, {}, () => {
+ const img = content.document.getElementsByTagName("img")[0];
+
+ ok(!!img, "Image tag should exist");
+ ok(img.complete && img.naturalWidth > 0, "Image should have loaded ");
+ });
+}
+
+add_task(async function test_bug1874801() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.mixed_content.upgrade_display_content", false],
+ ["dom.security.https_first", true],
+ ["dom.security.https_only_mode", true],
+ ],
+ });
+
+ // Open Tab
+ const tabToClose = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TAB_URL,
+ true
+ );
+
+ // Make sure the image was loaded via HTTPS
+ await assertImageLoaded(tabToClose);
+
+ // Close Tab
+ const tabClosePromise =
+ BrowserTestUtils.waitForSessionStoreUpdate(tabToClose);
+ BrowserTestUtils.removeTab(tabToClose);
+ await tabClosePromise;
+
+ // Restore Tab
+ const restoredTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ TAB_URL,
+ true
+ );
+ undoCloseTab();
+ const restoredTab = await restoredTabPromise;
+
+ // Make sure the image was loaded via HTTPS
+ await assertImageLoaded(restoredTab);
+});
diff --git a/dom/security/test/https-only/browser_console_logging.js b/dom/security/test/https-only/browser_console_logging.js
new file mode 100644
index 0000000000..d648c27289
--- /dev/null
+++ b/dom/security/test/https-only/browser_console_logging.js
@@ -0,0 +1,153 @@
+// Bug 1625448 - HTTPS Only Mode - Tests for console logging
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1625448
+// This test makes sure that the various console messages from the HTTPS-Only
+// mode get logged to the console.
+"use strict";
+
+// Test Cases
+// description: Description of what the subtests expects.
+// expectLogLevel: Expected log-level of a message.
+// expectIncludes: Expected substrings the message should contain.
+let tests = [
+ {
+ description: "Top-Level upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "HTTPS-Only Mode: Upgrading insecure request",
+ "to use",
+ "file_console_logging.html",
+ ],
+ },
+ {
+ description: "iFrame upgrade failure should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.error,
+ expectIncludes: [
+ "HTTPS-Only Mode: Upgrading insecure request",
+ "failed",
+ "file_console_logging.html",
+ ],
+ },
+ {
+ description: "WebSocket upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "HTTPS-Only Mode: Upgrading insecure request",
+ "to use",
+ "ws://does.not.exist",
+ ],
+ },
+ {
+ description: "Sub-Resource upgrade for file_1 should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: ["Upgrading insecure", "request", "file_1.jpg"],
+ },
+ {
+ description: "Sub-Resource upgrade for file_2 should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: ["Upgrading insecure", "request", "to use", "file_2.jpg"],
+ },
+ {
+ description: "Exempt request for file_exempt should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.info,
+ expectIncludes: [
+ "Not upgrading insecure request",
+ "because it is exempt",
+ "file_exempt.jpg",
+ ],
+ },
+ {
+ description: "Sub-Resource upgrade failure for file_2 should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.error,
+ expectIncludes: ["Upgrading insecure request", "failed", "file_2.jpg"],
+ },
+];
+
+const testPathUpgradeable = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+// DNS errors are not logged as HTTPS-Only Mode upgrade failures, so we have to
+// upgrade to a domain that exists but fails.
+const testPathNotUpgradeable = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://self-signed.example.com"
+);
+const kTestURISuccess = testPathUpgradeable + "file_console_logging.html";
+const kTestURIFail = testPathNotUpgradeable + "file_console_logging.html";
+const kTestURIExempt = testPathUpgradeable + "file_exempt.jpg";
+
+const UPGRADE_DISPLAY_CONTENT =
+ "security.mixed_content.upgrade_display_content";
+
+add_task(async function () {
+ // A longer timeout is necessary for this test than the plain mochitests
+ // due to opening a new tab with the web console.
+ requestLongerTimeout(4);
+
+ // Enable HTTPS-Only Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+ Services.console.registerListener(on_new_message);
+ // 1. Upgrade page to https://
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ kTestURISuccess
+ );
+ // 2. Make an exempt http:// request
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", kTestURIExempt, true);
+ xhr.channel.loadInfo.httpsOnlyStatus |= Ci.nsILoadInfo.HTTPS_ONLY_EXEMPT;
+ xhr.send();
+ // 3. Make Websocket request
+ new WebSocket("ws://does.not.exist");
+
+ await BrowserTestUtils.waitForCondition(() => tests.length === 0);
+
+ // Clean up
+ Services.console.unregisterListener(on_new_message);
+});
+
+function on_new_message(msgObj) {
+ const message = msgObj.message;
+ const logLevel = msgObj.logLevel;
+
+ // Bools about message and pref
+ const isMCL2Enabled = Services.prefs.getBoolPref(UPGRADE_DISPLAY_CONTENT);
+ const isHTTPSOnlyModeLog = message.includes("HTTPS-Only Mode:");
+ const isMCLog = message.includes("Mixed Content:");
+
+ // Check for messages about HTTPS-only upgrades (those should be unrelated to mixed content upgrades)
+ // or for mixed content upgrades which should only occur if security.mixed_content.upgrade_display_content is enabled
+ // (unrelated to https-only logs).
+ if (
+ (isHTTPSOnlyModeLog && !isMCLog) ||
+ (isMCLog && isMCL2Enabled && !isHTTPSOnlyModeLog)
+ ) {
+ for (let i = 0; i < tests.length; i++) {
+ const testCase = tests[i];
+ // If security.mixed_content.upgrade_display_content is enabled, the mixed content control mechanism is upgrading file2.jpg
+ // and HTTPS-Only mode is not failing upgrading file2.jpg, so it won't be logged.
+ // so skip last test case
+ if (
+ testCase.description ==
+ "Sub-Resource upgrade failure for file_2 should get logged" &&
+ isMCL2Enabled
+ ) {
+ tests.splice(i, 1);
+ continue;
+ }
+ // Check if log-level matches
+ if (logLevel !== testCase.expectLogLevel) {
+ continue;
+ }
+ // Check if all substrings are included
+ if (testCase.expectIncludes.some(str => !message.includes(str))) {
+ continue;
+ }
+ ok(true, testCase.description);
+ tests.splice(i, 1);
+ break;
+ }
+ }
+}
diff --git a/dom/security/test/https-only/browser_continue_button_delay.js b/dom/security/test/https-only/browser_continue_button_delay.js
new file mode 100644
index 0000000000..6bdee1610e
--- /dev/null
+++ b/dom/security/test/https-only/browser_continue_button_delay.js
@@ -0,0 +1,59 @@
+"use strict";
+
+function waitForEnabledButton() {
+ return new Promise(resolve => {
+ const button = content.document.getElementById("openInsecure");
+ const observer = new content.MutationObserver(mutations => {
+ for (const mutation of mutations) {
+ if (
+ mutation.type === "attributes" &&
+ mutation.attributeName === "inert" &&
+ !mutation.target.inert
+ ) {
+ resolve();
+ }
+ }
+ });
+ observer.observe(button, { attributeFilter: ["inert"] });
+ ok(
+ button.inert,
+ "The 'Continue to HTTP Site' button should be inert right after the error page is loaded."
+ );
+ });
+}
+
+add_task(async function () {
+ waitForExplicitFinish();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ const specifiedDelay = Services.prefs.getIntPref(
+ "security.dialog_enable_delay",
+ 1000
+ );
+
+ let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ info("Loading insecure page");
+ const startTime = Date.now();
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser,
+ // We specifically want a insecure url here that will fail to upgrade
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://untrusted.example.com:80"
+ );
+ await loaded;
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], waitForEnabledButton);
+ const endTime = Date.now();
+
+ const observedDelay = endTime - startTime;
+
+ Assert.greater(
+ observedDelay,
+ specifiedDelay - 100,
+ `The observed delay (${observedDelay}ms) should be roughly the same or greater than the delay specified in "security.dialog_enable_delay" (${specifiedDelay}ms)`
+ );
+
+ finish();
+});
diff --git a/dom/security/test/https-only/browser_cors_mixedcontent.js b/dom/security/test/https-only/browser_cors_mixedcontent.js
new file mode 100644
index 0000000000..fb78d66979
--- /dev/null
+++ b/dom/security/test/https-only/browser_cors_mixedcontent.js
@@ -0,0 +1,127 @@
+// Bug 1659505 - Https-Only: CORS and MixedContent tests
+// https://bugzilla.mozilla.org/bug/1659505
+"use strict";
+
+// > How does this test work?
+// We open a page, that makes two fetch-requests to example.com (same-origin)
+// and example.org (cross-origin). When both fetch-calls have either failed or
+// succeeded, the site dispatches an event with the results.
+
+add_task(async function () {
+ // HTTPS-Only Mode disabled
+ await runTest({
+ description: "Load site with HTTP and HOM disabled",
+ topLevelScheme: "http",
+
+ expectedSameOrigin: "success", // ok
+ expectedCrossOrigin: "error", // CORS
+ });
+ await runTest({
+ description: "Load site with HTTPS and HOM disabled",
+ topLevelScheme: "https",
+
+ expectedSameOrigin: "error", // Mixed Content
+ expectedCrossOrigin: "error", // Mixed Content
+ });
+
+ // HTTPS-Only Mode disabled and MixedContent blocker disabled
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.mixed_content.block_active_content", false]],
+ });
+ await runTest({
+ description: "Load site with HTTPS; HOM and MixedContent blocker disabled",
+ topLevelScheme: "https",
+
+ expectedSameOrigin: "error", // CORS
+ expectedCrossOrigin: "error", // CORS
+ });
+ await SpecialPowers.popPrefEnv();
+
+ // HTTPS-Only Mode enabled, no exception
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+ await runTest({
+ description: "Load site with HTTP and HOM enabled",
+ topLevelScheme: "http",
+
+ expectedSameOrigin: "success", // ok
+ expectedCrossOrigin: "error", // CORS
+ });
+
+ // HTTPS-Only enabled, with exception
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "http://example.com",
+ },
+ ]);
+
+ await runTest({
+ description: "Load site with HTTP, HOM enabled but site exempt",
+ topLevelScheme: "http",
+
+ expectedSameOrigin: "success", // ok
+ expectedCrossOrigin: "error", // CORS
+ });
+
+ await SpecialPowers.popPermissions();
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "https://example.com",
+ },
+ ]);
+ await runTest({
+ description: "Load site with HTTPS, HOM enabled but site exempt",
+ topLevelScheme: "https",
+
+ expectedSameOrigin: "error", // Mixed Content
+ expectedCrossOrigin: "error", // Mixed Content
+ });
+
+ // Remove permission again (has to be done manually for some reason?)
+ await SpecialPowers.popPermissions();
+});
+
+const SERVER_URL = scheme =>
+ `${scheme}://example.com/browser/dom/security/test/https-only/file_cors_mixedcontent.html`;
+
+async function runTest(test) {
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ SERVER_URL(test.topLevelScheme)
+ );
+
+ await loaded;
+
+ // eslint-disable-next-line no-shadow
+ await SpecialPowers.spawn(browser, [test], async function (test) {
+ const promise = new Promise(resolve => {
+ content.addEventListener("FetchEnded", resolve, {
+ once: true,
+ });
+ });
+
+ content.dispatchEvent(new content.Event("StartFetch"));
+
+ const { detail } = await promise;
+
+ is(
+ detail.comResult,
+ test.expectedSameOrigin,
+ `${test.description} (same-origin)`
+ );
+ is(
+ detail.orgResult,
+ test.expectedCrossOrigin,
+ `${test.description} (cross-origin)`
+ );
+ });
+ });
+}
diff --git a/dom/security/test/https-only/browser_hsts_host.js b/dom/security/test/https-only/browser_hsts_host.js
new file mode 100644
index 0000000000..858c19865c
--- /dev/null
+++ b/dom/security/test/https-only/browser_hsts_host.js
@@ -0,0 +1,203 @@
+// Bug 1722489 - HTTPS-Only Mode - Tests evaluation order
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1722489
+// This test ensures that an http request to an hsts host
+// gets upgraded by hsts and not by https-only.
+"use strict";
+
+// Set bools to track that tests ended.
+let readMessage = false;
+let testFinished = false;
+// Visit a secure site that sends an HSTS header to set up the rest of the
+// test.
+add_task(async function see_hsts_header() {
+ let setHstsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers.sjs";
+ Services.obs.addObserver(observer, "http-on-examine-response");
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ setHstsUrl
+ );
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, setHstsUrl);
+ await promiseLoaded;
+
+ await BrowserTestUtils.waitForCondition(() => readMessage);
+ // Clean up
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+});
+
+// Test that HTTPS_Only is not performed if HSTS host is visited.
+add_task(async function () {
+ // A longer timeout is necessary for this test than the plain mochitests
+ // due to opening a new tab with the web console.
+ requestLongerTimeout(4);
+
+ // Enable HTTPS-Only Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ Services.console.registerListener(onNewMessage);
+ const RESOURCE_LINK =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ ) + "hsts_headers.sjs";
+
+ // 1. Upgrade page to https://
+ let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ RESOURCE_LINK
+ );
+ await promiseLoaded;
+
+ await BrowserTestUtils.waitForCondition(() => testFinished);
+
+ // Clean up
+ Services.console.unregisterListener(onNewMessage);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test that when clicking on #fragment with a different scheme (http vs https)
+// DOES cause an actual navigation with HSTS, even though https-only mode is
+// enabled.
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_only_mode", true],
+ [
+ "dom.security.https_only_mode_break_upgrade_downgrade_endless_loop",
+ false,
+ ],
+ ],
+ });
+
+ const TEST_PAGE =
+ "http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ const UPGRADED_URL = TEST_PAGE.replace("http:", "https:");
+
+ await SpecialPowers.spawn(browser, [UPGRADED_URL], async function (url) {
+ is(content.window.location.href, url);
+
+ content.window.addEventListener("scroll", () => {
+ ok(false, "scroll event should not trigger");
+ });
+
+ let beforeUnload = new Promise(resolve => {
+ content.window.addEventListener("beforeunload", resolve, {
+ once: true,
+ });
+ });
+
+ content.window.document.querySelector("#clickMeButton").click();
+
+ // Wait for unload event.
+ await beforeUnload;
+ });
+
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(browser, [UPGRADED_URL], async function (url) {
+ is(content.window.location.href, url + "#foo");
+ });
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function () {
+ // Reset HSTS header
+ readMessage = false;
+ let clearHstsUrl =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "hsts_headers.sjs?reset";
+
+ Services.obs.addObserver(observer, "http-on-examine-response");
+ // reset hsts header
+ let promiseLoaded = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ clearHstsUrl
+ );
+ await BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ clearHstsUrl
+ );
+ await promiseLoaded;
+ await BrowserTestUtils.waitForCondition(() => readMessage);
+ // Clean up
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+});
+
+function observer(subject, topic, state) {
+ info("observer called with " + topic);
+ if (topic == "http-on-examine-response") {
+ onExamineResponse(subject);
+ }
+}
+
+function onExamineResponse(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ // If message was already read or is not related to "example.com",
+ // don't examine it.
+ if (!channel.URI.spec.includes("example.com") || readMessage) {
+ return;
+ }
+ info("onExamineResponse with " + channel.URI.spec);
+ if (channel.URI.spec.includes("reset")) {
+ try {
+ let hsts = channel.getResponseHeader("Strict-Transport-Security");
+ is(hsts, "max-age=0", "HSTS header is not set");
+ } catch (e) {
+ ok(false, "HSTS header still set");
+ }
+ readMessage = true;
+ return;
+ }
+ try {
+ let hsts = channel.getResponseHeader("Strict-Transport-Security");
+ let csp = channel.getResponseHeader("Content-Security-Policy");
+ // Check that HSTS and CSP upgrade headers are set
+ is(hsts, "max-age=60", "HSTS header is set");
+ is(csp, "upgrade-insecure-requests", "CSP header is set");
+ } catch (e) {
+ ok(false, "No header set");
+ }
+ readMessage = true;
+}
+
+function onNewMessage(msgObj) {
+ const message = msgObj.message;
+ // ensure that request is not upgraded HTTPS-Only.
+ if (message.includes("Upgrading insecure request")) {
+ ok(false, "Top-Level upgrade shouldn't get logged");
+ testFinished = true;
+ } else if (
+ message.includes("Upgrading insecure speculative TCP connection")
+ ) {
+ // TODO: Check assertion
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1735683
+ ok(true, "Top-Level upgrade shouldn't get logged");
+ testFinished = true;
+ } else if (gBrowser.selectedBrowser.currentURI.scheme === "https") {
+ ok(true, "Top-Level upgrade shouldn't get logged");
+ testFinished = true;
+ }
+}
diff --git a/dom/security/test/https-only/browser_httpsonly_prefs.js b/dom/security/test/https-only/browser_httpsonly_prefs.js
new file mode 100644
index 0000000000..467ab490e7
--- /dev/null
+++ b/dom/security/test/https-only/browser_httpsonly_prefs.js
@@ -0,0 +1,118 @@
+"use strict";
+
+async function runPrefTest(
+ aHTTPSOnlyPref,
+ aHTTPSOnlyPrefPBM,
+ aExecuteFromPBM,
+ aDesc,
+ aAssertURLStartsWith
+) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_only_mode", aHTTPSOnlyPref],
+ ["dom.security.https_only_mode_pbm", aHTTPSOnlyPrefPBM],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ await ContentTask.spawn(
+ browser,
+ { aExecuteFromPBM, aDesc, aAssertURLStartsWith },
+ // eslint-disable-next-line no-shadow
+ async function ({ aExecuteFromPBM, aDesc, aAssertURLStartsWith }) {
+ const responseURL = await new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.timeout = 1200;
+ xhr.open("GET", "http://example.com");
+ if (aExecuteFromPBM) {
+ xhr.channel.loadInfo.originAttributes = {
+ privateBrowsingId: 1,
+ };
+ }
+ xhr.onreadystatechange = () => {
+ // We don't care about the result and it's possible that
+ // the requests might even succeed in some testing environments
+ if (
+ xhr.readyState !== XMLHttpRequest.OPENED ||
+ xhr.readyState !== XMLHttpRequest.UNSENT
+ ) {
+ // Let's make sure this function does not get called anymore
+ xhr.onreadystatechange = undefined;
+ resolve(xhr.responseURL);
+ }
+ };
+ xhr.send();
+ });
+ ok(responseURL.startsWith(aAssertURLStartsWith), aDesc);
+ }
+ );
+ });
+}
+
+add_task(async function () {
+ requestLongerTimeout(2);
+
+ await runPrefTest(
+ false,
+ false,
+ false,
+ "Setting no prefs should not upgrade",
+ "http://"
+ );
+
+ await runPrefTest(
+ true,
+ false,
+ false,
+ "Setting aHTTPSOnlyPref should upgrade",
+ "https://"
+ );
+
+ await runPrefTest(
+ false,
+ true,
+ false,
+ "Setting aHTTPSOnlyPrefPBM should not upgrade",
+ "http://"
+ );
+
+ await runPrefTest(
+ false,
+ false,
+ true,
+ "Setting aPBM should not upgrade",
+ "http://"
+ );
+
+ await runPrefTest(
+ true,
+ true,
+ false,
+ "Setting aHTTPSOnlyPref and aHTTPSOnlyPrefPBM should should upgrade",
+ "https://"
+ );
+
+ await runPrefTest(
+ true,
+ false,
+ true,
+ "Setting aHTTPSOnlyPref and aPBM should upgrade",
+ "https://"
+ );
+
+ await runPrefTest(
+ false,
+ true,
+ true,
+ "Setting aHTTPSOnlyPrefPBM and aPBM should upgrade",
+ "https://"
+ );
+
+ await runPrefTest(
+ true,
+ true,
+ true,
+ "Setting aHTTPSOnlyPref and aHTTPSOnlyPrefPBM and aPBM should upgrade",
+ "https://"
+ );
+});
diff --git a/dom/security/test/https-only/browser_httpsonly_speculative_connect.js b/dom/security/test/https-only/browser_httpsonly_speculative_connect.js
new file mode 100644
index 0000000000..d48f27b1a9
--- /dev/null
+++ b/dom/security/test/https-only/browser_httpsonly_speculative_connect.js
@@ -0,0 +1,71 @@
+"use strict";
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+);
+
+let console_messages = [
+ {
+ description: "Speculative Connection should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure speculative TCP connection",
+ "to use",
+ "example.org",
+ "file_httpsonly_speculative_connect.html",
+ ],
+ },
+ {
+ description: "Upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "Upgrading insecure request",
+ "to use",
+ "example.org",
+ "file_httpsonly_speculative_connect.html",
+ ],
+ },
+];
+
+function on_new_console_messages(msgObj) {
+ const message = msgObj.message;
+ const logLevel = msgObj.logLevel;
+
+ if (message.includes("HTTPS-Only Mode:")) {
+ for (let i = 0; i < console_messages.length; i++) {
+ const testCase = console_messages[i];
+ // Check if log-level matches
+ if (logLevel !== testCase.expectLogLevel) {
+ continue;
+ }
+ // Check if all substrings are included
+ if (testCase.expectIncludes.some(str => !message.includes(str))) {
+ continue;
+ }
+ ok(true, testCase.description);
+ console_messages.splice(i, 1);
+ break;
+ }
+ }
+}
+
+add_task(async function () {
+ requestLongerTimeout(4);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+ Services.console.registerListener(on_new_console_messages);
+
+ let promiseLoaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ await BrowserTestUtils.startLoadingURIString(
+ gBrowser.selectedBrowser,
+ `${TEST_PATH_HTTP}file_httpsonly_speculative_connect.html`
+ );
+ await promiseLoaded;
+
+ await BrowserTestUtils.waitForCondition(() => console_messages.length === 0);
+
+ Services.console.unregisterListener(on_new_console_messages);
+});
diff --git a/dom/security/test/https-only/browser_iframe_test.js b/dom/security/test/https-only/browser_iframe_test.js
new file mode 100644
index 0000000000..eb4c1a97c3
--- /dev/null
+++ b/dom/security/test/https-only/browser_iframe_test.js
@@ -0,0 +1,223 @@
+// Bug 1658264 - Https-Only: HTTPS-Only and iFrames
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1658264
+"use strict";
+
+// > How does this test work?
+// We're sending a request to file_iframe_test.sjs with various
+// browser-configurations. The sjs-file returns a website with two iFrames
+// loading the same sjs-file again. One iFrame is same origin (example.com) and
+// the other cross-origin (example.org) Each request gets saved in a semicolon
+// seperated list of strings. The sjs-file gets initialized with the
+// query-string "setup" and the result string can be polled with "results". Each
+// string has this format: {top/com/org}-{queryString}-{scheme}. In the end
+// we're just checking if all expected requests were recorded and had the
+// correct scheme. Requests that are meant to fail should explicitly not be
+// contained in the list of results.
+
+// The test loads all tabs and evaluates when all have finished loading
+// it may take quite a long time.
+// This requires more twice as much as the default 45 seconds per test:
+requestLongerTimeout(2);
+SimpleTest.requestCompleteLog();
+
+add_task(async function () {
+ await setup();
+
+ // Using this variable to parallelize and collect tests
+ let testSet = [];
+
+ /*
+ * HTTPS-Only Mode disabled
+ */
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", false]],
+ });
+
+ // Top-Level scheme: HTTP
+ // NOTE(freddyb): Test case temporarily disabled. See bug 1735565
+ /*testSet.push(
+ runTest({
+ queryString: "test1.1",
+ topLevelScheme: "http",
+
+ expectedTopLevel: "http",
+ expectedSameOrigin: "http",
+ expectedCrossOrigin: "http",
+ })
+ );*/
+ // Top-Level scheme: HTTPS
+ testSet.push(
+ runTest({
+ queryString: "test1.2",
+ topLevelScheme: "https",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "fail",
+ expectedCrossOrigin: "fail",
+ })
+ );
+
+ await Promise.all(testSet);
+ testSet = [];
+ /*
+ * HTTPS-Only Mode enabled, no exception
+ */
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ // Top-Level scheme: HTTP
+ testSet.push(
+ runTest({
+ queryString: "test2.1",
+ topLevelScheme: "http",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "https",
+ expectedCrossOrigin: "https",
+ })
+ );
+ // Top-Level scheme: HTTPS
+ testSet.push(
+ runTest({
+ queryString: "test2.2",
+ topLevelScheme: "https",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "https",
+ expectedCrossOrigin: "https",
+ })
+ );
+
+ await Promise.all(testSet);
+ testSet = [];
+
+ /*
+ * HTTPS-Only enabled, with exceptions
+ * for http://example.org and http://example.com
+ */
+ // Exempting example.org (cross-site) should not affect anything
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "http://example.org",
+ },
+ ]);
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "http://example.com",
+ },
+ ]);
+
+ // Top-Level scheme: HTTP
+ await runTest({
+ queryString: "test3.1",
+ topLevelScheme: "http",
+
+ expectedTopLevel: "http",
+ expectedSameOrigin: "http",
+ expectedCrossOrigin: "http",
+ });
+
+ await SpecialPowers.popPermissions();
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "https://example.com",
+ },
+ ]);
+ // Top-Level scheme: HTTPS
+ await runTest({
+ queryString: "test3.2",
+ topLevelScheme: "https",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "fail",
+ expectedCrossOrigin: "fail",
+ });
+
+ // Remove permissions again (has to be done manually for some reason?)
+ await SpecialPowers.popPermissions();
+ await SpecialPowers.popPermissions();
+
+ await evaluate();
+});
+
+const SERVER_URL = scheme =>
+ `${scheme}://example.com/browser/dom/security/test/https-only/file_iframe_test.sjs?`;
+let shouldContain = [];
+let shouldNotContain = [];
+
+async function setup() {
+ info(`TEST-CASE-setup - A`);
+ const response = await fetch(SERVER_URL("https") + "setup");
+ info(`TEST-CASE-setup - B`);
+ const txt = await response.text();
+ info(`TEST-CASE-setup - C`);
+ if (txt != "ok") {
+ ok(false, "Failed to setup test server.");
+ finish();
+ }
+}
+
+async function evaluate() {
+ info(`TEST-CASE-evaluate - A`);
+ const response = await fetch(SERVER_URL("https") + "results");
+ info(`TEST-CASE-evaluate - B`);
+ const requestResults = (await response.text()).split(";");
+ info(`TEST-CASE-evaluate - C`);
+
+ shouldContain.map(str =>
+ ok(requestResults.includes(str), `Results should contain '${str}'.`)
+ );
+ shouldNotContain.map(str =>
+ ok(!requestResults.includes(str), `Results shouldn't contain '${str}'.`)
+ );
+}
+
+async function runTest(test) {
+ const queryString = test.queryString;
+ info(`TEST-CASE-${test.queryString} - runTest BEGIN`);
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false, // includeSubFrames
+ SERVER_URL(test.expectedTopLevel) + queryString,
+ false // maybeErrorPage
+ );
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ SERVER_URL(test.topLevelScheme) + queryString
+ );
+ info(`TEST-CASE-${test.queryString} - Before 'await loaded'`);
+ await loaded;
+ info(`TEST-CASE-${test.queryString} - After 'await loaded'`);
+ });
+ info(`TEST-CASE-${test.queryString} - After 'await withNewTab'`);
+
+ if (test.expectedTopLevel !== "fail") {
+ shouldContain.push(`top-${queryString}-${test.expectedTopLevel}`);
+ } else {
+ shouldNotContain.push(`top-${queryString}-http`);
+ shouldNotContain.push(`top-${queryString}-https`);
+ }
+
+ if (test.expectedSameOrigin !== "fail") {
+ shouldContain.push(`com-${queryString}-${test.expectedSameOrigin}`);
+ } else {
+ shouldNotContain.push(`com-${queryString}-http`);
+ shouldNotContain.push(`com-${queryString}-https`);
+ }
+
+ if (test.expectedCrossOrigin !== "fail") {
+ shouldContain.push(`org-${queryString}-${test.expectedCrossOrigin}`);
+ } else {
+ shouldNotContain.push(`org-${queryString}-http`);
+ shouldNotContain.push(`org-${queryString}-https`);
+ }
+ info(`TEST-CASE-${test.queryString} - runTest END`);
+}
diff --git a/dom/security/test/https-only/browser_navigation.js b/dom/security/test/https-only/browser_navigation.js
new file mode 100644
index 0000000000..8c4609a57a
--- /dev/null
+++ b/dom/security/test/https-only/browser_navigation.js
@@ -0,0 +1,94 @@
+"use strict";
+
+// For each FIRST_URL_* this test does the following:
+// 1. Navigate to FIRST_URL_*
+// 2. Check if we are on a HTTPS-Only error page
+// 3. Navigate to SECOND_URL
+// 4. Navigate back
+// 5. Check if we are on a HTTPS-Only error page
+
+const FIRST_URL_SECURE = "https://example.com";
+const FIRST_URL_INSECURE_REDIRECT =
+ "http://example.com/browser/dom/security/test/https-only/file_redirect_to_insecure.sjs";
+const FIRST_URL_INSECURE_NOCERT = "http://nocert.example.com";
+const SECOND_URL = "https://example.org";
+
+function waitForPage() {
+ return new Promise(resolve => {
+ BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser).then(resolve);
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(resolve);
+ });
+}
+
+async function verifyErrorPage(expectErrorPage = true) {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [expectErrorPage],
+ async function (_expectErrorPage) {
+ let doc = content.document;
+ let innerHTML = doc.body.innerHTML;
+ let errorPageL10nId = "about-httpsonly-title-alert";
+
+ is(
+ innerHTML.includes(errorPageL10nId) &&
+ doc.documentURI.startsWith("about:httpsonlyerror"),
+ _expectErrorPage,
+ "we should be on the https-only error page"
+ );
+ }
+ );
+}
+
+async function runTest(
+ firstUrl,
+ expectErrorPageOnFirstVisit,
+ expectErrorPageOnSecondVisit
+) {
+ let loaded = waitForPage();
+ info("Loading first page");
+ BrowserTestUtils.startLoadingURIString(gBrowser, firstUrl);
+ await loaded;
+ await verifyErrorPage(expectErrorPageOnFirstVisit);
+
+ loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ info("Navigating to second page");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [SECOND_URL],
+ async url => (content.location.href = url)
+ );
+ await loaded;
+
+ // Go back one site by clicking the back button
+ loaded = BrowserTestUtils.waitForLocationChange(gBrowser);
+ info("Clicking back button");
+ let backButton = document.getElementById("back-button");
+ backButton.click();
+ await loaded;
+ await verifyErrorPage(expectErrorPageOnSecondVisit);
+}
+
+add_task(async function () {
+ waitForExplicitFinish();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ // We don't expect any HTTPS-Only error pages, on the first and second visit of this URL,
+ // since the URL is reachable via https.
+ await runTest(FIRST_URL_SECURE, false, false);
+
+ // Since trying to upgrade this url will result in being redirected again to the insecure
+ // site, we are not able to upgrade it and a HTTPS-Only error page is shown.
+ // This is happening both on the first and second visit.
+ await runTest(FIRST_URL_INSECURE_REDIRECT, true, true);
+
+ // Similar to the previous case, we can not upgrade this URL, since this time it has a
+ // invalid certificate. We would expect a HTTPS-Only error page on both vists, but it is only
+ // shown on the first one, on the second one we get an errror page about the invalid
+ // certificate instead (Bug 1848117).
+ await runTest(FIRST_URL_INSECURE_NOCERT, true, false);
+
+ finish();
+});
diff --git a/dom/security/test/https-only/browser_redirect_tainting.js b/dom/security/test/https-only/browser_redirect_tainting.js
new file mode 100644
index 0000000000..0823ec4658
--- /dev/null
+++ b/dom/security/test/https-only/browser_redirect_tainting.js
@@ -0,0 +1,39 @@
+/* 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/. */
+
+// Test steps:
+// 1. Load file_redirect_tainting.sjs?html.
+// 2. The server returns an html which loads an image at http://example.net.
+// 3. The image request will be upgraded to HTTPS since HTTPS-only mode is on.
+// 4. In file_redirect_tainting.sjs, we set "Access-Control-Allow-Origin" to
+// the value of the Origin header.
+// 5. If the vlaue does not match, the image won't be loaded.
+async function do_test() {
+ let requestUrl = `https://example.com/browser/dom/security/test/https-only/file_redirect_tainting.sjs?html`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ let imageLoaded = await SpecialPowers.spawn(browser, [], function () {
+ let image = content.document.getElementById("test_image");
+ return image && image.complete && image.naturalHeight !== 0;
+ });
+ await Assert.ok(imageLoaded, "test_image should be loaded");
+ }
+ );
+}
+
+add_task(async function test_https_only_redirect_tainting() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ await do_test();
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/https-only/browser_save_as.js b/dom/security/test/https-only/browser_save_as.js
new file mode 100644
index 0000000000..309dd69c79
--- /dev/null
+++ b/dom/security/test/https-only/browser_save_as.js
@@ -0,0 +1,187 @@
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+// Using insecure HTTP URL for a test cases around HTTP/HTTPS download interaction
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const HTTP_LINK = `http://example.org/`;
+const HTTPS_LINK = `https://example.org/`;
+const TEST_PATH =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/dom/security/test/https-only/file_save_as.html";
+
+let MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+const tempDir = createTemporarySaveDirectory();
+MockFilePicker.displayDirectory = tempDir;
+
+add_setup(async function () {
+ info("Setting MockFilePicker.");
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ tempDir.remove(true);
+ });
+});
+
+function createTemporarySaveDirectory() {
+ let saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ saveDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ return saveDir;
+}
+
+function createPromiseForObservingChannel(expectedUrl) {
+ return new Promise(resolve => {
+ let observer = (aSubject, aTopic) => {
+ if (aTopic === "http-on-modify-request") {
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+
+ if (httpChannel.URI.spec != expectedUrl) {
+ return;
+ }
+
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ resolve();
+ }
+ };
+
+ Services.obs.addObserver(observer, "http-on-modify-request");
+ });
+}
+
+function createPromiseForTransferComplete() {
+ return new Promise(resolve => {
+ MockFilePicker.showCallback = fp => {
+ info("MockFilePicker showCallback");
+
+ let fileName = fp.defaultString;
+ let destFile = tempDir.clone();
+ destFile.append(fileName);
+
+ MockFilePicker.setFiles([destFile]);
+ MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
+
+ MockFilePicker.showCallback = null;
+ mockTransferCallback = function (downloadSuccess) {
+ ok(downloadSuccess, "File should have been downloaded successfully");
+ mockTransferCallback = () => {};
+ resolve();
+ };
+ };
+ });
+}
+
+function createPromiseForConsoleError(message) {
+ return new Promise((resolve, reject) => {
+ function listener(msgObj) {
+ let text = msgObj.message;
+ if (text.includes(message)) {
+ info(`Found occurence of '${message}'`);
+ Services.console.unregisterListener(listener);
+ resolve();
+ }
+ }
+ Services.console.registerListener(listener);
+ });
+}
+
+async function runTest(selector, expectedUrl, expectedError) {
+ info(`Open a new tab for testing "Save link as" in context menu.`);
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PATH);
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
+
+ let browser = gBrowser.selectedBrowser;
+
+ info("Open the context menu.");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ selector,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ browser
+ );
+
+ await popupShownPromise;
+
+ let downloadEndPromise = expectedError
+ ? createPromiseForConsoleError(expectedError)
+ : createPromiseForTransferComplete();
+ let observerPromise = createPromiseForObservingChannel(expectedUrl);
+
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+
+ // Select "Save As" option from context menu.
+ let saveElement = document.getElementById(`context-savelink`);
+ info("Triggering the save process.");
+ contextMenu.activateItem(saveElement);
+
+ info("Waiting for the channel.");
+ await observerPromise;
+
+ info(
+ expectedError
+ ? "Waiting for error in console."
+ : "Wait until the save is finished."
+ );
+ await downloadEndPromise;
+
+ info("Wait until the menu is closed.");
+ await popupHiddenPromise;
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function setHttpsFirstAndOnlyPrefs(httpsFirst, httpsOnly) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_first", httpsFirst],
+ ["dom.security.https_only_mode", httpsOnly],
+ ],
+ });
+}
+
+add_task(async function testBaseline() {
+ // Run with HTTPS-First and HTTPS-Only disabled
+ await setHttpsFirstAndOnlyPrefs(false, false);
+ await runTest("#insecure-link", HTTP_LINK, undefined);
+ await runTest("#secure-link", HTTPS_LINK, undefined);
+});
+
+add_task(async function testHttpsFirst() {
+ // Run with HTTPS-First enabled
+ // The the user will get a warning about really wanting to download
+ // from a insecure site, because we upgraded the top level document,
+ // but the download is still insecure. In the future we also want to
+ // upgrade these Save-As downloads.
+ await setHttpsFirstAndOnlyPrefs(true, false);
+ await runTest(
+ "#insecure-link",
+ HTTP_LINK,
+ "Blocked downloading insecure content “http://example.org/”."
+ );
+ await runTest("#secure-link", HTTPS_LINK, undefined);
+});
+
+add_task(async function testHttpsOnly() {
+ // Run with HTTPS-Only enabled
+ // Should have same behaviour as HTTPS-First
+ await setHttpsFirstAndOnlyPrefs(false, true);
+ await runTest(
+ "#insecure-link",
+ HTTP_LINK,
+ "Blocked downloading insecure content “http://example.org/”."
+ );
+ await runTest("#secure-link", HTTPS_LINK, undefined);
+});
diff --git a/dom/security/test/https-only/browser_triggering_principal_exemption.js b/dom/security/test/https-only/browser_triggering_principal_exemption.js
new file mode 100644
index 0000000000..09a867a63b
--- /dev/null
+++ b/dom/security/test/https-only/browser_triggering_principal_exemption.js
@@ -0,0 +1,72 @@
+// Bug 1662359 - Don't upgrade subresources whose triggering principal is exempt from HTTPS-Only mode.
+// https://bugzilla.mozilla.org/bug/1662359
+"use strict";
+
+const TRIGGERING_PAGE = "http://example.org";
+const LOADED_RESOURCE = "http://example.com";
+
+add_task(async function () {
+ // Enable HTTPS-Only Mode
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ await runTest(
+ "Request with not exempt triggering principal should get upgraded.",
+ "https://"
+ );
+
+ // Now exempt the triggering page
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: TRIGGERING_PAGE,
+ },
+ ]);
+
+ await runTest(
+ "Request with exempt triggering principal should not get upgraded.",
+ "http://"
+ );
+
+ await SpecialPowers.popPermissions();
+});
+
+async function runTest(desc, startsWith) {
+ const responseURL = await new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", LOADED_RESOURCE);
+
+ // Replace loadinfo with one whose triggeringPrincipal is a content
+ // principal for TRIGGERING_PAGE.
+ const triggeringPrincipal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ TRIGGERING_PAGE
+ );
+ let dummyURI = Services.io.newURI(LOADED_RESOURCE);
+ let dummyChannel = NetUtil.newChannel({
+ uri: dummyURI,
+ triggeringPrincipal,
+ loadingPrincipal: xhr.channel.loadInfo.loadingPrincipal,
+ securityFlags: xhr.channel.loadInfo.securityFlags,
+ contentPolicyType: xhr.channel.loadInfo.externalContentPolicyType,
+ });
+ xhr.channel.loadInfo = dummyChannel.loadInfo;
+
+ xhr.onreadystatechange = () => {
+ // We don't care about the result, just if Firefox upgraded the URL
+ // internally.
+ if (
+ xhr.readyState !== XMLHttpRequest.OPENED ||
+ xhr.readyState !== XMLHttpRequest.UNSENT
+ ) {
+ // Let's make sure this function doesn't get called anymore
+ xhr.onreadystatechange = undefined;
+ resolve(xhr.responseURL);
+ }
+ };
+ xhr.send();
+ });
+ ok(responseURL.startsWith(startsWith), desc);
+}
diff --git a/dom/security/test/https-only/browser_upgrade_exceptions.js b/dom/security/test/https-only/browser_upgrade_exceptions.js
new file mode 100644
index 0000000000..8611b32a0f
--- /dev/null
+++ b/dom/security/test/https-only/browser_upgrade_exceptions.js
@@ -0,0 +1,86 @@
+// Bug 1625448 - HTTPS Only Mode - Exceptions for loopback and local IP addresses
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1631384
+// This test ensures that various configurable upgrade exceptions work
+"use strict";
+
+add_task(async function () {
+ requestLongerTimeout(2);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ // Loopback test
+ await runTest(
+ "Loopback IP addresses should always be exempt from upgrades (localhost)",
+ "http://localhost",
+ "http://"
+ );
+ await runTest(
+ "Loopback IP addresses should always be exempt from upgrades (127.0.0.1)",
+ "http://127.0.0.1",
+ "http://"
+ );
+ // Default local-IP and onion tests
+ await runTest(
+ "Local IP addresses should be exempt from upgrades by default",
+ "http://10.0.250.250",
+ "http://"
+ );
+ await runTest(
+ "Hosts ending with .onion should be be exempt from HTTPS-Only upgrades by default",
+ "http://grocery.shopping.for.one.onion",
+ "http://"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_only_mode.upgrade_local", true],
+ ["dom.security.https_only_mode.upgrade_onion", true],
+ ],
+ });
+
+ // Local-IP and onion tests with upgrade enabled
+ await runTest(
+ "Local IP addresses should get upgraded when 'dom.security.https_only_mode.upgrade_local' is set to true",
+ "http://10.0.250.250",
+ "https://"
+ );
+ await runTest(
+ "Hosts ending with .onion should get upgraded when 'dom.security.https_only_mode.upgrade_onion' is set to true",
+ "http://grocery.shopping.for.one.onion",
+ "https://"
+ );
+ // Local-IP request with HTTPS_ONLY_EXEMPT flag
+ await runTest(
+ "The HTTPS_ONLY_EXEMPT flag should overrule upgrade-prefs",
+ "http://10.0.250.250",
+ "http://",
+ true
+ );
+});
+
+async function runTest(desc, url, startsWith, exempt = false) {
+ const responseURL = await new Promise(resolve => {
+ let xhr = new XMLHttpRequest();
+ xhr.timeout = 1200;
+ xhr.open("GET", url);
+ if (exempt) {
+ xhr.channel.loadInfo.httpsOnlyStatus |= Ci.nsILoadInfo.HTTPS_ONLY_EXEMPT;
+ }
+ xhr.onreadystatechange = () => {
+ // We don't care about the result and it's possible that
+ // the requests might even succeed in some testing environments
+ if (
+ xhr.readyState !== XMLHttpRequest.OPENED ||
+ xhr.readyState !== XMLHttpRequest.UNSENT
+ ) {
+ // Let's make sure this function doesn't get caled anymore
+ xhr.onreadystatechange = undefined;
+ resolve(xhr.responseURL);
+ }
+ };
+ xhr.send();
+ });
+ ok(responseURL.startsWith(startsWith), desc);
+}
diff --git a/dom/security/test/https-only/browser_upgrade_exemption.js b/dom/security/test/https-only/browser_upgrade_exemption.js
new file mode 100644
index 0000000000..23d857b511
--- /dev/null
+++ b/dom/security/test/https-only/browser_upgrade_exemption.js
@@ -0,0 +1,80 @@
+"use strict";
+
+const PAGE_WITHOUT_SCHEME = "://example.com";
+
+add_task(async function () {
+ // Load a insecure page with HTTPS-Only and HTTPS-First disabled
+ await runTest({
+ loadScheme: "http",
+ expectScheme: "http",
+ });
+
+ // Load a secure page with HTTPS-Only and HTTPS-First disabled
+ await runTest({
+ loadScheme: "https",
+ expectScheme: "https",
+ });
+
+ // Load a exempted insecure page with HTTPS-Only and HTTPS-First disabled
+ await runTest({
+ exempt: true,
+ loadScheme: "http",
+ expectScheme: "http",
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ // Load a insecure page with HTTPS-Only enabled
+ await runTest({
+ loadScheme: "http",
+ expectScheme: "https",
+ });
+
+ // Load a exempted insecure page with HTTPS-Only enabled
+ await runTest({
+ exempt: true,
+ loadScheme: "http",
+ expectScheme: "http",
+ });
+
+ await SpecialPowers.flushPrefEnv();
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", true]],
+ });
+
+ // Load a insecure page with HTTPS-First enabled
+ await runTest({
+ loadScheme: "http",
+ expectScheme: "https",
+ });
+
+ // Load a exempted insecure page with HTTPS-First enabled
+ await runTest({
+ exempt: true,
+ loadScheme: "http",
+ expectScheme: "http",
+ });
+});
+
+async function runTest(options) {
+ const { exempt = false, loadScheme, expectScheme } = options;
+ const page = loadScheme + PAGE_WITHOUT_SCHEME;
+
+ if (exempt) {
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: page,
+ },
+ ]);
+ }
+
+ await BrowserTestUtils.withNewTab(page, async function (browser) {
+ is(browser.currentURI.scheme, expectScheme, "Unexpected scheme");
+ await SpecialPowers.popPermissions();
+ await SpecialPowers.popPrefEnv();
+ });
+}
diff --git a/dom/security/test/https-only/browser_user_gesture.js b/dom/security/test/https-only/browser_user_gesture.js
new file mode 100644
index 0000000000..e7d6a20318
--- /dev/null
+++ b/dom/security/test/https-only/browser_user_gesture.js
@@ -0,0 +1,55 @@
+// Bug 1725026 - HTTPS Only Mode - Test if a load triggered by a user gesture
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1725026
+// Test if a load triggered by a user gesture can be upgraded to HTTPS
+// successfully.
+
+"use strict";
+
+const testPathUpgradeable = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const kTestURI = testPathUpgradeable + "file_user_gesture.html";
+
+add_task(async function () {
+ // Enable HTTPS-Only Mode and register console-listener
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ const loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ // 1. Upgrade a page to https://
+ BrowserTestUtils.startLoadingURIString(browser, kTestURI);
+ await loaded;
+ await ContentTask.spawn(browser, {}, async args => {
+ ok(
+ content.document.location.href.startsWith("https://"),
+ "Should be https"
+ );
+
+ // 2. Trigger a load by clicking button.
+ // The scheme of the link url is `http` and the load should be able to
+ // upgraded to `https` because of HTTPS-only mode.
+ let button = content.document.getElementById("httpLinkButton");
+ await EventUtils.synthesizeMouseAtCenter(
+ button,
+ { type: "mousedown" },
+ content
+ );
+ await EventUtils.synthesizeMouseAtCenter(
+ button,
+ { type: "mouseup" },
+ content
+ );
+ await ContentTaskUtils.waitForCondition(() => {
+ return content.document.location.href.startsWith("https://");
+ });
+ ok(
+ content.document.location.href.startsWith("https://"),
+ "Should be https"
+ );
+ });
+ });
+});
diff --git a/dom/security/test/https-only/browser_websocket_exceptions.js b/dom/security/test/https-only/browser_websocket_exceptions.js
new file mode 100644
index 0000000000..a6e5336c63
--- /dev/null
+++ b/dom/security/test/https-only/browser_websocket_exceptions.js
@@ -0,0 +1,68 @@
+"use strict";
+
+const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://localhost:9898"
+);
+
+let WEBSOCKET_DOC_URL = `${TEST_PATH_HTTP}file_websocket_exceptions.html`;
+
+add_task(async function () {
+ // Here is a sequence of how this test works:
+ // 1. Dynamically inject a localhost iframe
+ // 2. Add an exemption for localhost
+ // 3. Fire up Websocket
+ // Generally local IP addresses are exempt from https-only, but if we do not add
+ // an exemption for localhost, then the TriggeringPrincipal of the WebSocket is
+ // `not` exempt and we would upgrade ws to wss.
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.security.https_only_mode", true],
+ ["network.proxy.allow_hijacking_localhost", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab("about:blank", async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+
+ BrowserTestUtils.startLoadingURIString(browser, WEBSOCKET_DOC_URL);
+ await loaded;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ // Part 1:
+ let myIframe = content.document.createElement("iframe");
+ content.document.body.appendChild(myIframe);
+ myIframe.src =
+ "http://localhost:9898/browser/dom/security/test/https-only/file_websocket_exceptions_iframe.html";
+
+ myIframe.onload = async function () {
+ // Part 2:
+ await SpecialPowers.pushPermissions([
+ {
+ type: "https-only-load-insecure",
+ allow: true,
+ context: "http://localhost:9898",
+ },
+ ]);
+ // Part 3.
+ myIframe.contentWindow.postMessage({ myMessage: "runWebSocket" }, "*");
+ };
+
+ const promise = new Promise(resolve => {
+ content.addEventListener("WebSocketEnded", resolve, {
+ once: true,
+ });
+ });
+
+ const { detail } = await promise;
+
+ is(detail.state, "onopen", "sanity: websocket loaded");
+ ok(
+ detail.url.startsWith("ws://example.com/tests"),
+ "exempt websocket should not be upgraded to wss://"
+ );
+ });
+ });
+ await SpecialPowers.popPermissions();
+});
diff --git a/dom/security/test/https-only/file_background_redirect.sjs b/dom/security/test/https-only/file_background_redirect.sjs
new file mode 100644
index 0000000000..45e01797e3
--- /dev/null
+++ b/dom/security/test/https-only/file_background_redirect.sjs
@@ -0,0 +1,32 @@
+// Custom *.sjs file specifically for the needs of Bug 1683015
+"use strict";
+
+let { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+async function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let query = request.queryString;
+ if (request.scheme === "https" && query === "start") {
+ // Simulating long repsonse time by processing the https request
+ // using a 5 seconds delay. Note that the http background request
+ // gets send after 3 seconds
+ response.processAsync();
+ setTimeout(() => {
+ response.setStatusLine("1.1", 200, "OK");
+ response.write(
+ "<html><body>Test Page for Bug 1683015 loaded</body></html>"
+ );
+ response.finish();
+ }, 5000); /* wait 5 seconds */
+ return;
+ }
+
+ // we should never get here, but just in case return something unexpected
+ response.setStatusLine("1.1", 404, "Not Found");
+ response.write("<html><body>SHOULDN'T DISPLAY THIS PAGE</body></html>");
+}
diff --git a/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs b/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs
new file mode 100644
index 0000000000..a8a9083ef3
--- /dev/null
+++ b/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs
@@ -0,0 +1,67 @@
+// Custom *.sjs file specifically for the needs of Bug 1691888
+"use strict";
+
+const REDIRECT_META = `
+ <html>
+ <head>
+ <meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test1b'">
+ </head>
+ <body>
+ META REDIRECT
+ </body>
+ </html>`;
+
+const REDIRECT_JS = `
+ <html>
+ <body>
+ JS REDIRECT
+ <script>
+ let url= "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test2b";
+ window.location = url;
+ </script>
+ </body>
+ </html>`;
+
+const REDIRECT_302 =
+ "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs?test3b";
+
+const REDIRECT_302_DIFFERENT_PATH =
+ "http://example.com/tests/dom/security/test/https-only/file_user_gesture.html";
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviour
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ // if the scheme is not https, meaning that the initial request did not
+ // get upgraded, then we rather fall through and display unexpected content.
+ if (request.scheme === "https") {
+ let query = request.queryString;
+
+ if (query === "test1a") {
+ response.write(REDIRECT_META);
+ return;
+ }
+
+ if (query === "test2a") {
+ response.write(REDIRECT_JS);
+ return;
+ }
+
+ if (query === "test3a") {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", REDIRECT_302, false);
+ return;
+ }
+
+ if (query === "test4a") {
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", REDIRECT_302_DIFFERENT_PATH, false);
+ return;
+ }
+ }
+
+ // we should never get here, just in case,
+ // let's return something unexpected
+ response.write("<html><body>DO NOT DISPLAY THIS</body></html>");
+}
diff --git a/dom/security/test/https-only/file_bug1874801.html b/dom/security/test/https-only/file_bug1874801.html
new file mode 100644
index 0000000000..58c2f03c81
--- /dev/null
+++ b/dom/security/test/https-only/file_bug1874801.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Bug 1874801</title>
+</head>
+<body>
+ <img src="http://example.com/browser/dom/security/test/https-only/file_bug1874801.sjs">
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_bug1874801.sjs b/dom/security/test/https-only/file_bug1874801.sjs
new file mode 100644
index 0000000000..ce84af1d5f
--- /dev/null
+++ b/dom/security/test/https-only/file_bug1874801.sjs
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ if (request.scheme === "https") {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/svg+xml");
+ response.write(
+ `<svg version="1.1" width="100" height="40" xmlns="http://www.w3.org/2000/svg"><text x="20" y="20">HTTPS</text></svg>`
+ );
+ return;
+ }
+ if (request.scheme === "http") {
+ response.setStatusLine(request.httpVersion, 400, "Bad Request");
+ }
+}
diff --git a/dom/security/test/https-only/file_console_logging.html b/dom/security/test/https-only/file_console_logging.html
new file mode 100644
index 0000000000..94e07cddd9
--- /dev/null
+++ b/dom/security/test/https-only/file_console_logging.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1625448 - HTTPS Only Mode - Tests for console logging</title>
+</head>
+<body>
+ <!-- These files don't exist since we only care if the webserver can respond. -->
+ <!-- This request can get upgraded. -->
+ <img src="http://example.com/file_1.jpg">
+ <!-- This request can't get upgraded -->
+ <img src="http://self-signed.example.com/file_2.jpg">
+
+ <iframe src="http://self-signed.example.com/browser/dom/security/test/https-only/file_console_logging.html"></iframe>
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_cors_mixedcontent.html b/dom/security/test/https-only/file_cors_mixedcontent.html
new file mode 100644
index 0000000000..50d32954ef
--- /dev/null
+++ b/dom/security/test/https-only/file_cors_mixedcontent.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+
+ <script>
+ addEventListener("StartFetch", async function() {
+ let comResult;
+ let orgResult;
+
+ await Promise.all([
+ fetch("http://example.com/")
+ .then(() => {
+ comResult = "success";
+ })
+ .catch(() => {
+ comResult = "error";
+ }),
+ fetch("http://example.org/")
+ .then(() => {
+ orgResult = "success";
+ })
+ .catch(() => {
+ orgResult = "error";
+ }),
+ ]);
+
+ window.dispatchEvent(new CustomEvent("FetchEnded", {
+ detail: { comResult, orgResult }
+ }));
+
+ })
+ </script>
+</head>
+
+<body>
+ <h2>Https-Only: CORS and MixedContent tests</h2>
+ <p><a href="https://bugzilla.mozilla.org/bug/1659505">Bug 1659505</a></p>
+</body>
+
+</html>
diff --git a/dom/security/test/https-only/file_fragment.html b/dom/security/test/https-only/file_fragment.html
new file mode 100644
index 0000000000..49cff76f9a
--- /dev/null
+++ b/dom/security/test/https-only/file_fragment.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+// sends url of current window and onbeforeunloadMessage
+// when we enter here test failed.
+function beforeunload(){
+ window.opener.postMessage({
+ info: "before-unload",
+ result: window.location.hash,
+ button: false,
+ }, "*");
+}
+
+// sends url of current window and then click on button
+window.onload = function (){
+ // get button by ID
+ let button = window.document.getElementById("clickMeButton");
+ // if getting button was successful, buttonExist = true
+ let buttonExist = button !== null;
+ // send loading message
+ window.opener.postMessage({
+ info: "onload",
+ result: window.location.href,
+ button: buttonExist,
+ }, "*");
+ // click button
+ button.click();
+}
+// after button clicked and paged scrolled sends URL of current window
+window.onscroll = function(){
+ window.opener.postMessage({
+ info: "scrolled-to-foo",
+ result: window.location.href,
+ button: true,
+ documentURI: document.documentURI,
+ }, "*");
+ }
+
+
+</script>
+<body onbeforeunload="/*just to notify if we load a new page*/ beforeunload()";>
+ <a id="clickMeButton" href="http://example.com/tests/dom/security/test/https-only/file_fragment.html#foo">Click me</a>
+ <div style="height: 1000px; border: 1px solid black;"> space</div>
+ <a name="foo" href="http://example.com/tests/dom/security/test/https-only/file_fragment.html">foo</a>
+ <div style="height: 1000px; border: 1px solid black;">space</div>
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_fragment_noscript.html b/dom/security/test/https-only/file_fragment_noscript.html
new file mode 100644
index 0000000000..609961fd98
--- /dev/null
+++ b/dom/security/test/https-only/file_fragment_noscript.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <a id="clickMeButton" href="http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html#foo">Click me</a>
+ <div style="height: 1000px; border: 1px solid black;"> space</div>
+ <a name="foo" href="http://example.com/browser/dom/security/test/https-only/file_fragment_noscript.html">foo</a>
+ <div style="height: 1000px; border: 1px solid black;">space</div>
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_http_background_auth_request.sjs b/dom/security/test/https-only/file_http_background_auth_request.sjs
new file mode 100644
index 0000000000..80fc8ffcf7
--- /dev/null
+++ b/dom/security/test/https-only/file_http_background_auth_request.sjs
@@ -0,0 +1,16 @@
+// Custom *.sjs file specifically for the needs of Bug 1665062
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if (request.scheme === "https") {
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="bug1665062"');
+ return;
+ }
+
+ // we should never get here; just in case, return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/https-only/file_http_background_request.sjs b/dom/security/test/https-only/file_http_background_request.sjs
new file mode 100644
index 0000000000..ef0e2ce5bd
--- /dev/null
+++ b/dom/security/test/https-only/file_http_background_request.sjs
@@ -0,0 +1,15 @@
+// Custom *.sjs file specifically for the needs of Bug 1663396
+
+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/dom/security/test/https-only/file_httpsonly_speculative_connect.html b/dom/security/test/https-only/file_httpsonly_speculative_connect.html
new file mode 100644
index 0000000000..46a10401f9
--- /dev/null
+++ b/dom/security/test/https-only/file_httpsonly_speculative_connect.html
@@ -0,0 +1 @@
+<html><body>dummy file for speculative https-only upgrade test</body></html>
diff --git a/dom/security/test/https-only/file_iframe_test.sjs b/dom/security/test/https-only/file_iframe_test.sjs
new file mode 100644
index 0000000000..611870c87c
--- /dev/null
+++ b/dom/security/test/https-only/file_iframe_test.sjs
@@ -0,0 +1,58 @@
+// Bug 1658264 - HTTPS-Only and iFrames
+// see browser_iframe_test.js
+
+const IFRAME_CONTENT = `
+<!DOCTYPE HTML>
+<html>
+ <head><meta charset="utf-8"></head>
+ <body>Helo Friend!</body>
+</html>`;
+const DOCUMENT_CONTENT = q => `
+<!DOCTYPE HTML>
+<html>
+ <head><meta charset="utf-8"></head>
+ <body>
+ <iframe src="http://example.com/browser/dom/security/test/https-only/file_iframe_test.sjs?com-${q}"></iframe>
+ <iframe src="http://example.org/browser/dom/security/test/https-only/file_iframe_test.sjs?org-${q}"></iframe>
+ </body>
+</html>`;
+
+function handleRequest(request, response) {
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ let queryString = request.queryString;
+ const queryScheme = request.scheme;
+
+ // Setup the state with an empty string and return "ok"
+ if (queryString == "setup") {
+ setState("receivedQueries", "");
+ response.write("ok");
+ return;
+ }
+
+ let receivedQueries = getState("receivedQueries");
+
+ // Return result-string
+ if (queryString == "results") {
+ response.write(receivedQueries);
+ return;
+ }
+
+ // Add semicolon to seperate strings
+ if (receivedQueries !== "") {
+ receivedQueries += ";";
+ }
+
+ // Requests from iFrames start with com or org
+ if (queryString.startsWith("com-") || queryString.startsWith("org-")) {
+ receivedQueries += queryString;
+ setState("receivedQueries", `${receivedQueries}-${queryScheme}`);
+ response.write(IFRAME_CONTENT);
+ return;
+ }
+
+ // Everything else has to be a top-level request
+ receivedQueries += `top-${queryString}`;
+ setState("receivedQueries", `${receivedQueries}-${queryScheme}`);
+ response.write(DOCUMENT_CONTENT(queryString));
+}
diff --git a/dom/security/test/https-only/file_insecure_reload.sjs b/dom/security/test/https-only/file_insecure_reload.sjs
new file mode 100644
index 0000000000..a48ec4e20f
--- /dev/null
+++ b/dom/security/test/https-only/file_insecure_reload.sjs
@@ -0,0 +1,27 @@
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1702001
+
+// An onload postmessage to window opener
+const ON_LOAD = `
+ <html>
+ <body>
+ send onload message...
+ <script type="application/javascript">
+ window.opener.postMessage({result: 'you entered the http page', historyLength: history.length}, '*');
+ </script>
+ </body>
+ </html>`;
+
+// When an https request is sent, cause a timeout so that the https-only error
+// page is displayed.
+function handleRequest(request, response) {
+ 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;
+ }
+ if (request.scheme === "http") {
+ response.write(ON_LOAD);
+ }
+}
diff --git a/dom/security/test/https-only/file_redirect.sjs b/dom/security/test/https-only/file_redirect.sjs
new file mode 100644
index 0000000000..c66cbaa226
--- /dev/null
+++ b/dom/security/test/https-only/file_redirect.sjs
@@ -0,0 +1,37 @@
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
+
+// Step 1. Send request with redirect queryString (eg. file_redirect.sjs?302)
+// Step 2. Server responds with corresponding redirect code to http://example.com/../file_redirect.sjs?check
+// Step 3. Response from ?check indicates whether the redirected request was secure or not.
+
+const RESPONSE_SECURE = "secure-ok";
+const RESPONSE_INSECURE = "secure-error";
+const RESPONSE_ERROR = "unexpected-query";
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ const query = request.queryString;
+
+ // Send redirect header
+ if ((query >= 301 && query <= 303) || query == 307) {
+ const loc =
+ "http://example.com/tests/dom/security/test/https-only/file_redirect.sjs?check";
+ response.setStatusLine(request.httpVersion, query, "Moved");
+ response.setHeader("Location", loc, false);
+ return;
+ }
+
+ // Check if scheme is http:// oder https://
+ if (query == "check") {
+ const secure =
+ request.scheme == "https" ? RESPONSE_SECURE : RESPONSE_INSECURE;
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.write(secure);
+ return;
+ }
+
+ // This should not happen
+ response.setStatusLine(request.httpVersion, 500, "OK");
+ response.write(RESPONSE_ERROR);
+}
diff --git a/dom/security/test/https-only/file_redirect_tainting.sjs b/dom/security/test/https-only/file_redirect_tainting.sjs
new file mode 100644
index 0000000000..9ecca88d6d
--- /dev/null
+++ b/dom/security/test/https-only/file_redirect_tainting.sjs
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
+);
+
+const body = `<!DOCTYPE html>
+<html lang="en">
+ <body>
+ <script>
+ let image = new Image();
+ image.crossOrigin = "anonymous";
+ image.src = "http://example.net/browser/dom/security/test/https-only/file_redirect_tainting.sjs?img";
+ image.id = "test_image";
+ document.body.appendChild(image);
+ </script>
+ </body>
+</html>`;
+
+function handleRequest(request, response) {
+ if (request.queryString === "html") {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "text/html");
+ response.write(body);
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png");
+ let origin = request.getHeader("Origin");
+ response.setHeader("Access-Control-Allow-Origin", origin);
+ response.write(IMG_BYTES);
+}
diff --git a/dom/security/test/https-only/file_redirect_to_insecure.sjs b/dom/security/test/https-only/file_redirect_to_insecure.sjs
new file mode 100644
index 0000000000..ea88223926
--- /dev/null
+++ b/dom/security/test/https-only/file_redirect_to_insecure.sjs
@@ -0,0 +1,16 @@
+// Redirect back to http if visited via https. This way we can simulate
+// a site which can not be upgraded by HTTPS-Only.
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ if (request.scheme === "https") {
+ response.setStatusLine(request.httpVersion, "302", "Found");
+ response.setHeader(
+ "Location",
+ // We explicitly want a insecure URL here, so disable eslint
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ `http://${request.host}${request.path}`,
+ false
+ );
+ }
+}
diff --git a/dom/security/test/https-only/file_save_as.html b/dom/security/test/https-only/file_save_as.html
new file mode 100644
index 0000000000..44232b16ec
--- /dev/null
+++ b/dom/security/test/https-only/file_save_as.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <a href="http://example.org" id="insecure-link">Insecure Link</a>
+ <a href="https://example.org" id="secure-link">Secure Link</a>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/security/test/https-only/file_upgrade_insecure.html b/dom/security/test/https-only/file_upgrade_insecure.html
new file mode 100644
index 0000000000..346cfbeb9c
--- /dev/null
+++ b/dom/security/test/https-only/file_upgrade_insecure.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1613063 - HTTPS Only Mode</title>
+ <!-- style -->
+ <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?style' media='screen' />
+
+ <!-- font -->
+ <style>
+ @font-face {
+ font-family: "foofont";
+ src: url('http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?font');
+ }
+ .div_foo { font-family: "foofont"; }
+ </style>
+</head>
+<body>
+
+ <!-- images: -->
+ <img src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?img"></img>
+
+ <!-- redirects: upgrade http:// to https:// redirect to http:// and then upgrade to https:// again -->
+ <img src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?redirect-image"></img>
+
+ <!-- script: -->
+ <script src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?script"></script>
+
+ <!-- media: -->
+ <audio src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?media"></audio>
+
+ <!-- objects: -->
+ <object width="10" height="10" data="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?object"></object>
+
+ <!-- font: (apply font loaded in header to div) -->
+ <div class="div_foo">foo</div>
+
+ <!-- iframe: (same origin) -->
+ <iframe src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?iframe">
+ <!-- within that iframe we load an image over http and make sure the requested gets upgraded to https -->
+ </iframe>
+
+ <!-- xhr: -->
+ <script type="application/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?xhr");
+ myXHR.send(null);
+ </script>
+
+ <!-- websockets: upgrade ws:// to wss://-->
+ <script type="application/javascript">
+ // WebSocket tests are not supported on Android yet. Bug 1566168
+ const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ if (AppConstants.platform !== "android") {
+ var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure");
+ mySocket.onopen = function(e) {
+ if (mySocket.url.includes("wss://")) {
+ window.parent.postMessage({result: "websocket-ok"}, "*");
+ }
+ else {
+ window.parent.postMessage({result: "websocket-error"}, "*");
+ }
+ mySocket.close();
+ };
+ mySocket.onerror = function(e) {
+ // debug information for Bug 1316305
+ dump(" xxx mySocket.onerror: (mySocket): " + mySocket + "\n");
+ dump(" xxx mySocket.onerror: (mySocket.url): " + mySocket.url + "\n");
+ dump(" xxx mySocket.onerror: (e): " + e + "\n");
+ dump(" xxx mySocket.onerror: (e.message): " + e.message + "\n");
+ dump(" xxx mySocket.onerror: This might be related to Bug 1316305!\n");
+ window.parent.postMessage({result: "websocket-unexpected-error"}, "*");
+ };
+ }
+ </script>
+
+ <!-- form action: (upgrade POST from http:// to https://) -->
+ <iframe name='formFrame' id='formFrame'></iframe>
+ <form target="formFrame" action="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?form" method="POST">
+ <input name="foo" value="foo">
+ <input type="submit" id="submitButton" formenctype='multipart/form-data' value="Submit form">
+ </form>
+ <script type="text/javascript">
+ var submitButton = document.getElementById('submitButton');
+ submitButton.click();
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_upgrade_insecure_server.sjs b/dom/security/test/https-only/file_upgrade_insecure_server.sjs
new file mode 100644
index 0000000000..7cf590141c
--- /dev/null
+++ b/dom/security/test/https-only/file_upgrade_insecure_server.sjs
@@ -0,0 +1,112 @@
+// SJS file for HTTPS-Only Mode mochitests
+// Bug 1613063 - HTTPS Only Mode
+
+const TOTAL_EXPECTED_REQUESTS = 11;
+
+const IFRAME_CONTENT =
+ "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head><meta charset='utf-8'>" +
+ "<title>Bug 1613063 - HTTPS Only Mode</title>" +
+ "</head>" +
+ "<body>" +
+ "<img src='http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?nested-img'></img>" +
+ "</body>" +
+ "</html>";
+
+const expectedQueries = [
+ "script",
+ "style",
+ "img",
+ "iframe",
+ "form",
+ "xhr",
+ "media",
+ "object",
+ "font",
+ "img-redir",
+ "nested-img",
+];
+
+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 == "queryresult") {
+ setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString());
+ setState("receivedQueries", "");
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // handle img redirect (https->http)
+ if (queryString == "redirect-image") {
+ var newLocation =
+ "http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?img-redir";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ 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 https
+ queryString += request.scheme == "https" ? "-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(queryString)) {
+ return;
+ }
+
+ // append the result to the total query string array
+ if (receivedQueries != "") {
+ receivedQueries += ",";
+ }
+ receivedQueries += queryString;
+ 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());
+
+ // return content (img) for the nested iframe to test
+ // that subresource requests within nested contexts
+ // get upgraded as well. We also have to return
+ // the iframe context in case of an error so we
+ // can test both, using upgrade-insecure as well
+ // as the base case of not using upgrade-insecure.
+ if (queryString == "iframe-ok" || queryString == "iframe-error") {
+ response.write(IFRAME_CONTENT);
+ }
+
+ // if we have received all the requests, we return
+ // the result back.
+ if (totaltests == 0) {
+ getObjectState("queryResult", function (queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+ receivedQueries = getState("receivedQueries");
+ queryResponse.write(receivedQueries);
+ queryResponse.finish();
+ });
+ }
+}
diff --git a/dom/security/test/https-only/file_upgrade_insecure_wsh.py b/dom/security/test/https-only/file_upgrade_insecure_wsh.py
new file mode 100644
index 0000000000..b7159c742b
--- /dev/null
+++ b/dom/security/test/https-only/file_upgrade_insecure_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/https-only/file_user_gesture.html b/dom/security/test/https-only/file_user_gesture.html
new file mode 100644
index 0000000000..ac67064bf0
--- /dev/null
+++ b/dom/security/test/https-only/file_user_gesture.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1725026 - HTTPS Only Mode - Test if a load triggered by a user gesture can be upgraded to HTTPS</title>
+</head>
+<body>
+ <button id="httpLinkButton" onclick="location.href='http://example.com/tests/dom/security/test/https-only/file_console_logging.html'" type="button">
+ button</button>
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_websocket_exceptions.html b/dom/security/test/https-only/file_websocket_exceptions.html
new file mode 100644
index 0000000000..6c6ba07480
--- /dev/null
+++ b/dom/security/test/https-only/file_websocket_exceptions.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+ Dummy file which gets iframe injected<br/>
+</body>
+</html>
diff --git a/dom/security/test/https-only/file_websocket_exceptions_iframe.html b/dom/security/test/https-only/file_websocket_exceptions_iframe.html
new file mode 100644
index 0000000000..23c6af2d45
--- /dev/null
+++ b/dom/security/test/https-only/file_websocket_exceptions_iframe.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script>
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+ window.removeEventListener("message", receiveMessage);
+
+ var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure");
+ mySocket.onopen = function(e) {
+ parent.dispatchEvent(new CustomEvent("WebSocketEnded", {
+ detail: { url: mySocket.url, state: "onopen" }
+ }));
+ mySocket.close();
+ };
+ mySocket.onerror = function(e) {
+ parent.dispatchEvent(new CustomEvent("WebSocketEnded", {
+ detail: { url: mySocket.url, state: "onerror" }
+ }));
+ mySocket.close();
+ };
+}
+</script>
+</head>
+<body>
+ Https-Only: WebSocket exemption test in iframe</br>
+</body>
+</html>
diff --git a/dom/security/test/https-only/hsts_headers.sjs b/dom/security/test/https-only/hsts_headers.sjs
new file mode 100644
index 0000000000..72e82caaf3
--- /dev/null
+++ b/dom/security/test/https-only/hsts_headers.sjs
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ if (request.queryString === "reset") {
+ // Reset the HSTS policy, prevent influencing other tests
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Strict-Transport-Security", "max-age=0");
+ response.write("Resetting HSTS");
+ return;
+ }
+ let hstsHeader = "max-age=60";
+ response.setHeader("Strict-Transport-Security", hstsHeader);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ // Set header for csp upgrade
+ response.setHeader(
+ "Content-Security-Policy",
+ "upgrade-insecure-requests",
+ false
+ );
+ response.setStatusLine(request.httpVersion, 200);
+ response.write("<!DOCTYPE html><html><body><h1>Ok!</h1></body></html>");
+}
diff --git a/dom/security/test/https-only/mochitest.toml b/dom/security/test/https-only/mochitest.toml
new file mode 100644
index 0000000000..e7ecd439c4
--- /dev/null
+++ b/dom/security/test/https-only/mochitest.toml
@@ -0,0 +1,65 @@
+[DEFAULT]
+support-files = [
+ "file_redirect.sjs",
+ "file_upgrade_insecure.html",
+ "file_upgrade_insecure_server.sjs",
+ "file_upgrade_insecure_wsh.py",
+]
+prefs = [
+ "dom.security.https_first=false",
+ "security.mixed_content.upgrade_display_content=false",
+]
+
+["test_break_endless_upgrade_downgrade_loop.html"]
+skip-if = [
+ "os == 'android'", # no support for error pages, Bug 1697866
+ "http3",
+ "http2",
+]
+support-files = [
+ "file_break_endless_upgrade_downgrade_loop.sjs",
+ "file_user_gesture.html",
+]
+
+["test_fragment.html"]
+support-files = ["file_fragment.html"]
+
+["test_http_background_auth_request.html"]
+support-files = ["file_http_background_auth_request.sjs"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_http_background_request.html"]
+support-files = ["file_http_background_request.sjs"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_insecure_reload.html"]
+support-files = ["file_insecure_reload.sjs"]
+skip-if = ["os == 'android'"] # no https-only errorpage support in android
+
+["test_redirect_upgrade.html"]
+scheme = "https"
+fail-if = ["xorigin"]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_resource_upgrade.html"]
+scheme = "https"
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_user_suggestion_box.html"]
+skip-if = [
+ "os == 'android'", # no https-only errorpage support in android
+ "http3",
+ "http2",
+]
diff --git a/dom/security/test/https-only/test_break_endless_upgrade_downgrade_loop.html b/dom/security/test/https-only/test_break_endless_upgrade_downgrade_loop.html
new file mode 100644
index 0000000000..847a71f378
--- /dev/null
+++ b/dom/security/test/https-only/test_break_endless_upgrade_downgrade_loop.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1691888: Break endless upgrade downgrade loops when using https-only</title>
+<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">
+"use strict";
+/*
+ * Description of the test:
+ * We perform three tests where our upgrade/downgrade redirect loop detector should break the
+ * endless loop:
+ * Test 1: Meta Refresh
+ * Test 2: JS Redirect
+ * Test 3: 302 redirect
+ */
+
+SimpleTest.requestFlakyTimeout("We need to wait for the HTTPS-Only error page to appear");
+SimpleTest.requestLongerTimeout(10);
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL =
+ "http://example.com/tests/dom/security/test/https-only/file_break_endless_upgrade_downgrade_loop.sjs";
+
+function resolveAfter6Seconds() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 6000);
+ });
+}
+
+async function verifyResult(aTestName) {
+ let errorPageL10nId = "about-httpsonly-title-alert";
+ let innerHTML = content.document.body.innerHTML;
+ ok(innerHTML.includes(errorPageL10nId), "the error page should be shown for " + aTestName);
+}
+
+async function verifyTest4Result() {
+ let pathname = content.document.location.pathname;
+ ok(
+ pathname === "/tests/dom/security/test/https-only/file_user_gesture.html",
+ "the http:// page should be loaded"
+ );
+}
+
+async function runTests() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ["dom.security.https_only_mode_break_upgrade_downgrade_endless_loop", true],
+ ["dom.security.https_only_check_path_upgrade_downgrade_endless_loop", true],
+ ]});
+
+ // Test 1: Meta Refresh Redirect
+ let winTest1 = window.open(REQUEST_URL + "?test1a", "_blank");
+ // Test 2: JS win.location Redirect
+ let winTest2 = window.open(REQUEST_URL + "?test2a", "_blank");
+ // Test 3: 302 Redirect
+ let winTest3 = window.open(REQUEST_URL + "?test3a", "_blank");
+ // Test 4: 302 Redirect with a different path
+ let winTest4 = window.open(REQUEST_URL + "?test4a", "_blank");
+
+ // provide enough time for:
+ // the redirects to occur, and the error page to be displayed
+ await resolveAfter6Seconds();
+
+ await SpecialPowers.spawn(winTest1, ["test1"], verifyResult);
+ winTest1.close();
+
+ await SpecialPowers.spawn(winTest2, ["test2"], verifyResult);
+ winTest2.close();
+
+ await SpecialPowers.spawn(winTest3, ["test3"], verifyResult);
+ winTest3.close();
+
+ await SpecialPowers.spawn(winTest4, ["test4"], verifyTest4Result);
+ winTest4.close();
+
+ SimpleTest.finish();
+}
+
+runTests();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_fragment.html b/dom/security/test/https-only/test_fragment.html
new file mode 100644
index 0000000000..52a63764fb
--- /dev/null
+++ b/dom/security/test/https-only/test_fragment.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1694932: Https-only mode reloads the page in certain cases when there should be just a fragment navigation </title>
+<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">
+"use strict";
+/*
+ * Description of the test:
+ * Perform a test where a button click leads to scroll the page. Test if https-only detects
+ * that the redirection address of the button is on the same page. Instead of a reload https-only
+ * should only scroll.
+ *
+ * Test:
+ * Enable https-only and load the url
+ * Load: http://mozilla.pettay.fi/moztests/fragment.html
+ * Click "Click me"
+ * The page should be scrolled down to 'foo' without a reload
+ * It shouldn't receive a message 'before unload' because the on before unload
+ * function in file_fragment.html should not be called
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const REQUEST_URL = "http://example.com/tests/dom/security/test/https-only/file_fragment.html";
+const EXPECT_URL = REQUEST_URL.replace("http://", "https://");
+let winTest = null;
+let checkButtonClicked = false;
+
+async function receiveMessage(event) {
+ let data = event.data;
+ // checks if click was successful
+ if (!checkButtonClicked){
+ // expected window location ( expected URL)
+ ok(data.result == EXPECT_URL, "location is correct");
+ ok(data.button, "button is clicked");
+ // checks if loading was successful
+ ok(data.info == "onload", "Onloading worked");
+ // button was clicked
+ checkButtonClicked = true;
+ return;
+ }
+ // if Button was clicked once -> test finished
+ // check if hash exist and if hash of location is correct
+ ok(data.button, "button is clicked");
+ ok(data.result == EXPECT_URL + "#foo", "location (hash) is correct");
+ // check that page is scrolled not reloaded
+ ok(data.info == "scrolled-to-foo","Scrolled successfully without reloading!");
+ is(data.documentURI, EXPECT_URL + "#foo", "Document URI is correct");
+ // complete test and close window
+ window.removeEventListener("message",receiveMessage);
+ winTest.close();
+ SimpleTest.finish();
+}
+
+async function runTest() {
+ //Test: With https-only mode activated
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ]});
+ winTest = window.open(REQUEST_URL);
+}
+window.addEventListener("message", receiveMessage);
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_http_background_auth_request.html b/dom/security/test/https-only/test_http_background_auth_request.html
new file mode 100644
index 0000000000..5a7bf665b9
--- /dev/null
+++ b/dom/security/test/https-only/test_http_background_auth_request.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1665062 - HTTPS-Only: Do not cancel channel if auth is in progress</title>
+ <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 send a top-level request which results in a '401 - Unauthorized' and ensure that the
+ * http background request does not accidentally treat that request as a potential timeout.
+ * We make sure that ther HTTPS-Only Mode Error Page does *NOT* show up.
+ */
+
+const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("When Auth is in progress, HTTPS-Only page should not show up");
+SimpleTest.requestLongerTimeout(10);
+
+const EXPECTED_KICK_OFF_REQUEST =
+ "http://test1.example.com/tests/dom/security/test/https-only/file_http_background_auth_request.sjs?foo";
+const EXPECTED_UPGRADE_REQUEST = EXPECTED_KICK_OFF_REQUEST.replace("http://", "https://");
+let EXPECTED_BG_REQUEST = "http://test1.example.com/";
+let requestCounter = 0;
+
+function examiner() {
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
+}
+examiner.prototype = {
+ observe(subject, topic, data) {
+ if (topic !== "specialpowers-http-notify-request") {
+ return;
+ }
+
+ // On Android we have other requests appear here as well. Let's make
+ // sure we only evaluate requests triggered by the test.
+ if (!data.startsWith("http://test1.example.com") &&
+ !data.startsWith("https://test1.example.com")) {
+ return;
+ }
+ ++requestCounter;
+ if (requestCounter == 1) {
+ is(data, EXPECTED_KICK_OFF_REQUEST, "kick off request needs to be http");
+ return;
+ }
+ if (requestCounter == 2) {
+ is(data, EXPECTED_UPGRADE_REQUEST, "upgraded request needs to be https");
+ return;
+ }
+ if (requestCounter == 3) {
+ is(data, EXPECTED_BG_REQUEST, "background request needs to be http and no sensitive info");
+ return;
+ }
+ ok(false, "we should never get here, but just in case");
+ },
+ remove() {
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.AuthBackgroundRequestExaminer = new examiner();
+
+// https-only top-level background request occurs after 3 seconds, hence
+// we use 4 seconds to make sure the background request did not happen.
+function resolveAfter4Seconds() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 4000);
+ });
+}
+
+async function runTests() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ["dom.security.https_only_mode_send_http_background_request", true],
+ ]});
+
+ let testWin = window.open(EXPECTED_KICK_OFF_REQUEST, "_blank");
+
+ // Give the Auth Process and background request some time before moving on.
+ await resolveAfter4Seconds();
+
+ if (AppConstants.platform !== "android") {
+ is(requestCounter, 3, "three requests total (kickoff, upgraded, background)");
+ } else {
+ // On Android, the auth request resolves and hence the background request
+ // is not even kicked off - nevertheless, the error page should not appear!
+ is(requestCounter, 2, "two requests total (kickoff, upgraded)");
+ }
+
+ await SpecialPowers.spawn(testWin, [], () => {
+ let innerHTML = content.document.body.innerHTML;
+ is(innerHTML, "", "expection page should not be displayed");
+ });
+
+ testWin.close();
+
+ window.AuthBackgroundRequestExaminer.remove();
+ SimpleTest.finish();
+}
+
+runTests();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_http_background_request.html b/dom/security/test/https-only/test_http_background_request.html
new file mode 100644
index 0000000000..5dff0a5fdb
--- /dev/null
+++ b/dom/security/test/https-only/test_http_background_request.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1663396: Test HTTPS-Only-Mode top-level background request not leaking sensitive info</title>
+ <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:
+ * Send a top-level request and make sure that the the top-level https-only background request
+ * (a) does only use pre-path information
+ * (b) does not happen if the pref is set to false
+ */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("have to test that https-only mode background request does not happen");
+SimpleTest.requestLongerTimeout(8);
+
+const SJS_PATH = "tests/dom/security/test/https-only/file_http_background_request.sjs?sensitive";
+
+const EXPECTED_KICK_OFF_REQUEST = "http://example.com/" + SJS_PATH;
+const EXPECTED_KICK_OFF_REQUEST_LOCAL = "http://localhost:8/" + SJS_PATH;
+const EXPECTED_UPGRADE_REQUEST = EXPECTED_KICK_OFF_REQUEST.replace("http://", "https://");
+let expectedBackgroundRequest = "";
+let requestCounter = 0;
+
+function examiner() {
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request");
+}
+examiner.prototype = {
+ observe(subject, topic, data) {
+ if (topic !== "specialpowers-http-notify-request") {
+ return;
+ }
+ // On Android we have other requests appear here as well. Let's make
+ // sure we only evaluate requests triggered by the test.
+ if (!data.startsWith("http://example.com") &&
+ !data.startsWith("https://example.com") &&
+ !data.startsWith("http://localhost:8") &&
+ !data.startsWith("https://localhost:8")) {
+ return;
+ }
+ ++requestCounter;
+ if (requestCounter == 1) {
+ ok(
+ data === EXPECTED_KICK_OFF_REQUEST || data === EXPECTED_KICK_OFF_REQUEST_LOCAL,
+ "kick off request needs to be http"
+ );
+ return;
+ }
+ if (requestCounter == 2) {
+ is(data, EXPECTED_UPGRADE_REQUEST, "upgraded request needs to be https");
+ return;
+ }
+ if (requestCounter == 3) {
+ is(data, expectedBackgroundRequest, "background request needs to be http and no sensitive info like path");
+ return;
+ }
+ ok(false, "we should never get here, but just in case");
+ },
+ remove() {
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.BackgroundRequestExaminer = new examiner();
+
+// https-only top-level background request occurs after 3 seconds, hence
+// we use 4 seconds to make sure the background request did not happen.
+function resolveAfter4Seconds() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 4000);
+ });
+}
+
+async function runTests() {
+ // (a) Test http background request to only use prePath information
+ expectedBackgroundRequest = "http://example.com/";
+ requestCounter = 0;
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ["dom.security.https_only_mode_send_http_background_request", true],
+ ["dom.security.https_only_mode_error_page_user_suggestions", false],
+ ]});
+ let testWin = window.open(EXPECTED_KICK_OFF_REQUEST, "_blank");
+ await resolveAfter4Seconds();
+ is(requestCounter, 3, "three requests total (kickoff, upgraded, background)");
+ testWin.close();
+
+ // (x) Test no http background request happens when localhost
+ expectedBackgroundRequest = "";
+ requestCounter = 0;
+ testWin = window.open(EXPECTED_KICK_OFF_REQUEST_LOCAL, "_blank");
+ await resolveAfter4Seconds();
+ is(requestCounter, 1, "one requests total (kickoff, no upgraded, no background)");
+ testWin.close();
+
+ // (b) Test no http background request happens if pref is set to false
+ expectedBackgroundRequest = "";
+ requestCounter = 0;
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ["dom.security.https_only_mode_send_http_background_request", false],
+ ["dom.security.https_only_mode_error_page_user_suggestions", false],
+ ]});
+ testWin = window.open(EXPECTED_KICK_OFF_REQUEST, "_blank");
+ await resolveAfter4Seconds();
+ is(requestCounter, 2, "two requests total (kickoff, upgraded, no background)");
+ testWin.close();
+
+ // clean up and finish tests
+ window.BackgroundRequestExaminer.remove();
+ SimpleTest.finish();
+}
+
+runTests();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_insecure_reload.html b/dom/security/test/https-only/test_insecure_reload.html
new file mode 100644
index 0000000000..d143c9080b
--- /dev/null
+++ b/dom/security/test/https-only/test_insecure_reload.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1702001: Https-only mode does not reload pages after clicking "Continue to HTTP Site", when url contains navigation </title>
+<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">
+"use strict";
+/*
+ * Description of the test:
+ *
+ * Load a page including a fragment portion which does not support https and make
+ * sure that exempting the page from https-only-mode does not result in a fragment
+ * navigation.
+ */
+
+
+ function resolveAfter6Seconds() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 6000);
+ });
+}
+
+SimpleTest.requestFlakyTimeout("We need to wait for the HTTPS-Only error page to appear");
+SimpleTest.waitForExplicitFinish();
+
+let winTest = null;
+let TEST_URL = "http://example.com/tests/dom/security/test/https-only/file_insecure_reload.sjs#nav";
+
+// verify that https-only page appeared
+async function verifyErrorPage() {
+ let errorPageL10nId = "about-httpsonly-title-alert";
+ let innerHTML = content.document.body.innerHTML;
+ ok(innerHTML.includes(errorPageL10nId), "the error page should be shown for ");
+ let button = content.document.getElementById("openInsecure");
+ // Click "Continue to HTTP Site"
+ ok(button, "button exist");
+ if(button) {
+ button.click();
+ }
+}
+// verify that you entered the page and are not still displaying
+// the https-only error page
+async function receiveMessage(event) {
+ // read event
+ let { result, historyLength } = event.data;
+ is(result, "you entered the http page", "The requested page should be shown");
+ is(historyLength, 1, "History should contain one item");
+ window.removeEventListener("message",receiveMessage);
+ winTest.close();
+ SimpleTest.finish();
+}
+
+
+async function runTest() {
+ //Test: With https-only mode activated
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ]});
+ winTest = window.open(TEST_URL);
+ await resolveAfter6Seconds();
+ await SpecialPowers.spawn(winTest,[],verifyErrorPage);
+}
+window.addEventListener("message", receiveMessage);
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_redirect_upgrade.html b/dom/security/test/https-only/test_redirect_upgrade.html
new file mode 100644
index 0000000000..59f02f96d0
--- /dev/null
+++ b/dom/security/test/https-only/test_redirect_upgrade.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
+Test that 302 redirect requests get upgraded to https:// with HTTPS-Only Mode enabled
+-->
+
+<head>
+ <title>HTTPS-Only Mode - XHR Redirect Upgrade</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+ <h1>HTTPS-Only Mode</h1>
+ <p>Upgrade Test for insecure XHR redirects.</p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</a>
+
+ <script type="application/javascript">
+
+ const redirectCodes = ["301", "302", "303", "307"]
+ let currentTest = 0
+
+ function startTest() {
+ const currentCode = redirectCodes[currentTest];
+
+ const myXHR = new XMLHttpRequest();
+ // Make a request to a site (eg. https://file_redirect.sjs?301), which will redirect to http://file_redirect.sjs?check.
+ // The response will either be secure-ok, if the request has been upgraded to https:// or secure-error if it didn't.
+ myXHR.open("GET", `https://example.com/tests/dom/security/test/https-only/file_redirect.sjs?${currentCode}`);
+ myXHR.onload = (e) => {
+ is(myXHR.responseText, "secure-ok", `a ${currentCode} redirect when posting violation report should be blocked`)
+ testDone();
+ }
+ // This should not happen
+ myXHR.onerror = (e) => {
+ ok(false, `Could not query results from server for ${currentCode}-redirect test (" + e.message + ")`);
+ testDone();
+ }
+ myXHR.send();
+ }
+
+ function testDone() {
+ // Check if there are remaining tests
+ if (++currentTest < redirectCodes.length) {
+ startTest()
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, startTest);
+
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/https-only/test_resource_upgrade.html b/dom/security/test/https-only/test_resource_upgrade.html
new file mode 100644
index 0000000000..6584bad020
--- /dev/null
+++ b/dom/security/test/https-only/test_resource_upgrade.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>HTTPS-Only Mode - Resource Upgrade</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>
+ <h1>HTTPS-Only Mode</h1>
+ <p>Upgrade Test for various resources</p>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</a>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+ <script class="testbody" type="text/javascript">
+ /* Description of the test:
+ * We load resources (img, script, sytle, etc) over *http* and make sure
+ * that all the resources get upgraded to use >> https << when the
+ * preference "dom.security.https_only_mode" is set to true. We further
+ * test that subresources within nested contexts (iframes) get upgraded
+ * and also test the handling of server side redirects.
+ *
+ * In detail:
+ * We perform an XHR request to the *.sjs file which is processed async on
+ * the server and waits till all the requests were processed by the server.
+ * Once the server received all the different requests, the server responds
+ * to the initial XHR request with an array of results which must match
+ * the expected results from each test, making sure that all requests
+ * received by the server (*.sjs) were actually *https* requests.
+ */
+
+ const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ const splitRegex = /^(.*)-(.*)$/
+ const testConfig = {
+ topLevelScheme: "http://",
+ results: [
+ "iframe", "script", "img", "img-redir", "font", "xhr", "style",
+ "media", "object", "form", "nested-img"
+ ]
+ }
+ // TODO: WebSocket tests are not supported on Android Yet. Bug 1566168.
+ if (AppConstants.platform !== "android") {
+ testConfig.results.push("websocket");
+ }
+
+
+ function runTest() {
+ // sends an xhr request to the server which is processed async, which only
+ // returns after the server has received all the expected requests.
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult");
+ myXHR.onload = function (e) {
+ var results = myXHR.responseText.split(",");
+ for (var index in results) {
+ checkResult(results[index]);
+ }
+ }
+ myXHR.onerror = function (e) {
+ ok(false, "Could not query results from server (" + e.message + ")");
+ finishTest();
+ }
+ myXHR.send();
+
+ // give it some time and run the testpage
+ SimpleTest.executeSoon(() => {
+ var src = testConfig.topLevelScheme + "example.com/tests/dom/security/test/https-only/file_upgrade_insecure.html";
+ document.getElementById("testframe").src = src;
+ });
+ }
+
+ // a postMessage handler that is used by sandboxed iframes without
+ // 'allow-same-origin' to bubble up results back to this main page.
+ window.addEventListener("message", receiveMessage);
+ function receiveMessage(event) {
+ checkResult(event.data.result);
+ }
+
+ function finishTest() {
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+ }
+
+ function checkResult(response) {
+ // A response looks either like this "iframe-ok" or "[key]-[result]"
+ const [, key, result] = splitRegex.exec(response)
+ // try to find the expected result within the results array
+ var index = testConfig.results.indexOf(key);
+
+ // If the response is not even part of the results array, something is super wrong
+ if (index == -1) {
+ ok(false, `Unexpected response from server (${response})`);
+ finishTest();
+ }
+
+ // take the element out the array and continue till the results array is empty
+ if (index != -1) {
+ testConfig.results.splice(index, 1);
+ }
+
+ // Check if the result was okay or had an error
+ is(result, 'ok', `Upgrade all requests on toplevel http for '${key}' came back with: '${result}'`)
+
+ // If we're not expecting any more resulsts, finish the test
+ if (!testConfig.results.length) {
+ finishTest();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, runTest);
+
+ </script>
+</body>
+
+</html>
diff --git a/dom/security/test/https-only/test_user_suggestion_box.html b/dom/security/test/https-only/test_user_suggestion_box.html
new file mode 100644
index 0000000000..1feabcd003
--- /dev/null
+++ b/dom/security/test/https-only/test_user_suggestion_box.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bug 1665057 - Add www button on https-only error page</title>
+ <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">
+"use strict";
+
+/*
+ * Description of the test:
+ * We send a top-level request to a http-page in https-only mode
+ * The page has a bad certificate and can't be updated so the error page appears
+ * If there is a secure connection possible to www the suggestion-box on the error page
+ * should appear and have a link to that secure www-page
+ * if the original-pagerequest already has a www or there is no secure www connection
+ * the suggestion box should not appear
+ */
+
+SimpleTest.requestFlakyTimeout("We need to wait for the HTTPS-Only error page to appear and for the additional 'www' request to provide a suggestion.");
+SimpleTest.requestLongerTimeout(10);
+
+// urls of server locations with bad cert -> https-only should display error page
+const KICK_OFF_REQUEST_WITH_SUGGESTION = "http://suggestion-example.com";
+const KICK_OFF_REQUEST_WITHOUT_SUGGESTION = "http://no-suggestion-example.com";
+
+// l10n ids to compare html to
+const ERROR_PAGE_L10N_ID = "about-httpsonly-title-alert";
+const SUGGESTION_BOX_L10N_ID = "about-httpsonly-suggestion-box-www-text";
+
+// the error page needs to be build and a background https://www request needs to happen
+// we use 4 seconds to make sure these requests did happen.
+function resolveAfter4Seconds() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve();
+ }, 4000);
+ });
+}
+
+async function runTests(aMessage) {
+ let errorPageL10nId = "about-httpsonly-title-alert";
+ let suggestionBoxL10nId = "about-httpsonly-suggestion-box-www-text";
+
+ let innerHTML = content.document.body.innerHTML;
+
+ if (aMessage === "with_suggestion") {
+ // test if the page with suggestion shows the error page and the suggestion box
+ ok(innerHTML.includes(errorPageL10nId), "the error page should be shown.");
+ ok(innerHTML.includes(suggestionBoxL10nId), "the suggestion box should be shown.");
+ } else if (aMessage === "without_suggestion") {
+ // test if the page without suggestion shows the error page but not the suggestion box
+ ok(innerHTML.includes(errorPageL10nId), "the error page should be shown.");
+ ok(!innerHTML.includes(suggestionBoxL10nId), "the suggestion box should not be shown.");
+ } else {
+ ok(false, "we should never get here");
+ }
+}
+
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({ set: [
+ ["dom.security.https_only_mode", true],
+ ["dom.security.https_only_mode_send_http_background_request", false],
+ ["dom.security.https_only_mode_error_page_user_suggestions", true],
+ ]});
+ let testWinSuggestion = window.open(KICK_OFF_REQUEST_WITH_SUGGESTION, "_blank");
+ let testWinWithoutSuggestion = window.open(KICK_OFF_REQUEST_WITHOUT_SUGGESTION, "_blank");
+
+ await resolveAfter4Seconds();
+
+ await SpecialPowers.spawn(testWinSuggestion, ["with_suggestion"], runTests);
+ await SpecialPowers.spawn(testWinWithoutSuggestion, ["without_suggestion"], runTests);
+
+ testWinSuggestion.close();
+ testWinWithoutSuggestion.close();
+});
+</script>
+</body>
+</html>