mirror of https://github.com/gorhill/uBlock.git
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:
parent
663fd5fab0
commit
aa73f292ec
|
@ -303,7 +303,7 @@ RedirectEngine.prototype.lookup = function(fctxt) {
|
|||
for (;;) {
|
||||
if ( this.ruleSources.has(src) ) {
|
||||
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) ) {
|
||||
return this.resourceNameRegister;
|
||||
}
|
||||
|
@ -414,14 +414,18 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
|
|||
}
|
||||
}
|
||||
|
||||
const pattern =
|
||||
const path = matches[2] || '';
|
||||
let pattern =
|
||||
des
|
||||
.replace(/\*/g, '[\\w.%-]*')
|
||||
.replace(/\./g, '\\.') +
|
||||
matches[2]
|
||||
path
|
||||
.replace(/[.+?{}()|[\]\/\\]/g, '\\$&')
|
||||
.replace(/\^/g, '[^\\w.%-]')
|
||||
.replace(/\*/g, '.*?');
|
||||
if ( pattern === '' ) {
|
||||
pattern = '^';
|
||||
}
|
||||
|
||||
let type,
|
||||
redirect = '',
|
||||
|
@ -431,6 +435,10 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
|
|||
redirect = option.slice(9);
|
||||
continue;
|
||||
}
|
||||
if ( option.startsWith('redirect-rule=') ) {
|
||||
redirect = option.slice(14);
|
||||
continue;
|
||||
}
|
||||
if ( option.startsWith('domain=') ) {
|
||||
srchns = option.slice(7).split('|');
|
||||
continue;
|
||||
|
@ -468,12 +476,14 @@ RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
|
|||
out.push(`${srchn}\t${deshn}\t${type}\t${pattern}\t${redirect}`);
|
||||
}
|
||||
|
||||
if ( out.length === 0 ) { return; }
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*)([^$]+)\$([^$]+)$/;
|
||||
RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*)([^$]+)?\$([^$]+)$/;
|
||||
|
||||
RedirectEngine.prototype.supportedTypes = new Map([
|
||||
[ 'css', 'stylesheet' ],
|
||||
|
|
|
@ -1883,7 +1883,7 @@ FilterParser.prototype.reset = function() {
|
|||
this.isPureHostname = false;
|
||||
this.isRegex = false;
|
||||
this.raw = '';
|
||||
this.redirect = false;
|
||||
this.redirect = 0;
|
||||
this.token = '*';
|
||||
this.tokenHash = this.noTokenHash;
|
||||
this.tokenBeg = 0;
|
||||
|
@ -1963,25 +1963,9 @@ FilterParser.prototype.parseOptions = function(s) {
|
|||
this.parsePartyOption(false, not);
|
||||
continue;
|
||||
}
|
||||
// https://issues.adblockplus.org/ticket/616
|
||||
// `generichide` concept already supported, just a matter of
|
||||
// adding support for the new keyword.
|
||||
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 ( opt === 'first-party' || opt === '1p' ) {
|
||||
this.parsePartyOption(true, not);
|
||||
continue;
|
||||
}
|
||||
if ( this.toNormalizedType.hasOwnProperty(opt) ) {
|
||||
this.parseTypeOption(opt, not);
|
||||
|
@ -2002,8 +1986,12 @@ FilterParser.prototype.parseOptions = function(s) {
|
|||
this.important = Important;
|
||||
continue;
|
||||
}
|
||||
if ( opt === 'first-party' || opt === '1p' ) {
|
||||
this.parsePartyOption(true, not);
|
||||
if ( /^redirect(?:-rule)?=/.test(opt) ) {
|
||||
if ( this.redirect !== 0 ) {
|
||||
this.unsupported = true;
|
||||
break;
|
||||
}
|
||||
this.redirect = opt.charCodeAt(8) === 0x3D /* '=' */ ? 1 : 2;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
|
@ -2036,6 +2024,11 @@ FilterParser.prototype.parseOptions = function(s) {
|
|||
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 non-network types can only toggle themselves.
|
||||
if ( (this.notTypes & allNetworkTypesBits) !== 0 ) {
|
||||
|
@ -2216,6 +2209,10 @@ FilterParser.prototype.parse = function(raw) {
|
|||
if ( 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
|
||||
// Hostname-anchored makes no sense if matching all requests.
|
||||
|
@ -2332,9 +2329,8 @@ FilterParser.prototype.makeToken = function() {
|
|||
|
||||
FilterParser.prototype.isJustOrigin = function() {
|
||||
return this.dataType === undefined &&
|
||||
this.redirect === false &&
|
||||
this.domainOpt !== '' &&
|
||||
/^(?:\*|https?:(?:\/\/)?)$/.test(this.f) &&
|
||||
/^(?:\*|http[s*]?:(?:\/\/)?)$/.test(this.f) &&
|
||||
this.domainOpt.indexOf('~') === -1;
|
||||
};
|
||||
|
||||
|
@ -2654,6 +2650,23 @@ FilterContainer.prototype.compile = function(raw, writer) {
|
|||
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
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/665
|
||||
// Create a dict keyed on request type etc.
|
||||
|
@ -2694,25 +2707,24 @@ FilterContainer.prototype.compile = function(raw, writer) {
|
|||
} else {
|
||||
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 ) {
|
||||
fdata = FilterGeneric.compile(parsed);
|
||||
} 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);
|
||||
} else if ( parsed.anchor === 0x1 ) {
|
||||
fdata = FilterPlainRightAnchored.compile(parsed);
|
||||
|
@ -2747,11 +2759,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(
|
|||
|
||||
// 0 = network filters
|
||||
// 1 = network filters: bad filters
|
||||
if ( parsed.badFilter ) {
|
||||
writer.select(1);
|
||||
} else {
|
||||
writer.select(0);
|
||||
}
|
||||
writer.select(parsed.badFilter ? 1 : 0);
|
||||
|
||||
const descBits = parsed.action | parsed.important | parsed.party;
|
||||
let typeBits = parsed.types;
|
||||
|
@ -2777,17 +2785,19 @@ FilterContainer.prototype.compileToAtomicFilter = function(
|
|||
bitOffset += 1;
|
||||
typeBits >>>= 1;
|
||||
} 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 ) {
|
||||
const redirects = µb.redirectEngine.compileRuleFromStaticFilter(parsed.raw);
|
||||
if ( Array.isArray(redirects) ) {
|
||||
for ( const redirect of redirects ) {
|
||||
writer.push([ typeNameToTypeValue.redirect, redirect ]);
|
||||
}
|
||||
}
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.compileRedirectRule = function(parsed, writer) {
|
||||
const redirects = µb.redirectEngine.compileRuleFromStaticFilter(parsed.raw);
|
||||
if ( Array.isArray(redirects) === false ) { return false; }
|
||||
writer.select(parsed.badFilter ? 1 : 0);
|
||||
const type = typeNameToTypeValue.redirect;
|
||||
for ( const redirect of redirects ) {
|
||||
writer.push([ type, redirect ]);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
Loading…
Reference in New Issue