-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AG-22709 Add evaldata-prune scriptlet. #322
Squashed commit of the following: commit 07d47b6 Merge: 969881b 7e485bb Author: Adam Wróblewski <adam@adguard.com> Date: Tue Jun 6 13:04:49 2023 +0200 Merge branch 'master' into feature/AG-22709 commit 969881b Author: Adam Wróblewski <adam@adguard.com> Date: Mon Jun 5 16:02:24 2023 +0200 Fix JSDoc return value in description Add related uBO scriptlet to description commit 8911c2e Author: Adam Wróblewski <adam@adguard.com> Date: Thu Jun 1 09:09:22 2023 +0200 Move jsonPruner and isPruningNeeded to helpers Fix description commit 318084d Author: Adam Wróblewski <adam@adguard.com> Date: Wed May 31 17:50:55 2023 +0200 Move toRegExp before the comment Add @added unreleased information commit 27e2c8b Merge: 5990342 fdade01 Author: Adam Wróblewski <adam@adguard.com> Date: Wed May 31 17:36:01 2023 +0200 Merge branch 'master' into feature/AG-22709 commit 5990342 Author: Adam Wróblewski <adam@adguard.com> Date: Wed May 31 16:58:27 2023 +0200 Add evaldata-prune scriptlet
- Loading branch information
Showing
7 changed files
with
465 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { hit } from './hit'; | ||
import { getWildcardPropertyInChain } from './get-wildcard-property-in-chain'; | ||
import { logMessage } from './log-message'; | ||
import { toRegExp } from './string-utils'; | ||
|
||
/** | ||
* Checks if prunning is required | ||
* | ||
* @param {Object} source required, scriptlet properties | ||
* @param {Object} root object which should be pruned or logged | ||
* @param {Array} prunePaths array with string of space-separated properties to remove | ||
* @param {Array} requiredPaths array with string of space-separated properties | ||
* which must be all present for the pruning to occur | ||
* @returns {boolean|undefined} true if prunning is required | ||
*/ | ||
export function isPruningNeeded(source, root, prunePaths, requiredPaths) { | ||
if (!root) { | ||
return false; | ||
} | ||
|
||
let shouldProcess; | ||
|
||
// Only log hostname and matched JSON payload if only second argument is present | ||
if (prunePaths.length === 0 && requiredPaths.length > 0) { | ||
const rootString = JSON.stringify(root); | ||
const matchRegex = toRegExp(requiredPaths.join('')); | ||
const shouldLog = matchRegex.test(rootString); | ||
if (shouldLog) { | ||
logMessage(source, `${window.location.hostname}\n${JSON.stringify(root, null, 2)}`, true); | ||
if (root && typeof root === 'object') { | ||
logMessage(source, root, true, false); | ||
} | ||
shouldProcess = false; | ||
return shouldProcess; | ||
} | ||
} | ||
|
||
const wildcardSymbols = ['.*.', '*.', '.*', '.[].', '[].', '.[]']; | ||
|
||
for (let i = 0; i < requiredPaths.length; i += 1) { | ||
const requiredPath = requiredPaths[i]; | ||
const lastNestedPropName = requiredPath.split('.').pop(); | ||
const hasWildcard = wildcardSymbols.some((symbol) => requiredPath.includes(symbol)); | ||
|
||
// if the path has wildcard, getPropertyInChain should 'look through' chain props | ||
const details = getWildcardPropertyInChain(root, requiredPath, hasWildcard); | ||
|
||
// start value of 'shouldProcess' due to checking below | ||
shouldProcess = !hasWildcard; | ||
|
||
for (let i = 0; i < details.length; i += 1) { | ||
if (hasWildcard) { | ||
// if there is a wildcard, | ||
// at least one (||) of props chain should be present in object | ||
shouldProcess = !(details[i].base[lastNestedPropName] === undefined) | ||
|| shouldProcess; | ||
} else { | ||
// otherwise each one (&&) of them should be there | ||
shouldProcess = !(details[i].base[lastNestedPropName] === undefined) | ||
&& shouldProcess; | ||
} | ||
} | ||
} | ||
|
||
return shouldProcess; | ||
} | ||
|
||
/** | ||
* Prunes properties of 'root' object | ||
* | ||
* @param {Object} source required, scriptlet properties | ||
* @param {Object} root object which should be pruned or logged | ||
* @param {Array} prunePaths array with string of space-separated properties to remove | ||
* @param {Array} requiredPaths array with string of space-separated properties | ||
* which must be all present for the pruning to occur | ||
* @returns {Object} pruned root | ||
*/ | ||
export const jsonPruner = (source, root, prunePaths, requiredPaths) => { | ||
if (prunePaths.length === 0 && requiredPaths.length === 0) { | ||
logMessage(source, `${window.location.hostname}\n${JSON.stringify(root, null, 2)}`, true); | ||
if (root && typeof root === 'object') { | ||
logMessage(source, root, true, false); | ||
} | ||
return root; | ||
} | ||
|
||
try { | ||
if (isPruningNeeded(source, root, prunePaths, requiredPaths) === false) { | ||
return root; | ||
} | ||
|
||
// if pruning is needed, we check every input pathToRemove | ||
// and delete it if root has it | ||
prunePaths.forEach((path) => { | ||
const ownerObjArr = getWildcardPropertyInChain(root, path, true); | ||
ownerObjArr.forEach((ownerObj) => { | ||
if (ownerObj !== undefined && ownerObj.base) { | ||
delete ownerObj.base[ownerObj.prop]; | ||
hit(source); | ||
} | ||
}); | ||
}); | ||
} catch (e) { | ||
logMessage(source, e); | ||
} | ||
|
||
return root; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import { | ||
hit, | ||
matchStackTrace, | ||
getWildcardPropertyInChain, | ||
logMessage, | ||
toRegExp, | ||
isPruningNeeded, | ||
jsonPruner, | ||
// following helpers are needed for helpers above | ||
getNativeRegexpTest, | ||
shouldAbortInlineOrInjectedScript, | ||
} from '../helpers/index'; | ||
|
||
/* eslint-disable max-len */ | ||
/** | ||
* @scriptlet evaldata-prune | ||
* | ||
* @description | ||
* Removes specified properties from the result of calling eval (if payloads contains `Object`) and returns to the caller. | ||
* | ||
* Related UBO scriptlet: | ||
* https://github.com/gorhill/uBlock/commit/c8de9041917b61035171e454df886706f27fc4f3 | ||
* | ||
* ### Syntax | ||
* | ||
* ```text | ||
* example.org#%#//scriptlet('evaldata-prune'[, propsToRemove [, obligatoryProps [, stack]]]) | ||
* ``` | ||
* | ||
* - `propsToRemove` — optional, string of space-separated properties to remove | ||
* - `obligatoryProps` — optional, string of space-separated properties | ||
* which must be all present for the pruning to occur | ||
* - `stack` — optional, string or regular expression that must match the current function call stack trace; | ||
* if regular expression is invalid it will be skipped | ||
* | ||
* > Note please that you can use wildcard `*` for chain property name, | ||
* > e.g. `ad.*.src` instead of `ad.0.src ad.1.src ad.2.src`. | ||
* | ||
* ### Examples | ||
* | ||
* 1. Removes property `example` from the payload of the eval call | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', 'example') | ||
* ``` | ||
* | ||
* For instance, the following call will return `{ one: 1}` | ||
* | ||
* ```html | ||
* eval({ one: 1, example: true }) | ||
* ``` | ||
* | ||
* 2. If there are no specified properties in the payload of eval call, pruning will NOT occur | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', 'one', 'obligatoryProp') | ||
* ``` | ||
* | ||
* For instance, the following call will return `{ one: 1, two: 2}` | ||
* | ||
* ```html | ||
* JSON.parse('{"one":1,"two":2}') | ||
* ``` | ||
* | ||
* 3. A property in a list of properties can be a chain of properties | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', 'a.b', 'ads.url.first') | ||
* ``` | ||
* | ||
* 4. Removes property `content.ad` from the payload of eval call if its error stack trace contains `test.js` | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', 'content.ad', '', 'test.js') | ||
* ``` | ||
* | ||
* 5. A property in a list of properties can be a chain of properties with wildcard in it | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', 'content.*.media.src', 'content.*.media.ad') | ||
* ``` | ||
* | ||
* 6. Call with no arguments will log the current hostname and object payload at the console | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune') | ||
* ``` | ||
* | ||
* 7. Call with only second argument will log the current hostname and matched object payload at the console | ||
* | ||
* ```adblock | ||
* example.org#%#//scriptlet('evaldata-prune', '', '"id":"117458"') | ||
* ``` | ||
* | ||
* @added unreleased. | ||
*/ | ||
/* eslint-enable max-len */ | ||
export function evalDataPrune(source, propsToRemove, requiredInitialProps, stack) { | ||
if (!!stack && !matchStackTrace(stack, new Error().stack)) { | ||
return; | ||
} | ||
const prunePaths = propsToRemove !== undefined && propsToRemove !== '' | ||
? propsToRemove.split(/ +/) | ||
: []; | ||
const requiredPaths = requiredInitialProps !== undefined && requiredInitialProps !== '' | ||
? requiredInitialProps.split(/ +/) | ||
: []; | ||
|
||
const evalWrapper = (target, thisArg, args) => { | ||
let data = Reflect.apply(target, thisArg, args); | ||
if (typeof data === 'object') { | ||
data = jsonPruner(source, data, prunePaths, requiredPaths); | ||
} | ||
return data; | ||
}; | ||
|
||
const evalHandler = { | ||
apply: evalWrapper, | ||
}; | ||
// eslint-disable-next-line no-eval | ||
window.eval = new Proxy(window.eval, evalHandler); | ||
} | ||
|
||
evalDataPrune.names = [ | ||
'evaldata-prune', | ||
// aliases are needed for matching the related scriptlet converted into our syntax | ||
'evaldata-prune.js', | ||
'ubo-evaldata-prune.js', | ||
'ubo-evaldata-prune', | ||
]; | ||
|
||
evalDataPrune.injections = [ | ||
hit, | ||
matchStackTrace, | ||
getWildcardPropertyInChain, | ||
logMessage, | ||
toRegExp, | ||
isPruningNeeded, | ||
jsonPruner, | ||
// following helpers are needed for helpers above | ||
getNativeRegexpTest, | ||
shouldAbortInlineOrInjectedScript, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.