commit 15d2393fd4d89b070c3ddf402e7a5fdba3461636
parent ce12fe1250a15a290f59b643c52e99878645abb2
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date: Thu, 16 Oct 2025 11:05:03 +0000
Bug 1433890 - Test that webextension dir listing does not contain file or jar URLs r=robwu
Differential Revision: https://phabricator.services.mozilla.com/D268357
Diffstat:
3 files changed, 138 insertions(+), 0 deletions(-)
diff --git a/toolkit/components/extensions/moz.build b/toolkit/components/extensions/moz.build
@@ -132,6 +132,7 @@ JAR_MANIFESTS += ["jar.mn"]
BROWSER_CHROME_MANIFESTS += [
"test/browser/browser.toml",
+ "test/browser/protocol-remote-false/browser.toml",
]
MOCHITEST_MANIFESTS += [
diff --git a/toolkit/components/extensions/test/browser/protocol-remote-false/browser.toml b/toolkit/components/extensions/test/browser/protocol-remote-false/browser.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+prefs = [
+ "extensions.webextensions.remote=false", # Needed to display index of unpacked ext
+ "extensions.webextensions.protocol.remote=false"
+]
+
+["browser_ext_dir_listing.js"]
diff --git a/toolkit/components/extensions/test/browser/protocol-remote-false/browser_ext_dir_listing.js b/toolkit/components/extensions/test/browser/protocol-remote-false/browser_ext_dir_listing.js
@@ -0,0 +1,130 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix
+const { ExtensionTestCommon } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionTestCommon.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+async function checkNoFileOrJarURLs(extensionURL) {
+ // Load the root of the extension using moz-extension:// protocol
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: extensionURL,
+ },
+ async browser => {
+ info("Successfully loaded extension root page");
+
+ // Check the page title
+ let title = await SpecialPowers.spawn(browser, [], () => {
+ return content.document.title;
+ });
+ is(
+ title,
+ `Index of ${extensionURL}`,
+ `Page title should start with "Index of moz-extension://", got: ${title}`
+ );
+
+ // Check that at least the manifest entry is there
+ let hasManifest = await SpecialPowers.spawn(browser, [], () => {
+ for (let elem of content.document.querySelectorAll("[href]")) {
+ let url = elem.href;
+ if (url.endsWith("manifest.json")) {
+ return true;
+ }
+ }
+ return false;
+ });
+ ok(hasManifest, "Directory listing should include manifest.json");
+
+ // Check that no URLs are file:// or jar://
+ let invalidURLs = await SpecialPowers.spawn(browser, [], () => {
+ let invalid = [];
+ for (let elem of content.document.querySelectorAll("[href],[src]")) {
+ let url = elem.href || elem.src;
+ if (url.startsWith("file:") || url.startsWith("jar:")) {
+ invalid.push(elem.outerHTML);
+ }
+ }
+ return invalid;
+ });
+
+ Assert.deepEqual(
+ invalidURLs,
+ [],
+ "No URLs should use file:// or jar:// schemes."
+ );
+ }
+ );
+}
+
+// Test that all the URLs in the dirlisting of an extensions root dir
+// does not include any file or jar URLs when extensions.webextensions.protocol.remote
+// is false.
+add_task(async function test_webextension_dir_listing() {
+ // Verify the pref is set correctly
+ is(
+ Services.prefs.getBoolPref("extensions.webextensions.protocol.remote"),
+ false,
+ "extensions.webextensions.protocol.remote should be false"
+ );
+
+ // Get the extension's base URL. This is a packed extension
+ // that happens to already be a part of the test environment.
+ let policy = WebExtensionPolicy.getByID("mochikit@mozilla.org");
+ ok(policy, "This extension must exist");
+
+ let extensionURL = policy.getURL("/");
+ info(`Extension base URL: ${extensionURL}`);
+ await checkNoFileOrJarURLs(extensionURL);
+});
+
+add_task(async function test_temp_webextension_dir_listing() {
+ // Verify the pref is set correctly
+ is(
+ Services.prefs.getBoolPref("extensions.webextensions.protocol.remote"),
+ false,
+ "extensions.webextensions.protocol.remote should be false"
+ );
+
+ const ID = "addon@tests.mozilla.org";
+
+ const addonDir = AddonTestUtils.tempDir.clone().clone();
+ addonDir.append("exttest");
+ await AddonTestUtils.promiseWriteFilesToDir(
+ addonDir.path,
+ ExtensionTestCommon.generateFiles({
+ manifest: {
+ browser_specific_settings: { gecko: { id: ID } },
+ version: "1.0",
+ },
+ })
+ );
+
+ // Wait for the extension to start up
+ let startupPromise = AddonTestUtils.promiseWebExtensionStartup(ID);
+ let addon = await AddonManager.installTemporaryAddon(addonDir);
+ await startupPromise;
+
+ // Get the extension policy
+ let policy = WebExtensionPolicy.getByID(ID);
+ ok(policy, "Extension policy found");
+
+ // Get the extension's base URL and check it
+ let extensionURL = policy.getURL("/");
+ info(`Temporary extension base URL: ${extensionURL}`);
+ await checkNoFileOrJarURLs(extensionURL);
+
+ // Unload the extension
+ await addon.uninstall();
+
+ // Clean up the temporary directory
+ addonDir.remove(true);
+});