From 58a7f7a38f083fbc271c30330a0e41a895b55d1b Mon Sep 17 00:00:00 2001 From: Csoregi Natalia Date: Fri, 25 Nov 2022 01:13:40 +0200 Subject: [PATCH] Backed out 2 changesets (bug 1745761) for failures on test_ext_dnr_without_webrequest.js. CLOSED TREE Backed out changeset 74b33f61c4d3 (bug 1745761) Backed out changeset 5abe72701c13 (bug 1745761) --- .../extensions/ExtensionDNR.sys.mjs | 229 +----- .../test/mochitest/mochitest-common.ini | 1 - .../mochitest/test_ext_dnr_upgradeScheme.html | 120 --- .../xpcshell/test_ext_dnr_allowAllRequests.js | 96 --- .../xpcshell/test_ext_dnr_private_browsing.js | 130 ---- .../test_ext_dnr_system_restrictions.js | 66 -- .../test/xpcshell/test_ext_dnr_webrequest.js | 205 ----- .../test_ext_dnr_without_webrequest.js | 720 ------------------ .../test/xpcshell/xpcshell-common.ini | 5 - .../extensions/webrequest/WebRequest.jsm | 29 +- 10 files changed, 9 insertions(+), 1592 deletions(-) delete mode 100644 toolkit/components/extensions/test/mochitest/test_ext_dnr_upgradeScheme.html delete mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js delete mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dnr_private_browsing.js delete mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js delete mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js delete mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_dnr_without_webrequest.js diff --git a/toolkit/components/extensions/ExtensionDNR.sys.mjs b/toolkit/components/extensions/ExtensionDNR.sys.mjs index 6deb3b763009..cd63d7839682 100644 --- a/toolkit/components/extensions/ExtensionDNR.sys.mjs +++ b/toolkit/components/extensions/ExtensionDNR.sys.mjs @@ -69,13 +69,6 @@ const gRuleManagers = []; * - allow / allowAllRequests */ -const lazy = {}; -ChromeUtils.defineModuleGetter( - lazy, - "WebRequest", - "resource://gre/modules/WebRequest.jsm" -); - // The RuleCondition class represents a rule's "condition" type as described in // schemas/declarative_net_request.json. This class exists to allow the JS // engine to use one Shape for all Rule instances. @@ -314,8 +307,8 @@ class RuleValidator { // http(s) URLs can (regardless of extension permissions). // data:-URLs are currently blocked due to bug 1622986. - // TODO bug 1801870: Implement rule.action.redirect.transform. - // TODO bug 1745760: With regexFilter support, implement regexSubstitution. + // TODO bug 1745761: With the redirect action, add schema definitions + + // implement rule.action.redirect.transform / regexSubstitution. return true; } @@ -446,17 +439,6 @@ class RequestDetails { : null; } - static fromChannelWrapper(channel) { - return new RequestDetails({ - requestURI: channel.finalURI, - // Note: originURI may be null, if missing or null principal, as desired. - initiatorURI: channel.originURI, - type: channel.type, - method: channel.method.toLowerCase(), - tabId: null, // TODO: use getBrowserData to populate. - }); - } - canExtensionModify(extension) { const policy = extension.policy; return ( @@ -572,6 +554,9 @@ class RequestEvaluator { return; } + // TODO bug 1745761: when the channel/originAttributes is chosen, use + // ruleManager.extension to exclude private requests if needed. + this.#collectMatchInRuleset(this.ruleManager.sessionRules); this.#collectMatchInRuleset(this.ruleManager.dynamicRules); for (let ruleset of this.ruleManager.enabledStaticRules) { @@ -661,15 +646,7 @@ class RequestEvaluator { // Check this.req.requestURI: if (cond.urlFilter) { - if ( - !this.#matchesUrlFilter( - this.req.requestURI, - cond.urlFilter, - cond.isUrlFilterCaseSensitive - ) - ) { - return false; - } + // TODO bug 1745759: Check cond.urlFilter + isUrlFilterCaseSensitive } else if (cond.regexFilter) { // TODO bug 1745760: check cond.regexFilter + isUrlFilterCaseSensitive } @@ -726,22 +703,6 @@ class RequestEvaluator { return true; } - /** - * @param {nsIURI} uri - The request URI. - * @param {string} urlFilter - * @param {boolean} [isUrlFilterCaseSensitive] - * @returns {boolean} Whether urlFilter matches the given uri. - */ - #matchesUrlFilter(uri, urlFilter, isUrlFilterCaseSensitive) { - // TODO bug 1745759: Check cond.urlFilter + isUrlFilterCaseSensitive - // Placeholder for unit test until we have a complete implementation. - if (urlFilter === "|https:*") { - return uri.schemeIs("https"); - } - throw new Error(`urlFilter not implemented yet: ${urlFilter}`); - // return true; after all other checks passed. - } - /** * @param {string[]} domains - A list of canonicalized domain patterns. * Canonical means punycode, no ports, and IPv6 without brackets, and not @@ -785,130 +746,6 @@ class RequestEvaluator { } } -const NetworkIntegration = { - register() { - // We register via WebRequest.jsm to ensure predictable ordering of DNR and - // WebRequest behavior. - lazy.WebRequest.setDNRHandlingEnabled(true); - }, - unregister() { - lazy.WebRequest.setDNRHandlingEnabled(false); - }, - - startDNREvaluation(channel) { - let ruleManagers = gRuleManagers; - if (!channel.canModify) { - ruleManagers = []; - } - if (channel.loadInfo.originAttributes.privateBrowsingId > 0) { - ruleManagers = ruleManagers.filter( - rm => rm.extension.privateBrowsingAllowed - ); - } - let matchedRules; - if (ruleManagers.length) { - const request = RequestDetails.fromChannelWrapper(channel); - matchedRules = RequestEvaluator.evaluateRequest(request, ruleManagers); - } - // Cache for later. In case of redirects, _dnrMatchedRules may exist for - // the pre-redirect HTTP channel, and is overwritten here again. - channel._dnrMatchedRules = matchedRules; - }, - - /** - * Applies the actions of the DNR rules. - * - * @param {ChannelWrapper} channel - * @returns {boolean} Whether to ignore any responses from the webRequest API. - */ - onBeforeRequest(channel) { - let matchedRules = channel._dnrMatchedRules; - if (!matchedRules?.length) { - return false; - } - // If a matched rule closes the channel, it is the sole match. - const finalMatch = matchedRules[0]; - switch (finalMatch.rule.action.type) { - case "block": - this.applyBlock(channel, finalMatch); - return true; - case "redirect": - this.applyRedirect(channel, finalMatch); - return true; - case "upgradeScheme": - this.applyUpgradeScheme(channel, finalMatch); - return true; - } - // If there are multiple rules, then it may be a combination of allow, - // allowAllRequests and/or modifyHeaders. - - // TODO bug 1797403: Apply allowAllRequests actions. - - return false; - }, - - onBeforeSendHeaders(channel) { - // TODO bug 1797404: apply modifyHeaders actions (requestHeaders). - }, - - onHeadersReceived(channel) { - // TODO bug 1797404: apply modifyHeaders actions (responseHeaders). - }, - - applyBlock(channel, matchedRule) { - // TODO bug 1802259: Consider a DNR-specific reason. - channel.cancel( - Cr.NS_ERROR_ABORT, - Ci.nsILoadInfo.BLOCKING_REASON_EXTENSION_WEBREQUEST - ); - const addonId = matchedRule.ruleManager.extension.id; - let properties = channel.channel.QueryInterface(Ci.nsIWritablePropertyBag); - properties.setProperty("cancelledByExtension", addonId); - }, - - applyUpgradeScheme(channel, matchedRule) { - // Request upgrade. No-op if already secure (i.e. https). - channel.upgradeToSecure(); - }, - - applyRedirect(channel, matchedRule) { - // Ambiguity resolution order of redirect dict keys, consistent with Chrome: - // - url > extensionPath > transform > regexSubstitution - const redirect = matchedRule.rule.action.redirect; - const extension = matchedRule.ruleManager.extension; - let redirectUri; - if (redirect.url) { - // redirect.url already validated by checkActionRedirect. - redirectUri = Services.io.newURI(redirect.url); - } else if (redirect.extensionPath) { - redirectUri = extension.baseURI - .mutate() - .setPathQueryRef(redirect.extensionPath) - .finalize(); - } else if (redirect.transform) { - // TODO bug 1801870: Implement transform. - throw new Error("transform not implemented"); - } else if (redirect.regexSubstitution) { - // TODO bug 1745760: Implement along with regexFilter support. - throw new Error("regexSubstitution not implemented"); - } else { - // #checkActionRedirect ensures that the redirect action is non-empty. - } - - channel.redirectTo(redirectUri); - - let properties = channel.channel.QueryInterface(Ci.nsIWritablePropertyBag); - properties.setProperty("redirectedByExtension", extension.id); - - let origin = channel.getRequestHeader("Origin"); - if (origin) { - channel.setResponseHeader("Access-Control-Allow-Origin", origin); - channel.setResponseHeader("Access-Control-Allow-Credentials", "true"); - channel.setResponseHeader("Access-Control-Max-Age", "0"); - } - }, -}; - class RuleManager { constructor(extension) { this.extension = extension; @@ -944,10 +781,6 @@ function getRuleManager(extension, createIfMissing = true) { // instantiate a RuleManager claims the highest priority. // TODO bug 1786059: order extensions by "installation time". gRuleManagers.unshift(ruleManager); - if (gRuleManagers.length === 1) { - // The first DNR registration. - NetworkIntegration.register(); - } } return ruleManager; } @@ -956,10 +789,6 @@ function clearRuleManager(extension) { let i = gRuleManagers.findIndex(rm => rm.extension === extension); if (i !== -1) { gRuleManagers.splice(i, 1); - if (gRuleManagers.length === 0) { - // The last DNR registration. - NetworkIntegration.unregister(); - } } } @@ -980,55 +809,9 @@ function getMatchedRulesForRequest(request, extension) { return RequestEvaluator.evaluateRequest(requestDetails, ruleManagers); } -/** - * Runs before any webRequest event is notified. Headers may be modified, but - * the request should not be canceled (see handleRequest instead). - * - * @param {ChannelWrapper} channel - * @param {string} kind - The name of the webRequest event. - */ -function beforeWebRequestEvent(channel, kind) { - try { - switch (kind) { - case "onBeforeRequest": - NetworkIntegration.startDNREvaluation(channel); - break; - case "onBeforeSendHeaders": - NetworkIntegration.onBeforeSendHeaders(channel); - break; - case "onHeadersReceived": - NetworkIntegration.onHeadersReceived(channel); - break; - } - } catch (e) { - Cu.reportError(e); - } -} - -/** - * Applies matching DNR rules, some of which may potentially cancel the request. - * - * @param {ChannelWrapper} channel - * @param {string} kind - The name of the webRequest event. - * @returns {boolean} Whether to ignore any responses from the webRequest API. - */ -function handleRequest(channel, kind) { - try { - if (kind === "onBeforeRequest") { - return NetworkIntegration.onBeforeRequest(channel); - } - } catch (e) { - Cu.reportError(e); - } - return false; -} - export const ExtensionDNR = { RuleValidator, getRuleManager, clearRuleManager, getMatchedRulesForRequest, - - beforeWebRequestEvent, - handleRequest, }; diff --git a/toolkit/components/extensions/test/mochitest/mochitest-common.ini b/toolkit/components/extensions/test/mochitest/mochitest-common.ini index 99471f216287..77a8631bcb0c 100644 --- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini @@ -131,7 +131,6 @@ skip-if = os == 'android' || tsan # Times out on TSan intermittently, bug 161518 skip-if = os == 'android' # Bug 1513544 Android does not support multiple windows. [test_ext_cookies_permissions_bad.html] [test_ext_cookies_permissions_good.html] -[test_ext_dnr_upgradeScheme.html] [test_ext_downloads_download.html] [test_ext_embeddedimg_iframe_frameAncestors.html] [test_ext_exclude_include_globs.html] diff --git a/toolkit/components/extensions/test/mochitest/test_ext_dnr_upgradeScheme.html b/toolkit/components/extensions/test/mochitest/test_ext_dnr_upgradeScheme.html deleted file mode 100644 index 4c53c7c86e22..000000000000 --- a/toolkit/components/extensions/test/mochitest/test_ext_dnr_upgradeScheme.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - DNR with upgradeScheme action - - - - - - - - - diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js deleted file mode 100644 index b98807b7dde3..000000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_allowAllRequests.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; - -add_setup(() => { - Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); - Services.prefs.setBoolPref("extensions.dnr.enabled", true); -}); - -const server = createHttpServer({ - hosts: ["example.com", "example.net", "example.org"], -}); -server.registerPathHandler("/never_reached", (req, res) => { - Assert.ok(false, "Server should never have been reached"); -}); -server.registerPathHandler("/allowed", (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); - res.write("allowed"); -}); -server.registerPathHandler("/", (req, res) => { - res.write("Dummy page"); -}); - -add_task(async function allowAllRequests_allows_request() { - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - // allowAllRequests should take precedence over block. - { - id: 1, - condition: { resourceTypes: ["main_frame", "xmlhttprequest"] }, - action: { type: "block" }, - }, - { - id: 2, - condition: { resourceTypes: ["main_frame"] }, - action: { type: "allowAllRequests" }, - }, - { - id: 3, - priority: 2, - // Note: when not specified, main_frame is excluded by default. So - // when a main_frame request is triggered, only rules 1 and 2 match. - condition: { requestDomains: ["example.com"] }, - action: { type: "block" }, - }, - ], - }); - browser.test.sendMessage("dnr_registered"); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - manifest_version: 3, - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitMessage("dnr_registered"); - - let contentPage = await ExtensionTestUtils.loadContentPage( - "http://example.com/" - ); - Assert.equal( - await contentPage.spawn(null, () => content.document.URL), - "http://example.com/", - "main_frame request should have been allowed by allowAllRequests" - ); - - async function checkCanFetch(url) { - return contentPage.spawn(url, async url => { - try { - await (await content.fetch(url)).text(); - return true; - } catch (e) { - return false; // NetworkError: blocked - } - }); - } - - Assert.equal( - await checkCanFetch("http://example.com/never_reached"), - false, - "should be blocked by DNR rule 3" - ); - Assert.equal( - await checkCanFetch("http://example.net/"), - // TODO bug 1797403: Fix expectation once allowAllRequests is implemented: - // true, - // "should not be blocked by block rule due to allowAllRequests rule" - false, - "is blocked because persistency of allowAllRequests is not yet implemented" - ); - - await contentPage.close(); - await extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_private_browsing.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_private_browsing.js deleted file mode 100644 index d94c31c858c8..000000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_private_browsing.js +++ /dev/null @@ -1,130 +0,0 @@ -"use strict"; - -const server = createHttpServer({ hosts: ["example.com"] }); -server.registerPathHandler("/", (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); -}); - -add_setup(() => { - Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); - Services.prefs.setBoolPref("extensions.dnr.enabled", true); -}); - -async function startDNRExtension({ privateBrowsingAllowed }) { - let extension = ExtensionTestUtils.loadExtension({ - incognitoOverride: privateBrowsingAllowed ? "spanning" : undefined, - async background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [{ id: 1, condition: {}, action: { type: "block" } }], - }); - browser.test.sendMessage("dnr_registered"); - }, - manifest: { - manifest_version: 3, - permissions: ["declarativeNetRequest"], - browser_specific_settings: { gecko: { id: "@dnr-ext" } }, - }, - }); - await extension.startup(); - await extension.awaitMessage("dnr_registered"); - return extension; -} - -async function testMatchedByDNR(privateBrowsing) { - let contentPage = await ExtensionTestUtils.loadContentPage( - "http://example.com/?page", - { privateBrowsing } - ); - let wasRequestBlocked = await contentPage.spawn(null, async () => { - try { - await content.fetch("http://example.com/?fetch"); - return false; - } catch (e) { - // Request blocked by DNR rule from startDNRExtension(). - return true; - } - }); - await contentPage.close(); - return wasRequestBlocked; -} - -add_task(async function private_browsing_not_allowed_by_default() { - let extension = await startDNRExtension({ privateBrowsingAllowed: false }); - Assert.equal( - await testMatchedByDNR(false), - true, - "DNR applies to non-private browsing requests by default" - ); - Assert.equal( - await testMatchedByDNR(true), - false, - "DNR not applied to private browsing requests by default" - ); - await extension.unload(); -}); - -add_task(async function private_browsing_allowed() { - let extension = await startDNRExtension({ privateBrowsingAllowed: true }); - Assert.equal( - await testMatchedByDNR(false), - true, - "DNR applies to non-private requests regardless of privateBrowsingAllowed" - ); - Assert.equal( - await testMatchedByDNR(true), - true, - "DNR applied to private browsing requests when privateBrowsingAllowed" - ); - await extension.unload(); -}); - -add_task( - { pref_set: [["extensions.dnr.feedback", true]] }, - async function testMatchOutcome_unaffected_by_privateBrowsing() { - let extensionWithoutPrivateBrowsingAllowed = await startDNRExtension({}); - let extension = ExtensionTestUtils.loadExtension({ - incognitoOverride: "spanning", - manifest: { - manifest_version: 3, - permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"], - }, - files: { - "page.html": ``, - "page.js": async () => { - browser.test.assertTrue( - browser.extension.inIncognitoContext, - "Extension page is opened in a private browsing context" - ); - browser.test.assertDeepEq( - { - matchedRules: [ - { ruleId: 1, rulesetId: "_session", extensionId: "@dnr-ext" }, - ], - }, - // testMatchOutcome does not offer a way to specify the private - // browsing mode of a request. Confirm that testMatchOutcome always - // simulates requests in normal private browsing mode, even if the - // testMatchOutcome method itself is called from an extension page - // in private browsing mode. - await browser.declarativeNetRequest.testMatchOutcome( - { url: "http://example.com/?simulated_request", type: "image" }, - { includeOtherExtensions: true } - ), - "testMatchOutcome includes DNR from extensions without pbm access" - ); - browser.test.sendMessage("done"); - }, - }, - }); - await extension.startup(); - let contentPage = await ExtensionTestUtils.loadContentPage( - `moz-extension://${extension.uuid}/page.html`, - { privateBrowsing: true } - ); - await extension.awaitMessage("done"); - await contentPage.close(); - await extension.unload(); - await extensionWithoutPrivateBrowsingAllowed.unload(); - } -); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js deleted file mode 100644 index e2f6da072a2d..000000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js +++ /dev/null @@ -1,66 +0,0 @@ -"use strict"; - -const server = createHttpServer({ hosts: ["example.com", "restricted"] }); -server.registerPathHandler("/", (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.write("response from server"); -}); - -add_setup(() => { - Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); - Services.prefs.setBoolPref("extensions.dnr.enabled", true); - // The restrictedDomains pref should be set early, because the pref is read - // only once (on first use) by WebExtensionPolicy::IsRestrictedURI. - Services.prefs.setCharPref( - "extensions.webextensions.restrictedDomains", - "restricted" - ); -}); - -async function startDNRExtension() { - let extension = ExtensionTestUtils.loadExtension({ - async background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [{ id: 1, condition: {}, action: { type: "block" } }], - }); - browser.test.sendMessage("dnr_registered"); - }, - manifest: { - manifest_version: 3, - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitMessage("dnr_registered"); - return extension; -} - -add_task(async function dnr_ignores_system_requests() { - let extension = await startDNRExtension(); - Assert.equal( - await (await fetch("http://example.com/")).text(), - "response from server", - "DNR should not block requests from system principal" - ); - await extension.unload(); -}); - -add_task(async function dnr_ignores_requests_to_restrictedDomains() { - let extension = await startDNRExtension(); - Assert.equal( - await ExtensionTestUtils.fetch("http://example.com/", "http://restricted/"), - "response from server", - "DNR should not block destination in restrictedDomains" - ); - await extension.unload(); -}); - -add_task(async function dnr_ignores_initiator_from_restrictedDomains() { - let extension = await startDNRExtension(); - Assert.equal( - await ExtensionTestUtils.fetch("http://restricted/", "http://example.com/"), - "response from server", - "DNR should not block requests initiated from a page in restrictedDomains" - ); - await extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js deleted file mode 100644 index 15dd11b14d29..000000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_webrequest.js +++ /dev/null @@ -1,205 +0,0 @@ -"use strict"; - -add_setup(() => { - Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); - Services.prefs.setBoolPref("extensions.dnr.enabled", true); -}); - -const server = createHttpServer({ - hosts: ["example.com", "redir"], -}); -server.registerPathHandler("/never_reached", (req, res) => { - Assert.ok(false, "Server should never have been reached"); -}); - -add_task(async function block_request_with_dnr() { - async function background() { - let onBeforeRequestPromise = new Promise(resolve => { - browser.webRequest.onBeforeRequest.addListener(resolve, { - urls: ["*://example.com/*"], - }); - }); - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestDomains: ["example.com"] }, - action: { type: "block" }, - }, - ], - }); - - await browser.test.assertRejects( - fetch("http://example.com/never_reached"), - "NetworkError when attempting to fetch resource.", - "blocked by DNR rule" - ); - // DNR is documented to take precedence over webRequest. We should still - // receive the webRequest event, however. - browser.test.log("Waiting for webRequest.onBeforeRequest..."); - await onBeforeRequestPromise; - browser.test.log("Seen webRequest.onBeforeRequest!"); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://example.com/*"], - permissions: ["declarativeNetRequest", "webRequest"], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - await extension.unload(); -}); - -add_task(async function upgradeScheme_and_redirect_request_with_dnr() { - async function background() { - let onBeforeRequestSeen = []; - browser.webRequest.onBeforeRequest.addListener( - d => { - onBeforeRequestSeen.push(d.url); - // webRequest cancels, but DNR should actually be taking precedence. - return { cancel: true }; - }, - { urls: ["*://example.com/*", "http://redir/here"] }, - ["blocking"] - ); - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestDomains: ["example.com"] }, - action: { type: "upgradeScheme" }, - }, - { - id: 2, - condition: { requestDomains: ["example.com"], urlFilter: "|https:*" }, - action: { type: "redirect", redirect: { url: "http://redir/here" } }, - // The upgradeScheme and redirect actions have equal precedence. To - // make sure that the redirect action is executed when both rules - // match, we assign a higher priority to the redirect action. - priority: 2, - }, - ], - }); - - await browser.test.assertRejects( - fetch("http://example.com/never_reached"), - "NetworkError when attempting to fetch resource.", - "although initially redirected by DNR, ultimately blocked by webRequest" - ); - // DNR is documented to take precedence over webRequest. - // So we should actually see redirects according to the DNR rules, and - // the webRequest listener should still be able to observe all requests. - browser.test.assertDeepEq( - [ - "http://example.com/never_reached", - "https://example.com/never_reached", - "http://redir/here", - ], - onBeforeRequestSeen, - "Expected onBeforeRequest events" - ); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://example.com/*", "*://redir/*"], - permissions: [ - "declarativeNetRequest", - "webRequest", - "webRequestBlocking", - ], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - await extension.unload(); -}); - -add_task(async function block_request_with_webRequest_after_allow_with_dnr() { - async function background() { - let onBeforeRequestSeen = []; - browser.webRequest.onBeforeRequest.addListener( - d => { - onBeforeRequestSeen.push(d.url); - return { cancel: !d.url.includes("webRequestNoCancel") }; - }, - { urls: ["*://example.com/*"] }, - ["blocking"] - ); - // All DNR actions that do not end up canceling/redirecting the request: - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestMethods: ["get"] }, - action: { type: "allow" }, - }, - { - id: 2, - condition: { requestMethods: ["put"] }, - action: { - type: "modifyHeaders", - requestHeaders: [{ operation: "set", header: "x", value: "y" }], - }, - }, - ], - }); - - await browser.test.assertRejects( - fetch("http://example.com/never_reached?1", { method: "get" }), - "NetworkError when attempting to fetch resource.", - "despite DNR 'allow' rule, still blocked by webRequest" - ); - await browser.test.assertRejects( - fetch("http://example.com/never_reached?2", { method: "put" }), - "NetworkError when attempting to fetch resource.", - "despite DNR 'modifyHeaders' rule, still blocked by webRequest" - ); - // Just to rule out the request having been canceled by DNR instead of - // webRequest, repeat the requests and verify that they succeed. - await fetch("http://example.com/?webRequestNoCancel1", { method: "get" }); - await fetch("http://example.com/?webRequestNoCancel2", { method: "put" }); - - browser.test.assertDeepEq( - [ - "http://example.com/never_reached?1", - "http://example.com/never_reached?2", - "http://example.com/?webRequestNoCancel1", - "http://example.com/?webRequestNoCancel2", - ], - onBeforeRequestSeen, - "Expected onBeforeRequest events" - ); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://example.com/*"], - permissions: [ - "declarativeNetRequest", - "webRequest", - "webRequestBlocking", - ], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - await extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_without_webrequest.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_without_webrequest.js deleted file mode 100644 index 7550e5f375cb..000000000000 --- a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_without_webrequest.js +++ /dev/null @@ -1,720 +0,0 @@ -"use strict"; - -// This test file verifies that the declarativeNetRequest API can modify -// network requests as expected without the presence of the webRequest API. See -// test_ext_dnr_webRequest.js for the interaction between webRequest and DNR. - -add_setup(() => { - Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); - Services.prefs.setBoolPref("extensions.dnr.enabled", true); -}); - -const server = createHttpServer({ - hosts: ["example.com", "example.net", "example.org", "redir", "dummy"], -}); -server.registerPathHandler("/cors_202", (req, res) => { - res.setStatusLine(req.httpVersion, 202, "Accepted"); - // The extensions in this test have minimal permissions, so grant CORS to - // allow them to read the response without host permissions. - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); - res.write("cors_response"); -}); -server.registerPathHandler("/never_reached", (req, res) => { - Assert.ok(false, "Server should never have been reached"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); -}); -let gPreflightCount = 0; -server.registerPathHandler("/preflight_count", (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); - res.setHeader("Access-Control-Allow-Methods", "NONSIMPLE"); - if (req.method === "OPTIONS") { - ++gPreflightCount; - } else { - // CORS Preflight considers 2xx to be successful. To rule out inadvertent - // server opt-in to CORS, respond with a non-2xx response. - res.setStatusLine(req.httpVersion, 418, "I'm a teapot"); - res.write(`count=${gPreflightCount}`); - } -}); -server.registerPathHandler("/", (req, res) => { - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Max-Age", "0"); - res.write("Dummy page"); -}); - -async function contentFetch(initiatorURL, url, options) { - let contentPage = await ExtensionTestUtils.loadContentPage(initiatorURL); - // Sanity check: that the initiator is as specified, and not redirected. - Assert.equal( - await contentPage.spawn(null, () => content.document.URL), - initiatorURL, - `Expected document load at: ${initiatorURL}` - ); - let result = await contentPage.spawn({ url, options }, async args => { - try { - let req = await content.fetch(args.url, args.options); - return { - status: req.status, - url: req.url, - body: await req.text(), - }; - } catch (e) { - return { error: e.message }; - } - }); - await contentPage.close(); - return result; -} - -add_task(async function block_request_with_dnr() { - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestMethods: ["get"] }, - action: { type: "block" }, - }, - { - id: 2, - condition: { requestMethods: ["head"] }, - action: { type: "allow" }, - }, - ], - }); - { - // Request not matching DNR. - let req = await fetch("http://example.com/cors_202", { method: "post" }); - browser.test.assertEq(202, req.status, "allowed without DNR rule"); - browser.test.assertEq("cors_response", await req.text()); - } - { - // Request with "allow" DNR action. - let req = await fetch("http://example.com/cors_202", { method: "head" }); - browser.test.assertEq(202, req.status, "allowed by DNR rule"); - browser.test.assertEq("", await req.text(), "no response for HEAD"); - } - - // Request with "block" DNR action. - await browser.test.assertRejects( - fetch("http://example.com/never_reached", { method: "get" }), - "NetworkError when attempting to fetch resource.", - "blocked by DNR rule" - ); - - browser.test.sendMessage("tested_dnr_block"); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - manifest: { - manifest_version: 3, - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitMessage("tested_dnr_block"); - - // DNR should not only work with requests within the extension, but also from - // web pages. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://example.com/never_reached"), - { error: "NetworkError when attempting to fetch resource." }, - "Blocked by DNR with declarativeNetRequestWithHostAccess" - ); - - // The declarativeNetRequest permission grants the ability to block requests - // from other extensions. (The declarativeNetRequestWithHostAccess permission - // does not; see test task block_with_declarativeNetRequestWithHostAccess.) - let otherExtension = ExtensionTestUtils.loadExtension({ - async background() { - await browser.test.assertRejects( - fetch("http://example.com/never_reached", { method: "get" }), - "NetworkError when attempting to fetch resource.", - "blocked by different extension with declarativeNetRequest permission" - ); - browser.test.sendMessage("other_extension_done"); - }, - }); - await otherExtension.startup(); - await otherExtension.awaitMessage("other_extension_done"); - await otherExtension.unload(); - - await extension.unload(); -}); - -// Verifies that the "declarativeNetRequestWithHostAccess" permission can only -// block if it has permission for the initiator. -add_task(async function block_with_declarativeNetRequestWithHostAccess() { - let extension = ExtensionTestUtils.loadExtension({ - async background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [{ id: 1, condition: {}, action: { type: "block" } }], - }); - browser.test.sendMessage("dnr_registered"); - }, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: [""], - permissions: ["declarativeNetRequestWithHostAccess"], - }, - }); - await extension.startup(); - await extension.awaitMessage("dnr_registered"); - - // Initiator "http://dummy" does match "", so DNR rule should apply. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://example.com/never_reached"), - { error: "NetworkError when attempting to fetch resource." }, - "Blocked by DNR with declarativeNetRequestWithHostAccess" - ); - - // Extensions cannot have permissions for another extension and therefore the - // DNR rule never applies. - let otherExtension = ExtensionTestUtils.loadExtension({ - async background() { - let req = await fetch("http://example.com/cors_202", { method: "get" }); - browser.test.assertEq(202, req.status, "not blocked by other extension"); - browser.test.assertEq("cors_response", await req.text()); - browser.test.sendMessage("other_extension_done"); - }, - }); - await otherExtension.startup(); - await otherExtension.awaitMessage("other_extension_done"); - await otherExtension.unload(); - - await extension.unload(); -}); - -// Verifies that upgradeScheme works. -// The HttpServer helper does not support https (bug 1742061), so in this -// test we just verify whether the upgrade has been attempted. Coverage that -// verifies that the upgraded request completes is in: -// toolkit/components/extensions/test/mochitest/test_ext_dnr_upgradeScheme.html -add_task(async function upgradeScheme_declarativeNetRequestWithHostAccess() { - let extension = ExtensionTestUtils.loadExtension({ - async background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { excludedRequestDomains: ["dummy"] }, - action: { type: "upgradeScheme" }, - }, - { - id: 2, - // HttpServer does not support https (bug 1742061). - // As a work-around, we just redirect the https:-request to http. - condition: { urlFilter: "|https:*" }, - action: { - type: "redirect", - redirect: { url: "http://dummy/cors_202?from_https" }, - }, - // The upgradeScheme and redirect actions have equal precedence. To - // make sure that the redirect action is executed when both rules - // match, we assign a higher priority to the redirect action. - priority: 2, - }, - ], - }); - - let req = await fetch("http://redir/never_reached"); - browser.test.assertEq( - "http://dummy/cors_202?from_https", - req.url, - "upgradeScheme upgraded to https" - ); - browser.test.assertEq("cors_response", await req.text()); - - browser.test.sendMessage("tested_dnr_upgradeScheme"); - }, - temporarilyInstalled: true, // Needed for granted_host_permissions. - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://dummy/*", "*://redir/*"], - permissions: ["declarativeNetRequestWithHostAccess"], - }, - }); - await extension.startup(); - await extension.awaitMessage("tested_dnr_upgradeScheme"); - - // Request to same-origin subresource, which should be upgraded. - Assert.equal( - (await contentFetch("http://redir/", "http://redir/never_reached")).url, - "http://dummy/cors_202?from_https", - "upgradeScheme + host access should upgrade (same-origin request)" - ); - - // Request to cross-origin subresource, which should be upgraded. - // Note: after the upgrade, a cross-origin redirect happens. Internally, we - // reflect the Origin request header in the Access-Control-Allow-Origin (ACAO) - // response header, to ensure that the request is accepted by CORS. See - // https://github.com/w3c/webappsec-upgrade-insecure-requests/issues/32 - Assert.equal( - (await contentFetch("http://dummy/", "http://redir/never_reached")).url, - // TODO bug 1800990: despite the mirrored Origin in ACAO, the CORS check - // fails after a request is upgraded. Once fixed, update this expectation: - undefined, // Should be: "http://dummy/cors_202?from_https", - "TODO 1800990: upgradeScheme + host access should upgrade (cross-origin request)" - ); - - // The DNR extension does not have example.net in host_permissions. - const urlNoHostPerms = "http://example.net/cors_202?missing_host_permission"; - Assert.equal( - (await contentFetch("http://dummy/", urlNoHostPerms)).url, - urlNoHostPerms, - "upgradeScheme not matched when extension lacks host access" - ); - - await extension.unload(); -}); - -add_task(async function redirect_request_with_dnr() { - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { - requestDomains: ["example.com"], - requestMethods: ["get"], - }, - action: { - type: "redirect", - redirect: { - url: "http://example.net/cors_202?1", - }, - }, - }, - { - id: 2, - // Note: extension does not have example.org host permission. - condition: { requestDomains: ["example.org"] }, - action: { - type: "redirect", - redirect: { - url: "http://example.net/cors_202?2", - }, - }, - }, - ], - }); - // The extension only has example.com permission, but the redirects to - // example.net are still due to the CORS headers from the server. - { - // Simple GET request. - let req = await fetch("http://example.com/never_reached"); - browser.test.assertEq(202, req.status, "redirected by DNR (simple)"); - browser.test.assertEq("http://example.net/cors_202?1", req.url); - browser.test.assertEq("cors_response", await req.text()); - } - { - // GeT request should be matched despite having a different case. - let req = await fetch("http://example.com/never_reached", { - method: "GeT", - }); - browser.test.assertEq(202, req.status, "redirected by DNR (GeT)"); - browser.test.assertEq("http://example.net/cors_202?1", req.url); - browser.test.assertEq("cors_response", await req.text()); - } - { - // Host permission missing for request, request not redirected by DNR. - // Response is readable due to the CORS response headers from the server. - let req = await fetch("http://example.org/cors_202?noredir"); - browser.test.assertEq(202, req.status, "not redirected by DNR"); - browser.test.assertEq("http://example.org/cors_202?noredir", req.url); - browser.test.assertEq("cors_response", await req.text()); - } - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://example.com/*"], - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - - let otherExtension = ExtensionTestUtils.loadExtension({ - async background() { - // The DNR extension has permissions for example.com, but not for this - // extension. Therefore the "redirect" action should not apply. - let req = await fetch("http://example.com/cors_202?other_ext"); - browser.test.assertEq(202, req.status, "not redirected by DNR"); - browser.test.assertEq("http://example.com/cors_202?other_ext", req.url); - browser.test.assertEq("cors_response", await req.text()); - browser.test.sendMessage("other_extension_done"); - }, - }); - await otherExtension.startup(); - await otherExtension.awaitMessage("other_extension_done"); - await otherExtension.unload(); - - await extension.unload(); -}); - -// Verifies that DNR redirects requiring a CORS preflight behave as expected. -add_task(async function redirect_request_with_dnr_cors_preflight() { - // Most other test tasks only test requests within the test extension. This - // test intentionally triggers requests outside the extension, to make sure - // that the usual CORS mechanisms is triggered (instead of exceptions from - // host permissions). - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { - requestDomains: ["redir"], - excludedRequestMethods: ["options"], - }, - action: { - type: "redirect", - redirect: { - url: "http://example.com/preflight_count", - }, - }, - }, - { - id: 2, - condition: { - requestDomains: ["example.net"], - excludedRequestMethods: ["nonsimple"], // note: redirects "options" - }, - action: { - type: "redirect", - redirect: { - url: "http://example.com/preflight_count", - }, - }, - }, - ], - }); - let req = await fetch("http://redir/never_reached", { - method: "NONSIMPLE", - }); - // Extension has permission for "redir", but not for the redirect target. - // The request is non-simple (see below for explanation of non-simple), so - // a preflight (OPTIONS) request to /preflight_count is expected before the - // redirection target is requested. - browser.test.assertEq( - "count=1", - await req.text(), - "Got preflight before redirect target because of missing host_permissions" - ); - - browser.test.sendMessage("continue_preflight_tests"); - } - - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - // "redir" and "example.net" are needed to allow redirection of these. - // "dummy" is needed to redirect requests initiated from http://dummy. - host_permissions: ["*://redir/*", "*://example.net/*", "*://dummy/*"], - permissions: ["declarativeNetRequest"], - }, - }); - gPreflightCount = 0; - await extension.startup(); - await extension.awaitMessage("continue_preflight_tests"); - gPreflightCount = 0; // value already checked before continue_preflight_tests. - - // Simple request (i.e. without preflight requirement), that's redirected to - // another URL by the DNR rule. The redirect should be accepted, and in - // particular not be blocked by the same-origin policy. The redirect target - // (/preflight_count) is readable due to the CORS headers from the server. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://redir/never_reached"), - // count=0: A simple request does not trigger a preflight (OPTIONS) request. - { status: 418, url: "http://example.com/preflight_count", body: "count=0" }, - "Simple request should not have a preflight." - ); - - // Any request method other than "GET", "POST" and "PUT" (e.g "NONSIMPLE") is - // a non-simple request that triggers a preflight request ("OPTIONS"). - // - // Usually, this happens (without extension-triggered redirects): - // 1. NONSIMPLE /never_reached : is started, but does NOT hit the server yet. - // 2. OPTIONS /never_reached + Access-Control-Request-Method: NONSIMPLE - // 3. NONSIMPLE /never_reached : reaches the server if allowed by OPTIONS. - // - // With an extension-initiated redirect to /preflight_count: - // 1. NONSIMPLE /never_reached : is started, but does not hit the server yet. - // 2. extension redirects to /preflight_count - // 3. OPTIONS /preflight_count + Access-Control-Request-Method: NONSIMPLE - // - This is because the redirect preserves the request method/body/etc. - // 4. NONSIMPLE /preflight_count : reaches the server if allowed by OPTIONS. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://redir/never_reached", { - method: "NONSIMPLE", - }), - // Due to excludedRequestMethods: ["options"], the preflight for the - // redirect target is not intercepted, so the server sees a preflight. - { status: 418, url: "http://example.com/preflight_count", body: "count=1" }, - "Initial URL redirected, redirection target has preflight" - ); - gPreflightCount = 0; - - // The "example.net" rule has "excludedRequestMethods": ["nonsimple"], so the - // initial "NONSIMPLE" request is not immediately redirected. Therefore the - // preflight request happens. This OPTIONS request is matched by the DNR rule - // and redirected to /preflight_count. While preflight_count offers a very - // permissive preflight response, it is not even fetched: - // Only a 2xx HTTP status is considered a valid response to a pre-flight. - // A redirect is like a 3xx HTTP status, so the whole request is rejected, - // and the redirect is not followed for the OPTIONS request. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://example.net/never_reached", { - method: "NONSIMPLE", - }), - { error: "NetworkError when attempting to fetch resource." }, - "Redirect of preflight request (OPTIONS) should be a CORS failure" - ); - - Assert.equal(gPreflightCount, 0, "Preflight OPTIONS has been intercepted"); - - await extension.unload(); -}); - -// Tests that DNR redirect rules can be chained. -add_task(async function redirect_request_with_dnr_multiple_hops() { - async function background() { - // Set up redirects from example.com up until dummy. - let hosts = ["example.com", "example.net", "example.org", "redir", "dummy"]; - let rules = []; - for (let i = 1; i < hosts.length; ++i) { - const from = hosts[i - 1]; - const to = hosts[i]; - const end = hosts.length - 1 === i; - rules.push({ - id: i, - condition: { requestDomains: [from] }, - action: { - type: "redirect", - redirect: { - // All intermediate redirects should never hit the server, but the - // last one should.. - url: end ? `http://${to}/?end` : `http://${to}/never_reached`, - }, - }, - }); - } - await browser.declarativeNetRequest.updateSessionRules({ addRules: rules }); - let req = await fetch("http://example.com/never_reached"); - browser.test.assertEq(200, req.status, "redirected by DNR (multiple)"); - browser.test.assertEq("http://dummy/?end", req.url, "Last URL in chain"); - browser.test.assertEq("Dummy page", await req.text()); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://*/*"], // matches all in the |hosts| list. - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - - // Test again, but without special extension permissions to verify that DNR - // redirects pass CORS checks. - Assert.deepEqual( - await contentFetch("http://dummy/", "http://redir/never_reached"), - { status: 200, url: "http://dummy/?end", body: "Dummy page" }, - "Multiple redirects by DNR, requested from web origin." - ); - - await extension.unload(); -}); - -add_task(async function redirect_request_with_dnr_with_redirect_loop() { - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestDomains: ["redir"] }, - action: { - type: "redirect", - redirect: { - url: "http://redir/cors_202?loop", - }, - }, - }, - ], - }); - - // Redirect with initially a different URL. - await browser.test.assertRejects( - fetch("http://redir/never_reached?"), - "NetworkError when attempting to fetch resource.", - "Redirect loop caught (initially different URL)" - ); - - // Redirect where redirect is exactly the same URL as requested. - await browser.test.assertRejects( - fetch("http://redir/cors_202?loop"), - "NetworkError when attempting to fetch resource.", - "Redirect loop caught (redirect target same as initial URL)" - ); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://redir/*"], - permissions: ["declarativeNetRequest"], - }, - }); - await extension.startup(); - await extension.awaitFinish(); - await extension.unload(); -}); - -// Tests that redirect to extensionPath works, provided that the initiator is -// either the extension itself, or in host_permissions. Moreover, the requested -// resource must match a web_accessible_resources entry for both the initiator -// AND the pre-redirect URL. -add_task(async function redirect_request_with_dnr_to_extensionPath() { - async function background() { - await browser.declarativeNetRequest.updateSessionRules({ - addRules: [ - { - id: 1, - condition: { requestDomains: ["redir"], requestMethods: ["post"] }, - action: { - type: "redirect", - redirect: { - extensionPath: "/war.txt?1", - }, - }, - }, - { - id: 2, - condition: { requestDomains: ["redir"], requestMethods: ["put"] }, - action: { - type: "redirect", - redirect: { - extensionPath: "/nonwar.txt?2", - }, - }, - }, - ], - }); - { - let req = await fetch("http://redir/never_reached", { method: "post" }); - browser.test.assertEq(200, req.status, "redirected to extensionPath"); - browser.test.assertEq(`${location.origin}/war.txt?1`, req.url); - browser.test.assertEq("war_ext_res", await req.text()); - } - // Redirects to extensionPath that is not in web_accessible_resources. - // While the initiator (extension) would be allowed to read the resource - // due to it being same-origin, the pre-redirect URL (http://redir) is not - // matching web_accessible_resources[].matches, so the load is rejected. - // - // This behavior differs from Chrome (e.g. at least in Chrome 109) that - // does allow the load to complete. Extensions who really care about - // exposing a web-accessible resource to the world can just put an all_urls - // pattern in web_accessible_resources[].matches. - await browser.test.assertRejects( - fetch("http://redir/never_reached", { method: "put" }), - "NetworkError when attempting to fetch resource.", - "Redirect to nowar.txt, but pre-redirect host is not in web_accessible_resources[].matches" - ); - - browser.test.notifyPass(); - } - let extension = ExtensionTestUtils.loadExtension({ - background, - temporarilyInstalled: true, // Needed for granted_host_permissions - manifest: { - manifest_version: 3, - granted_host_permissions: true, - host_permissions: ["*://redir/*", "*://dummy/*"], - permissions: ["declarativeNetRequest"], - web_accessible_resources: [ - // *://redir/* is in matches, because that is the pre-redirect host. - // *://dummy/* is in matches, because that is an initiator below. - { resources: ["war.txt"], matches: ["*://redir/*", "*://dummy/*"] }, - // without "matches", this is almost equivalent to not being listed in - // web_accessible_resources at all. This entry is listed here to verify - // that the presence of extension_ids does not somehow allow a request - // with an extension initiator to complete. - { resources: ["nonwar.txt"], extension_ids: ["*"] }, - ], - }, - files: { - "war.txt": "war_ext_res", - "nonwar.txt": "non_war_ext_res", - }, - }); - await extension.startup(); - await extension.awaitFinish(); - const extPrefix = `moz-extension://${extension.uuid}`; - - // Request from origin in host_permissions, for web-accessible resource. - Assert.deepEqual( - await contentFetch( - "http://dummy/", // <-- Matching web_accessible_resources[].matches - "http://redir/never_reached", // <-- With matching host_permissions - { method: "post" } - ), - { status: 200, url: `${extPrefix}/war.txt?1`, body: "war_ext_res" }, - "Should have got redirect to web_accessible_resources (war.txt)" - ); - - // Request from origin in host_permissions, for non-web-accessible resource. - let { messages } = await promiseConsoleOutput(async () => { - Assert.deepEqual( - await contentFetch( - "http://dummy/", // <-- Matching web_accessible_resources[].matches - "http://redir/never_reached", // <-- With matching host_permissions - { method: "put" } - ), - { error: "NetworkError when attempting to fetch resource." }, - "Redirect to nowar.txt, without matching web_accessible_resources[].matches" - ); - }); - const EXPECTED_SECURITY_ERROR = `Content at http://redir/never_reached may not load or link to ${extPrefix}/nonwar.txt?2.`; - Assert.equal( - messages.filter(m => m.message.includes(EXPECTED_SECURITY_ERROR)).length, - 1, - `Should log SecurityError: ${EXPECTED_SECURITY_ERROR}` - ); - - // Request from origin not in host_permissions. DNR rule should not apply. - Assert.deepEqual( - await contentFetch( - "http://dummy/", // <-- Matching web_accessible_resources[].matches - "http://example.com/cors_202", // <-- NOT in host_permissions - { method: "post" } - ), - { status: 202, url: "http://example.com/cors_202", body: "cors_response" }, - "Extension should not have redirected, due to lack of host permissions" - ); - - await extension.unload(); -}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini index cdbfad6e2956..f7406f338ddb 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini @@ -114,14 +114,9 @@ skip-if = [test_ext_cors_mozextension.js] [test_ext_csp_frame_ancestors.js] [test_ext_debugging_utils.js] -[test_ext_dnr_allowAllRequests.js] [test_ext_dnr_api.js] -[test_ext_dnr_private_browsing.js] [test_ext_dnr_session_rules.js] -[test_ext_dnr_system_restrictions.js] [test_ext_dnr_testMatchOutcome.js] -[test_ext_dnr_webrequest.js] -[test_ext_dnr_without_webrequest.js] [test_ext_dns.js] skip-if = os == "android" # Android needs alternative for proxy.settings - bug 1723523 [test_ext_downloads.js] diff --git a/toolkit/components/extensions/webrequest/WebRequest.jsm b/toolkit/components/extensions/webrequest/WebRequest.jsm index 9de07fb4e0ee..9b9982c42cb1 100644 --- a/toolkit/components/extensions/webrequest/WebRequest.jsm +++ b/toolkit/components/extensions/webrequest/WebRequest.jsm @@ -19,7 +19,6 @@ const { XPCOMUtils } = ChromeUtils.importESModule( const lazy = {}; XPCOMUtils.defineLazyModuleGetters(lazy, { - ExtensionDNR: "resource://gre/modules/ExtensionDNR.jsm", ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", ExtensionUtils: "resource://gre/modules/ExtensionUtils.jsm", WebRequestUpload: "resource://gre/modules/WebRequestUpload.jsm", @@ -620,9 +619,6 @@ HttpObserverManager = { onErrorOccurred: new Map(), onCompleted: new Map(), }, - // Whether there are any registered declarativeNetRequest rules. These DNR - // rules may match new requests and result in request modifications. - dnrActive: false, openingInitialized: false, beforeConnectInitialized: false, @@ -664,11 +660,10 @@ HttpObserverManager = { // webRequest listeners and removing those that are no longer needed if // there are no more listeners for corresponding webRequest events. addOrRemove() { - let needOpening = this.listeners.onBeforeRequest.size || this.dnrActive; + let needOpening = this.listeners.onBeforeRequest.size; let needBeforeConnect = this.listeners.onBeforeSendHeaders.size || - this.listeners.onSendHeaders.size || - this.dnrActive; + this.listeners.onSendHeaders.size; if (needOpening && !this.openingInitialized) { this.openingInitialized = true; Services.obs.addObserver(this, "http-on-modify-request"); @@ -697,8 +692,7 @@ HttpObserverManager = { let needExamine = this.needTracing || this.listeners.onHeadersReceived.size || - this.listeners.onAuthRequired.size || - this.dnrActive; + this.listeners.onAuthRequired.size; if (needExamine && !this.examineInitialized) { this.examineInitialized = true; @@ -746,11 +740,6 @@ HttpObserverManager = { this.addOrRemove(); }, - setDNRHandlingEnabled(dnrActive) { - this.dnrActive = dnrActive; - this.addOrRemove(); - }, - observe(subject, topic, data) { let channel = this.getWrapper(subject); switch (topic) { @@ -928,10 +917,6 @@ HttpObserverManager = { if (kind !== "onErrorOccurred" && channel.errorString) { return; } - if (this.dnrActive) { - // DNR may modify (but not cancel) the request at this stage. - lazy.ExtensionDNR.beforeWebRequestEvent(channel, kind); - } let registerFilter = this.FILTER_TYPES.has(kind); let commonData = null; @@ -1027,10 +1012,6 @@ HttpObserverManager = { Cu.reportError(e); } - if (this.dnrActive && lazy.ExtensionDNR.handleRequest(channel, kind)) { - return; - } - return this.applyChanges( kind, channel, @@ -1306,10 +1287,6 @@ var onCompleted = new HttpEvent("onCompleted", ["responseHeaders"]); var onErrorOccurred = new HttpEvent("onErrorOccurred"); var WebRequest = { - setDNRHandlingEnabled: dnrActive => { - HttpObserverManager.setDNRHandlingEnabled(dnrActive); - }, - onBeforeRequest, onBeforeSendHeaders, onSendHeaders,