[mv3] Add support for redirect= filters

This adds support for `redirect=` filters. As with `removeparam=`
filters, `redirect=` filters can only be enforced when the
default filtering mode is set to Optimal or Complete, since these
filters require broad host permissions to be enforced by the DNR
engine.

`redirect-rule=` filters are not supported since there is no
corresponding DNR syntax.

Additionally, fixed the dropping of whole network filters even though
those filters are still useful despite not being completely
enforceable -- for example a filter with a single (unsupported) domain
using entity syntax in its `domain=` option should not be wholly
dropped when there are other valid domains in the list.
This commit is contained in:
Raymond Hill 2022-10-16 12:05:24 -04:00
parent 5a9cd724ca
commit 985ea24e82
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
11 changed files with 418 additions and 219 deletions

View File

@ -291,9 +291,9 @@ async function init() {
const div = qs$('#templates .rulesetDetails').cloneNode(true); const div = qs$('#templates .rulesetDetails').cloneNode(true);
dom.text(qs$('h1', div), details.name); dom.text(qs$('h1', div), details.name);
const { rules, filters, css } = details; const { rules, filters, css } = details;
let ruleCount = rules.plain + rules.regexes; let ruleCount = rules.plain + rules.regex;
if ( popupPanelData.hasOmnipotence ) { if ( popupPanelData.hasOmnipotence ) {
ruleCount += rules.removeparams; ruleCount += rules.removeparam + rules.redirect;
} }
dom.text( dom.text(
qs$('p', div), qs$('p', div),

View File

@ -33,8 +33,10 @@ import { fetchJSON } from './fetch.js';
const RULE_REALM_SIZE = 1000000; const RULE_REALM_SIZE = 1000000;
const REGEXES_REALM_START = 1000000; const REGEXES_REALM_START = 1000000;
const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE; const REGEXES_REALM_END = REGEXES_REALM_START + RULE_REALM_SIZE;
const REMOVEPARAMS_REALM_START = 2000000; const REMOVEPARAMS_REALM_START = REGEXES_REALM_END;
const REMOVEPARAMS_REALM_END = REMOVEPARAMS_REALM_START + RULE_REALM_SIZE; 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 TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000; const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
const BLOCKING_MODES_RULE_ID = TRUSTED_DIRECTIVE_BASE_RULE_ID + 1; const BLOCKING_MODES_RULE_ID = TRUSTED_DIRECTIVE_BASE_RULE_ID + 1;
const CURRENT_CONFIG_BASE_RULE_ID = 9000000; const CURRENT_CONFIG_BASE_RULE_ID = 9000000;
@ -100,8 +102,8 @@ async function updateRegexRules() {
// Fetch regexes for all enabled rulesets // Fetch regexes for all enabled rulesets
const toFetch = []; const toFetch = [];
for ( const details of rulesetDetails ) { for ( const details of rulesetDetails ) {
if ( details.rules.regexes === 0 ) { continue; } if ( details.rules.regex === 0 ) { continue; }
toFetch.push(fetchJSON(`/rulesets/regex/${details.id}.regexes`)); toFetch.push(fetchJSON(`/rulesets/regex/${details.id}`));
} }
const regexRulesets = await Promise.all(toFetch); const regexRulesets = await Promise.all(toFetch);
@ -195,8 +197,8 @@ async function updateRemoveparamRules() {
// Fetch removeparam rules for all enabled rulesets // Fetch removeparam rules for all enabled rulesets
const toFetch = []; const toFetch = [];
for ( const details of rulesetDetails ) { for ( const details of rulesetDetails ) {
if ( details.rules.removeparams === 0 ) { continue; } if ( details.rules.removeparam === 0 ) { continue; }
toFetch.push(fetchJSON(`/rulesets/removeparam/${details.id}.removeparams`)); toFetch.push(fetchJSON(`/rulesets/removeparam/${details.id}`));
} }
const removeparamRulesets = await Promise.all(toFetch); const removeparamRulesets = await Promise.all(toFetch);
@ -253,17 +255,90 @@ async function updateRemoveparamRules() {
/******************************************************************************/ /******************************************************************************/
async function updateRedirectRules() {
const [
hasOmnipotence,
rulesetDetails,
dynamicRuleMap,
] = await Promise.all([
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
getEnabledRulesetsDetails(),
getDynamicRules(),
]);
// Fetch redirect rules for all enabled rulesets
const toFetch = [];
for ( const details of rulesetDetails ) {
if ( details.rules.redirect === 0 ) { continue; }
toFetch.push(fetchJSON(`/rulesets/redirect/${details.id}`));
}
const redirectRulesets = await Promise.all(toFetch);
// Redirect rules can only be enforced with omnipotence
const newRules = [];
if ( hasOmnipotence ) {
let redirectRuleId = REDIRECT_REALM_START;
for ( const rules of redirectRulesets ) {
if ( Array.isArray(rules) === false ) { continue; }
for ( const rule of rules ) {
rule.id = redirectRuleId++;
newRules.push(rule);
}
}
}
// Add redirect rules to dynamic ruleset without affecting rules
// outside redirect 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 < REDIRECT_REALM_START ) { continue; }
if ( oldRule.id >= REDIRECT_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 });
}
/******************************************************************************/
async function updateDynamicRules() { async function updateDynamicRules() {
return Promise.all([ return Promise.all([
updateRegexRules(), updateRegexRules(),
updateRemoveparamRules(), updateRemoveparamRules(),
updateRedirectRules(),
]); ]);
} }
/******************************************************************************/ /******************************************************************************/
async function defaultRulesetsFromLanguage() { async function defaultRulesetsFromLanguage() {
const out = [ 'default' ]; const out = [ 'default', 'cname-trackers' ];
const dropCountry = lang => { const dropCountry = lang => {
const pos = lang.indexOf('-'); const pos = lang.indexOf('-');
@ -335,10 +410,7 @@ async function enableRulesets(ids) {
} }
await dnr.updateEnabledRulesets({ enableRulesetIds, disableRulesetIds }); await dnr.updateEnabledRulesets({ enableRulesetIds, disableRulesetIds });
return Promise.all([ return updateDynamicRules();
updateRegexRules(),
updateRemoveparamRules(),
]);
} }
/******************************************************************************/ /******************************************************************************/

View File

@ -47,9 +47,9 @@ function rulesetStats(rulesetId) {
const rulesetDetails = rulesetMap.get(rulesetId); const rulesetDetails = rulesetMap.get(rulesetId);
if ( rulesetDetails === undefined ) { return; } if ( rulesetDetails === undefined ) { return; }
const { rules, filters } = rulesetDetails; const { rules, filters } = rulesetDetails;
let ruleCount = rules.plain + rules.regexes; let ruleCount = rules.plain + rules.regex;
if ( canRemoveParams ) { if ( canRemoveParams ) {
ruleCount += rules.removeparams; ruleCount += rules.removeparam + rules.redirect;
} }
const filterCount = filters.accepted; const filterCount = filters.accepted;
return { ruleCount, filterCount }; return { ruleCount, filterCount };

View File

@ -37,5 +37,6 @@
"scripting" "scripting"
], ],
"short_name": "uBO Lite", "short_name": "uBO Lite",
"version": "0.1" "version": "0.1",
"web_accessible_resources": []
} }

View File

@ -28,6 +28,7 @@ import https from 'https';
import path from 'path'; import path from 'path';
import process from 'process'; import process from 'process';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import redirectResourcesMap from './js/redirect-resources.js';
import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js';
import { StaticFilteringParser } from './js/static-filtering-parser.js'; import { StaticFilteringParser } from './js/static-filtering-parser.js';
import { fnameFromFileId } from './js/utils.js'; import { fnameFromFileId } from './js/utils.js';
@ -142,6 +143,14 @@ const writeFile = async (fname, data) => {
return promise; return promise;
}; };
const copyFile = async (from, to) => {
const dir = path.dirname(to);
await fs.mkdir(dir, { recursive: true });
const promise = fs.copyFile(from, to);
writeOps.push(promise);
return promise;
};
const writeOps = []; const writeOps = [];
/******************************************************************************/ /******************************************************************************/
@ -153,6 +162,7 @@ const proceduralDetails = new Map();
const scriptletStats = new Map(); const scriptletStats = new Map();
const specificDetails = new Map(); const specificDetails = new Map();
const genericDetails = new Map(); const genericDetails = new Map();
const requiredRedirectResources = new Set();
/******************************************************************************/ /******************************************************************************/
@ -265,7 +275,12 @@ async function processNetworkFilters(assetDetails, network) {
isUnsupported(rule) === false && isUnsupported(rule) === false &&
isRedirect(rule) isRedirect(rule)
); );
log(`\tredirect-rule= (discarded): ${redirects.length}`); redirects.forEach(rule => {
requiredRedirectResources.add(
rule.action.redirect.extensionPath.replace(/^\/+/, '')
);
});
log(`\tredirect=: ${redirects.length}`);
const headers = rules.filter(rule => const headers = rules.filter(rule =>
isUnsupported(rule) === false && isUnsupported(rule) === false &&
@ -294,25 +309,33 @@ async function processNetworkFilters(assetDetails, network) {
if ( regexes.length !== 0 ) { if ( regexes.length !== 0 ) {
writeFile( writeFile(
`${rulesetDir}/regex/${assetDetails.id}.regexes.json`, `${rulesetDir}/regex/${assetDetails.id}.json`,
`${JSON.stringify(regexes, replacer)}\n` `${JSON.stringify(regexes, replacer)}\n`
); );
} }
if ( removeparamsGood.length !== 0 ) { if ( removeparamsGood.length !== 0 ) {
writeFile( writeFile(
`${rulesetDir}/removeparam/${assetDetails.id}.removeparams.json`, `${rulesetDir}/removeparam/${assetDetails.id}.json`,
`${JSON.stringify(removeparamsGood, replacer)}\n` `${JSON.stringify(removeparamsGood, replacer)}\n`
); );
} }
if ( redirects.length !== 0 ) {
writeFile(
`${rulesetDir}/redirect/${assetDetails.id}.json`,
`${JSON.stringify(redirects, replacer)}\n`
);
}
return { return {
total: rules.length, total: rules.length,
plain: plainGood.length, plain: plainGood.length,
discarded: redirects.length + headers.length + removeparamsBad.length, discarded: redirects.length + headers.length + removeparamsBad.length,
rejected: bad.length, rejected: bad.length,
regexes: regexes.length, regex: regexes.length,
removeparams: removeparamsGood.length, removeparam: removeparamsGood.length,
redirect: redirects.length,
}; };
} }
@ -902,9 +925,25 @@ async function rulesetFromURLs(assetDetails) {
assetDetails.text = text; assetDetails.text = text;
} }
const extensionPaths = [];
for ( const [ fname, details ] of redirectResourcesMap ) {
const path = `/web_accessible_resources/${fname}`;
extensionPaths.push([ fname, path ]);
if ( details.alias === undefined ) { continue; }
if ( typeof details.alias === 'string' ) {
extensionPaths.push([ details.alias, path ]);
continue;
}
if ( Array.isArray(details.alias) === false ) { continue; }
for ( const alias of details.alias ) {
extensionPaths.push([ alias, path ]);
}
}
const results = await dnrRulesetFromRawLists( const results = await dnrRulesetFromRawLists(
[ { name: assetDetails.id, text: assetDetails.text } ], [ { name: assetDetails.id, text: assetDetails.text } ],
{ env } { env, extensionPaths }
); );
const netStats = await processNetworkFilters( const netStats = await processNetworkFilters(
@ -972,8 +1011,9 @@ async function rulesetFromURLs(assetDetails) {
rules: { rules: {
total: netStats.total, total: netStats.total,
plain: netStats.plain, plain: netStats.plain,
regexes: netStats.regexes, regex: netStats.regex,
removeparams: netStats.removeparams, removeparam: netStats.removeparam,
redirect: netStats.redirect,
discarded: netStats.discarded, discarded: netStats.discarded,
rejected: netStats.rejected, rejected: netStats.rejected,
}, },
@ -1113,6 +1153,7 @@ async function main() {
urls: [ 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/combined_disguised_trackers.txt' ], urls: [ 'https://raw.githubusercontent.com/AdguardTeam/cname-trackers/master/combined_disguised_trackers.txt' ],
homeURL: 'https://github.com/AdguardTeam/cname-trackers#cname-cloaked-trackers', homeURL: 'https://github.com/AdguardTeam/cname-trackers#cname-cloaked-trackers',
}); });
await rulesetFromURLs({ await rulesetFromURLs({
id: 'stevenblack-hosts', id: 'stevenblack-hosts',
name: 'Steven Black\'s hosts file', name: 'Steven Black\'s hosts file',
@ -1159,16 +1200,30 @@ async function main() {
`${JSON.stringify(genericDetails, jsonSetMapReplacer, 1)}\n` `${JSON.stringify(genericDetails, jsonSetMapReplacer, 1)}\n`
); );
// Copy required redirect resources
for ( const path of requiredRedirectResources ) {
copyFile(`./${path}`, `${outputDir}/${path}`);
}
await Promise.all(writeOps); await Promise.all(writeOps);
// Patch manifest // Patch manifest
// Patch declarative_net_request key
manifest.declarative_net_request = { rule_resources: ruleResources }; manifest.declarative_net_request = { rule_resources: ruleResources };
// Patch web_accessible_resources key
manifest.web_accessible_resources = [{
resources: Array.from(requiredRedirectResources).map(path => `/${path}`),
matches: [ '<all_urls>' ],
use_dynamic_url: true,
}];
// Patch version key
const now = new Date(); const now = new Date();
const yearPart = now.getUTCFullYear() - 2000; const yearPart = now.getUTCFullYear() - 2000;
const monthPart = (now.getUTCMonth() + 1) * 1000; const monthPart = (now.getUTCMonth() + 1) * 1000;
const dayPart = now.getUTCDate() * 10; const dayPart = now.getUTCDate() * 10;
const hourPart = Math.floor(now.getUTCHours() / 3) + 1; const hourPart = Math.floor(now.getUTCHours() / 3) + 1;
manifest.version = manifest.version + `.${yearPart}.${monthPart + dayPart + hourPart}`; manifest.version = manifest.version + `.${yearPart}.${monthPart + dayPart + hourPart}`;
// Commit changes
await fs.writeFile( await fs.writeFile(
`${outputDir}/manifest.json`, `${outputDir}/manifest.json`,
JSON.stringify(manifest, null, 2) + '\n' JSON.stringify(manifest, null, 2) + '\n'

View File

@ -23,6 +23,8 @@
/******************************************************************************/ /******************************************************************************/
import redirectableResources from './redirect-resources.js';
import { import {
LineIterator, LineIterator,
orphanizeString, orphanizeString,
@ -30,165 +32,6 @@ import {
/******************************************************************************/ /******************************************************************************/
// The resources referenced below are found in ./web_accessible_resources/
//
// The content of the resources which declare a `data` property will be loaded
// in memory, and converted to a suitable internal format depending on the
// type of the loaded data. The `data` property allows for manual injection
// through `+js(...)`, or for redirection to a data: URI when a redirection
// to a web accessible resource is not desirable.
const redirectableResources = new Map([
[ '1x1.gif', {
alias: '1x1-transparent.gif',
data: 'blob',
} ],
[ '2x2.png', {
alias: '2x2-transparent.png',
data: 'blob',
} ],
[ '3x2.png', {
alias: '3x2-transparent.png',
data: 'blob',
} ],
[ '32x32.png', {
alias: '32x32-transparent.png',
data: 'blob',
} ],
[ 'addthis_widget.js', {
alias: 'addthis.com/addthis_widget.js',
} ],
[ 'amazon_ads.js', {
alias: 'amazon-adsystem.com/aax2/amzn_ads.js',
data: 'text',
} ],
[ 'amazon_apstag.js', {
} ],
[ 'ampproject_v0.js', {
alias: 'ampproject.org/v0.js',
} ],
[ 'chartbeat.js', {
alias: 'static.chartbeat.com/chartbeat.js',
} ],
[ 'click2load.html', {
params: [ 'aliasURL', 'url' ],
} ],
[ 'doubleclick_instream_ad_status.js', {
alias: 'doubleclick.net/instream/ad_status.js',
data: 'text',
} ],
[ 'empty', {
data: 'text', // Important!
} ],
[ 'fingerprint2.js', {
data: 'text',
} ],
[ 'fingerprint3.js', {
data: 'text',
} ],
[ 'google-analytics_analytics.js', {
alias: [
'google-analytics.com/analytics.js',
'googletagmanager_gtm.js',
'googletagmanager.com/gtm.js'
],
data: 'text',
} ],
[ 'google-analytics_cx_api.js', {
alias: 'google-analytics.com/cx/api.js',
} ],
[ 'google-analytics_ga.js', {
alias: 'google-analytics.com/ga.js',
data: 'text',
} ],
[ 'google-analytics_inpage_linkid.js', {
alias: 'google-analytics.com/inpage_linkid.js',
} ],
[ 'google-ima.js', {
} ],
[ 'googlesyndication_adsbygoogle.js', {
alias: 'googlesyndication.com/adsbygoogle.js',
data: 'text',
} ],
[ 'googletagservices_gpt.js', {
alias: 'googletagservices.com/gpt.js',
data: 'text',
} ],
[ 'hd-main.js', {
} ],
[ 'ligatus_angular-tag.js', {
alias: 'ligatus.com/*/angular-tag.js',
} ],
[ 'mxpnl_mixpanel.js', {
} ],
[ 'monkeybroker.js', {
alias: 'd3pkae9owd2lcf.cloudfront.net/mb105.js',
} ],
[ 'noeval.js', {
data: 'text',
} ],
[ 'noeval-silent.js', {
alias: 'silent-noeval.js',
data: 'text',
} ],
[ 'nobab.js', {
alias: 'bab-defuser.js',
data: 'text',
} ],
[ 'nobab2.js', {
data: 'text',
} ],
[ 'nofab.js', {
alias: 'fuckadblock.js-3.2.0',
data: 'text',
} ],
[ 'noop-0.1s.mp3', {
alias: [ 'noopmp3-0.1s', 'abp-resource:blank-mp3' ],
data: 'blob',
} ],
[ 'noop-0.5s.mp3', {
} ],
[ 'noop-1s.mp4', {
alias: 'noopmp4-1s',
data: 'blob',
} ],
[ 'noop.html', {
alias: 'noopframe',
} ],
[ 'noop.js', {
alias: [ 'noopjs', 'abp-resource:blank-js' ],
data: 'text',
} ],
[ 'noop.txt', {
alias: 'nooptext',
data: 'text',
} ],
[ 'noop-vmap1.0.xml', {
alias: 'noopvmap-1.0',
data: 'text',
} ],
[ 'outbrain-widget.js', {
alias: 'widgets.outbrain.com/outbrain.js',
} ],
[ 'popads.js', {
alias: 'popads.net.js',
data: 'text',
} ],
[ 'popads-dummy.js', {
data: 'text',
} ],
[ 'prebid-ads.js', {
data: 'text',
} ],
[ 'scorecardresearch_beacon.js', {
alias: 'scorecardresearch.com/beacon.js',
} ],
[ 'window.open-defuser.js', {
alias: 'nowoif.js',
data: 'text',
} ],
]);
const extToMimeMap = new Map([ const extToMimeMap = new Map([
[ 'gif', 'image/gif' ], [ 'gif', 'image/gif' ],
[ 'html', 'text/html' ], [ 'html', 'text/html' ],

View File

@ -0,0 +1,183 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
'use strict';
/******************************************************************************/
// The resources referenced below are found in ./web_accessible_resources/
//
// The content of the resources which declare a `data` property will be loaded
// in memory, and converted to a suitable internal format depending on the
// type of the loaded data. The `data` property allows for manual injection
// through `+js(...)`, or for redirection to a data: URI when a redirection
// to a web accessible resource is not desirable.
export default new Map([
[ '1x1.gif', {
alias: '1x1-transparent.gif',
data: 'blob',
} ],
[ '2x2.png', {
alias: '2x2-transparent.png',
data: 'blob',
} ],
[ '3x2.png', {
alias: '3x2-transparent.png',
data: 'blob',
} ],
[ '32x32.png', {
alias: '32x32-transparent.png',
data: 'blob',
} ],
[ 'addthis_widget.js', {
alias: 'addthis.com/addthis_widget.js',
} ],
[ 'amazon_ads.js', {
alias: 'amazon-adsystem.com/aax2/amzn_ads.js',
data: 'text',
} ],
[ 'amazon_apstag.js', {
} ],
[ 'ampproject_v0.js', {
alias: 'ampproject.org/v0.js',
} ],
[ 'chartbeat.js', {
alias: 'static.chartbeat.com/chartbeat.js',
} ],
[ 'click2load.html', {
params: [ 'aliasURL', 'url' ],
} ],
[ 'doubleclick_instream_ad_status.js', {
alias: 'doubleclick.net/instream/ad_status.js',
data: 'text',
} ],
[ 'empty', {
data: 'text', // Important!
} ],
[ 'fingerprint2.js', {
data: 'text',
} ],
[ 'fingerprint3.js', {
data: 'text',
} ],
[ 'google-analytics_analytics.js', {
alias: [
'google-analytics.com/analytics.js',
'googletagmanager_gtm.js',
'googletagmanager.com/gtm.js'
],
data: 'text',
} ],
[ 'google-analytics_cx_api.js', {
alias: 'google-analytics.com/cx/api.js',
} ],
[ 'google-analytics_ga.js', {
alias: 'google-analytics.com/ga.js',
data: 'text',
} ],
[ 'google-analytics_inpage_linkid.js', {
alias: 'google-analytics.com/inpage_linkid.js',
} ],
[ 'google-ima.js', {
} ],
[ 'googlesyndication_adsbygoogle.js', {
alias: 'googlesyndication.com/adsbygoogle.js',
data: 'text',
} ],
[ 'googletagservices_gpt.js', {
alias: 'googletagservices.com/gpt.js',
data: 'text',
} ],
[ 'hd-main.js', {
} ],
[ 'ligatus_angular-tag.js', {
alias: 'ligatus.com/*/angular-tag.js',
} ],
[ 'mxpnl_mixpanel.js', {
} ],
[ 'monkeybroker.js', {
alias: 'd3pkae9owd2lcf.cloudfront.net/mb105.js',
} ],
[ 'noeval.js', {
data: 'text',
} ],
[ 'noeval-silent.js', {
alias: 'silent-noeval.js',
data: 'text',
} ],
[ 'nobab.js', {
alias: 'bab-defuser.js',
data: 'text',
} ],
[ 'nobab2.js', {
data: 'text',
} ],
[ 'nofab.js', {
alias: 'fuckadblock.js-3.2.0',
data: 'text',
} ],
[ 'noop-0.1s.mp3', {
alias: [ 'noopmp3-0.1s', 'abp-resource:blank-mp3' ],
data: 'blob',
} ],
[ 'noop-0.5s.mp3', {
} ],
[ 'noop-1s.mp4', {
alias: 'noopmp4-1s',
data: 'blob',
} ],
[ 'noop.html', {
alias: 'noopframe',
} ],
[ 'noop.js', {
alias: [ 'noopjs', 'abp-resource:blank-js' ],
data: 'text',
} ],
[ 'noop.txt', {
alias: 'nooptext',
data: 'text',
} ],
[ 'noop-vmap1.0.xml', {
alias: 'noopvmap-1.0',
data: 'text',
} ],
[ 'outbrain-widget.js', {
alias: 'widgets.outbrain.com/outbrain.js',
} ],
[ 'popads.js', {
alias: 'popads.net.js',
data: 'text',
} ],
[ 'popads-dummy.js', {
data: 'text',
} ],
[ 'prebid-ads.js', {
data: 'text',
} ],
[ 'scorecardresearch_beacon.js', {
alias: 'scorecardresearch.com/beacon.js',
} ],
[ 'window.open-defuser.js', {
alias: 'nowoif.js',
data: 'text',
} ],
]);

View File

@ -219,6 +219,9 @@ function addToDNR(context, list) {
}); });
const compiler = staticNetFilteringEngine.createCompiler(parser); const compiler = staticNetFilteringEngine.createCompiler(parser);
// Can't enforce `redirect-rule=` with DNR
compiler.excludeOptions([ parser.OPTTokenRedirectRule ]);
writer.properties.set('name', list.name); writer.properties.set('name', list.name);
compiler.start(writer); compiler.start(writer);

View File

@ -1742,8 +1742,7 @@ const FilterOriginEntityHit = class extends FilterOriginHit {
} }
static dnrFromCompiled(args, rule) { static dnrFromCompiled(args, rule) {
dnrAddRuleError(rule, `Entity not supported: ${args[1]}`); dnrAddRuleError(rule, `FilterOriginEntityHit: Entity ${args[1]} not supported`);
super.dnrFromCompiled(args, rule);
} }
}; };
@ -1761,8 +1760,7 @@ const FilterOriginEntityMiss = class extends FilterOriginMiss {
} }
static dnrFromCompiled(args, rule) { static dnrFromCompiled(args, rule) {
dnrAddRuleError(rule, `Entity not supported: ${args[1]}`); dnrAddRuleError(rule, `FilterOriginEntityMiss: Entity ${args[1]} not supported`);
super.dnrFromCompiled(args, rule);
} }
}; };
@ -2623,7 +2621,7 @@ const FilterStrictParty = class {
static dnrFromCompiled(args, rule) { static dnrFromCompiled(args, rule) {
const partyness = args[1] === 0 ? 1 : 3; const partyness = args[1] === 0 ? 1 : 3;
dnrAddRuleError(rule, `Strict partyness not supported: strict${partyness}p`); dnrAddRuleError(rule, `FilterStrictParty: Strict partyness strict${partyness}p not supported`);
} }
static keyFromArgs(args) { static keyFromArgs(args) {
@ -2891,6 +2889,7 @@ class FilterCompiler {
[ parser.OPTTokenWebrtc, bitFromType('unsupported') ], [ parser.OPTTokenWebrtc, bitFromType('unsupported') ],
[ parser.OPTTokenWebsocket, bitFromType('websocket') ], [ parser.OPTTokenWebsocket, bitFromType('websocket') ],
]); ]);
this.excludedOptionSet = new Set();
// These top 100 "bad tokens" are collated using the "miss" histogram // These top 100 "bad tokens" are collated using the "miss" histogram
// from tokenHistograms(). The "score" is their occurrence among the // from tokenHistograms(). The "score" is their occurrence among the
// 200K+ URLs used in the benchmark and executed against default // 200K+ URLs used in the benchmark and executed against default
@ -3053,6 +3052,12 @@ class FilterCompiler {
return ''; return '';
} }
excludeOptions(options) {
for ( const option of options ) {
this.excludedOptionSet.add(option);
}
}
// https://github.com/chrisaljoudi/uBlock/issues/589 // https://github.com/chrisaljoudi/uBlock/issues/589
// Be ready to handle multiple negated types // Be ready to handle multiple negated types
@ -3109,30 +3114,31 @@ class FilterCompiler {
} }
processOptions() { processOptions() {
for ( let { id, val, not } of this.parser.netOptions() ) { const { parser } = this;
for ( let { id, val, not } of parser.netOptions() ) {
switch ( id ) { switch ( id ) {
case this.parser.OPTToken1p: case parser.OPTToken1p:
this.processPartyOption(true, not); this.processPartyOption(true, not);
break; break;
case this.parser.OPTToken1pStrict: case parser.OPTToken1pStrict:
this.strictParty = this.strictParty === -1 ? 0 : 1; this.strictParty = this.strictParty === -1 ? 0 : 1;
this.optionUnitBits |= this.STRICT_PARTY_BIT; this.optionUnitBits |= this.STRICT_PARTY_BIT;
break; break;
case this.parser.OPTToken3p: case parser.OPTToken3p:
this.processPartyOption(false, not); this.processPartyOption(false, not);
break; break;
case this.parser.OPTToken3pStrict: case parser.OPTToken3pStrict:
this.strictParty = this.strictParty === 1 ? 0 : -1; this.strictParty = this.strictParty === 1 ? 0 : -1;
this.optionUnitBits |= this.STRICT_PARTY_BIT; this.optionUnitBits |= this.STRICT_PARTY_BIT;
break; break;
case this.parser.OPTTokenAll: case parser.OPTTokenAll:
this.processTypeOption(-1); this.processTypeOption(-1);
break; break;
// https://github.com/uBlockOrigin/uAssets/issues/192 // https://github.com/uBlockOrigin/uAssets/issues/192
case this.parser.OPTTokenBadfilter: case parser.OPTTokenBadfilter:
this.badFilter = true; this.badFilter = true;
break; break;
case this.parser.OPTTokenCsp: case parser.OPTTokenCsp:
if ( this.processModifierOption(id, val) === false ) { if ( this.processModifierOption(id, val) === false ) {
return false; return false;
} }
@ -3144,7 +3150,7 @@ class FilterCompiler {
// https://github.com/gorhill/uBlock/issues/2294 // https://github.com/gorhill/uBlock/issues/2294
// Detect and discard filter if domain option contains // Detect and discard filter if domain option contains
// nonsensical characters. // nonsensical characters.
case this.parser.OPTTokenDomain: case parser.OPTTokenDomain:
this.domainOpt = this.processHostnameList( this.domainOpt = this.processHostnameList(
val, val,
0b1010, 0b1010,
@ -3153,73 +3159,76 @@ class FilterCompiler {
if ( this.domainOpt === '' ) { return false; } if ( this.domainOpt === '' ) { return false; }
this.optionUnitBits |= this.DOMAIN_BIT; this.optionUnitBits |= this.DOMAIN_BIT;
break; break;
case this.parser.OPTTokenDenyAllow: case parser.OPTTokenDenyAllow:
this.denyallowOpt = this.processHostnameList(val, 0b0000); this.denyallowOpt = this.processHostnameList(val, 0b0000);
if ( this.denyallowOpt === '' ) { return false; } if ( this.denyallowOpt === '' ) { return false; }
this.optionUnitBits |= this.DENYALLOW_BIT; this.optionUnitBits |= this.DENYALLOW_BIT;
break; break;
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Add support for `elemhide`. Rarely used but it happens. // Add support for `elemhide`. Rarely used but it happens.
case this.parser.OPTTokenEhide: case parser.OPTTokenEhide:
this.processTypeOption(this.parser.OPTTokenShide, not); this.processTypeOption(parser.OPTTokenShide, not);
this.processTypeOption(this.parser.OPTTokenGhide, not); this.processTypeOption(parser.OPTTokenGhide, not);
break; break;
case this.parser.OPTTokenHeader: case parser.OPTTokenHeader:
this.headerOpt = val !== undefined ? val : ''; this.headerOpt = val !== undefined ? val : '';
this.optionUnitBits |= this.HEADER_BIT; this.optionUnitBits |= this.HEADER_BIT;
break; break;
case this.parser.OPTTokenImportant: case parser.OPTTokenImportant:
if ( this.action === AllowAction ) { return false; } if ( this.action === AllowAction ) { return false; }
this.optionUnitBits |= this.IMPORTANT_BIT; this.optionUnitBits |= this.IMPORTANT_BIT;
this.action = BlockImportant; this.action = BlockImportant;
break; break;
// Used by Adguard: // Used by Adguard:
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#empty-modifier // https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#empty-modifier
case this.parser.OPTTokenEmpty: case parser.OPTTokenEmpty:
id = this.action === AllowAction id = this.action === AllowAction
? this.parser.OPTTokenRedirectRule ? parser.OPTTokenRedirectRule
: this.parser.OPTTokenRedirect; : parser.OPTTokenRedirect;
if ( this.processModifierOption(id, 'empty') === false ) { if ( this.processModifierOption(id, 'empty') === false ) {
return false; return false;
} }
this.optionUnitBits |= this.REDIRECT_BIT; this.optionUnitBits |= this.REDIRECT_BIT;
break; break;
case this.parser.OPTTokenMatchCase: case parser.OPTTokenMatchCase:
this.patternMatchCase = true; this.patternMatchCase = true;
break; break;
case this.parser.OPTTokenMp4: case parser.OPTTokenMp4:
id = this.action === AllowAction id = this.action === AllowAction
? this.parser.OPTTokenRedirectRule ? parser.OPTTokenRedirectRule
: this.parser.OPTTokenRedirect; : parser.OPTTokenRedirect;
if ( this.processModifierOption(id, 'noopmp4-1s') === false ) { if ( this.processModifierOption(id, 'noopmp4-1s') === false ) {
return false; return false;
} }
this.optionUnitBits |= this.REDIRECT_BIT; this.optionUnitBits |= this.REDIRECT_BIT;
break; break;
case this.parser.OPTTokenNoop: case parser.OPTTokenNoop:
break; break;
case this.parser.OPTTokenRemoveparam: case parser.OPTTokenRemoveparam:
if ( this.processModifierOption(id, val) === false ) { if ( this.processModifierOption(id, val) === false ) {
return false; return false;
} }
this.optionUnitBits |= this.REMOVEPARAM_BIT; this.optionUnitBits |= this.REMOVEPARAM_BIT;
break; break;
case this.parser.OPTTokenRedirect: case parser.OPTTokenRedirect:
if ( this.action === AllowAction ) { if ( this.action === AllowAction ) {
id = this.parser.OPTTokenRedirectRule; id = parser.OPTTokenRedirectRule;
} }
if ( this.processModifierOption(id, val) === false ) { if ( this.processModifierOption(id, val) === false ) {
return false; return false;
} }
this.optionUnitBits |= this.REDIRECT_BIT; this.optionUnitBits |= this.REDIRECT_BIT;
break; break;
case this.parser.OPTTokenRedirectRule: case parser.OPTTokenRedirectRule:
if ( this.excludedOptionSet.has(parser.OPTTokenRedirectRule) ) {
return false;
}
if ( this.processModifierOption(id, val) === false ) { if ( this.processModifierOption(id, val) === false ) {
return false; return false;
} }
this.optionUnitBits |= this.REDIRECT_BIT; this.optionUnitBits |= this.REDIRECT_BIT;
break; break;
case this.parser.OPTTokenInvalid: case parser.OPTTokenInvalid:
return false; return false;
default: default:
if ( this.tokenIdToNormalizedType.has(id) === false ) { if ( this.tokenIdToNormalizedType.has(id) === false ) {
@ -4051,6 +4060,34 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) {
} }
} }
// Try to recover from errors for when the rule is still useful despite not
// being complete.
for ( const rule of ruleset ) {
if ( rule._error === undefined ) { continue; }
let i = rule._error.length;
while ( i-- ) {
const error = rule._error[i];
if ( error.startsWith('FilterOriginEntityHit:') ) {
if (
Array.isArray(rule.condition.initiatorDomains) &&
rule.condition.initiatorDomains.length > 0
) {
rule._error.splice(i, 1);
}
} else if ( error.startsWith('FilterOriginEntityMiss:') ) {
if (
Array.isArray(rule.condition.excludedInitiatorDomains) &&
rule.condition.excludedInitiatorDomains.length > 0
) {
rule._error.splice(i, 1);
}
}
}
if ( rule._error.length === 0 ) {
delete rule._error;
}
}
// Patch modifier filters // Patch modifier filters
for ( const rule of ruleset ) { for ( const rule of ruleset ) {
if ( rule.__modifierType === undefined ) { continue; } if ( rule.__modifierType === undefined ) { continue; }
@ -4067,10 +4104,12 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) {
} }
break; break;
case 'redirect-rule': { case 'redirect-rule': {
let priority = rule.priority || 0;
let token = rule.__modifierValue; let token = rule.__modifierValue;
if ( token !== '' ) { if ( token !== '' ) {
const match = /:\d+$/.exec(token); const match = /:(\d+)$/.exec(token);
if ( match !== null ) { if ( match !== null ) {
priority += parseInt(match[1], 10);
token = token.slice(0, match.index); token = token.slice(0, match.index);
} }
} }
@ -4078,14 +4117,14 @@ FilterContainer.prototype.dnrFromCompiled = function(op, context, ...args) {
if ( rule.__modifierValue !== '' && resource === undefined ) { if ( rule.__modifierValue !== '' && resource === undefined ) {
dnrAddRuleError(rule, `Unpatchable redirect filter: ${rule.__modifierValue}`); dnrAddRuleError(rule, `Unpatchable redirect filter: ${rule.__modifierValue}`);
} }
const extensionPath = resource && resource.extensionPath || token;
if ( rule.__modifierAction !== AllowAction ) { if ( rule.__modifierAction !== AllowAction ) {
const extensionPath = resource || token;
rule.action.type = 'redirect'; rule.action.type = 'redirect';
rule.action.redirect = { extensionPath }; rule.action.redirect = { extensionPath };
rule.priority = (rule.priority || 1) + 1; rule.priority = priority + 1;
} else { } else {
rule.action.type = 'block'; rule.action.type = 'block';
rule.priority = (rule.priority || 1) + 2; rule.priority = priority + 2;
} }
break; break;
} }

View File

@ -51,6 +51,8 @@ if [ "$1" != "quick" ]; then
cp platform/mv3/extension/js/utils.js $TMPDIR/js/ cp platform/mv3/extension/js/utils.js $TMPDIR/js/
cp assets/assets.json $TMPDIR/ cp assets/assets.json $TMPDIR/
cp -R platform/mv3/scriptlets $TMPDIR/ cp -R platform/mv3/scriptlets $TMPDIR/
mkdir -p $TMPDIR/web_accessible_resources
cp src/web_accessible_resources/* $TMPDIR/web_accessible_resources/
cd $TMPDIR cd $TMPDIR
node --no-warnings make-rulesets.js output=$DES node --no-warnings make-rulesets.js output=$DES
cd - > /dev/null cd - > /dev/null

View File

@ -13,6 +13,7 @@ cp src/js/dynamic-net-filtering.js $DES/js
cp src/js/filtering-context.js $DES/js cp src/js/filtering-context.js $DES/js
cp src/js/hnswitches.js $DES/js cp src/js/hnswitches.js $DES/js
cp src/js/hntrie.js $DES/js cp src/js/hntrie.js $DES/js
cp src/js/redirect-resources.js $DES/js
cp src/js/static-dnr-filtering.js $DES/js cp src/js/static-dnr-filtering.js $DES/js
cp src/js/static-filtering-parser.js $DES/js cp src/js/static-filtering-parser.js $DES/js
cp src/js/static-net-filtering.js $DES/js cp src/js/static-net-filtering.js $DES/js