diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index fc39f54ce..d234613bf 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -23,8 +23,6 @@ /******************************************************************************/ -import io from './assets.js'; - import { LineIterator, orphanizeString, @@ -204,6 +202,14 @@ const mimeFromName = function(name) { } }; +// vAPI.warSecret() is optional, it could be absent in some environments, +// i.e. nodejs for example. Probably the best approach is to have the +// "web_accessible_resources secret" added outside by the client of this +// module, but for now I just want to remove an obstacle to modularization. +const warSecret = typeof vAPI === 'object' && vAPI !== null + ? vAPI.warSecret + : ( ) => ''; + /******************************************************************************/ /******************************************************************************/ @@ -230,14 +236,20 @@ const RedirectEntry = class { fctxt instanceof Object && fctxt.type !== 'xmlhttprequest' ) { - let url = `${this.warURL}?secret=${vAPI.warSecret()}`; + const params = []; + const secret = warSecret(); + if ( secret !== '' ) { params.push(`secret=${secret}`); } if ( this.params !== undefined ) { for ( const name of this.params ) { const value = fctxt[name]; if ( value === undefined ) { continue; } - url += `&${name}=${encodeURIComponent(value)}`; + params.push(`${name}=${encodeURIComponent(value)}`); } } + let url = `${this.warURL}`; + if ( params.length !== 0 ) { + url += `?${params.join('&')}`; + } return url; } if ( this.data === undefined ) { return; } @@ -439,18 +451,18 @@ const removeTopCommentBlock = function(text) { /******************************************************************************/ -RedirectEngine.prototype.loadBuiltinResources = function() { +RedirectEngine.prototype.loadBuiltinResources = function(fetcher) { this.resources = new Map(); this.aliases = new Map(); const fetches = [ - io.fetchText( + fetcher( '/assets/resources/scriptlets.js' ).then(result => { const content = result.content; - if ( typeof content === 'string' && content.length !== 0 ) { - this.resourcesFromString(content); - } + if ( typeof content !== 'string' ) { return; } + if ( content.length === 0 ) { return; } + this.resourcesFromString(content); }), ]; @@ -459,7 +471,7 @@ RedirectEngine.prototype.loadBuiltinResources = function() { const entry = RedirectEntry.fromSelfie({ mime: mimeFromName(name), data, - warURL: vAPI.getURL(`/web_accessible_resources/${name}`), + warURL: `/web_accessible_resources/${name}`, params: details.params, }); this.resources.set(name, entry); @@ -506,10 +518,9 @@ RedirectEngine.prototype.loadBuiltinResources = function() { continue; } fetches.push( - io.fetch( - `/web_accessible_resources/${name}?secret=${vAPI.warSecret()}`, - { responseType: details.data } - ).then( + fetcher(`/web_accessible_resources/${name}`, { + responseType: details.data + }).then( result => process(result) ) ); @@ -545,21 +556,22 @@ RedirectEngine.prototype.getResourceDetails = function() { /******************************************************************************/ -const resourcesSelfieVersion = 5; +const RESOURCES_SELFIE_VERSION = 6; +const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources'; -RedirectEngine.prototype.selfieFromResources = function() { - io.put( - 'compiled/redirectEngine/resources', +RedirectEngine.prototype.selfieFromResources = function(storage) { + storage.put( + RESOURCES_SELFIE_NAME, JSON.stringify({ - version: resourcesSelfieVersion, + version: RESOURCES_SELFIE_VERSION, aliases: Array.from(this.aliases), resources: Array.from(this.resources), }) ); }; -RedirectEngine.prototype.resourcesFromSelfie = async function() { - const result = await io.get('compiled/redirectEngine/resources'); +RedirectEngine.prototype.resourcesFromSelfie = async function(storage) { + const result = await storage.get(RESOURCES_SELFIE_NAME); let selfie; try { selfie = JSON.parse(result.content); @@ -567,7 +579,7 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() { } if ( selfie instanceof Object === false || - selfie.version !== resourcesSelfieVersion || + selfie.version !== RESOURCES_SELFIE_VERSION || Array.isArray(selfie.resources) === false ) { return false; @@ -580,8 +592,8 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() { return true; }; -RedirectEngine.prototype.invalidateResourcesSelfie = function() { - io.remove('compiled/redirectEngine/resources'); +RedirectEngine.prototype.invalidateResourcesSelfie = function(storage) { + storage.remove(RESOURCES_SELFIE_NAME); }; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index dca5f15ca..9308f5d7b 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -140,7 +140,7 @@ const onVersionReady = function(lastVersion) { // Since built-in resources may have changed since last version, we // force a reload of all resources. - redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(io); // https://github.com/LiCybora/NanoDefenderFirefox/issues/196 // Toggle on the blocking of CSP reports by default for Firefox. diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 25fe86450..162a8f6ca 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -35,7 +35,6 @@ import { hostnameFromNetworkURL, } from './uri-utils.js'; - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility // // This import would be best done dynamically, but since dynamic imports are @@ -4287,22 +4286,20 @@ FilterContainer.prototype.redirectRequest = function(redirectEngine, fctxt) { const highest = directives.length - 1; // More than a single directive means more work. if ( highest !== 0 ) { - directives.sort( - FilterContainer.compareRedirectRequests.bind(this, redirectEngine) - ); + directives.sort((a, b) => compareRedirectRequests(redirectEngine, a, b)); } // Redirect to highest-ranked directive const directive = directives[highest]; if ( (directive.bits & AllowAction) === 0 ) { const { token } = - FilterContainer.parseRedirectRequestValue(directive.modifier); + parseRedirectRequestValue(directive.modifier); fctxt.redirectURL = redirectEngine.tokenToURL(fctxt, token); if ( fctxt.redirectURL === undefined ) { return; } } return directives; }; -FilterContainer.parseRedirectRequestValue = function(modifier) { +const parseRedirectRequestValue = function(modifier) { if ( modifier.cache === undefined ) { modifier.cache = StaticFilteringParser.parseRedirectValue(modifier.value); @@ -4310,12 +4307,12 @@ FilterContainer.parseRedirectRequestValue = function(modifier) { return modifier.cache; }; -FilterContainer.compareRedirectRequests = function(redirectEngine, a, b) { +const compareRedirectRequests = function(redirectEngine, a, b) { const { token: atok, priority: aint, bits: abits } = - FilterContainer.parseRedirectRequestValue(a.modifier); + parseRedirectRequestValue(a.modifier); if ( redirectEngine.hasToken(atok) === false ) { return -1; } const { token: btok, priority: bint, bits: bbits } = - FilterContainer.parseRedirectRequestValue(b.modifier); + parseRedirectRequestValue(b.modifier); if ( redirectEngine.hasToken(btok) === false ) { return 1; } if ( abits !== bbits ) { if ( (abits & Important) !== 0 ) { return 1; } diff --git a/src/js/storage.js b/src/js/storage.js index 9525440b6..c1d37512a 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -1151,11 +1151,19 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { µb.loadRedirectResources = async function() { try { - const success = await redirectEngine.resourcesFromSelfie(); + const success = await redirectEngine.resourcesFromSelfie(io); if ( success === true ) { return true; } + const fetcher = (path, options = undefined) => { + if ( path.startsWith('/web_accessible_resources/') ) { + path += `?secret=${vAPI.warSecret()}`; + return io.fetch(path, options); + } + return io.fetchText(path); + }; + const fetchPromises = [ - redirectEngine.loadBuiltinResources() + redirectEngine.loadBuiltinResources(fetcher) ]; const userResourcesLocation = this.hiddenSettings.userResourcesLocation; @@ -1182,7 +1190,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { } redirectEngine.resourcesFromString(content); - redirectEngine.selfieFromResources(); + redirectEngine.selfieFromResources(io); } catch(ex) { ubolog(ex); return false; @@ -1617,7 +1625,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.hiddenSettings.userResourcesLocation !== 'unset' || vAPI.webextFlavor.soup.has('devbuild') ) { - redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(io); } this.loadFilterLists(); } diff --git a/src/js/traffic.js b/src/js/traffic.js index 7dbec5677..b3ecc7ab7 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -61,6 +61,12 @@ const supportsFloc = document.interestCohort instanceof Function; /******************************************************************************/ +const patchLocalRedirectURL = url => url.charCodeAt(0) === 0x2F /* '/' */ + ? vAPI.getURL(url) + : url; + +/******************************************************************************/ + // Intercept and filter web requests. const onBeforeRequest = function(details) { @@ -102,7 +108,7 @@ const onBeforeRequest = function(details) { // Redirected if ( fctxt.redirectURL !== undefined ) { - return { redirectUrl: fctxt.redirectURL }; + return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) }; } // Not redirected @@ -208,7 +214,7 @@ const onBeforeRootFrameRequest = function(fctxt) { // Redirected if ( fctxt.redirectURL !== undefined ) { - return { redirectUrl: fctxt.redirectURL }; + return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) }; } // Not blocked @@ -414,7 +420,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) { // Redirected if ( fctxt.redirectURL !== undefined ) { - return { redirectUrl: fctxt.redirectURL }; + return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) }; } // Blocked? diff --git a/src/js/ublock.js b/src/js/ublock.js index d6567e545..304fbf6e2 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -25,6 +25,7 @@ import contextMenu from './contextmenu.js'; import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import io from './assets.js'; import µb from './background.js'; import { hostnameFromURI } from './uri-utils.js'; import { redirectEngine } from './redirect-engine.js'; @@ -423,7 +424,7 @@ const matchBucket = function(url, hostname, bucket, start) { this.hiddenSettings = hs; this.saveHiddenSettings(); if ( mustReloadResources ) { - redirectEngine.invalidateResourcesSelfie(); + redirectEngine.invalidateResourcesSelfie(io); this.loadRedirectResources(); } this.fireDOMEvent('hiddenSettingsChanged');