diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/security/test/mixedcontentblocker | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/security/test/mixedcontentblocker')
37 files changed, 2646 insertions, 0 deletions
diff --git a/dom/security/test/mixedcontentblocker/auto_upgrading_identity.html b/dom/security/test/mixedcontentblocker/auto_upgrading_identity.html new file mode 100644 index 0000000000..d843b7fae1 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/auto_upgrading_identity.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset='utf-8'> + <title>Bug 1674341: Test SiteIdentity when auto-upgrading mixed content</title> +</head> +<body> + <!-- needs to be http: image for mixed content and auto-upgrading --> + <img type="image/png" id="testimage" src="http://example.com/browser/dom/security/test/mixedcontentblocker/auto_upgrading_identity.png" /> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/auto_upgrading_identity.png b/dom/security/test/mixedcontentblocker/auto_upgrading_identity.png Binary files differnew file mode 100644 index 0000000000..52c591798e --- /dev/null +++ b/dom/security/test/mixedcontentblocker/auto_upgrading_identity.png diff --git a/dom/security/test/mixedcontentblocker/browser.ini b/dom/security/test/mixedcontentblocker/browser.ini new file mode 100644 index 0000000000..eae1091128 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser.ini @@ -0,0 +1,28 @@ +[DEFAULT] +prefs = + dom.security.https_first=false +support-files = + download_page.html + download_server.sjs + +[browser_auto_upgrading_identity.js] +support-files = + auto_upgrading_identity.html + auto_upgrading_identity.png +[browser_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.js] +support-files = + file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html + pass.png + test.ogv + test.wav +[browser_mixed_content_auth_download.js] +support-files = + file_auth_download_page.html + file_auth_download_server.sjs +[browser_mixed_content_auto_upgrade_display_console.js] +support-files = file_mixed_content_auto_upgrade_display_console.html +[browser_test_mixed_content_download.js] +skip-if = + win10_2004 && debug # Bug 1723573 + win11_2009 # Bug 1784764 + os == "linux" && !debug # Bug 1784764 diff --git a/dom/security/test/mixedcontentblocker/browser_auto_upgrading_identity.js b/dom/security/test/mixedcontentblocker/browser_auto_upgrading_identity.js new file mode 100644 index 0000000000..7860c06fc2 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser_auto_upgrading_identity.js @@ -0,0 +1,55 @@ +"use strict"; + +const TEST_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +const TEST_TOPLEVEL_URI = TEST_PATH + "auto_upgrading_identity.html"; + +// auto upgrading mixed content should not indicate passive mixed content loaded +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", true]], + }); + await BrowserTestUtils.withNewTab( + TEST_TOPLEVEL_URI, + async function (browser) { + await ContentTask.spawn(browser, {}, async function () { + let testImg = content.document.getElementById("testimage"); + ok( + testImg.src.includes("auto_upgrading_identity.png"), + "sanity: correct image is loaded" + ); + }); + // Ensure the identiy handler does not show mixed content! + ok( + !gIdentityHandler._isMixedPassiveContentLoaded, + "Auto-Upgrading Mixed Content: Identity should note indicate mixed content" + ); + } + ); +}); + +// regular mixed content test should indicate passive mixed content loaded +add_task(async () => { + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", false]], + }); + await BrowserTestUtils.withNewTab( + TEST_TOPLEVEL_URI, + async function (browser) { + await ContentTask.spawn(browser, {}, async function () { + let testImg = content.document.getElementById("testimage"); + ok( + testImg.src.includes("auto_upgrading_identity.png"), + "sanity: correct image is loaded" + ); + }); + // Ensure the identiy handler does show mixed content! + ok( + gIdentityHandler._isMixedPassiveContentLoaded, + "Regular Mixed Content: Identity should indicate mixed content" + ); + } + ); +}); diff --git a/dom/security/test/mixedcontentblocker/browser_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.js b/dom/security/test/mixedcontentblocker/browser_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.js new file mode 100644 index 0000000000..1f738b951d --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.js @@ -0,0 +1,78 @@ +/* + * Description of the Test: + * We load an https page which uses a CSP including block-all-mixed-content. + * The page embedded an audio, img and video. ML2 should upgrade them and + * CSP should not be triggered. + */ + +const PRE_PATH = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); +var gTestBrowser = null; +let expectedMessages = 3; +function on_new_message(msgObj) { + const message = msgObj.message; + + // Check if csp warns about block-all-mixed content being obsolete + if (message.includes("Content-Security-Policy")) { + ok( + message.includes("block-all-mixed-content obsolete"), + "CSP warns about block-all-mixed content being obsolete" + ); + } + if (message.includes("Mixed Content:")) { + ok( + message.includes("Upgrading insecure display request"), + "msg included a mixed content upgrade" + ); + expectedMessages--; + } +} + +add_task(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", true]], + }); + Services.console.registerListener(on_new_message); + // Starting the test + var url = + PRE_PATH + + "file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html"; + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + waitForLoad: true, + }, + async function (browser) { + let loadedElements = await ContentTask.spawn( + browser, + [], + async function () { + // Check image loaded + let image = content.document.getElementById("some-img"); + let imageLoaded = + image && image.complete && image.naturalHeight !== 0; + // Check audio loaded + let audio = content.document.getElementById("some-audio"); + let audioLoaded = audio && audio.readyState >= 2; + // Check video loaded + let video = content.document.getElementById("some-video"); + //let videoPlayable = await once(video, "loadeddata").then(_ => true); + let videoLoaded = video && video.readyState === 4; + return { audio: audioLoaded, img: imageLoaded, video: videoLoaded }; + } + ); + is(true, loadedElements.img, "Image loaded and was upgraded " + url); + is(true, loadedElements.video, "Video loaded and was upgraded " + url); + is(true, loadedElements.audio, "Audio loaded and was upgraded " + url); + } + ); + + await BrowserTestUtils.waitForCondition(() => expectedMessages === 0); + + // Clean up + Services.console.unregisterListener(on_new_message); +}); diff --git a/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js b/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js new file mode 100644 index 0000000000..e6b01a0cc0 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser_mixed_content_auth_download.js @@ -0,0 +1,165 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs", +}); + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +let authPromptModalType = Services.prefs.getIntPref( + "prompts.modalType.httpAuth" +); + +const downloadMonitoringView = { + _listeners: [], + onDownloadAdded(download) { + for (let listener of this._listeners) { + listener(download); + } + this._listeners = []; + }, + waitForDownload(listener) { + this._listeners.push(listener); + }, +}; + +let SECURE_BASE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "https://example.com/" + ) + "file_auth_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(res => { + downloadMonitoringView.waitForDownload(res); + }); +} +function shouldNotifyDownloadUI() { + return new Promise(res => { + downloadMonitoringView.waitForDownload(async aDownload => { + let { error } = aDownload; + if ( + error.becauseBlockedByReputationCheck && + error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE + ) { + // It's an insecure Download, now Check that it has been cleaned up properly + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download target is not empty!`); + } + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download partFile was not cleaned up properly`); + } + // Assert that the Referrer is present + if (!aDownload.source.referrerInfo) { + throw new Error("The Blocked download is missing the ReferrerInfo"); + } + + res(aDownload); + } else { + ok(false, "No error for download that was expected to error!"); + } + }); + }); +} + +async function resetDownloads() { + // Removes all downloads from the download List + const types = new Set(); + let publicList = await Downloads.getList(Downloads.ALL); + let downloads = await publicList.getAll(); + for (let download of downloads) { + if (download.contentType) { + types.add(download.contentType); + } + publicList.remove(download); + await download.finalize(true); + } +} + +async function runTest(url, link, checkFunction, description) { + await resetDownloads(); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + 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(); + }); + // Wait for the auth prompt, enter the login details and close the prompt + await PromptTestUtils.handleNextPrompt( + gBrowser.selectedBrowser, + { modalType: authPromptModalType, promptType: "promptUserAndPass" }, + { buttonNumClick: 0, loginInput: "user", passwordInput: "pass" } + ); + await checkPromise; + ok(true, description); + // Close download panel + DownloadsPanel.hidePanel(); + is(DownloadsPanel.panel.state, "closed", "Panel should be closed"); + await BrowserTestUtils.removeTab(tab); +} + +add_setup(async function () { + let list = await Downloads.getList(Downloads.ALL); + list.addView(downloadMonitoringView); + registerCleanupFunction(() => list.removeView(downloadMonitoringView)); + // Ensure to delete all cached credentials before running test + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + await SpecialPowers.pushPrefEnv({ + set: [["dom.block_download_insecure", true]], + }); +}); +//Test description: +// 1. Open "https://example.com". +// 2. From "https://example.com" download something, but that download is only available via http +// and with authentication. +// 3. Login and start download. +// 4. Mixed-content blocker blocks download. +// 5. Unblock download and verify the downloaded file. +add_task(async function test_auth_download() { + await runTest( + SECURE_BASE_URL, + "insecure", + async () => { + let [, download] = await Promise.all([ + shouldTriggerDownload(), + shouldNotifyDownloadUI(), + ]); + await download.unblock(); + ok(download.error == null, "There should be no error after unblocking"); + info( + "Start download to be able to validate the size and the success of the download" + ); + await download.start(); + is( + download.contentType, + "text/html", + "File contentType should be correct." + ); + ok(download.succeeded, "Download succeeded!"); + is(download.target.size, 27, "Download has correct size"); + }, + "A locked Download from an auth server should succeeded to Download after a Manual unblock" + ); +}); diff --git a/dom/security/test/mixedcontentblocker/browser_mixed_content_auto_upgrade_display_console.js b/dom/security/test/mixedcontentblocker/browser_mixed_content_auto_upgrade_display_console.js new file mode 100644 index 0000000000..4f41d8b51c --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser_mixed_content_auto_upgrade_display_console.js @@ -0,0 +1,51 @@ +// Bug 1673574 - Improve Console logging for mixed content auto upgrading +"use strict"; + +const testPath = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" +); + +let seenAutoUpgradeMessage = false; + +const kTestURI = + testPath + "file_mixed_content_auto_upgrade_display_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-Only Mode and register console-listener + await SpecialPowers.pushPrefEnv({ + set: [["security.mixed_content.upgrade_display_content", true]], + }); + Services.console.registerListener(on_auto_upgrade_message); + + BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, kTestURI); + + await BrowserTestUtils.waitForCondition(() => seenAutoUpgradeMessage); + + Services.console.unregisterListener(on_auto_upgrade_message); +}); + +function on_auto_upgrade_message(msgObj) { + const message = msgObj.message; + + // The console message is: + // "Mixed Content: Upgrading insecure display request + // ‘http://example.com/file_mixed_content_auto_upgrade_display_console.jpg’ to use ‘https’" + + if (!message.includes("Mixed Content:")) { + return; + } + ok( + message.includes("Upgrading insecure display request"), + "msg includes info" + ); + ok( + message.includes("file_mixed_content_auto_upgrade_display_console.jpg"), + "msg includes file" + ); + seenAutoUpgradeMessage = true; +} diff --git a/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js b/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js new file mode 100644 index 0000000000..41b4d3d524 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/browser_test_mixed_content_download.js @@ -0,0 +1,333 @@ +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 INSECURE_BASE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://example.com/" + ) + "download_page.html"; +let SECURE_BASE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "https://example.com/" + ) + "download_page.html"; + +function promiseFocus() { + return new Promise(resolve => { + waitForFocus(resolve); + }); +} + +function promisePanelOpened() { + if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { + return Promise.resolve(); + } + return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown"); +} + +async function task_openPanel() { + await promiseFocus(); + + let promise = promisePanelOpened(); + DownloadsPanel.showPanel(); + await promise; +} + +const downloadMonitoringView = { + _listeners: [], + onDownloadAdded(download) { + for (let listener of this._listeners) { + listener(download); + } + this._listeners = []; + }, + waitForDownload(listener) { + this._listeners.push(listener); + }, +}; + +/** + * Waits until a download is triggered. + * Unless the always_ask_before_handling_new_types pref is true, the download + * will simply be saved, so resolve when the view is notified of the new + * download. Otherwise, it waits until a prompt is shown, selects the choosen + * <action>, then accepts the dialog + * @param [action] Which action to select, either: + * "handleInternally", "save" or "open". + * @returns {Promise} Resolved once done. + */ + +function shouldTriggerDownload(action = "save") { + if ( + Services.prefs.getBoolPref( + "browser.download.always_ask_before_handling_new_types" + ) + ) { + 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(action); + actionRadio.click(); + button.disabled = false; + dialog.acceptDialog(); + resolve(); + } else { + reject(); + } + }, win); + }, + }); + }); + } + return new Promise(res => { + downloadMonitoringView.waitForDownload(res); + }); +} + +const CONSOLE_ERROR_MESSAGE = "Blocked downloading insecure content"; + +function shouldConsoleError() { + // Waits until CONSOLE_ERROR_MESSAGE was logged + return new Promise((resolve, reject) => { + function listener(msgObj) { + let text = msgObj.message; + if (text.includes(CONSOLE_ERROR_MESSAGE)) { + Services.console.unregisterListener(listener); + resolve(); + } + } + 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); + } + } +} + +function shouldNotifyDownloadUI() { + return new Promise(res => { + downloadMonitoringView.waitForDownload(async aDownload => { + let { error } = aDownload; + if ( + error.becauseBlockedByReputationCheck && + error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE + ) { + // It's an insecure Download, now Check that it has been cleaned up properly + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download target is not empty!`); + } + if ((await IOUtils.stat(aDownload.target.path)).size != 0) { + throw new Error(`Download partFile was not cleaned up properly`); + } + // Assert that the Referrer is presnt + if (!aDownload.source.referrerInfo) { + throw new Error("The Blocked download is missing the ReferrerInfo"); + } + + res(aDownload); + } else { + ok(false, "No error for download that was expected to error!"); + } + }); + }); +} + +async function runTest(url, link, checkFunction, description) { + await SpecialPowers.pushPrefEnv({ + set: [["dom.block_download_insecure", true]], + }); + await resetDownloads(); + + let tab = BrowserTestUtils.addTab(gBrowser, url); + gBrowser.selectedTab = tab; + + let browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + + 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); + + await SpecialPowers.popPrefEnv(); +} + +add_setup(async () => { + let list = await Downloads.getList(Downloads.ALL); + list.addView(downloadMonitoringView); + registerCleanupFunction(() => list.removeView(downloadMonitoringView)); +}); + +// Test Blocking +add_task(async function test_blocking() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + await runTest( + INSECURE_BASE_URL, + "insecure", + shouldTriggerDownload, + "Insecure -> Insecure should download" + ); + await runTest( + INSECURE_BASE_URL, + "secure", + shouldTriggerDownload, + "Insecure -> Secure should download" + ); + await runTest( + SECURE_BASE_URL, + "insecure", + () => + Promise.all([ + shouldTriggerDownload(), + shouldNotifyDownloadUI(), + shouldConsoleError(), + ]), + "Secure -> Insecure should Error" + ); + await runTest( + SECURE_BASE_URL, + "secure", + shouldTriggerDownload, + "Secure -> Secure should Download" + ); + await SpecialPowers.popPrefEnv(); + } +}); + +// Test Manual Unblocking +add_task(async function test_manual_unblocking() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + await runTest( + SECURE_BASE_URL, + "insecure", + async () => { + let [, download] = await Promise.all([ + shouldTriggerDownload(), + shouldNotifyDownloadUI(), + ]); + await download.unblock(); + ok(download.error == null, "There should be no error after unblocking"); + }, + "A Blocked Download Should succeeded to Download after a Manual unblock" + ); + await SpecialPowers.popPrefEnv(); + } +}); + +// Test Unblock Download Visible +add_task(async function test_unblock_download_visible() { + for (let prefVal of [true, false]) { + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", prefVal]], + }); + // Focus, open and close the panel once + // to make sure the panel is loaded and ready + await promiseFocus(); + await runTest( + SECURE_BASE_URL, + "insecure", + async () => { + let panelHasOpened = promisePanelOpened(); + info("awaiting that the download is triggered and added to the list"); + await Promise.all([shouldTriggerDownload(), shouldNotifyDownloadUI()]); + info("awaiting that the Download list shows itself"); + await panelHasOpened; + DownloadsPanel.hidePanel(); + ok(true, "The Download Panel should have opened on blocked download"); + }, + "A Blocked Download Should open the Download Panel" + ); + await SpecialPowers.popPrefEnv(); + } +}); + +// Test Download an insecure svg and choose "Open with Firefox" +add_task(async function download_open_insecure_SVG() { + const mimeInfo = MIMEService.getFromTypeAndExtension("image/svg+xml", "svg"); + mimeInfo.alwaysAskBeforeHandling = false; + mimeInfo.preferredAction = mimeInfo.handleInternally; + HandlerService.store(mimeInfo); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", false]], + }); + await promiseFocus(); + await runTest( + SECURE_BASE_URL, + "insecureSVG", + async () => { + info("awaiting that the download is triggered and added to the list"); + let [_, download] = await Promise.all([ + shouldTriggerDownload("handleInternally"), + shouldNotifyDownloadUI(), + ]); + + let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + await download.unblock(); + ok(download.error == null, "There should be no error after unblocking"); + + let tab = await newTabPromise; + + ok( + tab.linkedBrowser._documentURI.filePath.includes(".svg"), + "The download target was opened" + ); + BrowserTestUtils.removeTab(tab); + ok(true, "The Content was opened in a new tab"); + await SpecialPowers.popPrefEnv(); + }, + "A Blocked SVG can be opened internally" + ); + + HandlerService.remove(mimeInfo); +}); diff --git a/dom/security/test/mixedcontentblocker/download_page.html b/dom/security/test/mixedcontentblocker/download_page.html new file mode 100644 index 0000000000..86f605c478 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/download_page.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=676619 +--> + <head> + + <title>Test for the download attribute</title> + + </head> + <body> + hi + + <script> + const host = window.location.host; + const path = location.pathname.replace("download_page.html","download_server.sjs"); + + const secureLink = document.createElement("a"); + secureLink.href=`https://${host}/${path}`; + secureLink.download="true"; + secureLink.textContent="Secure Link"; + secureLink.id="secure"; + + const insecureLink = document.createElement("a"); + insecureLink.href=`http://${host}/${path}`; + insecureLink.download="true"; + insecureLink.id="insecure"; + insecureLink.textContent="Not secure Link"; + + const insecureSVG = document.createElement("a"); + insecureSVG.href=`http://${host}/${path}?type=image/svg+xml&name=example.svg`; + insecureSVG.download="true"; + insecureSVG.id="insecureSVG"; + insecureSVG.textContent="Not secure Link to SVG"; + + document.body.append(insecureSVG); + document.body.append(secureLink); + document.body.append(insecureLink); + </script> + </body> +</html> diff --git a/dom/security/test/mixedcontentblocker/download_server.sjs b/dom/security/test/mixedcontentblocker/download_server.sjs new file mode 100644 index 0000000000..e659df2f40 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/download_server.sjs @@ -0,0 +1,20 @@ +// force the Browser to Show a Download Prompt + +function handleRequest(request, response) { + let type = "image/png"; + let filename = "hello.png"; + request.queryString.split("&").forEach(val => { + var [key, value] = val.split("="); + if (key == "type") { + type = value; + } + if (key == "name") { + filename = value; + } + }); + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Disposition", `attachment; filename=${filename}`); + response.setHeader("Content-Type", type); + response.write("🙈🙊🐵🙊"); +} diff --git a/dom/security/test/mixedcontentblocker/file_auth_download_page.html b/dom/security/test/mixedcontentblocker/file_auth_download_page.html new file mode 100644 index 0000000000..931d9d00ad --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_auth_download_page.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> + <head> + <title>Test mixed content download with auth header by https-first</title> + </head> + <body> + hi + + <script> + const host = window.location.host; + const path = location.pathname.replace("file_auth_download_page.html","file_auth_download_server.sjs"); + + const insecureLink = document.createElement("a"); + insecureLink.href = `http://${host}${path}?user=user&pass=pass`; + insecureLink.download = "true"; + insecureLink.id = "insecure"; + insecureLink.textContent = "Not secure Link"; + + document.body.append(insecureLink); + </script> + </body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_auth_download_server.sjs b/dom/security/test/mixedcontentblocker/file_auth_download_server.sjs new file mode 100644 index 0000000000..d28625abb6 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_auth_download_server.sjs @@ -0,0 +1,62 @@ +"use strict"; + +function handleRequest(request, response) { + let match; + + // Allow the caller to drive how authentication is processed via the query. + // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar + // The extra ? allows the user/pass/realm checks to succeed if the name is + // at the beginning of the query string. + Components.utils.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + let expected_user = query.get("user"); + let expected_pass = query.get("pass"); + let realm = query.get("realm"); + + // Look for an authentication header, if any, in the request. + // + // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + // + // This test only supports Basic auth. The value sent by the client is + // "username:password", obscured with base64 encoding. + + let actual_user = "", + actual_pass = "", + authHeader; + if (request.hasHeader("Authorization")) { + authHeader = request.getHeader("Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) { + throw new Error("Couldn't parse auth header: " + authHeader); + } + // Decode base64 to string + let userpass = atob(match[1]); + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) { + throw new Error("Couldn't decode auth header: " + userpass); + } + actual_user = match[1]; + actual_pass = match[2]; + } + + // Don't request authentication if the credentials we got were what we + // expected. + let requestAuth = + expected_user != actual_user || expected_pass != actual_pass; + + if (requestAuth) { + response.setStatusLine("1.0", 401, "Authentication required"); + response.setHeader("WWW-Authenticate", 'basic realm="' + realm + '"', true); + response.write("Authentication required"); + } else { + response.setStatusLine("1.0", 200, "OK"); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader( + "Content-Disposition", + "attachment; filename=dummy-file.html" + ); + response.setHeader("Content-Type", "text/html"); + response.write("<p id='success'>SUCCESS</p>"); + } +} diff --git a/dom/security/test/mixedcontentblocker/file_bug1551886.html b/dom/security/test/mixedcontentblocker/file_bug1551886.html new file mode 100644 index 0000000000..41c46b5273 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_bug1551886.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> +</head> + +<body> +<script> + let f = document.createElement("iframe"); + f.src = "data:text/html,<iframe src='http://example.com' onload=\"parent.postMessage({status:'loaded', type: 'http'}, 'https://example.com')\" onerror=\"parent.postMessage({status:'blocked', type: 'http'}, 'https://example.com')\"></iframe>"; + window.addEventListener("message", (event) => { + parent.postMessage(event.data, "http://mochi.test:8888"); + + // Only create second iframe once + if(event.data.type === "https") { + return; + } + + let f2 = document.createElement("iframe"); + f2.src = "data:text/html,<iframe src='https://example.com' onload=\"parent.postMessage({status:'loaded', type: 'https'}, 'https://example.com')\" onerror=\"parent.postMessage({status:'blocked', type: 'https'}, 'https://example.com')\"></iframe>"; + document.body.appendChild(f2); + }); + document.body.appendChild(f); +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html b/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html new file mode 100644 index 0000000000..f1459d3667 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker - Mailto Protocol Compose Page +https://bugzilla.mozilla.org/show_bug.cgi?id=803225 +--> +<head> <meta charset="utf-8"> +</head> +<body> +Hello +<script>window.close();</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html b/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html new file mode 100644 index 0000000000..80e97443ed --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_csp_block_all_mixedcontent_and_mixed_content_display_upgrade.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1806080 - ML2 with CSP block-all-mixed-content </title> + <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content"> +</head> +<body> + <!--upgradeable resources---> + <img id="some-img" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/pass.png" width="100px"> + <video id="some-video" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/test.ogv" width="100px"> + <audio id="some-audio" src="http://test1.example.com/browser/dom/security/test/mixedcontentblocker/test.wav" width="100px"> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation.html b/dom/security/test/mixedcontentblocker/file_frameNavigation.html new file mode 100644 index 0000000000..fd9ea23178 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker related to navigating children, grandchildren, etc +https://bugzilla.mozilla.org/show_bug.cgi?id=840388 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Mixed Content Frame Navigation</title> +</head> +<body> +<div id="testContent"></div> + +<script> + var baseUrlHttps = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html"; + + // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds. + var MAX_COUNT = 50; + var TIMEOUT_INTERVAL = 100; + + var testContent = document.getElementById("testContent"); + + // Test 1: Navigate secure iframe to insecure iframe on an insecure page + var iframe_test1 = document.createElement("iframe"); + var counter_test1 = 0; + iframe_test1.src = baseUrlHttps + "?insecurePage_navigate_child"; + iframe_test1.setAttribute("id", "test1"); + iframe_test1.onerror = function() { + parent.postMessage({"test": "insecurePage_navigate_child", "msg": "got an onerror alert when loading or navigating testing iframe"}, "http://mochi.test:8888"); + }; + testContent.appendChild(iframe_test1); + + function navigationStatus(iframe_test1) + { + // When the page is navigating, it goes through about:blank and we will get a permission denied for loc. + // Catch that specific exception and return + try { + var loc = document.getElementById("test1").contentDocument.location; + } catch(e) { + if (e.name === "SecurityError") { + // We received an exception we didn't expect. + throw e; + } + counter_test1++; + return; + } + if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response") { + return; + } + else { + if(counter_test1 < MAX_COUNT) { + counter_test1++; + setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1); + } + else { + // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked + parent.postMessage({"test": "insecurePage_navigate_child", "msg": "navigating to insecure iframe blocked on insecure page"}, "http://mochi.test:8888"); + } + } + } + + setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1); + + // Test 2: Navigate secure grandchild iframe to insecure grandchild iframe on a page that has no secure parents + var iframe_test2 = document.createElement("iframe"); + iframe_test2.src = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html" + iframe_test2.onerror = function() { + parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "got an on error alert when loading or navigating testing iframe"}, "http://mochi.test:8888"); + }; + testContent.appendChild(iframe_test2); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html new file mode 100644 index 0000000000..c4502ce8d2 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker - Opening link with _blank target in an https iframe. +https://bugzilla.mozilla.org/show_bug.cgi?id=841850 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Mixed Content Frame Navigation</title> +</head> +<body> +<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?blankTarget" id="blankTarget" target="_blank" rel="opener">Go to http site</a> + +<script> + var blankTarget = document.getElementById("blankTarget"); + blankTarget.click(); + + var observer = { + observe(subject, topic, data) { + if (topic === "specialpowers-http-notify-request" && + data === "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?blankTarget") { + parent.parent.postMessage({"test": "blankTarget", "msg": "opened an http link with target=_blank from a secure page"}, "http://mochi.test:8888"); + SpecialPowers.removeObserver(observer, "specialpowers-http-notify-request"); + } + } + } + + // This is a special observer topic that is proxied from http-on-modify-request + // in the parent process to inform us when a URI is loaded + SpecialPowers.addObserver(observer, "specialpowers-http-notify-request"); +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html new file mode 100644 index 0000000000..10cf1e4d90 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker - Navigating Grandchild frames when a secure parent doesn't exist +https://bugzilla.mozilla.org/show_bug.cgi?id=840388 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Mixed Content Frame Navigation</title> +</head> +<body> +<iframe src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild" id="child"></iframe> + +<script> + // For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds. + var MAX_COUNT = 50; + var TIMEOUT_INTERVAL = 100; + var counter = 0; + + var child = document.getElementById("child"); + function navigationStatus(child) + { + // When the page is navigating, it goes through about:blank and we will get a permission denied for loc. + // Catch that specific exception and return + try { + var loc; + if (child.contentDocument) { + loc = child.contentDocument.location; + } + } catch(e) { + if (e.message && !e.message.includes("Permission denied to access property")) { + // We received an exception we didn't expect. + throw e; + } + counter++; + return; + } + if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild_response") { + return; + } + else { + if(counter < MAX_COUNT) { + counter++; + setTimeout(navigationStatus, TIMEOUT_INTERVAL, child); + } + else { + // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked + parent.parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "navigating to insecure grandchild iframe blocked on insecure page"}, "http://mochi.test:8888"); + } + } + } + + setTimeout(navigationStatus, TIMEOUT_INTERVAL, child); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html new file mode 100644 index 0000000000..251bb73e33 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<body> +<div id="content"></div> +<script> + // get the case from the query string + var type = location.search.substring(1); + + function clickLink() { + // If we don't reflow before clicking the link, the test will fail intermittently. The reason is still unknown. We'll track this issue in bug 1259715. + requestAnimationFrame(function() { + setTimeout(function() { + document.getElementById("link").click(); + }, 0); + }); + } + + switch (type) { + case "insecurePage_navigate_child": + document.getElementById("content").innerHTML = + '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response" id="link">Testing\<\/a>'; + clickLink(); + break; + + case "insecurePage_navigate_child_response": + parent.parent.postMessage({"test": "insecurePage_navigate_child", "msg": "navigated to insecure iframe on insecure page"}, "http://mochi.test:8888"); + document.getElementById("content").innerHTML = "Navigated from secure to insecure frame on an insecure page"; + break; + + case "insecurePage_navigate_grandchild": + document.getElementById("content").innerHTML = + '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild_response" id="link">Testing\<\/a>'; + clickLink(); + break; + + case "insecurePage_navigate_grandchild_response": + parent.parent.parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "navigated to insecure grandchild iframe on insecure page"}, "http://mochi.test:8888"); + document.getElementById("content").innerHTML = "Navigated from secure to insecure grandchild frame on an insecure page"; + break; + + case "securePage_navigate_child": + document.getElementById("content").innerHTML = + '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_child_response" id="link">Testing\<\/a>'; + clickLink(); + break; + + case "securePage_navigate_child_response": + document.getElementById("content").innerHTML = "<p>Navigated from secure to insecure frame on a secure page</p>"; + parent.parent.postMessage({"test": "securePage_navigate_child", "msg": "navigated to insecure iframe on secure page"}, "http://mochi.test:8888"); + break; + + case "securePage_navigate_grandchild": + document.getElementById("content").innerHTML= + '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild_response" id="link">Testing\<\/a>'; + clickLink(); + break; + + case "securePage_navigate_grandchild_response": + parent.parent.parent.postMessage({"test": "securePage_navigate_grandchild", "msg": "navigated to insecure grandchild iframe on secure page"}, "http://mochi.test:8888"); + document.getElementById("content").innerHTML = "<p>Navigated from secure to insecure grandchild frame on a secure page</p>"; + break; + + case "blankTarget": + window.close(); + break; + + default: + document.getElementById("content").innerHTML = "Hello"; + break; + } + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html new file mode 100644 index 0000000000..692d542067 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker related to navigating children, grandchildren, etc +https://bugzilla.mozilla.org/show_bug.cgi?id=840388 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Mixed Content Frame Navigation</title> +</head> +<body> +<div id="testContent"></div> + +<script> + var baseUrl = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html"; + + // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds. + var MAX_COUNT = 50; + var TIMEOUT_INTERVAL = 100; + + var testContent = document.getElementById("testContent"); + + // Test 1: Navigate secure iframe to insecure iframe on a secure page + var iframe_test1 = document.createElement("iframe"); + var counter_test1 = 0; + iframe_test1.setAttribute("id", "test1"); + iframe_test1.src = baseUrl + "?securePage_navigate_child"; + iframe_test1.onerror = function() { + parent.postMessage({"test": "securePage_navigate_child", "msg": "navigating to insecure iframe blocked on secure page"}, "http://mochi.test:8888"); + }; + testContent.appendChild(iframe_test1); + + function navigationStatus(iframe_test1) + { + // When the page is navigating, it goes through about:blank and we will get a permission denied for loc. + // Catch that specific exception and return + try { + var loc = document.getElementById("test1").contentDocument.location; + } catch(e) { + if (e.name === "SecurityError") { + // We received an exception we didn't expect. + throw e; + } + counter_test1++; + return; + } + if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response") { + return; + } else { + if(counter_test1 < MAX_COUNT) { + counter_test1++; + setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1); + } + else { + // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked + parent.postMessage({"test": "securePage_navigate_child", "msg": "navigating to insecure iframe blocked on secure page"}, "http://mochi.test:8888"); + } + } + } + + setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1); + + // Test 2: Open an http page in a new tab from a link click with target=_blank. + var iframe_test3 = document.createElement("iframe"); + iframe_test3.src = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html"; + iframe_test3.onerror = function() { + parent.postMessage({"test": "blankTarget", "msg": "got an onerror event when loading or navigating testing iframe"}, "http://mochi.test:8888"); + }; + testContent.appendChild(iframe_test3); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html new file mode 100644 index 0000000000..235ac0055e --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker - Navigating Grandchild Frames when a secure parent exists +https://bugzilla.mozilla.org/show_bug.cgi?id=840388 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Mixed Content Frame Navigation</title> +</head> +<body> + +<iframe src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild" id="child"></iframe> +<script> + // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds. + var MAX_COUNT = 50; + var TIMEOUT_INTERVAL = 100; + var counter = 0; + + var child = document.getElementById("child"); + function navigationStatus(child) + { + var loc; + // When the page is navigating, it goes through about:blank and we will get a permission denied for loc. + // Catch that specific exception and return + try { + loc = document.getElementById("child").contentDocument.location; + } catch(e) { + if (e.message && !e.message.includes("Permission denied to access property")) { + // We received an exception we didn't expect. + throw e; + } + counter++; + return; + } + if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild_response") { + return; + } + else { + if(counter < MAX_COUNT) { + counter++; + setTimeout(navigationStatus, TIMEOUT_INTERVAL, child); + } + else { + // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked + dump("\nThe current location of the grandchild iframe is: "+loc+".\n"); + dump("\nWe have past the maximum timeout. Navigating a grandchild iframe from an https location to an http location on a secure page failed. We are about to post message to the top level page\n"); + parent.parent.postMessage({"test": "securePage_navigate_grandchild", "msg": "navigating to insecure grandchild iframe blocked on secure page"}, "http://mochi.test:8888"); + dump("\nAttempted postMessage\n"); + } + } + } + + setTimeout(navigationStatus, TIMEOUT_INTERVAL, child); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_main.html b/dom/security/test/mixedcontentblocker/file_main.html new file mode 100644 index 0000000000..e4a2d8d08b --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_main.html @@ -0,0 +1,338 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker +https://bugzilla.mozilla.org/show_bug.cgi?id=62178 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Bug 62178</title> + <script src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> +<div id="testContent"></div> + +<!-- types the Mixed Content Blocker can block + /* + switch (aContentType) { + case nsIContentPolicy::TYPE_OBJECT: + case nsIContentPolicy::TYPE_SCRIPT: + case nsIContentPolicy::TYPE_STYLESHEET: + case nsIContentPolicy::TYPE_SUBDOCUMENT: + case nsIContentPolicy::TYPE_XMLHTTPREQUEST: + + case nsIContentPolicy::TYPE_FONT: - NO TEST: + Load events for external fonts are not detectable by javascript. + case nsIContentPolicy::TYPE_WEBSOCKET: - NO TEST: + websocket connections over https require an encrypted websocket protocol (wss:) + + case nsIContentPolicy::TYPE_IMAGE: + case nsIContentPolicy::TYPE_IMAGESET: + case nsIContentPolicy::TYPE_MEDIA: + case nsIContentPolicy::TYPE_PING: + our ping implementation is off by default and does not comply with the current spec (bug 786347) + case nsIContentPolicy::TYPE_BEACON: + + } + */ +--> + +<script> + function ok(a, msg) { + parent.postMessage({msg, check: true, status: !!a}, "http://mochi.test:8888"); + } + + function is(a, b, msg) { + ok(a == b, msg); + } + + function report(type, msg) { + parent.postMessage({test: type, msg}, "http://mochi.test:8888"); + } + + function uniqueID() { + return Math.floor(Math.random() * 100000) + } + function uniqueIDParam(id) { + return "&uniqueID=" + id; + } + + async function init() { + var baseUrl = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs"; + var checkLastRequestUrl = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs?lastRequest=true"; + + //For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds. + var MAX_COUNT = 100; + var TIMEOUT_INTERVAL = 100; + + var testContent = document.getElementById("testContent"); + + async function checkLastRequest() { + const response = await fetch(checkLastRequestUrl); + return response.json(); + } + + /* Part 1: Mixed Script tests */ + + // Test 1a: insecure object + var object = document.createElement("object"); + var objectId = uniqueID(); + object.data = baseUrl + "?type=object" + uniqueIDParam(objectId); + object.type = "image/png"; + object.width = "200"; + object.height = "200"; + + testContent.appendChild(object); + + var objectCount = 0; + + function objectStatus(object) { + // Expose our privileged bits on the object. We will match the MIME type to the one + // provided by file_server.sjs + object = SpecialPowers.wrap(object); + var typeIsSet = object.actualType && (object.actualType !== ''); + var isLoaded = typeIsSet && object.actualType === 'application/x-test-match'; + if (isLoaded) { + //object loaded + report("object", "insecure object loaded"); + } + else { + if(!typeIsSet && objectCount < MAX_COUNT) { + objectCount++; + setTimeout(objectStatus, TIMEOUT_INTERVAL, object); + } + else { + //After we have called setTimeout the maximum number of times, assume object is blocked + report("object", "insecure object blocked"); + } + } + } + + // object does not have onload and onerror events. Hence we need a setTimeout to check the object's status + setTimeout(objectStatus, TIMEOUT_INTERVAL, object); + + // Test 1b: insecure script + var script = document.createElement("script"); + var scriptId = uniqueID(); + var scriptLoad = false; + var scriptCount = 0; + script.src = baseUrl + "?type=script" + uniqueIDParam(scriptId); + script.onload = function(e) { + report("script", "insecure script loaded"); + scriptLoad = true; + } + testContent.appendChild(script); + + function scriptStatus(script) + { + if(scriptLoad) { + return; + } + else { + if(scriptCount < MAX_COUNT) { + scriptCount++; + setTimeout(scriptStatus, TIMEOUT_INTERVAL, script); + } + else { + //After we have called setTimeout the maximum number of times, assume script is blocked + report("script", "insecure script blocked"); + } + } + } + + // scripts blocked by Content Policy's do not have onerror events (see bug 789856). Hence we need a setTimeout to check the script's status + setTimeout(scriptStatus, TIMEOUT_INTERVAL, script); + + + // Test 1c: insecure stylesheet + var cssStyleSheet = document.createElement("link"); + var cssStyleSheetId = uniqueID(); + cssStyleSheet.rel = "stylesheet"; + cssStyleSheet.href = baseUrl + "?type=stylesheet" + uniqueIDParam(cssStyleSheetId); + cssStyleSheet.type = "text/css"; + testContent.appendChild(cssStyleSheet); + + var styleCount = 0; + + function styleStatus(cssStyleSheet) { + if( cssStyleSheet.sheet || cssStyleSheet.styleSheet || cssStyleSheet.innerHTML ) { + report("stylesheet", "insecure stylesheet loaded"); + } + else { + if(styleCount < MAX_COUNT) { + styleCount++; + setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet); + } + else { + //After we have called setTimeout the maximum number of times, assume stylesheet is blocked + report("stylesheet", "insecure stylesheet blocked"); + } + } + } + + // link does not have onload and onerror events. Hence we need a setTimeout to check the link's status + window.setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet); + + // Test 1d: insecure iframe + var iframe = document.createElement("iframe"); + var iframeId = uniqueID(); + iframe.src = baseUrl + "?type=iframe" + uniqueIDParam(iframeId); + iframe.onload = function() { + report("iframe", "insecure iframe loaded"); + } + iframe.onerror = function() { + report("iframe", "insecure iframe blocked"); + }; + testContent.appendChild(iframe); + + + // Test 1e: insecure xhr + await new Promise((resolve) => { + var xhr = new XMLHttpRequest; + var xhrId = uniqueID(); + try { + xhr.open("GET", baseUrl + "?type=xhr" + uniqueIDParam(xhrId), true); + xhr.send(); + xhr.onloadend = function (oEvent) { + if (xhr.status == 200) { + report("xhr", "insecure xhr loaded"); + resolve(); + } + else { + report("xhr", "insecure xhr blocked"); + resolve(); + } + } + } catch(ex) { + report("xhr", "insecure xhr blocked"); + resolve(); + } + }); + + /* Part 2: Mixed Display tests */ + + // Shorthand for all image test variants + async function imgHandlers(img, test, uniqueID) { + await new Promise((resolve) => { + img.onload = async () => { + const lastRequest = await checkLastRequest(); + is(lastRequest.uniqueID, uniqueID, "UniqueID for the last request matches"); + let message = "insecure image loaded"; + if (lastRequest.scheme == "https") { + message = "secure image loaded after upgrade"; + } + report(test, message); + resolve(); + } + img.onerror = async () => { + let message = "insecure image blocked"; + report(test, message); + resolve(); + } + }); + } + + // Test 2a: insecure image + var img = document.createElement("img"); + var imgId = uniqueID(); + img.src = baseUrl + "?type=img" + uniqueIDParam(imgId); + await imgHandlers(img, "image", imgId); + // We don't need to append the image to the document. Doing so causes the image test to run twice. + + // Test 2b: insecure media + var media = document.createElement("video"); + var mediaId = uniqueID(); + media.src = baseUrl + "?type=media" + uniqueIDParam(mediaId); + media.width = "320"; + media.height = "200"; + media.type = "video/ogg"; + await new Promise(res => { + media.onloadeddata = async () => { + const lastRequest = await checkLastRequest(); + is(lastRequest.uniqueID, mediaId, "UniqueID for the last request matches"); + let message = "insecure media loaded"; + if (lastRequest.scheme == "https") { + message = "secure media loaded after upgrade"; + } + report("media", message); + res(); + } + media.onerror = function() { + report("media", "insecure media blocked"); + res(); + } + }); + // We don't need to append the video to the document. Doing so causes the image test to run twice. + + /* Part 3: Mixed Active Tests for Image srcset */ + + // Test 3a: image with srcset + var imgA = document.createElement("img"); + var imgAId = uniqueID(); + imgA.srcset = baseUrl + "?type=img&subtype=imageSrcset" + uniqueIDParam(imgAId); + await imgHandlers(imgA, "imageSrcset", imgAId); + + // Test 3b: image with srcset, using fallback from src, should still use imageset policy + var imgB = document.createElement("img"); + var imgBId = uniqueID(); + imgB.srcset = " "; + imgB.src = baseUrl + "?type=img&subtype=imageSrcSetFallback" + uniqueIDParam(imgBId); + await imgHandlers(imgB, "imageSrcsetFallback", imgBId); + + // Test 3c: image in <picture> + var imgC = document.createElement("img"); + var pictureC = document.createElement("picture"); + var sourceC = document.createElement("source"); + var sourceCId = uniqueID(); + sourceC.srcset = baseUrl + "?type=img&subtype=imagePicture" + uniqueIDParam(sourceCId); + pictureC.appendChild(sourceC); + pictureC.appendChild(imgC); + await imgHandlers(imgC, "imagePicture", sourceCId); + + // Test 3d: Loaded basic image switching to a <picture>, loading + // same source, should still redo the request with new + // policy. + var imgD = document.createElement("img"); + var imgDId = uniqueID(); + var sourceDId = uniqueID(); + imgD.src = baseUrl + "?type=img&subtype=imageJoinPicture" + uniqueIDParam(imgDId); + await new Promise(res => { + imgD.onload = imgD.onerror = function() { + // Whether or not it loads, we want to now append it to a picture and observe + var pictureD = document.createElement("picture"); + var sourceD = document.createElement("source"); + sourceD.srcset = baseUrl + "?type=img&subtype=imageJoinPicture2" + uniqueIDParam(sourceDId); + pictureD.appendChild(sourceD); + pictureD.appendChild(imgD); + res(); + } + }); + await imgHandlers(imgD, "imageJoinPicture", sourceDId); + + // Test 3e: img load from <picture> source reverts to img.src as it + // is removed -- the new request should revert to mixed + // display policy + var imgE = document.createElement("img"); + var pictureE = document.createElement("picture"); + var pictureEId = uniqueID(); + var sourceE = document.createElement("source"); + var sourceEId = uniqueID(); + sourceE.srcset = baseUrl + "?type=img&subtype=imageLeavePicture" + uniqueIDParam(sourceEId); + pictureE.appendChild(sourceE); + pictureE.appendChild(imgE); + imgE.src = baseUrl + "?type=img&subtype=imageLeavePicture2" + uniqueIDParam(pictureEId); + await new Promise(res => { + imgE.onload = imgE.onerror = function() { + // Whether or not it loads, remove it from the picture and observe + pictureE.removeChild(imgE) + res(); + } + }); + await imgHandlers(imgE, "imageLeavePicture", pictureEId); + } + + init(); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_main_bug803225.html b/dom/security/test/mixedcontentblocker/file_main_bug803225.html new file mode 100644 index 0000000000..3404e54471 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_main_bug803225.html @@ -0,0 +1,175 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker - Allowed Protocols +https://bugzilla.mozilla.org/show_bug.cgi?id=803225 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Bug 62178</title> + <script src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> +<div id="testContent"></div> + +<!-- Test additional schemes the Mixed Content Blocker should not block + "about" protocol URIs that are URI_SAFE_FOR_UNTRUSTED_CONTENT (moz-safe-about; see nsAboutProtocolHandler::NewURI + "data", + "javascript", + "mailto", + "resource", + "wss" +--> + +<script> + const { AppConstants } = SpecialPowers.ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" + ); + + //For tests that require setTimeout, set the timeout interval + var TIMEOUT_INTERVAL = 100; + + var testContent = document.getElementById("testContent"); + + // Test 1 & 2: about and javascript protcols within an iframe + var data = Array(2,2); + var protocols = [ + ["about", ""], //When no source is specified, the frame gets a source of about:blank + ["javascript", "javascript:document.open();document.write='<h1>SUCCESS</h1>';document.close();"], + ]; + for(var i=0; i < protocols.length; i++) + { + var generic_frame = document.createElement("iframe"); + generic_frame.src = protocols[i][1]; + generic_frame.name="generic_protocol"; + + generic_frame.onload = function(i) { + data = {"test": protocols[i][0], "msg": "resource with " + protocols[i][0] + " protocol loaded"}; + parent.postMessage(data, "http://mochi.test:8888"); + }.bind(generic_frame, i) + + generic_frame.onerror = function(i) { + data = {"test": protocols[i][0], "msg": "resource with " + protocols[i][0] + " protocol did not load"}; + parent.postMessage(data, "http://mochi.test:8888"); + }.bind(generic_frame, i); + + testContent.appendChild(generic_frame, i); + } + + // Test 3: for resource within a script tag + // Note: the script we load throws an exception, but the script element's + // onload listener is called after we successfully fetch the script, + // independently of whether it throws an exception. + var resource_script=document.createElement("script"); + resource_script.src = "resource://gre/modules/XPCOMUtils.sys.mjs"; + resource_script.name = "resource_protocol"; + resource_script.onload = function() { + parent.postMessage({"test": "resource", "msg": "resource with resource protocol loaded"}, "http://mochi.test:8888"); + } + resource_script.onerror = function() { + parent.postMessage({"test": "resource", "msg": "resource with resource protocol did not load"}, "http://mochi.test:8888"); + } + + testContent.appendChild(resource_script); + + // Test 4: about unsafe protocol within an iframe + var unsafe_about_frame = document.createElement("iframe"); + unsafe_about_frame.src = "about:config"; + unsafe_about_frame.name = "unsafe_about_protocol"; + unsafe_about_frame.onload = function() { + parent.postMessage({"test": "unsafe_about", "msg": "resource with unsafe about protocol loaded"}, "http://mochi.test:8888"); + } + unsafe_about_frame.onerror = function() { + parent.postMessage({"test": "unsafe_about", "msg": "resource with unsafe about protocol did not load"}, "http://mochi.test:8888"); + } + testContent.appendChild(unsafe_about_frame); + + // Test 5: data protocol within a script tag + var x = 2; + var newscript = document.createElement("script"); + newscript.src= "data:text/javascript,var x = 4;"; + newscript.onload = function() { + parent.postMessage({"test": "data_protocol", "msg": "resource with data protocol loaded"}, "http://mochi.test:8888"); + } + newscript.onerror = function() { + parent.postMessage({"test": "data_protocol", "msg": "resource with data protocol did not load"}, "http://mochi.test:8888"); + } + testContent.appendChild(newscript); + + // Test 6: mailto protocol + let mm = SpecialPowers.loadChromeScript(function launchHandler() { + /* eslint-env mozilla/chrome-script */ + var ioService = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + + var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"]. + createInstance(Ci.nsIWebHandlerApp); + webHandler.name = "Web Handler"; + webHandler.uriTemplate = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html?s=%"; + + Services.ppmm.addMessageListener("Test:content-ready", function contentReadyListener() { + Services.ppmm.removeMessageListener("Test:content-ready", contentReadyListener); + sendAsyncMessage("Test:content-ready-forward"); + Services.ppmm.removeDelayedProcessScript(pScript); + }) + + var pScript = "data:,new " + function () { + /* eslint-env mozilla/process-script */ + var os = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); + var observer = { + observe(subject, topic, data) { + if (topic == "content-document-global-created" && data == "http://example.com") { + sendAsyncMessage("Test:content-ready"); + os.removeObserver(observer, "content-document-global-created"); + } + } + }; + os.addObserver(observer, "content-document-global-created"); + } + + Services.ppmm.loadProcessScript(pScript, true); + + var uri = ioService.newURI("mailto:foo@bar.com"); + webHandler.launchWithURI(uri); + }); + + var mailto = false; + + mm.addMessageListener("Test:content-ready-forward", function contentReadyListener() { + mm.removeMessageListener("Test:content-ready-forward", contentReadyListener); + mailto = true; + parent.postMessage({"test": "mailto", "msg": "resource with mailto protocol loaded"}, "http://mochi.test:8888"); + }); + + function mailtoProtocolStatus() { + if(!mailto) { + //There is no onerror event associated with the WebHandler, and hence we need a setTimeout to check the status + setTimeout(mailtoProtocolStatus, TIMEOUT_INTERVAL); + } + } + + mailtoProtocolStatus(); + + // Test 7: wss protocol + // WebSocket tests are not supported on Android Yet. Bug 1566168. + if (AppConstants.platform !== "android") { + var wss; + wss = new WebSocket("wss://example.com/tests/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket"); + + var status_wss = "started"; + wss.onopen = function(e) { + status_wss = "opened"; + wss.close(); + } + wss.onclose = function(e) { + if(status_wss == "opened") { + parent.postMessage({"test": "wss", "msg": "resource with wss protocol loaded"}, "http://mochi.test:8888"); + } else { + parent.postMessage({"test": "wss", "msg": "resource with wss protocol did not load"}, "http://mochi.test:8888"); + } + } + } +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py b/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py new file mode 100644 index 0000000000..b7159c742b --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py @@ -0,0 +1,6 @@ +def web_socket_do_extra_handshake(request): + pass + + +def web_socket_transfer_data(request): + pass diff --git a/dom/security/test/mixedcontentblocker/file_mixed_content_auto_upgrade_display_console.html b/dom/security/test/mixedcontentblocker/file_mixed_content_auto_upgrade_display_console.html new file mode 100644 index 0000000000..b86fbc9cbc --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_mixed_content_auto_upgrade_display_console.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1673574 - Improve Console logging for mixed content auto upgrading</title> +</head> +<body> + <!-- The following file does in fact not exist because we only care if it shows up in the console --> + <img src="http://example.com/file_mixed_content_auto_upgrade_display_console.jpg"> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_redirect.html b/dom/security/test/mixedcontentblocker/file_redirect.html new file mode 100644 index 0000000000..99e1873791 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_redirect.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug1402363: Test mixed content redirects</title> +</head> +<body> + +<script type="text/javascript"> + const PATH = "https://example.com/tests/dom/security/test/mixedcontentblocker/"; + + // check a fetch redirect from https to https (should be allowed) + fetch(PATH + "file_redirect_handler.sjs?https-to-https-redirect", { + method: 'get' + }).then(function(response) { + window.parent.postMessage("https-to-https-loaded", "*"); + }).catch(function(err) { + window.parent.postMessage("https-to-https-blocked", "*"); + }); + + // check a fetch redirect from https to http (should be blocked) + fetch(PATH + "file_redirect_handler.sjs?https-to-http-redirect", { + method: 'get' + }).then(function(response) { + window.parent.postMessage("https-to-http-loaded", "*"); + }).catch(function(err) { + window.parent.postMessage("https-to-http-blocked", "*"); + }); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/file_redirect_handler.sjs b/dom/security/test/mixedcontentblocker/file_redirect_handler.sjs new file mode 100644 index 0000000000..4c134c00e8 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_redirect_handler.sjs @@ -0,0 +1,35 @@ +// custom *.sjs file for +// Bug 1402363: Test Mixed Content Redirect Blocking. + +const URL_PATH = "example.com/tests/dom/security/test/mixedcontentblocker/"; + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache", false); + let queryStr = request.queryString; + + if (queryStr === "https-to-https-redirect") { + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + "https://" + URL_PATH + "file_redirect_handler.sjs?load", + false + ); + return; + } + + if (queryStr === "https-to-http-redirect") { + response.setStatusLine("1.1", 302, "Found"); + response.setHeader( + "Location", + "http://" + URL_PATH + "file_redirect_handler.sjs?load", + false + ); + return; + } + + if (queryStr === "load") { + response.setHeader("Content-Type", "text/html", false); + response.write("foo"); + return; + } +} diff --git a/dom/security/test/mixedcontentblocker/file_server.sjs b/dom/security/test/mixedcontentblocker/file_server.sjs new file mode 100644 index 0000000000..3270a4ce15 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/file_server.sjs @@ -0,0 +1,131 @@ +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +function ERR(response, msg) { + dump("ERROR: " + msg + "\n"); + response.write("HTTP/1.1 400 Bad Request\r\n"); + response.write("Content-Type: text/html; charset=UTF-8\r\n"); + response.write("Content-Length: " + msg.length + "\r\n"); + response.write("\r\n"); + response.write(msg); +} + +function loadContentFromFile(path) { + // Load the content to return in the response from file. + // Since it's relative to the cwd of the test runner, we start there and + // append to get to the actual path of the file. + var testContentFile = Components.classes[ + "@mozilla.org/file/directory_service;1" + ] + .getService(Components.interfaces.nsIProperties) + .get("CurWorkD", Components.interfaces.nsIFile); + var dirs = path.split("/"); + for (var i = 0; i < dirs.length; i++) { + testContentFile.append(dirs[i]); + } + var testContentFileStream = Components.classes[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Components.interfaces.nsIFileInputStream); + testContentFileStream.init(testContentFile, -1, 0, 0); + var testContent = NetUtil.readInputStreamToString( + testContentFileStream, + testContentFileStream.available() + ); + return testContent; +} + +function handleRequest(request, response) { + const { scheme, host, path } = request; + // get the Content-Type to serve from the query string + var contentType = null; + var uniqueID = null; + var showLastRequest = false; + request.queryString.split("&").forEach(function (val) { + var [name, value] = val.split("="); + if (name == "type") { + contentType = unescape(value); + } + if (name == "uniqueID") { + uniqueID = unescape(value); + } + if (name == "lastRequest") { + showLastRequest = true; + } + }); + + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + if (showLastRequest) { + response.setHeader("Content-Type", "text/html", false); + + // We don't want to expose the same lastRequest multiple times. + var state = getState("lastRequest"); + setState("lastRequest", ""); + + if (state == "") { + ERR(response, "No last request!"); + return; + } + + response.write(state); + return; + } + + if (!uniqueID) { + ERR(response, "No uniqueID?!?"); + return; + } + + setState( + "lastRequest", + JSON.stringify({ + scheme, + host, + path, + uniqueID, + contentType: contentType || "other", + }) + ); + + switch (contentType) { + case "img": + response.setHeader("Content-Type", "image/png", false); + response.write( + loadContentFromFile("tests/image/test/mochitest/blue.png") + ); + break; + + case "media": + response.setHeader("Content-Type", "video/ogg", false); + response.write(loadContentFromFile("tests/dom/media/test/320x240.ogv")); + break; + + case "iframe": + response.setHeader("Content-Type", "text/html", false); + response.write("frame content"); + break; + + case "script": + response.setHeader("Content-Type", "application/javascript", false); + break; + + case "stylesheet": + response.setHeader("Content-Type", "text/css", false); + break; + + case "object": + response.setHeader("Content-Type", "application/x-test-match", false); + break; + + case "xhr": + response.setHeader("Content-Type", "text/xml", false); + response.setHeader("Access-Control-Allow-Origin", "https://example.com"); + response.write('<?xml version="1.0" encoding="UTF-8" ?><test></test>'); + break; + + default: + response.setHeader("Content-Type", "text/html", false); + response.write("<html><body>Hello World</body></html>"); + break; + } +} diff --git a/dom/security/test/mixedcontentblocker/mochitest.ini b/dom/security/test/mixedcontentblocker/mochitest.ini new file mode 100644 index 0000000000..d94a775af9 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/mochitest.ini @@ -0,0 +1,40 @@ +[DEFAULT] +tags = mcb +prefs = + security.mixed_content.upgrade_display_content=false + dom.security.https_first=false +support-files = + file_bug803225_test_mailto.html + file_frameNavigation.html + file_frameNavigation_blankTarget.html + file_frameNavigation_grandchild.html + file_frameNavigation_innermost.html + file_frameNavigation_secure.html + file_frameNavigation_secure_grandchild.html + file_main.html + file_main_bug803225.html + file_main_bug803225_websocket_wsh.py + file_server.sjs + !/dom/media/test/320x240.ogv + !/image/test/mochitest/blue.png + file_redirect.html + file_redirect_handler.sjs + file_bug1551886.html + +[test_main.html] +skip-if = + true + toolkit == 'android' + (verify && !debug && os == 'linux') # Android: TIMED_OUT; bug 1402554 + tsan # Times out / Memory consumption, bug 1612707 +[test_bug803225.html] +skip-if = + (os=='linux' && bits==32) || headless || tsan # Linux32:bug 1324870; Headless:bug 1405870; tsan:bug 1612707 + http3 +[test_frameNavigation.html] +skip-if = + true # Bug 1424752 +[test_redirect.html] +[test_bug1551886.html] +skip-if = + http3 diff --git a/dom/security/test/mixedcontentblocker/pass.png b/dom/security/test/mixedcontentblocker/pass.png Binary files differnew file mode 100644 index 0000000000..2fa1e0ac06 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/pass.png diff --git a/dom/security/test/mixedcontentblocker/test.ogv b/dom/security/test/mixedcontentblocker/test.ogv Binary files differnew file mode 100644 index 0000000000..0f83996e5d --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test.ogv diff --git a/dom/security/test/mixedcontentblocker/test.wav b/dom/security/test/mixedcontentblocker/test.wav Binary files differnew file mode 100644 index 0000000000..85dc1ea904 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test.wav diff --git a/dom/security/test/mixedcontentblocker/test_bug1551886.html b/dom/security/test/mixedcontentblocker/test_bug1551886.html new file mode 100644 index 0000000000..bf128256a4 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test_bug1551886.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Bug 1551886: Opaque documents aren't considered in the mixed content blocker</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"> + +SimpleTest.waitForExplicitFinish(); + +let f = document.createElement("iframe"); +f.src = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_bug1551886.html"; + +window.addEventListener("message", (event) => { + switch(event.data.type) { + case 'http': + is(event.data.status, "blocked", "nested load of http://example should get blocked by the MCB"); + break + case 'https': + is(event.data.status, "loaded", "nested load of https://example should not get blocked by the MCB"); + SimpleTest.finish(); + break; + } +}); + +document.body.appendChild(f); + +</script> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/test_bug803225.html b/dom/security/test/mixedcontentblocker/test_bug803225.html new file mode 100644 index 0000000000..56f5e02e05 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test_bug803225.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML> +<html> +<!-- +Testing Allowlist of Resource Scheme for Mixed Content Blocker +https://bugzilla.mozilla.org/show_bug.cgi?id=803225 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Bug 803225</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script> + const { AppConstants } = SpecialPowers.ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" + ); + + var counter = 0; + var settings = [ [true, true], [true, false], [false, true], [false, false] ]; + + var blockActive; + var blockDisplay; + + //Cycle through 4 different preference settings. + function changePrefs(callback) { + let newPrefs = [ + ["security.all_resource_uri_content_accessible", true], // Temporarily allow content to access all resource:// URIs. + ["security.mixed_content.block_display_content", settings[counter][0]], + ["security.mixed_content.block_active_content", settings[counter][1]] + ]; + + SpecialPowers.pushPrefEnv({"set": newPrefs}, function () { + blockDisplay = SpecialPowers.getBoolPref("security.mixed_content.block_display_content"); + blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content"); + counter++; + callback(); + }); + } + + var testsToRun = { + /* https - Tests already run as part of bug 62178. */ + about: false, + resource: false, + unsafe_about: false, + data_protocol: false, + javascript: false, + }; + + if (AppConstants.platform !== "android") { + // WebSocket tests are not supported on Android Yet. Bug 1566168. + testsToRun.wss = false; + testsToRun.mailto = false; + } + + function log(msg) { + document.getElementById("log").textContent += "\n" + msg; + } + + function reloadFrame() { + document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_main_bug803225.html"></iframe>'; + } + + function checkTestsCompleted() { + for (var prop in testsToRun) { + // some test hasn't run yet so we're not done + if (!testsToRun[prop]) + return; + } + //if the testsToRun are all completed, change the pref and run the tests again until we have cycled through all the prefs. + if(counter < 4) { + for (var prop in testsToRun) { + testsToRun[prop] = false; + } + //call to change the preferences + changePrefs(function() { + log("\nblockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+"."); + reloadFrame(); + }); + } + else { + SimpleTest.finish(); + } + } + + var firstTest = true; + + function receiveMessage(event) { + if(firstTest) { + log("blockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+"."); + firstTest = false; + } + + log("test: "+event.data.test+", msg: "+event.data.msg + " logging message."); + // test that the load type matches the pref for this type of content + // (i.e. active vs. display) + + switch(event.data.test) { + + /* Mixed Script tests */ + case "about": + ok(event.data.msg == "resource with about protocol loaded", "resource with about protocol did not load"); + testsToRun.about = true; + break; + + case "resource": + ok(event.data.msg == "resource with resource protocol loaded", "resource with resource protocol did not load"); + testsToRun.resource = true; + break; + + case "unsafe_about": + // This one should not load + ok(event.data.msg == "resource with unsafe about protocol did not load", "resource with unsafe about protocol loaded"); + testsToRun.unsafe_about = true; + break; + + case "data_protocol": + ok(event.data.msg == "resource with data protocol loaded", "resource with data protocol did not load"); + testsToRun.data_protocol = true; + break; + + case "javascript": + ok(event.data.msg == "resource with javascript protocol loaded", "resource with javascript protocol did not load"); + testsToRun.javascript = true; + break; + + case "wss": + ok(event.data.msg == "resource with wss protocol loaded", "resource with wss protocol did not load"); + testsToRun.wss = true; + break; + + case "mailto": + ok(event.data.msg == "resource with mailto protocol loaded", "resource with mailto protocol did not load"); + testsToRun.mailto = true; + break; + } + checkTestsCompleted(); + } + + function startTest() { + //Set the first set of settings (true, true) and increment the counter. + changePrefs(function() { + // listen for a messages from the mixed content test harness + window.addEventListener("message", receiveMessage); + + reloadFrame(); + }); + } + + SimpleTest.waitForExplicitFinish(); + </script> +</head> + +<body onload='startTest()'> + <div id="framediv"></div> + <pre id="log"></pre> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/test_frameNavigation.html b/dom/security/test/mixedcontentblocker/test_frameNavigation.html new file mode 100644 index 0000000000..82e3e715d2 --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test_frameNavigation.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker +https://bugzilla.mozilla.org/show_bug.cgi?id=840388 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Bug 840388</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script> + var counter = 0; + var origBlockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content"); + + SpecialPowers.setBoolPref("security.mixed_content.block_active_content", true); + var blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content"); + + + var testsToRunInsecure = { + insecurePage_navigate_child: false, + insecurePage_navigate_grandchild: false, + }; + + var testsToRunSecure = { + securePage_navigate_child: false, + blankTarget: false, + }; + + function log(msg) { + document.getElementById("log").textContent += "\n" + msg; + } + + var secureTestsStarted = false; + async function checkTestsCompleted() { + for (var prop in testsToRunInsecure) { + // some test hasn't run yet so we're not done + if (!testsToRunInsecure[prop]) + return; + } + // If we are here, all the insecure tests have run. + // If we haven't changed the iframe to run the secure tests, change it now. + if (!secureTestsStarted) { + document.getElementById('testing_frame').src = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html"; + secureTestsStarted = true; + } + for (var prop in testsToRunSecure) { + // some test hasn't run yet so we're not done + if (!testsToRunSecure[prop]) + return; + } + //if the secure and insecure testsToRun are all completed, change the block mixed active content pref and run the tests again. + if(counter < 1) { + for (var prop in testsToRunSecure) { + testsToRunSecure[prop] = false; + } + for (var prop in testsToRunInsecure) { + testsToRunInsecure[prop] = false; + } + //call to change the preferences + counter++; + await SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false); + blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content"); + log("blockActive set to "+blockActive+"."); + secureTestsStarted = false; + document.getElementById('framediv').innerHTML = '<iframe src="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation.html" id="testing_frame"></iframe>'; + } + else { + //set the prefs back to what they were set to originally + SpecialPowers.setBoolPref("security.mixed_content.block_active_content", origBlockActive); + SimpleTest.finish(); + } + } + + var firstTestDebugMessage = true; + + // listen for a messages from the mixed content test harness + window.addEventListener("message", receiveMessage); + function receiveMessage(event) { + if(firstTestDebugMessage) { + log("blockActive set to "+blockActive); + firstTestDebugMessage = false; + } + + log("test: "+event.data.test+", msg: "+event.data.msg + "."); + // test that the load type matches the pref for this type of content + // (i.e. active vs. display) + + switch(event.data.test) { + + case "insecurePage_navigate_child": + is(event.data.msg, "navigated to insecure iframe on insecure page", "navigating to insecure iframe blocked on insecure page"); + testsToRunInsecure.insecurePage_navigate_child = true; + break; + + case "insecurePage_navigate_grandchild": + is(event.data.msg, "navigated to insecure grandchild iframe on insecure page", "navigating to insecure grandchild iframe blocked on insecure page"); + testsToRunInsecure.insecurePage_navigate_grandchild = true; + break; + + case "securePage_navigate_child": + ok(blockActive == (event.data.msg == "navigating to insecure iframe blocked on secure page"), "navigated to insecure iframe on secure page"); + testsToRunSecure.securePage_navigate_child = true; + break; + + case "blankTarget": + is(event.data.msg, "opened an http link with target=_blank from a secure page", "couldn't open an http link in a new window from a secure page"); + testsToRunSecure.blankTarget = true; + break; + + } + checkTestsCompleted(); + } + + SimpleTest.waitForExplicitFinish(); + </script> +</head> + +<body> + <div id="framediv"> + <iframe src="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation.html" id="testing_frame"></iframe> + </div> + + <pre id="log"></pre> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/test_main.html b/dom/security/test/mixedcontentblocker/test_main.html new file mode 100644 index 0000000000..9a13a0853f --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test_main.html @@ -0,0 +1,231 @@ +<!DOCTYPE HTML> +<html> +<!-- +Tests for Mixed Content Blocker +https://bugzilla.mozilla.org/show_bug.cgi?id=62178 +--> +<head> + <meta charset="utf-8"> + <title>Tests for Bug 62178</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script> + let counter = 0; + // blockDisplay blockActive upgradeDisplay + const settings = [ + [true, true, true], + [true, false, true], + [false, true, true], + [false, false, true], + [true, true, false], + [true, false, false], + [false, true, false], + [false, false, false], + ]; + + let blockActive; + let blockDisplay; + let upgradeDisplay; + + //Cycle through 8 different preference settings. + function changePrefs(otherPrefs, callback) { + let basePrefs = [["security.mixed_content.block_display_content", settings[counter][0]], + ["security.mixed_content.block_active_content", settings[counter][1]], + ["security.mixed_content.upgrade_display_content", settings[counter][2]]]; + let newPrefs = basePrefs.concat(otherPrefs); + + SpecialPowers.pushPrefEnv({"set": newPrefs}, function () { + blockDisplay = SpecialPowers.getBoolPref("security.mixed_content.block_display_content"); + blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content"); + upgradeDisplay = SpecialPowers.getBoolPref("security.mixed_content.upgrade_display_content"); + counter++; + callback(); + }); + } + + let testsToRun = { + iframe: false, + image: false, + imageSrcset: false, + imageSrcsetFallback: false, + imagePicture: false, + imageJoinPicture: false, + imageLeavePicture: false, + script: false, + stylesheet: false, + object: false, + media: false, + xhr: false, + }; + + function log(msg) { + document.getElementById("log").textContent += "\n" + msg; + } + + function reloadFrame() { + document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_main.html"></iframe>'; + } + + function checkTestsCompleted() { + for (var prop in testsToRun) { + // some test hasn't run yet so we're not done + if (!testsToRun[prop]) + return; + } + //if the testsToRun are all completed, chnage the pref and run the tests again until we have cycled through all the prefs. + if(counter < 8) { + for (var prop in testsToRun) { + testsToRun[prop] = false; + } + //call to change the preferences + changePrefs([], function() { + log(`\nblockDisplay set to ${blockDisplay}, blockActive set to ${blockActive}, upgradeDisplay set to ${upgradeDisplay}`); + reloadFrame(); + }); + } + else { + SimpleTest.finish(); + } + } + + var firstTest = true; + + function receiveMessage(event) { + if(firstTest) { + log(`blockActive set to ${blockActive}, blockDisplay set to ${blockDisplay}, upgradeDisplay set to ${upgradeDisplay}.`); + firstTest = false; + } + + // Simple check from the iframe. + if (event.data.check) { + ok(event.data.status, event.data.msg); + return; + } + + log("test: "+event.data.test+", msg: "+event.data.msg + " logging message."); + // test that the load type matches the pref for this type of content + // (i.e. active vs. display) + + switch(event.data.test) { + + /* Mixed Script tests */ + case "iframe": + ok(blockActive == (event.data.msg == "insecure iframe blocked"), "iframe did not follow block_active_content pref"); + testsToRun.iframe = true; + break; + + case "object": + ok(blockActive == (event.data.msg == "insecure object blocked"), "object did not follow block_active_content pref"); + testsToRun.object = true; + break; + + case "script": + ok(blockActive == (event.data.msg == "insecure script blocked"), "script did not follow block_active_content pref"); + testsToRun.script = true; + break; + + case "stylesheet": + ok(blockActive == (event.data.msg == "insecure stylesheet blocked"), "stylesheet did not follow block_active_content pref"); + testsToRun.stylesheet = true; + break; + + case "xhr": + ok(blockActive == (event.data.msg == "insecure xhr blocked"), "xhr did not follow block_active_content pref"); + testsToRun.xhr = true; + break; + + /* Mixed Display tests */ + case "image": + //test that the image load matches the pref for display content + if (upgradeDisplay) { + ok(event.data.msg == "secure image loaded after upgrade", "image did not follow upgrade_display_content pref"); + } else { + ok(blockDisplay == (event.data.msg == "insecure image blocked"), "image did not follow block_display_content pref"); + } + testsToRun.image = true; + break; + + case "media": + if (upgradeDisplay) { + ok(event.data.msg == "secure media loaded after upgrade", "media did not follow upgrade_display_content pref"); + } else { + ok(blockDisplay == (event.data.msg == "insecure media blocked"), "media did not follow block_display_content pref"); + } + testsToRun.media = true; + break; + + /* Images using the "imageset" policy, from <img srcset> and <picture>, do not get the mixed display exception */ + case "imageSrcset": + // When blockDisplay && blockActive && upgradeDisplay are all true the request is blocked + // This appears to be a side effect of blockDisplay taking precedence here. + if (event.data.msg == "secure image loaded after upgrade") { + ok(upgradeDisplay, "imageSrcset did not follow upgrade_display_content pref"); + } else { + ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcset did not follow block_active_content pref"); + } + testsToRun.imageSrcset = true; + break; + + case "imageSrcsetFallback": + if (event.data.msg == "secure image loaded after upgrade") { + ok(upgradeDisplay, "imageSrcsetFallback did not follow upgrade_display_content pref"); + } else { + ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcsetFallback did not follow block_active_content pref"); + } + testsToRun.imageSrcsetFallback = true; + break; + + case "imagePicture": + if (event.data.msg == "secure image loaded after upgrade") { + ok(upgradeDisplay, "imagePicture did not follow upgrade_display_content pref"); + } else { + ok(blockActive == (event.data.msg == "insecure image blocked"), "imagePicture did not follow block_active_content pref"); + } + testsToRun.imagePicture = true; + break; + + case "imageJoinPicture": + if (event.data.msg == "secure image loaded after upgrade") { + ok(upgradeDisplay, "imageJoinPicture did not follow upgrade_display_content pref"); + } else { + ok(blockActive == (event.data.msg == "insecure image blocked"), "imageJoinPicture did not follow block_active_content pref"); + } + testsToRun.imageJoinPicture = true; + break; + + // Should return to mixed display mode + case "imageLeavePicture": + if (event.data.msg == "secure image loaded after upgrade") { + ok(upgradeDisplay, "imageLeavePicture did not follow upgrade_display_content pref"); + } else { + ok(blockDisplay == (event.data.msg == "insecure image blocked"), "imageLeavePicture did not follow block_display_content pref"); + } + testsToRun.imageLeavePicture = true; + break; + + } + checkTestsCompleted(); + } + + function startTest() { + //Set the first set of mixed content settings and increment the counter. + changePrefs([], function() { + //listen for a messages from the mixed content test harness + window.addEventListener("message", receiveMessage); + + //Kick off test + reloadFrame(); + }); + } + + SimpleTest.waitForExplicitFinish(); + + </script> +</head> + +<body onload='startTest()'> + <div id="framediv"></div> + <pre id="log"></pre> +</body> +</html> diff --git a/dom/security/test/mixedcontentblocker/test_redirect.html b/dom/security/test/mixedcontentblocker/test_redirect.html new file mode 100644 index 0000000000..3fdd4e2e7b --- /dev/null +++ b/dom/security/test/mixedcontentblocker/test_redirect.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug1402363: Test mixed content redirects</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body onload='startTest()'> +<iframe style="width:100%;height:300px;" id="testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const PATH = "https://example.com/tests/dom/security/test/mixedcontentblocker/"; +let testcounter = 0; + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + if (event.data === "https-to-https-loaded") { + ok(true, "https to https fetch redirect should be allowed"); + } + else if (event.data === "https-to-http-blocked") { + ok(true, "https to http fetch redirect should be blocked"); + } + else { + ok(false, "sanity: we should never enter that branch (" + event.data + ")"); + } + testcounter++; + if (testcounter < 2) { + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +function startTest() { + let testframe = document.getElementById("testframe"); + testframe.src = PATH + "file_redirect.html"; +} + +</script> +</body> +</html> |