From 5db8d05975f3c1038fdc568df35cce3d569234c5 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 30 Nov 2020 09:09:37 -0500 Subject: [PATCH] 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 --- src/js/static-filtering-parser.js | 51 ++++++++++++++++++++++++++++--- src/js/static-net-filtering.js | 46 ++++++++++------------------ 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 9337a627a..912e2f975 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -1161,8 +1161,9 @@ const Parser = class { BITFlavorError | BITFlavorUnsupported | BITFlavorIgnore ); } + static parseQueryPruneValue(arg) { - let s = arg; + let s = arg.trim(); if ( s === '*' ) { return { all: true }; } const out = { }; out.not = s.charCodeAt(0) === 0x7E /* '~' */; @@ -1196,6 +1197,29 @@ const Parser = class { out.name = s; 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 { const i = this.tokenPos[OPTTokenHeader]; - if ( i !== -1 && hasBits(allBits, OPTModifierType) ) { - optSlices[i] = OPTTokenInvalid; - if ( this.interactive ) { - this.parser.errorSlices(optSlices[i+1], optSlices[i+5]); + if ( i !== -1 ) { + if ( hasBits(allBits, OPTModifierType) ) { + optSlices[i] = OPTTokenInvalid; + 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] + ); + } + } } } } diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index a4a825f6a..463e342db 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -19,8 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint bitwise: false */ - 'use strict'; /******************************************************************************/ @@ -2389,32 +2387,22 @@ registerFilterClass(FilterStrictParty); const FilterOnHeaders = class { constructor(headerOpt) { this.headerOpt = headerOpt; - if ( headerOpt !== '' ) { - 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; + this.parsed = undefined; } match() { - if ( this.name === '' ) { return true; } - const value = $httpHeaders.lookup(this.name); - if ( value === undefined ) { return false; } - 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'); + if ( this.parsed === undefined ) { + this.parsed = + vAPI.StaticFilteringParser.parseHeaderValue(this.headerOpt); } - 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) { @@ -4231,12 +4219,10 @@ FilterContainer.prototype.matchHeaders = function(fctxt, headers) { let r = 0; if ( this.realmMatchString(Headers | BlockImportant, typeValue, partyBits) ) { r = 1; - } - if ( r !== 1 && this.realmMatchString(Headers | BlockAction, typeValue, partyBits) ) { - r = 1; - if ( r === 1 && this.realmMatchString(Headers | AllowAction, typeValue, partyBits) ) { - r = 2; - } + } else if ( this.realmMatchString(Headers | BlockAction, typeValue, partyBits) ) { + r = this.realmMatchString(Headers | AllowAction, typeValue, partyBits) + ? 2 + : 1; } $httpHeaders.reset();