diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 3b4c4fad9..2abb1eea7 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -32,6 +32,7 @@ import redirectResourcesMap from './js/redirect-resources.js'; import { dnrRulesetFromRawLists } from './js/static-dnr-filtering.js'; import * as sfp from './js/static-filtering-parser.js'; import * as makeScriptlet from './make-scriptlets.js'; +import { safeReplace } from './safe-replace.js'; /******************************************************************************/ @@ -400,14 +401,14 @@ async function processGenericCosmeticFilters(assetDetails, bucketsMap, exclusion const selectorLists = bucketsList.map(v => [ v[0], v[1].join(',') ]); const originalScriptletMap = await loadAllSourceScriptlets(); - const patchedScriptlet = originalScriptletMap.get('css-generic') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$genericSelectorMap\$/m, - `${JSON.stringify(selectorLists, scriptletJsonReplacer)}` - ); + let patchedScriptlet = originalScriptletMap.get('css-generic').replace( + '$rulesetId$', + assetDetails.id + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$genericSelectorMap\$/, + `${JSON.stringify(selectorLists, scriptletJsonReplacer)}` + ); writeFile( `${scriptletDir}/generic/${assetDetails.id}.js`, @@ -593,23 +594,26 @@ async function processCosmeticFilters(assetDetails, mapin) { } const originalScriptletMap = await loadAllSourceScriptlets(); - const patchedScriptlet = originalScriptletMap.get('css-specific') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$hostnamesMap\$/m, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$entitiesMap\$/m, - `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$exceptionsMap\$/m, - `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` - ); + let patchedScriptlet = originalScriptletMap.get('css-specific').replace( + '$rulesetId$', + assetDetails.id + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$argsList\$/, + `${JSON.stringify(argsList, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$hostnamesMap\$/, + `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$entitiesMap\$/, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$exceptionsMap\$/, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` + ); writeFile(`${scriptletDir}/specific/${assetDetails.id}.js`, patchedScriptlet); generatedFiles.push(`${assetDetails.id}`); @@ -677,23 +681,26 @@ async function processDeclarativeCosmeticFilters(assetDetails, mapin) { } const originalScriptletMap = await loadAllSourceScriptlets(); - const patchedScriptlet = originalScriptletMap.get('css-declarative') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$hostnamesMap\$/m, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$entitiesMap\$/m, - `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$exceptionsMap\$/m, - `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` - ); + let patchedScriptlet = originalScriptletMap.get('css-declarative').replace( + '$rulesetId$', + assetDetails.id + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$argsList\$/, + `${JSON.stringify(argsList, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$hostnamesMap\$/, + `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$entitiesMap\$/, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$exceptionsMap\$/, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` + ); writeFile(`${scriptletDir}/declarative/${assetDetails.id}.js`, patchedScriptlet); if ( contentArray.length !== 0 ) { @@ -760,23 +767,26 @@ async function processProceduralCosmeticFilters(assetDetails, mapin) { } const originalScriptletMap = await loadAllSourceScriptlets(); - const patchedScriptlet = originalScriptletMap.get('css-procedural') - .replace( - '$rulesetId$', - assetDetails.id - ).replace( - /\bself\.\$argsList\$/m, - `${JSON.stringify(argsList, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$hostnamesMap\$/m, - `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$entitiesMap\$/m, - `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` - ).replace( - /\bself\.\$exceptionsMap\$/m, - `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` - ); + let patchedScriptlet = originalScriptletMap.get('css-procedural').replace( + '$rulesetId$', + assetDetails.id + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$argsList\$/, + `${JSON.stringify(argsList, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$hostnamesMap\$/, + `${JSON.stringify(hostnamesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$entitiesMap\$/, + `${JSON.stringify(entitiesMap, scriptletJsonReplacer)}` + ); + patchedScriptlet = safeReplace(patchedScriptlet, + /\bself\.\$exceptionsMap\$/, + `${JSON.stringify(exceptionsMap, scriptletJsonReplacer)}` + ); writeFile(`${scriptletDir}/procedural/${assetDetails.id}.js`, patchedScriptlet); if ( contentArray.length !== 0 ) { diff --git a/platform/mv3/make-scriptlets.js b/platform/mv3/make-scriptlets.js index 53a4435c6..7dfeb89b6 100644 --- a/platform/mv3/make-scriptlets.js +++ b/platform/mv3/make-scriptlets.js @@ -25,6 +25,7 @@ import fs from 'fs/promises'; import { builtinScriptlets } from './scriptlets.js'; +import { safeReplace } from './safe-replace.js'; /******************************************************************************/ @@ -52,25 +53,6 @@ function createScriptletCoreCode(scriptletToken) { /******************************************************************************/ -function safeReplace(text, pattern, replacement, count = 1) { - const rePattern = typeof pattern === 'string' - ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) - : pattern; - let out = text; - for (;;) { - const match = rePattern.exec(out); - if ( match === null ) { break; } - out = out.slice(0, match.index) + - replacement + - out.slice(match.index + match[0].length); - count -= 1; - if ( count === 0 ) { break; } - } - return out; -} - -/******************************************************************************/ - export function init() { for ( const scriptlet of builtinScriptlets ) { const { name, aliases, fn } = scriptlet; diff --git a/platform/mv3/safe-replace.js b/platform/mv3/safe-replace.js new file mode 100644 index 000000000..5adb9ffd2 --- /dev/null +++ b/platform/mv3/safe-replace.js @@ -0,0 +1,41 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +export function safeReplace(text, pattern, replacement, count = 1) { + const rePattern = typeof pattern === 'string' + ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + : pattern; + let out = text; + for (;;) { + const match = rePattern.exec(out); + if ( match === null ) { break; } + out = out.slice(0, match.index) + + replacement + + out.slice(match.index + match[0].length); + count -= 1; + if ( count === 0 ) { break; } + } + return out; +}