mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-02 00:42:45 +01:00
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
|
// Read-only
|
||||||
systemSettings: {
|
systemSettings: {
|
||||||
compiledMagic: 30, // Increase when compiled format changes
|
compiledMagic: 31, // Increase when compiled format changes
|
||||||
selfieMagic: 30, // Increase when selfie format changes
|
selfieMagic: 31, // Increase when selfie format changes
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501
|
||||||
|
@ -302,7 +302,13 @@ const processLoggerEntries = function(response) {
|
|||||||
if ( autoDeleteVoidedRows ) { continue; }
|
if ( autoDeleteVoidedRows ) { continue; }
|
||||||
parsed.voided = true;
|
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);
|
const separator = createLogSeparator(parsed, unboxed.url);
|
||||||
loggerEntries.unshift(separator);
|
loggerEntries.unshift(separator);
|
||||||
if ( rowFilterer.filterOne(separator) ) {
|
if ( rowFilterer.filterOne(separator) ) {
|
||||||
|
@ -647,22 +647,9 @@ const PageStore = class {
|
|||||||
// Redirect non-blocked request?
|
// Redirect non-blocked request?
|
||||||
if ( (fctxt.itype & fctxt.INLINE_ANY) === 0 ) {
|
if ( (fctxt.itype & fctxt.INLINE_ANY) === 0 ) {
|
||||||
if ( result === 1 ) {
|
if ( result === 1 ) {
|
||||||
if ( µb.hiddenSettings.ignoreRedirectFilters !== true ) {
|
this.redirectBlockedRequest(fctxt);
|
||||||
const redirectURL = µb.redirectEngine.toURL(fctxt);
|
|
||||||
if ( redirectURL !== undefined ) {
|
|
||||||
fctxt.redirectURL = redirectURL;
|
|
||||||
this.internalRedirectionCount += 1;
|
|
||||||
fctxt.pushFilter({
|
|
||||||
source: 'redirect',
|
|
||||||
raw: µb.redirectEngine.resourceNameRegister
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ( snfe.hasQuery(fctxt) ) {
|
} else if ( snfe.hasQuery(fctxt) ) {
|
||||||
const directives = snfe.filterQuery(fctxt);
|
this.redirectNonBlockedRequest(fctxt);
|
||||||
if ( directives !== undefined && loggerEnabled ) {
|
|
||||||
fctxt.pushFilters(directives.map(a => a.logData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,6 +662,32 @@ const PageStore = class {
|
|||||||
return result;
|
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) {
|
filterCSPReport(fctxt) {
|
||||||
if (
|
if (
|
||||||
µb.sessionSwitches.evaluateZ(
|
µb.sessionSwitches.evaluateZ(
|
||||||
|
@ -275,24 +275,13 @@ const RedirectEngine = function() {
|
|||||||
this.aliases = new Map();
|
this.aliases = new Map();
|
||||||
this.resources = new Map();
|
this.resources = new Map();
|
||||||
this.reset();
|
this.reset();
|
||||||
|
this.modifyTime = Date.now();
|
||||||
this.resourceNameRegister = '';
|
this.resourceNameRegister = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
RedirectEngine.prototype.reset = function() {
|
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) {
|
RedirectEngine.prototype.tokenToURL = function(fctxt, token) {
|
||||||
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;
|
|
||||||
const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */;
|
const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */;
|
||||||
if ( asDataURI ) {
|
if ( asDataURI ) {
|
||||||
token = token.slice(1);
|
token = token.slice(1);
|
||||||
}
|
}
|
||||||
const entry = this.resources.get(this.aliases.get(token) || token);
|
const entry = this.resources.get(this.aliases.get(token) || token);
|
||||||
if ( entry !== undefined ) {
|
if ( entry === undefined ) { return; }
|
||||||
return entry.toURL(fctxt, asDataURI);
|
this.resourceNameRegister = token;
|
||||||
}
|
return entry.toURL(fctxt, asDataURI);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) {
|
RedirectEngine.prototype.toSelfie = async function() {
|
||||||
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.fromCompiledRule = function(line) {
|
RedirectEngine.prototype.fromSelfie = async function() {
|
||||||
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();
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -851,9 +564,6 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() {
|
|||||||
|
|
||||||
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
||||||
µBlock.assets.remove('compiled/redirectEngine/resources');
|
µ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 ],
|
[ 'popunder', OPTTokenPopunder | OPTType ],
|
||||||
[ 'popup', OPTTokenPopup | OPTType | OPTCanNegate ],
|
[ 'popup', OPTTokenPopup | OPTType | OPTCanNegate ],
|
||||||
[ 'queryprune', OPTTokenQueryprune | OPTMustAssign | OPTAllowMayAssign | OPTModifierType ],
|
[ 'queryprune', OPTTokenQueryprune | OPTMustAssign | OPTAllowMayAssign | OPTModifierType ],
|
||||||
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
|
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTAllowMayAssign | OPTModifierType | OPTRedirectType ],
|
||||||
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
|
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTAllowMayAssign | OPTModifierType | OPTRedirectType ],
|
||||||
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
|
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
|
||||||
[ 'shide', OPTTokenShide | OPTType ],
|
[ 'shide', OPTTokenShide | OPTType ],
|
||||||
[ 'specifichide', OPTTokenShide | OPTType ],
|
[ 'specifichide', OPTTokenShide | OPTType ],
|
||||||
|
@ -2267,7 +2267,6 @@ const FilterParser = class {
|
|||||||
this.denyallowOpt = '';
|
this.denyallowOpt = '';
|
||||||
this.isPureHostname = false;
|
this.isPureHostname = false;
|
||||||
this.isRegex = false;
|
this.isRegex = false;
|
||||||
this.redirect = 0;
|
|
||||||
this.token = '*';
|
this.token = '*';
|
||||||
this.tokenHash = this.noTokenHash;
|
this.tokenHash = this.noTokenHash;
|
||||||
this.tokenBeg = 0;
|
this.tokenBeg = 0;
|
||||||
@ -2355,6 +2354,7 @@ const FilterParser = class {
|
|||||||
this.badFilter = true;
|
this.badFilter = true;
|
||||||
break;
|
break;
|
||||||
case parser.OPTTokenCsp:
|
case parser.OPTTokenCsp:
|
||||||
|
if ( this.modifyType !== undefined ) { return false; }
|
||||||
this.modifyType = parser.OPTTokenCsp;
|
this.modifyType = parser.OPTTokenCsp;
|
||||||
if ( val !== undefined ) {
|
if ( val !== undefined ) {
|
||||||
if ( this.reBadCSP.test(val) ) { return false; }
|
if ( this.reBadCSP.test(val) ) { return false; }
|
||||||
@ -2391,14 +2391,20 @@ const FilterParser = class {
|
|||||||
// 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 parser.OPTTokenEmpty:
|
||||||
|
if ( this.modifyType !== undefined ) { return false; }
|
||||||
|
this.modifyType = parser.OPTTokenRedirect;
|
||||||
|
this.modifyValue = 'empty';
|
||||||
|
break;
|
||||||
case parser.OPTTokenMp4:
|
case parser.OPTTokenMp4:
|
||||||
case parser.OPTTokenRedirect:
|
if ( this.modifyType !== undefined ) { return false; }
|
||||||
case parser.OPTTokenRedirectRule:
|
this.modifyType = parser.OPTTokenRedirect;
|
||||||
if ( this.redirect !== 0 ) { return false; }
|
this.modifyValue = 'noopmp4-1s';
|
||||||
this.redirect = id === parser.OPTTokenRedirectRule ? 2 : 1;
|
|
||||||
break;
|
break;
|
||||||
case parser.OPTTokenQueryprune:
|
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 ) {
|
if ( val !== undefined ) {
|
||||||
this.modifyValue = val;
|
this.modifyValue = val;
|
||||||
} else if ( this.action === AllowAction ) {
|
} else if ( this.action === AllowAction ) {
|
||||||
@ -2748,7 +2754,6 @@ FilterContainer.prototype.reset = function() {
|
|||||||
|
|
||||||
FilterContainer.prototype.freeze = function() {
|
FilterContainer.prototype.freeze = function() {
|
||||||
const filterBucketId = FilterBucket.fid;
|
const filterBucketId = FilterBucket.fid;
|
||||||
const redirectTypeValue = typeNameToTypeValue.redirect;
|
|
||||||
const unserialize = µb.CompiledLineIO.unserialize;
|
const unserialize = µb.CompiledLineIO.unserialize;
|
||||||
const units = filterUnits;
|
const units = filterUnits;
|
||||||
|
|
||||||
@ -2763,13 +2768,6 @@ FilterContainer.prototype.freeze = function() {
|
|||||||
const args = unserialize(line);
|
const args = unserialize(line);
|
||||||
const bits = args[0];
|
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.
|
// Plain static filters.
|
||||||
const tokenHash = args[1];
|
const tokenHash = args[1];
|
||||||
const fdata = args[2];
|
const fdata = args[2];
|
||||||
@ -2994,21 +2992,6 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||||||
return false;
|
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
|
// 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.
|
||||||
@ -3099,8 +3082,20 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||||||
units.push(FilterDenyAllow.compile(parsed));
|
units.push(FilterDenyAllow.compile(parsed));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data
|
// Modifier
|
||||||
if ( parsed.modifyType !== undefined ) {
|
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));
|
units.push(FilterModifier.compile(parsed));
|
||||||
parsed.action = ModifyAction;
|
parsed.action = ModifyAction;
|
||||||
parsed.important = 0;
|
parsed.important = 0;
|
||||||
@ -3109,7 +3104,6 @@ FilterContainer.prototype.compile = function(parser, writer) {
|
|||||||
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;
|
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) {
|
FilterContainer.prototype.fromCompiledContent = function(reader) {
|
||||||
// 0 = network filters
|
// 0 = network filters
|
||||||
reader.select(0);
|
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(
|
FilterContainer.prototype.matchAndFetchModifiers = function(
|
||||||
fctxt,
|
fctxt,
|
||||||
modifierType
|
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) {
|
FilterContainer.prototype.filterQuery = function(fctxt) {
|
||||||
const directives = this.matchAndFetchModifiers(fctxt, 'queryprune');
|
const directives = this.matchAndFetchModifiers(fctxt, 'queryprune');
|
||||||
if ( directives === undefined ) { return; }
|
if ( directives === undefined ) { return; }
|
||||||
@ -3564,6 +3581,11 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||||||
const out = [];
|
const out = [];
|
||||||
for ( const directive of directives ) {
|
for ( const directive of directives ) {
|
||||||
const modifier = directive.modifier;
|
const modifier = directive.modifier;
|
||||||
|
const isException = (directive.bits & ActionBitsMask) === AllowAction;
|
||||||
|
if ( isException && modifier.value === '' ) {
|
||||||
|
out.push(directive);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if ( modifier.cache === undefined ) {
|
if ( modifier.cache === undefined ) {
|
||||||
let retext = modifier.value;
|
let retext = modifier.value;
|
||||||
if ( retext.startsWith('|') ) { retext = `^${retext.slice(1)}`; }
|
if ( retext.startsWith('|') ) { retext = `^${retext.slice(1)}`; }
|
||||||
@ -3574,7 +3596,9 @@ FilterContainer.prototype.filterQuery = function(fctxt) {
|
|||||||
let filtered = false;
|
let filtered = false;
|
||||||
for ( const [ key, value ] of params ) {
|
for ( const [ key, value ] of params ) {
|
||||||
if ( re.test(`${key}=${value}`) === false ) { continue; }
|
if ( re.test(`${key}=${value}`) === false ) { continue; }
|
||||||
params.delete(key);
|
if ( isException === false ) {
|
||||||
|
params.delete(key);
|
||||||
|
}
|
||||||
filtered = true;
|
filtered = true;
|
||||||
}
|
}
|
||||||
if ( filtered ) {
|
if ( filtered ) {
|
||||||
@ -3699,11 +3723,15 @@ FilterContainer.prototype.benchmark = async function(action, target) {
|
|||||||
print(`\turl=${fctxt.url}`);
|
print(`\turl=${fctxt.url}`);
|
||||||
print(`\tdocOrigin=${fctxt.getDocOrigin()}`);
|
print(`\tdocOrigin=${fctxt.getDocOrigin()}`);
|
||||||
}
|
}
|
||||||
if ( r !== 1 && this.hasQuery(fctxt) ) {
|
if ( r !== 1 ) {
|
||||||
this.filterQuery(fctxt, 'queryprune');
|
if ( this.hasQuery(fctxt) ) {
|
||||||
}
|
this.filterQuery(fctxt, 'queryprune');
|
||||||
if ( r !== 1 && fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
|
}
|
||||||
this.matchAndFetchModifiers(fctxt, 'csp');
|
if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
|
||||||
|
this.matchAndFetchModifiers(fctxt, 'csp');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.redirectRequest(fctxt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const t1 = self.performance.now();
|
const t1 = self.performance.now();
|
||||||
|
@ -205,25 +205,19 @@ const onBeforeRootFrameRequest = function(fctxt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest');
|
const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest');
|
||||||
if ( pageStore ) {
|
if ( pageStore !== null ) {
|
||||||
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
pageStore.journalAddRootFrame('uncommitted', requestURL);
|
||||||
pageStore.journalAddRequest(requestHostname, result);
|
pageStore.journalAddRequest(requestHostname, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log
|
|
||||||
if ( loggerEnabled ) {
|
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
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/760
|
||||||
// Redirect non-blocked request?
|
// Redirect non-blocked request?
|
||||||
if ( result === 0 && snfe.hasQuery(fctxt) ) {
|
if ( result === 0 && pageStore !== null && snfe.hasQuery(fctxt) ) {
|
||||||
const directives = snfe.filterQuery(fctxt);
|
pageStore.redirectNonBlockedRequest(fctxt);
|
||||||
if ( directives !== undefined && loggerEnabled ) {
|
|
||||||
fctxt.pushFilters(directives.map(a => a.logData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( loggerEnabled ) {
|
if ( loggerEnabled ) {
|
||||||
|
Loading…
Reference in New Issue
Block a user