diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 569dd9e5a..a6ae9a288 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -1145,7 +1145,7 @@ vAPI.warSecret = (( ) => { url.lastIndexOf(`?secret=${secret}`) !== -1 ); if ( pos === -1 ) { - return { redirectUrl: root }; + return { cancel: true }; } secrets.splice(pos, 1); }; diff --git a/src/js/about.js b/src/js/about.js index 212abae8c..a7befaa39 100644 --- a/src/js/about.js +++ b/src/js/about.js @@ -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'); }); }); diff --git a/src/js/background.js b/src/js/background.js index a9990b90f..6aa6df696 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -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 diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js index 25ecd3187..6f7ac7088 100644 --- a/src/js/codemirror/ubo-static-filtering.js +++ b/src/js/codemirror/ubo-static-filtering.js @@ -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; } diff --git a/src/js/filtering-context.js b/src/js/filtering-context.js index cad4e1684..20b66ccb6 100644 --- a/src/js/filtering-context.js +++ b/src/js/filtering-context.js @@ -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 +} diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index cfd9ed4be..7a13b4d14 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -214,6 +214,7 @@ const LogEntry = function(details) { this[prop] = details[prop]; } } + this.type = details.stype; if ( details.aliasURL !== undefined ) { this.aliased = true; } diff --git a/src/js/logger.js b/src/js/logger.js index 8b5c05c31..6e3741402 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -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; }, diff --git a/src/js/messaging.js b/src/js/messaging.js index f3c57fdd4..253692f33 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -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'; diff --git a/src/js/pagestore.js b/src/js/pagestore.js index ad80d1ae0..f5e7e7acd 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -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; diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 4df8f7c6f..098a99e5b 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -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 ); } diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 0b10b9161..daa36b2e5 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -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} diff --git a/src/js/traffic.js b/src/js/traffic.js index b24d69971..4d2e53c4e 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -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; } diff --git a/src/js/utils.js b/src/js/utils.js index 83bc19e03..6ea1b85b6 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -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; } })(); diff --git a/src/logger-ui.html b/src/logger-ui.html index c21ca5dcf..65b25b235 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -58,11 +58,11 @@ angle-up
-
+
modified
css/fontimagemediascript
-
xhrframedomother
+
xhrframedomother