1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-01 16:33:06 +01:00

Fix compilation of blocking counterpart of redirect= filters

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1358
This commit is contained in:
Raymond Hill 2020-11-25 09:36:12 -05:00
parent 7d80416938
commit 57013c16e6
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 136 additions and 93 deletions

View File

@ -2455,6 +2455,16 @@ 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]);
}
}
}
return this;
}
next() {

View File

@ -2620,7 +2620,10 @@ const urlTokenizer = new (class {
/******************************************************************************/
const FilterParser = class {
constructor(parser) {
constructor(parser, other = undefined) {
if ( other !== undefined ) {
return Object.assign(this, other);
}
this.cantWebsocket = vAPI.cantWebsocket;
this.noTokenHash = urlTokenizer.noTokenHash;
this.reIsolateHostname = /^(\*?\.)?([^\x00-\x24\x26-\x2C\x2F\x3A-\x5E\x60\x7B-\x7F]+)(.*)/;
@ -2761,10 +2764,11 @@ const FilterParser = class {
[ 'new',1412],
]);
this.maxTokenLen = urlTokenizer.MAX_TOKEN_LENGTH;
this.reset();
this.reset(parser);
}
reset() {
reset(parser) {
this.parser = parser;
this.action = BlockAction;
// anchor: bit vector
// 0000 (0x0): no anchoring
@ -2780,7 +2784,7 @@ const FilterParser = class {
this.invalid = false;
this.pattern = '';
this.party = AnyParty;
this.hasOptionUnits = false;
this.optionUnitBits = 0;
this.domainOpt = '';
this.denyallowOpt = '';
this.headerOpt = undefined;
@ -2800,6 +2804,10 @@ const FilterParser = class {
return this;
}
clone() {
return new FilterParser(this.parser, this);
}
normalizeRegexSource(s) {
try {
const re = new RegExp(s);
@ -2830,14 +2838,14 @@ const FilterParser = class {
this.party |= firstParty ? FirstParty : ThirdParty;
}
parseHostnameList(parser, s, modeBits, out = []) {
parseHostnameList(s, modeBits, out = []) {
let beg = 0;
let slen = s.length;
let i = 0;
while ( beg < slen ) {
let end = s.indexOf('|', beg);
if ( end === -1 ) { end = slen; }
const hn = parser.normalizeHostnameValue(
const hn = this.parser.normalizeHostnameValue(
s.slice(beg, end),
modeBits
);
@ -2858,96 +2866,115 @@ const FilterParser = class {
} else if ( this.action === AllowAction ) {
this.modifyValue = '';
}
this.hasOptionUnits = true;
return true;
}
parseOptions(parser) {
for ( let { id, val, not } of parser.netOptions() ) {
parseOptions() {
for ( let { id, val, not } of this.parser.netOptions() ) {
switch ( id ) {
case parser.OPTToken1p:
case this.parser.OPTToken1p:
this.parsePartyOption(true, not);
break;
case parser.OPTToken1pStrict:
case this.parser.OPTToken1pStrict:
this.strictParty = this.strictParty === -1 ? 0 : 1;
this.hasOptionUnits = true;
this.optionUnitBits |= this.STRICT_PARTY_BIT;
break;
case parser.OPTToken3p:
case this.parser.OPTToken3p:
this.parsePartyOption(false, not);
break;
case parser.OPTToken3pStrict:
case this.parser.OPTToken3pStrict:
this.strictParty = this.strictParty === 1 ? 0 : -1;
this.hasOptionUnits = true;
this.optionUnitBits |= this.STRICT_PARTY_BIT;
break;
case parser.OPTTokenAll:
case this.parser.OPTTokenAll:
this.parseTypeOption(-1);
break;
// https://github.com/uBlockOrigin/uAssets/issues/192
case parser.OPTTokenBadfilter:
case this.parser.OPTTokenBadfilter:
this.badFilter = true;
break;
case parser.OPTTokenCsp:
case this.parser.OPTTokenCsp:
if ( this.parseModifierOption(id, val) === false ) {
return false;
}
if ( val !== undefined && this.reBadCSP.test(val) ) {
return false;
}
this.optionUnitBits |= this.CSP_BIT;
break;
// https://github.com/gorhill/uBlock/issues/2294
// Detect and discard filter if domain option contains
// nonsensical characters.
case parser.OPTTokenDomain:
case this.parser.OPTTokenDomain:
this.domainOpt = this.parseHostnameList(
parser,
val,
0b1010,
this.domainOptList
);
if ( this.domainOpt === '' ) { return false; }
this.hasOptionUnits = true;
this.optionUnitBits |= this.DOMAIN_BIT;
break;
case parser.OPTTokenDenyAllow:
this.denyallowOpt = this.parseHostnameList(parser, val, 0b0000);
case this.parser.OPTTokenDenyAllow:
this.denyallowOpt = this.parseHostnameList(val, 0b0000);
if ( this.denyallowOpt === '' ) { return false; }
this.hasOptionUnits = true;
this.optionUnitBits |= this.DENYALLOW_BIT;
break;
// https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/
// Add support for `elemhide`. Rarely used but it happens.
case parser.OPTTokenEhide:
this.parseTypeOption(parser.OPTTokenShide, not);
this.parseTypeOption(parser.OPTTokenGhide, not);
case this.parser.OPTTokenEhide:
this.parseTypeOption(this.parser.OPTTokenShide, not);
this.parseTypeOption(this.parser.OPTTokenGhide, not);
break;
case parser.OPTTokenHeader:
case this.parser.OPTTokenHeader:
this.headerOpt = val !== undefined ? val : '';
this.hasOptionUnits = true;
this.optionUnitBits |= this.HEADER_BIT;
break;
case parser.OPTTokenImportant:
case this.parser.OPTTokenImportant:
if ( this.action === AllowAction ) { return false; }
this.action = BlockImportant;
break;
// Used by Adguard:
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#empty-modifier
case parser.OPTTokenEmpty:
if ( this.modifyType !== undefined ) { return false; }
this.modifyType = parser.OPTTokenRedirect;
this.modifyValue = 'empty';
this.hasOptionUnits = true;
case this.parser.OPTTokenEmpty:
id = this.action === AllowAction
? this.parser.OPTTokenRedirectRule
: this.parser.OPTTokenRedirect;
if ( this.parseModifierOption(id, 'empty') === false ) {
return false;
}
this.optionUnitBits |= this.REDIRECT_BIT;
break;
case parser.OPTTokenMp4:
if ( this.modifyType !== undefined ) { return false; }
this.modifyType = parser.OPTTokenRedirect;
this.modifyValue = 'noopmp4-1s';
this.hasOptionUnits = true;
case this.parser.OPTTokenMp4:
id = this.action === AllowAction
? this.parser.OPTTokenRedirectRule
: this.parser.OPTTokenRedirect;
if ( this.parseModifierOption(id, 'noopmp4-1s') === false ) {
return false;
}
this.optionUnitBits |= this.REDIRECT_BIT;
break;
case parser.OPTTokenQueryprune:
case parser.OPTTokenRedirect:
case parser.OPTTokenRedirectRule:
case this.parser.OPTTokenQueryprune:
if ( this.parseModifierOption(id, val) === false ) {
return false;
}
this.optionUnitBits |= this.QUERYPRUNE_BIT;
break;
case parser.OPTTokenInvalid:
case this.parser.OPTTokenRedirect:
if ( this.action === AllowAction ) {
id = this.parser.OPTTokenRedirectRule;
}
if ( this.parseModifierOption(id, val) === false ) {
return false;
}
this.optionUnitBits |= this.REDIRECT_BIT;
break;
case this.parser.OPTTokenRedirectRule:
if ( this.parseModifierOption(id, val) === false ) {
return false;
}
this.optionUnitBits |= this.REDIRECT_BIT;
break;
case this.parser.OPTTokenInvalid:
return false;
default:
if ( this.tokenIdToNormalizedType.has(id) === false ) {
@ -2971,10 +2998,10 @@ const FilterParser = class {
if ( this.typeBits === 0 ) { return false; }
}
// CSP directives implicitly apply only to document/subdocument.
if ( this.modifyType === parser.OPTTokenCsp ) {
if ( this.modifyType === this.parser.OPTTokenCsp ) {
if ( this.typeBits === 0 ) {
this.parseTypeOption(parser.OPTTokenDoc, false);
this.parseTypeOption(parser.OPTTokenFrame, false);
this.parseTypeOption(this.parser.OPTTokenDoc, false);
this.parseTypeOption(this.parser.OPTTokenFrame, false);
}
}
// https://github.com/gorhill/uBlock/issues/2283
@ -2989,7 +3016,7 @@ const FilterParser = class {
parse(parser) {
// important!
this.reset();
this.reset(parser);
if ( parser.hasError() ) {
this.invalid = true;
@ -3019,7 +3046,7 @@ const FilterParser = class {
}
// options
if ( parser.hasOptions() && this.parseOptions(parser) === false ) {
if ( parser.hasOptions() && this.parseOptions() === false ) {
this.unsupported = true;
return this;
}
@ -3084,11 +3111,12 @@ const FilterParser = class {
// are not good. Avoid if possible. This has a significant positive
// impact on performance.
makeToken(parser) {
makeToken() {
if ( this.pattern === '*' ) { return; }
if ( this.isRegex ) {
return this.extractTokenFromRegex();
}
const match = this.extractTokenFromPattern(parser);
const match = this.extractTokenFromPattern();
if ( match === null ) { return; }
this.token = match.token;
this.tokenHash = urlTokenizer.tokenHashFromString(this.token);
@ -3096,10 +3124,10 @@ const FilterParser = class {
}
// Note: a one-char token is better than a documented bad token.
extractTokenFromPattern(parser) {
extractTokenFromPattern() {
let bestMatch = null;
let bestBadness = 0x7FFFFFFF;
for ( const match of parser.patternTokens() ) {
for ( const match of this.parser.patternTokens() ) {
const badness = match.token.length > 1
? this.badTokens.get(match.token) || 0
: 1;
@ -3162,18 +3190,13 @@ const FilterParser = class {
}
}
isJustPattern() {
return this.hasOptionUnits === false;
hasNoOptionUnits() {
return this.optionUnitBits === 0;
}
isJustOrigin() {
return this.hasOptionUnits &&
this.isRegex === false &&
this.modifyType === undefined &&
this.strictParty === 0 &&
this.headerOpt === undefined &&
this.denyallowOpt === '' &&
this.domainOpt !== '' && (
return this.optionUnitBits === this.DOMAIN_BIT &&
this.isRegex === false && (
this.pattern === '*' || (
this.anchor === 0b010 &&
/^(?:http[s*]?:(?:\/\/)?)$/.test(this.pattern)
@ -3190,6 +3213,15 @@ const FilterParser = class {
}
};
FilterParser.prototype.DOMAIN_BIT = 0b00000001;
FilterParser.prototype.DENYALLOW_BIT = 0b00000010;
FilterParser.prototype.HEADER_BIT = 0b00000100;
FilterParser.prototype.STRICT_PARTY_BIT = 0b00001000;
FilterParser.prototype.CSP_BIT = 0b00010000;
FilterParser.prototype.QUERYPRUNE_BIT = 0b00100000;
FilterParser.prototype.REDIRECT_BIT = 0b01000000;
/******************************************************************************/
FilterParser.parse = (( ) => {
@ -3558,19 +3590,38 @@ FilterContainer.prototype.compile = function(parser, writer) {
return false;
}
// Pure hostnames, use more efficient dictionary lookup
// https://github.com/chrisaljoudi/uBlock/issues/665
// Create a dict keyed on request type etc.
if ( parsed.isPureHostname && parsed.isJustPattern() ) {
parsed.tokenHash = this.dotTokenHash;
this.compileToAtomicFilter(parsed, parsed.pattern, writer);
return true;
// 0 = network filters
// 1 = network filters: bad filters
writer.select(parsed.badFilter ? 1 : 0);
// Reminder:
// `redirect=` is a combination of a `redirect-rule` filter and a
// block filter.
if ( parsed.modifyType === parser.OPTTokenRedirect ) {
parsed.modifyType = parser.OPTTokenRedirectRule;
const parsedBlock = parsed.clone();
parsedBlock.modifyType = undefined;
parsedBlock.optionUnitBits &= ~parsed.REDIRECT_BIT;
this.compileParsed(parsedBlock, writer);
}
if ( parser.patternIsMatchAll() === false ) {
parsed.makeToken(parser);
this.compileParsed(parsed, writer);
return true;
};
/******************************************************************************/
FilterContainer.prototype.compileParsed = function(parsed, writer) {
// Pure hostnames, use more efficient dictionary lookup
if ( parsed.isPureHostname && parsed.hasNoOptionUnits() ) {
parsed.tokenHash = this.dotTokenHash;
this.compileToAtomicFilter(parsed, parsed.pattern, writer);
return;
}
parsed.makeToken();
// Special pattern/option cases:
// - `*$domain=...`
// - `|http://$domain=...`
@ -3595,7 +3646,7 @@ FilterContainer.prototype.compile = function(parser, writer) {
entities.push(hn);
}
}
if ( entities.length === 0 ) { return true; }
if ( entities.length === 0 ) { return; }
parsed.tokenHash = tokenHash;
const leftAnchored = (parsed.anchor & 0b010) !== 0;
for ( const entity of entities ) {
@ -3607,7 +3658,7 @@ FilterContainer.prototype.compile = function(parser, writer) {
parsed, FilterCompositeAll.compile(units), writer
);
}
return true;
return;
}
const units = [];
@ -3656,30 +3707,16 @@ FilterContainer.prototype.compile = function(parser, writer) {
// Modifier
//
// IMPORTANT: the modifier unit MUST always appear first in a sequence.
//
// Reminder: A block filter is implicit with `redirect=` modifier.
// IMPORTANT: the modifier unit MUST always appear first in a sequence
if ( parsed.modifyType !== undefined ) {
if (
parsed.modifyType === parser.OPTTokenRedirect &&
parsed.action !== AllowAction
) {
const fdata = units.length === 1
? units[0]
: FilterCompositeAll.compile(units);
this.compileToAtomicFilter(parsed, fdata, writer);
parsed.modifyType = parser.OPTTokenRedirectRule;
}
units.unshift(FilterModifier.compile(parsed));
parsed.action = ModifyAction;
parsed.action = (parsed.action & ~ActionBitsMask) | ModifyAction;
}
const fdata = units.length === 1
? units[0]
: FilterCompositeAll.compile(units);
this.compileToAtomicFilter(parsed, fdata, writer);
return true;
};
/******************************************************************************/
@ -3689,10 +3726,6 @@ FilterContainer.prototype.compileToAtomicFilter = function(
fdata,
writer
) {
// 0 = network filters
// 1 = network filters: bad filters
writer.select(parsed.badFilter ? 1 : 0);
const catBits = parsed.action | parsed.party;
let typeBits = parsed.typeBits;