1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-02 00:42:45 +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; return this;
} }
next() { next() {

View File

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