diff options
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/object-prune.js')
-rw-r--r-- | data/extensions/uBlock0@raymondhill.net/js/resources/object-prune.js | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/object-prune.js b/data/extensions/uBlock0@raymondhill.net/js/resources/object-prune.js new file mode 100644 index 0000000..50256fe --- /dev/null +++ b/data/extensions/uBlock0@raymondhill.net/js/resources/object-prune.js @@ -0,0 +1,272 @@ +/******************************************************************************* + + uBlock Origin - a comprehensive, efficient content blocker + Copyright (C) 2019-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 + +*/ + +import { matchesStackTraceFn } from './stack-trace.js'; +import { proxyApplyFn } from './proxy-apply.js'; +import { registerScriptlet } from './base.js'; +import { safeSelf } from './safe-self.js'; + +/******************************************************************************/ + +function objectFindOwnerFn( + root, + path, + prune = false +) { + const safe = safeSelf(); + let owner = root; + let chain = path; + for (;;) { + if ( typeof owner !== 'object' || owner === null ) { return false; } + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + if ( prune === false ) { + return safe.Object_hasOwn(owner, chain); + } + let modified = false; + if ( chain === '*' ) { + for ( const key in owner ) { + if ( safe.Object_hasOwn(owner, key) === false ) { continue; } + delete owner[key]; + modified = true; + } + } else if ( safe.Object_hasOwn(owner, chain) ) { + delete owner[chain]; + modified = true; + } + return modified; + } + const prop = chain.slice(0, pos); + const next = chain.slice(pos + 1); + let found = false; + if ( prop === '[-]' && Array.isArray(owner) ) { + let i = owner.length; + while ( i-- ) { + if ( objectFindOwnerFn(owner[i], next) === false ) { continue; } + owner.splice(i, 1); + found = true; + } + return found; + } + if ( prop === '{-}' && owner instanceof Object ) { + for ( const key of Object.keys(owner) ) { + if ( objectFindOwnerFn(owner[key], next) === false ) { continue; } + delete owner[key]; + found = true; + } + return found; + } + if ( + prop === '[]' && Array.isArray(owner) || + prop === '{}' && owner instanceof Object || + prop === '*' && owner instanceof Object + ) { + for ( const key of Object.keys(owner) ) { + if (objectFindOwnerFn(owner[key], next, prune) === false ) { continue; } + found = true; + } + return found; + } + if ( safe.Object_hasOwn(owner, prop) === false ) { return false; } + owner = owner[prop]; + chain = chain.slice(pos + 1); + } +} +registerScriptlet(objectFindOwnerFn, { + name: 'object-find-owner.fn', + dependencies: [ + safeSelf, + ], +}); + +/******************************************************************************/ + +// When no "prune paths" argument is provided, the scriptlet is +// used for logging purpose and the "needle paths" argument is +// used to filter logging output. +// +// https://github.com/uBlockOrigin/uBlock-issues/issues/1545 +// - Add support for "remove everything if needle matches" case + +export function objectPruneFn( + obj, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails = { matchAll: true }, + extraArgs = {} +) { + if ( typeof rawPrunePaths !== 'string' ) { return; } + const safe = safeSelf(); + const prunePaths = rawPrunePaths !== '' + ? safe.String_split.call(rawPrunePaths, / +/) + : []; + const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' + ? safe.String_split.call(rawNeedlePaths, / +/) + : []; + if ( stackNeedleDetails.matchAll !== true ) { + if ( matchesStackTraceFn(stackNeedleDetails, extraArgs.logstack) === false ) { + return; + } + } + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; + } + if ( prunePaths.length === 0 ) { return; } + let outcome = 'nomatch'; + if ( objectPruneFn.mustProcess(obj, needlePaths) ) { + for ( const path of prunePaths ) { + if ( objectFindOwnerFn(obj, path, true) ) { + outcome = 'match'; + } + } + } + if ( outcome === 'match' ) { return obj; } +} +registerScriptlet(objectPruneFn, { + name: 'object-prune.fn', + dependencies: [ + matchesStackTraceFn, + objectFindOwnerFn, + safeSelf, + ], +}); + +/******************************************************************************/ + +function trustedPruneInboundObject( + entryPoint = '', + argPos = '', + rawPrunePaths = '', + rawNeedlePaths = '' +) { + if ( entryPoint === '' ) { return; } + let context = globalThis; + let prop = entryPoint; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + if ( typeof context[prop] !== 'function' ) { return; } + const argIndex = parseInt(argPos); + if ( isNaN(argIndex) ) { return; } + if ( argIndex < 1 ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); + const needlePaths = []; + if ( rawPrunePaths !== '' ) { + needlePaths.push(...safe.String_split.call(rawPrunePaths, / +/)); + } + if ( rawNeedlePaths !== '' ) { + needlePaths.push(...safe.String_split.call(rawNeedlePaths, / +/)); + } + const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); + const mustProcess = root => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; + context[prop] = new Proxy(context[prop], { + apply: function(target, thisArg, args) { + const targetArg = argIndex <= args.length + ? args[argIndex-1] + : undefined; + if ( targetArg instanceof Object && mustProcess(targetArg) ) { + let objBefore = targetArg; + if ( extraArgs.dontOverwrite ) { + try { + objBefore = safe.JSON_parse(safe.JSON_stringify(targetArg)); + } catch { + objBefore = undefined; + } + } + if ( objBefore !== undefined ) { + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedle, + extraArgs + ); + args[argIndex-1] = objAfter || objBefore; + } + } + return Reflect.apply(target, thisArg, args); + }, + }); +} +registerScriptlet(trustedPruneInboundObject, { + name: 'trusted-prune-inbound-object.js', + requiresTrust: true, + dependencies: [ + objectFindOwnerFn, + objectPruneFn, + safeSelf, + ], +}); + +/******************************************************************************/ + +function trustedPruneOutboundObject( + propChain = '', + rawPrunePaths = '', + rawNeedlePaths = '' +) { + if ( propChain === '' ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + proxyApplyFn(propChain, function(context) { + const objBefore = context.reflect(); + if ( objBefore instanceof Object === false ) { return objBefore; } + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + { matchAll: true }, + extraArgs + ); + return objAfter || objBefore; + }); +} +registerScriptlet(trustedPruneOutboundObject, { + name: 'trusted-prune-outbound-object.js', + requiresTrust: true, + dependencies: [ + objectPruneFn, + proxyApplyFn, + safeSelf, + ], +}); + +/******************************************************************************/ |