diff --git a/platform/mv3/extension/js/popup.js b/platform/mv3/extension/js/popup.js index bd7ea7078..ca95b4637 100644 --- a/platform/mv3/extension/js/popup.js +++ b/platform/mv3/extension/js/popup.js @@ -293,7 +293,7 @@ async function init() { const { rules, filters, css } = details; let ruleCount = rules.plain + rules.regex; if ( popupPanelData.hasOmnipotence ) { - ruleCount += rules.removeparam + rules.redirect; + ruleCount += rules.removeparam + rules.redirect + rules.csp; } let specificCount = 0; if ( css.specific instanceof Object ) { diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js index c190c2e3b..d37cae12d 100644 --- a/platform/mv3/extension/js/ruleset-manager.js +++ b/platform/mv3/extension/js/ruleset-manager.js @@ -37,6 +37,8 @@ const REMOVEPARAMS_REALM_START = REGEXES_REALM_END; const REMOVEPARAMS_REALM_END = REMOVEPARAMS_REALM_START + RULE_REALM_SIZE; const REDIRECT_REALM_START = REMOVEPARAMS_REALM_END; const REDIRECT_REALM_END = REDIRECT_REALM_START + RULE_REALM_SIZE; +const CSP_REALM_START = REDIRECT_REALM_END; +const CSP_REALM_END = CSP_REALM_START + RULE_REALM_SIZE; const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000; const BLOCKING_MODES_RULE_ID = TRUSTED_DIRECTIVE_BASE_RULE_ID + 1; const CURRENT_CONFIG_BASE_RULE_ID = 9000000; @@ -327,11 +329,86 @@ async function updateRedirectRules() { /******************************************************************************/ +async function updateCspRules() { + const [ + hasOmnipotence, + rulesetDetails, + dynamicRuleMap, + ] = await Promise.all([ + browser.permissions.contains({ origins: [ '' ] }), + getEnabledRulesetsDetails(), + getDynamicRules(), + ]); + + // Fetch csp rules for all enabled rulesets + const toFetch = []; + for ( const details of rulesetDetails ) { + if ( details.rules.csp === 0 ) { continue; } + toFetch.push(fetchJSON(`/rulesets/csp/${details.id}`)); + } + const cspRulesets = await Promise.all(toFetch); + + // Redirect rules can only be enforced with omnipotence + const newRules = []; + if ( hasOmnipotence ) { + let cspRuleId = CSP_REALM_START; + for ( const rules of cspRulesets ) { + if ( Array.isArray(rules) === false ) { continue; } + for ( const rule of rules ) { + rule.id = cspRuleId++; + newRules.push(rule); + } + } + } + + // Add csp rules to dynamic ruleset without affecting rules + // outside csp rules realm. + const newRuleMap = new Map(newRules.map(rule => [ rule.id, rule ])); + const addRules = []; + const removeRuleIds = []; + + for ( const oldRule of dynamicRuleMap.values() ) { + if ( oldRule.id < CSP_REALM_START ) { continue; } + if ( oldRule.id >= CSP_REALM_END ) { continue; } + const newRule = newRuleMap.get(oldRule.id); + if ( newRule === undefined ) { + removeRuleIds.push(oldRule.id); + dynamicRuleMap.delete(oldRule.id); + } else if ( JSON.stringify(oldRule) !== JSON.stringify(newRule) ) { + removeRuleIds.push(oldRule.id); + addRules.push(newRule); + dynamicRuleMap.set(oldRule.id, newRule); + } + } + + for ( const newRule of newRuleMap.values() ) { + if ( dynamicRuleMap.has(newRule.id) ) { continue; } + addRules.push(newRule); + dynamicRuleMap.set(newRule.id, newRule); + } + + if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; } + + if ( removeRuleIds.length !== 0 ) { + console.info(`Remove ${removeRuleIds.length} DNR redirect rules`); + } + if ( addRules.length !== 0 ) { + console.info(`Add ${addRules.length} DNR redirect rules`); + } + + return dnr.updateDynamicRules({ addRules, removeRuleIds }); +} + +/******************************************************************************/ + +// TODO: group all omnipotence-related rules into one realm. + async function updateDynamicRules() { return Promise.all([ updateRegexRules(), updateRemoveparamRules(), updateRedirectRules(), + updateCspRules(), ]); } diff --git a/platform/mv3/extension/js/settings.js b/platform/mv3/extension/js/settings.js index b41d2b457..08acaede3 100644 --- a/platform/mv3/extension/js/settings.js +++ b/platform/mv3/extension/js/settings.js @@ -43,13 +43,13 @@ function renderNumber(value) { /******************************************************************************/ function rulesetStats(rulesetId) { - const canRemoveParams = cachedRulesetData.defaultFilteringMode > 1; + const hasOmnipotence = cachedRulesetData.defaultFilteringMode > 1; const rulesetDetails = rulesetMap.get(rulesetId); if ( rulesetDetails === undefined ) { return; } const { rules, filters } = rulesetDetails; let ruleCount = rules.plain + rules.regex; - if ( canRemoveParams ) { - ruleCount += rules.removeparam + rules.redirect; + if ( hasOmnipotence ) { + ruleCount += rules.removeparam + rules.redirect + rules.csp; } const filterCount = filters.accepted; return { ruleCount, filterCount }; diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 7da5eef17..35dfc3d74 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -282,12 +282,6 @@ async function processNetworkFilters(assetDetails, network) { }); log(`\tredirect=: ${redirects.length}`); - const headers = rules.filter(rule => - isUnsupported(rule) === false && - isCsp(rule) - ); - log(`\tcsp= (discarded): ${headers.length}`); - const removeparamsGood = rules.filter(rule => isUnsupported(rule) === false && isRemoveparam(rule) ); @@ -296,6 +290,12 @@ async function processNetworkFilters(assetDetails, network) { ); log(`\tremoveparams= (accepted/discarded): ${removeparamsGood.length}/${removeparamsBad.length}`); + const csps = rules.filter(rule => + isUnsupported(rule) === false && + isCsp(rule) + ); + log(`\tcsp=: ${csps.length}`); + const bad = rules.filter(rule => isUnsupported(rule) ); @@ -328,14 +328,22 @@ async function processNetworkFilters(assetDetails, network) { ); } + if ( csps.length !== 0 ) { + writeFile( + `${rulesetDir}/csp/${assetDetails.id}.json`, + `${JSON.stringify(csps, replacer, 1)}\n` + ); + } + return { total: rules.length, plain: plainGood.length, - discarded: redirects.length + headers.length + removeparamsBad.length, + discarded: removeparamsBad.length, rejected: bad.length, regex: regexes.length, removeparam: removeparamsGood.length, redirect: redirects.length, + csp: csps.length, }; } @@ -1216,6 +1224,7 @@ async function rulesetFromURLs(assetDetails) { regex: netStats.regex, removeparam: netStats.removeparam, redirect: netStats.redirect, + csp: netStats.csp, discarded: netStats.discarded, rejected: netStats.rejected, },