diff options
Diffstat (limited to '')
60 files changed, 3984 insertions, 0 deletions
diff --git a/dom/security/test/https-first/browser.ini b/dom/security/test/https-first/browser.ini new file mode 100644 index 0000000000..9e355423dc --- /dev/null +++ b/dom/security/test/https-first/browser.ini @@ -0,0 +1,35 @@ +[browser_httpsfirst.js] +support-files = + file_httpsfirst_timeout_server.sjs +[browser_httpsfirst_console_logging.js] +[browser_upgrade_onion.js] +[browser_mixed_content_console.js] +support-files = + file_mixed_content_console.html +[browser_downgrade_view_source.js] +support-files = file_downgrade_view_source.sjs +[browser_navigation.js] +support-files = file_navigation.html +[browser_httpsfirst_speculative_connect.js] +support-files = file_httpsfirst_speculative_connect.html +[browser_download_attribute.js] +support-files = file_download_attribute.html + file_download_attribute.sjs + +[browser_mixed_content_download.js] +skip-if = win10_2004 && debug # Bug 1723573 +support-files = + download_page.html + download_server.sjs +[browser_slow_download.js] +support-files = + file_slow_download.html + file_slow_download.sjs +[browser_downgrade_mixed_content_auto_upgrade_console.js] +support-files = + file_mixed_content_auto_upgrade.html + pass.png + test.ogv + test.wav +[browser_beforeunload_permit_http.js] +support-files = file_beforeunload_permit_http.html diff --git a/dom/security/test/https-first/browser_beforeunload_permit_http.js b/dom/security/test/https-first/browser_beforeunload_permit_http.js new file mode 100644 index 0000000000..dda5fbbba9 --- /dev/null +++ b/dom/security/test/https-first/browser_beforeunload_permit_http.js @@ -0,0 +1,208 @@ +"use strict"; + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://nocert.example.com/" +); +/* + * Description of Tests: + * + * Test load page and reload: + * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction + * 2. Open an HTTP site. HTTPS-First will try to upgrade it to https - but since it has no cert that try will fail + * 3. Then simulated user interaction and reload the page with a reload flag. + * 4. That should lead to a beforeUnload prompt that asks for users permission to perform reload. HTTPS-First should not try to upgrade the reload again + * + * Test Navigation: + * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction + * 2. Open an http site. HTTPS-First will try to upgrade it to https - but since it has no cert for https that try will fail + * 3. Then simulated user interaction and navigate to another http page. Again HTTPS-First will try to upgrade to HTTPS + * 4. This attempted navigation leads to a prompt which askes for permission to leave page - accept it + * 5. Since the site is not using a valid HTTPS cert HTTPS-First will downgrade the request back to HTTP + * 6. User should NOT get asked again for permission to unload + * + * Test Session History Navigation: + * 1. Enable HTTPS-First and the pref to trigger beforeunload by user interaction + * 2. Open an http site. HTTPS-First will try to upgrade it to https - but since it has no cert for https that try will fail + * 3. Then navigate to another http page and simulated a user interaction. + * 4. Trigger a session history navigation by clicking the "back button". + * 5. This attempted navigation leads to a prompt which askes for permission to leave page - accept it + */ +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.security.https_first", true], + ["dom.require_user_interaction_for_beforeunload", true], + ], + }); +}); +const TESTS = [ + { + name: "Normal Reload (No flag)", + reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + }, + { + name: "Bypass Cache Reload", + reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE, + }, + { + name: "Bypass Proxy Reload", + reloadFlag: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY, + }, + { + name: "Bypass Cache and Proxy Reload", + reloadFlag: + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE | + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY, + }, +]; + +add_task(async function testReloadFlags() { + for (let index = 0; index < TESTS.length; index++) { + const testCase = TESTS[index]; + // The onbeforeunload dialog should appear + let dialogPromise = PromptTestUtils.waitForPrompt(null, { + modalType: Services.prompt.MODAL_TYPE_CONTENT, + promptType: "confirmEx", + }); + let reloadPromise = loadPageAndReload(testCase); + let dialog = await dialogPromise; + Assert.ok(true, "Showed the beforeunload dialog."); + await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 }); + await reloadPromise; + } +}); + +add_task(async function testNavigation() { + // The onbeforeunload dialog should appear + let dialogPromise = PromptTestUtils.waitForPrompt(null, { + modalType: Services.prompt.MODAL_TYPE_CONTENT, + promptType: "confirmEx", + }); + + let openPagePromise = openPage(); + let dialog = await dialogPromise; + Assert.ok(true, "Showed the beforeunload dialog."); + await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 }); + await openPagePromise; +}); + +add_task(async function testSessionHistoryNavigation() { + // The onbeforeunload dialog should appear + let dialogPromise = PromptTestUtils.waitForPrompt(null, { + modalType: Services.prompt.MODAL_TYPE_CONTENT, + promptType: "confirmEx", + }); + + let openPagePromise = loadPagesAndUseBackButton(); + let dialog = await dialogPromise; + Assert.ok(true, "Showed the beforeunload dialog."); + await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 }); + await openPagePromise; +}); + +async function openPage() { + // Open about:blank in a new tab + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // Load http page + BrowserTestUtils.loadURIString( + browser, + `${TEST_PATH_HTTP}file_beforeunload_permit_http.html` + ); + await BrowserTestUtils.browserLoaded(browser); + // Interact with page such that unload permit will be necessary + await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser); + let hasInteractedWith = await SpecialPowers.spawn( + browser, + [""], + function () { + return content.document.userHasInteracted; + } + ); + + is(true, hasInteractedWith, "Simulated successfully user interaction"); + // And then navigate away to another site which proves that user won't be asked twice to permit a reload (otherwise the test get timed out) + BrowserTestUtils.loadURIString( + browser, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://self-signed.example.com/" + ); + await BrowserTestUtils.browserLoaded(browser); + Assert.ok(true, "Navigated successfully."); + } + ); +} + +async function loadPageAndReload(testCase) { + // Load initial site + // Open about:blank in a new tab + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + BrowserTestUtils.loadURIString( + browser, + `${TEST_PATH_HTTP}file_beforeunload_permit_http.html` + ); + await BrowserTestUtils.browserLoaded(browser); + // Interact with page such that unload permit will be necessary + await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser); + + let hasInteractedWith = await SpecialPowers.spawn( + browser, + [""], + function () { + return content.document.userHasInteracted; + } + ); + is(true, hasInteractedWith, "Simulated successfully user interaction"); + BrowserReloadWithFlags(testCase.reloadFlag); + await BrowserTestUtils.browserLoaded(browser); + is(true, true, `reload with flag ${testCase.name} was successful`); + } + ); +} + +async function loadPagesAndUseBackButton() { + // Load initial site + // Open about:blank in a new tab + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + BrowserTestUtils.loadURIString( + browser, + `${TEST_PATH_HTTP}file_beforeunload_permit_http.html` + ); + await BrowserTestUtils.browserLoaded(browser); + + BrowserTestUtils.loadURIString( + browser, + `${TEST_PATH_HTTP}file_beforeunload_permit_http.html?getASessionHistoryEntry` + ); + await BrowserTestUtils.browserLoaded(browser); + // Interact with page such that unload permit will be necessary + await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser); + + let hasInteractedWith = await SpecialPowers.spawn( + browser, + [""], + function () { + return content.document.userHasInteracted; + } + ); + is(true, hasInteractedWith, "Simulated successfully user interaction"); + // Go back one site by clicking the back button + info("Clicking back button"); + let backButton = document.getElementById("back-button"); + backButton.click(); + await BrowserTestUtils.browserLoaded(browser); + is(true, true, `Got back successful`); + } + ); +} diff --git a/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js b/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js new file mode 100644 index 0000000000..2235d7392c --- /dev/null +++ b/dom/security/test/https-first/browser_downgrade_mixed_content_auto_upgrade_console.js @@ -0,0 +1,80 @@ +// Bug 1673574 - Improve Console logging for mixed content auto upgrading +"use strict"; + +const testPath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://httpsfirst.com" +); + +let tests = [ + { + description: "Top-Level upgrade should get logged", + expectLogLevel: Ci.nsIConsoleMessage.warn, + expectIncludes: ["Upgrading insecure request", "to use", "httpsfirst.com"], + }, + { + description: "Top-Level upgrade failure should get logged", + expectLogLevel: Ci.nsIConsoleMessage.warn, + expectIncludes: [ + "Upgrading insecure request", + "failed", + "httpsfirst.com", + "Downgrading to", + ], + }, +]; + +const kTestURI = testPath + "file_mixed_content_auto_upgrade.html"; + +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 ML2 and HTTPS-First Mode and register console-listener + await SpecialPowers.pushPrefEnv({ + set: [ + ["security.mixed_content.upgrade_display_content", true], + ["dom.security.https_first", true], + ], + }); + Services.console.registerListener(on_new_message); + // 1. Upgrade page to https:// + await BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, kTestURI); + + 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; + + // The console message is: + // Should only show HTTPS-First messages + + if (message.includes("Mixed Content:")) { + ok( + !message.includes("Upgrading insecure display request"), + "msg included a mixed content upgrade" + ); + } + if (message.includes("HTTPS-First 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-first/browser_downgrade_view_source.js b/dom/security/test/https-first/browser_downgrade_view_source.js new file mode 100644 index 0000000000..38ff468490 --- /dev/null +++ b/dom/security/test/https-first/browser_downgrade_view_source.js @@ -0,0 +1,81 @@ +// This test ensures that view-source:https falls back to view-source:http +"use strict"; + +const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +const TEST_PATH_HTTPS = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +async function runTest(desc, url, expectedURI, excpectedContent) { + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true); + BrowserTestUtils.loadURIString(browser, url); + await loaded; + + await SpecialPowers.spawn( + browser, + [desc, expectedURI, excpectedContent], + async function (desc, expectedURI, excpectedContent) { + let loadedURI = content.document.documentURI; + is(loadedURI, expectedURI, desc); + let loadedContent = content.document.body.textContent; + is(loadedContent, excpectedContent, desc); + } + ); + }); +} + +add_task(async function () { + requestLongerTimeout(2); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + + await runTest( + "URL with query 'downgrade' should be http:", + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade`, + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade`, + "view-source:http://" + ); + + await runTest( + "URL with query 'downgrade' should be http and leave query params untouched:", + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade&https://httpsfirst.com`, + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?downgrade&https://httpsfirst.com`, + "view-source:http://" + ); + + await runTest( + "URL with query 'upgrade' should be https:", + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?upgrade`, + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`, + "view-source:https://" + ); + + await runTest( + "URL with query 'upgrade' should be https:", + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`, + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade`, + "view-source:https://" + ); + + await runTest( + "URL with query 'upgrade' should be https and leave query params untouched:", + `view-source:${TEST_PATH_HTTP}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`, + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`, + "view-source:https://" + ); + + await runTest( + "URL with query 'upgrade' should be https and leave query params untouched:", + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`, + `view-source:${TEST_PATH_HTTPS}/file_downgrade_view_source.sjs?upgrade&https://httpsfirst.com`, + "view-source:https://" + ); +}); diff --git a/dom/security/test/https-first/browser_download_attribute.js b/dom/security/test/https-first/browser_download_attribute.js new file mode 100644 index 0000000000..81bd0d799a --- /dev/null +++ b/dom/security/test/https-first/browser_download_attribute.js @@ -0,0 +1,125 @@ +"use strict"; + +// Create a uri for an http site +//(in that case a site without cert such that https-first isn't upgrading it) +const insecureTestPath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://nocert.example.com" +); +const insecureTestURI = insecureTestPath + "file_download_attribute.html"; + +function promisePanelOpened() { + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + return Promise.resolve(); + } + return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown"); +} + +const CONSOLE_UPGRADE_TRY_MESSAGE = "Upgrading insecure request"; +const CONSOLE_ERROR_MESSAGE = "Downgrading to “http” again"; +const DOWNLOAD_PAGE_URL = + "nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.html"; +const DOWNLOAD_LINK_URL = + "nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.sjs"; + +// Verifys that https-first tried to upgrade the download +// - and that the upgrade attempt failed. +// We will receive 4 messages. Two for upgrading and downgrading +// the download page and another two for upgrading and downgrading +// the download. +let msgCounter = 0; +function shouldConsoleTryUpgradeAndError() { + // Waits until CONSOLE_ERROR_MESSAGE was logged. + // Checks if download was tried via http:// + return new Promise((resolve, reject) => { + function listener(msgObj) { + let text = msgObj.message; + // Verify upgrade messages + if ( + text.includes(CONSOLE_UPGRADE_TRY_MESSAGE) && + text.includes("http://") + ) { + if (msgCounter == 0) { + ok( + text.includes(DOWNLOAD_PAGE_URL), + "Tries to upgrade nocert example to https" + ); + } else { + ok( + text.includes(DOWNLOAD_LINK_URL), + "Tries to upgrade download to https" + ); + } + msgCounter++; + } + // Verify downgrade messages + if (text.includes(CONSOLE_ERROR_MESSAGE) && msgCounter > 0) { + if (msgCounter == 1) { + ok( + text.includes("https://" + DOWNLOAD_PAGE_URL), + "Downgrades nocert example to http" + ); + msgCounter++; + } else { + ok( + text.includes("https://" + DOWNLOAD_LINK_URL), + "Downgrades download to http" + ); + Services.console.unregisterListener(listener); + resolve(); + } + } + } + Services.console.registerListener(listener); + }); +} + +// Test https-first download of an html file from an http site. +// Test description: +// 1. https-first tries to upgrade site to https +// 2. upgrade fails because site has no certificate +// 3. https-first downgrades to http and starts download via http +// 4. Successfully completes download +add_task(async function test_with_downloads_pref_enabled() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + let checkPromise = shouldConsoleTryUpgradeAndError(); + let downloadsPanelPromise = promisePanelOpened(); + let downloadsPromise = Downloads.getList(Downloads.PUBLIC); + + BrowserTestUtils.loadURIString(gBrowser, insecureTestURI); + // wait for downloadsPanel to open before continuing with test + await downloadsPanelPromise; + let downloadList = await downloadsPromise; + await checkPromise; + is(DownloadsPanel.isPanelShowing, true, "DownloadsPanel should be open."); + is( + downloadList._downloads.length, + 1, + "File should be successfully downloaded." + ); + + let [download] = downloadList._downloads; + is(download.contentType, "text/html", "File contentType should be correct."); + // ensure https-first didn't upgrade the scheme. + is( + download.source.url, + insecureTestPath + "file_download_attribute.sjs", + "Scheme should be http." + ); + + info("cleaning up downloads"); + try { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (error) { + info("The file " + download.target.path + " is not removed, " + error); + } + + await downloadList.remove(download); + await download.finalize(); +}); diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js new file mode 100644 index 0000000000..a2f916a2f0 --- /dev/null +++ b/dom/security/test/https-first/browser_httpsfirst.js @@ -0,0 +1,74 @@ +"use strict"; + +const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +const TIMEOUT_PAGE_URI_HTTP = + TEST_PATH_HTTP + "file_httpsfirst_timeout_server.sjs"; + +async function runPrefTest(aURI, aDesc, aAssertURLStartsWith) { + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + const loaded = BrowserTestUtils.browserLoaded(browser, false, null, true); + BrowserTestUtils.loadURIString(browser, aURI); + await loaded; + + await ContentTask.spawn( + browser, + { aDesc, aAssertURLStartsWith }, + function ({ aDesc, aAssertURLStartsWith }) { + ok( + content.document.location.href.startsWith(aAssertURLStartsWith), + aDesc + ); + } + ); + }); +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", false]], + }); + + await runPrefTest( + "http://example.com", + "HTTPS-First disabled; Should not upgrade", + "http://" + ); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + + await runPrefTest( + "http://example.com", + "Should upgrade upgradeable website", + "https://" + ); + + await runPrefTest( + "http://httpsfirst.com", + "Should downgrade after error.", + "http://" + ); + + await runPrefTest( + "http://httpsfirst.com/?https://httpsfirst.com", + "Should downgrade after error and leave query params untouched.", + "http://httpsfirst.com/?https://httpsfirst.com" + ); + + await runPrefTest( + "http://domain.does.not.exist", + "Should not downgrade on dnsNotFound error.", + "https://" + ); + + await runPrefTest( + TIMEOUT_PAGE_URI_HTTP, + "Should downgrade after timeout.", + "http://" + ); +}); diff --git a/dom/security/test/https-first/browser_httpsfirst_console_logging.js b/dom/security/test/https-first/browser_httpsfirst_console_logging.js new file mode 100644 index 0000000000..55f114ced1 --- /dev/null +++ b/dom/security/test/https-first/browser_httpsfirst_console_logging.js @@ -0,0 +1,71 @@ +// Bug 1658924 - HTTPS-First Mode - Tests for console logging +// https://bugzilla.mozilla.org/show_bug.cgi?id=1658924 +// This test makes sure that the various console messages from the HTTPS-First +// 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", "httpsfirst.com"], + }, + { + description: "Top-Level upgrade failure should get logged", + expectLogLevel: Ci.nsIConsoleMessage.warn, + expectIncludes: [ + "Upgrading insecure request", + "failed", + "httpsfirst.com", + "Downgrading to", + ], + }, +]; + +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-First Mode and register console-listener + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + Services.console.registerListener(on_new_message); + // 1. Upgrade page to https:// + await BrowserTestUtils.loadURIString( + gBrowser.selectedBrowser, + "http://httpsfirst.com" + ); + + 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-First 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-first/browser_httpsfirst_speculative_connect.js b/dom/security/test/https-first/browser_httpsfirst_speculative_connect.js new file mode 100644 index 0000000000..9bf02e797d --- /dev/null +++ b/dom/security/test/https-first/browser_httpsfirst_speculative_connect.js @@ -0,0 +1,69 @@ +"use strict"; + +const TEST_PATH_HTTP = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +let console_messages = [ + { + description: "Speculative Connection should get logged", + expectLogLevel: Ci.nsIConsoleMessage.warn, + expectIncludes: [ + "Upgrading insecure speculative TCP connection", + "to use", + "example.com", + "file_httpsfirst_speculative_connect.html", + ], + }, + { + description: "Upgrade should get logged", + expectLogLevel: Ci.nsIConsoleMessage.warn, + expectIncludes: [ + "Upgrading insecure request", + "to use", + "example.com", + "file_httpsfirst_speculative_connect.html", + ], + }, +]; + +function on_new_console_messages(msgObj) { + const message = msgObj.message; + const logLevel = msgObj.logLevel; + + if (message.includes("HTTPS-First 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_first", true]], + }); + Services.console.registerListener(on_new_console_messages); + + await BrowserTestUtils.loadURIString( + gBrowser.selectedBrowser, + `${TEST_PATH_HTTP}file_httpsfirst_speculative_connect.html` + ); + + await BrowserTestUtils.waitForCondition(() => console_messages.length === 0); + + Services.console.unregisterListener(on_new_console_messages); +}); diff --git a/dom/security/test/https-first/browser_mixed_content_console.js b/dom/security/test/https-first/browser_mixed_content_console.js new file mode 100644 index 0000000000..0b93850ff7 --- /dev/null +++ b/dom/security/test/https-first/browser_mixed_content_console.js @@ -0,0 +1,104 @@ +// Bug 1713593: HTTPS-First: Add test for mixed content blocker. +"use strict"; + +const testPath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "http://example.com" +); + +const UPGRADE_DISPLAY_CONTENT = + "security.mixed_content.upgrade_display_content"; + +let threeMessagesArrived = 0; +let messageImageSeen = false; + +const kTestURI = testPath + "file_mixed_content_console.html"; + +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-First Mode and register console-listener + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + Services.console.registerListener(on_console_message); + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, kTestURI); + + await BrowserTestUtils.waitForCondition(() => threeMessagesArrived === 3); + + Services.console.unregisterListener(on_console_message); +}); + +function on_console_message(msgObj) { + const message = msgObj.message; + + // The first console message is: + // "HTTPS-First Mode: Upgrading insecure request + // ‘http://example.com/browser/dom/security/test/https-first/file_mixed_content_console.html’ to use ‘https’" + if (message.includes("HTTPS-First Mode: Upgrading insecure request")) { + ok(message.includes("Upgrading insecure request"), "request got upgraded"); + ok( + message.includes( + "“http://example.com/browser/dom/security/test/https-first/file_mixed_content_console.html” to use “https”." + ), + "correct top-level request" + ); + threeMessagesArrived++; + } + // If security.mixed_content.upgrade_display_content is enabled: + // The second console message is about upgrading the insecure image + else if ( + Services.prefs.getBoolPref(UPGRADE_DISPLAY_CONTENT) && + message.includes("Mixed Content: Upgrading") + ) { + ok( + message.includes("insecure display request"), + "display content got load" + ); + ok( + message.includes( + "‘http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png’ to use ‘https’" + ), + "img loaded secure" + ); + threeMessagesArrived++; + messageImageSeen = true; + } + // Else: + // The second console message is about blocking the image: + // Message: "Loading mixed (insecure) display content + // “http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png” on a secure page". + // Since the message is send twice, prevent reading the image message two times + else if (message.includes("Loading mixed") && !messageImageSeen) { + ok( + message.includes("Loading mixed (insecure) display content"), + "display content got load" + ); + ok( + message.includes( + "“http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png” on a secure page" + ), + "img loaded insecure" + ); + threeMessagesArrived++; + messageImageSeen = true; + } + // The third message is: + // "Blocked loading mixed active content + // "http://example.com/browser/dom/security/test/https-first/barfoo"" + else if (message.includes("Blocked loading")) { + ok( + message.includes("Blocked loading mixed active content"), + "script got blocked" + ); + ok( + message.includes( + "http://example.com/browser/dom/security/test/https-first/barfoo" + ), + "the right script got blocked" + ); + threeMessagesArrived++; + } +} diff --git a/dom/security/test/https-first/browser_mixed_content_download.js b/dom/security/test/https-first/browser_mixed_content_download.js new file mode 100644 index 0000000000..09ea64cea8 --- /dev/null +++ b/dom/security/test/https-first/browser_mixed_content_download.js @@ -0,0 +1,156 @@ +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs", +}); + +const HandlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" +].getService(Ci.nsIHandlerService); + +const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + +let SECURE_BASE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "https://example.com/" + ) + "download_page.html"; + +/** + * Waits until a download is triggered. + * It waits until a prompt is shown, + * saves and then accepts the dialog. + * @returns {Promise} Resolved once done. + */ + +function shouldTriggerDownload() { + return new Promise((resolve, reject) => { + Services.wm.addListener({ + onOpenWindow(xulWin) { + Services.wm.removeListener(this); + let win = xulWin.docShell.domWindow; + waitForFocus(() => { + if ( + win.location == + "chrome://mozapps/content/downloads/unknownContentType.xhtml" + ) { + let dialog = win.document.getElementById("unknownContentType"); + let button = dialog.getButton("accept"); + let actionRadio = win.document.getElementById("save"); + actionRadio.click(); + button.disabled = false; + dialog.acceptDialog(); + resolve(); + } else { + reject(); + } + }, win); + }, + }); + }); +} + +const CONSOLE_UPGRADE_MESSAGE = "Upgrading insecure request"; +const CONSOLE_DOWNGRADE_MESSAGE = "Downgrading to “http” again."; +const DOWNLOAD_URL = + "example.com/browser/dom/security/test/https-first/download_server.sjs"; +// Verifies that https-first tries to upgrade download, +// falls back since download is not available via https +let msgCounter = 0; +function shouldConsoleError() { + return new Promise((resolve, reject) => { + function listener(msgObj) { + let text = msgObj.message; + if (text.includes(CONSOLE_UPGRADE_MESSAGE) && msgCounter == 0) { + ok( + text.includes("http://" + DOWNLOAD_URL), + "Https-first tries to upgrade download to https" + ); + msgCounter++; + } + if (text.includes(CONSOLE_DOWNGRADE_MESSAGE) && msgCounter == 1) { + ok( + text.includes("https://" + DOWNLOAD_URL), + "Https-first downgrades download to http." + ); + resolve(); + Services.console.unregisterListener(listener); + } + } + Services.console.registerListener(listener); + }); +} + +async function resetDownloads() { + // Removes all downloads from the download List + const types = new Set(); + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + if (download.contentType) { + types.add(download.contentType); + } + publicList.remove(download); + await download.finalize(true); + } + + if (types.size) { + // reset handlers for the contentTypes of any files previously downloaded + for (let type of types) { + const mimeInfo = MIMEService.getFromTypeAndExtension(type, ""); + info("resetting handler for type: " + type); + HandlerService.remove(mimeInfo); + } + } +} + +async function runTest(url, link, checkFunction, description) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.security.https_first", true], + ["browser.download.always_ask_before_handling_new_types", true], + ], + }); + requestLongerTimeout(2); + await resetDownloads(); + + let tab = BrowserTestUtils.addTab(gBrowser, url); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + is( + gBrowser.currentURI.schemeIs("https"), + true, + "Scheme of opened tab should be https" + ); + info("Checking: " + description); + + let checkPromise = checkFunction(); + // Click the Link to trigger the download + SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { + content.document.getElementById(contentLink).click(); + }); + await checkPromise; + ok(true, description); + BrowserTestUtils.removeTab(tab); +} + +//Test description: +// 1. Open "https://example.com" +// 2. From "https://example.com" download something, but that download is only available via http. +// 3. Https-first tries to upgrade the download. +// 4. Upgrading fails - so http-first downgrade download to http. + +add_task(async function test_mixed_download() { + await runTest( + SECURE_BASE_URL, + "insecure", + () => Promise.all([shouldTriggerDownload(), shouldConsoleError()]), + "Secure -> Insecure should Error" + ); + // remove downloaded file + let downloadsPromise = Downloads.getList(Downloads.PUBLIC); + let downloadList = await downloadsPromise; + let [download] = downloadList._downloads; + await downloadList.remove(download); +}); diff --git a/dom/security/test/https-first/browser_navigation.js b/dom/security/test/https-first/browser_navigation.js new file mode 100644 index 0000000000..b8e81f76bb --- /dev/null +++ b/dom/security/test/https-first/browser_navigation.js @@ -0,0 +1,97 @@ +"use strict"; + +const REQUEST_URL = + "http://httpsfirst.com/browser/dom/security/test/https-first/"; + +async function promiseGetHistoryIndex(browser) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + return SpecialPowers.spawn(browser, [], function () { + let shistory = + docShell.browsingContext.childSessionHistory.legacySHistory; + return shistory.index; + }); + } + + let shistory = browser.browsingContext.sessionHistory; + return shistory.index; +} + +async function testNavigations() { + // Load initial site + + let url1 = REQUEST_URL + "file_navigation.html?foo1"; + let url2 = REQUEST_URL + "file_navigation.html?foo2"; + let url3 = REQUEST_URL + "file_navigation.html?foo3"; + + let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + BrowserTestUtils.loadURIString(gBrowser, url1); + await loaded; + + // Load another site + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [url2], + async url => (content.location.href = url) + ); + await loaded; + + // Load another site + loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [url3], + async url => (content.location.href = url) + ); + await loaded; + is( + await promiseGetHistoryIndex(gBrowser.selectedBrowser), + 2, + "correct session history index after load 3" + ); + + // Go back one site by clicking the back button + info("Clicking back button"); + loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url2); + let backButton = document.getElementById("back-button"); + backButton.click(); + await loaded; + is( + await promiseGetHistoryIndex(gBrowser.selectedBrowser), + 1, + "correct session history index after going back for the first time" + ); + + // Go back again + info("Clicking back button again"); + loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url1); + backButton.click(); + await loaded; + is( + await promiseGetHistoryIndex(gBrowser.selectedBrowser), + 0, + "correct session history index after going back for the second time" + ); +} + +add_task(async function () { + waitForExplicitFinish(); + + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + + await testNavigations(); + + // If fission is enabled we also want to test the navigations with the bfcache + // in the parent. + if (SpecialPowers.getBoolPref("fission.autostart")) { + await SpecialPowers.pushPrefEnv({ + set: [["fission.bfcacheInParent", true]], + }); + + await testNavigations(); + } + + finish(); +}); diff --git a/dom/security/test/https-first/browser_slow_download.js b/dom/security/test/https-first/browser_slow_download.js new file mode 100644 index 0000000000..82d7a99b07 --- /dev/null +++ b/dom/security/test/https-first/browser_slow_download.js @@ -0,0 +1,158 @@ +"use strict"; + +XPCOMUtils.defineLazyModuleGetters(this, { + BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm", +}); +// Create a uri for an https site +const testPath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const TEST_URI = testPath + "file_slow_download.html"; +const EXPECTED_DOWNLOAD_URL = + "example.com/browser/dom/security/test/https-first/file_slow_download.sjs"; + +// Since the server send the complete download file after 3 seconds we need an longer timeout +requestLongerTimeout(4); + +function promisePanelOpened() { + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + return Promise.resolve(); + } + return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown"); +} + +/** + * Waits for a download to finish, in case it has not finished already. + * + * @param aDownload + * The Download object to wait upon. + * + * @return {Promise} + * @resolves When the download has finished successfully. + * @rejects JavaScript exception if the download failed. + */ +function promiseDownloadStopped(aDownload) { + if (!aDownload.stopped) { + // The download is in progress, wait for the current attempt to finish and + // report any errors that may occur. + return aDownload.start(); + } + + if (aDownload.succeeded) { + return Promise.resolve(); + } + + // The download failed or was canceled. + return Promise.reject(aDownload.error || new Error("Download canceled.")); +} + +// Verifys that no background request was send +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") + ) { + return; + } + ++requestCounter; + if (requestCounter == 1) { + is(data, TEST_URI, "Download start page is https"); + return; + } + if (requestCounter == 2) { + // The specialpowers-http-notify-request fires before the internal redirect( /upgrade) to + // https happens. + is( + data, + "http://" + EXPECTED_DOWNLOAD_URL, + "First download request is http (internal)" + ); + return; + } + if (requestCounter == 3) { + is( + data, + "https://" + EXPECTED_DOWNLOAD_URL, + "Download got upgraded to https" + ); + return; + } + ok(false, "we should never get here, but just in case"); + }, + remove() { + SpecialPowers.removeObserver(this, "specialpowers-http-notify-request"); + }, +}; + +// Test description: +// 1. Open https://example.com +// 2. Start download - location of download is http +// 3. https-first upgrades to https +// 4. Server send first part of download and after 3 seconds the rest +// 5. Complete download of text file +add_task(async function test_slow_download() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.security.https_first", true]], + }); + + // remove all previous downloads + let downloadsList = await Downloads.getList(Downloads.PUBLIC); + await downloadsList.removeFinished(); + + // add observer to ensure that the background request gets canceled for the upgraded Download + this.examiner = new examiner(); + + let downloadsPanelPromise = promisePanelOpened(); + let downloadsPromise = Downloads.getList(Downloads.PUBLIC); + BrowserTestUtils.loadURIString(gBrowser, TEST_URI); + // wait for downloadsPanel to open before continuing with test + await downloadsPanelPromise; + let downloadList = await downloadsPromise; + is(DownloadsPanel.isPanelShowing, true, "DownloadsPanel should be open."); + is(downloadList._downloads.length, 1, "File should be downloaded."); + let [download] = downloadList._downloads; + // wait for download to finish (with success or error) + await promiseDownloadStopped(download); + is(download.contentType, "text/plain", "File contentType should be correct."); + // ensure https-first did upgrade the scheme. + is( + download.source.url, + "https://" + EXPECTED_DOWNLOAD_URL, + "Scheme should be https." + ); + // ensure that no background request was send + is( + requestCounter, + 3, + "three requests total (download page, download http, download https/ upgraded)" + ); + // ensure that downloaded is complete + is(download.target.size, 25, "Download size is correct"); + //clean up + this.examiner.remove(); + info("cleaning up downloads"); + try { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (error) { + info("The file " + download.target.path + " is not removed, " + error); + } + + await downloadList.remove(download); + await download.finalize(); +}); diff --git a/dom/security/test/https-first/browser_upgrade_onion.js b/dom/security/test/https-first/browser_upgrade_onion.js new file mode 100644 index 0000000000..a6a6a85412 --- /dev/null +++ b/dom/security/test/https-first/browser_upgrade_onion.js @@ -0,0 +1,60 @@ +// This test ensures that various configurable upgrade exceptions work +"use strict"; + +async function runTest(desc, url, expectedURI) { + await BrowserTestUtils.withNewTab("about:blank", async function (browser) { + let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true); + BrowserTestUtils.loadURIString(browser, url); + await loaded; + + await SpecialPowers.spawn( + browser, + [desc, expectedURI], + async function (desc, expectedURI) { + // XXX ckerschb: generally we use the documentURI, but our test infra + // can not handle .onion, hence we use the URI of the failed channel + // stored on the docshell to see if the scheme was upgraded to https. + let loadedURI = content.document.documentURI; + if (loadedURI.startsWith("about:neterror")) { + loadedURI = content.docShell.failedChannel.URI.spec; + } + is(loadedURI, expectedURI, desc); + } + ); + }); +} + +// by default local addresses and .onion should *not* get upgraded +add_task(async function () { + requestLongerTimeout(2); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.security.https_first", true], + ["dom.security.https_only_mode", false], + ["dom.security.https_only_mode.upgrade_local", false], + ["dom.security.https_only_mode.upgrade_onion", false], + ], + }); + + await runTest( + "Hosts ending with .onion should be be exempt from HTTPS-First upgrades by default", + "http://grocery.shopping.for.one.onion/", + "http://grocery.shopping.for.one.onion/" + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.security.https_first", true], + ["dom.security.https_only_mode", false], + ["dom.security.https_only_mode.upgrade_local", false], + ["dom.security.https_only_mode.upgrade_onion", true], + ], + }); + + 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://grocery.shopping.for.one.onion/" + ); +}); diff --git a/dom/security/test/https-first/download_page.html b/dom/security/test/https-first/download_page.html new file mode 100644 index 0000000000..a828ee07db --- /dev/null +++ b/dom/security/test/https-first/download_page.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test mixed content download by https-first</title> + </head> + <body> + hi + + <script> + const host = window.location.host; + const path = location.pathname.replace("download_page.html","download_server.sjs"); + + const insecureLink = document.createElement("a"); + insecureLink.href=`http://${host}${path}`; + insecureLink.download="true"; + insecureLink.id="insecure"; + insecureLink.textContent="Not secure Link"; + + document.body.append(insecureLink); + </script> + </body> +</html> diff --git a/dom/security/test/https-first/download_server.sjs b/dom/security/test/https-first/download_server.sjs new file mode 100644 index 0000000000..7af5722e7b --- /dev/null +++ b/dom/security/test/https-first/download_server.sjs @@ -0,0 +1,16 @@ +function handleRequest(request, response) { + // Only answer to http, in case request is in https let the reply time out. + if (request.scheme === "https") { + response.processAsync(); + return; + } + + response.setHeader("Cache-Control", "no-cache", false); + // Send some file, e.g. an image + response.setHeader( + "Content-Disposition", + "attachment; filename=some-file.png" + ); + response.setHeader("Content-Type", "image/png"); + response.write("Success!"); +} diff --git a/dom/security/test/https-first/file_bad_cert.sjs b/dom/security/test/https-first/file_bad_cert.sjs new file mode 100644 index 0000000000..1a8ae08a86 --- /dev/null +++ b/dom/security/test/https-first/file_bad_cert.sjs @@ -0,0 +1,34 @@ +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, downgraded + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'Error', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +function handleRequest(request, response) { + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + // if the received request is not http send an error + if (request.scheme === "http") { + response.write(RESPONSE_SUCCESS); + return; + } + // we should never get here; just in case, return something unexpected + response.write(RESPONSE_UNEXPECTED); +} diff --git a/dom/security/test/https-first/file_beforeunload_permit_http.html b/dom/security/test/https-first/file_beforeunload_permit_http.html new file mode 100644 index 0000000000..50459d6006 --- /dev/null +++ b/dom/security/test/https-first/file_beforeunload_permit_http.html @@ -0,0 +1,9 @@ +<!DOCTYPE html><html> +<body> + <script> + window.onbeforeunload = function() { + return "stop"; + } + </script> +</body> +</html> diff --git a/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs b/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs new file mode 100644 index 0000000000..eb64c59e97 --- /dev/null +++ b/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs @@ -0,0 +1,61 @@ +"use strict"; + +const REDIRECT_URI = + "http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?verify"; +const DOWNGRADE_URI = + "http://example.com/tests/dom/security/test/https-first/file_downgrade_with_different_path.sjs"; +const RESPONSE_ERROR = "unexpected-query"; + +// An onload postmessage to window opener +const RESPONSE_HTTPS_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-https'}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_HTTP_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-http'}, '*'); + </script> + </body> + </html>`; + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + const query = request.queryString; + + if (query == "downgrade") { + // send same-origin downgrade from https: to http: with a different path. + // we don't consider it's an endless upgrade downgrade loop in this case. + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", DOWNGRADE_URI, false); + return; + } + + // handle the redirect case + if ((query >= 301 && query <= 303) || query == 307) { + // send same-origin downgrade from https: to http: again simluating + // and endless upgrade downgrade loop. + response.setStatusLine(request.httpVersion, query, "Found"); + response.setHeader("Location", REDIRECT_URI, false); + return; + } + + // Check if scheme is http:// or https:// + if (query == "verify") { + let response_content = + request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(response_content); + return; + } + + // We should never get here, but just in case ... + response.setStatusLine(request.httpVersion, 500, "OK"); + response.write("unexepcted query"); +} diff --git a/dom/security/test/https-first/file_data_uri.html b/dom/security/test/https-first/file_data_uri.html new file mode 100644 index 0000000000..69133e5079 --- /dev/null +++ b/dom/security/test/https-first/file_data_uri.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1709069: Test that Data URI which makes a top-level request gets updated in https-first</title> +</head> +<body> +<script class="testbody" type="text/javascript"> + window.onload = (event) => { + let myLoc = window.location.href; + window.opener.parent.postMessage({location: myLoc}, "*"); + window.close(); +}; +</script> +</body> +</html> diff --git a/dom/security/test/https-first/file_downgrade_500_responses.sjs b/dom/security/test/https-first/file_downgrade_500_responses.sjs new file mode 100644 index 0000000000..b3cfbd79dd --- /dev/null +++ b/dom/security/test/https-first/file_downgrade_500_responses.sjs @@ -0,0 +1,70 @@ +// Custom *.sjs file specifically for the needs of Bug 1709552 +"use strict"; + +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, downgraded + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'Error', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +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 the scheme is not https and it is the initial request + // then we rather fall through and display unexpected content + if (request.scheme === "https") { + if (query === "test1a") { + response.setStatusLine("1.1", 501, "Not Implemented"); + response.write("Not Implemented\n"); + return; + } + + if (query === "test2a") { + response.setStatusLine("1.1", 504, "Gateway Timeout"); + response.write("Gateway Timeout\n"); + return; + } + + if (query === "test3a") { + response.setStatusLine("1.1", 521, "Web Server Is Down"); + response.write("Web Server Is Down\n"); + return; + } + if (query === "test4a") { + response.setStatusLine("1.1", 530, "Railgun Error"); + response.write("Railgun Error\n"); + return; + } + if (query === "test5a") { + response.setStatusLine("1.1", 560, "Unauthorized"); + response.write("Unauthorized\n"); + return; + } + + // We should never arrive here, just in case send something unexpected + response.write(RESPONSE_UNEXPECTED); + return; + } + + // We should arrive here when the redirection was downraded successful + response.write(RESPONSE_SUCCESS); +} diff --git a/dom/security/test/https-first/file_downgrade_bad_responses.sjs b/dom/security/test/https-first/file_downgrade_bad_responses.sjs new file mode 100644 index 0000000000..1a6eea2dd1 --- /dev/null +++ b/dom/security/test/https-first/file_downgrade_bad_responses.sjs @@ -0,0 +1,73 @@ +// Custom *.sjs file specifically for the needs of Bug 1709552 +"use strict"; + +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, downgraded + <script type="application/javascript"> + window.opener.postMessage({result: 'downgraded', scheme: 'http'}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + window.opener.postMessage({result: 'Error', scheme: 'http'}, '*'); + </script> + </body> + </html>`; + +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 the scheme is not https and it is the initial request + // then we rather fall through and display unexpected content + if (request.scheme === "https") { + if (query === "test1a") { + response.setStatusLine("1.1", 400, "Bad Request"); + response.write("Bad Request\n"); + return; + } + + if (query === "test2a") { + response.setStatusLine("1.1", 403, "Forbidden"); + response.write("Forbidden\n"); + return; + } + + if (query === "test3a") { + response.setStatusLine("1.1", 404, "Not Found"); + response.write("Not Found\n"); + return; + } + if (query === "test4a") { + response.setStatusLine("1.1", 416, "Requested Range Not Satisfiable"); + response.write("Requested Range Not Satisfiable\n"); + return; + } + if (query === "test5a") { + response.setStatusLine("1.1", 418, "I'm a teapot"); + response.write("I'm a teapot\n"); + return; + } + if (query == "test6a") { + // Simulating a timeout by processing the https request + response.processAsync(); + return; + } + + // We should never arrive here, just in case send something unexpected + response.write(RESPONSE_UNEXPECTED); + return; + } + + // We should arrive here when the redirection was downraded successful + response.write(RESPONSE_SUCCESS); +} diff --git a/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs b/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs new file mode 100644 index 0000000000..6004d57eaf --- /dev/null +++ b/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs @@ -0,0 +1,52 @@ +// Custom *.sjs file specifically for the needs of Bug 1706126 +"use strict"; +// subdomain of example.com +const REDIRECT_302 = + "http://www.redirect-example.com/tests/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs"; + +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, upgraded + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'upgraded', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'error', scheme: scheme}, '*'); + </script> + </body> + </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 https and it is the initial request time it out + if (request.scheme === "https" && request.host === "redirect-example.com") { + // Simulating a timeout by processing the https request + response.processAsync(); + return; + } + if (request.scheme === "http" && request.host === "redirect-example.com") { + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", REDIRECT_302, false); + return; + } + // if the request was sent to subdomain + if (request.host.startsWith("www.")) { + response.write(RESPONSE_SUCCESS); + return; + } + // We should never arrive here, just in case send 'error' + response.write(RESPONSE_UNEXPECTED); +} diff --git a/dom/security/test/https-first/file_downgrade_view_source.sjs b/dom/security/test/https-first/file_downgrade_view_source.sjs new file mode 100644 index 0000000000..c57dd0deb8 --- /dev/null +++ b/dom/security/test/https-first/file_downgrade_view_source.sjs @@ -0,0 +1,30 @@ +"use strict"; + +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.split("&"); + let scheme = request.scheme; + + if (scheme === "https") { + if (query.includes("downgrade")) { + response.setStatusLine("1.1", 400, "Bad Request"); + response.write("Bad Request\n"); + return; + } + if (query.includes("upgrade")) { + response.write("view-source:https://"); + return; + } + } + + if (scheme === "http" && query.includes("downgrade")) { + response.write("view-source:http://"); + return; + } + + // We should arrive here when the redirection was downraded successful + response.write("unexpected scheme and query given"); +} diff --git a/dom/security/test/https-first/file_downgrade_with_different_path.sjs b/dom/security/test/https-first/file_downgrade_with_different_path.sjs new file mode 100644 index 0000000000..7450313d98 --- /dev/null +++ b/dom/security/test/https-first/file_downgrade_with_different_path.sjs @@ -0,0 +1,27 @@ +"use strict"; + +// An onload postmessage to window opener +const RESPONSE_HTTPS_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-https'}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_HTTP_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-http'}, '*'); + </script> + </body> + </html>`; + +function handleRequest(request, response) { + let response_content = + request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(response_content); +} diff --git a/dom/security/test/https-first/file_download_attribute.html b/dom/security/test/https-first/file_download_attribute.html new file mode 100644 index 0000000000..453bf408b3 --- /dev/null +++ b/dom/security/test/https-first/file_download_attribute.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test download attribute for http site</title> +</head> +<body> + <a href="http://nocert.example.com/browser/dom/security/test/https-first/file_download_attribute.sjs" download="some.html" id="testlink">download by attribute</a> + <script> + // click the link to start download + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/https-first/file_download_attribute.sjs b/dom/security/test/https-first/file_download_attribute.sjs new file mode 100644 index 0000000000..8941da1a41 --- /dev/null +++ b/dom/security/test/https-first/file_download_attribute.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) { + // Only answer to http, in case request is in https let the reply time out. + if (request.scheme === "https") { + response.processAsync(); + return; + } + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Disposition", "attachment; filename=some.html"); + response.setHeader("Content-Type", "text/html"); + response.write("success!"); +} diff --git a/dom/security/test/https-first/file_form_submission.sjs b/dom/security/test/https-first/file_form_submission.sjs new file mode 100644 index 0000000000..63b248d773 --- /dev/null +++ b/dom/security/test/https-first/file_form_submission.sjs @@ -0,0 +1,84 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, downgraded + <script type="application/javascript"> + let scheme = document.location.protocol; + const loc = document.location.href; + window.opener.postMessage({location: loc, scheme: scheme, form:"test=success" }, '*'); + </script> + </body> + </html>`; + +const POST_FORMULAR = ` +<html> + <body> + <form action="http://example.com/tests/dom/security/test/https-first/file_form_submission.sjs?" method="POST" id="POSTForm"> + <div> + <label id="submit">Submit</label> + <input name="test" id="form" value="success"> + </div> + </form> + <script class="testbody" type="text/javascript"> + document.getElementById("POSTForm").submit(); + </script> + </body> +</html> +`; + +function handleRequest(request, response) { + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + let queryString = request.queryString; + if (request.scheme === "https" && queryString === "test=1") { + response.write(RESPONSE_SUCCESS); + return; + } + if ( + request.scheme === "https" && + (queryString === "test=2" || queryString === "test=4") + ) { + // time out request + response.processAsync(); + return; + } + if (request.scheme === "http" && queryString === "test=2") { + response.write(RESPONSE_SUCCESS); + return; + } + if (queryString === "test=3" || queryString === "test=4") { + // send post form + response.write(POST_FORMULAR); + return; + } + if (request.method == "POST") { + // extract form parameters + let body = new BinaryInputStream(request.bodyInputStream); + let avail; + let bytes = []; + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + let requestBodyContents = String.fromCharCode.apply(null, bytes); + + response.write(` + <html> + <script type="application/javascript"> + let scheme = document.location.protocol; + const loc = document.location.href; + window.opener.postMessage({location: loc, scheme: scheme, form: '${requestBodyContents}'}, '*'); + </script> + </html>`); + + return; + } + // we should never get here; just in case, return something unexpected + response.write("do'h"); +} diff --git a/dom/security/test/https-first/file_fragment.html b/dom/security/test/https-first/file_fragment.html new file mode 100644 index 0000000000..5846d6d977 --- /dev/null +++ b/dom/security/test/https-first/file_fragment.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<script> + +function beforeunload(){ + window.opener.postMessage({ + info: "before-unload", + result: window.location.hash, + button: false, + }, "*"); +} + +window.onload = function (){ + let button = window.document.getElementById("clickMeButton"); + let buttonExist = button !== null; + window.opener.postMessage({ + info: "onload", + result: window.location.href, + button: buttonExist, + }, "*"); + 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-first/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-first/file_fragment.html">foo</a> + <div style="height: 1000px; border: 1px solid black;">space</div> +</body> +</html> diff --git a/dom/security/test/https-first/file_httpsfirst_speculative_connect.html b/dom/security/test/https-first/file_httpsfirst_speculative_connect.html new file mode 100644 index 0000000000..6542884191 --- /dev/null +++ b/dom/security/test/https-first/file_httpsfirst_speculative_connect.html @@ -0,0 +1 @@ +<html><body>dummy file for speculative https-first upgrade test</body></html> diff --git a/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs b/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs new file mode 100644 index 0000000000..81c4c0328b --- /dev/null +++ b/dom/security/test/https-first/file_httpsfirst_timeout_server.sjs @@ -0,0 +1,13 @@ +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-first/file_mixed_content_auto_upgrade.html b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html new file mode 100644 index 0000000000..7dda8909a5 --- /dev/null +++ b/dom/security/test/https-first/file_mixed_content_auto_upgrade.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1673574 - Improve Console logging for mixed content auto upgrading</title> +</head> +<body> + <!--upgradeable resources---> + <img src="http://example.com/browser/dom/security/test/https-first/pass.png"> + <video src="http://example.com/browser/dom/security/test/https-first/test.ogv"> + <audio src="http://example.com/browser/dom/security/test/https-first/test.wav"> +</body> +</html> diff --git a/dom/security/test/https-first/file_mixed_content_console.html b/dom/security/test/https-first/file_mixed_content_console.html new file mode 100644 index 0000000000..631ac0b40f --- /dev/null +++ b/dom/security/test/https-first/file_mixed_content_console.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1713593: HTTPS-First: Add test for mixed content blocker.</title> +</head> +<body> + <!-- Test that image gets loaded (insecure) - the *.png does not actually exist because we only care for the console message to appear--> + <img type="image/png" id="testimage" src="http://example.com/browser/dom/security/test/https-first/auto_upgrading_identity.png" /> + <!-- Test that the script gets blocked. The script does not actually exist because we only care for the console message to appear--> + <script type="text/javascript" src="http://example.com/browser/dom/security/test/https-first/barfoo" > +</script> +</body> +</html> diff --git a/dom/security/test/https-first/file_multiple_redirection.sjs b/dom/security/test/https-first/file_multiple_redirection.sjs new file mode 100644 index 0000000000..49098ccdb7 --- /dev/null +++ b/dom/security/test/https-first/file_multiple_redirection.sjs @@ -0,0 +1,87 @@ +"use strict"; + +// redirection uri +const REDIRECT_URI = + "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?redirect"; +const REDIRECT_URI_HTTP = + "http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; +const REDIRECT_URI_HTTPS = + "https://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?verify"; + +const RESPONSE_ERROR = "unexpected-query"; + +// An onload postmessage to window opener +const RESPONSE_HTTPS_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-https'}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_HTTP_SCHEME = ` + <html> + <body> + <script type="application/javascript"> + window.opener.postMessage({result: 'scheme-http'}, '*'); + </script> + </body> + </html>`; + +function sendRedirection(query, response) { + // send a redirection to an http uri + if (query.includes("test1")) { + response.setHeader("Location", REDIRECT_URI_HTTP, false); + return; + } + // send a redirection to an https uri + if (query.includes("test2")) { + response.setHeader("Location", REDIRECT_URI_HTTPS, false); + return; + } + // send a redirection to an http uri with hsts header + if (query.includes("test3")) { + response.setHeader("Strict-Transport-Security", "max-age=60"); + response.setHeader("Location", REDIRECT_URI_HTTP, false); + } +} + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + const query = request.queryString; + + // if the query contains a test query start first test + if (query.startsWith("test")) { + // send a 302 redirection + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", REDIRECT_URI + query, false); + return; + } + // Send a redirection + if (query.includes("redirect")) { + response.setStatusLine(request.httpVersion, 302, "Found"); + sendRedirection(query, response); + return; + } + // Reset the HSTS policy, prevent influencing other tests + if (request.queryString === "reset") { + response.setHeader("Strict-Transport-Security", "max-age=0"); + let response_content = + request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(response_content); + } + // Check if scheme is http:// or https:// + if (query == "verify") { + let response_content = + request.scheme === "https" ? RESPONSE_HTTPS_SCHEME : RESPONSE_HTTP_SCHEME; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(response_content); + return; + } + + // We should never get here, but just in case ... + response.setStatusLine(request.httpVersion, 500, "OK"); + response.write("unexepcted query"); +} diff --git a/dom/security/test/https-first/file_navigation.html b/dom/security/test/https-first/file_navigation.html new file mode 100644 index 0000000000..02d366291b --- /dev/null +++ b/dom/security/test/https-first/file_navigation.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> + <body> + <p>Blank page</p> + <body> +</html> diff --git a/dom/security/test/https-first/file_redirect.sjs b/dom/security/test/https-first/file_redirect.sjs new file mode 100644 index 0000000000..2042bcbc88 --- /dev/null +++ b/dom/security/test/https-first/file_redirect.sjs @@ -0,0 +1,58 @@ +//https://bugzilla.mozilla.org/show_bug.cgi?id=1706351 + +// 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_ERROR = "unexpected-query"; + +// An onload postmessage to window opener +const RESPONSE_SECURE = ` + <html> + <body> + send onload message... + <script type="application/javascript"> + window.opener.postMessage({result: 'secure'}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_INSECURE = ` + <html> + <body> + send onload message... + <script type="application/javascript"> + window.opener.postMessage({result: 'insecure'}, '*'); + </script> + </body> + </html>`; + +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) { + // needs to be a cross site redirect to http://example.com otherwise + // our upgrade downgrade endless loop break mechanism kicks in + const loc = + "http://test1.example.com/tests/dom/security/test/https-first/file_redirect.sjs?check"; + response.setStatusLine(request.httpVersion, query, "Found"); + response.setHeader("Location", loc, false); + return; + } + + // Check if scheme is http:// or 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-first/file_redirect_downgrade.sjs b/dom/security/test/https-first/file_redirect_downgrade.sjs new file mode 100644 index 0000000000..a31c8cb99b --- /dev/null +++ b/dom/security/test/https-first/file_redirect_downgrade.sjs @@ -0,0 +1,87 @@ +// Custom *.sjs file specifically for the needs of Bug 1707856 +"use strict"; + +const REDIRECT_META = ` + <html> + <head> + <meta http-equiv="refresh" content="0; url='http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd'"> + </head> + <body> + META REDIRECT + </body> + </html>`; + +const REDIRECT_JS = ` + <html> + <body> + JS REDIRECT + <script> + let url= "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd"; + window.location = url; + </script> + </body> + </html>`; + +const REDIRECT_302 = + "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs?testEnd"; + +const RESPONSE_SUCCESS = ` + <html> + <body> + send message, downgraded + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'downgraded', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + let scheme = document.location.protocol; + window.opener.postMessage({result: 'error', scheme: scheme}, '*'); + </script> + </body> + </html>`; + +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 the scheme is not https and it is the initial request + // then we rather fall through and display unexpected content + if (request.scheme === "https") { + 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; + } + + // Simulating a timeout by processing the https request + response.processAsync(); + return; + } + + // We should arrive here when the redirection was downraded successful + if (query == "testEnd") { + response.write(RESPONSE_SUCCESS); + return; + } + // We should never arrive here, just in case send 'error' + response.write(RESPONSE_UNEXPECTED); +} diff --git a/dom/security/test/https-first/file_referrer_policy.sjs b/dom/security/test/https-first/file_referrer_policy.sjs new file mode 100644 index 0000000000..282f7ba09f --- /dev/null +++ b/dom/security/test/https-first/file_referrer_policy.sjs @@ -0,0 +1,102 @@ +const RESPONSE_ERROR = ` + <html> + <body> + Error occurred... + <script type="application/javascript"> + window.opener.postMessage({result: 'ERROR'}, '*'); + </script> + </body> + </html>`; +const RESPONSE_POLICY = ` +<html> +<body> +Send policy onload... +<script type="application/javascript"> + const loc = document.location.href; + window.opener.postMessage({result: document.referrer, location: loc}, "*"); +</script> +</body> +</html>`; + +const expectedQueries = [ + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin", + "unsafe-url", +]; +function readQuery(testCase) { + let twoValues = testCase.split("-"); + let upgradeRequest = twoValues[0] === "https" ? 1 : 0; + let httpsResponse = twoValues[1] === "https" ? 1 : 0; + return [upgradeRequest, httpsResponse]; +} + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + Cu.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + // Downgrade to test http/https -> HTTP referrer policy + if (query.has("sendMe2") && request.scheme === "https") { + // Simulating a timeout by processing the https request + response.processAsync(); + return; + } + if (query.has("sendMe") || query.has("sendMe2")) { + response.write(RESPONSE_POLICY); + return; + } + // Get the referrer policy that we want to set + let referrerPolicy = query.get("rp"); + //If the query contained one of the expected referrer policies send a request with the given policy, + // else send error + if (expectedQueries.includes(referrerPolicy)) { + // Determine the test case, e.g. don't upgrade request but send response in https + let testCase = readQuery(query.get("upgrade")); + let httpsRequest = testCase[0]; + let httpsResponse = testCase[1]; + // Downgrade to http if upgrade equals 0 + if (httpsRequest === 0 && request.scheme === "https") { + // Simulating a timeout by processing the https request + response.processAsync(); + return; + } + // create js redirection that request with the given (related to the query) referrer policy + const SEND_REQUEST_HTTPS = ` + <html> + <head> + <meta name="referrer" content=${referrerPolicy}> + </head> + <body> + JS REDIRECT + <script> + let url = 'https://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe'; + window.location = url; + </script> + </body> + </html>`; + const SEND_REQUEST_HTTP = ` + <html> + <head> + <meta name="referrer" content=${referrerPolicy}> + </head> + <body> + JS REDIRECT + <script> + let url = 'http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe2'; + window.location = url; + </script> + </body> + </html>`; + let respond = httpsResponse === 1 ? SEND_REQUEST_HTTPS : SEND_REQUEST_HTTP; + response.write(respond); + return; + } + + // We should never get here but in case we send an error + response.setStatusLine(request.httpVersion, 500, "OK"); + response.write(RESPONSE_ERROR); +} diff --git a/dom/security/test/https-first/file_slow_download.html b/dom/security/test/https-first/file_slow_download.html new file mode 100644 index 0000000000..084977607d --- /dev/null +++ b/dom/security/test/https-first/file_slow_download.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test slow download from an http site that gets upgraded to https</title> +</head> +<body> + <a href="http://example.com/browser/dom/security/test/https-first/file_slow_download.sjs" download="large-dummy-file.txt" id="testlink">download by attribute</a> + <script> + // click the link to start download + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/https-first/file_slow_download.sjs b/dom/security/test/https-first/file_slow_download.sjs new file mode 100644 index 0000000000..6e4f109068 --- /dev/null +++ b/dom/security/test/https-first/file_slow_download.sjs @@ -0,0 +1,26 @@ +"use strict"; +let timer; + +// Send a part of the file then wait for 3 second before sending the rest. +// If download isn't exempt from background timer of https-only/-first then the download +// gets cancelled before it completed. +const DELAY_MS = 3500; +function handleRequest(request, response) { + response.processAsync(); + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader( + "Content-Disposition", + "attachment; filename=large-dummy-file.txt" + ); + response.setHeader("Content-Type", "text/plain"); + response.write("Begin the file"); + timer.init( + () => { + response.write("End of file"); + response.finish(); + }, + DELAY_MS, + Ci.nsITimer.TYPE_ONE_SHOT + ); +} diff --git a/dom/security/test/https-first/file_toplevel_cookies.sjs b/dom/security/test/https-first/file_toplevel_cookies.sjs new file mode 100644 index 0000000000..dd9f7c0909 --- /dev/null +++ b/dom/security/test/https-first/file_toplevel_cookies.sjs @@ -0,0 +1,233 @@ +// Custom *.sjs file specifically for the needs of Bug 1711453 +"use strict"; + +// small red image +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" +); + +const IFRAME_INC = `<iframe id="testframeinc"></iframe>`; + +// Sets an image sends cookie and location after loading +const SET_COOKIE_IMG = ` +<html> +<body> +<img id="cookieImage"> +<script class="testbody" type="text/javascript"> + var cookieImage = document.getElementById("cookieImage"); + cookieImage.onload = function() { + let myLocation = window.location.href; + let myCookie = document.cookie; + window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*'); + } + cookieImage.onerror = function() { + window.opener.postMessage({result: 'error'}, '*'); + } + // Add the last number of the old query to the new query to set cookie properly + cookieImage.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?setSameSiteCookie" + + window.location.href.charAt(window.location.href.length -1); +</script> +</body> +</html> +`; + +// Load blank frame navigation sends cookie and location after loading +const LOAD_BLANK_FRAME_NAV = ` +<html> +<body> +<iframe id="testframe"></iframe> +<script> + let testframe = document.getElementById("testframe"); + testframe.onload = function() { + let myLocation = window.location.href; + let myCookie = document.cookie; + window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*'); + } + testframe.onerror = function() { + window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*'); + } + testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadblankframeNav"; +</script> +</body> +</html> +`; + +// Load frame navigation sends cookie and location after loading +const LOAD_FRAME_NAV = ` +<html> +<body> +<iframe id="testframe"></iframe> +<script> + let testframe = document.getElementById("testframe"); + testframe.onload = function() { + let myLocation = window.location.href; + let myCookie = document.cookie; + window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*'); + } + testframe.onerror = function() { + window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*'); + } + testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadsrcdocframeNav"; +</script> +</body> +</html> + +`; +// blank frame sends cookie and location after loading +const LOAD_BLANK_FRAME = ` +<html> +<body> +<iframe id="testframe"></iframe> +<script> + let testframe = document.getElementById("testframe"); + testframe.onload = function() { + let myLocation = window.location.href; + let myCookie = document.cookie; + window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*'); + } + testframe.onerror = function() { + window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*'); + } + testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadblankframeInc"; +</script> +</body> +</html> +`; +// frame sends cookie and location after loading +const LOAD_FRAME = ` +<html> +<body> +<iframe id="testframe"></iframe> +<script> + let testframe = document.getElementById("testframe"); + testframe.onload = function() { + let myLocation = window.location.href; + let myCookie = document.cookie; + window.opener.postMessage({result: 'upgraded', loc: myLocation, cookie: myCookie}, '*'); + } + testframe.onerror = function() { + window.opener.postMessage({result: 'error', loc: 'error', cookie: ''}, '*'); + } + testframe.src = window.location.origin + "/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?loadsrcdocframeInc"; +</script> +</body> +</html> +`; + +const RESPONSE_UNEXPECTED = ` + <html> + <body> + send message, error + <script type="application/javascript"> + let myLocation = document.location.href; + window.opener.postMessage({result: 'error', loc: myLocation}, '*'); + </script> + </body> + </html>`; + +function setCookie(name, query) { + let cookie = name + "="; + if (query.includes("0")) { + cookie += "0;Domain=.example.com;sameSite=none"; + return cookie; + } + if (query.includes("1")) { + cookie += "1;Domain=.example.com;sameSite=strict"; + return cookie; + } + if (query.includes("2")) { + cookie += "2;Domain=.example.com;sameSite=none;secure"; + return cookie; + } + if (query.includes("3")) { + cookie += "3;Domain=.example.com;sameSite=strict;secure"; + return cookie; + } + return cookie + "error"; +} + +function handleRequest(request, response) { + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + let query = request.queryString; + if (query.includes("setImage")) { + response.write(SET_COOKIE_IMG); + return; + } + // using startsWith and discard the math random + if (query.includes("setSameSiteCookie")) { + response.setHeader("Set-Cookie", setCookie("setImage", query), true); + response.setHeader("Content-Type", "image/png"); + response.write(IMG_BYTES); + return; + } + + // navigation tests + if (query.includes("loadNavBlank")) { + response.setHeader("Set-Cookie", setCookie("loadNavBlank", query), true); + response.write(LOAD_BLANK_FRAME_NAV); + return; + } + + if (request.queryString === "loadblankframeNav") { + let FRAME = ` + <iframe src="about:blank" + // nothing happens here + </iframe>`; + response.write(FRAME); + return; + } + + if (query.includes("loadNav")) { + response.setHeader("Set-Cookie", setCookie("loadNav", query), true); + response.write(LOAD_FRAME_NAV); + return; + } + + if (query === "loadsrcdocframeNav") { + let FRAME = ` + <iframe srcdoc="foo" + // nothing happens here + </iframe>`; + response.write(FRAME); + return; + } + + // inclusion tests + if (query.includes("loadframeIncBlank")) { + response.setHeader( + "Set-Cookie", + setCookie("loadframeIncBlank", query), + true + ); + response.write(LOAD_BLANK_FRAME); + return; + } + + if (request.queryString === "loadblankframeInc") { + let FRAME = + ` <iframe id="blankframe" src="about:blank"></iframe> + <script> + document.getElementById("blankframe").contentDocument.write("` + + IFRAME_INC + + `"); + <\script>`; + response.write(FRAME); + return; + } + + if (query.includes("loadframeInc")) { + response.setHeader("Set-Cookie", setCookie("loadframeInc", query), true); + response.write(LOAD_FRAME); + return; + } + + if (request.queryString === "loadsrcdocframeInc") { + response.write('<iframe srcdoc="' + IFRAME_INC + '"></iframe>'); + return; + } + + // We should never arrive here, just in case send 'error' + response.write(RESPONSE_UNEXPECTED); +} diff --git a/dom/security/test/https-first/file_upgrade_insecure.html b/dom/security/test/https-first/file_upgrade_insecure.html new file mode 100644 index 0000000000..af306d2a16 --- /dev/null +++ b/dom/security/test/https-first/file_upgrade_insecure.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1704454 - HTTPS FIRST Mode</title> + <!-- style --> + <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/https-first/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-first/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-first/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-first/file_upgrade_insecure_server.sjs?redirect-image"></img> + + <!-- script: --> + <script src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?script"></script> + + <!-- media: --> + <audio src="http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?media"></audio> + + <!-- objects: --> + <object width="10" height="10" data="http://example.com/tests/dom/security/test/https-first/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-first/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> + + <!-- toplevel: --> + <script type="application/javascript"> + let myWin = window.open("http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?top-level"); + //close right after opening + myWin.onunload = function(){ + myWin.close(); + } + </script> + + <!-- xhr: --> + <script type="application/javascript"> + var myXHR = new XMLHttpRequest(); + myXHR.open("GET", "http://example.com/tests/dom/security/test/https-first/file_upgrade_insecure_server.sjs?xhr"); + myXHR.send(null); + </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-first/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-first/file_upgrade_insecure_server.sjs b/dom/security/test/https-first/file_upgrade_insecure_server.sjs new file mode 100644 index 0000000000..a8f4d66659 --- /dev/null +++ b/dom/security/test/https-first/file_upgrade_insecure_server.sjs @@ -0,0 +1,114 @@ +// SJS file for https-first Mode mochitests +// Bug 1704454 - HTTPS First Mode + +const TOTAL_EXPECTED_REQUESTS = 12; + +const IFRAME_CONTENT = + "<!DOCTYPE HTML>" + + "<html>" + + "<head><meta charset='utf-8'>" + + "<title>Bug 1704454 - Test HTTPS First Mode</title>" + + "</head>" + + "<body>" + + "<img src='http://example.com/tests/dom/security/test/https-first/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", + "top-level", +]; + +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-first/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 aren't upgraded to https + // except of toplevel requests + if (queryString === "top-level") { + queryString += request.scheme === "https" ? "-ok" : "-error"; + } else { + queryString += request.scheme === "http" ? "-ok" : "-error"; + } + var receivedQueries = getState("receivedQueries"); + + // images, scripts, etc. get queried twice, do not + // confuse the server by storing the preload as + // well as the actual load. If either the preload + // or the actual load is not https, then we would + // append "-error" in the array and the test would + // fail at the end. + + // 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-first/mochitest.ini b/dom/security/test/https-first/mochitest.ini new file mode 100644 index 0000000000..5a0ff62f21 --- /dev/null +++ b/dom/security/test/https-first/mochitest.ini @@ -0,0 +1,44 @@ +[DEFAULT] +skip-if = http3 + +[test_fragment.html] +support-files = file_fragment.html +[test_resource_upgrade.html] +scheme=https +support-files = + file_upgrade_insecure.html + file_upgrade_insecure_server.sjs +skip-if = true # Bug 1727101, Bug 1727925 +[test_redirect_upgrade.html] +scheme=https +support-files = + file_redirect.sjs +[test_redirect_downgrade.html] +support-files = file_redirect_downgrade.sjs +[test_data_uri.html] +support-files = + file_data_uri.html + [test_toplevel_cookies.html] +support-files = + file_toplevel_cookies.sjs +[test_downgrade_bad_responses.html] +support-files= file_downgrade_bad_responses.sjs +[test_referrer_policy.html] +support-files= file_referrer_policy.sjs +[test_break_endless_upgrade_downgrade_loop.html] +support-files = + file_break_endless_upgrade_downgrade_loop.sjs + file_downgrade_with_different_path.sjs +[test_multiple_redirection.html] +support-files = + file_multiple_redirection.sjs +[test_form_submission.html] +support-files = + file_form_submission.sjs +[test_bad_cert.html] +support-files = + file_bad_cert.sjs +[test_downgrade_request_upgrade_request.html] +support-files = file_downgrade_request_upgrade_request.sjs +[test_downgrade_500_responses.html] +support-files = file_downgrade_500_responses.sjs diff --git a/dom/security/test/https-first/pass.png b/dom/security/test/https-first/pass.png Binary files differnew file mode 100644 index 0000000000..2fa1e0ac06 --- /dev/null +++ b/dom/security/test/https-first/pass.png diff --git a/dom/security/test/https-first/test.ogv b/dom/security/test/https-first/test.ogv Binary files differnew file mode 100644 index 0000000000..0f83996e5d --- /dev/null +++ b/dom/security/test/https-first/test.ogv diff --git a/dom/security/test/https-first/test.wav b/dom/security/test/https-first/test.wav Binary files differnew file mode 100644 index 0000000000..85dc1ea904 --- /dev/null +++ b/dom/security/test/https-first/test.wav diff --git a/dom/security/test/https-first/test_bad_cert.html b/dom/security/test/https-first/test_bad_cert.html new file mode 100644 index 0000000000..d7e9296d97 --- /dev/null +++ b/dom/security/test/https-first/test_bad_cert.html @@ -0,0 +1,67 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1719309 +Test that bad cert sites won't get upgraded by https-first +--> + +<head> + <title>HTTPS-FirstMode - Bad Certificates</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + <h1>HTTPS-First Mode</h1> + <p>Test: Downgrade bad certificates without warning page </p> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1706351">Bug 1719309</a> + + <script class="testbody" type="text/javascript"> + "use strict"; + /* + * We perform the following tests: + * 1. Request nocert.example.com which is a site without a certificate + * 2. Request a site with self-signed cert (self-signed.example.com) + * 3. Request a site with an untrusted cert (untrusted.example.com) + * 4. Request a site with an expired cert + * 5. Request a site with an untrusted and expired cert + * 6. Request a site with no subject alternative dns name matching + * + * Expected result: Https-first tries to upgrade each request. Receives for each one an SSL_ERROR_* + * and downgrades back to http. + */ + const badCertificates = ["nocert","self-signed", "untrusted","expired","untrusted-expired", "no-subject-alt-name"]; + let currentTest = 0; + let testWin; + window.addEventListener("message", receiveMessage); + + // Receive message and verify that it is from an http site. + // Verify that we got the correct message and an http scheme + async function receiveMessage(event) { + let data = event.data; + let currentBadCert = badCertificates[currentTest]; + ok(data.result === "downgraded", "Downgraded request " + currentBadCert); + ok(data.scheme === "http:", "Received 'http' for " + currentBadCert); + testWin.close(); + if (++currentTest < badCertificates.length) { + startTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + } + + async function startTest() { + const currentCode = badCertificates[currentTest]; + // make a request to a subdomain of example.com with a bad certificate + testWin = window.open(`http://${currentCode}.example.com/tests/dom/security/test/https-first/file_bad_cert.sjs`); + } + + // Set preference and start test + SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ]}, startTest); + SimpleTest.waitForExplicitFinish(); + </script> +</body> +</html> diff --git a/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html b/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html new file mode 100644 index 0000000000..7d239350a1 --- /dev/null +++ b/dom/security/test/https-first/test_break_endless_upgrade_downgrade_loop.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1715253 +Test that same origin redirect does not cause endless loop with https-first enabled +--> + +<head> + <title>HTTPS-First-Mode - Break endless upgrade downgrade redirect loop</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + <h1>HTTPS-First Mode</h1> + <p>Upgrade Test for insecure redirects.</p> + + <script class="testbody" type="text/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + + const redirectCodes = ["301", "302","303","307"]; + let currentTest = 0; + let testWin; + window.addEventListener("message", receiveMessage); + + // receive message from loaded site verifying the scheme of + // the loaded document. + async function receiveMessage(event) { + let currentRedirectCode = redirectCodes[currentTest]; + is(event.data.result, + "scheme-http", + "same-origin redirect results in 'http' for " + currentRedirectCode + ); + testWin.close(); + if (++currentTest < redirectCodes.length) { + startTest(); + return; + } + window.removeEventListener("message", receiveMessage); + window.addEventListener("message", receiveMessageForDifferentPathTest); + testDifferentPath(); + } + + async function receiveMessageForDifferentPathTest(event) { + is(event.data.result, + "scheme-https", + "scheme should be https when the path is different" + ); + testWin.close(); + window.removeEventListener("message", receiveMessageForDifferentPathTest); + SimpleTest.finish(); + } + + async function startTest() { + const currentCode = redirectCodes[currentTest]; + // Load an http:// window which gets upgraded to https:// + let uri = + `http://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?${currentCode}`; + testWin = window.open(uri); + } + + async function testDifferentPath() { + // Load an https:// window which gets downgraded to http:// + let uri = + `https://example.com/tests/dom/security/test/https-first/file_break_endless_upgrade_downgrade_loop.sjs?downgrade`; + testWin = window.open(uri); + } + + // Set preference and start test + SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ["security.mixed_content.block_active_content", false], + ["security.mixed_content.block_display_content", false], + ["dom.security.https_only_check_path_upgrade_downgrade_endless_loop", true], + ]}, startTest); + </script> +</body> +</html> diff --git a/dom/security/test/https-first/test_data_uri.html b/dom/security/test/https-first/test_data_uri.html new file mode 100644 index 0000000000..b9891260db --- /dev/null +++ b/dom/security/test/https-first/test_data_uri.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1709069: Test that Data URI which makes a top-level request gets updated in https-first</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +window.addEventListener("message", receiveMessage); + +// HTML site which makes a top-level http request +const HTML = ` +<html> +<body> + DATA HTML +<script> + window.open("http://example.com/tests/dom/security/test/https-first/file_data_uri.html"); +<\/script> +<\/body> +<\/html> +`; + +const DATA_HTML = "data:text/html, " + HTML; + +// Verify that data uri top-level request got upgraded to https and +// the reached location is correct +async function receiveMessage(event){ + let data = event.data; + is(data.location, "https://example.com/tests/dom/security/test/https-first/file_data_uri.html", + "Reached the correct location"); + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +function test_toplevel_https() { + document.getElementById("testframe").src = DATA_HTML; +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ]}, test_toplevel_https); + + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_downgrade_500_responses.html b/dom/security/test/https-first/test_downgrade_500_responses.html new file mode 100644 index 0000000000..3943c9095c --- /dev/null +++ b/dom/security/test/https-first/test_downgrade_500_responses.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1747673 : HTTPS First fallback to http for non-standard 5xx status code responses</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 five tests where https-first receives an + * 5xx status code (standard and non-standard 5xx status) if request is send to site by https. + * Expected behaviour: https-first fallbacks to http after receiving 5xx error. + * Test 1: 501 Response + * Test 2: 504 Response + * Test 3: 521 Response + * Test 4: 530 Response + * Test 5: 560 Response + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = + "http://example.com/tests/dom/security/test/https-first/file_downgrade_500_responses.sjs"; + +const redirectQueries = ["?test1a", "?test2a","?test3a", "?test4a", "?test5a"]; +let currentTest = 0; +let testWin; +let currentQuery; +window.addEventListener("message", receiveMessage); + +// Receive message and verify that it is from an http site. +// When the message is 'downgraded' then it was send by an http site +// and the redirection worked. +async function receiveMessage(event) { + let data = event.data; + currentQuery = redirectQueries[currentTest]; + ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery); + is(data.scheme, "http:", "scheme is 'http' for " + currentQuery ); + testWin.close(); + if (++currentTest < redirectQueries.length) { + runTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +async function runTest() { + currentQuery = redirectQueries[currentTest]; + testWin = window.open(REQUEST_URL + currentQuery, "_blank"); +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true] + ]}, runTest); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_downgrade_bad_responses.html b/dom/security/test/https-first/test_downgrade_bad_responses.html new file mode 100644 index 0000000000..39cef7f26a --- /dev/null +++ b/dom/security/test/https-first/test_downgrade_bad_responses.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1709552 : HTTPS-First: Add downgrade tests for bad responses to https request </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 five tests where we expect https-first to detect + * that the target site only supports http + * Test 1: 400 Response + * Test 2: 401 Response + * Test 3: 403 Response + * Test 4: 416 Response + * Test 5: 418 Response + * Test 6: Timeout + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = + "http://example.com/tests/dom/security/test/https-first/file_downgrade_bad_responses.sjs"; + +const redirectQueries = ["?test1a", "?test2a","?test3a", "?test4a", "?test5a", "?test6a"]; +let currentTest = 0; +let testWin; +let currentQuery; +window.addEventListener("message", receiveMessage); + +// Receive message and verify that it is from an http site. +// When the message is 'downgraded' then it was send by an http site +// and the redirection worked. +async function receiveMessage(event) { + let data = event.data; + currentQuery = redirectQueries[currentTest]; + ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery); + ok(data.scheme === "http", "scheme is 'http' for " + currentQuery ); + testWin.close(); + if (++currentTest < redirectQueries.length) { + runTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +async function runTest() { + currentQuery = redirectQueries[currentTest]; + testWin = window.open(REQUEST_URL + currentQuery, "_blank"); +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true] + ]}, runTest); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_downgrade_request_upgrade_request.html b/dom/security/test/https-first/test_downgrade_request_upgrade_request.html new file mode 100644 index 0000000000..b659636ace --- /dev/null +++ b/dom/security/test/https-first/test_downgrade_request_upgrade_request.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> +<title> Bug 1706126: Test https-first, downgrade first request and then upgrade redirection to subdomain</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: + * First we request http://redirect-example.com which HTTPS-First upgrades to https://redirect-example.com. + * The request https://redirect-example.com doesn't receive an answer (timeout), so we send a background + * request. + * The background request receives an answer. So the request https://redirect-example.com gets downgraded + * to http://redirect-example.com by the exempt flag. + * The request http://redirect-example.com gets redirected to http://wwww.redirect-example.com. At that stage + * HTTPS-First should clear the exempt flag and upgrade the redirection to https://wwww.redirect-example.com. + * + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = + "http://redirect-example.com/tests/dom/security/test/https-first/file_downgrade_request_upgrade_request.sjs"; + +let testWin; +window.addEventListener("message", receiveMessage); + +// Receive message and verify that it is from an https site. +async function receiveMessage(event) { + let data = event.data; + ok(data.result === "upgraded", "Redirected successful to 'https' for subdomain "); + is(data.scheme,"https:", "scheme is 'https' for subdomain"); + testWin.close(); + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +async function runTest() { + testWin = window.open(REQUEST_URL, "_blank"); +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true] + ]}, runTest); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_form_submission.html b/dom/security/test/https-first/test_form_submission.html new file mode 100644 index 0000000000..a68c3501c6 --- /dev/null +++ b/dom/security/test/https-first/test_form_submission.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1720103 - Https-first: Do not upgrade form submissions (for now)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> +<script class="testbody" type="text/javascript"> +/* + * Description of the test: + * We test https-first behaviour with forms. + * We perform each test with once with same origin and the second time + * with a cross origin. We perform two GET form requests and two POST + * form requests. + * In more detail: + * + * 1. Test: Request that gets upgraded to https, GET form submission. + * + * 2. Test: Request that gets upgraded to https, that upgraded request + * gets timed out, so https-first send an http request, GET form submission. + * + * 3. Test: request that gets upgraded to https, and sends a POST form + * to http://example.com. + * + * 4. Test: Request where the https upgrade get timed out -> http, and sends a POST form + * to http://example.com, + * + */ +SimpleTest.waitForExplicitFinish(); +window.addEventListener("message", receiveMessage); + +const SAME_ORIGIN = "http://example.com/tests/dom/security/test/https-first/file_form_submission.sjs"; +const CROSS_ORIGIN = SAME_ORIGIN.replace(".com", ".org"); +const Tests = [{ + // 1. Test GET, gets upgraded + query: "?test=1", + scheme: "https:", + method: "GET", + value: "test=success", +}, +{ + // 2. Test GET, initial request will be downgraded + query:"?test=2", + scheme: "http:", + method: "GET", + value: "test=success" +}, +{ // 3. Test POST formular, gets upgraded + query: "?test=3", + scheme: "http:", + method: "POST", + value: "test=success" +}, +{ // 4. Test POST formular, request will be downgraded + query: "?test=4", + scheme: "http:", + method: "POST", + value: "test=success" +}, +]; +let currentTest; +let counter = 0; +let testWin; +let sameOrigin = true; + +// Verify that top-level request got the expected scheme and reached the correct location. +async function receiveMessage(event){ + let data = event.data; + let origin = sameOrigin? SAME_ORIGIN : CROSS_ORIGIN + const expectedLocation = origin.replace("http:", currentTest.scheme); + // If GET request check that form was transfered by url + if (currentTest.method === "GET") { + is(data.location, expectedLocation + currentTest.query, + "Reached the correct location for " + currentTest.query ); + } else { + // Since the form is always send to example.com we expect it here as location + is(data.location.includes(SAME_ORIGIN.replace("http:", currentTest.scheme)), true, + "Reached the correct location for " + currentTest.query ); + } + is(data.scheme, currentTest.scheme,`${currentTest.query} upgraded or downgraded to ` + currentTest.scheme); + // Check that the form value is correct + is(data.form, currentTest.value, "Form was transfered"); + testWin.close(); + // Flip origin flag + sameOrigin ^= true; + // Only go to next test if already sent same and cross origin request for current test + if (sameOrigin) { + counter++; + } + // Check if we have test left, if not finish the testing + if (counter >= Tests.length) { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + return; + } + // If we didn't reached the end yet, run next test + runTest(); +} + +function runTest() { + currentTest = Tests[counter]; + // If sameOrigin flag is set make a origin request, else a cross origin request + if (sameOrigin) { + testWin= window.open(SAME_ORIGIN + currentTest.query, "_blank"); + } else { + testWin= window.open(CROSS_ORIGIN + currentTest.query, "_blank"); + } +} + +// Set prefs and start test +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ["security.warn_submit_secure_to_insecure", false] + ]}, runTest); + + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_fragment.html b/dom/security/test/https-first/test_fragment.html new file mode 100644 index 0000000000..4a27f198e1 --- /dev/null +++ b/dom/security/test/https-first/test_fragment.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1706577: Have https-first mode account for fragment navigations</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: + * Have https-first detect a fragment navigation rather than navigating away + * from the page. + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = "http://example.com/tests/dom/security/test/https-first/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; + if (!checkButtonClicked) { + ok(data.result == EXPECT_URL, "location is correct"); + ok(data.button, "button is clicked"); + ok(data.info == "onload", "Onloading worked"); + checkButtonClicked = true; + return; + } + + // Once the button was clicked we know the tast has finished + ok(data.button, "button is clicked"); + is(data.result, EXPECT_URL + "#foo", "location (hash) is correct"); + ok(data.info == "scrolled-to-foo","Scrolled successfully without reloading!"); + is(data.documentURI, EXPECT_URL + "#foo", "Document URI is correct"); + window.removeEventListener("message",receiveMessage); + winTest.close(); + SimpleTest.finish(); +} + +async function runTest() { + await SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ]}); + winTest = window.open(REQUEST_URL); +} + +window.addEventListener("message", receiveMessage); + +runTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_multiple_redirection.html b/dom/security/test/https-first/test_multiple_redirection.html new file mode 100644 index 0000000000..d631f140e6 --- /dev/null +++ b/dom/security/test/https-first/test_multiple_redirection.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1721410 +Test multiple redirects using https-first and ensure the entire redirect chain is using https +--> + +<head> + <title>HTTPS-First-Mode - Test for multiple redirections</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"; + + SimpleTest.waitForExplicitFinish(); + + const testCase = [ + // test 1: https-first upgrades http://example.com/test1 -> https://example.com/test1 + // that's redirect to https://example.com/.../redirect which then redirects + // to http://example.com/../verify. Since the last redirect is http, and the + // the redirection chain contains already example.com we expect https-first + // to downgrade the request. + {name: "test last redirect HTTP", result: "scheme-http", query: "test1" }, + // test 2: https-first upgrades http://example.com/test2 -> https://example.com/test2 + // that's redirect to https://example.com/.../redirect which then redirects + // to https://example.com/../verify. Since the last redirect is https, we + // expect to reach an https website. + {name: "test last redirect HTTPS", result: "scheme-https", query: "test2"}, + // test 3: https-first upgrades http://example.com/test3 -> https://example.com/test3 + // that's redirect to https://example.com/.../hsts which then sets an hsts header + // and redirects to http://example.com/../verify. Since an hsts header was set + // we expect that to reach an https site + {name: "test last redirect HSTS", result: "scheme-https", query: "test3"}, + // reset: reset hsts header for example.com + {name: "reset HSTS header", result: "scheme-https", query: "reset"}, + ] + let currentTest = 0; + let testWin; + window.addEventListener("message", receiveMessage); + + // receive message from loaded site verifying the scheme of + // the loaded document. + async function receiveMessage(event) { + let test = testCase[currentTest]; + is(event.data.result, + test.result, + "same-origin redirect results in " + test.name + ); + testWin.close(); + if (++currentTest < testCase.length) { + startTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + } + + async function startTest() { + const test = testCase[currentTest]; + // Load an http:// window which gets upgraded to https:// + let uri = + `http://example.com/tests/dom/security/test/https-first/file_multiple_redirection.sjs?${test.query}`; + testWin = window.open(uri); + } + + // Set preference and start test + SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ]}, startTest); + </script> +</body> +</html> diff --git a/dom/security/test/https-first/test_redirect_downgrade.html b/dom/security/test/https-first/test_redirect_downgrade.html new file mode 100644 index 0000000000..07f998c085 --- /dev/null +++ b/dom/security/test/https-first/test_redirect_downgrade.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1707856: Test redirect downgrades with https-first</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 we expect https-first to detect + * that the target site only supports http + * Test 1: Meta Refresh + * Test 2: JS Redirect + * Test 3: 302 redirect + */ + +SimpleTest.waitForExplicitFinish(); + +const REQUEST_URL = + "http://example.com/tests/dom/security/test/https-first/file_redirect_downgrade.sjs"; + +const redirectQueries = ["?test1a", "?test2a","?test3a"]; +let currentTest = 0; +let testWin; +let currentQuery; +window.addEventListener("message", receiveMessage); + +// Receive message and verify that it is from an https site. +// When the message is 'downgraded' then it was send by an http site +// and the redirection worked. +async function receiveMessage(event) { + let data = event.data; + ok(data.result === "downgraded", "Redirected successful to 'http' for " + currentQuery); + ok(data.scheme === "http:", "scheme is 'http' for " + currentQuery ); + testWin.close(); + if (++currentTest < redirectQueries.length) { + runTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +async function runTest() { + currentQuery = redirectQueries[currentTest]; + testWin = window.open(REQUEST_URL + currentQuery, "_blank"); +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true] + ]}, runTest); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_redirect_upgrade.html b/dom/security/test/https-first/test_redirect_upgrade.html new file mode 100644 index 0000000000..6cccf6af67 --- /dev/null +++ b/dom/security/test/https-first/test_redirect_upgrade.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1706351 +Test that 302 redirect requests get upgraded to https:// with HTTPS-First Mode enabled +--> + +<head> + <title>HTTPS-FirstMode - 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-First Mode</h1> + <p>Upgrade Test for insecure redirects.</p> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1706351">Bug 1706351</a> + + <script class="testbody" type="text/javascript"> + "use strict"; + + const redirectCodes = ["301", "302","303","307"]; + let currentTest = 0; + let testWin; + window.addEventListener("message", receiveMessage); + + // Receive message and verify that it is from an https site. + // When the message is 'secure' then it was send by an https site. + async function receiveMessage(event) { + let data = event.data; + let currentRedirectCode = redirectCodes[currentTest]; + ok(data.result === "secure", "Received 'https' for " + currentRedirectCode); + testWin.close(); + if (++currentTest < redirectCodes.length) { + startTest(); + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + } + + async function startTest() { + const currentCode = redirectCodes[currentTest]; + // 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. + testWin = window.open(`https://example.com/tests/dom/security/test/https-first/file_redirect.sjs?${currentCode}`); + } + + // Set preference and start test + SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ["security.mixed_content.block_active_content", false], + ["security.mixed_content.block_display_content", false], + ]}, startTest); + SimpleTest.waitForExplicitFinish(); + </script> +</body> +</html> diff --git a/dom/security/test/https-first/test_referrer_policy.html b/dom/security/test/https-first/test_referrer_policy.html new file mode 100644 index 0000000000..61521e2351 --- /dev/null +++ b/dom/security/test/https-first/test_referrer_policy.html @@ -0,0 +1,237 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1716706 : Write referrer-policy tests for https-first </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 each test with 8 different settings. + * The first is a same origin request from an http site to an https site. + * The second is a same origin request from an https -> https. + * The third is a cross-origin request from an http -> https. + * The fourth is a cross-origin request from an https -> https. + * The fifth is a same origin request from an http -> http site. + * The sixth is a same origin request from an https -> http. + * The seventh is a cross-origin request from an http -> http. + * The last is a cross-origin request from an https -> http. + */ + +SimpleTest.waitForExplicitFinish(); +// This test performs a lot of requests and checks (64 requests). +// So to prevent to get a timeout before executing all test request longer timeout. +SimpleTest.requestLongerTimeout(2); +const SAME_ORIGIN = + "http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?"; +// SAME ORIGIN with "https" instead of "http" +const SAME_ORIGIN_HTTPS = SAME_ORIGIN.replace("http", "https"); + +const CROSS_ORIGIN = + "http://example.org/tests/dom/security/test/https-first/file_referrer_policy.sjs?"; +// CROSS ORIGIN with "https" instead of "http" +const CROSS_ORIGIN_HTTPS = CROSS_ORIGIN.replace("http", "https"); + +// Define test cases. Query equals the test case referrer policy. +// We will set in the final request the url parameters such that 'rp=' equals the referrer policy +//and 'upgrade=' equals '1' if the request should be https. +// For a 'upgrade=0' url parameter the server lead to a timeout such that https-first downgrades +// the request to http. +const testCases = [ + { + query: "no-referrer", + expectedResultSameOriginDownUp: "", + expectedResultSameOriginUpUp: "", + expectedResultCrossOriginDownUp:"", + expectedResultCrossOriginUpUp:"", + expectedResultSameOriginDownDown: "", + expectedResultSameOriginUpDown: "", + expectedResultCrossOriginDownDown:"", + expectedResultCrossOriginUpDown: "", + }, + { + query: "no-referrer-when-downgrade", + expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https", + expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https", + expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https", + expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https", + expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http", + expectedResultSameOriginUpDown: "", + expectedResultCrossOriginDownDown: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http", + expectedResultCrossOriginUpDown:"", + }, + { + query: "origin", + expectedResultSameOriginDownUp: "http://example.com/", + expectedResultSameOriginUpUp: "https://example.com/", + expectedResultCrossOriginDownUp:"http://example.org/", + expectedResultCrossOriginUpUp:"https://example.org/", + expectedResultSameOriginDownDown: "http://example.com/", + expectedResultSameOriginUpDown: "https://example.com/", + expectedResultCrossOriginDownDown:"http://example.org/", + expectedResultCrossOriginUpDown:"https://example.org/", + }, + { + query: "origin-when-cross-origin", + expectedResultSameOriginDownUp: "http://example.com/", + expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=origin-when-cross-origin&upgrade=https-https", + expectedResultCrossOriginDownUp:"http://example.org/", + expectedResultCrossOriginUpUp:"https://example.org/", + expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=origin-when-cross-origin&upgrade=http-http", + expectedResultSameOriginUpDown: "https://example.com/", + expectedResultCrossOriginDownDown:"http://example.org/", + expectedResultCrossOriginUpDown:"https://example.org/", + }, + { + query: "same-origin", + expectedResultSameOriginDownUp: "", + expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=same-origin&upgrade=https-https", + expectedResultCrossOriginDownUp:"", + expectedResultCrossOriginUpUp:"", + expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=same-origin&upgrade=http-http", + expectedResultSameOriginUpDown: "", + expectedResultCrossOriginDownDown: "", + expectedResultCrossOriginUpDown:"", + }, + { + query: "strict-origin", + expectedResultSameOriginDownUp: "http://example.com/", + expectedResultSameOriginUpUp: "https://example.com/", + expectedResultCrossOriginDownUp:"http://example.org/", + expectedResultCrossOriginUpUp:"https://example.org/", + expectedResultSameOriginDownDown: "http://example.com/", + expectedResultSameOriginUpDown: "", + expectedResultCrossOriginDownDown:"http://example.org/", + expectedResultCrossOriginUpDown:"", + }, + { + query: "strict-origin-when-cross-origin", + expectedResultSameOriginDownUp: "http://example.com/", + expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=strict-origin-when-cross-origin&upgrade=https-https", + expectedResultCrossOriginDownUp:"http://example.org/", + expectedResultCrossOriginUpUp:"https://example.org/", + expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=strict-origin-when-cross-origin&upgrade=http-http", + expectedResultSameOriginUpDown: "", + expectedResultCrossOriginDownDown:"http://example.org/", + expectedResultCrossOriginUpDown:"", + }, + { + query: "unsafe-url", + expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-https", + expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https", + expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-https", + expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https", + expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-http", + expectedResultSameOriginUpDown: SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http", + expectedResultCrossOriginDownDown:CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-http", + expectedResultCrossOriginUpDown:CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http", + }, +]; + + +let currentTest = 0; +let sameOriginRequest = true; +let testWin; +let currentQuery; +window.addEventListener("message", receiveMessage); +let currentRun = 0; +// All combinations, HTTP -> HTTPS, HTTPS -> HTTPS, HTTP -> HTTP, HTTPS -> HTTP +const ALL_COMB = ["http-https", "https-https" ,"http-http", "https-http"]; + +// Receive message and verify that we receive the expected referrer header +async function receiveMessage(event) { + let data = event.data; + currentQuery = testCases[currentTest].query; + let currentComb = ALL_COMB[currentRun]; + // if request was http -> https + if (currentComb === "http-https") { + if (sameOriginRequest){ + is(data.result, testCases[currentTest].expectedResultSameOriginDownUp , + "We received for the downgraded same site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN_HTTPS + "sendMe","Opened correct location"); + } else { + is(data.result, testCases[currentTest].expectedResultCrossOriginDownUp , + "We received for the downgraded cross site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location"); + } + // if request was https -> https + } else if (currentComb === "https-https") { + if (sameOriginRequest){ + is(data.result, testCases[currentTest].expectedResultSameOriginUpUp , + "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location"); + } else { + is(data.result, testCases[currentTest].expectedResultCrossOriginUpUp, + "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location"); + } + } else if (currentComb === "http-http") { + if (sameOriginRequest){ + is(data.result, testCases[currentTest].expectedResultSameOriginDownDown , + "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location for" + currentQuery + currentComb); + } else { + is(data.result, testCases[currentTest].expectedResultCrossOriginDownDown, + "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb); + } + } else if (currentComb === "https-http") { + if (sameOriginRequest){ + is(data.result, testCases[currentTest].expectedResultSameOriginUpDown , + "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location " + currentQuery + currentComb); + } else { + is(data.result, testCases[currentTest].expectedResultCrossOriginUpDown, + "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer"); + is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb); + } + } + testWin.close(); + currentRun++; + if (currentTest >= testCases.length -1 && currentRun === ALL_COMB.length && !sameOriginRequest) { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + return; + } + runTest(); +} + +async function runTest() { + currentQuery = testCases[currentTest].query; + // send same origin request + if (sameOriginRequest && currentRun < ALL_COMB.length) { + // if upgrade = 0 downgrade request, else upgrade + testWin = window.open(SAME_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank"); + } else { + // if same origin isn't set, check if we need to send cross origin requests + // eslint-disable-next-line no-lonely-if + if (!sameOriginRequest && currentRun < ALL_COMB.length ) { + // if upgrade = 0 downgrade request, else upgrade + testWin = window.open(CROSS_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank"); + } // else we completed all test case of the current query for the current origin. Prepare and call next test + else { + // reset currentRun and go to next query + currentRun = 0; + if(!sameOriginRequest){ + currentTest++; + } + // run same test again for crossOrigin or start new test with sameOrigin + sameOriginRequest = !sameOriginRequest; + currentQuery = testCases[currentTest].query; + runTest(); + } + } +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ["network.http.referer.disallowCrossSiteRelaxingDefault", false], + ]}, runTest); + +</script> +</body> +</html> diff --git a/dom/security/test/https-first/test_resource_upgrade.html b/dom/security/test/https-first/test_resource_upgrade.html new file mode 100644 index 0000000000..c71879ac42 --- /dev/null +++ b/dom/security/test/https-first/test_resource_upgrade.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML> +<html> + +<head> + <meta charset="utf-8"> + <title>HTTPS-First Mode - Resource Upgrade</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + <h1>HTTPS-First Mode</h1> + <p>Upgrade Test for various resources</p> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1704454">Bug 1704454/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 they do not get upgraded to *https* because + * https-first only applies to top-level requests. + * + * 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 *http* requests. + */ + + const { AppConstants } = SpecialPowers.ChromeUtils.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","top-level" + ] + } + + + 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-first/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_first", true], + ["security.mixed_content.block_active_content", false], + ["security.mixed_content.block_display_content", false] + ] }, runTest); + + </script> +</body> + +</html> diff --git a/dom/security/test/https-first/test_toplevel_cookies.html b/dom/security/test/https-first/test_toplevel_cookies.html new file mode 100644 index 0000000000..2c0c64db46 --- /dev/null +++ b/dom/security/test/https-first/test_toplevel_cookies.html @@ -0,0 +1,116 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1711453 : HTTPS-First: Add test for cookies </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 each test with 4 different cookie settings and + * expect https-first to detect which cookie is same origin and + * which is cross origin. The cookies are in an image or in a frame. + * The 4 cookie settings differ in two flags which are set or not. + * The first call is always with secure flag not set and sameSite=none + * In the second call we don't set the secure flag but sameSite=strict + * In the third call we set the secure flag and sameSite=none + * In the forth call we set the secure flag and sameSite=strict + * More detailed: + * We run the tests in the following order. + * Test 1a: Image is loaded with cookie same-origin, not secure and sameSite=none + * Test 1b: Image is loaded with cookie same-origin, not secure and sameSite=strict + * Test 1c: Image is loaded with cookie same-origin, secure and sameSite=none + * Test 1d: Image is loaded with cookie same-origin, secure and sameSite=strict + * Test 1e: Image is loaded with cookie cross-origin, not secure and sameSite=none + * Test 1f: Image is loaded with cookie cross-origin, not secure and sameSite=strict + * Test 2a: Load frame navigation with cookie same-origin, not secure and sameSite=none + * ... + * Test 3a: Load frame navigation blank with cookie same-origin, not secure and sameSite=none + * ... + * Test 4a: Load frame Inc with cookie same-origin, not secure and sameSite=none + * ... + * Test 5a: Load frame Inc Blank with cookie same-origin, not secure and sameSite=none + * ... + */ + +SimpleTest.waitForExplicitFinish(); + +const SAME_ORIGIN = + "http://example.com/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?"; + +const CROSS_ORIGIN = + "http://example.org/tests/dom/security/test/https-first/file_toplevel_cookies.sjs?"; + +const redirectQueries = ["setImage", "loadNav", "loadNavBlank","loadframeInc", "loadframeIncBlank"]; +let currentTest = 0; +let sameOriginRequest = true; +let testWin; +let currentQuery; +window.addEventListener("message", receiveMessage); +let currentRun = 0; +// All possible cookie attribute combinations +// cookie attributes are secure=set/not set and sameSite= none/ strict +const ALL_COOKIE_COMB = ["notSecure,none", "notSecure,strict", "secure,none", "secure,strict"] + +// Receive message and verify that it is from an https site. +// When the message is 'upgraded' then it was send by an https site +// and validate that we received the right cookie. Verify that for a cross +//origin request we didn't receive a cookie. +async function receiveMessage(event) { + let data = event.data; + currentQuery = redirectQueries[currentTest]; + ok(data.result === "upgraded", "Upgraded successful to https for " + currentQuery); + ok(data.loc.includes("https"), "scheme is 'https' for " + currentQuery ); + if (!sameOriginRequest) { + ok(data.cookie === "", "Cookie from cross-Origin site shouldn't be accepted " + currentQuery + " " + ALL_COOKIE_COMB[currentRun]); + } else { + is(data.cookie.includes(currentQuery + "=" + currentRun), true, "Cookie successfully arrived for " + currentQuery + " " + ALL_COOKIE_COMB[currentRun]); + } + testWin.close(); + currentRun++; + if (currentTest >= redirectQueries.length -1 && currentRun === ALL_COOKIE_COMB.length && !sameOriginRequest) { + window.removeEventListener("message", receiveMessage); + SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault"); + SimpleTest.finish(); + return; + } + runTest(); +} + +async function runTest() { + currentQuery = redirectQueries[currentTest]; + // send same origin request + if (sameOriginRequest && currentRun < ALL_COOKIE_COMB.length) { + testWin = window.open(SAME_ORIGIN + currentQuery + currentRun, "_blank"); + } else { + // if same origin isn't set, check if we need to send cross origin requests + // eslint-disable-next-line no-lonely-if + if (!sameOriginRequest && currentRun < ALL_COOKIE_COMB.length ) { + testWin = window.open(CROSS_ORIGIN + currentQuery + currentRun, "_blank"); + } // else we completed all test case of the current query for the current origin. Prepare and call next test + else { + // reset currentRun and go to next query + currentRun = 0; + if(!sameOriginRequest){ + currentTest++; + } + // run same test again for crossOrigin or start new test with sameOrigin + sameOriginRequest = !sameOriginRequest; + currentQuery = redirectQueries[currentTest]; + runTest(); + } + } +} + +SpecialPowers.pushPrefEnv({ set: [ + ["dom.security.https_first", true], + ["network.cookie.sameSite.noneRequiresSecure", false], + ]}, runTest); + +</script> +</body> +</html> |