From f876b68171ff307f27601225607a6801f400437d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 13 Mar 2021 08:53:34 -0500 Subject: [PATCH] Add support for removal of response headers The syntax to remove response header is a special case of HTML filtering, whereas the response headers are targeted, rather than the response body: example.com##^responseheader(header-name) Where `header-name` is the name of the header to remove, and must always be lowercase. The removal of response headers can only be applied to document resources, i.e. main- or sub-frames. Only a limited set of headers can be targeted for removal: location refresh report-to set-cookie This limitation is to ensure that uBO never lowers the security profile of web pages, i.e. we wouldn't want to remove `content-security-policy`. Given that the header removal occurs at onHeaderReceived time, this new ability works for all browsers. The motivation for this new filtering ability is instance of website using a `refresh` header to redirect a visitor to an undesirable destination after a few seconds. --- src/background.html | 1 + src/css/logger-ui.css | 18 ++++----- src/js/background.js | 1 + src/js/codemirror/ubo-static-filtering.js | 37 +++++++++++++++++-- src/js/cosmetic-filtering.js | 9 +++++ src/js/html-filtering.js | 29 +++++++++------ src/js/logger-ui.js | 45 +++++++++++++++-------- src/js/messaging.js | 21 ++++------- src/js/reverselookup.js | 1 + src/js/scriptlet-filtering.js | 33 ++++++++++------- src/js/static-ext-filtering.js | 31 +++++++++++++++- src/js/static-filtering-parser.js | 26 +++++++++++++ src/js/traffic.js | 25 ++++++++----- 13 files changed, 201 insertions(+), 76 deletions(-) diff --git a/src/background.html b/src/background.html index 7d183dfa4..393b15024 100644 --- a/src/background.html +++ b/src/background.html @@ -34,6 +34,7 @@ + diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index c0941bdaf..9ea2b3317 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -260,11 +260,11 @@ body.colorBlind #netFilteringDialog > .panes > .details > div[data-status="2"] { #vwRenderer .logEntry > div[data-tabid="-1"] { text-shadow: 0 0.2em 0.4em #aaa; } -#vwRenderer .logEntry > div.cosmeticRealm, +#vwRenderer .logEntry > div.extendedRealm, #vwRenderer .logEntry > div.redirect { background-color: rgba(255, 255, 0, 0.1); } -body.colorBlind #vwRenderer .logEntry > div.cosmeticRealm, +body.colorBlind #vwRenderer .logEntry > div.extendedRealm, body.colorBlind #vwRenderer .logEntry > div.redirect { background-color: rgba(0, 19, 110, 0.1); } @@ -324,13 +324,13 @@ body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child { #vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) { text-align: center; } -#vwRenderer .logEntry > div.cosmeticRealm > span:nth-of-type(2) > span:first-of-type { +#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:first-of-type { display: none; } -#vwRenderer .logEntry > div.cosmeticRealm > span:nth-of-type(2) > span:last-of-type { +#vwRenderer .logEntry > div.extendedRealm > span:nth-of-type(2) > span:last-of-type { pointer-events: none; } -#vwRenderer .logEntry > div.cosmeticRealm.isException > span:nth-of-type(2) > span:last-of-type { +#vwRenderer .logEntry > div.extendedRealm.isException > span:nth-of-type(2) > span:last-of-type { text-decoration: line-through rgba(0,0,255,0.7); } #vwRenderer .logEntry > div > span:nth-of-type(3) { @@ -615,12 +615,12 @@ body[dir="rtl"] #netFilteringDialog > .headers > .tools { #netFilteringDialog > .headers > .tools > span:hover { background-color: #eee; } -#netFilteringDialog.cosmeticRealm > .headers > .dynamic, -#netFilteringDialog.cosmeticRealm > .panes > .dynamic { +#netFilteringDialog.extendedRealm > .headers > .dynamic, +#netFilteringDialog.extendedRealm > .panes > .dynamic { display: none; } -#netFilteringDialog.cosmeticRealm > .headers > .static, -#netFilteringDialog.cosmeticRealm > .panes > .static { +#netFilteringDialog.extendedRealm > .headers > .static, +#netFilteringDialog.extendedRealm > .panes > .static { display: none; } #netFilteringDialog > div.panes { diff --git a/src/js/background.js b/src/js/background.js index df1621f1d..06710d417 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -166,6 +166,7 @@ const µBlock = (( ) => { // jshint ignore:line compiledCosmeticSection: 200, compiledScriptletSection: 300, compiledHTMLSection: 400, + compiledHTTPHeaderSection: 500, compiledSentinelSection: 1000, compiledBadSubsection: 1, diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js index 891c3c072..eebc81125 100644 --- a/src/js/codemirror/ubo-static-filtering.js +++ b/src/js/codemirror/ubo-static-filtering.js @@ -549,6 +549,23 @@ const initHints = function() { const getExtSelectorHints = function(cursor, line) { const beg = cursor.ch; + // Special selector case: `^responseheader` + { + const match = /#\^([a-z]+)$/.exec(line.slice(0, beg)); + if ( + match !== null && + 'responseheader'.startsWith(match[1]) && + line.slice(beg) === '' + ) { + return pickBestHints( + cursor, + match[1], + '', + [ 'responseheader()' ] + ); + } + } + // Procedural operators const matchLeft = /#\^?.*:([^:]*)$/.exec(line.slice(0, beg)); const matchRight = /^([a-z-]*)\(?/.exec(line.slice(beg)); if ( matchLeft === null || matchRight === null ) { return; } @@ -561,6 +578,18 @@ const initHints = function() { return pickBestHints(cursor, matchLeft[1], matchRight[1], hints); }; + const getExtHeaderHints = function(cursor, line) { + const beg = cursor.ch; + const matchLeft = /#\^responseheader\((.*)$/.exec(line.slice(0, beg)); + const matchRight = /^([^)]*)/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const hints = []; + for ( const hint of parser.removableHTTPHeaders ) { + hints.push(hint); + } + return pickBestHints(cursor, matchLeft[1], matchRight[1], hints); + }; + const getExtScriptletHints = function(cursor, line) { const beg = cursor.ch; const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg)); @@ -607,10 +636,12 @@ const initHints = function() { let hints; if ( cursor.ch <= parser.slices[parser.optionsAnchorSpan.i+1] ) { hints = getOriginHints(cursor, line); + } else if ( parser.hasFlavor(parser.BITFlavorExtScriptlet) ) { + hints = getExtScriptletHints(cursor, line); + } else if ( parser.hasFlavor(parser.BITFlavorExtResponseHeader) ) { + hints = getExtHeaderHints(cursor, line); } else { - hints = parser.hasFlavor(parser.BITFlavorExtScriptlet) - ? getExtScriptletHints(cursor, line) - : getExtSelectorHints(cursor, line); + hints = getExtSelectorHints(cursor, line); } return hints; } diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 4a9e4cabe..bb4340ec1 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -540,6 +540,15 @@ FilterContainer.prototype.compileSpecificSelector = function( /******************************************************************************/ +FilterContainer.prototype.compileTemporary = function(parser) { + return { + session: this.sessionFilterDB, + selector: parser.result.compiled, + }; +}; + +/******************************************************************************/ + FilterContainer.prototype.fromCompiledContent = function(reader, options) { if ( options.skipCosmetic ) { this.skipCompiledContent(reader); diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js index 3d0f03cc9..8431d670c 100644 --- a/src/js/html-filtering.js +++ b/src/js/html-filtering.js @@ -237,12 +237,12 @@ µBlock.filteringContext .duplicate() .fromTabId(details.tabId) - .setRealm('cosmetic') + .setRealm('extended') .setType('dom') .setURL(details.url) .setDocOriginFromURL(details.url) .setFilter({ - source: 'cosmetic', + source: 'extended', raw: `${exception === 0 ? '##' : '#@#'}^${selector}` }) .toLogger(); @@ -322,6 +322,13 @@ } }; + api.compileTemporary = function(parser) { + return { + session: sessionFilterDB, + selector: parser.result.compiled, + }; + }; + api.fromCompiledContent = function(reader) { // Don't bother loading filters if stream filtering is not supported. if ( µb.canFilterResponseData === false ) { return; } @@ -348,15 +355,6 @@ api.retrieve = function(details) { const hostname = details.hostname; - // https://github.com/gorhill/uBlock/issues/2835 - // Do not filter if the site is under an `allow` rule. - if ( - µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 - ) { - return; - } - const plains = new Set(); const procedurals = new Set(); const exceptions = new Set(); @@ -379,6 +377,15 @@ if ( plains.size === 0 && procedurals.size === 0 ) { return; } + // https://github.com/gorhill/uBlock/issues/2835 + // Do not filter if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + const out = { plains, procedurals }; if ( exceptions.size === 0 ) { diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index aae02f3e1..59f19a2e7 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -29,6 +29,9 @@ /******************************************************************************/ +// TODO: fix the inconsistencies re. realm vs. filter source which have +// accumulated over time. + const messaging = vAPI.messaging; const logger = self.logger = { ownerId: Date.now() }; const logDate = new Date(); @@ -341,6 +344,11 @@ const processLoggerEntries = function(response) { /******************************************************************************/ const parseLogEntry = function(details) { + // Patch realm until changed all over codebase to make this unecessary + if ( details.realm === 'cosmetic' ) { + details.realm = 'extended'; + } + const entry = new LogEntry(details); // Assemble the text content, i.e. the pre-built string which will be @@ -441,6 +449,8 @@ const viewPort = (( ) => { const vwLogEntryTemplate = document.querySelector('#logEntryTemplate > div'); const vwEntries = []; + const detailableRealms = new Set([ 'network', 'extended' ]); + let vwHeight = 0; let lineHeight = 0; let wholeHeight = 0; @@ -672,7 +682,7 @@ const viewPort = (( ) => { return div; } - if ( details.realm === 'network' || details.realm === 'cosmetic' ) { + if ( detailableRealms.has(details.realm) ) { divcl.add('canDetails'); } @@ -685,13 +695,13 @@ const viewPort = (( ) => { } if ( filteringType === 'static' ) { divcl.add('canLookup'); - if ( filter.modifier === true ) { - div.setAttribute('data-modifier', ''); - } - } else if ( filteringType === 'cosmetic' ) { + } else if ( details.realm === 'extended' ) { divcl.add('canLookup'); divcl.toggle('isException', filter.raw.startsWith('#@#')); } + if ( filter.modifier === true ) { + div.setAttribute('data-modifier', ''); + } } span = div.children[1]; if ( renderFilterToSpan(span, cells[1]) === false ) { @@ -1575,7 +1585,7 @@ const reloadTab = function(ev) { rawFilter: rawFilter, }); handleResponse(response); - } else if ( targetRow.classList.contains('cosmeticRealm') ) { + } else if ( targetRow.classList.contains('extendedRealm') ) { const response = await messaging.send('loggerUI', { what: 'listsFromCosmeticFilter', url: targetRow.children[6].textContent, @@ -1583,7 +1593,7 @@ const reloadTab = function(ev) { }); handleResponse(response); } - } ; + }; const fillSummaryPane = function() { const rows = dialog.querySelectorAll('.pane.details > div'); @@ -1595,7 +1605,7 @@ const reloadTab = function(ev) { text = filterFromTargetRow(); if ( (text !== '') && - (trcl.contains('cosmeticRealm') || trcl.contains('networkRealm')) + (trcl.contains('extendedRealm') || trcl.contains('networkRealm')) ) { toSummaryPaneFilterNode(rows[0], text); } else { @@ -1607,7 +1617,7 @@ const reloadTab = function(ev) { ( trcl.contains('dynamicHost') || trcl.contains('dynamicUrl') || - trcl.contains('switch') + trcl.contains('switchRealm') ) ) { rows[2].children[1].textContent = text; @@ -1677,7 +1687,9 @@ const reloadTab = function(ev) { // Fill dynamic URL filtering pane const fillDynamicPane = function() { - if ( targetRow.classList.contains('cosmeticRealm') ) { return; } + if ( targetRow.classList.contains('extendedRealm') ) { + return; + } // https://github.com/uBlockOrigin/uBlock-issues/issues/662#issuecomment-509220702 if ( targetType === 'doc' ) { return; } @@ -1712,8 +1724,6 @@ const reloadTab = function(ev) { } colorize(); - - uDom('#modalOverlayContainer [data-pane="dynamic"]').removeClass('hide'); }; const fillOriginSelect = function(select, hostname, domain) { @@ -1733,7 +1743,9 @@ const reloadTab = function(ev) { // Fill static filtering pane const fillStaticPane = function() { - if ( targetRow.classList.contains('cosmeticRealm') ) { return; } + if ( targetRow.classList.contains('extendedRealm') ) { + return; + } const template = vAPI.i18n('loggerStaticFilteringSentence'); const rePlaceholder = /\{\{[^}]+?\}\}/g; @@ -1842,8 +1854,8 @@ const reloadTab = function(ev) { } ); dialog.classList.toggle( - 'cosmeticRealm', - targetRow.classList.contains('cosmeticRealm') + 'extendedRealm', + targetRow.classList.contains('extendedRealm') ); targetDomain = domains[0]; targetPageDomain = domains[1]; @@ -2403,10 +2415,10 @@ const popupManager = (( ) => { // Filter hit stats' MVP ("minimum viable product") // const loggerStats = (( ) => { + const enabled = false; const filterHits = new Map(); let dialog; let timer; - const makeRow = function() { const div = document.createElement('div'); div.appendChild(document.createElement('span')); @@ -2470,6 +2482,7 @@ const loggerStats = (( ) => { return { processFilter: function(filter) { + if ( enabled !== true ) { return; } if ( filter.source !== 'static' && filter.source !== 'cosmetic' ) { return; } diff --git a/src/js/messaging.js b/src/js/messaging.js index a0c9a1e4f..1dce1fe31 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1440,21 +1440,14 @@ const getURLFilteringData = function(details) { const compileTemporaryException = function(filter) { const parser = new vAPI.StaticFilteringParser(); parser.analyze(filter); - if ( parser.shouldDiscard() ) { return {}; } - let selector = parser.result.compiled; - let session; - if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { - session = µb.scriptletFilteringEngine.getSession(); - } else if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { - session = µb.htmlFilteringEngine.getSession(); - } else { - session = µb.cosmeticFilteringEngine.getSession(); - } - return { session, selector }; + if ( parser.shouldDiscard() ) { return; } + return µb.staticExtFilteringEngine.compileTemporary(parser); }; const toggleTemporaryException = function(details) { - const { session, selector } = compileTemporaryException(details.filter); + const result = compileTemporaryException(details.filter); + if ( result === undefined ) { return false; } + const { session, selector } = result; if ( session.has(1, selector) ) { session.remove(1, selector); return false; @@ -1464,7 +1457,9 @@ const toggleTemporaryException = function(details) { }; const hasTemporaryException = function(details) { - const { session, selector } = compileTemporaryException(details.filter); + const result = compileTemporaryException(details.filter); + if ( result === undefined ) { return false; } + const { session, selector } = result; return session && session.has(1, selector); }; diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 7a228231a..52d79da8c 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -221,6 +221,7 @@ if ( // Generic exception case 8: // HTML filtering + // Response header filtering case 64: if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } isProcedural = (fargs[2] & 0b010) !== 0; diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js index 39b38396b..1f50c9b10 100644 --- a/src/js/scriptlet-filtering.js +++ b/src/js/scriptlet-filtering.js @@ -206,12 +206,12 @@ µBlock.filteringContext .duplicate() .fromTabId(details.tabId) - .setRealm('cosmetic') + .setRealm('extended') .setType('dom') .setURL(details.url) .setDocOriginFromURL(details.url) .setFilter({ - source: 'cosmetic', + source: 'extended', raw: (isException ? '#@#' : '##') + `+js(${token})` }) .toLogger(); @@ -263,10 +263,17 @@ } }; + api.compileTemporary = function(parser) { + return { + session: sessionScriptletDB, + selector: parser.result.compiled, + }; + }; + // 01234567890123456789 // +js(token[, arg[, ...]]) - // ^ ^ - // 4 -1 + // ^ ^ + // 4 -1 api.fromCompiledContent = function(reader) { reader.select(µb.compiledScriptletSection); @@ -302,15 +309,6 @@ const hostname = request.hostname; - // https://github.com/gorhill/uBlock/issues/2835 - // Do not inject scriptlets if the site is under an `allow` rule. - if ( - µb.userSettings.advancedUserEnabled && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 - ) { - return; - } - $scriptlets.clear(); $exceptions.clear(); @@ -324,6 +322,15 @@ scriptletDB.retrieve(entity, [ $scriptlets, $exceptions ], 1); if ( $scriptlets.size === 0 ) { return; } + // https://github.com/gorhill/uBlock/issues/2835 + // Do not inject scriptlets if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + const loggerEnabled = µb.logger.enabled; // Wholly disable scriptlet injection? diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index 65a6c36bd..3a07c3c4e 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -59,11 +59,13 @@ get acceptedCount() { return µb.cosmeticFilteringEngine.acceptedCount + µb.scriptletFilteringEngine.acceptedCount + + µb.httpheaderFilteringEngine.acceptedCount + µb.htmlFilteringEngine.acceptedCount; }, get discardedCount() { return µb.cosmeticFilteringEngine.discardedCount + µb.scriptletFilteringEngine.discardedCount + + µb.httpheaderFilteringEngine.discardedCount + µb.htmlFilteringEngine.discardedCount; }, }; @@ -137,7 +139,7 @@ this.timer = undefined; this.strToIdMap.clear(); }, - { timeout: 10000 } + { timeout: 5000 } ); } @@ -181,6 +183,7 @@ } fromSelfie(selfie) { + if ( selfie === undefined ) { return; } this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap); this.hostnameSlots = selfie.hostnameSlots; this.strSlots = selfie.strSlots; @@ -239,12 +242,14 @@ api.reset = function() { µb.cosmeticFilteringEngine.reset(); µb.scriptletFilteringEngine.reset(); + µb.httpheaderFilteringEngine.reset(); µb.htmlFilteringEngine.reset(); }; api.freeze = function() { µb.cosmeticFilteringEngine.freeze(); µb.scriptletFilteringEngine.freeze(); + µb.httpheaderFilteringEngine.freeze(); µb.htmlFilteringEngine.freeze(); }; @@ -267,6 +272,12 @@ return true; } + // Response header filtering + if ( (parser.flavorBits & parser.BITFlavorExtResponseHeader) !== 0 ) { + µb.httpheaderFilteringEngine.compile(parser, writer); + return true; + } + // HTML filtering // TODO: evaluate converting Adguard's `$$` syntax into uBO's HTML // filtering syntax. @@ -280,9 +291,23 @@ return true; }; + api.compileTemporary = function(parser) { + if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { + return µb.scriptletFilteringEngine.compileTemporary(parser); + } + if ( (parser.flavorBits & parser.BITFlavorExtResponseHeader) !== 0 ) { + return µb.httpheaderFilteringEngine.compileTemporary(parser); + } + if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { + return µb.htmlFilteringEngine.compileTemporary(parser); + } + return µb.cosmeticFilteringEngine.compileTemporary(parser); + }; + api.fromCompiledContent = function(reader, options) { µb.cosmeticFilteringEngine.fromCompiledContent(reader, options); µb.scriptletFilteringEngine.fromCompiledContent(reader, options); + µb.httpheaderFilteringEngine.fromCompiledContent(reader, options); µb.htmlFilteringEngine.fromCompiledContent(reader, options); }; @@ -292,7 +317,8 @@ JSON.stringify({ cosmetic: µb.cosmeticFilteringEngine.toSelfie(), scriptlets: µb.scriptletFilteringEngine.toSelfie(), - html: µb.htmlFilteringEngine.toSelfie() + httpHeaders: µb.httpheaderFilteringEngine.toSelfie(), + html: µb.htmlFilteringEngine.toSelfie(), }) ); }; @@ -307,6 +333,7 @@ if ( selfie instanceof Object === false ) { return false; } µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets); + µb.httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); µb.htmlFilteringEngine.fromSelfie(selfie.html); return true; }); diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index e5c645b96..0599ffdb3 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -304,6 +304,20 @@ const Parser = class { } // ##^... if ( hasBits(this.slices[i], BITCaret) ) { + // ##^responseheader(...) + if ( + selector.startsWith('^responseheader(') && + selector.endsWith(')') + ) { + this.flavorBits |= BITFlavorExtResponseHeader; + this.result.raw = selector.slice(1); + const headerName = selector.slice(16, -1).trim().toLowerCase(); + this.result.compiled = `responseheader(${headerName})`; + if ( this.removableHTTPHeaders.has(headerName) === false ) { + this.flavorBits |= BITFlavorUnsupported; + } + return; + } this.flavorBits |= BITFlavorExtHTML; selector = selector.slice(1); } @@ -1244,6 +1258,16 @@ const Parser = class { /******************************************************************************/ +Parser.removableHTTPHeaders = Parser.prototype.removableHTTPHeaders = new Set([ + '', + 'location', + 'report-to', + 'refresh', + 'set-cookie', +]); + +/******************************************************************************/ + // https://github.com/chrisaljoudi/uBlock/issues/1004 // Detect and report invalid CSS selectors. @@ -1978,6 +2002,7 @@ const BITFlavorExtStrong = 1 << 8; const BITFlavorExtCosmetic = 1 << 9; const BITFlavorExtScriptlet = 1 << 10; const BITFlavorExtHTML = 1 << 11; +const BITFlavorExtResponseHeader = 1 << 12; const BITFlavorIgnore = 1 << 29; const BITFlavorUnsupported = 1 << 30; const BITFlavorError = 1 << 31; @@ -2087,6 +2112,7 @@ Parser.prototype.BITFlavorExtStrong = BITFlavorExtStrong; Parser.prototype.BITFlavorExtCosmetic = BITFlavorExtCosmetic; Parser.prototype.BITFlavorExtScriptlet = BITFlavorExtScriptlet; Parser.prototype.BITFlavorExtHTML = BITFlavorExtHTML; +Parser.prototype.BITFlavorExtResponseHeader = BITFlavorExtResponseHeader; Parser.prototype.BITFlavorIgnore = BITFlavorIgnore; Parser.prototype.BITFlavorUnsupported = BITFlavorUnsupported; Parser.prototype.BITFlavorError = BITFlavorError; diff --git a/src/js/traffic.js b/src/js/traffic.js index 27ab87f06..89b836e4d 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -508,7 +508,8 @@ const onHeadersReceived = function(details) { // Keep in mind response headers will be modified in-place if needed, so // `details.responseHeaders` will always point to the modified response // headers. - const responseHeaders = details.responseHeaders; + const { responseHeaders } = details; + if ( Array.isArray(responseHeaders) === false ) { return; } if ( isRootDoc === false && µb.hiddenSettings.filterOnHeaders === true ) { const result = pageStore.filterOnHeaders(fctxt, responseHeaders); @@ -539,11 +540,17 @@ const onHeadersReceived = function(details) { } // At this point we have a HTML document. + + const filteredHTML = + µb.canFilterResponseData && filterDocument(fctxt, details) === true; - const filteredHTML = µb.canFilterResponseData && - filterDocument(pageStore, fctxt, details) === true; - - let modifiedHeaders = injectCSP(fctxt, pageStore, responseHeaders) === true; + let modifiedHeaders = false; + if ( µb.httpheaderFilteringEngine.apply(fctxt, responseHeaders) === true ) { + modifiedHeaders = true; + } + if ( injectCSP(fctxt, pageStore, responseHeaders) === true ) { + modifiedHeaders = true; + } // https://bugzilla.mozilla.org/show_bug.cgi?id=1376932 // Prevent document from being cached by the browser if we modified it, @@ -552,7 +559,7 @@ const onHeadersReceived = function(details) { // Use `no-cache` instead of `no-cache, no-store, must-revalidate`, this // allows Firefox's offline mode to work as expected. if ( (filteredHTML || modifiedHeaders) && dontCacheResponseHeaders ) { - let cacheControl = µb.hiddenSettings.cacheControlForFirefox1376932; + const cacheControl = µb.hiddenSettings.cacheControlForFirefox1376932; if ( cacheControl !== 'unset' ) { let i = headerIndexFromName('cache-control', responseHeaders); if ( i !== -1 ) { @@ -565,7 +572,7 @@ const onHeadersReceived = function(details) { } if ( modifiedHeaders ) { - return { responseHeaders: responseHeaders }; + return { responseHeaders }; } }; @@ -614,7 +621,7 @@ const normalizeBehindTheSceneResponseHeaders = function(details) { **/ -const filterDocument = (function() { +const filterDocument = (( ) => { const µb = µBlock; const filterers = new Map(); let domParser, xmlSerializer, @@ -805,7 +812,7 @@ const filterDocument = (function() { filterers.delete(this); }; - return function(pageStore, fctxt, extras) { + return function(fctxt, extras) { // https://github.com/gorhill/uBlock/issues/3478 const statusCode = extras.statusCode || 0; if ( statusCode !== 0 && (statusCode < 200 || statusCode >= 300) ) {