mirror of
https://github.com/gorhill/uBlock.git
synced 2024-09-15 15:32:28 +02:00
Another round of fine-tuning queryprune=
syntax
Related discussions: - https://github.com/uBlockOrigin/uBlock-issues/issues/1356#issuecomment-732411286 - https://github.com/AdguardTeam/CoreLibs/issues/1384 Changes: Negation character is `~` (instead of `!`). Drop special anchor character `|` -- leading `|` will be supported until no such filter is present in uBO's own filter lists. For example, instance of `queryprune=|ad` will have to be replaced with `queryprune=/^ad/` (or `queryprune=ad` if the name of the parameter to remove is exactly `ad`). Align semantic with that of AdGuard's `removeparam=`, except that specifying multiple `|`-separated names is not supported.
This commit is contained in:
parent
6261b2ab63
commit
d1895d4749
@ -140,8 +140,8 @@ const µBlock = (( ) => { // jshint ignore:line
|
|||||||
|
|
||||||
// Read-only
|
// Read-only
|
||||||
systemSettings: {
|
systemSettings: {
|
||||||
compiledMagic: 36, // Increase when compiled format changes
|
compiledMagic: 37, // Increase when compiled format changes
|
||||||
selfieMagic: 36, // Increase when selfie format changes
|
selfieMagic: 37, // 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
|
||||||
|
@ -389,14 +389,24 @@ const Parser = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the pattern is not a regex, there might be options.
|
// If the pattern is not a regex, there might be options.
|
||||||
|
//
|
||||||
|
// The character `$` is deemed to be an option anchor if and only if
|
||||||
|
// all the following conditions are fulfilled:
|
||||||
|
// - `$` is not the last character in the filter
|
||||||
|
// - The character following `$` is either comma, alphanumeric, or `~`.
|
||||||
if ( patternIsRegex === false ) {
|
if ( patternIsRegex === false ) {
|
||||||
let optionsBits = 0;
|
let optionsBits = 0;
|
||||||
let i = this.optionsAnchorSpan.i;
|
let i = this.optionsAnchorSpan.i - 3;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
i -= 3;
|
i -= 3;
|
||||||
if ( i < islice ) { break; }
|
if ( i < islice ) { break; }
|
||||||
const bits = this.slices[i];
|
const bits = this.slices[i];
|
||||||
if ( hasBits(bits, BITDollar) ) { break; }
|
if (
|
||||||
|
hasBits(bits, BITDollar) &&
|
||||||
|
hasBits(this.slices[i+3], BITAlphaNum | BITComma | BITTilde)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
optionsBits |= bits;
|
optionsBits |= bits;
|
||||||
}
|
}
|
||||||
if ( i >= islice ) {
|
if ( i >= islice ) {
|
||||||
@ -1151,6 +1161,41 @@ const Parser = class {
|
|||||||
BITFlavorError | BITFlavorUnsupported | BITFlavorIgnore
|
BITFlavorError | BITFlavorUnsupported | BITFlavorIgnore
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
static parseQueryPruneValue(arg) {
|
||||||
|
let s = arg;
|
||||||
|
if ( s === '*' ) { return { all: true }; }
|
||||||
|
const out = { };
|
||||||
|
out.not = s.charCodeAt(0) === 0x7E /* '~' */;
|
||||||
|
if ( out.not ) {
|
||||||
|
s = s.slice(1);
|
||||||
|
}
|
||||||
|
const match = /^\/(.+)\/(i)?$/.exec(s);
|
||||||
|
if ( match !== null ) {
|
||||||
|
try {
|
||||||
|
out.re = new RegExp(match[1], match[2] || '');
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
out.bad = true;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// TODO: remove once no longer used in filter lists
|
||||||
|
if ( s.startsWith('|') ) {
|
||||||
|
try {
|
||||||
|
out.re = new RegExp('^' + s.slice(1), 'i');
|
||||||
|
} catch(ex) {
|
||||||
|
out.bad = true;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
// Multiple values not supported (because very inefficient)
|
||||||
|
if ( s.includes('|') ) {
|
||||||
|
out.bad = true;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
out.name = s;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@ -1926,20 +1971,21 @@ const OPTTokenInlineScript = 23;
|
|||||||
const OPTTokenMatchCase = 24;
|
const OPTTokenMatchCase = 24;
|
||||||
const OPTTokenMedia = 25;
|
const OPTTokenMedia = 25;
|
||||||
const OPTTokenMp4 = 26;
|
const OPTTokenMp4 = 26;
|
||||||
const OPTTokenObject = 27;
|
const OPTTokenNoop = 27;
|
||||||
const OPTTokenOther = 28;
|
const OPTTokenObject = 28;
|
||||||
const OPTTokenPing = 29;
|
const OPTTokenOther = 29;
|
||||||
const OPTTokenPopunder = 30;
|
const OPTTokenPing = 30;
|
||||||
const OPTTokenPopup = 31;
|
const OPTTokenPopunder = 31;
|
||||||
const OPTTokenRedirect = 32;
|
const OPTTokenPopup = 32;
|
||||||
const OPTTokenRedirectRule = 33;
|
const OPTTokenRedirect = 33;
|
||||||
const OPTTokenQueryprune = 34;
|
const OPTTokenRedirectRule = 34;
|
||||||
const OPTTokenScript = 35;
|
const OPTTokenQueryprune = 35;
|
||||||
const OPTTokenShide = 36;
|
const OPTTokenScript = 36;
|
||||||
const OPTTokenXhr = 37;
|
const OPTTokenShide = 37;
|
||||||
const OPTTokenWebrtc = 38;
|
const OPTTokenXhr = 38;
|
||||||
const OPTTokenWebsocket = 39;
|
const OPTTokenWebrtc = 39;
|
||||||
const OPTTokenCount = 40;
|
const OPTTokenWebsocket = 40;
|
||||||
|
const OPTTokenCount = 41;
|
||||||
|
|
||||||
//const OPTPerOptionMask = 0x0000ff00;
|
//const OPTPerOptionMask = 0x0000ff00;
|
||||||
const OPTCanNegate = 1 << 8;
|
const OPTCanNegate = 1 << 8;
|
||||||
@ -2025,6 +2071,7 @@ Parser.prototype.OPTTokenInvalid = OPTTokenInvalid;
|
|||||||
Parser.prototype.OPTTokenMatchCase = OPTTokenMatchCase;
|
Parser.prototype.OPTTokenMatchCase = OPTTokenMatchCase;
|
||||||
Parser.prototype.OPTTokenMedia = OPTTokenMedia;
|
Parser.prototype.OPTTokenMedia = OPTTokenMedia;
|
||||||
Parser.prototype.OPTTokenMp4 = OPTTokenMp4;
|
Parser.prototype.OPTTokenMp4 = OPTTokenMp4;
|
||||||
|
Parser.prototype.OPTTokenNoop = OPTTokenNoop;
|
||||||
Parser.prototype.OPTTokenObject = OPTTokenObject;
|
Parser.prototype.OPTTokenObject = OPTTokenObject;
|
||||||
Parser.prototype.OPTTokenOther = OPTTokenOther;
|
Parser.prototype.OPTTokenOther = OPTTokenOther;
|
||||||
Parser.prototype.OPTTokenPing = OPTTokenPing;
|
Parser.prototype.OPTTokenPing = OPTTokenPing;
|
||||||
@ -2087,6 +2134,7 @@ const netOptionTokenDescriptors = new Map([
|
|||||||
[ 'match-case', OPTTokenMatchCase ],
|
[ '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 ],
|
||||||
|
[ '_', OPTTokenNoop ],
|
||||||
[ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
[ 'object', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
||||||
[ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
[ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
||||||
[ 'other', OPTTokenOther | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
[ 'other', OPTTokenOther | OPTCanNegate | OPTNetworkType | OPTModifiableType | OPTRedirectableType | OPTNonCspableType ],
|
||||||
@ -2144,6 +2192,7 @@ Parser.netOptionTokenIds = new Map([
|
|||||||
[ 'match-case', OPTTokenMatchCase ],
|
[ 'match-case', OPTTokenMatchCase ],
|
||||||
[ 'media', OPTTokenMedia ],
|
[ 'media', OPTTokenMedia ],
|
||||||
[ 'mp4', OPTTokenMp4 ],
|
[ 'mp4', OPTTokenMp4 ],
|
||||||
|
[ '_', OPTTokenNoop ],
|
||||||
[ 'object', OPTTokenObject ],
|
[ 'object', OPTTokenObject ],
|
||||||
[ 'object-subrequest', OPTTokenObject ],
|
[ 'object-subrequest', OPTTokenObject ],
|
||||||
[ 'other', OPTTokenOther ],
|
[ 'other', OPTTokenOther ],
|
||||||
@ -2191,6 +2240,7 @@ Parser.netOptionTokenNames = new Map([
|
|||||||
[ OPTTokenMatchCase, 'match-case' ],
|
[ OPTTokenMatchCase, 'match-case' ],
|
||||||
[ OPTTokenMedia, 'media' ],
|
[ OPTTokenMedia, 'media' ],
|
||||||
[ OPTTokenMp4, 'mp4' ],
|
[ OPTTokenMp4, 'mp4' ],
|
||||||
|
[ OPTTokenNoop, '_' ],
|
||||||
[ OPTTokenObject, 'object' ],
|
[ OPTTokenObject, 'object' ],
|
||||||
[ OPTTokenOther, 'other' ],
|
[ OPTTokenOther, 'other' ],
|
||||||
[ OPTTokenPing, 'ping' ],
|
[ OPTTokenPing, 'ping' ],
|
||||||
@ -2434,10 +2484,20 @@ const NetOptionsIterator = class {
|
|||||||
if ( this.interactive ) {
|
if ( this.interactive ) {
|
||||||
this.parser.errorSlices(optSlices[i+1], optSlices[i+5]);
|
this.parser.errorSlices(optSlices[i+1], optSlices[i+5]);
|
||||||
}
|
}
|
||||||
} else if ( this.validateQueryPruneArg(i) === false ) {
|
} else {
|
||||||
optSlices[i] = OPTTokenInvalid;
|
const val = this.parser.strFromSlices(
|
||||||
if ( this.interactive ) {
|
optSlices[i+4],
|
||||||
this.parser.errorSlices(optSlices[i+4], optSlices[i+5]);
|
optSlices[i+5] - 3
|
||||||
|
);
|
||||||
|
const r = Parser.parseQueryPruneValue(val);
|
||||||
|
if ( r.bad ) {
|
||||||
|
optSlices[i] = OPTTokenInvalid;
|
||||||
|
if ( this.interactive ) {
|
||||||
|
this.parser.errorSlices(
|
||||||
|
optSlices[i+4],
|
||||||
|
optSlices[i+5]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2501,24 +2561,6 @@ const NetOptionsIterator = class {
|
|||||||
this.readPtr = i + 6;
|
this.readPtr = i + 6;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
validateQueryPruneArg(i) {
|
|
||||||
let val = this.parser.strFromSlices(
|
|
||||||
this.optSlices[i+4],
|
|
||||||
this.optSlices[i+5] - 3
|
|
||||||
);
|
|
||||||
if ( val === '*' ) { return true; }
|
|
||||||
if ( val.charCodeAt(0) === 0x21 /* '!' */ ) {
|
|
||||||
val = val.slice(1);
|
|
||||||
}
|
|
||||||
if ( val.startsWith('|') ) { val = `^${val.slice(1)}`; }
|
|
||||||
if ( val.endsWith('|') ) { val = `${val.slice(0,-1)}$`; }
|
|
||||||
try {
|
|
||||||
void new RegExp(val);
|
|
||||||
} catch(ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -2967,6 +2967,8 @@ const FilterParser = class {
|
|||||||
}
|
}
|
||||||
this.optionUnitBits |= this.REDIRECT_BIT;
|
this.optionUnitBits |= this.REDIRECT_BIT;
|
||||||
break;
|
break;
|
||||||
|
case this.parser.OPTTokenNoop:
|
||||||
|
break;
|
||||||
case this.parser.OPTTokenQueryprune:
|
case this.parser.OPTTokenQueryprune:
|
||||||
if ( this.parseModifierOption(id, val) === false ) {
|
if ( this.parseModifierOption(id, val) === false ) {
|
||||||
return false;
|
return false;
|
||||||
@ -3232,33 +3234,20 @@ const FilterParser = class {
|
|||||||
|
|
||||||
makePatternFromQuerypruneValue() {
|
makePatternFromQuerypruneValue() {
|
||||||
let pattern = this.modifyValue;
|
let pattern = this.modifyValue;
|
||||||
if ( pattern === '*' || pattern.charCodeAt(0) === 0x21 /* '!' */ ) {
|
if ( pattern === '*' || pattern.charCodeAt(0) === 0x7E /* '~' */ ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ( /^\w+$/.test(pattern) ) {
|
const match = /^\/(.+)\/i?$/.exec(pattern);
|
||||||
this.pattern = `${pattern}=`;
|
if ( match !== null ) {
|
||||||
return true;
|
pattern = match[1];
|
||||||
}
|
this.isRegex = true;
|
||||||
const reRegex = /^\/(.+)\/i?$/;
|
} else if ( pattern.startsWith('|') ) {
|
||||||
if ( reRegex.test(pattern) ) {
|
pattern = '\\b' + pattern.slice(1);
|
||||||
pattern = reRegex.exec(pattern)[1];
|
this.isRegex = true;
|
||||||
} else {
|
} else {
|
||||||
let prefix = '', suffix = '';
|
pattern = encodeURIComponent(pattern).toLowerCase() + '=';
|
||||||
if ( pattern.startsWith('|') ) {
|
|
||||||
pattern = pattern.slice(1);
|
|
||||||
prefix = '\\b';
|
|
||||||
}
|
|
||||||
if ( pattern.endsWith('|') ) {
|
|
||||||
pattern = pattern.slice(0, -1);
|
|
||||||
suffix = '\\b';
|
|
||||||
}
|
|
||||||
if ( pattern.indexOf('|') !== -1 ) {
|
|
||||||
pattern = `(?:${pattern})`;
|
|
||||||
}
|
|
||||||
pattern = prefix + pattern + suffix;
|
|
||||||
}
|
}
|
||||||
this.pattern = pattern;
|
this.pattern = pattern;
|
||||||
this.isRegex = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4323,24 +4312,42 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||||||
const params = new Map(new self.URLSearchParams(url.slice(qpos + 1)));
|
const params = new Map(new self.URLSearchParams(url.slice(qpos + 1)));
|
||||||
const out = [];
|
const out = [];
|
||||||
for ( const directive of directives ) {
|
for ( const directive of directives ) {
|
||||||
|
if ( params.size === 0 ) { break; }
|
||||||
const modifier = directive.modifier;
|
const modifier = directive.modifier;
|
||||||
const isException = (directive.bits & AllowAction) !== 0;
|
const isException = (directive.bits & AllowAction) !== 0;
|
||||||
if ( isException && modifier.value === '' ) {
|
if ( isException && modifier.value === '' ) {
|
||||||
out.push(directive);
|
out.push(directive);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ( modifier.cache === undefined ) {
|
const { all, bad, name, not, re } = this.parseFilterPruneValue(modifier);
|
||||||
this.parseFilterPruneValue(modifier);
|
if ( bad ) { continue; }
|
||||||
|
if ( all ) {
|
||||||
|
if ( isException === false ) { params.clear(); }
|
||||||
|
out.push(directive);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const { all, not, re } = modifier.cache;
|
if ( name !== undefined ) {
|
||||||
let filtered = false;
|
const value = params.get(name);
|
||||||
for ( const [ key, value ] of params ) {
|
if ( not === false ) {
|
||||||
if ( all !== true && re.test(`${key}=${value}`) === not ) {
|
if ( value !== undefined ) {
|
||||||
|
if ( isException === false ) { params.delete(name); }
|
||||||
|
out.push(directive);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ( isException === false ) {
|
if ( value !== undefined ) { params.delete(name); }
|
||||||
params.delete(key);
|
if ( params.size !== 0 ) {
|
||||||
|
if ( isException === false ) { params.clear(); }
|
||||||
|
out.push(directive);
|
||||||
}
|
}
|
||||||
|
if ( value !== undefined ) { params.set(name, value); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( re === undefined ) { continue; }
|
||||||
|
let filtered = false;
|
||||||
|
for ( const [ key, value ] of params ) {
|
||||||
|
if ( re.test(`${key}=${value}`) === not ) { continue; }
|
||||||
|
if ( isException === false ) { params.delete(key); }
|
||||||
filtered = true;
|
filtered = true;
|
||||||
}
|
}
|
||||||
if ( filtered ) {
|
if ( filtered ) {
|
||||||
@ -4358,29 +4365,11 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
FilterContainer.prototype.parseFilterPruneValue = function(modifier) {
|
FilterContainer.prototype.parseFilterPruneValue = function(modifier) {
|
||||||
const cache = {};
|
if ( modifier.cache === undefined ) {
|
||||||
const reRegex = /^\/(.+)\/i?$/;
|
modifier.cache =
|
||||||
let retext = modifier.value;
|
vAPI.StaticFilteringParser.parseQueryPruneValue(modifier.value);
|
||||||
if ( retext === '*' ) {
|
|
||||||
cache.all = true;
|
|
||||||
} else {
|
|
||||||
cache.not = retext.charCodeAt(0) === 0x21 /* '!' */;
|
|
||||||
if ( cache.not ) { retext = retext.slice(1); }
|
|
||||||
if ( /^\w+$/.test(retext) ) {
|
|
||||||
retext = `^${retext}=`;
|
|
||||||
} else if ( reRegex.test(retext) ) {
|
|
||||||
retext = reRegex.exec(retext)[1];
|
|
||||||
} else {
|
|
||||||
if ( retext.startsWith('|') ) { retext = `^${retext.slice(1)}`; }
|
|
||||||
if ( retext.endsWith('|') ) { retext = `${retext.slice(0,-1)}$`; }
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
cache.re = new RegExp(retext, 'i');
|
|
||||||
} catch(ex) {
|
|
||||||
cache.re = /.^/;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
modifier.cache = cache;
|
return modifier.cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
Loading…
Reference in New Issue
Block a user