mirror of https://github.com/gorhill/uBlock.git
Re-classify `redirect=` option as a modifier option
This commit moves the parsing, compiling and enforcement of the `redirect=` and `redirect-rule=` network filter options into the static network filtering engine as modifier options -- just like `csp=` and `queryprune=`. This solves the two following issues: - https://github.com/gorhill/uBlock/issues/3590 - https://github.com/uBlockOrigin/uBlock-issues/issues/1008#issuecomment-716164214 Additionally, `redirect=` option is not longer afflicted by static network filtering syntax quirks, `redirect=` filters can be used with any other static filtering modifier options, can be excepted using `@@` and can be badfilter-ed. Since more than one `redirect=` directives could be found to apply to a single network request, the concept of redirect priority is introduced. By default, `redirect=` directives have an implicit priority of 0. Filter authors can declare an explicit priority by appending `:[integer]` to the token of the `redirect=` option, for example: ||example.com/*.js$1p,script,redirect=noopjs:100 The priority dictates which redirect token out of many will be ultimately used. Cases of multiple `redirect=` directives applying to a single blocked network request are expected to be rather unlikely. Explicit redirect priority should be used if and only if there is a case of redirect ambiguity to solve.
This commit is contained in:
parent
1b44bf276a
commit
157cef6034
|
@ -139,8 +139,8 @@ const µBlock = (( ) => { // jshint ignore:line
|
|||
|
||||
// Read-only
|
||||
systemSettings: {
|
||||
compiledMagic: 30, // Increase when compiled format changes
|
||||
selfieMagic: 30, // Increase when selfie format changes
|
||||
compiledMagic: 31, // Increase when compiled format changes
|
||||
selfieMagic: 31, // Increase when selfie format changes
|
||||
},
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501
|
||||
|
|
|
@ -302,7 +302,13 @@ const processLoggerEntries = function(response) {
|
|||
if ( autoDeleteVoidedRows ) { continue; }
|
||||
parsed.voided = true;
|
||||
}
|
||||
if ( parsed.type === 'main_frame' && parsed.aliased === false ) {
|
||||
if (
|
||||
parsed.type === 'main_frame' &&
|
||||
parsed.aliased === false && (
|
||||
parsed.filter === undefined ||
|
||||
parsed.filter.source !== 'redirect'
|
||||
)
|
||||
) {
|
||||
const separator = createLogSeparator(parsed, unboxed.url);
|
||||
loggerEntries.unshift(separator);
|
||||
if ( rowFilterer.filterOne(separator) ) {
|
||||
|
|
|
@ -647,22 +647,9 @@ const PageStore = class {
|
|||
// Redirect non-blocked request?
|
||||
if ( (fctxt.itype & fctxt.INLINE_ANY) === 0 ) {
|
||||
if ( result === 1 ) {
|
||||
if ( µb.hiddenSettings.ignoreRedirectFilters !== true ) {
|
||||
const redirectURL = µb.redirectEngine.toURL(fctxt);
|
||||
if ( redirectURL !== undefined ) {
|
||||
fctxt.redirectURL = redirectURL;
|
||||
this.internalRedirectionCount += 1;
|
||||
fctxt.pushFilter({
|
||||
source: 'redirect',
|
||||
raw: µb.redirectEngine.resourceNameRegister
|
||||
});
|
||||
}
|
||||
}
|
||||
this.redirectBlockedRequest(fctxt);
|
||||
} else if ( snfe.hasQuery(fctxt) ) {
|
||||
const directives = snfe.filterQuery(fctxt);
|
||||
if ( directives !== undefined && loggerEnabled ) {
|
||||
fctxt.pushFilters(directives.map(a => a.logData()));
|
||||
}
|
||||
this.redirectNonBlockedRequest(fctxt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -675,6 +662,32 @@ const PageStore = class {
|
|||
return result;
|
||||
}
|
||||
|
||||
redirectBlockedRequest(fctxt) {
|
||||
if ( µb.hiddenSettings.ignoreRedirectFilters === true ) { return; }
|
||||
const directive = µb.staticNetFilteringEngine.redirectRequest(fctxt);
|
||||
if ( directive === undefined ) { return; }
|
||||
this.internalRedirectionCount += 1;
|
||||
if ( µb.logger.enabled !== true ) { return; }
|
||||
fctxt.pushFilter(directive.logData());
|
||||
if ( fctxt.redirectURL === undefined ) { return; }
|
||||
fctxt.pushFilter({
|
||||
source: 'redirect',
|
||||
raw: µb.redirectEngine.resourceNameRegister
|
||||
});
|
||||
}
|
||||
|
||||
redirectNonBlockedRequest(fctxt) {
|
||||
const directives = µb.staticNetFilteringEngine.filterQuery(fctxt);
|
||||
if ( directives === undefined ) { return; }
|
||||
if ( µb.logger.enabled !== true ) { return; }
|
||||
fctxt.pushFilters(directives.map(a => a.logData()));
|
||||
if ( fctxt.redirectURL === undefined ) { return; }
|
||||
fctxt.pushFilter({
|
||||
source: 'redirect',
|
||||
raw: fctxt.redirectURL
|
||||
});
|
||||
}
|
||||
|
||||
filterCSPReport(fctxt) {
|
||||
if (
|
||||
µb.sessionSwitches.evaluateZ(
|
||||
|
|
|
@ -275,24 +275,13 @@ const RedirectEngine = function() {
|
|||
this.aliases = new Map();
|
||||
this.resources = new Map();
|
||||
this.reset();
|
||||
this.modifyTime = Date.now();
|
||||
this.resourceNameRegister = '';
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.reset = function() {
|
||||
this.rules = new Map();
|
||||
this.ruleSources = new Set();
|
||||
this.ruleDestinations = new Set();
|
||||
this.resetCache();
|
||||
this.modifyTime = Date.now();
|
||||
};
|
||||
|
||||
RedirectEngine.prototype.resetCache = function() {
|
||||
this._src = '';
|
||||
this._srcAll = [ '*' ];
|
||||
this._des = '';
|
||||
this._desAll = [ '*' ];
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
@ -302,301 +291,25 @@ RedirectEngine.prototype.freeze = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.toBroaderHostname = function(hostname) {
|
||||
const pos = hostname.indexOf('.');
|
||||
if ( pos !== -1 ) {
|
||||
return hostname.slice(pos + 1);
|
||||
}
|
||||
return hostname !== '*' ? '*' : '';
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.decomposeHostname = function(hn, dict, out) {
|
||||
let i = 0;
|
||||
for (;;) {
|
||||
if ( dict.has(hn) ) {
|
||||
out[i] = hn; i += 1;
|
||||
}
|
||||
hn = this.toBroaderHostname(hn);
|
||||
if ( hn === '' ) { break; }
|
||||
}
|
||||
out.length = i;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.lookup = function(fctxt) {
|
||||
const src = fctxt.getDocHostname();
|
||||
const des = fctxt.getHostname();
|
||||
const type = fctxt.type;
|
||||
if ( src !== this._src ) {
|
||||
this._src = src;
|
||||
this.decomposeHostname(src, this.ruleSources, this._srcAll);
|
||||
}
|
||||
if ( this._srcAll.length === 0 ) { return; }
|
||||
if ( des !== this._des ) {
|
||||
this._des = des;
|
||||
this.decomposeHostname(des, this.ruleDestinations, this._desAll);
|
||||
}
|
||||
if ( this._desAll.length === 0 ) { return; }
|
||||
const reqURL = fctxt.url;
|
||||
for ( const src of this._srcAll ) {
|
||||
for ( const des of this._desAll ) {
|
||||
let entries = this.rules.get(`${src} ${des} ${type}`);
|
||||
if ( entries !== undefined ) {
|
||||
const rule = this.lookupRule(entries, reqURL);
|
||||
if ( rule !== undefined ) { return rule; }
|
||||
}
|
||||
entries = this.rules.get(`${src} ${des} *`);
|
||||
if ( entries !== undefined ) {
|
||||
const rule = this.lookupRule(entries, reqURL);
|
||||
if ( rule !== undefined ) { return rule; }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RedirectEngine.prototype.lookupRule = function(entries, reqURL) {
|
||||
for ( const entry of entries ) {
|
||||
if ( entry.pat instanceof RegExp === false ) {
|
||||
entry.pat = new RegExp(entry.pat, 'i');
|
||||
}
|
||||
if ( entry.pat.test(reqURL) ) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.toURL = function(fctxt) {
|
||||
const rule = this.lookup(fctxt);
|
||||
if ( rule === undefined ) { return; }
|
||||
let token = this.resourceNameRegister = rule.tok;
|
||||
RedirectEngine.prototype.tokenToURL = function(fctxt, token) {
|
||||
const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */;
|
||||
if ( asDataURI ) {
|
||||
token = token.slice(1);
|
||||
}
|
||||
const entry = this.resources.get(this.aliases.get(token) || token);
|
||||
if ( entry !== undefined ) {
|
||||
if ( entry === undefined ) { return; }
|
||||
this.resourceNameRegister = token;
|
||||
return entry.toURL(fctxt, asDataURI);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) {
|
||||
this.ruleSources.add(src);
|
||||
this.ruleDestinations.add(des);
|
||||
const key = `${src} ${des} ${type}`,
|
||||
entries = this.rules.get(key);
|
||||
if ( entries === undefined ) {
|
||||
this.rules.set(key, [ { tok: redirect, pat: pattern } ]);
|
||||
this.modifyTime = Date.now();
|
||||
return;
|
||||
}
|
||||
let entry;
|
||||
for ( var i = 0, n = entries.length; i < n; i++ ) {
|
||||
entry = entries[i];
|
||||
if ( redirect === entry.tok ) { break; }
|
||||
}
|
||||
if ( i === n ) {
|
||||
entries.push({ tok: redirect, pat: pattern });
|
||||
return;
|
||||
}
|
||||
let p = entry.pat;
|
||||
if ( p instanceof RegExp ) {
|
||||
p = p.source;
|
||||
}
|
||||
// Duplicate?
|
||||
let pos = p.indexOf(pattern);
|
||||
if ( pos !== -1 ) {
|
||||
if ( pos === 0 || p.charAt(pos - 1) === '|' ) {
|
||||
pos += pattern.length;
|
||||
if ( pos === p.length || p.charAt(pos) === '|' ) { return; }
|
||||
}
|
||||
}
|
||||
entry.pat = p + '|' + pattern;
|
||||
RedirectEngine.prototype.toSelfie = async function() {
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.fromCompiledRule = function(line) {
|
||||
const fields = line.split('\t');
|
||||
if ( fields.length !== 5 ) { return; }
|
||||
this.addRule(fields[0], fields[1], fields[2], fields[3], fields[4]);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.compileRuleFromStaticFilter = function(line) {
|
||||
const matches = this.reFilterParser.exec(line);
|
||||
if ( matches === null || matches.length !== 4 ) { return; }
|
||||
|
||||
const des = matches[1] || '';
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/572
|
||||
// Extract best possible hostname.
|
||||
let deshn = des;
|
||||
let pos = deshn.lastIndexOf('*');
|
||||
if ( pos !== -1 ) {
|
||||
deshn = deshn.slice(pos + 1);
|
||||
pos = deshn.indexOf('.');
|
||||
if ( pos !== -1 ) {
|
||||
deshn = deshn.slice(pos + 1);
|
||||
} else {
|
||||
deshn = '';
|
||||
}
|
||||
}
|
||||
|
||||
const path = matches[2] || '';
|
||||
let pattern =
|
||||
des
|
||||
.replace(/\*/g, '[\\w.%-]*')
|
||||
.replace(/\./g, '\\.') +
|
||||
path
|
||||
.replace(/\|$/, '$')
|
||||
.replace(/[.+?{}()|[\]\/\\]/g, '\\$&')
|
||||
.replace(/\^/g, '[^\\w.%-]')
|
||||
.replace(/\*/g, '.*?');
|
||||
if ( pattern === '' ) {
|
||||
pattern = '^';
|
||||
}
|
||||
|
||||
let type,
|
||||
redirect = '',
|
||||
srchns = [];
|
||||
for ( const option of matches[3].trim().split(/,/) ) {
|
||||
if ( option.startsWith('redirect=') ) {
|
||||
redirect = option.slice(9);
|
||||
continue;
|
||||
}
|
||||
if ( option.startsWith('redirect-rule=') ) {
|
||||
redirect = option.slice(14);
|
||||
continue;
|
||||
}
|
||||
if ( option === 'empty' ) {
|
||||
redirect = 'empty';
|
||||
continue;
|
||||
}
|
||||
if ( option === 'mp4' ) {
|
||||
redirect = 'noopmp4-1s';
|
||||
continue;
|
||||
}
|
||||
if ( option.startsWith('domain=') ) {
|
||||
srchns = option.slice(7).split('|');
|
||||
continue;
|
||||
}
|
||||
if ( (option === 'first-party' || option === '1p') && deshn !== '' ) {
|
||||
srchns.push(µBlock.URI.domainFromHostname(deshn) || deshn);
|
||||
continue;
|
||||
}
|
||||
// One and only one type must be specified.
|
||||
if ( this.supportedTypes.has(option) ) {
|
||||
if ( type !== undefined ) { return; }
|
||||
type = this.supportedTypes.get(option);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Need a resource token.
|
||||
if ( redirect === '' ) { return; }
|
||||
|
||||
// Need one single type -- not negated.
|
||||
if ( type === undefined ) {
|
||||
if ( redirect === 'empty' ) {
|
||||
type = '*';
|
||||
} else if ( redirect === 'noopmp4-1s' ) {
|
||||
type = 'media';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( deshn === '' ) {
|
||||
deshn = '*';
|
||||
}
|
||||
|
||||
if ( srchns.length === 0 ) {
|
||||
srchns.push('*');
|
||||
}
|
||||
|
||||
const out = [];
|
||||
for ( const srchn of srchns ) {
|
||||
if ( srchn === '' ) { continue; }
|
||||
if ( srchn.startsWith('~') ) { continue; }
|
||||
out.push(`${srchn}\t${deshn}\t${type}\t${pattern}\t${redirect}`);
|
||||
}
|
||||
|
||||
if ( out.length === 0 ) { return; }
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.reFilterParser = /^(?:\|\|([^\/:?#^]+)|\*?)([^$]+)?\$([^$]+)$/;
|
||||
|
||||
RedirectEngine.prototype.supportedTypes = new Map([
|
||||
[ 'css', 'stylesheet' ],
|
||||
[ 'font', 'font' ],
|
||||
[ 'image', 'image' ],
|
||||
[ 'media', 'media' ],
|
||||
[ 'object', 'object' ],
|
||||
[ 'script', 'script' ],
|
||||
[ 'stylesheet', 'stylesheet' ],
|
||||
[ 'frame', 'sub_frame' ],
|
||||
[ 'subdocument', 'sub_frame' ],
|
||||
[ 'xhr', 'xmlhttprequest' ],
|
||||
[ 'xmlhttprequest', 'xmlhttprequest' ],
|
||||
]);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.toSelfie = function(path) {
|
||||
// Because rules may contains RegExp instances, we need to manually
|
||||
// convert it to a serializable format. The serialized format must be
|
||||
// suitable to be used as an argument to the Map() constructor.
|
||||
const rules = [];
|
||||
for ( const item of this.rules ) {
|
||||
const rule = [ item[0], [] ];
|
||||
const entries = item[1];
|
||||
let i = entries.length;
|
||||
while ( i-- ) {
|
||||
const entry = entries[i];
|
||||
rule[1].push({
|
||||
tok: entry.tok,
|
||||
pat: entry.pat instanceof RegExp ? entry.pat.source : entry.pat
|
||||
});
|
||||
}
|
||||
rules.push(rule);
|
||||
}
|
||||
return µBlock.assets.put(
|
||||
`${path}/main`,
|
||||
JSON.stringify({
|
||||
rules: rules,
|
||||
ruleSources: Array.from(this.ruleSources),
|
||||
ruleDestinations: Array.from(this.ruleDestinations)
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
RedirectEngine.prototype.fromSelfie = async function(path) {
|
||||
const result = await µBlock.assets.get(`${path}/main`);
|
||||
let selfie;
|
||||
try {
|
||||
selfie = JSON.parse(result.content);
|
||||
} catch (ex) {
|
||||
}
|
||||
if ( selfie instanceof Object === false ) { return false; }
|
||||
this.rules = new Map(selfie.rules);
|
||||
this.ruleSources = new Set(selfie.ruleSources);
|
||||
this.ruleDestinations = new Set(selfie.ruleDestinations);
|
||||
this.resetCache();
|
||||
this.modifyTime = Date.now();
|
||||
RedirectEngine.prototype.fromSelfie = async function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -851,9 +564,6 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() {
|
|||
|
||||
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
||||
µBlock.assets.remove('compiled/redirectEngine/resources');
|
||||
|
||||
// TODO: obsolete, remove eventually
|
||||
µBlock.cacheStorage.remove('resourcesSelfie');
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -2069,8 +2069,8 @@ const netOptionTokenDescriptors = new Map([
|
|||
[ 'popunder', OPTTokenPopunder | OPTType ],
|
||||
[ 'popup', OPTTokenPopup | OPTType | OPTCanNegate ],
|
||||
[ 'queryprune', OPTTokenQueryprune | OPTMustAssign | OPTAllowMayAssign | OPTModifierType ],
|
||||
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
|
||||
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
|
||||
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTAllowMayAssign | OPTModifierType | OPTRedirectType ],
|
||||
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTAllowMayAssign | OPTModifierType | OPTRedirectType ],
|
||||
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
|
||||
[ 'shide', OPTTokenShide | OPTType ],
|
||||
[ 'specifichide', OPTTokenShide | OPTType ],
|
||||
|
|
|
@ -2267,7 +2267,6 @@ const FilterParser = class {
|
|||
this.denyallowOpt = '';
|
||||
this.isPureHostname = false;
|
||||
this.isRegex = false;
|
||||
this.redirect = 0;
|
||||
this.token = '*';
|
||||
this.tokenHash = this.noTokenHash;
|
||||
this.tokenBeg = 0;
|
||||
|
@ -2355,6 +2354,7 @@ const FilterParser = class {
|
|||
this.badFilter = true;
|
||||
break;
|
||||
case parser.OPTTokenCsp:
|
||||
if ( this.modifyType !== undefined ) { return false; }
|
||||
this.modifyType = parser.OPTTokenCsp;
|
||||
if ( val !== undefined ) {
|
||||
if ( this.reBadCSP.test(val) ) { return false; }
|
||||
|
@ -2391,14 +2391,20 @@ const FilterParser = class {
|
|||
// 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';
|
||||
break;
|
||||
case parser.OPTTokenMp4:
|
||||
case parser.OPTTokenRedirect:
|
||||
case parser.OPTTokenRedirectRule:
|
||||
if ( this.redirect !== 0 ) { return false; }
|
||||
this.redirect = id === parser.OPTTokenRedirectRule ? 2 : 1;
|
||||
if ( this.modifyType !== undefined ) { return false; }
|
||||
this.modifyType = parser.OPTTokenRedirect;
|
||||
this.modifyValue = 'noopmp4-1s';
|
||||
break;
|
||||
case parser.OPTTokenQueryprune:
|
||||
this.modifyType = parser.OPTTokenQueryprune;
|
||||
case parser.OPTTokenRedirect:
|
||||
case parser.OPTTokenRedirectRule:
|
||||
if ( this.modifyType !== undefined ) { return false; }
|
||||
this.modifyType = id;
|
||||
if ( val !== undefined ) {
|
||||
this.modifyValue = val;
|
||||
} else if ( this.action === AllowAction ) {
|
||||
|
@ -2748,7 +2754,6 @@ FilterContainer.prototype.reset = function() {
|
|||
|
||||
FilterContainer.prototype.freeze = function() {
|
||||
const filterBucketId = FilterBucket.fid;
|
||||
const redirectTypeValue = typeNameToTypeValue.redirect;
|
||||
const unserialize = µb.CompiledLineIO.unserialize;
|
||||
const units = filterUnits;
|
||||
|
||||
|
@ -2763,13 +2768,6 @@ FilterContainer.prototype.freeze = function() {
|
|||
const args = unserialize(line);
|
||||
const bits = args[0];
|
||||
|
||||
// Special cases: delegate to more specialized engines.
|
||||
// Redirect engine.
|
||||
if ( (bits & TypeBitsMask) === redirectTypeValue ) {
|
||||
µb.redirectEngine.fromCompiledRule(args[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Plain static filters.
|
||||
const tokenHash = args[1];
|
||||
const fdata = args[2];
|
||||
|
@ -2994,21 +2992,6 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Redirect rule
|
||||
if ( parsed.redirect !== 0 ) {
|
||||
const result = this.compileRedirectRule(parser.raw, parsed.badFilter, writer);
|
||||
if ( result === false ) {
|
||||
const who = writer.properties.get('assetKey') || '?';
|
||||
µb.logger.writeOne({
|
||||
realm: 'message',
|
||||
type: 'error',
|
||||
text: `Invalid redirect rule in ${who}: ${parser.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.
|
||||
|
@ -3099,8 +3082,20 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||
units.push(FilterDenyAllow.compile(parsed));
|
||||
}
|
||||
|
||||
// Data
|
||||
// Modifier
|
||||
if ( parsed.modifyType !== undefined ) {
|
||||
// A block filter is implicit with `redirect=` modifier
|
||||
if (
|
||||
parsed.modifyType === parser.OPTTokenRedirect &&
|
||||
(parsed.action & ActionBitsMask) !== AllowAction
|
||||
) {
|
||||
this.compileToAtomicFilter(
|
||||
parsed,
|
||||
FilterCompositeAll.compile(units),
|
||||
writer
|
||||
);
|
||||
parsed.modifyType = parser.OPTTokenRedirectRule;
|
||||
}
|
||||
units.push(FilterModifier.compile(parsed));
|
||||
parsed.action = ModifyAction;
|
||||
parsed.important = 0;
|
||||
|
@ -3109,7 +3104,6 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||
const fdata = units.length === 1
|
||||
? units[0]
|
||||
: FilterCompositeAll.compile(units);
|
||||
|
||||
this.compileToAtomicFilter(parsed, fdata, writer);
|
||||
|
||||
return true;
|
||||
|
@ -3158,19 +3152,6 @@ FilterContainer.prototype.compileToAtomicFilter = function(
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.compileRedirectRule = function(raw, badFilter, writer) {
|
||||
const redirects = µb.redirectEngine.compileRuleFromStaticFilter(raw);
|
||||
if ( Array.isArray(redirects) === false ) { return false; }
|
||||
writer.select(badFilter ? 1 : 0);
|
||||
const type = typeNameToTypeValue.redirect;
|
||||
for ( const redirect of redirects ) {
|
||||
writer.push([ type, redirect ]);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.fromCompiledContent = function(reader) {
|
||||
// 0 = network filters
|
||||
reader.select(0);
|
||||
|
@ -3192,18 +3173,6 @@ FilterContainer.prototype.fromCompiledContent = function(reader) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
// TODO:
|
||||
// Evaluate converting redirect directives in redirect engine into
|
||||
// modifiers in static network filtering engine.
|
||||
//
|
||||
// Advantages: no more syntax quirks, gain all performance benefits, ability
|
||||
// to reverse-lookup list of redirect directive in logger.
|
||||
//
|
||||
// Challenges: need to figure a way to calculate specificity so that the
|
||||
// most specific redirect directive out of many can be
|
||||
// identified (possibly based on total number of hostname labels
|
||||
// seen at compile time).
|
||||
|
||||
FilterContainer.prototype.matchAndFetchModifiers = function(
|
||||
fctxt,
|
||||
modifierType
|
||||
|
@ -3554,6 +3523,54 @@ FilterContainer.prototype.matchString = function(fctxt, modifiers = 0) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.redirectRequest = function(fctxt) {
|
||||
const directives = this.matchAndFetchModifiers(fctxt, 'redirect-rule');
|
||||
if ( directives === undefined ) { return; }
|
||||
let winningDirective;
|
||||
let winningPriority = 0;
|
||||
for ( const directive of directives ) {
|
||||
const modifier = directive.modifier;
|
||||
const isException = (directive.bits & ActionBitsMask) === AllowAction;
|
||||
if ( isException && modifier.value === '' ) {
|
||||
winningDirective = directive;
|
||||
break;
|
||||
}
|
||||
if ( modifier.cache === undefined ) {
|
||||
const rawToken = modifier.value;
|
||||
let token = rawToken;
|
||||
let priority = 0;
|
||||
const match = /:(\d+)$/.exec(rawToken);
|
||||
if ( match !== null ) {
|
||||
token = rawToken.slice(0, match.index);
|
||||
priority = parseInt(match[1], 10);
|
||||
}
|
||||
modifier.cache = { token, priority };
|
||||
}
|
||||
if (
|
||||
winningDirective === undefined ||
|
||||
modifier.cache.priority > winningPriority
|
||||
) {
|
||||
winningDirective = directive;
|
||||
winningPriority = modifier.cache.priority;
|
||||
}
|
||||
}
|
||||
if ( winningDirective === undefined ) { return; }
|
||||
if ( (winningDirective.bits & ActionBitsMask) !== AllowAction ) {
|
||||
fctxt.redirectURL = µb.redirectEngine.tokenToURL(
|
||||
fctxt,
|
||||
winningDirective.modifier.cache.token
|
||||
);
|
||||
}
|
||||
return winningDirective;
|
||||
};
|
||||
|
||||
FilterContainer.prototype.hasQuery = function(fctxt) {
|
||||
urlTokenizer.setURL(fctxt.url);
|
||||
return urlTokenizer.hasQuery();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
FilterContainer.prototype.filterQuery = function(fctxt) {
|
||||
const directives = this.matchAndFetchModifiers(fctxt, 'queryprune');
|
||||
if ( directives === undefined ) { return; }
|
||||
|
@ -3564,6 +3581,11 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||
const out = [];
|
||||
for ( const directive of directives ) {
|
||||
const modifier = directive.modifier;
|
||||
const isException = (directive.bits & ActionBitsMask) === AllowAction;
|
||||
if ( isException && modifier.value === '' ) {
|
||||
out.push(directive);
|
||||
break;
|
||||
}
|
||||
if ( modifier.cache === undefined ) {
|
||||
let retext = modifier.value;
|
||||
if ( retext.startsWith('|') ) { retext = `^${retext.slice(1)}`; }
|
||||
|
@ -3574,7 +3596,9 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||
let filtered = false;
|
||||
for ( const [ key, value ] of params ) {
|
||||
if ( re.test(`${key}=${value}`) === false ) { continue; }
|
||||
if ( isException === false ) {
|
||||
params.delete(key);
|
||||
}
|
||||
filtered = true;
|
||||
}
|
||||
if ( filtered ) {
|
||||
|
@ -3699,12 +3723,16 @@ FilterContainer.prototype.benchmark = async function(action, target) {
|
|||
print(`\turl=${fctxt.url}`);
|
||||
print(`\tdocOrigin=${fctxt.getDocOrigin()}`);
|
||||
}
|
||||
if ( r !== 1 && this.hasQuery(fctxt) ) {
|
||||
if ( r !== 1 ) {
|
||||
if ( this.hasQuery(fctxt) ) {
|
||||
this.filterQuery(fctxt, 'queryprune');
|
||||
}
|
||||
if ( r !== 1 && fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
|
||||
if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
|
||||
this.matchAndFetchModifiers(fctxt, 'csp');
|
||||
}
|
||||
} else {
|
||||
this.redirectRequest(fctxt);
|
||||
}
|
||||
}
|
||||
const t1 = self.performance.now();
|
||||
const dur = t1 - t0;
|
||||
|
|
|
@ -205,25 +205,19 @@ const onBeforeRootFrameRequest = function(fctxt) {
|
|||
}
|
||||
|
||||
const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest');
|
||||
if ( pageStore ) {
|
||||
if ( pageStore !== null ) {
|
||||
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
||||
pageStore.journalAddRequest(requestHostname, result);
|
||||
}
|
||||
|
||||
// Log
|
||||
if ( loggerEnabled ) {
|
||||
fctxt.setRealm('network').setFilter(logData);
|
||||
fctxt.setFilter(logData);
|
||||
}
|
||||
|
||||
// Modifier(s)?
|
||||
// A modifier is an action which transform the original network request.
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/760
|
||||
// Redirect non-blocked request?
|
||||
if ( result === 0 && snfe.hasQuery(fctxt) ) {
|
||||
const directives = snfe.filterQuery(fctxt);
|
||||
if ( directives !== undefined && loggerEnabled ) {
|
||||
fctxt.pushFilters(directives.map(a => a.logData()));
|
||||
}
|
||||
if ( result === 0 && pageStore !== null && snfe.hasQuery(fctxt) ) {
|
||||
pageStore.redirectNonBlockedRequest(fctxt);
|
||||
}
|
||||
|
||||
if ( loggerEnabled ) {
|
||||
|
|
Loading…
Reference in New Issue