diff --git a/src/js/jsonpath.js b/src/js/jsonpath.js new file mode 100644 index 000000000..04cb95cdd --- /dev/null +++ b/src/js/jsonpath.js @@ -0,0 +1,332 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2025-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 + +*/ + +/******************************************************************************/ + +export class JSONPath { + static create(query) { + const jsonp = new JSONPath(); + jsonp.compile(query); + return jsonp; + } + compile(query) { + this.#compiled = this.#compile(query, 0); + return this.#compiled ? this.#compiled.i : 0; + } + evaluate(root) { + if ( this.#compiled === undefined ) { return []; } + this.root = root; + return this.#evaluate(this.#compiled.steps, []); + } + resolvePath(path) { + if ( path.length === 0 ) { return { value: this.root }; } + const key = path.at(-1); + let obj = this.root + for ( let i = 0, n = path.length-1; i < n; i++ ) { + obj = obj[path[i]]; + } + return { obj, key, value: obj[key] }; + } + toString() { + return JSON.stringify(this.#compiled); + } + #UNDEFINED = 0; + #ROOT = 1; + #CURRENT = 2; + #CHILDREN = 3; + #DESCENDANTS = 4; + #reUnquotedIdentifier = /^[A-Za-z_][\w]*|^\*/; + #reExpr = /^([!=^$*]=|[<>]=?)(.+?)\)\]/; + #reIndice = /^\[-?\d+\]/; + #compiled; + #compile(query, i) { + if ( query.length === 0 ) { return; } + const steps = []; + let c = query.charCodeAt(i); + steps.push({ mv: c === 0x24 /* $ */ ? this.#ROOT : this.#CURRENT }); + if ( c === 0x24 /* $ */ || c === 0x40 /* @ */ ) { i += 1; } + let mv = this.#UNDEFINED; + for (;;) { + if ( i === query.length ) { break; } + c = query.charCodeAt(i); + if ( c === 0x20 /* whitespace */ ) { + i += 1; + continue; + } + // Dot accessor syntax + if ( c === 0x2E /* . */ ) { + if ( mv !== this.#UNDEFINED ) { return; } + if ( query.startsWith('..', i) ) { + mv = this.#DESCENDANTS; + i += 2; + } else { + mv = this.#CHILDREN; + i += 1; + } + continue; + } + if ( c !== 0x5B /* [ */ ) { + if ( mv === this.#UNDEFINED ) { + const step = steps.at(-1); + if ( step === undefined ) { return; } + i = this.#compileExpr(step, query, i); + break; + } + const s = this.#consumeUnquotedIdentifier(query, i); + if ( s === undefined ) { return; } + steps.push({ mv, k: s }); + i += s.length; + mv = this.#UNDEFINED; + continue; + } + // Bracket accessor syntax + if ( query.startsWith('[*]', i) ) { + mv ||= this.#CHILDREN; + steps.push({ mv, k: '*' }); + i += 3; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith("['", i) ) { + const r = this.#consumeQuotedIdentifier(query, i+2); + if ( r === undefined ) { return; } + mv ||= this.#CHILDREN; + steps.push({ mv, k: r.s }); + i = r.i; + mv = this.#UNDEFINED; + continue; + } + if ( query.startsWith('[?(', i) ) { + const not = query.charCodeAt(i+3) === 0x21 /* ! */; + const j = i + 3 + (not ? 1 : 0); + const r = this.#compile(query, j); + if ( r === undefined ) { return; } + if ( query.startsWith(')]', r.i) === false ) { return; } + if ( not ) { r.steps.at(-1).not = true; } + steps.push({ mv: mv || this.#CHILDREN, steps: r.steps }); + i = r.i + 2; + mv = this.#UNDEFINED; + continue; + } + if ( this.#reIndice.test(query.slice(i)) ) { + const match = this.#reIndice.exec(query.slice(i)); + const indice = parseInt(query.slice(i+1), 10); + mv ||= this.CHILDREN; + steps.push({ mv, k: indice }); + i += match[0].length; + mv = this.#UNDEFINED; + continue; + } + return; + } + if ( steps.length <= 1 ) { return; } + return { steps, i }; + } + #evaluate(steps, pathin) { + let resultset = []; + if ( Array.isArray(steps) === false ) { return resultset; } + for ( const step of steps ) { + switch ( step.mv ) { + case this.#ROOT: + resultset = [ [] ]; + break; + case this.#CURRENT: + resultset = [ pathin ]; + break; + case this.#CHILDREN: + case this.#DESCENDANTS: + resultset = this.#getMatches(resultset, step); + break; + default: + break; + } + } + return resultset; + } + #getMatches(listin, step) { + const listout = []; + const recursive = step.mv === this.#DESCENDANTS; + for ( const pathin of listin ) { + const { value: v } = this.resolvePath(pathin); + if ( v === null ) { continue; } + if ( v === undefined ) { continue; } + const { steps, k } = step; + if ( k ) { + if ( k === '*' ) { + const entries = Array.from(this.#getDescendants(v, recursive)); + for ( const { path } of entries ) { + this.#evaluateExpr(step, [ ...pathin, ...path ], listout); + } + continue; + } + if ( typeof k === 'number' ) { + if ( Array.isArray(v) === false ) { continue; } + const n = v.length; + const i = k >= 0 ? k : n + k; + if ( i < 0 ) { continue; } + if ( i >= n ) { continue; } + this.#evaluateExpr(step, [ ...pathin, i ], listout); + } else if ( Array.isArray(k) ) { + for ( const l of k ) { + this.#evaluateExpr(step, [ ...pathin, l ], listout); + } + } else { + this.#evaluateExpr(step, [ ...pathin, k ], listout); + } + if ( recursive !== true ) { continue; } + for ( const { obj, key, path } of this.#getDescendants(v, recursive) ) { + const w = obj[key]; + if ( w instanceof Object === false ) { continue; } + if ( Object.hasOwn(w, k) === false ) { continue; } + this.#evaluateExpr(step, [ ...pathin, ...path, k ], listout); + } + continue; + } + if ( steps ) { + const isArray = Array.isArray(v); + if ( isArray === false ) { + const r = this.#evaluate(steps, pathin); + if ( r.length !== 0 ) { + listout.push(pathin); + } + if ( recursive !== true ) { continue; } + } + for ( const { obj, key, path } of this.#getDescendants(v, recursive) ) { + const w = obj[key]; + if ( Array.isArray(w) ) { continue; } + const x = [ ...pathin, ...path ]; + const r = this.#evaluate(steps, x); + if ( r.length !== 0 ) { + listout.push(x); + } + } + } + } + return listout; + } + #getDescendants(v, recursive) { + const iterator = { + next() { + const n = this.stack.length; + if ( n === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + const details = this.stack[n-1]; + const entry = details.keys.next(); + if ( entry.done ) { + this.stack.pop(); + this.path.pop(); + return this.next(); + } + this.path[n-1] = entry.value; + this.value = { + obj: details.obj, + key: entry.value, + path: this.path.slice(), + }; + const v = this.value.obj[this.value.key]; + if ( recursive ) { + if ( Array.isArray(v) ) { + this.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + this.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + } + return this; + }, + path: [], + value: undefined, + done: false, + stack: [], + [Symbol.iterator]() { return this; }, + }; + if ( Array.isArray(v) ) { + iterator.stack.push({ obj: v, keys: v.keys() }); + } else if ( typeof v === 'object' && v !== null ) { + iterator.stack.push({ obj: v, keys: Object.keys(v).values() }); + } + return iterator; + } + #consumeQuotedIdentifier(query, i) { + const len = query.length; + const parts = []; + let beg = i, end = i; + for (;;) { + if ( end === len ) { return; } + const c = query.charCodeAt(end); + if ( c === 0x27 /* ' */ ) { + if ( query.startsWith("']", end) === false ) { return; } + parts.push(query.slice(beg, end)); + end += 2; + break; + } + if ( c === 0x5C /* \ */ && (end+1) < len ) { + parts.push(query.slice(beg, end)); + const d = query.chatCodeAt(end+1); + if ( d === 0x27 || d === 0x5C ) { + end += 1; + beg = end; + } + } + end += 1; + } + return { s: parts.join(''), i: end }; + } + #consumeUnquotedIdentifier(query, i) { + const match = this.#reUnquotedIdentifier.exec(query.slice(i)); + if ( match === null ) { return; } + return match[0]; + } + #compileExpr(step, query, i) { + const match = this.#reExpr.exec(query.slice(i)); + if ( match === null ) { return i; } + try { + step.rval = JSON.parse(match[2]); + step.op = match[1]; + } catch { + } + return i + match[1].length + match[2].length; + } + #evaluateExpr(step, path, out) { + const { obj: o, key: k } = this.resolvePath(path); + const hasOwn = o instanceof Object && Object.hasOwn(o, k); + const v = o[k]; + let outcome = true; + if ( step.op !== undefined && hasOwn === false ) { return; } + switch ( step.op ) { + case '==': outcome = v === step.rval; break; + case '!=': outcome = v !== step.rval; break; + case '<': outcome = v < step.rval; break; + case '<=': outcome = v <= step.rval; break; + case '>': outcome = v > step.rval; break; + case '>=': outcome = v >= step.rval; break; + case '^=': outcome = `${v}`.startsWith(step.rval); break; + case '$=': outcome = `${v}`.endsWith(step.rval); break; + case '*=': outcome = `${v}`.includes(step.rval); break; + default: outcome = hasOwn; break; + } + if ( outcome === (step.not === true) ) { return; } + out.push(path); + } +} diff --git a/src/js/resources/json-prune.js b/src/js/resources/json-prune.js index 62f134473..08284e313 100644 --- a/src/js/resources/json-prune.js +++ b/src/js/resources/json-prune.js @@ -73,7 +73,7 @@ registerScriptlet(jsonPrune, { /******************************************************************************/ -function jsonPruneFetchResponseFn( +function jsonPruneFetchResponse( rawPrunePaths = '', rawNeedlePaths = '' ) { @@ -145,8 +145,8 @@ function jsonPruneFetchResponseFn( apply: applyHandler }); } -registerScriptlet(jsonPruneFetchResponseFn, { - name: 'json-prune-fetch-response.fn', +registerScriptlet(jsonPruneFetchResponse, { + name: 'json-prune-fetch-response.js', dependencies: [ matchObjectPropertiesFn, objectPruneFn, @@ -157,19 +157,7 @@ registerScriptlet(jsonPruneFetchResponseFn, { /******************************************************************************/ -function jsonPruneFetchResponse(...args) { - jsonPruneFetchResponseFn(...args); -} -registerScriptlet(jsonPruneFetchResponse, { - name: 'json-prune-fetch-response.js', - dependencies: [ - jsonPruneFetchResponseFn, - ], -}); - -/******************************************************************************/ - -function jsonPruneXhrResponseFn( +function jsonPruneXhrResponse( rawPrunePaths = '', rawNeedlePaths = '' ) { @@ -250,8 +238,8 @@ function jsonPruneXhrResponseFn( } }; } -registerScriptlet(jsonPruneXhrResponseFn, { - name: 'json-prune-xhr-response.fn', +registerScriptlet(jsonPruneXhrResponse, { + name: 'json-prune-xhr-response.js', dependencies: [ matchObjectPropertiesFn, objectPruneFn, @@ -262,18 +250,6 @@ registerScriptlet(jsonPruneXhrResponseFn, { /******************************************************************************/ -function jsonPruneXhrResponse(...args) { - jsonPruneXhrResponseFn(...args); -} -registerScriptlet(jsonPruneXhrResponse, { - name: 'json-prune-xhr-response.js', - dependencies: [ - jsonPruneXhrResponseFn, - ], -}); - -/******************************************************************************/ - // There is still code out there which uses `eval` in lieu of `JSON.parse`. function evaldataPrune( diff --git a/src/js/resources/jsonl-prune.js b/src/js/resources/jsonl-prune.js index d62682332..be1f82437 100644 --- a/src/js/resources/jsonl-prune.js +++ b/src/js/resources/jsonl-prune.js @@ -25,6 +25,7 @@ import { parsePropertiesToMatchFn, } from './utils.js'; +import { JSONPath } from './shared.js'; import { objectPruneFn } from './object-prune.js'; import { registerScriptlet } from './base.js'; import { safeSelf } from './safe-self.js'; @@ -32,36 +33,38 @@ import { safeSelf } from './safe-self.js'; /******************************************************************************/ function jsonlPruneFn( - text = '', - rawPrunePaths = '', - rawNeedlePaths = '' + jsonp, + text = '' ) { const safe = safeSelf(); const linesBefore = text.split(/\n+/); const linesAfter = []; for ( const lineBefore of linesBefore ) { - let objBefore; + let obj; try { - objBefore = safe.JSON_parse(lineBefore); + obj = safe.JSON_parse(lineBefore); } catch { } - if ( typeof objBefore !== 'object' ) { + if ( typeof obj !== 'object' || obj === null ) { linesAfter.push(lineBefore); continue; } - const objAfter = objectPruneFn(objBefore, rawPrunePaths, rawNeedlePaths); - if ( typeof objAfter !== 'object' ) { + const paths = jsonp.evaluate(obj); + if ( paths.length === 0 ) { linesAfter.push(lineBefore); continue; } - linesAfter.push(safe.JSON_stringify(objAfter).replace(/\//g, '\\/')); + for ( const path of paths ) { + const { obj, key } = jsonp.resolvePath(path); + delete obj[key]; + } + linesAfter.push(safe.JSON_stringify(obj).replace(/\//g, '\\/')); } return linesAfter.join('\n'); } registerScriptlet(jsonlPruneFn, { name: 'jsonl-prune.fn', dependencies: [ - objectPruneFn, safeSelf, ], }); @@ -74,11 +77,8 @@ registerScriptlet(jsonlPruneFn, { * @description * Prune the objects found in a JSONL resource fetched through a XHR instance. * - * @param rawPrunePaths - * The property to remove from the objects. - * - * @param rawNeedlePaths - * A property which must be present for the pruning to take effect. + * @param jsonq + * A uBO-flavored JSONPath query. * * @param [propsToMatch, value] * An optional vararg detailing the arguments to match when xhr.open() is @@ -86,14 +86,14 @@ registerScriptlet(jsonlPruneFn, { * * */ -function jsonlPruneXhrResponseFn( - rawPrunePaths = '', - rawNeedlePaths = '' +function jsonlPruneXhrResponse( + jsonq = '', ) { const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('jsonl-prune-xhr-response', rawPrunePaths, rawNeedlePaths); + const logPrefix = safe.makeLogPrefix('jsonl-prune-xhr-response', jsonq); const xhrInstances = new WeakMap(); - const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const jsonp = JSONPath.create(jsonq); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 1); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); self.XMLHttpRequest = class extends self.XMLHttpRequest { open(method, url, ...args) { @@ -127,7 +127,7 @@ function jsonlPruneXhrResponseFn( if ( typeof innerResponse !== 'string' ) { return (xhrDetails.response = innerResponse); } - const outerResponse = jsonlPruneFn(innerResponse, rawPrunePaths, rawNeedlePaths); + const outerResponse = jsonlPruneFn(jsonp, innerResponse); if ( outerResponse !== innerResponse ) { safe.uboLog(logPrefix, 'Pruned'); } @@ -141,9 +141,10 @@ function jsonlPruneXhrResponseFn( } }; } -registerScriptlet(jsonlPruneXhrResponseFn, { - name: 'jsonl-prune-xhr-response.fn', +registerScriptlet(jsonlPruneXhrResponse, { + name: 'jsonl-prune-xhr-response.js', dependencies: [ + JSONPath, jsonlPruneFn, matchObjectPropertiesFn, parsePropertiesToMatchFn, @@ -153,18 +154,6 @@ registerScriptlet(jsonlPruneXhrResponseFn, { /******************************************************************************/ -function jsonlPruneXhrResponse(...args) { - jsonlPruneXhrResponseFn(...args); -} -registerScriptlet(jsonlPruneXhrResponse, { - name: 'jsonl-prune-xhr-response.js', - dependencies: [ - jsonlPruneXhrResponseFn, - ], -}); - -/******************************************************************************/ - /** * @scriptlet jsonl-prune-fetch-response.js * @@ -172,11 +161,8 @@ registerScriptlet(jsonlPruneXhrResponse, { * Prune the objects found in a JSONL resource fetched through the fetch API. * Once the pruning is performed. * - * @param rawPrunePaths - * The property to remove from the objects. - * - * @param rawNeedlePaths - * A property which must be present for the pruning to take effect. + * @param jsonq + * A uBO-flavored JSONPath query. * * @param [propsToMatch, value] * An optional vararg detailing the arguments to match when xhr.open() is @@ -184,15 +170,15 @@ registerScriptlet(jsonlPruneXhrResponse, { * * */ -function jsonlPruneFetchResponseFn( - rawPrunePaths = '', - rawNeedlePaths = '' +function jsonlPruneFetchResponse( + jsonq = '' ) { const safe = safeSelf(); - const logPrefix = safe.makeLogPrefix('jsonl-prune-fetch-response', rawPrunePaths, rawNeedlePaths); + const logPrefix = safe.makeLogPrefix('jsonl-prune-fetch-response', jsonq); + const jsonp = JSONPath.create(jsonq); const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const propNeedles = parsePropertiesToMatchFn(extraArgs.propsToMatch, 'url'); - const logall = rawPrunePaths === ''; + const logall = jsonq === ''; const applyHandler = function(target, thisArg, args) { const fetchPromise = Reflect.apply(target, thisArg, args); if ( propNeedles.size !== 0 ) { @@ -221,7 +207,7 @@ function jsonlPruneFetchResponseFn( safe.uboLog(logPrefix, textBefore); return responseBefore; } - const textAfter = jsonlPruneFn(textBefore, rawPrunePaths, rawNeedlePaths); + const textAfter = jsonlPruneFn(jsonp, textBefore); if ( textAfter === textBefore ) { return responseBefore; } safe.uboLog(logPrefix, 'Pruned'); const responseAfter = new Response(textAfter, { @@ -249,9 +235,10 @@ function jsonlPruneFetchResponseFn( apply: applyHandler }); } -registerScriptlet(jsonlPruneFetchResponseFn, { - name: 'jsonl-prune-fetch-response.fn', +registerScriptlet(jsonlPruneFetchResponse, { + name: 'jsonl-prune-fetch-response.js', dependencies: [ + JSONPath, jsonlPruneFn, matchObjectPropertiesFn, parsePropertiesToMatchFn, @@ -260,15 +247,3 @@ registerScriptlet(jsonlPruneFetchResponseFn, { }); /******************************************************************************/ - -function jsonlPruneFetchResponse(...args) { - jsonlPruneFetchResponseFn(...args); -} -registerScriptlet(jsonlPruneFetchResponse, { - name: 'jsonl-prune-fetch-response.js', - dependencies: [ - jsonlPruneFetchResponseFn, - ], -}); - -/******************************************************************************/ diff --git a/src/js/resources/parse-replace.js b/src/js/resources/parse-replace.js index e7fd488e9..604644f47 100644 --- a/src/js/resources/parse-replace.js +++ b/src/js/resources/parse-replace.js @@ -20,14 +20,14 @@ */ -import { createArglistParser } from './shared.js'; +import { ArglistParser } from './shared.js'; import { registerScriptlet } from './base.js'; /******************************************************************************/ export function parseReplaceFn(s) { if ( s.charCodeAt(0) !== 0x2F /* / */ ) { return; } - const parser = createArglistParser('/'); + const parser = new ArglistParser('/'); parser.nextArg(s, 1); let pattern = s.slice(parser.argBeg, parser.argEnd); if ( parser.transform ) { @@ -49,6 +49,6 @@ export function parseReplaceFn(s) { registerScriptlet(parseReplaceFn, { name: 'parse-replace.fn', dependencies: [ - createArglistParser, + ArglistParser, ], }); diff --git a/src/js/resources/shared.js b/src/js/resources/shared.js index 9a38fca48..01a9de718 100644 --- a/src/js/resources/shared.js +++ b/src/js/resources/shared.js @@ -21,24 +21,20 @@ */ // Code imported from main code base and exposed as injectable scriptlets -import { ArglistParser } from '../arglist-parser.js'; - +import { ArglistParser as __ArglistParser__ } from '../arglist-parser.js'; +import { JSONPath as __JSONPath__ } from '../jsonpath.js'; import { registerScriptlet } from './base.js'; /******************************************************************************/ +export const ArglistParser = __ArglistParser__; + registerScriptlet(ArglistParser, { name: 'arglist-parser.fn', }); -/******************************************************************************/ +export const JSONPath = __JSONPath__; -export function createArglistParser(...args) { - return new ArglistParser(...args); -} -registerScriptlet(createArglistParser, { - name: 'create-arglist-parser.fn', - dependencies: [ - ArglistParser, - ], +registerScriptlet(JSONPath, { + name: 'jsonpath.fn', }); diff --git a/src/js/scriptlet-filtering-core.js b/src/js/scriptlet-filtering-core.js index 89b06ca05..b6d7c0be2 100644 --- a/src/js/scriptlet-filtering-core.js +++ b/src/js/scriptlet-filtering-core.js @@ -35,6 +35,14 @@ const $isolatedWorldMap = new Map(); /******************************************************************************/ +// For debugging convenience: all the top function calls will appear +// at the bottom of a generated content script +const codeSorter = (a, b) => { + if ( a.startsWith('try') ) { return 1; } + if ( b.startsWith('try') ) { return -1; } + return 0; +}; + const normalizeRawFilter = (parser, sourceIsTrusted = false) => { const args = parser.getScriptletArgs(); if ( args.length !== 0 ) { @@ -59,6 +67,7 @@ const lookupScriptlet = (rawToken, mainMap, isolatedMap, debug = false) => { const details = reng.contentFromName(token, 'text/javascript'); if ( details === undefined ) { return; } const targetWorldMap = details.world !== 'ISOLATED' ? mainMap : isolatedMap; + targetWorldMap.set(token, details.js); const content = patchScriptlet(details.js, args.slice(1)); const dependencies = details.dependencies || []; while ( dependencies.length !== 0 ) { @@ -73,22 +82,22 @@ const lookupScriptlet = (rawToken, mainMap, isolatedMap, debug = false) => { } targetWorldMap.set(rawToken, [ 'try {', - '// >>>> scriptlet start', - content, - '// <<<< scriptlet end', + `\t${content}`, '} catch (e) {', - debug ? 'console.error(e);' : '', + debug ? '\tconsole.error(e);' : '', '}', ].join('\n')); }; // Fill-in scriptlet argument placeholders. const patchScriptlet = (content, arglist) => { - if ( content.startsWith('function') && content.endsWith('}') ) { - content = `(${content})({{args}});`; - } - for ( let i = 0; i < arglist.length; i++ ) { - content = content.replace(`{{${i+1}}}`, arglist[i]); + const match = /^function\s+([^(\s]+)\s*\(/.exec(content); + if ( match ) { + content = `${match[1]}({{args}});`; + } else { + for ( let i = 0; i < arglist.length; i++ ) { + content = content.replace(`{{${i+1}}}`, arglist[i]); + } } return content.replace('{{args}}', JSON.stringify(arglist).slice(1,-1).replace(/\$/g, '$$$') @@ -249,11 +258,13 @@ export class ScriptletFilteringEngine { for ( const js of $mainWorldMap.values() ) { mainWorldCode.push(js); } + mainWorldCode.sort(codeSorter); const isolatedWorldCode = []; for ( const js of $isolatedWorldMap.values() ) { isolatedWorldCode.push(js); } + isolatedWorldCode.sort(codeSorter); const scriptletDetails = { mainWorld: mainWorldCode.join('\n\n'), diff --git a/tools/make-nodejs.sh b/tools/make-nodejs.sh index ed8db6cbb..fb0a7a65f 100755 --- a/tools/make-nodejs.sh +++ b/tools/make-nodejs.sh @@ -6,41 +6,42 @@ set -e DES=$1 -mkdir -p $DES/js -cp src/js/arglist-parser.js $DES/js -cp src/js/base64-custom.js $DES/js -cp src/js/biditrie.js $DES/js -cp src/js/dynamic-net-filtering.js $DES/js -cp src/js/filtering-context.js $DES/js -cp src/js/hnswitches.js $DES/js -cp src/js/hntrie.js $DES/js -cp src/js/redirect-resources.js $DES/js -cp src/js/s14e-serializer.js $DES/js -cp src/js/static-dnr-filtering.js $DES/js -cp src/js/static-filtering-parser.js $DES/js -cp src/js/static-net-filtering.js $DES/js -cp src/js/static-filtering-io.js $DES/js -cp src/js/tasks.js $DES/js -cp src/js/text-utils.js $DES/js -cp src/js/urlskip.js $DES/js -cp src/js/uri-utils.js $DES/js -cp src/js/url-net-filtering.js $DES/js +mkdir -p "$DES/js" +cp src/js/arglist-parser.js "$DES/js" +cp src/js/base64-custom.js "$DES/js" +cp src/js/biditrie.js "$DES/js" +cp src/js/dynamic-net-filtering.js "$DES/js" +cp src/js/filtering-context.js "$DES/js" +cp src/js/hnswitches.js "$DES/js" +cp src/js/hntrie.js "$DES/js" +cp src/js/jsonpath.js "$DES/js" +cp src/js/redirect-resources.js "$DES/js" +cp src/js/s14e-serializer.js "$DES/js" +cp src/js/static-dnr-filtering.js "$DES/js" +cp src/js/static-filtering-parser.js "$DES/js" +cp src/js/static-net-filtering.js "$DES/js" +cp src/js/static-filtering-io.js "$DES/js" +cp src/js/tasks.js "$DES/js" +cp src/js/text-utils.js "$DES/js" +cp src/js/urlskip.js "$DES/js" +cp src/js/uri-utils.js "$DES/js" +cp src/js/url-net-filtering.js "$DES/js" -mkdir -p $DES/lib -cp -R src/lib/csstree $DES/lib/ -cp -R src/lib/punycode.js $DES/lib/ -cp -R src/lib/regexanalyzer $DES/lib/ -cp -R src/lib/publicsuffixlist $DES/lib/ +mkdir -p "$DES/lib/" +cp -R src/lib/csstree "$DES/lib/" +cp -R src/lib/punycode.js "$DES/lib/" +cp -R src/lib/regexanalyzer "$DES/lib/" +cp -R src/lib/publicsuffixlist "$DES/lib/" # Convert wasm modules into json arrays -mkdir -p $DES/js/wasm -cp src/js/wasm/* $DES/js/wasm/ +mkdir -p "$DES/js/wasm" +cp src/js/wasm/* "$DES/js/wasm" node -pe "JSON.stringify(Array.from(fs.readFileSync('src/js/wasm/hntrie.wasm')))" \ - > $DES/js/wasm/hntrie.wasm.json + > "$DES/js/wasm/hntrie.wasm.json" node -pe "JSON.stringify(Array.from(fs.readFileSync('src/js/wasm/biditrie.wasm')))" \ - > $DES/js/wasm/biditrie.wasm.json + > "$DES/js/wasm/biditrie.wasm.json" node -pe "JSON.stringify(Array.from(fs.readFileSync('src/lib/publicsuffixlist/wasm/publicsuffixlist.wasm')))" \ - > $DES/lib/publicsuffixlist/wasm/publicsuffixlist.wasm.json + > "$DES/lib/publicsuffixlist/wasm/publicsuffixlist.wasm.json" -cp platform/nodejs/*.js $DES/ -cp LICENSE.txt $DES/ +cp platform/nodejs/*.js "$DES/" +cp LICENSE.txt "$DES/"