1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-05 11:37:01 +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
);
if ( pos === -1 ) {
return { redirectUrl: root };
return { cancel: true };
}
secrets.splice(pos, 1);
};

View File

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

View File

@ -139,8 +139,8 @@ const µBlock = (( ) => { // jshint ignore:line
// Read-only
systemSettings: {
compiledMagic: 29, // Increase when compiled format changes
selfieMagic: 29, // Increase when selfie format changes
compiledMagic: 30, // Increase when compiled format changes
selfieMagic: 30, // Increase when selfie format changes
},
// 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); }
const isException = parser.isException();
const hints = [];
for ( let [ text, bits ] of parser.netOptionTokens ) {
for ( let [ text, bits ] of parser.netOptionTokenDescriptors ) {
if ( isNegated && (bits & parser.OPTCanNegate) === 0 ) { continue; }
if ( isException ) {
if ( (bits & parser.OPTBlockOnly) !== 0 ) { continue; }

View File

@ -23,55 +23,140 @@
/******************************************************************************/
µBlock.FilteringContext = function(other) {
if ( other instanceof µBlock.FilteringContext ) {
return this.fromFilteringContext(other);
}
this.tstamp = 0;
this.realm = '';
this.id = undefined;
this.type = undefined;
this.url = undefined;
this.aliasURL = undefined;
this.hostname = undefined;
this.domain = undefined;
this.docId = -1;
this.frameId = -1;
this.docOrigin = undefined;
this.docHostname = undefined;
this.docDomain = undefined;
this.tabId = undefined;
this.tabOrigin = undefined;
this.tabHostname = undefined;
this.tabDomain = undefined;
this.redirectURL = undefined;
this.filter = undefined;
{
// >>>>> 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,
};
µBlock.FilteringContext.prototype = {
fromTabId: function(tabId) {
/******************************************************************************/
const FilteringContext = class {
constructor(other) {
if ( other instanceof FilteringContext ) {
return this.fromFilteringContext(other);
}
this.tstamp = 0;
this.realm = '';
this.id = undefined;
this.itype = 0;
this.stype = undefined;
this.url = undefined;
this.aliasURL = undefined;
this.hostname = undefined;
this.domain = undefined;
this.docId = -1;
this.frameId = -1;
this.docOrigin = undefined;
this.docHostname = undefined;
this.docDomain = undefined;
this.tabId = undefined;
this.tabOrigin = undefined;
this.tabHostname = undefined;
this.tabDomain = undefined;
this.redirectURL = undefined;
this.filter = undefined;
}
get type() {
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);
this.tabOrigin = tabContext.origin;
this.tabHostname = tabContext.rootHostname;
this.tabDomain = tabContext.rootDomain;
this.tabId = tabContext.tabId;
return this;
},
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/459
// 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.
fromWebrequestDetails: function(details) {
fromWebrequestDetails(details) {
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);
}
this.fromTabId(tabId);
this.realm = '';
this.id = details.requestId;
this.type = details.type;
this.setURL(details.url);
this.aliasURL = details.aliasURL || undefined;
if ( details.type !== 'sub_frame' ) {
if ( this.itype !== SUB_FRAME ) {
this.docId = details.frameId;
this.frameId = -1;
} else {
@ -95,12 +180,12 @@
}
}
} else if ( details.documentUrl !== undefined ) {
const origin = this.originFromURI(
const origin = originFromURI(
µBlock.normalizePageURL(0, details.documentUrl)
);
this.setDocOrigin(origin).setTabOrigin(origin);
} else if ( this.docId === -1 || this.type.endsWith('_frame') ) {
const origin = this.originFromURI(this.url);
} else if ( this.docId === -1 || (this.itype & FRAME_ANY) !== 0 ) {
const origin = originFromURI(this.url);
this.setDocOrigin(origin).setTabOrigin(origin);
} else {
this.setDocOrigin(this.tabOrigin);
@ -108,8 +193,9 @@
this.redirectURL = undefined;
this.filter = undefined;
return this;
},
fromFilteringContext: function(other) {
}
fromFilteringContext(other) {
this.realm = other.realm;
this.type = other.type;
this.url = other.url;
@ -127,90 +213,106 @@
this.redirectURL = other.redirectURL;
this.filter = undefined;
return this;
},
duplicate: function() {
return (new µBlock.FilteringContext(this));
},
setRealm: function(a) {
}
duplicate() {
return (new FilteringContext(this));
}
setRealm(a) {
this.realm = a;
return this;
},
setType: function(a) {
}
setType(a) {
this.type = a;
return this;
},
setURL: function(a) {
}
setURL(a) {
if ( a !== this.url ) {
this.hostname = this.domain = undefined;
this.url = a;
}
return this;
},
getHostname: function() {
}
getHostname() {
if ( this.hostname === undefined ) {
this.hostname = this.hostnameFromURI(this.url);
this.hostname = hostnameFromURI(this.url);
}
return this.hostname;
},
setHostname: function(a) {
}
setHostname(a) {
if ( a !== this.hostname ) {
this.domain = undefined;
this.hostname = a;
}
return this;
},
getDomain: function() {
}
getDomain() {
if ( this.domain === undefined ) {
this.domain = this.domainFromHostname(this.getHostname());
this.domain = domainFromHostname(this.getHostname());
}
return this.domain;
},
setDomain: function(a) {
}
setDomain(a) {
this.domain = a;
return this;
},
getDocOrigin: function() {
}
getDocOrigin() {
if ( this.docOrigin === undefined ) {
this.docOrigin = this.tabOrigin;
}
return this.docOrigin;
},
setDocOrigin: function(a) {
}
setDocOrigin(a) {
if ( a !== this.docOrigin ) {
this.docHostname = this.docDomain = undefined;
this.docOrigin = a;
}
return this;
},
setDocOriginFromURL: function(a) {
return this.setDocOrigin(this.originFromURI(a));
},
getDocHostname: function() {
}
setDocOriginFromURL(a) {
return this.setDocOrigin(originFromURI(a));
}
getDocHostname() {
if ( this.docHostname === undefined ) {
this.docHostname = this.hostnameFromURI(this.getDocOrigin());
this.docHostname = hostnameFromURI(this.getDocOrigin());
}
return this.docHostname;
},
setDocHostname: function(a) {
}
setDocHostname(a) {
if ( a !== this.docHostname ) {
this.docDomain = undefined;
this.docHostname = a;
}
return this;
},
getDocDomain: function() {
}
getDocDomain() {
if ( this.docDomain === undefined ) {
this.docDomain = this.domainFromHostname(this.getDocHostname());
this.docDomain = domainFromHostname(this.getDocHostname());
}
return this.docDomain;
},
setDocDomain: function(a) {
}
setDocDomain(a) {
this.docDomain = a;
return this;
},
}
// The idea is to minimize the amout of work done to figure out whether
// the resource is 3rd-party to the document.
is3rdPartyToDoc: function() {
is3rdPartyToDoc() {
let docDomain = this.getDocDomain();
if ( docDomain === '' ) { docDomain = this.docHostname; }
if ( this.domain !== undefined && this.domain !== '' ) {
@ -221,12 +323,14 @@
const i = hostname.length - docDomain.length;
if ( i === 0 ) { return false; }
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
},
setTabId: function(a) {
}
setTabId(a) {
this.tabId = a;
return this;
},
getTabOrigin: function() {
}
getTabOrigin() {
if ( this.tabOrigin === undefined ) {
const tabContext = µBlock.tabContextManager.mustLookup(this.tabId);
this.tabOrigin = tabContext.origin;
@ -234,43 +338,50 @@
this.tabDomain = tabContext.rootDomain;
}
return this.tabOrigin;
},
setTabOrigin: function(a) {
}
setTabOrigin(a) {
if ( a !== this.tabOrigin ) {
this.tabHostname = this.tabDomain = undefined;
this.tabOrigin = a;
}
return this;
},
setTabOriginFromURL: function(a) {
return this.setTabOrigin(this.originFromURI(a));
},
getTabHostname: function() {
}
setTabOriginFromURL(a) {
return this.setTabOrigin(originFromURI(a));
}
getTabHostname() {
if ( this.tabHostname === undefined ) {
this.tabHostname = this.hostnameFromURI(this.getTabOrigin());
this.tabHostname = hostnameFromURI(this.getTabOrigin());
}
return this.tabHostname;
},
setTabHostname: function(a) {
}
setTabHostname(a) {
if ( a !== this.tabHostname ) {
this.tabDomain = undefined;
this.tabHostname = a;
}
return this;
},
getTabDomain: function() {
}
getTabDomain() {
if ( this.tabDomain === undefined ) {
this.tabDomain = this.domainFromHostname(this.getTabHostname());
this.tabDomain = domainFromHostname(this.getTabHostname());
}
return this.tabDomain;
},
setTabDomain: function(a) {
}
setTabDomain(a) {
this.docDomain = a;
return this;
},
}
// The idea is to minimize the amout of work done to figure out whether
// the resource is 3rd-party to the top document.
is3rdPartyToTab: function() {
is3rdPartyToTab() {
let tabDomain = this.getTabDomain();
if ( tabDomain === '' ) { tabDomain = this.tabHostname; }
if ( this.domain !== undefined && this.domain !== '' ) {
@ -281,12 +392,38 @@
const i = hostname.length - tabDomain.length;
if ( i === 0 ) { return false; }
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
},
setFilter: function(a) {
}
setFilter(a) {
this.filter = a;
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();
if ( this.domain === undefined ) {
void this.getDomain();
@ -297,11 +434,49 @@
if ( this.tabDomain === undefined ) {
void this.getTabDomain();
}
µBlock.logger.writeOne(this);
},
originFromURI: µBlock.URI.originFromURI,
hostnameFromURI: µBlock.URI.hostnameFromURI,
domainFromHostname: µBlock.URI.domainFromHostname,
const logger = µBlock.logger;
const filters = this.filter;
// Many filters may have been applied to the current context
if ( Array.isArray(filters) === false ) {
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.type = details.stype;
if ( details.aliasURL !== undefined ) {
this.aliased = true;
}

View File

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

View File

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

View File

@ -545,27 +545,28 @@ const PageStore = class {
return 0;
}
const requestType = fctxt.type;
if (
requestType === 'csp_report' &&
fctxt.itype === fctxt.CSP_REPORT &&
this.filterCSPReport(fctxt) === 1
) {
return 1;
}
if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) {
if (
(fctxt.itype & fctxt.FONT_ANY) !== 0 &&
this.filterFont(fctxt) === 1 )
{
return 1;
}
if (
requestType === 'script' &&
fctxt.itype === fctxt.SCRIPT &&
this.filterScripting(fctxt, true) === 1
) {
return 1;
}
const cacheableResult = this.cacheableResults.has(requestType);
const cacheableResult = this.cacheableResults.has(fctxt.itype);
if ( cacheableResult ) {
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.
let result = µb.sessionURLFiltering.evaluateZ(
fctxt.getTabHostname(),
fctxt.url,
requestType
);
if ( result !== 0 && µb.logger.enabled ) {
if ( result !== 0 && loggerEnabled ) {
fctxt.filter = µb.sessionURLFiltering.toLogData();
}
@ -593,17 +597,17 @@ const PageStore = class {
fctxt.getHostname(),
requestType
);
if ( result !== 0 && result !== 3 && µb.logger.enabled ) {
if ( result !== 0 && result !== 3 && loggerEnabled ) {
fctxt.filter = µb.sessionFirewall.toLogData();
}
}
// Static filtering has lowest precedence.
const snfe = µb.staticNetFilteringEngine;
if ( result === 0 || result === 3 ) {
const snfe = µb.staticNetFilteringEngine;
result = snfe.matchString(fctxt);
if ( result !== 0 ) {
if ( µb.logger.enabled ) {
if ( loggerEnabled ) {
fctxt.filter = snfe.toLogData();
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/943
@ -625,8 +629,8 @@ const PageStore = class {
const frameStore = this.getFrameStore(fctxt.frameId);
if ( frameStore !== null && frameStore.clickToLoad ) {
result = 2;
if ( µb.logger.enabled ) {
fctxt.setFilter({
if ( loggerEnabled ) {
fctxt.pushFilter({
result,
source: 'network',
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
// Redirect blocked request?
if ( result === 1 && µb.hiddenSettings.ignoreRedirectFilters !== true ) {
const redirectURL = µb.redirectEngine.toURL(fctxt);
if ( redirectURL !== undefined ) {
fctxt.redirectURL = redirectURL;
this.internalRedirectionCount += 1;
// 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);
if ( redirectURL !== undefined ) {
fctxt.redirectURL = redirectURL;
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 ) {
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);
}
@ -670,7 +691,7 @@ const PageStore = class {
}
filterFont(fctxt) {
if ( fctxt.type === 'font' ) {
if ( fctxt.itype === fctxt.FONT ) {
this.remoteFontCount += 1;
}
if (
@ -800,7 +821,7 @@ const PageStore = class {
}
if ( exceptCname === false ) { return false; }
if ( exceptCname instanceof Object ) {
fctxt.setFilter(exceptCname);
fctxt.pushFilter(exceptCname);
}
return true;
}
@ -830,14 +851,14 @@ const PageStore = class {
};
PageStore.prototype.cacheableResults = new Set([
'sub_frame',
µb.FilteringContext.SUB_FRAME,
]);
PageStore.prototype.collapsibleResources = new Set([
'image',
'media',
'object',
'sub_frame',
µb.FilteringContext.IMAGE,
µb.FilteringContext.MEDIA,
µb.FilteringContext.OBJECT,
µb.FilteringContext.SUB_FRAME,
]);
µb.PageStore = PageStore;

View File

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

View File

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

View File

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

View File

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

View File

@ -58,11 +58,11 @@
<input type="search" placeholder="logFilterPrompt">
<span id="filterExprButton" class="button fa-icon expanded" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span>
<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>
<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><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>
</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>