Better align syntax of `header=` option to that of `queryprune=`

The header value is no longer implicitly a regex-based literal, but
a plain string against which the header name is compared. The value can
be set to a regex literal by bracing the header value with the usual
forward slashes, `/.../`.

Examples:

    *$1p,strict3p,script,header=via:1.1 google
    *$1p,strict3p,script,header=via:/1\.1\s+google/

The first form will cause a strict comparison with the value of the header
named `via` against the string `1.1 google`.

The second form will cause a regex-based test with the value of the header
named `via` against the regex `/1\.1\s+google/`.

The header value can be prepended with `~` to reverse the comparison:

    *$1p,strict3p,script,header=via:~1.1 google

The header value is optional and may be ommitted to test only for the
presence of a specific header:

    *$1p,strict3p,script,header=via
This commit is contained in:
Raymond Hill 2020-11-30 09:09:37 -05:00
parent ed64039912
commit 5db8d05975
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 62 additions and 35 deletions

View File

@ -1161,8 +1161,9 @@ const Parser = class {
BITFlavorError | BITFlavorUnsupported | BITFlavorIgnore BITFlavorError | BITFlavorUnsupported | BITFlavorIgnore
); );
} }
static parseQueryPruneValue(arg) { static parseQueryPruneValue(arg) {
let s = arg; let s = arg.trim();
if ( s === '*' ) { return { all: true }; } if ( s === '*' ) { return { all: true }; }
const out = { }; const out = { };
out.not = s.charCodeAt(0) === 0x7E /* '~' */; out.not = s.charCodeAt(0) === 0x7E /* '~' */;
@ -1196,6 +1197,29 @@ const Parser = class {
out.name = s; out.name = s;
return out; return out;
} }
static parseHeaderValue(arg) {
let s = arg.trim();
const out = { };
let pos = s.indexOf(':');
if ( pos === -1 ) { pos = s.length; }
out.name = s.slice(0, pos);
out.bad = out.name === '';
s = s.slice(pos + 1);
out.not = s.charCodeAt(0) === 0x7E /* '~' */;
if ( out.not ) { s = s.slice(1); }
out.value = s;
const match = /^\/(.+)\/(i)?$/.exec(s);
if ( match !== null ) {
try {
out.re = new RegExp(match[1], match[2] || '');
}
catch(ex) {
out.bad = true;
}
}
return out;
}
}; };
/******************************************************************************/ /******************************************************************************/
@ -2520,10 +2544,27 @@ const NetOptionsIterator = class {
// `header`: can't be used with any modifier type // `header`: can't be used with any modifier type
{ {
const i = this.tokenPos[OPTTokenHeader]; const i = this.tokenPos[OPTTokenHeader];
if ( i !== -1 && hasBits(allBits, OPTModifierType) ) { if ( i !== -1 ) {
optSlices[i] = OPTTokenInvalid; if ( hasBits(allBits, OPTModifierType) ) {
if ( this.interactive ) { optSlices[i] = OPTTokenInvalid;
this.parser.errorSlices(optSlices[i+1], optSlices[i+5]); if ( this.interactive ) {
this.parser.errorSlices(optSlices[i+1], optSlices[i+5]);
}
} else {
const val = this.parser.strFromSlices(
optSlices[i+4],
optSlices[i+5] - 3
);
const r = Parser.parseHeaderValue(val);
if ( r.bad ) {
optSlices[i] = OPTTokenInvalid;
if ( this.interactive ) {
this.parser.errorSlices(
optSlices[i+4],
optSlices[i+5]
);
}
}
} }
} }
} }

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* jshint bitwise: false */
'use strict'; 'use strict';
/******************************************************************************/ /******************************************************************************/
@ -2389,32 +2387,22 @@ registerFilterClass(FilterStrictParty);
const FilterOnHeaders = class { const FilterOnHeaders = class {
constructor(headerOpt) { constructor(headerOpt) {
this.headerOpt = headerOpt; this.headerOpt = headerOpt;
if ( headerOpt !== '' ) { this.parsed = undefined;
let pos = headerOpt.indexOf(':');
if ( pos === -1 ) { pos = headerOpt.length; }
this.name = headerOpt.slice(0, pos);
this.value = headerOpt.slice(pos + 1);
this.not = this.value.charCodeAt(0) === 0x21 /* '!' */;
if ( this.not ) { this.value.slice(1); }
} else {
this.name = this.value = '';
this.not = false;
}
this.reValue = null;
} }
match() { match() {
if ( this.name === '' ) { return true; } if ( this.parsed === undefined ) {
const value = $httpHeaders.lookup(this.name); this.parsed =
if ( value === undefined ) { return false; } vAPI.StaticFilteringParser.parseHeaderValue(this.headerOpt);
if ( this.value === '' ) { return true; }
if ( this.reValue === null ) {
let reText = this.value;
if ( reText.startsWith('|') ) { reText = '^' + reText.slice(1); }
if ( reText.endsWith('|') ) { reText = reText.slice(0, -1) + '$'; }
this.reValue = new RegExp(reText, 'i');
} }
return this.reValue.test(value) !== this.not; const { bad, name, not, re, value } = this.parsed;
if ( bad ) { return false; }
const headerValue = $httpHeaders.lookup(name);
if ( headerValue === undefined ) { return false; }
if ( value === '' ) { return true; }
return re === undefined
? (headerValue === value) !== not
: re.test(headerValue) !== not;
} }
logData(details) { logData(details) {
@ -4231,12 +4219,10 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) {
let r = 0; let r = 0;
if ( this.realmMatchString(Headers | BlockImportant, typeValue, partyBits) ) { if ( this.realmMatchString(Headers | BlockImportant, typeValue, partyBits) ) {
r = 1; r = 1;
} } else if ( this.realmMatchString(Headers | BlockAction, typeValue, partyBits) ) {
if ( r !== 1 && this.realmMatchString(Headers | BlockAction, typeValue, partyBits) ) { r = this.realmMatchString(Headers | AllowAction, typeValue, partyBits)
r = 1; ? 2
if ( r === 1 && this.realmMatchString(Headers | AllowAction, typeValue, partyBits) ) { : 1;
r = 2;
}
} }
$httpHeaders.reset(); $httpHeaders.reset();