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.ini12
-rw-r--r--dom/security/test/https-only/browser_console_logging.js125
-rw-r--r--dom/security/test/https-only/browser_cors_mixedcontent.js123
-rw-r--r--dom/security/test/https-only/browser_httpsonly_prefs.js117
-rw-r--r--dom/security/test/https-only/browser_iframe_test.js185
-rw-r--r--dom/security/test/https-only/browser_triggering_principal_exemption.js71
-rw-r--r--dom/security/test/https-only/browser_upgrade_exceptions.js86
-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_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_iframe_test.sjs58
-rw-r--r--dom/security/test/https-only/file_redirect.sjs37
-rw-r--r--dom/security/test/https-only/file_upgrade_insecure.html89
-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/mochitest.ini18
-rw-r--r--dom/security/test/https-only/test_http_background_auth_request.html109
-rw-r--r--dom/security/test/https-only/test_http_background_request.html123
-rw-r--r--dom/security/test/https-only/test_redirect_upgrade.html58
-rw-r--r--dom/security/test/https-only/test_resource_upgrade.html123
21 files changed, 1541 insertions, 0 deletions
diff --git a/dom/security/test/https-only/browser.ini b/dom/security/test/https-only/browser.ini
new file mode 100644
index 0000000000..41d64209ef
--- /dev/null
+++ b/dom/security/test/https-only/browser.ini
@@ -0,0 +1,12 @@
+[browser_console_logging.js]
+support-files =
+ file_console_logging.html
+[browser_upgrade_exceptions.js]
+[browser_httpsonly_prefs.js]
+[browser_cors_mixedcontent.js]
+support-files =
+ file_cors_mixedcontent.html
+[browser_iframe_test.js]
+support-files =
+ file_iframe_test.sjs
+[browser_triggering_principal_exemption.js]
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..332f423d22
--- /dev/null
+++ b/dom/security/test/https-only/browser_console_logging.js
@@ -0,0 +1,125 @@
+// 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: [
+ "Upgrading insecure request",
+ "to use",
+ "file_console_logging.html",
+ ],
+ },
+ {
+ description: "iFrame upgrade failure should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.error,
+ expectIncludes: [
+ "Upgrading insecure request",
+ "failed",
+ "file_console_logging.html",
+ ],
+ },
+ {
+ description: "WebSocket upgrade should get logged",
+ expectLogLevel: Ci.nsIConsoleMessage.warn,
+ expectIncludes: [
+ "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", "to use", "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";
+
+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.loadURI(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;
+
+ if (message.includes("HTTPS-Only Mode:")) {
+ for (let i = 0; i < tests.length; i++) {
+ const testCase = tests[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);
+ tests.splice(i, 1);
+ break;
+ }
+ }
+}
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..13802e6008
--- /dev/null
+++ b/dom/security/test/https-only/browser_cors_mixedcontent.js
@@ -0,0 +1,123 @@
+// 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.loadURI(browser, SERVER_URL(test.topLevelScheme));
+
+ await loaded;
+
+ 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_httpsonly_prefs.js b/dom/security/test/https-only/browser_httpsonly_prefs.js
new file mode 100644
index 0000000000..a88320038a
--- /dev/null
+++ b/dom/security/test/https-only/browser_httpsonly_prefs.js
@@ -0,0 +1,117 @@
+"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 },
+ 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_iframe_test.js b/dom/security/test/https-only/browser_iframe_test.js
new file mode 100644
index 0000000000..1c0ae27922
--- /dev/null
+++ b/dom/security/test/https-only/browser_iframe_test.js
@@ -0,0 +1,185 @@
+// 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.
+
+add_task(async function() {
+ await setup();
+
+ /*
+ * HTTPS-Only Mode disabled
+ */
+
+ // Top-Level scheme: HTTP
+ await runTest({
+ queryString: "test1.1",
+ topLevelScheme: "http",
+
+ expectedTopLevel: "http",
+ expectedSameOrigin: "http",
+ expectedCrossOrigin: "http",
+ });
+ // Top-Level scheme: HTTPS
+ await runTest({
+ queryString: "test1.2",
+ topLevelScheme: "https",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "fail",
+ expectedCrossOrigin: "fail",
+ });
+
+ /*
+ * HTTPS-Only Mode enabled, no exception
+ */
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ // Top-Level scheme: HTTP
+ await runTest({
+ queryString: "test2.1",
+ topLevelScheme: "http",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "https",
+ expectedCrossOrigin: "https",
+ });
+ // Top-Level scheme: HTTPS
+ await runTest({
+ queryString: "test2.2",
+ topLevelScheme: "https",
+
+ expectedTopLevel: "https",
+ expectedSameOrigin: "https",
+ expectedCrossOrigin: "https",
+ });
+
+ /*
+ * HTTPS-Only enabled, with exception
+ */
+ // 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() {
+ const response = await fetch(SERVER_URL("https") + "setup");
+ const txt = await response.text();
+ if (txt != "ok") {
+ ok(false, "Failed to setup test server.");
+ finish();
+ }
+}
+
+async function evaluate() {
+ const response = await fetch(SERVER_URL("https") + "results");
+ const requestResults = (await response.text()).split(";");
+
+ 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;
+ await BrowserTestUtils.withNewTab("about:blank", async function(browser) {
+ let loaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false, // includeSubFrames
+ SERVER_URL(test.expectedTopLevel) + queryString,
+ false // maybeErrorPage
+ );
+ BrowserTestUtils.loadURI(
+ browser,
+ SERVER_URL(test.topLevelScheme) + queryString
+ );
+ await loaded;
+ });
+
+ 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`);
+ }
+}
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..d2d4038ac8
--- /dev/null
+++ b/dom/security/test/https-only/browser_triggering_principal_exemption.js
@@ -0,0 +1,71 @@
+// 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..8397939fe3
--- /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 (127.0.0.1)",
+ "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/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_http_background_auth_request.sjs b/dom/security/test/https-only/file_http_background_auth_request.sjs
new file mode 100644
index 0000000000..6da4f1977f
--- /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_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_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_upgrade_insecure.html b/dom/security/test/https-only/file_upgrade_insecure.html
new file mode 100644
index 0000000000..c92e8037c0
--- /dev/null
+++ b/dom/security/test/https-only/file_upgrade_insecure.html
@@ -0,0 +1,89 @@
+<!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.Cu.import("resource://gre/modules/AppConstants.jsm", {});
+ 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..4247cfdde1
--- /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.indexOf(queryString) == -1) {
+ 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;
+ }
+ var 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/mochitest.ini b/dom/security/test/https-only/mochitest.ini
new file mode 100644
index 0000000000..3f6aff6e52
--- /dev/null
+++ b/dom/security/test/https-only/mochitest.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+support-files =
+ file_redirect.sjs
+ file_upgrade_insecure.html
+ file_upgrade_insecure_server.sjs
+ file_upgrade_insecure_wsh.py
+prefs =
+ security.mixed_content.upgrade_display_content=false
+
+[test_resource_upgrade.html]
+scheme=https
+[test_redirect_upgrade.html]
+scheme=https
+fail-if = xorigin
+[test_http_background_request.html]
+support-files = file_http_background_request.sjs
+[test_http_background_auth_request.html]
+support-files = file_http_background_auth_request.sjs
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..3af14ec24b
--- /dev/null
+++ b/dom/security/test/https-only/test_http_background_auth_request.html
@@ -0,0 +1,109 @@
+<!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.Cu.import("resource://gre/modules/AppConstants.jsm", {});
+
+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)");
+ }
+
+ let wrappedWin = SpecialPowers.wrap(testWin);
+ is(wrappedWin.document.body.innerHTML, "", "exception 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..a21eb61ba7
--- /dev/null
+++ b/dom/security/test/https-only/test_http_background_request.html
@@ -0,0 +1,123 @@
+<!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],
+ ]});
+ 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],
+ ]});
+ 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_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..661168137d
--- /dev/null
+++ b/dom/security/test/https-only/test_resource_upgrade.html
@@ -0,0 +1,123 @@
+<!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.Cu.import(
+ "resource://gre/modules/AppConstants.jsm",
+ {}
+ );
+ 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 == 0) {
+ finishTest();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Set preference and start test
+ SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, runTest);
+
+ </script>
+</body>
+
+</html>