Remove `assets` dependency from redirect engine

Related issue:
- https://github.com/uBlockOrigin/uBlock-issues/issues/1664

This change allows to add the redirect engine into the
nodejs package. The purpose of the redirect engine is to
resolve a redirect token into a path to a local resource,
to be used by the caller as wished.
This commit is contained in:
Raymond Hill 2021-08-02 09:23:48 -04:00
parent 3879835324
commit f8daea085b
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
6 changed files with 66 additions and 42 deletions

View File

@ -23,8 +23,6 @@
/******************************************************************************/ /******************************************************************************/
import io from './assets.js';
import { import {
LineIterator, LineIterator,
orphanizeString, 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 instanceof Object &&
fctxt.type !== 'xmlhttprequest' 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 ) { if ( this.params !== undefined ) {
for ( const name of this.params ) { for ( const name of this.params ) {
const value = fctxt[name]; const value = fctxt[name];
if ( value === undefined ) { continue; } 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; return url;
} }
if ( this.data === undefined ) { return; } 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.resources = new Map();
this.aliases = new Map(); this.aliases = new Map();
const fetches = [ const fetches = [
io.fetchText( fetcher(
'/assets/resources/scriptlets.js' '/assets/resources/scriptlets.js'
).then(result => { ).then(result => {
const content = result.content; const content = result.content;
if ( typeof content === 'string' && content.length !== 0 ) { if ( typeof content !== 'string' ) { return; }
if ( content.length === 0 ) { return; }
this.resourcesFromString(content); this.resourcesFromString(content);
}
}), }),
]; ];
@ -459,7 +471,7 @@ RedirectEngine.prototype.loadBuiltinResources = function() {
const entry = RedirectEntry.fromSelfie({ const entry = RedirectEntry.fromSelfie({
mime: mimeFromName(name), mime: mimeFromName(name),
data, data,
warURL: vAPI.getURL(`/web_accessible_resources/${name}`), warURL: `/web_accessible_resources/${name}`,
params: details.params, params: details.params,
}); });
this.resources.set(name, entry); this.resources.set(name, entry);
@ -506,10 +518,9 @@ RedirectEngine.prototype.loadBuiltinResources = function() {
continue; continue;
} }
fetches.push( fetches.push(
io.fetch( fetcher(`/web_accessible_resources/${name}`, {
`/web_accessible_resources/${name}?secret=${vAPI.warSecret()}`, responseType: details.data
{ responseType: details.data } }).then(
).then(
result => process(result) 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() { RedirectEngine.prototype.selfieFromResources = function(storage) {
io.put( storage.put(
'compiled/redirectEngine/resources', RESOURCES_SELFIE_NAME,
JSON.stringify({ JSON.stringify({
version: resourcesSelfieVersion, version: RESOURCES_SELFIE_VERSION,
aliases: Array.from(this.aliases), aliases: Array.from(this.aliases),
resources: Array.from(this.resources), resources: Array.from(this.resources),
}) })
); );
}; };
RedirectEngine.prototype.resourcesFromSelfie = async function() { RedirectEngine.prototype.resourcesFromSelfie = async function(storage) {
const result = await io.get('compiled/redirectEngine/resources'); const result = await storage.get(RESOURCES_SELFIE_NAME);
let selfie; let selfie;
try { try {
selfie = JSON.parse(result.content); selfie = JSON.parse(result.content);
@ -567,7 +579,7 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() {
} }
if ( if (
selfie instanceof Object === false || selfie instanceof Object === false ||
selfie.version !== resourcesSelfieVersion || selfie.version !== RESOURCES_SELFIE_VERSION ||
Array.isArray(selfie.resources) === false Array.isArray(selfie.resources) === false
) { ) {
return false; return false;
@ -580,8 +592,8 @@ RedirectEngine.prototype.resourcesFromSelfie = async function() {
return true; return true;
}; };
RedirectEngine.prototype.invalidateResourcesSelfie = function() { RedirectEngine.prototype.invalidateResourcesSelfie = function(storage) {
io.remove('compiled/redirectEngine/resources'); storage.remove(RESOURCES_SELFIE_NAME);
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -140,7 +140,7 @@ const onVersionReady = function(lastVersion) {
// Since built-in resources may have changed since last version, we // Since built-in resources may have changed since last version, we
// force a reload of all resources. // force a reload of all resources.
redirectEngine.invalidateResourcesSelfie(); redirectEngine.invalidateResourcesSelfie(io);
// https://github.com/LiCybora/NanoDefenderFirefox/issues/196 // https://github.com/LiCybora/NanoDefenderFirefox/issues/196
// Toggle on the blocking of CSP reports by default for Firefox. // Toggle on the blocking of CSP reports by default for Firefox.

View File

@ -35,7 +35,6 @@ import {
hostnameFromNetworkURL, hostnameFromNetworkURL,
} from './uri-utils.js'; } from './uri-utils.js';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility // 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 // 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; const highest = directives.length - 1;
// More than a single directive means more work. // More than a single directive means more work.
if ( highest !== 0 ) { if ( highest !== 0 ) {
directives.sort( directives.sort((a, b) => compareRedirectRequests(redirectEngine, a, b));
FilterContainer.compareRedirectRequests.bind(this, redirectEngine)
);
} }
// Redirect to highest-ranked directive // Redirect to highest-ranked directive
const directive = directives[highest]; const directive = directives[highest];
if ( (directive.bits & AllowAction) === 0 ) { if ( (directive.bits & AllowAction) === 0 ) {
const { token } = const { token } =
FilterContainer.parseRedirectRequestValue(directive.modifier); parseRedirectRequestValue(directive.modifier);
fctxt.redirectURL = redirectEngine.tokenToURL(fctxt, token); fctxt.redirectURL = redirectEngine.tokenToURL(fctxt, token);
if ( fctxt.redirectURL === undefined ) { return; } if ( fctxt.redirectURL === undefined ) { return; }
} }
return directives; return directives;
}; };
FilterContainer.parseRedirectRequestValue = function(modifier) { const parseRedirectRequestValue = function(modifier) {
if ( modifier.cache === undefined ) { if ( modifier.cache === undefined ) {
modifier.cache = modifier.cache =
StaticFilteringParser.parseRedirectValue(modifier.value); StaticFilteringParser.parseRedirectValue(modifier.value);
@ -4310,12 +4307,12 @@ FilterContainer.parseRedirectRequestValue = function(modifier) {
return modifier.cache; return modifier.cache;
}; };
FilterContainer.compareRedirectRequests = function(redirectEngine, a, b) { const compareRedirectRequests = function(redirectEngine, a, b) {
const { token: atok, priority: aint, bits: abits } = const { token: atok, priority: aint, bits: abits } =
FilterContainer.parseRedirectRequestValue(a.modifier); parseRedirectRequestValue(a.modifier);
if ( redirectEngine.hasToken(atok) === false ) { return -1; } if ( redirectEngine.hasToken(atok) === false ) { return -1; }
const { token: btok, priority: bint, bits: bbits } = const { token: btok, priority: bint, bits: bbits } =
FilterContainer.parseRedirectRequestValue(b.modifier); parseRedirectRequestValue(b.modifier);
if ( redirectEngine.hasToken(btok) === false ) { return 1; } if ( redirectEngine.hasToken(btok) === false ) { return 1; }
if ( abits !== bbits ) { if ( abits !== bbits ) {
if ( (abits & Important) !== 0 ) { return 1; } if ( (abits & Important) !== 0 ) { return 1; }

View File

@ -1151,11 +1151,19 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
µb.loadRedirectResources = async function() { µb.loadRedirectResources = async function() {
try { try {
const success = await redirectEngine.resourcesFromSelfie(); const success = await redirectEngine.resourcesFromSelfie(io);
if ( success === true ) { return true; } 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 = [ const fetchPromises = [
redirectEngine.loadBuiltinResources() redirectEngine.loadBuiltinResources(fetcher)
]; ];
const userResourcesLocation = this.hiddenSettings.userResourcesLocation; const userResourcesLocation = this.hiddenSettings.userResourcesLocation;
@ -1182,7 +1190,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
} }
redirectEngine.resourcesFromString(content); redirectEngine.resourcesFromString(content);
redirectEngine.selfieFromResources(); redirectEngine.selfieFromResources(io);
} catch(ex) { } catch(ex) {
ubolog(ex); ubolog(ex);
return false; return false;
@ -1617,7 +1625,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
this.hiddenSettings.userResourcesLocation !== 'unset' || this.hiddenSettings.userResourcesLocation !== 'unset' ||
vAPI.webextFlavor.soup.has('devbuild') vAPI.webextFlavor.soup.has('devbuild')
) { ) {
redirectEngine.invalidateResourcesSelfie(); redirectEngine.invalidateResourcesSelfie(io);
} }
this.loadFilterLists(); this.loadFilterLists();
} }

View File

@ -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. // Intercept and filter web requests.
const onBeforeRequest = function(details) { const onBeforeRequest = function(details) {
@ -102,7 +108,7 @@ const onBeforeRequest = function(details) {
// Redirected // Redirected
if ( fctxt.redirectURL !== undefined ) { if ( fctxt.redirectURL !== undefined ) {
return { redirectUrl: fctxt.redirectURL }; return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) };
} }
// Not redirected // Not redirected
@ -208,7 +214,7 @@ const onBeforeRootFrameRequest = function(fctxt) {
// Redirected // Redirected
if ( fctxt.redirectURL !== undefined ) { if ( fctxt.redirectURL !== undefined ) {
return { redirectUrl: fctxt.redirectURL }; return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) };
} }
// Not blocked // Not blocked
@ -414,7 +420,7 @@ const onBeforeBehindTheSceneRequest = function(fctxt) {
// Redirected // Redirected
if ( fctxt.redirectURL !== undefined ) { if ( fctxt.redirectURL !== undefined ) {
return { redirectUrl: fctxt.redirectURL }; return { redirectUrl: patchLocalRedirectURL(fctxt.redirectURL) };
} }
// Blocked? // Blocked?

View File

@ -25,6 +25,7 @@
import contextMenu from './contextmenu.js'; import contextMenu from './contextmenu.js';
import cosmeticFilteringEngine from './cosmetic-filtering.js'; import cosmeticFilteringEngine from './cosmetic-filtering.js';
import io from './assets.js';
import µb from './background.js'; import µb from './background.js';
import { hostnameFromURI } from './uri-utils.js'; import { hostnameFromURI } from './uri-utils.js';
import { redirectEngine } from './redirect-engine.js'; import { redirectEngine } from './redirect-engine.js';
@ -423,7 +424,7 @@ const matchBucket = function(url, hostname, bucket, start) {
this.hiddenSettings = hs; this.hiddenSettings = hs;
this.saveHiddenSettings(); this.saveHiddenSettings();
if ( mustReloadResources ) { if ( mustReloadResources ) {
redirectEngine.invalidateResourcesSelfie(); redirectEngine.invalidateResourcesSelfie(io);
this.loadRedirectResources(); this.loadRedirectResources();
} }
this.fireDOMEvent('hiddenSettingsChanged'); this.fireDOMEvent('hiddenSettingsChanged');