-
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.
- Loading branch information
Showing
4 changed files
with
424 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import { | ||
hit, | ||
matchStackTrace, | ||
getWildcardPropertyInChain, | ||
logMessage, | ||
// following helpers are needed for helpers above | ||
toRegExp, | ||
getNativeRegexpTest, | ||
shouldAbortInlineOrInjectedScript, | ||
} from '../helpers/index'; | ||
|
||
/** | ||
* @scriptlet evaldata-prune | ||
* | ||
* @description | ||
* Removes specified properties from the result of calling eval (if payloads contains `Object`) and returns the caller. | ||
* | ||
* ### 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"') | ||
* ``` | ||
* | ||
*/ | ||
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(/ +/) | ||
: []; | ||
|
||
function isPruningNeeded(root) { | ||
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; | ||
} | ||
} | ||
|
||
for (let i = 0; i < requiredPaths.length; i += 1) { | ||
const requiredPath = requiredPaths[i]; | ||
const lastNestedPropName = requiredPath.split('.').pop(); | ||
|
||
const wildcardSymbols = ['.*.', '*.', '.*', '.[].', '[].', '.[]']; | ||
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; | ||
} | ||
|
||
const jsonPruner = (root) => { | ||
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(root) === 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; | ||
}; | ||
|
||
const evalWrapper = (target, thisArg, args) => { | ||
let data = Reflect.apply(target, thisArg, args); | ||
if (typeof data === 'object') { | ||
data = jsonPruner(data, propsToRemove, requiredInitialProps); | ||
} | ||
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, | ||
// following helpers are needed for helpers above | ||
toRegExp, | ||
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.