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:
Raymond Hill 2020-11-03 09:15:26 -05:00
parent 1b44bf276a
commit 157cef6034
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
7 changed files with 140 additions and 389 deletions

View File

@ -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

View File

@ -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) ) {

View File

@ -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(

View File

@ -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 ) {
return entry.toURL(fctxt, asDataURI);
}
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');
};
/******************************************************************************/

View File

@ -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 ],

View File

@ -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; }
params.delete(key);
if ( isException === false ) {
params.delete(key);
}
filtered = true;
}
if ( filtered ) {
@ -3699,11 +3723,15 @@ FilterContainer.prototype.benchmark = async function(action, target) {
print(`\turl=${fctxt.url}`);
print(`\tdocOrigin=${fctxt.getDocOrigin()}`);
}
if ( r !== 1 && this.hasQuery(fctxt) ) {
this.filterQuery(fctxt, 'queryprune');
}
if ( r !== 1 && fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
this.matchAndFetchModifiers(fctxt, 'csp');
if ( r !== 1 ) {
if ( this.hasQuery(fctxt) ) {
this.filterQuery(fctxt, 'queryprune');
}
if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
this.matchAndFetchModifiers(fctxt, 'csp');
}
} else {
this.redirectRequest(fctxt);
}
}
const t1 = self.performance.now();

View File

@ -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 ) {