1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-10-04 16:47:15 +02:00

Add new filter option queryprune=

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

The purpose of this new network filter option is to remove
query parameters form the URL of network requests.

The name `queryprune` has been picked over `querystrip`
since the purpose of the option is to remove some
parameters from the URL rather than all parameters.

`queryprune` is a modifier option (like `csp`) in that it
does not cause a network request to be blocked but rather
modified before being emitted.

`queryprune` must be assigned a value, which value will
determine which parameters from a query string will be
removed. The syntax for the value is that of regular
expression *except* for the following rules:

- do not wrap the regex directive between `/`
- do not use regex special values `^` and `$`
- do not use literal comma character in the value,
  though you can use hex-encoded version, `\x2c`
- to match the start of a query parameter, prepend `|`
- to match the end of a query parameter, append `|`

`queryprune` regex-like values will be tested against each
key-value parameter pair as `[key]=[value]` string. This
way you can prune according to either the key, the value,
or both.

This commit introduces the concept of modifier filter
options, which as of now are:

- `csp=`
- `queryprune=`

They both work in similar way when used with `important`
option or when used in exception filters. Modifier
options can apply to any network requests, hence the
logger reports the type of the network requests, and no
longer use the modifier as the type, i.e. `csp` filters
are no longer reported as requests of type `csp`.

Though modifier options can apply to any network requests,
for the time being the `csp=` modifier option still apply
only to top or embedded (frame) documents, just as before.
In some future we may want to apply `csp=` directives to
network requests of type script, to control the behavior
of service workers for example.

A new built-in filter expression has been added to the
logger: "modified", which allow to see all the network
requests which were modified before being emitted. The
translation work for this new option will be available
in a future commit.
This commit is contained in:
Raymond Hill 2020-10-31 10:42:53 -04:00
parent ba2ef925e9
commit 1e2eb037e5
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
14 changed files with 958 additions and 489 deletions

View File

@ -1145,7 +1145,7 @@ vAPI.warSecret = (( ) => {
url.lastIndexOf(`?secret=${secret}`) !== -1 url.lastIndexOf(`?secret=${secret}`) !== -1
); );
if ( pos === -1 ) { if ( pos === -1 ) {
return { redirectUrl: root }; return { cancel: true };
} }
secrets.splice(pos, 1); secrets.splice(pos, 1);
}; };

View File

@ -49,7 +49,9 @@
vAPI.messaging.send('dashboard', { vAPI.messaging.send('dashboard', {
what: 'sfneBenchmark', what: 'sfneBenchmark',
}).then(result => { }).then(result => {
document.getElementById('sfneBenchmarkResult').textContent = result; document.getElementById('sfneBenchmarkResult').prepend(
document.createTextNode(result.trim() + '\n')
);
button.removeAttribute('disabled'); button.removeAttribute('disabled');
}); });
}); });

View File

@ -139,8 +139,8 @@ const µBlock = (( ) => { // jshint ignore:line
// Read-only // Read-only
systemSettings: { systemSettings: {
compiledMagic: 29, // Increase when compiled format changes compiledMagic: 30, // Increase when compiled format changes
selfieMagic: 29, // Increase when selfie format changes selfieMagic: 30, // 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

View File

@ -420,7 +420,7 @@ const initHints = function() {
if ( assignPos !== -1 ) { seedRight = seedRight.slice(0, assignPos); } if ( assignPos !== -1 ) { seedRight = seedRight.slice(0, assignPos); }
const isException = parser.isException(); const isException = parser.isException();
const hints = []; const hints = [];
for ( let [ text, bits ] of parser.netOptionTokens ) { for ( let [ text, bits ] of parser.netOptionTokenDescriptors ) {
if ( isNegated && (bits & parser.OPTCanNegate) === 0 ) { continue; } if ( isNegated && (bits & parser.OPTCanNegate) === 0 ) { continue; }
if ( isException ) { if ( isException ) {
if ( (bits & parser.OPTBlockOnly) !== 0 ) { continue; } if ( (bits & parser.OPTBlockOnly) !== 0 ) { continue; }

View File

@ -23,14 +23,82 @@
/******************************************************************************/ /******************************************************************************/
µBlock.FilteringContext = function(other) { {
if ( other instanceof µBlock.FilteringContext ) { // >>>>> start of local scope
/******************************************************************************/
const originFromURI = µBlock.URI.originFromURI;
const hostnameFromURI = vAPI.hostnameFromURI;
const domainFromHostname = vAPI.domainFromHostname;
/******************************************************************************/
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType
// Long term, convert code wherever possible to work with integer-based type
// values -- the assumption being that integer operations are faster than
// string operations.
const NO_TYPE = 0;
const BEACON = 1 << 0;
const CSP_REPORT = 1 << 1;
const FONT = 1 << 2;
const IMAGE = 1 << 4;
const IMAGESET = 1 << 4;
const MAIN_FRAME = 1 << 5;
const MEDIA = 1 << 6;
const OBJECT = 1 << 7;
const OBJECT_SUBREQUEST = 1 << 7;
const PING = 1 << 8;
const SCRIPT = 1 << 9;
const STYLESHEET = 1 << 10;
const SUB_FRAME = 1 << 11;
const WEBSOCKET = 1 << 12;
const XMLHTTPREQUEST = 1 << 13;
const INLINE_FONT = 1 << 14;
const INLINE_SCRIPT = 1 << 15;
const OTHER = 1 << 16;
const FRAME_ANY = MAIN_FRAME | SUB_FRAME;
const FONT_ANY = FONT | INLINE_FONT;
const INLINE_ANY = INLINE_FONT | INLINE_SCRIPT;
const PING_ANY = BEACON | CSP_REPORT | PING;
const SCRIPT_ANY = SCRIPT | INLINE_SCRIPT;
const typeStrToIntMap = {
'no_type': NO_TYPE,
'beacon': BEACON,
'csp_report': CSP_REPORT,
'font': FONT,
'image': IMAGE,
'imageset': IMAGESET,
'main_frame': MAIN_FRAME,
'media': MEDIA,
'object': OBJECT,
'object_subrequest': OBJECT_SUBREQUEST,
'ping': PING,
'script': SCRIPT,
'stylesheet': STYLESHEET,
'sub_frame': SUB_FRAME,
'websocket': WEBSOCKET,
'xmlhttprequest': XMLHTTPREQUEST,
'inline-font': INLINE_FONT,
'inline-script': INLINE_SCRIPT,
'other': OTHER,
};
/******************************************************************************/
const FilteringContext = class {
constructor(other) {
if ( other instanceof FilteringContext ) {
return this.fromFilteringContext(other); return this.fromFilteringContext(other);
} }
this.tstamp = 0; this.tstamp = 0;
this.realm = ''; this.realm = '';
this.id = undefined; this.id = undefined;
this.type = undefined; this.itype = 0;
this.stype = undefined;
this.url = undefined; this.url = undefined;
this.aliasURL = undefined; this.aliasURL = undefined;
this.hostname = undefined; this.hostname = undefined;
@ -46,32 +114,49 @@
this.tabDomain = undefined; this.tabDomain = undefined;
this.redirectURL = undefined; this.redirectURL = undefined;
this.filter = undefined; this.filter = undefined;
}; }
µBlock.FilteringContext.prototype = { get type() {
fromTabId: function(tabId) { return this.stype;
}
set type(a) {
this.itype = typeStrToIntMap[a] || NO_TYPE;
this.stype = a;
}
isDocument() {
return (this.itype & FRAME_ANY) !== 0;
}
isFont() {
return (this.itype & FONT_ANY) !== 0;
}
fromTabId(tabId) {
const tabContext = µBlock.tabContextManager.mustLookup(tabId); const tabContext = µBlock.tabContextManager.mustLookup(tabId);
this.tabOrigin = tabContext.origin; this.tabOrigin = tabContext.origin;
this.tabHostname = tabContext.rootHostname; this.tabHostname = tabContext.rootHostname;
this.tabDomain = tabContext.rootDomain; this.tabDomain = tabContext.rootDomain;
this.tabId = tabContext.tabId; this.tabId = tabContext.tabId;
return this; return this;
}, }
// https://github.com/uBlockOrigin/uBlock-issues/issues/459 // https://github.com/uBlockOrigin/uBlock-issues/issues/459
// In case of a request for frame and if ever no context is specified, // In case of a request for frame and if ever no context is specified,
// assume the origin of the context is the same as the request itself. // assume the origin of the context is the same as the request itself.
fromWebrequestDetails: function(details) { fromWebrequestDetails(details) {
const tabId = details.tabId; const tabId = details.tabId;
if ( tabId > 0 && details.type === 'main_frame' ) { this.fromTabId(tabId);
this.type = details.type;
if ( this.itype === MAIN_FRAME && tabId > 0 ) {
µBlock.tabContextManager.push(tabId, details.url); µBlock.tabContextManager.push(tabId, details.url);
} }
this.fromTabId(tabId);
this.realm = ''; this.realm = '';
this.id = details.requestId; this.id = details.requestId;
this.type = details.type;
this.setURL(details.url); this.setURL(details.url);
this.aliasURL = details.aliasURL || undefined; this.aliasURL = details.aliasURL || undefined;
if ( details.type !== 'sub_frame' ) { if ( this.itype !== SUB_FRAME ) {
this.docId = details.frameId; this.docId = details.frameId;
this.frameId = -1; this.frameId = -1;
} else { } else {
@ -95,12 +180,12 @@
} }
} }
} else if ( details.documentUrl !== undefined ) { } else if ( details.documentUrl !== undefined ) {
const origin = this.originFromURI( const origin = originFromURI(
µBlock.normalizePageURL(0, details.documentUrl) µBlock.normalizePageURL(0, details.documentUrl)
); );
this.setDocOrigin(origin).setTabOrigin(origin); this.setDocOrigin(origin).setTabOrigin(origin);
} else if ( this.docId === -1 || this.type.endsWith('_frame') ) { } else if ( this.docId === -1 || (this.itype & FRAME_ANY) !== 0 ) {
const origin = this.originFromURI(this.url); const origin = originFromURI(this.url);
this.setDocOrigin(origin).setTabOrigin(origin); this.setDocOrigin(origin).setTabOrigin(origin);
} else { } else {
this.setDocOrigin(this.tabOrigin); this.setDocOrigin(this.tabOrigin);
@ -108,8 +193,9 @@
this.redirectURL = undefined; this.redirectURL = undefined;
this.filter = undefined; this.filter = undefined;
return this; return this;
}, }
fromFilteringContext: function(other) {
fromFilteringContext(other) {
this.realm = other.realm; this.realm = other.realm;
this.type = other.type; this.type = other.type;
this.url = other.url; this.url = other.url;
@ -127,90 +213,106 @@
this.redirectURL = other.redirectURL; this.redirectURL = other.redirectURL;
this.filter = undefined; this.filter = undefined;
return this; return this;
}, }
duplicate: function() {
return (new µBlock.FilteringContext(this)); duplicate() {
}, return (new FilteringContext(this));
setRealm: function(a) { }
setRealm(a) {
this.realm = a; this.realm = a;
return this; return this;
}, }
setType: function(a) {
setType(a) {
this.type = a; this.type = a;
return this; return this;
}, }
setURL: function(a) {
setURL(a) {
if ( a !== this.url ) { if ( a !== this.url ) {
this.hostname = this.domain = undefined; this.hostname = this.domain = undefined;
this.url = a; this.url = a;
} }
return this; return this;
}, }
getHostname: function() {
getHostname() {
if ( this.hostname === undefined ) { if ( this.hostname === undefined ) {
this.hostname = this.hostnameFromURI(this.url); this.hostname = hostnameFromURI(this.url);
} }
return this.hostname; return this.hostname;
}, }
setHostname: function(a) {
setHostname(a) {
if ( a !== this.hostname ) { if ( a !== this.hostname ) {
this.domain = undefined; this.domain = undefined;
this.hostname = a; this.hostname = a;
} }
return this; return this;
}, }
getDomain: function() {
getDomain() {
if ( this.domain === undefined ) { if ( this.domain === undefined ) {
this.domain = this.domainFromHostname(this.getHostname()); this.domain = domainFromHostname(this.getHostname());
} }
return this.domain; return this.domain;
}, }
setDomain: function(a) {
setDomain(a) {
this.domain = a; this.domain = a;
return this; return this;
}, }
getDocOrigin: function() {
getDocOrigin() {
if ( this.docOrigin === undefined ) { if ( this.docOrigin === undefined ) {
this.docOrigin = this.tabOrigin; this.docOrigin = this.tabOrigin;
} }
return this.docOrigin; return this.docOrigin;
}, }
setDocOrigin: function(a) {
setDocOrigin(a) {
if ( a !== this.docOrigin ) { if ( a !== this.docOrigin ) {
this.docHostname = this.docDomain = undefined; this.docHostname = this.docDomain = undefined;
this.docOrigin = a; this.docOrigin = a;
} }
return this; return this;
}, }
setDocOriginFromURL: function(a) {
return this.setDocOrigin(this.originFromURI(a)); setDocOriginFromURL(a) {
}, return this.setDocOrigin(originFromURI(a));
getDocHostname: function() { }
getDocHostname() {
if ( this.docHostname === undefined ) { if ( this.docHostname === undefined ) {
this.docHostname = this.hostnameFromURI(this.getDocOrigin()); this.docHostname = hostnameFromURI(this.getDocOrigin());
} }
return this.docHostname; return this.docHostname;
}, }
setDocHostname: function(a) {
setDocHostname(a) {
if ( a !== this.docHostname ) { if ( a !== this.docHostname ) {
this.docDomain = undefined; this.docDomain = undefined;
this.docHostname = a; this.docHostname = a;
} }
return this; return this;
}, }
getDocDomain: function() {
getDocDomain() {
if ( this.docDomain === undefined ) { if ( this.docDomain === undefined ) {
this.docDomain = this.domainFromHostname(this.getDocHostname()); this.docDomain = domainFromHostname(this.getDocHostname());
} }
return this.docDomain; return this.docDomain;
}, }
setDocDomain: function(a) {
setDocDomain(a) {
this.docDomain = a; this.docDomain = a;
return this; return this;
}, }
// The idea is to minimize the amout of work done to figure out whether // The idea is to minimize the amout of work done to figure out whether
// the resource is 3rd-party to the document. // the resource is 3rd-party to the document.
is3rdPartyToDoc: function() { is3rdPartyToDoc() {
let docDomain = this.getDocDomain(); let docDomain = this.getDocDomain();
if ( docDomain === '' ) { docDomain = this.docHostname; } if ( docDomain === '' ) { docDomain = this.docHostname; }
if ( this.domain !== undefined && this.domain !== '' ) { if ( this.domain !== undefined && this.domain !== '' ) {
@ -221,12 +323,14 @@
const i = hostname.length - docDomain.length; const i = hostname.length - docDomain.length;
if ( i === 0 ) { return false; } if ( i === 0 ) { return false; }
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
}, }
setTabId: function(a) {
setTabId(a) {
this.tabId = a; this.tabId = a;
return this; return this;
}, }
getTabOrigin: function() {
getTabOrigin() {
if ( this.tabOrigin === undefined ) { if ( this.tabOrigin === undefined ) {
const tabContext = µBlock.tabContextManager.mustLookup(this.tabId); const tabContext = µBlock.tabContextManager.mustLookup(this.tabId);
this.tabOrigin = tabContext.origin; this.tabOrigin = tabContext.origin;
@ -234,43 +338,50 @@
this.tabDomain = tabContext.rootDomain; this.tabDomain = tabContext.rootDomain;
} }
return this.tabOrigin; return this.tabOrigin;
}, }
setTabOrigin: function(a) {
setTabOrigin(a) {
if ( a !== this.tabOrigin ) { if ( a !== this.tabOrigin ) {
this.tabHostname = this.tabDomain = undefined; this.tabHostname = this.tabDomain = undefined;
this.tabOrigin = a; this.tabOrigin = a;
} }
return this; return this;
}, }
setTabOriginFromURL: function(a) {
return this.setTabOrigin(this.originFromURI(a)); setTabOriginFromURL(a) {
}, return this.setTabOrigin(originFromURI(a));
getTabHostname: function() { }
getTabHostname() {
if ( this.tabHostname === undefined ) { if ( this.tabHostname === undefined ) {
this.tabHostname = this.hostnameFromURI(this.getTabOrigin()); this.tabHostname = hostnameFromURI(this.getTabOrigin());
} }
return this.tabHostname; return this.tabHostname;
}, }
setTabHostname: function(a) {
setTabHostname(a) {
if ( a !== this.tabHostname ) { if ( a !== this.tabHostname ) {
this.tabDomain = undefined; this.tabDomain = undefined;
this.tabHostname = a; this.tabHostname = a;
} }
return this; return this;
}, }
getTabDomain: function() {
getTabDomain() {
if ( this.tabDomain === undefined ) { if ( this.tabDomain === undefined ) {
this.tabDomain = this.domainFromHostname(this.getTabHostname()); this.tabDomain = domainFromHostname(this.getTabHostname());
} }
return this.tabDomain; return this.tabDomain;
}, }
setTabDomain: function(a) {
setTabDomain(a) {
this.docDomain = a; this.docDomain = a;
return this; return this;
}, }
// The idea is to minimize the amout of work done to figure out whether // The idea is to minimize the amout of work done to figure out whether
// the resource is 3rd-party to the top document. // the resource is 3rd-party to the top document.
is3rdPartyToTab: function() { is3rdPartyToTab() {
let tabDomain = this.getTabDomain(); let tabDomain = this.getTabDomain();
if ( tabDomain === '' ) { tabDomain = this.tabHostname; } if ( tabDomain === '' ) { tabDomain = this.tabHostname; }
if ( this.domain !== undefined && this.domain !== '' ) { if ( this.domain !== undefined && this.domain !== '' ) {
@ -281,12 +392,38 @@
const i = hostname.length - tabDomain.length; const i = hostname.length - tabDomain.length;
if ( i === 0 ) { return false; } if ( i === 0 ) { return false; }
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
}, }
setFilter: function(a) {
setFilter(a) {
this.filter = a; this.filter = a;
return this; return this;
}, }
toLogger: function() {
pushFilter(a) {
if ( this.filter === undefined ) {
return this.setFilter(a);
}
if ( Array.isArray(this.filter) ) {
this.filter.push(a);
} else {
this.filter = [ this.filter, a ];
}
return this;
}
pushFilters(a) {
if ( this.filter === undefined ) {
return this.setFilter(a);
}
if ( Array.isArray(this.filter) ) {
this.filter.push(...a);
} else {
this.filter = [ this.filter, ...a ];
}
return this;
}
toLogger() {
this.tstamp = Date.now(); this.tstamp = Date.now();
if ( this.domain === undefined ) { if ( this.domain === undefined ) {
void this.getDomain(); void this.getDomain();
@ -297,11 +434,49 @@
if ( this.tabDomain === undefined ) { if ( this.tabDomain === undefined ) {
void this.getTabDomain(); void this.getTabDomain();
} }
µBlock.logger.writeOne(this); const logger = µBlock.logger;
}, const filters = this.filter;
originFromURI: µBlock.URI.originFromURI, // Many filters may have been applied to the current context
hostnameFromURI: µBlock.URI.hostnameFromURI, if ( Array.isArray(filters) === false ) {
domainFromHostname: µBlock.URI.domainFromHostname, return logger.writeOne(this);
}
for ( const filter of filters ) {
this.filter = filter;
logger.writeOne(this);
}
}
}; };
µBlock.filteringContext = new µBlock.FilteringContext(); /******************************************************************************/
FilteringContext.prototype.BEACON = FilteringContext.BEACON = BEACON;
FilteringContext.prototype.CSP_REPORT = FilteringContext.CSP_REPORT = CSP_REPORT;
FilteringContext.prototype.FONT = FilteringContext.FONT = FONT;
FilteringContext.prototype.IMAGE = FilteringContext.IMAGE = IMAGE;
FilteringContext.prototype.IMAGESET = FilteringContext.IMAGESET = IMAGESET;
FilteringContext.prototype.MAIN_FRAME = FilteringContext.MAIN_FRAME = MAIN_FRAME;
FilteringContext.prototype.MEDIA = FilteringContext.MEDIA = MEDIA;
FilteringContext.prototype.OBJECT = FilteringContext.OBJECT = OBJECT;
FilteringContext.prototype.OBJECT_SUBREQUEST = FilteringContext.OBJECT_SUBREQUEST = OBJECT_SUBREQUEST;
FilteringContext.prototype.PING = FilteringContext.PING = PING;
FilteringContext.prototype.SCRIPT = FilteringContext.SCRIPT = SCRIPT;
FilteringContext.prototype.STYLESHEET = FilteringContext.STYLESHEET = STYLESHEET;
FilteringContext.prototype.SUB_FRAME = FilteringContext.SUB_FRAME = SUB_FRAME;
FilteringContext.prototype.WEBSOCKET = FilteringContext.WEBSOCKET = WEBSOCKET;
FilteringContext.prototype.XMLHTTPREQUEST = FilteringContext.XMLHTTPREQUEST = XMLHTTPREQUEST;
FilteringContext.prototype.INLINE_FONT = FilteringContext.INLINE_FONT = INLINE_FONT;
FilteringContext.prototype.INLINE_SCRIPT = FilteringContext.INLINE_SCRIPT = INLINE_SCRIPT;
FilteringContext.prototype.OTHER = FilteringContext.OTHER = OTHER;
FilteringContext.prototype.FRAME_ANY = FilteringContext.FRAME_ANY = FRAME_ANY;
FilteringContext.prototype.FONT_ANY = FilteringContext.FONT_ANY = FONT_ANY;
FilteringContext.prototype.INLINE_ANY = FilteringContext.INLINE_ANY = INLINE_ANY;
FilteringContext.prototype.PING_ANY = FilteringContext.PING_ANY = PING_ANY;
FilteringContext.prototype.SCRIPT_ANY = FilteringContext.SCRIPT_ANY = SCRIPT_ANY;
/******************************************************************************/
µBlock.FilteringContext = FilteringContext;
µBlock.filteringContext = new FilteringContext();
// <<<<< end of local scope
}

View File

@ -214,6 +214,7 @@ const LogEntry = function(details) {
this[prop] = details[prop]; this[prop] = details[prop];
} }
} }
this.type = details.stype;
if ( details.aliasURL !== undefined ) { if ( details.aliasURL !== undefined ) {
this.aliased = true; this.aliased = true;
} }

View File

@ -62,10 +62,11 @@
ownerId: undefined, ownerId: undefined,
writeOne: function(details) { writeOne: function(details) {
if ( buffer === null ) { return; } if ( buffer === null ) { return; }
const box = boxEntry(details);
if ( writePtr === buffer.length ) { if ( writePtr === buffer.length ) {
buffer.push(boxEntry(details)); buffer.push(box);
} else { } else {
buffer[writePtr] = boxEntry(details); buffer[writePtr] = box;
} }
writePtr += 1; writePtr += 1;
}, },

View File

@ -1546,10 +1546,12 @@ const logCSPViolations = function(pageStore, request) {
cspData = new Map(); cspData = new Map();
const staticDirectives = const staticDirectives =
µb.staticNetFilteringEngine.matchAndFetchData(fctxt, 'csp'); µb.staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp');
if ( staticDirectives !== undefined ) {
for ( const directive of staticDirectives ) { for ( const directive of staticDirectives ) {
if ( directive.result !== 1 ) { continue; } if ( directive.result !== 1 ) { continue; }
cspData.set(directive.getData('csp'), directive.logData()); cspData.set(directive.value, directive.logData());
}
} }
fctxt.type = 'inline-script'; fctxt.type = 'inline-script';

View File

@ -545,27 +545,28 @@ const PageStore = class {
return 0; return 0;
} }
const requestType = fctxt.type;
if ( if (
requestType === 'csp_report' && fctxt.itype === fctxt.CSP_REPORT &&
this.filterCSPReport(fctxt) === 1 this.filterCSPReport(fctxt) === 1
) { ) {
return 1; return 1;
} }
if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) { if (
(fctxt.itype & fctxt.FONT_ANY) !== 0 &&
this.filterFont(fctxt) === 1 )
{
return 1; return 1;
} }
if ( if (
requestType === 'script' && fctxt.itype === fctxt.SCRIPT &&
this.filterScripting(fctxt, true) === 1 this.filterScripting(fctxt, true) === 1
) { ) {
return 1; return 1;
} }
const cacheableResult = this.cacheableResults.has(requestType); const cacheableResult = this.cacheableResults.has(fctxt.itype);
if ( cacheableResult ) { if ( cacheableResult ) {
const entry = this.netFilteringCache.lookupResult(fctxt); const entry = this.netFilteringCache.lookupResult(fctxt);
@ -576,13 +577,16 @@ const PageStore = class {
} }
} }
const requestType = fctxt.type;
const loggerEnabled = µb.logger.enabled;
// Dynamic URL filtering. // Dynamic URL filtering.
let result = µb.sessionURLFiltering.evaluateZ( let result = µb.sessionURLFiltering.evaluateZ(
fctxt.getTabHostname(), fctxt.getTabHostname(),
fctxt.url, fctxt.url,
requestType requestType
); );
if ( result !== 0 && µb.logger.enabled ) { if ( result !== 0 && loggerEnabled ) {
fctxt.filter = µb.sessionURLFiltering.toLogData(); fctxt.filter = µb.sessionURLFiltering.toLogData();
} }
@ -593,17 +597,17 @@ const PageStore = class {
fctxt.getHostname(), fctxt.getHostname(),
requestType requestType
); );
if ( result !== 0 && result !== 3 && µb.logger.enabled ) { if ( result !== 0 && result !== 3 && loggerEnabled ) {
fctxt.filter = µb.sessionFirewall.toLogData(); fctxt.filter = µb.sessionFirewall.toLogData();
} }
} }
// Static filtering has lowest precedence. // Static filtering has lowest precedence.
if ( result === 0 || result === 3 ) {
const snfe = µb.staticNetFilteringEngine; const snfe = µb.staticNetFilteringEngine;
if ( result === 0 || result === 3 ) {
result = snfe.matchString(fctxt); result = snfe.matchString(fctxt);
if ( result !== 0 ) { if ( result !== 0 ) {
if ( µb.logger.enabled ) { if ( loggerEnabled ) {
fctxt.filter = snfe.toLogData(); fctxt.filter = snfe.toLogData();
} }
// https://github.com/uBlockOrigin/uBlock-issues/issues/943 // https://github.com/uBlockOrigin/uBlock-issues/issues/943
@ -625,8 +629,8 @@ const PageStore = class {
const frameStore = this.getFrameStore(fctxt.frameId); const frameStore = this.getFrameStore(fctxt.frameId);
if ( frameStore !== null && frameStore.clickToLoad ) { if ( frameStore !== null && frameStore.clickToLoad ) {
result = 2; result = 2;
if ( µb.logger.enabled ) { if ( loggerEnabled ) {
fctxt.setFilter({ fctxt.pushFilter({
result, result,
source: 'network', source: 'network',
raw: 'click-to-load', raw: 'click-to-load',
@ -635,19 +639,36 @@ const PageStore = class {
} }
} }
// Modifier(s)?
// A modifier is an action which transform the original network request.
// https://github.com/gorhill/uBlock/issues/949 // https://github.com/gorhill/uBlock/issues/949
// Redirect blocked request? // Redirect blocked request?
if ( result === 1 && µb.hiddenSettings.ignoreRedirectFilters !== true ) { // https://github.com/uBlockOrigin/uBlock-issues/issues/760
// 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); const redirectURL = µb.redirectEngine.toURL(fctxt);
if ( redirectURL !== undefined ) { if ( redirectURL !== undefined ) {
fctxt.redirectURL = redirectURL; fctxt.redirectURL = redirectURL;
this.internalRedirectionCount += 1; this.internalRedirectionCount += 1;
fctxt.pushFilter({
source: 'redirect',
raw: µb.redirectEngine.resourceNameRegister
});
}
}
} else if ( snfe.hasQuery(fctxt) ) {
const directives = snfe.filterQuery(fctxt);
if ( directives !== undefined && loggerEnabled ) {
fctxt.pushFilters(directives.map(a => a.logData()));
}
} }
} }
if ( cacheableResult ) { if ( cacheableResult ) {
this.netFilteringCache.rememberResult(fctxt, result); this.netFilteringCache.rememberResult(fctxt, result);
} else if ( result === 1 && this.collapsibleResources.has(requestType) ) { } else if ( result === 1 && this.collapsibleResources.has(fctxt.itype) ) {
this.netFilteringCache.rememberBlock(fctxt); this.netFilteringCache.rememberBlock(fctxt);
} }
@ -670,7 +691,7 @@ const PageStore = class {
} }
filterFont(fctxt) { filterFont(fctxt) {
if ( fctxt.type === 'font' ) { if ( fctxt.itype === fctxt.FONT ) {
this.remoteFontCount += 1; this.remoteFontCount += 1;
} }
if ( if (
@ -800,7 +821,7 @@ const PageStore = class {
} }
if ( exceptCname === false ) { return false; } if ( exceptCname === false ) { return false; }
if ( exceptCname instanceof Object ) { if ( exceptCname instanceof Object ) {
fctxt.setFilter(exceptCname); fctxt.pushFilter(exceptCname);
} }
return true; return true;
} }
@ -830,14 +851,14 @@ const PageStore = class {
}; };
PageStore.prototype.cacheableResults = new Set([ PageStore.prototype.cacheableResults = new Set([
'sub_frame', µb.FilteringContext.SUB_FRAME,
]); ]);
PageStore.prototype.collapsibleResources = new Set([ PageStore.prototype.collapsibleResources = new Set([
'image', µb.FilteringContext.IMAGE,
'media', µb.FilteringContext.MEDIA,
'object', µb.FilteringContext.OBJECT,
'sub_frame', µb.FilteringContext.SUB_FRAME,
]); ]);
µb.PageStore = PageStore; µb.PageStore = PageStore;

View File

@ -1922,11 +1922,12 @@ const OPTTokenPopunder = 26;
const OPTTokenPopup = 27; const OPTTokenPopup = 27;
const OPTTokenRedirect = 28; const OPTTokenRedirect = 28;
const OPTTokenRedirectRule = 29; const OPTTokenRedirectRule = 29;
const OPTTokenScript = 30; const OPTTokenQueryprune = 30;
const OPTTokenShide = 31; const OPTTokenScript = 31;
const OPTTokenXhr = 32; const OPTTokenShide = 32;
const OPTTokenWebrtc = 33; const OPTTokenXhr = 33;
const OPTTokenWebsocket = 34; const OPTTokenWebrtc = 34;
const OPTTokenWebsocket = 35;
const OPTCanNegate = 1 << 8; const OPTCanNegate = 1 << 8;
const OPTBlockOnly = 1 << 9; const OPTBlockOnly = 1 << 9;
@ -1937,8 +1938,9 @@ const OPTDomainList = 1 << 13;
const OPTType = 1 << 14; const OPTType = 1 << 14;
const OPTNetworkType = 1 << 15; const OPTNetworkType = 1 << 15;
const OPTRedirectType = 1 << 16; const OPTRedirectType = 1 << 16;
const OPTRedirectableType = 1 << 17; const OPTModifiableType = 1 << 17;
const OPTNotSupported = 1 << 18; const OPTModifierType = 1 << 18;
const OPTNotSupported = 1 << 19;
/******************************************************************************/ /******************************************************************************/
@ -2003,6 +2005,7 @@ Parser.prototype.OPTTokenOther = OPTTokenOther;
Parser.prototype.OPTTokenPing = OPTTokenPing; Parser.prototype.OPTTokenPing = OPTTokenPing;
Parser.prototype.OPTTokenPopunder = OPTTokenPopunder; Parser.prototype.OPTTokenPopunder = OPTTokenPopunder;
Parser.prototype.OPTTokenPopup = OPTTokenPopup; Parser.prototype.OPTTokenPopup = OPTTokenPopup;
Parser.prototype.OPTTokenQueryprune = OPTTokenQueryprune;
Parser.prototype.OPTTokenRedirect = OPTTokenRedirect; Parser.prototype.OPTTokenRedirect = OPTTokenRedirect;
Parser.prototype.OPTTokenRedirectRule = OPTTokenRedirectRule; Parser.prototype.OPTTokenRedirectRule = OPTTokenRedirectRule;
Parser.prototype.OPTTokenScript = OPTTokenScript; Parser.prototype.OPTTokenScript = OPTTokenScript;
@ -2023,12 +2026,12 @@ Parser.prototype.OPTDomainList = OPTDomainList;
Parser.prototype.OPTType = OPTType; Parser.prototype.OPTType = OPTType;
Parser.prototype.OPTNetworkType = OPTNetworkType; Parser.prototype.OPTNetworkType = OPTNetworkType;
Parser.prototype.OPTRedirectType = OPTRedirectType; Parser.prototype.OPTRedirectType = OPTRedirectType;
Parser.prototype.OPTRedirectableType = OPTRedirectableType; Parser.prototype.OPTModifiableType = OPTModifiableType;
Parser.prototype.OPTNotSupported = OPTNotSupported; Parser.prototype.OPTNotSupported = OPTNotSupported;
/******************************************************************************/ /******************************************************************************/
const netOptionTokens = new Map([ const netOptionTokenDescriptors = new Map([
[ '1p', OPTToken1p | OPTCanNegate ], [ '1p', OPTToken1p | OPTCanNegate ],
[ 'first-party', OPTToken1p | OPTCanNegate ], [ 'first-party', OPTToken1p | OPTCanNegate ],
[ '3p', OPTToken3p | OPTCanNegate ], [ '3p', OPTToken3p | OPTCanNegate ],
@ -2036,47 +2039,136 @@ const netOptionTokens = new Map([
[ 'all', OPTTokenAll | OPTType | OPTNetworkType ], [ 'all', OPTTokenAll | OPTType | OPTNetworkType ],
[ 'badfilter', OPTTokenBadfilter ], [ 'badfilter', OPTTokenBadfilter ],
[ 'cname', OPTTokenCname | OPTAllowOnly | OPTType ], [ 'cname', OPTTokenCname | OPTAllowOnly | OPTType ],
[ 'csp', OPTTokenCsp | OPTMustAssign | OPTAllowMayAssign ], [ 'csp', OPTTokenCsp | OPTMustAssign | OPTAllowMayAssign | OPTModifierType ],
[ 'css', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'css', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'stylesheet', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'stylesheet', OPTTokenCss | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'denyallow', OPTTokenDenyAllow | OPTMustAssign | OPTDomainList ], [ 'denyallow', OPTTokenDenyAllow | OPTMustAssign | OPTDomainList ],
[ 'doc', OPTTokenDoc | OPTType | OPTNetworkType | OPTCanNegate ], [ 'doc', OPTTokenDoc | OPTType | OPTNetworkType | OPTCanNegate | OPTModifiableType ],
[ 'document', OPTTokenDoc | OPTType | OPTNetworkType | OPTCanNegate ], [ 'document', OPTTokenDoc | OPTType | OPTNetworkType | OPTCanNegate | OPTModifiableType ],
[ 'domain', OPTTokenDomain | OPTMustAssign | OPTDomainList ], [ 'domain', OPTTokenDomain | OPTMustAssign | OPTDomainList ],
[ 'ehide', OPTTokenEhide | OPTType ], [ 'ehide', OPTTokenEhide | OPTType ],
[ 'elemhide', OPTTokenEhide | OPTType ], [ 'elemhide', OPTTokenEhide | OPTType ],
[ 'empty', OPTTokenEmpty | OPTBlockOnly | OPTRedirectType ], [ 'empty', OPTTokenEmpty | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
[ 'frame', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'frame', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'subdocument', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'subdocument', OPTTokenFrame | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'font', OPTTokenFont | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'font', OPTTokenFont | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'genericblock', OPTTokenGenericblock | OPTNotSupported ], [ 'genericblock', OPTTokenGenericblock | OPTNotSupported ],
[ 'ghide', OPTTokenGhide | OPTType ], [ 'ghide', OPTTokenGhide | OPTType ],
[ 'generichide', OPTTokenGhide | OPTType ], [ 'generichide', OPTTokenGhide | OPTType ],
[ 'image', OPTTokenImage | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'image', OPTTokenImage | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'important', OPTTokenImportant | OPTBlockOnly ], [ 'important', OPTTokenImportant | OPTBlockOnly ],
[ 'inline-font', OPTTokenInlineFont | OPTType | OPTCanNegate ], [ 'inline-font', OPTTokenInlineFont | OPTType | OPTCanNegate ],
[ 'inline-script', OPTTokenInlineScript | OPTType | OPTCanNegate ], [ 'inline-script', OPTTokenInlineScript | OPTType | OPTCanNegate ],
[ 'media', OPTTokenMedia | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'media', OPTTokenMedia | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'mp4', OPTTokenMp4 | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType | OPTRedirectableType ], [ 'mp4', OPTTokenMp4 | OPTType | OPTNetworkType | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
[ 'object', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'object', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType ], [ 'object-subrequest', OPTTokenObject | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'other', OPTTokenOther | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'other', OPTTokenOther | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'ping', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ], [ 'ping', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'beacon', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType ], [ 'beacon', OPTTokenPing | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'popunder', OPTTokenPopunder | OPTType ], [ 'popunder', OPTTokenPopunder | OPTType ],
[ 'popup', OPTTokenPopup | OPTType | OPTCanNegate ], [ 'popup', OPTTokenPopup | OPTType | OPTCanNegate ],
[ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType ], [ 'queryprune', OPTTokenQueryprune | OPTMustAssign | OPTAllowMayAssign | OPTModifierType ],
[ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType ], [ 'redirect', OPTTokenRedirect | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'redirect-rule', OPTTokenRedirectRule | OPTMustAssign | OPTBlockOnly | OPTRedirectType | OPTModifierType ],
[ 'script', OPTTokenScript | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'shide', OPTTokenShide | OPTType ], [ 'shide', OPTTokenShide | OPTType ],
[ 'specifichide', OPTTokenShide | OPTType ], [ 'specifichide', OPTTokenShide | OPTType ],
[ 'xhr', OPTTokenXhr | OPTCanNegate| OPTType | OPTNetworkType | OPTRedirectableType ], [ 'xhr', OPTTokenXhr | OPTCanNegate| OPTType | OPTNetworkType | OPTModifiableType ],
[ 'xmlhttprequest', OPTTokenXhr | OPTCanNegate | OPTType | OPTNetworkType | OPTRedirectableType ], [ 'xmlhttprequest', OPTTokenXhr | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
[ 'webrtc', OPTTokenWebrtc | OPTNotSupported ], [ 'webrtc', OPTTokenWebrtc | OPTNotSupported ],
[ 'websocket', OPTTokenWebsocket | OPTCanNegate | OPTType | OPTNetworkType ], [ 'websocket', OPTTokenWebsocket | OPTCanNegate | OPTType | OPTNetworkType | OPTModifiableType ],
]); ]);
Parser.prototype.netOptionTokens = netOptionTokens; Parser.prototype.netOptionTokenDescriptors =
Parser.netOptionTokenDescriptors = netOptionTokenDescriptors;
Parser.netOptionTokenIds = new Map([
[ '1p', OPTToken1p ],
[ 'first-party', OPTToken1p ],
[ '3p', OPTToken3p ],
[ 'third-party', OPTToken3p ],
[ 'all', OPTTokenAll ],
[ 'badfilter', OPTTokenBadfilter ],
[ 'cname', OPTTokenCname ],
[ 'csp', OPTTokenCsp ],
[ 'css', OPTTokenCss ],
[ 'stylesheet', OPTTokenCss ],
[ 'denyallow', OPTTokenDenyAllow ],
[ 'doc', OPTTokenDoc ],
[ 'document', OPTTokenDoc ],
[ 'domain', OPTTokenDomain ],
[ 'ehide', OPTTokenEhide ],
[ 'elemhide', OPTTokenEhide ],
[ 'empty', OPTTokenEmpty ],
[ 'frame', OPTTokenFrame ],
[ 'subdocument', OPTTokenFrame ],
[ 'font', OPTTokenFont ],
[ 'genericblock', OPTTokenGenericblock ],
[ 'ghide', OPTTokenGhide ],
[ 'generichide', OPTTokenGhide ],
[ 'image', OPTTokenImage ],
[ 'important', OPTTokenImportant ],
[ 'inline-font', OPTTokenInlineFont ],
[ 'inline-script', OPTTokenInlineScript ],
[ 'media', OPTTokenMedia ],
[ 'mp4', OPTTokenMp4 ],
[ 'object', OPTTokenObject ],
[ 'object-subrequest', OPTTokenObject ],
[ 'other', OPTTokenOther ],
[ 'ping', OPTTokenPing ],
[ 'beacon', OPTTokenPing ],
[ 'popunder', OPTTokenPopunder ],
[ 'popup', OPTTokenPopup ],
[ 'queryprune', OPTTokenQueryprune ],
[ 'redirect', OPTTokenRedirect ],
[ 'redirect-rule', OPTTokenRedirectRule ],
[ 'script', OPTTokenScript ],
[ 'shide', OPTTokenShide ],
[ 'specifichide', OPTTokenShide ],
[ 'xhr', OPTTokenXhr ],
[ 'xmlhttprequest', OPTTokenXhr ],
[ 'webrtc', OPTTokenWebrtc ],
[ 'websocket', OPTTokenWebsocket ],
]);
Parser.netOptionTokenNames = new Map([
[ OPTToken1p, '1p' ],
[ OPTToken3p, '3p' ],
[ OPTTokenAll, 'all' ],
[ OPTTokenBadfilter, 'badfilter' ],
[ OPTTokenCname, 'cname' ],
[ OPTTokenCsp, 'csp' ],
[ OPTTokenCss, 'stylesheet' ],
[ OPTTokenDenyAllow, 'denyallow' ],
[ OPTTokenDoc, 'document' ],
[ OPTTokenDomain, 'domain' ],
[ OPTTokenEhide, 'elemhide' ],
[ OPTTokenEmpty, 'empty' ],
[ OPTTokenFrame, 'subdocument' ],
[ OPTTokenFont, 'font' ],
[ OPTTokenGenericblock, 'genericblock' ],
[ OPTTokenGhide, 'generichide' ],
[ OPTTokenImage, 'image' ],
[ OPTTokenImportant, 'important' ],
[ OPTTokenInlineFont, 'inline-font' ],
[ OPTTokenInlineScript, 'inline-script' ],
[ OPTTokenMedia, 'media' ],
[ OPTTokenMp4, 'mp4' ],
[ OPTTokenObject, 'object' ],
[ OPTTokenOther, 'other' ],
[ OPTTokenPing, 'ping' ],
[ OPTTokenPopunder, 'popunder' ],
[ OPTTokenPopup, 'popup' ],
[ OPTTokenQueryprune, 'queryprune' ],
[ OPTTokenRedirect, 'redirect' ],
[ OPTTokenRedirectRule, 'redirect-rule' ],
[ OPTTokenScript, 'script' ],
[ OPTTokenShide, 'specifichide' ],
[ OPTTokenXhr, 'xmlhttprequest' ],
[ OPTTokenWebrtc, 'webrtc' ],
[ OPTTokenWebsocket, 'websocket' ],
]);
/******************************************************************************/ /******************************************************************************/
@ -2134,9 +2226,8 @@ const NetOptionsIterator = class {
const slices = this.parser.slices; const slices = this.parser.slices;
const optSlices = this.optSlices; const optSlices = this.optSlices;
let typeCount = 0; let typeCount = 0;
let redirectableTypeCount = 0; let modifiableTypeCount = 0;
let redirectIndex = -1; let modifierIndex = -1;
let cspIndex = -1;
let writePtr = 0; let writePtr = 0;
let lopt = lopts; let lopt = lopts;
while ( lopt < ropts ) { while ( lopt < ropts ) {
@ -2170,7 +2261,7 @@ const NetOptionsIterator = class {
if ( good ) { if ( good ) {
const rtok = lval === 0 ? i : lval; const rtok = lval === 0 ? i : lval;
const token = this.parser.raw.slice(slices[ltok+1], slices[rtok+1]); const token = this.parser.raw.slice(slices[ltok+1], slices[rtok+1]);
descriptor = netOptionTokens.get(token); descriptor = netOptionTokenDescriptors.get(token);
} }
// Validate option according to context // Validate option according to context
if ( if (
@ -2189,20 +2280,17 @@ const NetOptionsIterator = class {
// Keep count of types // Keep count of types
if ( hasBits(descriptor, OPTType) ) { if ( hasBits(descriptor, OPTType) ) {
typeCount += 1; typeCount += 1;
if ( hasBits(descriptor, OPTRedirectableType) ) { const modifiable = hasBits(descriptor, OPTModifiableType);
redirectableTypeCount += 1; if ( modifiable ) {
} modifiableTypeCount += 1;
} } else if ( modifierIndex !== -1 ) {
// Only one `redirect` or `csp` can be present
if ( hasBits(descriptor, OPTRedirectType) ) {
if ( redirectIndex === -1 ) {
redirectIndex = writePtr;
} else {
descriptor = OPTTokenInvalid; descriptor = OPTTokenInvalid;
} }
} else if ( (descriptor & 0xFF) === OPTTokenCsp ) { }
if ( cspIndex === -1 ) { // Only one modifier can be present
cspIndex = writePtr; if ( hasBits(descriptor, OPTModifierType) ) {
if ( modifierIndex === -1 ) {
modifierIndex = writePtr;
} else { } else {
descriptor = OPTTokenInvalid; descriptor = OPTTokenInvalid;
} }
@ -2244,38 +2332,26 @@ const NetOptionsIterator = class {
if ( this.interactive && hasBits(this.parser.slices[ropts-3], BITComma) ) { if ( this.interactive && hasBits(this.parser.slices[ropts-3], BITComma) ) {
this.parser.slices[ropts-3] |= BITError; this.parser.slices[ropts-3] |= BITError;
} }
// Invalid combinations of options
//
// `csp` can't be used with any other types or redirection
if ( cspIndex !== -1 && ( typeCount !== 0 || redirectIndex !== -1 ) ) {
optSlices[cspIndex] = OPTTokenInvalid;
if ( this.interactive ) {
this.parser.markSlices(
optSlices[cspIndex+1],
optSlices[cspIndex+5],
BITError
);
}
}
// `redirect` requires one single redirectable type, EXCEPT for when we // `redirect` requires one single redirectable type, EXCEPT for when we
// redirect to `empty`, in which case it is allowed to not have any // redirect to `empty`, in which case it is allowed to not have any
// network type specified. // network type specified.
if ( if (
redirectIndex !== -1 && modifierIndex !== -1 &&
redirectableTypeCount !== 1 && ( hasBits(optSlices[modifierIndex+0], OPTRedirectType) &&
redirectableTypeCount !== 0 || modifiableTypeCount !== 1 && (
modifiableTypeCount !== 0 ||
typeCount !== 0 || typeCount !== 0 ||
this.parser.raw.slice( this.parser.raw.slice(
this.parser.slices[optSlices[redirectIndex+0]+1], this.parser.slices[optSlices[modifierIndex+0]+1],
this.parser.slices[optSlices[redirectIndex+5]+1] this.parser.slices[optSlices[modifierIndex+5]+1]
).endsWith('empty') === false ).endsWith('empty') === false
) )
) { ) {
optSlices[redirectIndex] = OPTTokenInvalid; optSlices[modifierIndex] = OPTTokenInvalid;
if ( this.interactive ) { if ( this.interactive ) {
this.parser.markSlices( this.parser.markSlices(
optSlices[redirectIndex+1], optSlices[modifierIndex+1],
optSlices[redirectIndex+5], optSlices[modifierIndex+5],
BITError BITError
); );
} }

View File

@ -33,103 +33,117 @@ const µb = µBlock;
const urlTokenizer = µb.urlTokenizer; const urlTokenizer = µb.urlTokenizer;
// fedcba9876543210 // fedcba9876543210
// | | ||| // | | || |
// | | ||| // | | || |
// | | ||| // | | || |
// | | ||| // | | || |
// | | ||+---- bit 0: [BlockAction | AllowAction] // | | || +---- bit 0- 1: block=0, allow=1, modify=2
// | | |+----- bit 1: `important` // | | |+------ bit 2: important
// | | +------ bit 2- 3: party [0 - 3] // | | +------- bit 3- 4: party [0-3]
// | +-------- bit 4- 8: type [0 - 31] // | +--------- bit 5- 9: type [0-31]
// +------------- bit 9-15: unused // +-------------- bit 10-15: unused
const ActionBitsMask = 0b0000000011;
const TypeBitsMask = 0b1111100000;
const TypeBitsOffset = 5;
const BlockAction = 0 << 0; const BlockAction = 0 << 0;
const AllowAction = 1 << 0; const AllowAction = 1 << 0;
const Important = 1 << 1; const ModifyAction = 2 << 0;
const AnyParty = 0 << 2; // Note:
const FirstParty = 1 << 2; // It's possible to increase granularity of ModifyAction realm with
const ThirdParty = 2 << 2; // sub-realms if it helps performance, but so far I found it's not
// needed, there is no meaningful gains to be had.
const Important = 1 << 2;
const BlockImportant = BlockAction | Important; const BlockImportant = BlockAction | Important;
const AnyParty = 0 << 3;
const FirstParty = 1 << 3;
const ThirdParty = 2 << 3;
const typeNameToTypeValue = { const typeNameToTypeValue = {
'no_type': 0 << 4, 'no_type': 0 << TypeBitsOffset,
'stylesheet': 1 << 4, 'stylesheet': 1 << TypeBitsOffset,
'image': 2 << 4, 'image': 2 << TypeBitsOffset,
'object': 3 << 4, 'object': 3 << TypeBitsOffset,
'object_subrequest': 3 << 4, 'object_subrequest': 3 << TypeBitsOffset,
'script': 4 << 4, 'script': 4 << TypeBitsOffset,
'fetch': 5 << 4, 'fetch': 5 << TypeBitsOffset,
'xmlhttprequest': 5 << 4, 'xmlhttprequest': 5 << TypeBitsOffset,
'sub_frame': 6 << 4, 'sub_frame': 6 << TypeBitsOffset,
'font': 7 << 4, 'font': 7 << TypeBitsOffset,
'media': 8 << 4, 'media': 8 << TypeBitsOffset,
'websocket': 9 << 4, 'websocket': 9 << TypeBitsOffset,
'beacon': 10 << 4, 'beacon': 10 << TypeBitsOffset,
'ping': 10 << 4, 'ping': 10 << TypeBitsOffset,
'other': 11 << 4, 'other': 11 << TypeBitsOffset,
'popup': 12 << 4, // start of behavorial filtering 'popup': 12 << TypeBitsOffset, // start of behavorial filtering
'popunder': 13 << 4, 'popunder': 13 << TypeBitsOffset,
'main_frame': 14 << 4, // start of 1st-party-only behavorial filtering 'main_frame': 14 << TypeBitsOffset, // start of 1p behavorial filtering
'generichide': 15 << 4, 'generichide': 15 << TypeBitsOffset,
'specifichide': 16 << 4, 'specifichide': 16 << TypeBitsOffset,
'inline-font': 17 << 4, 'inline-font': 17 << TypeBitsOffset,
'inline-script': 18 << 4, 'inline-script': 18 << TypeBitsOffset,
'cname': 19 << 4, 'cname': 19 << TypeBitsOffset,
'data': 20 << 4, // special: a generic data holder 'unused': 20 << TypeBitsOffset,
'redirect': 21 << 4, 'redirect': 21 << TypeBitsOffset,
'webrtc': 22 << 4, 'webrtc': 22 << TypeBitsOffset,
'unsupported': 23 << 4, 'unsupported': 23 << TypeBitsOffset,
}; };
const otherTypeBitValue = typeNameToTypeValue.other; const otherTypeBitValue = typeNameToTypeValue.other;
const bitFromType = type => const bitFromType = type =>
1 << ((typeNameToTypeValue[type] >>> 4) - 1); 1 << ((typeNameToTypeValue[type] >>> TypeBitsOffset) - 1);
// All network request types to bitmap // All network request types to bitmap
// bring origin to 0 (from 4 -- see typeNameToTypeValue) // bring origin to 0 (from TypeBitsOffset -- see typeNameToTypeValue)
// left-shift 1 by the above-calculated value // left-shift 1 by the above-calculated value
// subtract 1 to set all type bits // subtract 1 to set all type bits
const allNetworkTypesBits = const allNetworkTypesBits =
(1 << (otherTypeBitValue >>> 4)) - 1; (1 << (otherTypeBitValue >>> TypeBitsOffset)) - 1;
const allTypesBits = const allTypesBits =
allNetworkTypesBits | allNetworkTypesBits |
1 << (typeNameToTypeValue['popup'] >>> 4) - 1 | 1 << (typeNameToTypeValue['popup'] >>> TypeBitsOffset) - 1 |
1 << (typeNameToTypeValue['main_frame'] >>> 4) - 1 | 1 << (typeNameToTypeValue['main_frame'] >>> TypeBitsOffset) - 1 |
1 << (typeNameToTypeValue['inline-font'] >>> 4) - 1 | 1 << (typeNameToTypeValue['inline-font'] >>> TypeBitsOffset) - 1 |
1 << (typeNameToTypeValue['inline-script'] >>> 4) - 1; 1 << (typeNameToTypeValue['inline-script'] >>> TypeBitsOffset) - 1;
const unsupportedTypeBit = const unsupportedTypeBit =
1 << (typeNameToTypeValue['unsupported'] >>> 4) - 1; 1 << (typeNameToTypeValue['unsupported'] >>> TypeBitsOffset) - 1;
const typeValueToTypeName = { const typeValueToTypeName = [
1: 'stylesheet', '',
2: 'image', 'stylesheet',
3: 'object', 'image',
4: 'script', 'object',
5: 'xmlhttprequest', 'script',
6: 'subdocument', 'xmlhttprequest',
7: 'font', 'subdocument',
8: 'media', 'font',
9: 'websocket', 'media',
10: 'ping', 'websocket',
11: 'other', 'ping',
12: 'popup', 'other',
13: 'popunder', 'popup',
14: 'document', 'popunder',
15: 'generichide', 'document',
16: 'specifichide', 'generichide',
17: 'inline-font', 'specifichide',
18: 'inline-script', 'inline-font',
19: 'cname', 'inline-script',
20: 'data', 'cname',
21: 'redirect', 'unused',
22: 'webrtc', 'redirect',
23: 'unsupported', 'webrtc',
}; 'unsupported',
];
const typeValueFromCatBits = catBits => (catBits >>> 4) & 0b11111; //const typeValueFromCatBits = catBits => (catBits >>> TypeBitsOffset) & 0b11111;
/******************************************************************************/ /******************************************************************************/
@ -222,9 +236,9 @@ const toLogDataInternal = function(categoryBits, tokenHash, iunit) {
} else if ( categoryBits & 0x004 ) { } else if ( categoryBits & 0x004 ) {
logData.options.unshift('1p'); logData.options.unshift('1p');
} }
const type = categoryBits & 0x1F0; const type = categoryBits & TypeBitsMask;
if ( type !== 0 && type !== typeNameToTypeValue.data ) { if ( type !== 0 ) {
logData.options.unshift(typeValueToTypeName[type >>> 4]); logData.options.unshift(typeValueToTypeName[type >>> TypeBitsOffset]);
} }
let raw = logData.pattern.join(''); let raw = logData.pattern.join('');
if ( if (
@ -1444,80 +1458,96 @@ registerFilterClass(FilterOriginEntityMiss);
/******************************************************************************/ /******************************************************************************/
const FilterDataHolder = class { const FilterModifier = class {
constructor(dataType, data) { constructor(actionBits, modifier, value) {
this.dataType = dataType; this.actionBits = actionBits;
this.data = data; this.type = modifier;
this.value = value;
this.cache = undefined;
} }
match() { match() {
return true; return true;
} }
matchAndFetchData(type, callback) { matchAndFetchModifiers(env) {
if ( this.dataType !== type ) { return; } if ( this.type !== env.modifier ) { return; }
callback(this); env.results.push(
new FilterModifierResult(env.bits, env.th, env.iunit)
);
} }
getData(type) { get modifier() {
if ( type === this.dataType ) { return this;
return this.data;
}
} }
logData(details) { logData(details) {
let opt = this.dataType; let opt = vAPI.StaticFilteringParser.netOptionTokenNames.get(this.type);
if ( this.data !== '' ) { if ( this.value !== '' ) {
opt += `=${this.data}`; opt += `=${this.value}`;
} }
details.options.push(opt); details.options.push(opt);
} }
toSelfie() { toSelfie() {
return [ this.fid, this.dataType, this.data ]; return [ this.fid, this.actionBits, this.type, this.value ];
} }
static compile(details) { static compile(details) {
return [ FilterDataHolder.fid, details.dataType, details.data ]; return [
FilterModifier.fid,
details.action | details.important,
details.modifyType,
details.modifyValue
];
} }
static fromCompiled(args) { static fromCompiled(args) {
return new FilterDataHolder(args[1], args[2]); return new FilterModifier(args[1], args[2], args[3]);
} }
static fromSelfie(args) { static fromSelfie(args) {
return new FilterDataHolder(args[1], args[2]); return new FilterModifier(args[1], args[2], args[3]);
} }
static keyFromArgs(args) { static keyFromArgs(args) {
return `${args[1]}\t${args[2]}`; return `${args[1]}\t${args[2]}\t${args[3]}`;
} }
}; };
registerFilterClass(FilterDataHolder); registerFilterClass(FilterModifier);
// Helper class for storing instances of FilterDataHolder which were found to // Helper class for storing instances of FilterModifier which were found to
// be a match. // be a match.
const FilterDataHolderResult = class { const FilterModifierResult = class {
constructor(bits, th, iunit) { constructor(bits, th, iunit) {
this.bits = bits;
this.th = th;
this.iunit = iunit; this.iunit = iunit;
this.th = th;
this.bits = (bits & ~ActionBitsMask) | this.modifier.actionBits;
} }
getData(type) { get filter() {
return filterUnits[this.iunit].getData(type); return filterUnits[this.iunit];
}
get modifier() {
return this.filter.modifier;
} }
get result() { get result() {
return (this.bits & AllowAction) === 0 ? 1 : 2; return (this.bits & AllowAction) === 0 ? 1 : 2;
} }
get value() {
return this.modifier.value;
}
logData() { logData() {
const r = toLogDataInternal(this.bits, this.th, this.iunit); const r = toLogDataInternal(this.bits, this.th, this.iunit);
r.source = 'static'; r.source = 'static';
r.result = this.result; r.result = this.result;
r.modifier = true;
return r; return r;
} }
}; };
@ -1636,20 +1666,21 @@ const FilterCompositeAll = class extends FilterCollection {
return true; return true;
} }
matchAndFetchData(type, callback) { matchAndFetchModifiers(env) {
if ( this.match() !== true ) { return false; } if ( this.match() !== true ) { return false; }
this.forEach(iunit => { this.forEach(iunit => {
const f = filterUnits[iunit]; const f = filterUnits[iunit];
if ( f.matchAndFetchData instanceof Function === false ) { return; } if ( f.matchAndFetchModifiers instanceof Function ) {
f.matchAndFetchData(type, ( ) => { callback(this); }); f.matchAndFetchModifiers(env);
}
}); });
} }
getData(type) { get modifier() {
return this.forEach(iunit => { return this.forEach(iunit => {
const f = filterUnits[iunit]; const f = filterUnits[iunit];
if ( f.matchAndFetchData instanceof Function ) { if ( f.matchAndFetchModifiers instanceof Function ) {
return f.getData(type); return f.modifier;
} }
}); });
} }
@ -1902,7 +1933,7 @@ const FilterPlainTrie = class {
return false; return false;
} }
matchAndFetchData(/* type, out */) { matchAndFetchModifiers(/* type, callback */) {
// TODO // TODO
} }
@ -1953,12 +1984,11 @@ const FilterBucket = class extends FilterCollection {
return false; return false;
} }
matchAndFetchData(type, callback) { matchAndFetchModifiers(env) {
const units = filterUnits; const units = filterUnits;
this.forEach(iunit => { this.forEach(iunit => {
units[iunit].matchAndFetchData(type, f => { env.iunit = iunit;
callback(f, iunit); units[iunit].matchAndFetchModifiers(env);
});
}); });
} }
@ -2226,8 +2256,8 @@ const FilterParser = class {
// 0101 (0x5): anchored to the hostname and end of the URL. // 0101 (0x5): anchored to the hostname and end of the URL.
this.anchor = 0; this.anchor = 0;
this.badFilter = false; this.badFilter = false;
this.dataType = undefined; this.modifyType = undefined;
this.data = undefined; this.modifyValue = undefined;
this.invalid = false; this.invalid = false;
this.pattern = ''; this.pattern = '';
this.firstParty = false; this.firstParty = false;
@ -2325,18 +2355,17 @@ const FilterParser = class {
this.badFilter = true; this.badFilter = true;
break; break;
case parser.OPTTokenCsp: case parser.OPTTokenCsp:
this.typeBits = bitFromType('data'); this.modifyType = parser.OPTTokenCsp;
this.dataType = 'csp';
if ( val !== undefined ) { if ( val !== undefined ) {
if ( this.reBadCSP.test(val) ) { return false; } if ( this.reBadCSP.test(val) ) { return false; }
this.data = val; this.modifyValue = val;
} else if ( this.action === AllowAction ) { } else if ( this.action === AllowAction ) {
this.data = ''; this.modifyValue = '';
} }
break; break;
// https://github.com/gorhill/uBlock/issues/2294 // https://github.com/gorhill/uBlock/issues/2294
// Detect and discard filter if domain option contains nonsensical // Detect and discard filter if domain option contains
// characters. // nonsensical characters.
case parser.OPTTokenDomain: case parser.OPTTokenDomain:
this.domainOpt = this.parseHostnameList( this.domainOpt = this.parseHostnameList(
parser, parser,
@ -2368,6 +2397,14 @@ const FilterParser = class {
if ( this.redirect !== 0 ) { return false; } if ( this.redirect !== 0 ) { return false; }
this.redirect = id === parser.OPTTokenRedirectRule ? 2 : 1; this.redirect = id === parser.OPTTokenRedirectRule ? 2 : 1;
break; break;
case parser.OPTTokenQueryprune:
this.modifyType = parser.OPTTokenQueryprune;
if ( val !== undefined ) {
this.modifyValue = val;
} else if ( this.action === AllowAction ) {
this.modifyValue = '';
}
break;
case parser.OPTTokenInvalid: case parser.OPTTokenInvalid:
return false; return false;
default: default:
@ -2576,7 +2613,7 @@ const FilterParser = class {
isJustOrigin() { isJustOrigin() {
return this.isRegex === false && return this.isRegex === false &&
this.dataType === undefined && this.modifyType === undefined &&
this.denyallowOpt === '' && this.denyallowOpt === '' &&
this.domainOpt !== '' && ( this.domainOpt !== '' && (
this.pattern === '*' || ( this.pattern === '*' || (
@ -2728,7 +2765,7 @@ FilterContainer.prototype.freeze = function() {
// Special cases: delegate to more specialized engines. // Special cases: delegate to more specialized engines.
// Redirect engine. // Redirect engine.
if ( (bits & 0x1F0) === redirectTypeValue ) { if ( (bits & TypeBitsMask) === redirectTypeValue ) {
µb.redirectEngine.fromCompiledRule(args[1]); µb.redirectEngine.fromCompiledRule(args[1]);
continue; continue;
} }
@ -2803,10 +2840,9 @@ FilterContainer.prototype.freeze = function() {
this.badFilters.clear(); this.badFilters.clear();
this.goodFilters.clear(); this.goodFilters.clear();
// Skip 'data' type since bidi-trie does not (yet) support matchAll(). // Skip modify realm, since bidi-trie does not (yet) support matchAll().
const dataTypeValue = typeValueFromCatBits(typeNameToTypeValue['data']);
for ( const [ catBits, bucket ] of this.categories ) { for ( const [ catBits, bucket ] of this.categories ) {
if ( typeValueFromCatBits(catBits) === dataTypeValue ) { continue; } if ( (catBits & ActionBitsMask) === ModifyAction ) { continue; }
for ( const iunit of bucket.values() ) { for ( const iunit of bucket.values() ) {
const f = units[iunit]; const f = units[iunit];
if ( f instanceof FilterBucket === false ) { continue; } if ( f instanceof FilterBucket === false ) { continue; }
@ -2980,7 +3016,7 @@ FilterContainer.prototype.compile = function(parser, writer) {
parsed.isPureHostname && parsed.isPureHostname &&
parsed.domainOpt === '' && parsed.domainOpt === '' &&
parsed.denyallowOpt === '' && parsed.denyallowOpt === '' &&
parsed.dataType === undefined parsed.modifyType === undefined
) { ) {
parsed.tokenHash = this.dotTokenHash; parsed.tokenHash = this.dotTokenHash;
this.compileToAtomicFilter(parsed, parsed.pattern, writer); this.compileToAtomicFilter(parsed, parsed.pattern, writer);
@ -3064,8 +3100,10 @@ FilterContainer.prototype.compile = function(parser, writer) {
} }
// Data // Data
if ( parsed.dataType !== undefined ) { if ( parsed.modifyType !== undefined ) {
units.push(FilterDataHolder.compile(parsed)); units.push(FilterModifier.compile(parsed));
parsed.action = ModifyAction;
parsed.important = 0;
} }
const fdata = units.length === 1 const fdata = units.length === 1
@ -3108,7 +3146,7 @@ FilterContainer.prototype.compileToAtomicFilter = function(
do { do {
if ( typeBits & 1 ) { if ( typeBits & 1 ) {
writer.push([ writer.push([
catBits | (bitOffset << 4), catBits | (bitOffset << TypeBitsOffset),
parsed.tokenHash, parsed.tokenHash,
fdata fdata
]); ]);
@ -3154,70 +3192,113 @@ FilterContainer.prototype.fromCompiledContent = function(reader) {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.realmMatchAndFetchData = function( // TODO:
realmBits, // Evaluate converting redirect directives in redirect engine into
partyBits, // modifiers in static network filtering engine.
type, //
out // 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
) { ) {
const bits01 = realmBits | typeNameToTypeValue.data;
const bits11 = realmBits | typeNameToTypeValue.data | partyBits;
const bucket01 = this.categories.get(bits01);
const bucket11 = partyBits !== 0
? this.categories.get(bits11)
: undefined;
if ( bucket01 === undefined && bucket11 === undefined ) { return false; }
const t = type, o = out; // to avoid jshint warning
const fdhr = (a, b, c) => new FilterDataHolderResult(a, b, c);
const units = filterUnits;
const tokenHashes = urlTokenizer.getTokens(bidiTrie);
let i = 0;
for (;;) {
const th = tokenHashes[i];
if ( th === 0 ) { return; }
$tokenBeg = tokenHashes[i+1];
if ( bucket01 !== undefined ) bucket01: {
const iunit = bucket01.get(th);
if ( iunit === undefined ) { break bucket01; }
units[iunit].matchAndFetchData(type, (f, i) => {
o.set(f.getData(t), fdhr(bits01, th, i || iunit));
});
}
if ( bucket11 !== undefined ) bucket11: {
const iunit = bucket11.get(th);
if ( iunit === undefined ) { break bucket11; }
units[iunit].matchAndFetchData(t, (f, i) => {
o.set(f.getData(t), fdhr(bits11, th, i || iunit));
});
}
i += 2;
}
};
/******************************************************************************/
FilterContainer.prototype.matchAndFetchData = function(fctxt, type) {
$requestURL = urlTokenizer.setURL(fctxt.url); $requestURL = urlTokenizer.setURL(fctxt.url);
$docHostname = fctxt.getDocHostname(); $docHostname = fctxt.getDocHostname();
$docDomain = fctxt.getDocDomain(); $docDomain = fctxt.getDocDomain();
$docEntity.reset(); $docEntity.reset();
$requestHostname = fctxt.getHostname(); $requestHostname = fctxt.getHostname();
const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue;
const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty; const partyBits = fctxt.is3rdPartyToDoc() ? ThirdParty : FirstParty;
const catBits00 = ModifyAction;
const catBits01 = ModifyAction | typeBits;
const catBits10 = ModifyAction | partyBits;
const catBits11 = ModifyAction | typeBits | partyBits;
const bucket00 = this.categories.get(catBits00);
const bucket01 = typeBits !== 0
? this.categories.get(catBits01)
: undefined;
const bucket10 = partyBits !== 0
? this.categories.get(catBits10)
: undefined;
const bucket11 = typeBits !== 0 && partyBits !== 0
? this.categories.get(catBits11)
: undefined;
const modifierResults = [];
const env = {
modifier: vAPI.StaticFilteringParser.netOptionTokenIds.get(modifierType) || 0,
bits: 0,
th: 0,
iunit: 0,
results: modifierResults,
};
const units = filterUnits;
const tokenHashes = urlTokenizer.getTokens(bidiTrie);
let i = 0;
for (;;) {
const th = tokenHashes[i];
if ( th === 0 ) { break; }
env.th = th;
$tokenBeg = tokenHashes[i+1];
if ( bucket00 !== undefined ) {
const iunit = bucket00.get(th);
if ( iunit !== undefined ) {
env.bits = catBits00; env.iunit = iunit;
units[iunit].matchAndFetchModifiers(env);
}
}
if ( bucket01 !== undefined ) {
const iunit = bucket01.get(th);
if ( iunit !== undefined ) {
env.bits = catBits01; env.iunit = iunit;
units[iunit].matchAndFetchModifiers(env);
}
}
if ( bucket10 !== undefined ) {
const iunit = bucket10.get(th);
if ( iunit !== undefined ) {
env.bits = catBits10; env.iunit = iunit;
units[iunit].matchAndFetchModifiers(env);
}
}
if ( bucket11 !== undefined ) {
const iunit = bucket11.get(th);
if ( iunit !== undefined ) {
env.bits = catBits11; env.iunit = iunit;
units[iunit].matchAndFetchModifiers(env);
}
}
i += 2;
}
if ( modifierResults.length === 0 ) { return; }
const toAddImportant = new Map(); const toAddImportant = new Map();
this.realmMatchAndFetchData(BlockImportant, partyBits, type, toAddImportant);
const toAdd = new Map(); const toAdd = new Map();
this.realmMatchAndFetchData(BlockAction, partyBits, type, toAdd);
if ( toAddImportant.size === 0 && toAdd.size === 0 ) { return []; }
const toRemove = new Map(); const toRemove = new Map();
this.realmMatchAndFetchData(AllowAction, partyBits, type, toRemove);
for ( const modifierResult of modifierResults ) {
const actionBits = modifierResult.bits & ActionBitsMask;
const modifyValue = modifierResult.modifier.value;
if ( actionBits === BlockImportant ) {
toAddImportant.set(modifyValue, modifierResult);
} else if ( actionBits === BlockAction ) {
toAdd.set(modifyValue, modifierResult);
} else {
toRemove.set(modifyValue, modifierResult);
}
}
if ( toAddImportant.size === 0 && toAdd.size === 0 ) { return; }
// Remove entries overriden by important block filters. // Remove entries overriden by important block filters.
for ( const key of toAddImportant.keys() ) { for ( const key of toAddImportant.keys() ) {
@ -3231,9 +3312,11 @@ FilterContainer.prototype.matchAndFetchData = function(fctxt, type) {
if ( toRemove.has('') ) { if ( toRemove.has('') ) {
if ( toAdd.size !== 0 ) { if ( toAdd.size !== 0 ) {
toAdd.clear(); toAdd.clear();
toRemove.forEach((v, k, m) => { if ( toRemove.size !== 1 ) {
if ( k !== '' ) { m.delete(k); } const entry = toRemove.get('');
}); toRemove.clear();
toRemove.set('', entry);
}
} else { } else {
toRemove.clear(); toRemove.clear();
} }
@ -3249,11 +3332,22 @@ FilterContainer.prototype.matchAndFetchData = function(fctxt, type) {
} }
} }
// Merge important and normal block filters if (
for ( const [ key, entry ] of toAddImportant ) { toAdd.size === 0 &&
toAdd.set(key, entry); toAddImportant.size === 0 &&
toRemove.size === 0
) {
return;
} }
return Array.from(toAdd.values()).concat(Array.from(toRemove.values()));
const out = Array.from(toAdd.values());
if ( toAddImportant.size !== 0 ) {
out.push(...toAddImportant.values());
}
if ( toRemove.size !== 0 ) {
out.push(...toRemove.values());
}
return out;
}; };
/******************************************************************************/ /******************************************************************************/
@ -3460,6 +3554,46 @@ FilterContainer.prototype.matchString = function(fctxt, modifiers = 0) {
/******************************************************************************/ /******************************************************************************/
FilterContainer.prototype.filterQuery = function(fctxt) {
const directives = this.matchAndFetchModifiers(fctxt, 'queryprune');
if ( directives === undefined ) { return; }
const params = [];
const out = [];
const url = new URL(fctxt.url);
for ( const directive of directives ) {
const modifier = directive.modifier;
if ( modifier.cache === undefined ) {
let retext = modifier.value;
if ( retext.startsWith('|') ) { retext = `^${retext.slice(1)}`; }
if ( retext.endsWith('|') ) { retext = `${retext.slice(0,-1)}$`; }
modifier.cache = new RegExp(retext);
}
const re = modifier.cache;
let filtered = false;
for ( const [ key, value ] of url.searchParams ) {
if ( re.test(`${key}=${value}`) ) {
filtered = true;
} else {
params.push(`${key}=${encodeURIComponent(value)}`);
}
}
if ( filtered ) {
out.push(directive);
}
}
if ( out.length === 0 ) { return; }
url.search = params.length !== 0 ? `?${params.join('&')}` : '';
fctxt.redirectURL = url.href;
return out;
};
FilterContainer.prototype.hasQuery = function(fctxt) {
urlTokenizer.setURL(fctxt.url);
return urlTokenizer.hasQuery();
};
/******************************************************************************/
FilterContainer.prototype.toLogData = function() { FilterContainer.prototype.toLogData = function() {
if ( this.$filterUnit === 0 ) { return; } if ( this.$filterUnit === 0 ) { return; }
const logData = toLogDataInternal( const logData = toLogDataInternal(
@ -3471,7 +3605,7 @@ FilterContainer.prototype.toLogData = function() {
logData.tokenHash = this.$tokenHash; logData.tokenHash = this.$tokenHash;
logData.result = this.$filterUnit === 0 logData.result = this.$filterUnit === 0
? 0 ? 0
: ((this.$catBits & 1) !== 0 ? 2 : 1); : ((this.$catBits & AllowAction) !== 0 ? 2 : 1);
return logData; return logData;
}; };
@ -3505,8 +3639,9 @@ FilterContainer.prototype.benchmark = async function(action, target) {
const requests = await µb.loadBenchmarkDataset(); const requests = await µb.loadBenchmarkDataset();
if ( Array.isArray(requests) === false || requests.length === 0 ) { if ( Array.isArray(requests) === false || requests.length === 0 ) {
console.info('No requests found to benchmark'); const text = 'No dataset found to benchmark';
return; console.info(text);
return text;
} }
const print = log.print; const print = log.print;
@ -3560,8 +3695,11 @@ FilterContainer.prototype.benchmark = async function(action, target) {
print(`\turl=${fctxt.url}`); print(`\turl=${fctxt.url}`);
print(`\tdocOrigin=${fctxt.getDocOrigin()}`); print(`\tdocOrigin=${fctxt.getDocOrigin()}`);
} }
if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) { if ( r !== 1 && this.hasQuery(fctxt) ) {
this.matchAndFetchData(fctxt, 'csp'); this.filterQuery(fctxt, 'queryprune');
}
if ( r !== 1 && fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) {
this.matchAndFetchModifiers(fctxt, 'csp');
} }
} }
const t1 = self.performance.now(); const t1 = self.performance.now();
@ -3687,7 +3825,7 @@ FilterContainer.prototype.bucketHistogram = function() {
"FilterAnchorHn" => 1453} "FilterAnchorHn" => 1453}
"FilterOriginMiss" => 730} "FilterOriginMiss" => 730}
"FilterPatternGeneric" => 601} "FilterPatternGeneric" => 601}
"FilterDataHolder" => 404} "FilterModifier" => 404}
"FilterOriginMissSet" => 316} "FilterOriginMissSet" => 316}
"FilterTrailingSeparator" => 235} "FilterTrailingSeparator" => 235}
"FilterAnchorRight" => 174} "FilterAnchorRight" => 174}

View File

@ -64,7 +64,7 @@ const onBeforeRequest = function(details) {
// https://github.com/chrisaljoudi/uBlock/issues/1001 // https://github.com/chrisaljoudi/uBlock/issues/1001
// This must be executed regardless of whether the request is // This must be executed regardless of whether the request is
// behind-the-scene // behind-the-scene
if ( details.type === 'main_frame' ) { if ( fctxt.itype === fctxt.MAIN_FRAME ) {
return onBeforeRootFrameRequest(fctxt); return onBeforeRootFrameRequest(fctxt);
} }
@ -94,33 +94,31 @@ const onBeforeRequest = function(details) {
fctxt.setRealm('network').toLogger(); fctxt.setRealm('network').toLogger();
} }
// Redirected
if ( fctxt.redirectURL !== undefined ) {
return { redirectUrl: fctxt.redirectURL };
}
// Not redirected
// Blocked
if ( result === 1 ) {
return { cancel: true };
}
// Not blocked // Not blocked
if ( result !== 1 ) {
if ( if (
fctxt.itype === fctxt.SUB_FRAME &&
details.parentFrameId !== -1 && details.parentFrameId !== -1 &&
details.type === 'sub_frame' &&
details.aliasURL === undefined details.aliasURL === undefined
) { ) {
pageStore.setFrameURL(details.frameId, details.url); pageStore.setFrameURL(details.frameId, details.url);
} }
if ( result === 2 ) { if ( result === 2 ) {
return { cancel: false }; return { cancel: false };
} }
return;
}
// Blocked
if ( fctxt.redirectURL === undefined ) {
return { cancel: true };
}
if ( µb.logger.enabled ) {
fctxt.setRealm('redirect')
.setFilter({ source: 'redirect', raw: µb.redirectEngine.resourceNameRegister })
.toLogger();
}
return { redirectUrl: fctxt.redirectURL };
}; };
/******************************************************************************/ /******************************************************************************/
@ -134,14 +132,14 @@ const onBeforeRootFrameRequest = function(fctxt) {
// This must be executed regardless of whether the request is // This must be executed regardless of whether the request is
// behind-the-scene // behind-the-scene
const requestHostname = fctxt.getHostname(); const requestHostname = fctxt.getHostname();
const logEnabled = µb.logger.enabled; const loggerEnabled = µb.logger.enabled;
let result = 0, let result = 0;
logData; let logData;
// If the site is whitelisted, disregard strict blocking // If the site is whitelisted, disregard strict blocking
if ( µb.getNetFilteringSwitch(requestURL) === false ) { if ( µb.getNetFilteringSwitch(requestURL) === false ) {
result = 2; result = 2;
if ( logEnabled ) { if ( loggerEnabled ) {
logData = { engine: 'u', result: 2, raw: 'whitelisted' }; logData = { engine: 'u', result: 2, raw: 'whitelisted' };
} }
} }
@ -152,16 +150,24 @@ const onBeforeRootFrameRequest = function(fctxt) {
µb.sessionSwitches.evaluateZ('no-strict-blocking', requestHostname) µb.sessionSwitches.evaluateZ('no-strict-blocking', requestHostname)
) { ) {
result = 2; result = 2;
if ( logEnabled ) { if ( loggerEnabled ) {
logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: ' + µb.sessionSwitches.z + ' true' }; logData = {
engine: 'u',
result: 2,
raw: `no-strict-blocking: ${µb.sessionSwitches.z} true`
};
} }
} }
// Temporarily whitelisted? // Temporarily whitelisted?
if ( result === 0 && strictBlockBypasser.isBypassed(requestHostname) ) { if ( result === 0 && strictBlockBypasser.isBypassed(requestHostname) ) {
result = 2; result = 2;
if ( logEnabled ) { if ( loggerEnabled ) {
logData = { engine: 'u', result: 2, raw: 'no-strict-blocking: true (temporary)' }; logData = {
engine: 'u',
result: 2,
raw: 'no-strict-blocking: true (temporary)'
};
} }
} }
@ -170,9 +176,8 @@ const onBeforeRootFrameRequest = function(fctxt) {
// Check for specific block // Check for specific block
if ( result === 0 ) { if ( result === 0 ) {
fctxt.type = 'main_frame';
result = snfe.matchString(fctxt, 0b0001); result = snfe.matchString(fctxt, 0b0001);
if ( result !== 0 || logEnabled ) { if ( result !== 0 || loggerEnabled ) {
logData = snfe.toLogData(); logData = snfe.toLogData();
} }
} }
@ -181,7 +186,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
if ( result === 0 ) { if ( result === 0 ) {
fctxt.type = 'no_type'; fctxt.type = 'no_type';
result = snfe.matchString(fctxt, 0b0001); result = snfe.matchString(fctxt, 0b0001);
if ( result !== 0 || logEnabled ) { if ( result !== 0 || loggerEnabled ) {
logData = snfe.toLogData(); logData = snfe.toLogData();
} }
// https://github.com/chrisaljoudi/uBlock/issues/1128 // https://github.com/chrisaljoudi/uBlock/issues/1128
@ -196,21 +201,43 @@ const onBeforeRootFrameRequest = function(fctxt) {
result = 0; result = 0;
logData = undefined; logData = undefined;
} }
fctxt.type = 'main_frame';
} }
// Log
fctxt.type = 'main_frame';
const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest'); const pageStore = µb.bindTabToPageStats(fctxt.tabId, 'beforeRequest');
if ( pageStore ) { if ( pageStore ) {
pageStore.journalAddRootFrame('uncommitted', requestURL); pageStore.journalAddRootFrame('uncommitted', requestURL);
pageStore.journalAddRequest(requestHostname, result); pageStore.journalAddRequest(requestHostname, result);
} }
if ( logEnabled ) { // Log
fctxt.setRealm('network').setFilter(logData).toLogger(); if ( loggerEnabled ) {
fctxt.setRealm('network').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 ( loggerEnabled ) {
fctxt.setRealm('network').toLogger();
}
// Redirected
if ( fctxt.redirectURL !== undefined ) {
return { redirectUrl: fctxt.redirectURL };
} }
// Not blocked // Not blocked
if ( result !== 1 ) { return; } if ( result !== 1 ) { return; }
// No log data means no strict blocking (because we need to report why // No log data means no strict blocking (because we need to report why
@ -218,6 +245,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
if ( logData === undefined ) { return; } if ( logData === undefined ) { return; }
// Blocked // Blocked
const query = encodeURIComponent(JSON.stringify({ const query = encodeURIComponent(JSON.stringify({
url: requestURL, url: requestURL,
hn: requestHostname, hn: requestHostname,
@ -307,7 +335,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) {
fctxt.tabOrigin.endsWith('-scheme') === false && fctxt.tabOrigin.endsWith('-scheme') === false &&
µb.URI.isNetworkURI(fctxt.tabOrigin) || µb.URI.isNetworkURI(fctxt.tabOrigin) ||
µb.userSettings.advancedUserEnabled || µb.userSettings.advancedUserEnabled ||
fctxt.type === 'csp_report' fctxt.itype === fctxt.CSP_REPORT
) { ) {
result = pageStore.filterRequest(fctxt); result = pageStore.filterRequest(fctxt);
@ -440,9 +468,7 @@ const onHeadersReceived = function(details) {
const µb = µBlock; const µb = µBlock;
const fctxt = µb.filteringContext.fromWebrequestDetails(details); const fctxt = µb.filteringContext.fromWebrequestDetails(details);
const requestType = fctxt.type; const isRootDoc = fctxt.itype === fctxt.MAIN_FRAME;
const isRootDoc = requestType === 'main_frame';
const isDoc = isRootDoc || requestType === 'sub_frame';
let pageStore = µb.pageStoreFromTabId(fctxt.tabId); let pageStore = µb.pageStoreFromTabId(fctxt.tabId);
if ( pageStore === null ) { if ( pageStore === null ) {
@ -451,11 +477,11 @@ const onHeadersReceived = function(details) {
} }
if ( pageStore.getNetFilteringSwitch(fctxt) === false ) { return; } if ( pageStore.getNetFilteringSwitch(fctxt) === false ) { return; }
if ( requestType === 'image' || requestType === 'media' ) { if ( fctxt.itype === fctxt.IMAGE || fctxt.itype === fctxt.MEDIA ) {
return foilLargeMediaElement(details, fctxt, pageStore); return foilLargeMediaElement(details, fctxt, pageStore);
} }
if ( isDoc === false ) { return; } if ( isRootDoc === false && fctxt.itype !== fctxt.SUB_FRAME ) { return; }
// Keep in mind response headers will be modified in-place if needed, so // Keep in mind response headers will be modified in-place if needed, so
// `details.responseHeaders` will always point to the modified response // `details.responseHeaders` will always point to the modified response
@ -801,6 +827,7 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
const µb = µBlock; const µb = µBlock;
const loggerEnabled = µb.logger.enabled; const loggerEnabled = µb.logger.enabled;
const cspSubsets = []; const cspSubsets = [];
const requestType = fctxt.type;
// Start collecting policies >>>>>>>> // Start collecting policies >>>>>>>>
@ -849,11 +876,14 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
// Static filtering. // Static filtering.
fctxt.type = requestType;
const staticDirectives = const staticDirectives =
µb.staticNetFilteringEngine.matchAndFetchData(fctxt, 'csp'); µb.staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp');
if ( staticDirectives !== undefined ) {
for ( const directive of staticDirectives ) { for ( const directive of staticDirectives ) {
if ( directive.result !== 1 ) { continue; } if ( directive.result !== 1 ) { continue; }
cspSubsets.push(directive.getData('csp')); cspSubsets.push(directive.modifier.value);
}
} }
// URL filtering `allow` rules override static filtering. // URL filtering `allow` rules override static filtering.
@ -897,11 +927,10 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) {
// Static CSP policies will be applied. // Static CSP policies will be applied.
if ( loggerEnabled && staticDirectives.length !== 0 ) { if ( loggerEnabled && staticDirectives !== undefined ) {
fctxt.setRealm('network').setType('csp'); fctxt.setRealm('network')
for ( const directive of staticDirectives ) { .pushFilters(staticDirectives.map(a => a.logData()))
fctxt.setFilter(directive.logData()).toLogger(); .toLogger();
}
} }
if ( cspSubsets.length === 0 ) { return; } if ( cspSubsets.length === 0 ) { return; }

View File

@ -61,6 +61,7 @@
this._urlIn = ''; this._urlIn = '';
this._urlOut = ''; this._urlOut = '';
this._tokenized = false; this._tokenized = false;
this._hasQuery = 0;
// https://www.reddit.com/r/uBlockOrigin/comments/dzw57l/ // https://www.reddit.com/r/uBlockOrigin/comments/dzw57l/
// Remember: 1 token needs two slots // Remember: 1 token needs two slots
this._tokens = new Uint32Array(2064); this._tokens = new Uint32Array(2064);
@ -74,6 +75,7 @@
if ( url !== this._urlIn ) { if ( url !== this._urlIn ) {
this._urlIn = url; this._urlIn = url;
this._urlOut = url.toLowerCase(); this._urlOut = url.toLowerCase();
this._hasQuery = 0;
this._tokenized = false; this._tokenized = false;
} }
return this._urlOut; return this._urlOut;
@ -115,6 +117,14 @@
return this._tokens; return this._tokens;
} }
hasQuery() {
if ( this._hasQuery === 0 ) {
const i = this._urlOut.indexOf('?');
this._hasQuery = i !== -1 ? i + 1 : -1;
}
return this._hasQuery > 0;
}
tokenHashFromString(s) { tokenHashFromString(s) {
const l = s.length; const l = s.length;
if ( l === 0 ) { return this.emptyTokenHash; } if ( l === 0 ) { return this.emptyTokenHash; }
@ -155,23 +165,34 @@
l = 2048; l = 2048;
} }
encodeInto.haystackLen = l; encodeInto.haystackLen = l;
let j = 0;
let hasq = -1;
mainLoop: {
const knownTokens = this.knownTokens; const knownTokens = this.knownTokens;
const vtc = this._validTokenChars; const vtc = this._validTokenChars;
const charCodes = encodeInto.haystack; const charCodes = encodeInto.haystack;
let i = 0, j = 0, n, ti, th; let i = 0, n = 0, ti = 0, th = 0;
for (;;) { for (;;) {
for (;;) { for (;;) {
if ( i === l ) { return j; } if ( i === l ) { break mainLoop; }
th = vtc[(charCodes[i] = url.charCodeAt(i))]; const cc = url.charCodeAt(i);
charCodes[i] = cc;
i += 1; i += 1;
th = vtc[cc];
if ( th !== 0 ) { break; } if ( th !== 0 ) { break; }
if ( cc === 0x3F /* '?' */ ) { hasq = i; }
} }
ti = i - 1; n = 1; ti = i - 1; n = 1;
for (;;) { for (;;) {
if ( i === l ) { break; } if ( i === l ) { break; }
const v = vtc[(charCodes[i] = url.charCodeAt(i))]; const cc = url.charCodeAt(i);
charCodes[i] = cc;
i += 1; i += 1;
if ( v === 0 ) { break; } const v = vtc[cc];
if ( v === 0 ) {
if ( cc === 0x3F /* '?' */ ) { hasq = i; }
break;
}
if ( n === 7 ) { continue; } if ( n === 7 ) { continue; }
th = th << 4 ^ v; th = th << 4 ^ v;
n += 1; n += 1;
@ -183,6 +204,9 @@
} }
} }
} }
this._hasQuery = hasq;
return j;
}
})(); })();
/******************************************************************************/ /******************************************************************************/

View File

@ -58,11 +58,11 @@
<input type="search" placeholder="logFilterPrompt"> <input type="search" placeholder="logFilterPrompt">
<span id="filterExprButton" class="button fa-icon expanded" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span> <span id="filterExprButton" class="button fa-icon expanded" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span>
<div id="filterExprPicker"> <div id="filterExprPicker">
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t--\t|\t<<\t|\t##" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="\t\+\+\t|\t\*\*\t|\t#@#" data-i18n="loggerRowFiltererBuiltinAllowed"></span></div> <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t--\t|\t<<\t|\t##" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="\t\+\+\t|\t\*\*\t|\t#@#" data-i18n="loggerRowFiltererBuiltinAllowed"></span><span data-filtex="[$,](?:csp|queryprune)=|\t\<\<\t">modified</span></div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span> <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span>
<span style="flex-direction: column;"> <span style="flex-direction: column;">
<div style="margin-bottom: 1px;"><span data-filtex="\t(?:css|(?:inline-)?font)\t">css/font</span><span data-filtex="\timage\t">image</span><span data-filtex="\tmedia\t">media</span><span data-filtex="\t(?:inline-)?script(?:ing)?\t">script</span></div> <div style="margin-bottom: 1px;"><span data-filtex="\t(?:css|(?:inline-)?font)\t">css/font</span><span data-filtex="\timage\t">image</span><span data-filtex="\tmedia\t">media</span><span data-filtex="\t(?:inline-)?script(?:ing)?\t">script</span></div>
<div><span data-filtex="\t(?:websocket|xhr)\t">xhr</span><span data-filtex="\tframe\t">frame</span><span data-filtex="\t(?:dom|g(?:eneric)?hide|s(?:pecific)?hide)\t">dom</span><span data-filtex="\t(?:beacon|csp|csp_report|doc|ping|popup|popunder|other)\t">other</span></div> <div><span data-filtex="\t(?:websocket|xhr)\t">xhr</span><span data-filtex="\tframe\t">frame</span><span data-filtex="\t(?:dom|g(?:eneric)?hide|s(?:pecific)?hide)\t">dom</span><span data-filtex="\t(?:beacon|csp_report|doc|ping|popup|popunder|other)\t">other</span></div>
</span> </span>
</div> </div>
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:0,)?1\t" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\t(?:3(?:,\d)?|0,3)\t" data-i18n="loggerRowFiltererBuiltin3p"></span></div> <div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:0,)?1\t" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\t(?:3(?:,\d)?|0,3)\t" data-i18n="loggerRowFiltererBuiltin3p"></span></div>