1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-09-18 17:02:27 +02:00

Add new static network filter option: redirect-rule=

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/310

The purpose of this new option is to add the ability
to create standalone redirect rule without being forced
to create a block filter (a corresponding block filter
is always created when using the `redirect=`).

Additionally:

The syntax `*$redirect=token,...` is now supported, there
is no need to "trick" the filter parser with
`*/*$redirect=token,...` in order to create redirect rules
which are meant to match all paths.

Filters of the form `|http*://` will be normalized into
two corresponding filters `|https://` and `|http://` so as
to reduce the number of filters in the buckets of
untokenizable filters.
This commit is contained in:
Raymond Hill 2019-08-03 10:18:47 -04:00
parent 663fd5fab0
commit aa73f292ec
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
2 changed files with 78 additions and 58 deletions

View File

@ -303,7 +303,7 @@ RedirectEngine.prototype.lookup = function(fctxt) {
for (;;) { for (;;) {
if ( this.ruleSources.has(src) ) { if ( this.ruleSources.has(src) ) {
for ( let i = 0; i < n; i++ ) { for ( let i = 0; i < n; i++ ) {
const entries = this.rules.get(src + ' ' + desAll[i] + ' ' + type); const entries = this.rules.get(`${src} ${desAll[i]} ${type}`);
if ( entries && this.lookupToken(entries, reqURL) ) { if ( entries && this.lookupToken(entries, reqURL) ) {
return this.resourceNameRegister; return this.resourceNameRegister;
} }
@ -414,14 +414,18 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
} }
} }
const pattern = const path = matches[2] || '';
let pattern =
des des
.replace(/\*/g, '[\\w.%-]*') .replace(/\*/g, '[\\w.%-]*')
.replace(/\./g, '\\.') + .replace(/\./g, '\\.') +
matches[2] path
.replace(/[.+?{}()|[\]\/\\]/g, '\\$&') .replace(/[.+?{}()|[\]\/\\]/g, '\\$&')
.replace(/\^/g, '[^\\w.%-]') .replace(/\^/g, '[^\\w.%-]')
.replace(/\*/g, '.*?'); .replace(/\*/g, '.*?');
if ( pattern === '' ) {
pattern = '^';
}
let type, let type,
redirect = '', redirect = '',
@ -431,6 +435,10 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
redirect = option.slice(9); redirect = option.slice(9);
continue; continue;
} }
if ( option.startsWith('redirect-rule=') ) {
redirect = option.slice(14);
continue;
}
if ( option.startsWith('domain=') ) { if ( option.startsWith('domain=') ) {
srchns = option.slice(7).split('|'); srchns = option.slice(7).split('|');
continue; continue;
@ -468,12 +476,14 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
out.push(`${srchn}\t${deshn}\t${type}\t${pattern}\t${redirect}`); out.push(`${srchn}\t${deshn}\t${type}\t${pattern}\t${redirect}`);
} }
if ( out.length === 0 ) { return; }
return out; return out;
}; };
/******************************************************************************/ /******************************************************************************/
RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*)([^$]+)\$([^$]+)$/; RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*)([^$]+)?\$([^$]+)$/;
RedirectEngine.prototype.supportedTypes = new Map([ RedirectEngine.prototype.supportedTypes = new Map([
[ 'css', 'stylesheet' ], [ 'css', 'stylesheet' ],

View File

@ -1883,7 +1883,7 @@ FilterParser.prototype.reset = function() {
this.isPureHostname = false; this.isPureHostname = false;
this.isRegex = false; this.isRegex = false;
this.raw = ''; this.raw = '';
this.redirect = false; this.redirect = 0;
this.token = '*'; this.token = '*';
this.tokenHash = this.noTokenHash; this.tokenHash = this.noTokenHash;
this.tokenBeg = 0; this.tokenBeg = 0;
@ -1963,25 +1963,9 @@ FilterParser.prototype.parseOptions = function(s) {
this.parsePartyOption(false, not); this.parsePartyOption(false, not);
continue; continue;
} }
// https://issues.adblockplus.org/ticket/616 if ( opt === 'first-party' || opt === '1p' ) {
// `generichide` concept already supported, just a matter of this.parsePartyOption(true, not);
// adding support for the new keyword. continue;
if ( opt === 'elemhide' || opt === 'generichide' ) {
if ( not === false ) {
this.parseTypeOption('generichide', false);
continue;
}
this.unsupported = true;
break;
}
// Test before handling all other types.
if ( opt.startsWith('redirect=') ) {
if ( this.action === BlockAction ) {
this.redirect = true;
continue;
}
this.unsupported = true;
break;
} }
if ( this.toNormalizedType.hasOwnProperty(opt) ) { if ( this.toNormalizedType.hasOwnProperty(opt) ) {
this.parseTypeOption(opt, not); this.parseTypeOption(opt, not);
@ -2002,8 +1986,12 @@ FilterParser.prototype.parseOptions = function(s) {
this.important = Important; this.important = Important;
continue; continue;
} }
if ( opt === 'first-party' || opt === '1p' ) { if ( /^redirect(?:-rule)?=/.test(opt) ) {
this.parsePartyOption(true, not); if ( this.redirect !== 0 ) {
this.unsupported = true;
break;
}
this.redirect = opt.charCodeAt(8) === 0x3D /* '=' */ ? 1 : 2;
continue; continue;
} }
if ( if (
@ -2036,6 +2024,11 @@ FilterParser.prototype.parseOptions = function(s) {
break; break;
} }
// Redirect rules can't be exception filters.
if ( this.redirect !== 0 && this.action !== BlockAction ) {
this.unsupported = true;
}
// Negated network types? Toggle on all network type bits. // Negated network types? Toggle on all network type bits.
// Negated non-network types can only toggle themselves. // Negated non-network types can only toggle themselves.
if ( (this.notTypes & allNetworkTypesBits) !== 0 ) { if ( (this.notTypes & allNetworkTypesBits) !== 0 ) {
@ -2216,6 +2209,10 @@ FilterParser.prototype.parse = function(raw) {
if ( s === '' ) { if ( s === '' ) {
s = '*'; s = '*';
} }
// TODO: remove once redirect rules with `*/*` pattern are no longer used.
else if ( this.redirect !== 0 && s === '/' ) {
s = '*';
}
// https://github.com/gorhill/uBlock/issues/1047 // https://github.com/gorhill/uBlock/issues/1047
// Hostname-anchored makes no sense if matching all requests. // Hostname-anchored makes no sense if matching all requests.
@ -2332,9 +2329,8 @@ FilterParser.prototype.makeToken = function() {
FilterParser.prototype.isJustOrigin = function() { FilterParser.prototype.isJustOrigin = function() {
return this.dataType === undefined && return this.dataType === undefined &&
this.redirect === false &&
this.domainOpt !== '' && this.domainOpt !== '' &&
/^(?:\*|https?:(?:\/\/)?)$/.test(this.f) && /^(?:\*|http[s*]?:(?:\/\/)?)$/.test(this.f) &&
this.domainOpt.indexOf('~') === -1; this.domainOpt.indexOf('~') === -1;
}; };
@ -2654,6 +2650,23 @@ FilterContainer.prototype.compile = function(raw, writer) {
return false; return false;
} }
// Redirect rule
if ( parsed.redirect !== 0 ) {
const result = this.compileRedirectRule(parsed, writer);
if ( result === false ) {
const who = writer.properties.get('assetKey') || '?';
µb.logger.writeOne({
realm: 'message',
type: 'error',
text: `Invalid redirect rule in ${who}: ${raw}`
});
return false;
}
if ( parsed.redirect === 2 ) {
return true;
}
}
// Pure hostnames, use more efficient dictionary lookup // Pure hostnames, use more efficient dictionary lookup
// https://github.com/chrisaljoudi/uBlock/issues/665 // https://github.com/chrisaljoudi/uBlock/issues/665
// Create a dict keyed on request type etc. // Create a dict keyed on request type etc.
@ -2694,25 +2707,24 @@ FilterContainer.prototype.compile = function(raw, writer) {
} else { } else {
fdata = FilterGenericHnAnchored.compile(parsed); fdata = FilterGenericHnAnchored.compile(parsed);
} }
} else if ( parsed.anchor === 0x2 && parsed.isJustOrigin() ) {
const hostnames = parsed.domainOpt.split('|');
const isHTTPS = parsed.f === 'https://' || parsed.f === 'http*://';
const isHTTP = parsed.f === 'http://' || parsed.f === 'http*://';
for ( const hn of hostnames ) {
if ( isHTTPS ) {
parsed.tokenHash = this.anyHTTPSTokenHash;
this.compileToAtomicFilter(parsed, hn, writer);
}
if ( isHTTP ) {
parsed.tokenHash = this.anyHTTPTokenHash;
this.compileToAtomicFilter(parsed, hn, writer);
}
}
return true;
} else if ( parsed.wildcarded || parsed.tokenHash === parsed.noTokenHash ) { } else if ( parsed.wildcarded || parsed.tokenHash === parsed.noTokenHash ) {
fdata = FilterGeneric.compile(parsed); fdata = FilterGeneric.compile(parsed);
} else if ( parsed.anchor === 0x2 ) { } else if ( parsed.anchor === 0x2 ) {
if ( parsed.isJustOrigin() ) {
if ( parsed.f === 'https://' ) {
parsed.tokenHash = this.anyHTTPSTokenHash;
for ( const hn of parsed.domainOpt.split('|') ) {
this.compileToAtomicFilter(parsed, hn, writer);
}
return true;
}
if ( parsed.f === 'http://' ) {
parsed.tokenHash = this.anyHTTPTokenHash;
for ( const hn of parsed.domainOpt.split('|') ) {
this.compileToAtomicFilter(parsed, hn, writer);
}
return true;
}
}
fdata = FilterPlainLeftAnchored.compile(parsed); fdata = FilterPlainLeftAnchored.compile(parsed);
} else if ( parsed.anchor === 0x1 ) { } else if ( parsed.anchor === 0x1 ) {
fdata = FilterPlainRightAnchored.compile(parsed); fdata = FilterPlainRightAnchored.compile(parsed);
@ -2747,11 +2759,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(
// 0 = network filters // 0 = network filters
// 1 = network filters: bad filters // 1 = network filters: bad filters
if ( parsed.badFilter ) { writer.select(parsed.badFilter ? 1 : 0);
writer.select(1);
} else {
writer.select(0);
}
const descBits = parsed.action | parsed.important | parsed.party; const descBits = parsed.action | parsed.important | parsed.party;
let typeBits = parsed.types; let typeBits = parsed.types;
@ -2777,17 +2785,19 @@ FilterContainer.prototype.compileToAtomicFilter = function(
bitOffset += 1; bitOffset += 1;
typeBits >>>= 1; typeBits >>>= 1;
} while ( typeBits !== 0 ); } while ( typeBits !== 0 );
};
// Only static filter with an explicit type can be redirected. If we reach /******************************************************************************/
// this point, it's because there is one or more explicit type.
if ( parsed.redirect ) { FilterContainer.prototype.compileRedirectRule = function(parsed, writer) {
const redirects = µb.redirectEngine.compileRuleFromStaticFilter(parsed.raw); const redirects = µb.redirectEngine.compileRuleFromStaticFilter(parsed.raw);
if ( Array.isArray(redirects) ) { if ( Array.isArray(redirects) === false ) { return false; }
for ( const redirect of redirects ) { writer.select(parsed.badFilter ? 1 : 0);
writer.push([ typeNameToTypeValue.redirect, redirect ]); const type = typeNameToTypeValue.redirect;
} for ( const redirect of redirects ) {
} writer.push([ type, redirect ]);
} }
return true;
}; };
/******************************************************************************/ /******************************************************************************/