mirror of https://github.com/gorhill/uBlock.git
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:
parent
c6d0204b23
commit
eae7cd58fe
|
@ -140,8 +140,8 @@ const µBlock = (( ) => { // jshint ignore:line
|
|||
|
||||
// Read-only
|
||||
systemSettings: {
|
||||
compiledMagic: 35, // Increase when compiled format changes
|
||||
selfieMagic: 35, // Increase when selfie format changes
|
||||
compiledMagic: 36, // Increase when compiled format changes
|
||||
selfieMagic: 36, // Increase when selfie format changes
|
||||
},
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501
|
||||
|
|
|
@ -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() {
|
||||
};
|
||||
|
||||
|
|
|
@ -1923,22 +1923,23 @@ const OPTTokenImage = 20;
|
|||
const OPTTokenImportant = 21;
|
||||
const OPTTokenInlineFont = 22;
|
||||
const OPTTokenInlineScript = 23;
|
||||
const OPTTokenMedia = 24;
|
||||
const OPTTokenMp4 = 25;
|
||||
const OPTTokenObject = 26;
|
||||
const OPTTokenOther = 27;
|
||||
const OPTTokenPing = 28;
|
||||
const OPTTokenPopunder = 29;
|
||||
const OPTTokenPopup = 30;
|
||||
const OPTTokenRedirect = 31;
|
||||
const OPTTokenRedirectRule = 32;
|
||||
const OPTTokenQueryprune = 33;
|
||||
const OPTTokenScript = 34;
|
||||
const OPTTokenShide = 35;
|
||||
const OPTTokenXhr = 36;
|
||||
const OPTTokenWebrtc = 37;
|
||||
const OPTTokenWebsocket = 38;
|
||||
const OPTTokenCount = 39;
|
||||
const OPTTokenMatchCase = 24;
|
||||
const OPTTokenMedia = 25;
|
||||
const OPTTokenMp4 = 26;
|
||||
const OPTTokenObject = 27;
|
||||
const OPTTokenOther = 28;
|
||||
const OPTTokenPing = 29;
|
||||
const OPTTokenPopunder = 30;
|
||||
const OPTTokenPopup = 31;
|
||||
const OPTTokenRedirect = 32;
|
||||
const OPTTokenRedirectRule = 33;
|
||||
const OPTTokenQueryprune = 34;
|
||||
const OPTTokenScript = 35;
|
||||
const OPTTokenShide = 36;
|
||||
const OPTTokenXhr = 37;
|
||||
const OPTTokenWebrtc = 38;
|
||||
const OPTTokenWebsocket = 39;
|
||||
const OPTTokenCount = 40;
|
||||
|
||||
//const OPTPerOptionMask = 0x0000ff00;
|
||||
const OPTCanNegate = 1 << 8;
|
||||
|
@ -2021,6 +2022,7 @@ Parser.prototype.OPTTokenImportant = OPTTokenImportant;
|
|||
Parser.prototype.OPTTokenInlineFont = OPTTokenInlineFont;
|
||||
Parser.prototype.OPTTokenInlineScript = OPTTokenInlineScript;
|
||||
Parser.prototype.OPTTokenInvalid = OPTTokenInvalid;
|
||||
Parser.prototype.OPTTokenMatchCase = OPTTokenMatchCase;
|
||||
Parser.prototype.OPTTokenMedia = OPTTokenMedia;
|
||||
Parser.prototype.OPTTokenMp4 = OPTTokenMp4;
|
||||
Parser.prototype.OPTTokenObject = OPTTokenObject;
|
||||
|
@ -2082,6 +2084,7 @@ const netOptionTokenDescriptors = new Map([
|
|||
[ 'important', OPTTokenImportant | OPTBlockOnly ],
|
||||
[ 'inline-font', OPTTokenInlineFont | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ],
|
||||
[ 'inline-script', OPTTokenInlineScript | OPTNonNetworkType | OPTCanNegate | OPTNonCspableType | OPTNonRedirectableType ],
|
||||
[ 'match-case', OPTTokenMatchCase ],
|
||||
[ 'media', OPTTokenMedia | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
||||
[ 'mp4', OPTTokenMp4 | OPTNetworkType | OPTBlockOnly | OPTModifierType ],
|
||||
[ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
||||
|
@ -2138,6 +2141,7 @@ Parser.netOptionTokenIds = new Map([
|
|||
[ 'important', OPTTokenImportant ],
|
||||
[ 'inline-font', OPTTokenInlineFont ],
|
||||
[ 'inline-script', OPTTokenInlineScript ],
|
||||
[ 'match-case', OPTTokenMatchCase ],
|
||||
[ 'media', OPTTokenMedia ],
|
||||
[ 'mp4', OPTTokenMp4 ],
|
||||
[ 'object', OPTTokenObject ],
|
||||
|
@ -2184,6 +2188,7 @@ Parser.netOptionTokenNames = new Map([
|
|||
[ OPTTokenImportant, 'important' ],
|
||||
[ OPTTokenInlineFont, 'inline-font' ],
|
||||
[ OPTTokenInlineScript, 'inline-script' ],
|
||||
[ OPTTokenMatchCase, 'match-case' ],
|
||||
[ OPTTokenMedia, 'media' ],
|
||||
[ OPTTokenMp4, 'mp4' ],
|
||||
[ 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;
|
||||
}
|
||||
next() {
|
||||
|
|
|
@ -146,6 +146,7 @@ const typeValueToTypeName = [
|
|||
// valid until the next evaluation.
|
||||
|
||||
let $requestURL = '';
|
||||
let $requestURLRaw = '';
|
||||
let $requestHostname = '';
|
||||
let $docHostname = '';
|
||||
let $docDomain = '';
|
||||
|
@ -867,9 +868,13 @@ const FilterPatternGeneric = class {
|
|||
}
|
||||
|
||||
static compile(details) {
|
||||
const anchor = details.anchor;
|
||||
const out = [
|
||||
FilterPatternGeneric.fid,
|
||||
details.pattern,
|
||||
details.anchor,
|
||||
];
|
||||
details.anchor = 0;
|
||||
return [ FilterPatternGeneric.fid, details.pattern, anchor ];
|
||||
return out;
|
||||
}
|
||||
|
||||
static fromCompiled(args) {
|
||||
|
@ -1107,20 +1112,22 @@ registerFilterClass(FilterTrailingSeparator);
|
|||
/******************************************************************************/
|
||||
|
||||
const FilterRegex = class {
|
||||
constructor(s) {
|
||||
constructor(s, matchCase = false) {
|
||||
this.s = s;
|
||||
if ( matchCase ) {
|
||||
this.matchCase = true;
|
||||
}
|
||||
}
|
||||
|
||||
match() {
|
||||
if ( this.re === null ) {
|
||||
this.re = FilterRegex.dict.get(this.s);
|
||||
if ( this.re === undefined ) {
|
||||
this.re = new RegExp(this.s, 'i');
|
||||
FilterRegex.dict.set(this.s, this.re);
|
||||
}
|
||||
this.re = new RegExp(
|
||||
this.s,
|
||||
this.matchCase ? '' : 'i'
|
||||
);
|
||||
}
|
||||
if ( this.re.test($requestURL) === false ) { return false; }
|
||||
$patternMatchLeft = $requestURL.search(this.re);
|
||||
if ( this.re.test($requestURLRaw) === false ) { return false; }
|
||||
$patternMatchLeft = $requestURLRaw.search(this.re);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1128,33 +1135,36 @@ const FilterRegex = class {
|
|||
details.pattern.push('/', this.s, '/');
|
||||
details.regex.push(this.s);
|
||||
details.isRegex = true;
|
||||
if ( this.matchCase ) {
|
||||
details.options.push('match-case');
|
||||
}
|
||||
}
|
||||
|
||||
toSelfie() {
|
||||
return [ this.fid, this.s ];
|
||||
return [ this.fid, this.s, this.matchCase ];
|
||||
}
|
||||
|
||||
static compile(details) {
|
||||
return [ FilterRegex.fid, details.pattern ];
|
||||
return [ FilterRegex.fid, details.pattern, details.patternMatchCase ];
|
||||
}
|
||||
|
||||
static fromCompiled(args) {
|
||||
return new FilterRegex(args[1]);
|
||||
return new FilterRegex(args[1], args[2]);
|
||||
}
|
||||
|
||||
static fromSelfie(args) {
|
||||
return new FilterRegex(args[1]);
|
||||
return new FilterRegex(args[1], args[2]);
|
||||
}
|
||||
|
||||
static keyFromArgs(args) {
|
||||
return args[1];
|
||||
return `${args[1]}\t${args[2]}`;
|
||||
}
|
||||
};
|
||||
|
||||
FilterRegex.prototype.re = null;
|
||||
FilterRegex.prototype.matchCase = false;
|
||||
|
||||
FilterRegex.isSlow = true;
|
||||
FilterRegex.dict = new Map();
|
||||
|
||||
registerFilterClass(FilterRegex);
|
||||
|
||||
|
@ -2783,6 +2793,7 @@ const FilterParser = class {
|
|||
this.modifyValue = undefined;
|
||||
this.invalid = false;
|
||||
this.pattern = '';
|
||||
this.patternMatchCase = false;
|
||||
this.party = AnyParty;
|
||||
this.optionUnitBits = 0;
|
||||
this.domainOpt = '';
|
||||
|
@ -2944,6 +2955,9 @@ const FilterParser = class {
|
|||
}
|
||||
this.optionUnitBits |= this.REDIRECT_BIT;
|
||||
break;
|
||||
case this.parser.OPTTokenMatchCase:
|
||||
this.patternMatchCase = true;
|
||||
break;
|
||||
case this.parser.OPTTokenMp4:
|
||||
id = this.action === AllowAction
|
||||
? this.parser.OPTTokenRedirectRule
|
||||
|
@ -3833,6 +3847,7 @@ FilterContainer.prototype.matchAndFetchModifiers = function(
|
|||
modifierType
|
||||
) {
|
||||
$requestURL = urlTokenizer.setURL(fctxt.url);
|
||||
$requestURLRaw = fctxt.url;
|
||||
$docHostname = fctxt.getDocHostname();
|
||||
$docDomain = fctxt.getDocDomain();
|
||||
$docEntity.reset();
|
||||
|
@ -4126,6 +4141,7 @@ FilterContainer.prototype.matchStringReverse = function(type, url) {
|
|||
|
||||
// Prime tokenizer: we get a normalized URL in return.
|
||||
$requestURL = urlTokenizer.setURL(url);
|
||||
$requestURLRaw = url;
|
||||
this.$filterUnit = 0;
|
||||
|
||||
// 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.
|
||||
$requestURL = urlTokenizer.setURL(fctxt.url);
|
||||
$requestURLRaw = fctxt.url;
|
||||
this.$filterUnit = 0;
|
||||
|
||||
// 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.
|
||||
$requestURL = urlTokenizer.setURL(fctxt.url);
|
||||
$requestURLRaw = fctxt.url;
|
||||
this.$filterUnit = 0;
|
||||
|
||||
// These registers will be used by various filters
|
||||
|
@ -4239,13 +4257,9 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
|
|||
const directive = directives[0];
|
||||
if ( (directive.bits & AllowAction) !== 0 ) { return directive; }
|
||||
const modifier = directive.modifier;
|
||||
if ( modifier.cache === undefined ) {
|
||||
modifier.cache = this.parseRedirectRequestValue(modifier.value);
|
||||
}
|
||||
fctxt.redirectURL = µb.redirectEngine.tokenToURL(
|
||||
fctxt,
|
||||
modifier.cache.token
|
||||
);
|
||||
const { token } = this.parseRedirectRequestValue(modifier);
|
||||
fctxt.redirectURL = µb.redirectEngine.tokenToURL(fctxt, token);
|
||||
if ( fctxt.redirectURL === undefined ) { return; }
|
||||
return directive;
|
||||
}
|
||||
// Multiple directives mean more work to do.
|
||||
|
@ -4258,15 +4272,11 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
|
|||
winningDirective = directive;
|
||||
break;
|
||||
}
|
||||
if ( modifier.cache === undefined ) {
|
||||
modifier.cache = this.parseRedirectRequestValue(modifier.value);
|
||||
}
|
||||
if (
|
||||
winningDirective === undefined ||
|
||||
modifier.cache.priority > winningPriority
|
||||
) {
|
||||
const { token, priority } = this.parseRedirectRequestValue(modifier);
|
||||
if ( µb.redirectEngine.hasToken(token) === false ) { continue; }
|
||||
if ( winningDirective === undefined || priority > winningPriority ) {
|
||||
winningDirective = directive;
|
||||
winningPriority = modifier.cache.priority;
|
||||
winningPriority = priority;
|
||||
}
|
||||
}
|
||||
if ( winningDirective === undefined ) { return; }
|
||||
|
@ -4279,15 +4289,18 @@ FilterContainer.prototype.redirectRequest = function(fctxt) {
|
|||
return winningDirective;
|
||||
};
|
||||
|
||||
FilterContainer.prototype.parseRedirectRequestValue = function(rawValue) {
|
||||
let token = rawValue;
|
||||
let priority = 0;
|
||||
const match = /:(\d+)$/.exec(rawValue);
|
||||
if ( match !== null ) {
|
||||
token = rawValue.slice(0, match.index);
|
||||
priority = parseInt(match[1], 10);
|
||||
FilterContainer.prototype.parseRedirectRequestValue = function(modifier) {
|
||||
if ( modifier.cache === undefined ) {
|
||||
let token = modifier.value;
|
||||
let priority = 1;
|
||||
const match = /:(\d+)$/.exec(token);
|
||||
if ( match !== null ) {
|
||||
token = token.slice(0, match.index);
|
||||
priority = parseInt(match[1], 10);
|
||||
}
|
||||
modifier.cache = { token, priority };
|
||||
}
|
||||
return { token, priority };
|
||||
return modifier.cache;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
Loading…
Reference in New Issue