From 6327aae56c25c5a74d8f67824cdcd70e53f44a1f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 28 Aug 2025 11:13:43 -0400 Subject: [PATCH] Add support to strict-block from `ipaddress=` option As discussed with filter list maintainers. --- platform/common/vapi-common.js | 5 +- src/js/traffic.js | 101 ++++++++++++++++----------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/platform/common/vapi-common.js b/platform/common/vapi-common.js index 1cf98242f..1402f4d7a 100644 --- a/platform/common/vapi-common.js +++ b/platform/common/vapi-common.js @@ -178,8 +178,11 @@ vAPI.webextFlavor = { soup.add('native_css_has'); } + const extensionOrigin = browser.runtime.getURL(''); + // Order of tests is important - if ( browser.runtime.getURL('').startsWith('moz-extension://') ) { + flavor.isGecko = extensionOrigin.startsWith('moz-extension://'); + if ( flavor.isGecko ) { soup.add('firefox') .add('user_stylesheet') .add('html_filtering'); diff --git a/src/js/traffic.js b/src/js/traffic.js index 049d88097..cd7f28943 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -39,20 +39,8 @@ import µb from './background.js'; /******************************************************************************/ -// Platform-specific behavior. - -// https://github.com/uBlockOrigin/uBlock-issues/issues/42 -// https://bugzilla.mozilla.org/show_bug.cgi?id=1376932 -// Add proper version number detection once issue is fixed in Firefox. -let dontCacheResponseHeaders = - vAPI.webextFlavor.soup.has('firefox'); - -// The real actual webextFlavor value may not be set in stone, so listen -// for possible future changes. -window.addEventListener('webextFlavor', function() { - dontCacheResponseHeaders = - vAPI.webextFlavor.soup.has('firefox'); -}, { once: true }); +// For platform-specific behavior. +const isGecko = vAPI.webextFlavor.isGecko; /******************************************************************************/ @@ -64,7 +52,7 @@ const patchLocalRedirectURL = url => url.charCodeAt(0) === 0x2F /* '/' */ // Intercept and filter web requests. -const onBeforeRequest = function(details) { +function onBeforeRequest(details) { const fctxt = µb.filteringContext.fromWebrequestDetails(details); // Special handling for root document. @@ -248,7 +236,7 @@ const onBeforeRootFrameRequest = function(fctxt) { ); return { cancel: true }; -}; +} /******************************************************************************/ @@ -278,7 +266,7 @@ const onBeforeRootFrameRequest = function(fctxt) { // | 2 | rg | rg | rs | rs | // --------+--------+--------+--------+--------+--------+ -const shouldStrictBlock = function(fctxt, loggerEnabled) { +function shouldStrictBlock(fctxt, loggerEnabled) { const snfe = staticNetFilteringEngine; // Explicit filtering: `document` option @@ -338,7 +326,7 @@ const shouldStrictBlock = function(fctxt, loggerEnabled) { // | 2 | - | - | - | x | // --------+--------+--------+--------+--------+--------+ return { result: rs, logData: lds }; -}; +} /******************************************************************************/ @@ -348,7 +336,7 @@ const shouldStrictBlock = function(fctxt, loggerEnabled) { // Do not strict-block if the filter pattern does not contain at least one // token character. -const validateStrictBlock = function(fctxt, logData) { +function validateStrictBlock(fctxt, logData) { if ( typeof logData.regex !== 'string' ) { return false; } if ( typeof logData.raw === 'string' && /\w/.test(logData.raw) === false ) { return false; @@ -370,13 +358,13 @@ const validateStrictBlock = function(fctxt, logData) { const end = match.index + match[0].length - hnpos - hnlen; return end === 0 || end === 1 || end === 2 && url.charCodeAt(hnpos + hnlen) === 0x2E /* '.' */; -}; +} /******************************************************************************/ // Intercept and filter behind-the-scene requests. -const onBeforeBehindTheSceneRequest = function(fctxt) { +function onBeforeBehindTheSceneRequest(fctxt) { const pageStore = µb.pageStoreFromTabId(fctxt.tabId); if ( pageStore === null ) { return; } @@ -436,7 +424,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { if ( result === 1 ) { return { cancel: true }; } -}; +} // https://github.com/uBlockOrigin/uBlock-issues/issues/1204 // Report the tabless network requests to all page stores matching the @@ -491,7 +479,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { // - HTML filtering (requires ability to modify response body) // - CSP injection -const onHeadersReceived = function(details) { +function onHeadersReceived(details) { const fctxt = µb.filteringContext.fromWebrequestDetails(details); const isRootDoc = fctxt.itype === fctxt.MAIN_FRAME; @@ -503,6 +491,12 @@ const onHeadersReceived = function(details) { } if ( pageStore.getNetFilteringSwitch(fctxt) === false ) { return; } + // To enforce strict-blocking with ipaddress option + if ( isRootDoc && fctxt.ipaddress ) { + const r = onBeforeRootFrameRequest(fctxt); + if ( r ) { return ( r ); } + } + if ( (fctxt.itype & foilLargeMediaElement.TYPE_BITS) !== 0 ) { const result = foilLargeMediaElement(details, fctxt, pageStore); if ( result !== undefined ) { return result; } @@ -589,7 +583,7 @@ const onHeadersReceived = function(details) { // https://github.com/uBlockOrigin/uBlock-issues/issues/229 // Use `no-cache` instead of `no-cache, no-store, must-revalidate`, this // allows Firefox's offline mode to work as expected. - if ( modifiedHeaders && dontCacheResponseHeaders ) { + if ( modifiedHeaders && isGecko ) { const cacheControl = µb.hiddenSettings.cacheControlForFirefox1376932; if ( cacheControl !== 'unset' ) { let i = headerIndexFromName('cache-control', responseHeaders); @@ -605,7 +599,7 @@ const onHeadersReceived = function(details) { if ( modifiedHeaders ) { return { responseHeaders }; } -}; +} const reMediaContentTypes = /^(?:audio|image|video)\/|(?:\/ogg)$/; @@ -1011,7 +1005,7 @@ const bodyFilterer = (( ) => { /******************************************************************************/ -const injectCSP = function(fctxt, pageStore, responseHeaders) { +function injectCSP(fctxt, pageStore, responseHeaders) { const cspSubsets = []; const requestType = fctxt.type; @@ -1138,11 +1132,11 @@ const injectCSP = function(fctxt, pageStore, responseHeaders) { }); return true; -}; +} /******************************************************************************/ -const injectPP = function(fctxt, pageStore, responseHeaders) { +function injectPP(fctxt, pageStore, responseHeaders) { const permissions = []; const directives = staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'permissions'); if ( directives !== undefined ) { @@ -1168,7 +1162,7 @@ const injectPP = function(fctxt, pageStore, responseHeaders) { }); return true; -}; +} /******************************************************************************/ @@ -1179,7 +1173,7 @@ const injectPP = function(fctxt, pageStore, responseHeaders) { // cache. This works only when the webext API supports the `fromCache` // property (Firefox). -const foilLargeMediaElement = function(details, fctxt, pageStore) { +function foilLargeMediaElement(details, fctxt, pageStore) { if ( details.fromCache === true ) { return; } onDemandHeaders.setHeaders(details.responseHeaders); @@ -1195,7 +1189,7 @@ const foilLargeMediaElement = function(details, fctxt, pageStore) { } return { cancel: true }; -}; +} foilLargeMediaElement.TYPE_BITS = fc.IMAGE | fc.MEDIA | fc.XMLHTTPREQUEST; @@ -1280,6 +1274,23 @@ const strictBlockBypasser = { /******************************************************************************/ +function onResponseStarted(details) { + if ( details.tabId === -1 ) { return; } + const pageStore = µb.pageStoreFromTabId(details.tabId); + if ( pageStore === null ) { return; } + if ( pageStore.getNetFilteringSwitch() === false ) { return; } + // To enforce strict-blocking with ipaddress option + if ( isGecko === false && details.type === 'main_frame' ) { + const fctxt = µb.filteringContext.fromWebrequestDetails(details); + const r = onBeforeRootFrameRequest(fctxt); + if ( r?.cancel ) { return; } + } + details.ancestors = pageStore.getFrameAncestorDetails(details.frameId); + scriptletFilteringEngine.injectNow(details); +} + +/******************************************************************************/ + // https://github.com/uBlockOrigin/uBlock-issues/issues/2350 // Added scriptlet injection attempt at onResponseStarted time as per // https://github.com/AdguardTeam/AdguardBrowserExtension/issues/1029 and @@ -1296,27 +1307,13 @@ const webRequest = { return ( ) => { vAPI.net.setSuspendableListener(onBeforeRequest); - vAPI.net.addListener( - 'onHeadersReceived', - onHeadersReceived, - { urls: [ 'http://*/*', 'https://*/*' ] }, - [ 'blocking', 'responseHeaders' ] - ); - vAPI.net.addListener( - 'onResponseStarted', - details => { - if ( details.tabId === -1 ) { return; } - const pageStore = µb.pageStoreFromTabId(details.tabId); - if ( pageStore === null ) { return; } - if ( pageStore.getNetFilteringSwitch() === false ) { return; } - details.ancestors = pageStore.getFrameAncestorDetails(details.frameId); - scriptletFilteringEngine.injectNow(details); - }, - { - types: [ 'main_frame', 'sub_frame' ], - urls: [ 'http://*/*', 'https://*/*' ] - } - ); + vAPI.net.addListener('onHeadersReceived', onHeadersReceived, { + urls: [ 'http://*/*', 'https://*/*' ] + }, [ 'blocking', 'responseHeaders' ]); + vAPI.net.addListener('onResponseStarted', onResponseStarted, { + types: [ 'main_frame', 'sub_frame' ], + urls: [ 'http://*/*', 'https://*/*' ] + }); vAPI.defer.once({ sec: µb.hiddenSettings.toolbarWarningTimeout }).then(( ) => { if ( vAPI.net.hasUnprocessedRequest() === false ) { return; } vAPI.net.removeUnprocessedRequest();