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)
This commit is contained in:
Csoregi Natalia 2022-11-25 01:13:40 +02:00
parent 3dab68ed84
commit 58a7f7a38f
10 changed files with 9 additions and 1592 deletions

View File

@ -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,
};

View File

@ -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]

View File

@ -1,120 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>DNR with upgradeScheme action</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
</head>
<body>
<script>
"use strict";
// This test is not a xpcshell test, because we want to test upgrades to https,
// and HttpServer helper does not support https (bug 1742061).
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [
["extensions.manifestV3.enabled", true],
["extensions.dnr.enabled", true],
],
});
});
// Tests that the upgradeScheme action works as expected:
// - http should be upgraded to https
// - after the https upgrade the request should happen instead of being stuck
// in a upgrade redirect loop.
add_task(async function upgradeScheme_with_dnr() {
let extension = ExtensionTestUtils.loadExtension({
async background() {
await browser.declarativeNetRequest.updateSessionRules({
addRules: [{ id: 1, condition: {}, action: { type: "upgradeScheme" } }],
});
let res = await fetch(
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
"http://example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.txt"
);
browser.test.assertEq(
"https://example.com/tests/toolkit/components/extensions/test/mochitest/file_sample.txt",
res.url,
"upgradeScheme should have upgraded to https"
);
// Server adds "Access-Control-Allow-Origin: *" to file_sample.txt, so
// we should be able to read the response despite no host_permissions.
browser.test.assertEq("Sample", await res.text(), "read body with CORS");
browser.test.sendMessage("dnr_registered");
},
manifest: {
manifest_version: 3,
// Note: host_permissions missing. upgradeScheme should not need it.
permissions: ["declarativeNetRequest"],
},
});
await extension.startup();
await extension.awaitMessage("dnr_registered");
let otherExtension = ExtensionTestUtils.loadExtension({
async background() {
let firstRequestPromise = new Promise(resolve => {
let count = 0;
browser.webRequest.onBeforeRequest.addListener(
({ url }) => {
++count;
browser.test.assertTrue(
count <= 2,
`Expected at most two requests; got ${count} to ${url}`
);
resolve(url);
},
{ urls: ["*://example.com/?test_dnr_upgradeScheme"] }
);
});
// Round-trip through ext-webRequest.js implementation to ensure that the
// listener has been registered (workaround for bug 1300234).
await browser.webRequest.handlerBehaviorChanged();
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
const insecureInitialUrl = "http://example.com/?test_dnr_upgradeScheme";
browser.test.log(`Requesting insecure URL: ${insecureInitialUrl}`);
let req = await fetch(insecureInitialUrl);
browser.test.assertEq(
"https://example.com/?test_dnr_upgradeScheme",
req.url,
"upgradeScheme action upgraded http to https"
);
browser.test.assertEq(200, req.status, "Correct HTTP status");
await req.text(); // Verify that the body can be read, just in case.
// Sanity check that the test did not pass trivially due to an automatic
// https upgrade of the extension / test environment.
browser.test.assertEq(
insecureInitialUrl,
await firstRequestPromise,
"Initial URL should be http"
);
browser.test.sendMessage("tested_dnr_upgradeScheme");
},
manifest: {
host_permissions: ["*://example.com/*"],
permissions: ["webRequest"],
},
});
await otherExtension.startup();
await otherExtension.awaitMessage("tested_dnr_upgradeScheme");
await otherExtension.unload();
await extension.unload();
});
</script>
</body>
</html>

View File

@ -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();
});

View File

@ -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": `<!DOCTYPE html><script src="page.js"></script>`,
"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();
}
);

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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: ["<all_urls>"],
permissions: ["declarativeNetRequestWithHostAccess"],
},
});
await extension.startup();
await extension.awaitMessage("dnr_registered");
// Initiator "http://dummy" does match "<all_urls>", 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();
});

View File

@ -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]

View File

@ -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,