Add support for `match-case` option; fine-tune behavior of `redirect=`

`match-case`
------------

Related issue:
- https://github.com/uBlockOrigin/uAssets/issues/8280#issuecomment-735245452

The new filter option `match-case` can be used only for
regex-based filters. Using `match-case` with any other
sort of filters will cause uBO to discard the filter.

`redirect=`
-----------

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1366

`redirect=` filters with unresolvable resource token at
runtime will be discarded.

Additionally, the implicit priority is now set to 1
(was 0). The idea is to allow custom `redirect=` filters
to be used strictly as fallback `redirect=` filters in case
another `redirect=` filter is not picked up.

For example, one might create a `redirect=click2load.html:0`
filter, to be taken if and only if the blocked resource is
not already being redirected by another "official" filter
in one of the enabled filter lists.
This commit is contained in:
Raymond Hill 2020-11-28 11:26:28 -05:00
parent c6d0204b23
commit eae7cd58fe
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
4 changed files with 95 additions and 57 deletions

View File

@ -140,8 +140,8 @@ const µBlock = (( ) => { // jshint ignore:line
// Read-only // Read-only
systemSettings: { systemSettings: {
compiledMagic: 35, // Increase when compiled format changes compiledMagic: 36, // Increase when compiled format changes
selfieMagic: 35, // Increase when selfie format changes selfieMagic: 36, // Increase when selfie format changes
}, },
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501

View File

@ -304,6 +304,16 @@ RedirectEngine.prototype.tokenToURL = function(fctxt, token) {
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.hasToken = function(token) {
const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */;
if ( asDataURI ) {
token = token.slice(1);
}
return this.resources.get(this.aliases.get(token) || token) !== undefined;
};
/******************************************************************************/
RedirectEngine.prototype.toSelfie = async function() { RedirectEngine.prototype.toSelfie = async function() {
}; };

View File

@ -1923,22 +1923,23 @@ const OPTTokenImage = 20;
const OPTTokenImportant = 21; const OPTTokenImportant = 21;
const OPTTokenInlineFont = 22; const OPTTokenInlineFont = 22;
const OPTTokenInlineScript = 23; const OPTTokenInlineScript = 23;
const OPTTokenMedia = 24; const OPTTokenMatchCase = 24;
const OPTTokenMp4 = 25; const OPTTokenMedia = 25;
const OPTTokenObject = 26; const OPTTokenMp4 = 26;
const OPTTokenOther = 27; const OPTTokenObject = 27;
const OPTTokenPing = 28; const OPTTokenOther = 28;
const OPTTokenPopunder = 29; const OPTTokenPing = 29;
const OPTTokenPopup = 30; const OPTTokenPopunder = 30;
const OPTTokenRedirect = 31; const OPTTokenPopup = 31;
const OPTTokenRedirectRule = 32; const OPTTokenRedirect = 32;
const OPTTokenQueryprune = 33; const OPTTokenRedirectRule = 33;
const OPTTokenScript = 34; const OPTTokenQueryprune = 34;
const OPTTokenShide = 35; const OPTTokenScript = 35;
const OPTTokenXhr = 36; const OPTTokenShide = 36;
const OPTTokenWebrtc = 37; const OPTTokenXhr = 37;
const OPTTokenWebsocket = 38; const OPTTokenWebrtc = 38;
const OPTTokenCount = 39; const OPTTokenWebsocket = 39;
const OPTTokenCount = 40;
//const OPTPerOptionMask = 0x0000ff00; //const OPTPerOptionMask = 0x0000ff00;
const OPTCanNegate = 1 << 8; const OPTCanNegate = 1 << 8;
@ -2021,6 +2022,7 @@ Parser.prototype.OPTTokenImportant = OPTTokenImportant;
Parser.prototype.OPTTokenInlineFont = OPTTokenInlineFont; Parser.prototype.OPTTokenInlineFont = OPTTokenInlineFont;
Parser.prototype.OPTTokenInlineScript = OPTTokenInlineScript; Parser.prototype.OPTTokenInlineScript = OPTTokenInlineScript;
Parser.prototype.OPTTokenInvalid = OPTTokenInvalid; Parser.prototype.OPTTokenInvalid = OPTTokenInvalid;
Parser.prototype.OPTTokenMatchCase = OPTTokenMatchCase;
Parser.prototype.OPTTokenMedia = OPTTokenMedia; Parser.prototype.OPTTokenMedia = OPTTokenMedia;
Parser.prototype.OPTTokenMp4 = OPTTokenMp4; Parser.prototype.OPTTokenMp4 = OPTTokenMp4;
Parser.prototype.OPTTokenObject = OPTTokenObject; Parser.prototype.OPTTokenObject = OPTTokenObject;
@ -2082,6 +2084,7 @@ const netOptionTokenDescriptors = new Map([
[ 'important', OPTTokenImportant | OPTBlockOnly ], [ 'important', OPTTokenImportant | OPTBlockOnly ],
[ 'inline-font', OPTTokenInlineFont | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ], [ 'inline-font', OPTTokenInlineFont | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ],
[ 'inline-script', OPTTokenInlineScript | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ], [ 'inline-script', OPTTokenInlineScript | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ],
[ 'match-case', OPTTokenMatchCase ],
[ 'media', OPTTokenMedia | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ], [ 'media', OPTTokenMedia | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
[ 'mp4', OPTTokenMp4 | OPTNetworkType | OPTBlockOnly | OPTModifierType ], [ 'mp4', OPTTokenMp4 | OPTNetworkType | OPTBlockOnly | OPTModifierType ],
[ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ], [ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
@ -2138,6 +2141,7 @@ Parser.netOptionTokenIds = new Map([
[ 'important', OPTTokenImportant ], [ 'important', OPTTokenImportant ],
[ 'inline-font', OPTTokenInlineFont ], [ 'inline-font', OPTTokenInlineFont ],
[ 'inline-script', OPTTokenInlineScript ], [ 'inline-script', OPTTokenInlineScript ],
[ 'match-case', OPTTokenMatchCase ],
[ 'media', OPTTokenMedia ], [ 'media', OPTTokenMedia ],
[ 'mp4', OPTTokenMp4 ], [ 'mp4', OPTTokenMp4 ],
[ 'object', OPTTokenObject ], [ 'object', OPTTokenObject ],
@ -2184,6 +2188,7 @@ Parser.netOptionTokenNames = new Map([
[ OPTTokenImportant, 'important' ], [ OPTTokenImportant, 'important' ],
[ OPTTokenInlineFont, 'inline-font' ], [ OPTTokenInlineFont, 'inline-font' ],
[ OPTTokenInlineScript, 'inline-script' ], [ OPTTokenInlineScript, 'inline-script' ],
[ OPTTokenMatchCase, 'match-case' ],
[ OPTTokenMedia, 'media' ], [ OPTTokenMedia, 'media' ],
[ OPTTokenMp4, 'mp4' ], [ OPTTokenMp4, 'mp4' ],
[ OPTTokenObject, 'object' ], [ OPTTokenObject, 'object' ],
@ -2462,6 +2467,16 @@ const NetOptionsIterator = class {
} }
} }
} }
// `match-case`: valid only for regex-based filters
{
const i = this.tokenPos[OPTTokenMatchCase];
if ( i !== -1 && this.parser.patternIsRegex() === false ) {
optSlices[i] = OPTTokenInvalid;
if ( this.interactive ) {
this.parser.errorSlices(optSlices[i+1], optSlices[i+5]);
}
}
}
return this; return this;
} }
next() { next() {

View File

@ -146,6 +146,7 @@ const typeValueToTypeName = [
// valid until the next evaluation. // valid until the next evaluation.
let $requestURL = ''; let $requestURL = '';
let $requestURLRaw = '';
let $requestHostname = ''; let $requestHostname = '';
let $docHostname = ''; let $docHostname = '';
let $docDomain = ''; let $docDomain = '';
@ -867,9 +868,13 @@ const FilterPatternGeneric = class {
} }
static compile(details) { static compile(details) {
const anchor = details.anchor; const out = [
FilterPatternGeneric.fid,
details.pattern,
details.anchor,
];
details.anchor = 0; details.anchor = 0;
return [ FilterPatternGeneric.fid, details.pattern, anchor ]; return out;
} }
static fromCompiled(args) { static fromCompiled(args) {
@ -1107,20 +1112,22 @@ registerFilterClass(FilterTrailingSeparator);
/******************************************************************************/ /******************************************************************************/
const FilterRegex = class { const FilterRegex = class {
constructor(s) { constructor(s, matchCase = false) {
this.s = s; this.s = s;
if ( matchCase ) {
this.matchCase = true;
}
} }
match() { match() {
if ( this.re === null ) { if ( this.re === null ) {
this.re = FilterRegex.dict.get(this.s); this.re = new RegExp(
if ( this.re === undefined ) { this.s,
this.re = new RegExp(this.s, 'i'); this.matchCase ? '' : 'i'
FilterRegex.dict.set(this.s, this.re); );
}
} }
if ( this.re.test($requestURL) === false ) { return false; } if ( this.re.test($requestURLRaw) === false ) { return false; }
$patternMatchLeft = $requestURL.search(this.re); $patternMatchLeft = $requestURLRaw.search(this.re);
return true; return true;
} }
@ -1128,33 +1135,36 @@ const FilterRegex = class {
details.pattern.push('/', this.s, '/'); details.pattern.push('/', this.s, '/');
details.regex.push(this.s); details.regex.push(this.s);
details.isRegex = true; details.isRegex = true;
if ( this.matchCase ) {
details.options.push('match-case');
}
} }
toSelfie() { toSelfie() {
return [ this.fid, this.s ]; return [ this.fid, this.s, this.matchCase ];
} }
static compile(details) { static compile(details) {
return [ FilterRegex.fid, details.pattern ]; return [ FilterRegex.fid, details.pattern, details.patternMatchCase ];
} }
static fromCompiled(args) { static fromCompiled(args) {
return new FilterRegex(args[1]); return new FilterRegex(args[1], args[2]);
} }
static fromSelfie(args) { static fromSelfie(args) {
return new FilterRegex(args[1]); return new FilterRegex(args[1], args[2]);
} }
static keyFromArgs(args) { static keyFromArgs(args) {
return args[1]; return `${args[1]}\t${args[2]}`;
} }
}; };
FilterRegex.prototype.re = null; FilterRegex.prototype.re = null;
FilterRegex.prototype.matchCase = false;
FilterRegex.isSlow = true; FilterRegex.isSlow = true;
FilterRegex.dict = new Map();
registerFilterClass(FilterRegex); registerFilterClass(FilterRegex);
@ -2783,6 +2793,7 @@ const FilterParser = class {
this.modifyValue = undefined; this.modifyValue = undefined;
this.invalid = false; this.invalid = false;
this.pattern = ''; this.pattern = '';
this.patternMatchCase = false;
this.party = AnyParty; this.party = AnyParty;
this.optionUnitBits = 0; this.optionUnitBits = 0;
this.domainOpt = ''; this.domainOpt = '';
@ -2944,6 +2955,9 @@ const FilterParser = class {
} }
this.optionUnitBits |= this.REDIRECT_BIT; this.optionUnitBits |= this.REDIRECT_BIT;
break; break;
case this.parser.OPTTokenMatchCase:
this.patternMatchCase = true;
break;
case this.parser.OPTTokenMp4: case this.parser.OPTTokenMp4:
id = this.action === AllowAction id = this.action === AllowAction
? this.parser.OPTTokenRedirectRule ? this.parser.OPTTokenRedirectRule
@ -3833,6 +3847,7 @@ FilterContainer.prototype.matchAndFetchModifiers = function(
modifierType modifierType
) { ) {
$requestURL = urlTokenizer.setURL(fctxt.url); $requestURL = urlTokenizer.setURL(fctxt.url);
$requestURLRaw = fctxt.url;
$docHostname = fctxt.getDocHostname(); $docHostname = fctxt.getDocHostname();
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$docEntity.reset(); $docEntity.reset();
@ -4126,6 +4141,7 @@ FilterContainer.prototype.matchStringReverse = function(type, url) {
// Prime tokenizer: we get a normalized URL in return. // Prime tokenizer: we get a normalized URL in return.
$requestURL = urlTokenizer.setURL(url); $requestURL = urlTokenizer.setURL(url);
$requestURLRaw = url;
this.$filterUnit = 0; this.$filterUnit = 0;
// These registers will be used by various filters // These registers will be used by various filters
@ -4172,6 +4188,7 @@ FilterContainer.prototype.matchString = function(fctxt, modifiers = 0) {
// Prime tokenizer: we get a normalized URL in return. // Prime tokenizer: we get a normalized URL in return.
$requestURL = urlTokenizer.setURL(fctxt.url); $requestURL = urlTokenizer.setURL(fctxt.url);
$requestURLRaw = fctxt.url;
this.$filterUnit = 0; this.$filterUnit = 0;
// These registers will be used by various filters // These registers will be used by various filters
@ -4203,6 +4220,7 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) {
// Prime tokenizer: we get a normalized URL in return. // Prime tokenizer: we get a normalized URL in return.
$requestURL = urlTokenizer.setURL(fctxt.url); $requestURL = urlTokenizer.setURL(fctxt.url);
$requestURLRaw = fctxt.url;
this.$filterUnit = 0; this.$filterUnit = 0;
// These registers will be used by various filters // These registers will be used by various filters
@ -4239,13 +4257,9 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
const directive = directives[0]; const directive = directives[0];
if ( (directive.bits & AllowAction) !== 0 ) { return directive; } if ( (directive.bits & AllowAction) !== 0 ) { return directive; }
const modifier = directive.modifier; const modifier = directive.modifier;
if ( modifier.cache === undefined ) { const { token } = this.parseRedirectRequestValue(modifier);
modifier.cache = this.parseRedirectRequestValue(modifier.value); fctxt.redirectURL = µb.redirectEngine.tokenToURL(fctxt, token);
} if ( fctxt.redirectURL === undefined ) { return; }
fctxt.redirectURL = µb.redirectEngine.tokenToURL(
fctxt,
modifier.cache.token
);
return directive; return directive;
} }
// Multiple directives mean more work to do. // Multiple directives mean more work to do.
@ -4258,15 +4272,11 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
winningDirective = directive; winningDirective = directive;
break; break;
} }
if ( modifier.cache === undefined ) { const { token, priority } = this.parseRedirectRequestValue(modifier);
modifier.cache = this.parseRedirectRequestValue(modifier.value); if ( µb.redirectEngine.hasToken(token) === false ) { continue; }
} if ( winningDirective === undefined || priority > winningPriority ) {
if (
winningDirective === undefined ||
modifier.cache.priority > winningPriority
) {
winningDirective = directive; winningDirective = directive;
winningPriority = modifier.cache.priority; winningPriority = priority;
} }
} }
if ( winningDirective === undefined ) { return; } if ( winningDirective === undefined ) { return; }
@ -4279,15 +4289,18 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
return winningDirective; return winningDirective;
}; };
FilterContainer.prototype.parseRedirectRequestValue = function(rawValue) { FilterContainer.prototype.parseRedirectRequestValue = function(modifier) {
let token = rawValue; if ( modifier.cache === undefined ) {
let priority = 0; let token = modifier.value;
const match = /:(\d+)$/.exec(rawValue); let priority = 1;
if ( match !== null ) { const match = /:(\d+)$/.exec(token);
token = rawValue.slice(0, match.index); if ( match !== null ) {
priority = parseInt(match[1], 10); token = token.slice(0, match.index);
priority = parseInt(match[1], 10);
}
modifier.cache = { token, priority };
} }
return { token, priority }; return modifier.cache;
}; };
/******************************************************************************/ /******************************************************************************/