Rename to hkt.sh
This commit is contained in:
583
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/index.js
generated
vendored
Normal file
583
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/index.js
generated
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
/**
|
||||
* A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces.
|
||||
*
|
||||
* Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser).
|
||||
*
|
||||
* Note: If for whatever reason you need to use this outside of `puppeteer-extra`:
|
||||
* Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context.
|
||||
*
|
||||
* Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities.
|
||||
*
|
||||
*/
|
||||
const utils = {}
|
||||
|
||||
utils.init = () => {
|
||||
utils.preloadCache()
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw.
|
||||
*
|
||||
* The presence of a JS Proxy can be revealed as it shows up in error stack traces.
|
||||
*
|
||||
* @param {object} handler - The JS Proxy handler to wrap
|
||||
*/
|
||||
utils.stripProxyFromErrors = (handler = {}) => {
|
||||
const newHandler = {
|
||||
setPrototypeOf: function (target, proto) {
|
||||
if (proto === null)
|
||||
throw new TypeError('Cannot convert object to primitive value')
|
||||
if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {
|
||||
throw new TypeError('Cyclic __proto__ value')
|
||||
}
|
||||
return Reflect.setPrototypeOf(target, proto)
|
||||
}
|
||||
}
|
||||
// We wrap each trap in the handler in a try/catch and modify the error stack if they throw
|
||||
const traps = Object.getOwnPropertyNames(handler)
|
||||
traps.forEach(trap => {
|
||||
newHandler[trap] = function () {
|
||||
try {
|
||||
// Forward the call to the defined proxy handler
|
||||
return handler[trap].apply(this, arguments || [])
|
||||
} catch (err) {
|
||||
// Stack traces differ per browser, we only support chromium based ones currently
|
||||
if (!err || !err.stack || !err.stack.includes(`at `)) {
|
||||
throw err
|
||||
}
|
||||
|
||||
// When something throws within one of our traps the Proxy will show up in error stacks
|
||||
// An earlier implementation of this code would simply strip lines with a blacklist,
|
||||
// but it makes sense to be more surgical here and only remove lines related to our Proxy.
|
||||
// We try to use a known "anchor" line for that and strip it with everything above it.
|
||||
// If the anchor line cannot be found for some reason we fall back to our blacklist approach.
|
||||
|
||||
const stripWithBlacklist = (stack, stripFirstLine = true) => {
|
||||
const blacklist = [
|
||||
`at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply
|
||||
`at Object.${trap} `, // e.g. Object.get or Object.apply
|
||||
`at Object.newHandler.<computed> [as ${trap}] ` // caused by this very wrapper :-)
|
||||
]
|
||||
return (
|
||||
err.stack
|
||||
.split('\n')
|
||||
// Always remove the first (file) line in the stack (guaranteed to be our proxy)
|
||||
.filter((line, index) => !(index === 1 && stripFirstLine))
|
||||
// Check if the line starts with one of our blacklisted strings
|
||||
.filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))
|
||||
.join('\n')
|
||||
)
|
||||
}
|
||||
|
||||
const stripWithAnchor = (stack, anchor) => {
|
||||
const stackArr = stack.split('\n')
|
||||
anchor = anchor || `at Object.newHandler.<computed> [as ${trap}] ` // Known first Proxy line in chromium
|
||||
const anchorIndex = stackArr.findIndex(line =>
|
||||
line.trim().startsWith(anchor)
|
||||
)
|
||||
if (anchorIndex === -1) {
|
||||
return false // 404, anchor not found
|
||||
}
|
||||
// Strip everything from the top until we reach the anchor line
|
||||
// Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)
|
||||
stackArr.splice(1, anchorIndex)
|
||||
return stackArr.join('\n')
|
||||
}
|
||||
|
||||
// Special cases due to our nested toString proxies
|
||||
err.stack = err.stack.replace(
|
||||
'at Object.toString (',
|
||||
'at Function.toString ('
|
||||
)
|
||||
if ((err.stack || '').includes('at Function.toString (')) {
|
||||
err.stack = stripWithBlacklist(err.stack, false)
|
||||
throw err
|
||||
}
|
||||
|
||||
// Try using the anchor method, fallback to blacklist if necessary
|
||||
err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)
|
||||
|
||||
throw err // Re-throw our now sanitized error
|
||||
}
|
||||
}
|
||||
})
|
||||
return newHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip error lines from stack traces until (and including) a known line the stack.
|
||||
*
|
||||
* @param {object} err - The error to sanitize
|
||||
* @param {string} anchor - The string the anchor line starts with
|
||||
*/
|
||||
utils.stripErrorWithAnchor = (err, anchor) => {
|
||||
const stackArr = err.stack.split('\n')
|
||||
const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))
|
||||
if (anchorIndex === -1) {
|
||||
return err // 404, anchor not found
|
||||
}
|
||||
// Strip everything from the top until we reach the anchor line (remove anchor line as well)
|
||||
// Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`)
|
||||
stackArr.splice(1, anchorIndex)
|
||||
err.stack = stackArr.join('\n')
|
||||
return err
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the property of an object in a stealthy way.
|
||||
*
|
||||
* Note: You also want to work on the prototype of an object most often,
|
||||
* as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)).
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
|
||||
*
|
||||
* @example
|
||||
* replaceProperty(WebGLRenderingContext.prototype, 'getParameter', { value: "alice" })
|
||||
* // or
|
||||
* replaceProperty(Object.getPrototypeOf(navigator), 'languages', { get: () => ['en-US', 'en'] })
|
||||
*
|
||||
* @param {object} obj - The object which has the property to replace
|
||||
* @param {string} propName - The property name to replace
|
||||
* @param {object} descriptorOverrides - e.g. { value: "alice" }
|
||||
*/
|
||||
utils.replaceProperty = (obj, propName, descriptorOverrides = {}) => {
|
||||
return Object.defineProperty(obj, propName, {
|
||||
// Copy over the existing descriptors (writable, enumerable, configurable, etc)
|
||||
...(Object.getOwnPropertyDescriptor(obj, propName) || {}),
|
||||
// Add our overrides (e.g. value, get())
|
||||
...descriptorOverrides
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a cache of function copies and data.
|
||||
*
|
||||
* For a determined enough observer it would be possible to overwrite and sniff usage of functions
|
||||
* we use in our internal Proxies, to combat that we use a cached copy of those functions.
|
||||
*
|
||||
* Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,
|
||||
* by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).
|
||||
*
|
||||
* This is evaluated once per execution context (e.g. window)
|
||||
*/
|
||||
utils.preloadCache = () => {
|
||||
if (utils.cache) {
|
||||
return
|
||||
}
|
||||
utils.cache = {
|
||||
// Used in our proxies
|
||||
Reflect: {
|
||||
get: Reflect.get.bind(Reflect),
|
||||
apply: Reflect.apply.bind(Reflect)
|
||||
},
|
||||
// Used in `makeNativeString`
|
||||
nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to generate a cross-browser `toString` result representing native code.
|
||||
*
|
||||
* There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings.
|
||||
* To future-proof this we use an existing native toString result as the basis.
|
||||
*
|
||||
* The only advantage we have over the other team is that our JS runs first, hence we cache the result
|
||||
* of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it.
|
||||
*
|
||||
* @example
|
||||
* makeNativeString('foobar') // => `function foobar() { [native code] }`
|
||||
*
|
||||
* @param {string} [name] - Optional function name
|
||||
*/
|
||||
utils.makeNativeString = (name = '') => {
|
||||
return utils.cache.nativeToStringStr.replace('toString', name || '')
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to modify the `toString()` result of the provided object.
|
||||
*
|
||||
* Note: Use `utils.redirectToString` instead when possible.
|
||||
*
|
||||
* There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object.
|
||||
* If no string is provided we will generate a `[native code]` thing based on the name of the property object.
|
||||
*
|
||||
* @example
|
||||
* patchToString(WebGLRenderingContext.prototype.getParameter, 'function getParameter() { [native code] }')
|
||||
*
|
||||
* @param {object} obj - The object for which to modify the `toString()` representation
|
||||
* @param {string} str - Optional string used as a return value
|
||||
*/
|
||||
utils.patchToString = (obj, str = '') => {
|
||||
const handler = {
|
||||
apply: function (target, ctx) {
|
||||
// This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`
|
||||
if (ctx === Function.prototype.toString) {
|
||||
return utils.makeNativeString('toString')
|
||||
}
|
||||
// `toString` targeted at our proxied Object detected
|
||||
if (ctx === obj) {
|
||||
// We either return the optional string verbatim or derive the most desired result automatically
|
||||
return str || utils.makeNativeString(obj.name)
|
||||
}
|
||||
// Check if the toString protype of the context is the same as the global prototype,
|
||||
// if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case
|
||||
const hasSameProto = Object.getPrototypeOf(
|
||||
Function.prototype.toString
|
||||
).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins
|
||||
if (!hasSameProto) {
|
||||
// Pass the call on to the local Function.prototype.toString instead
|
||||
return ctx.toString()
|
||||
}
|
||||
return target.call(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
const toStringProxy = new Proxy(
|
||||
Function.prototype.toString,
|
||||
utils.stripProxyFromErrors(handler)
|
||||
)
|
||||
utils.replaceProperty(Function.prototype, 'toString', {
|
||||
value: toStringProxy
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Make all nested functions of an object native.
|
||||
*
|
||||
* @param {object} obj
|
||||
*/
|
||||
utils.patchToStringNested = (obj = {}) => {
|
||||
return utils.execRecursively(obj, ['function'], utils.patchToString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect toString requests from one object to another.
|
||||
*
|
||||
* @param {object} proxyObj - The object that toString will be called on
|
||||
* @param {object} originalObj - The object which toString result we wan to return
|
||||
*/
|
||||
utils.redirectToString = (proxyObj, originalObj) => {
|
||||
const handler = {
|
||||
apply: function (target, ctx) {
|
||||
// This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""`
|
||||
if (ctx === Function.prototype.toString) {
|
||||
return utils.makeNativeString('toString')
|
||||
}
|
||||
|
||||
// `toString` targeted at our proxied Object detected
|
||||
if (ctx === proxyObj) {
|
||||
const fallback = () =>
|
||||
originalObj && originalObj.name
|
||||
? utils.makeNativeString(originalObj.name)
|
||||
: utils.makeNativeString(proxyObj.name)
|
||||
|
||||
// Return the toString representation of our original object if possible
|
||||
return originalObj + '' || fallback()
|
||||
}
|
||||
|
||||
if (typeof ctx === 'undefined' || ctx === null) {
|
||||
return target.call(ctx)
|
||||
}
|
||||
|
||||
// Check if the toString protype of the context is the same as the global prototype,
|
||||
// if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case
|
||||
const hasSameProto = Object.getPrototypeOf(
|
||||
Function.prototype.toString
|
||||
).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins
|
||||
if (!hasSameProto) {
|
||||
// Pass the call on to the local Function.prototype.toString instead
|
||||
return ctx.toString()
|
||||
}
|
||||
|
||||
return target.call(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
const toStringProxy = new Proxy(
|
||||
Function.prototype.toString,
|
||||
utils.stripProxyFromErrors(handler)
|
||||
)
|
||||
utils.replaceProperty(Function.prototype, 'toString', {
|
||||
value: toStringProxy
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps.
|
||||
*
|
||||
* Will stealthify these aspects (strip error stack traces, redirect toString, etc).
|
||||
* Note: This is meant to modify native Browser APIs and works best with prototype objects.
|
||||
*
|
||||
* @example
|
||||
* replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler)
|
||||
*
|
||||
* @param {object} obj - The object which has the property to replace
|
||||
* @param {string} propName - The name of the property to replace
|
||||
* @param {object} handler - The JS Proxy handler to use
|
||||
*/
|
||||
utils.replaceWithProxy = (obj, propName, handler) => {
|
||||
const originalObj = obj[propName]
|
||||
const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))
|
||||
|
||||
utils.replaceProperty(obj, propName, { value: proxyObj })
|
||||
utils.redirectToString(proxyObj, originalObj)
|
||||
|
||||
return true
|
||||
}
|
||||
/**
|
||||
* All-in-one method to replace a getter with a JS Proxy using the provided Proxy handler with traps.
|
||||
*
|
||||
* @example
|
||||
* replaceGetterWithProxy(Object.getPrototypeOf(navigator), 'vendor', proxyHandler)
|
||||
*
|
||||
* @param {object} obj - The object which has the property to replace
|
||||
* @param {string} propName - The name of the property to replace
|
||||
* @param {object} handler - The JS Proxy handler to use
|
||||
*/
|
||||
utils.replaceGetterWithProxy = (obj, propName, handler) => {
|
||||
const fn = Object.getOwnPropertyDescriptor(obj, propName).get
|
||||
const fnStr = fn.toString() // special getter function string
|
||||
const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))
|
||||
|
||||
utils.replaceProperty(obj, propName, { get: proxyObj })
|
||||
utils.patchToString(proxyObj, fnStr)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one method to replace a getter and/or setter. Functions get and set
|
||||
* of handler have one more argument that contains the native function.
|
||||
*
|
||||
* @example
|
||||
* replaceGetterSetter(HTMLIFrameElement.prototype, 'contentWindow', handler)
|
||||
*
|
||||
* @param {object} obj - The object which has the property to replace
|
||||
* @param {string} propName - The name of the property to replace
|
||||
* @param {object} handlerGetterSetter - The handler with get and/or set
|
||||
* functions
|
||||
* @see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty#description
|
||||
*/
|
||||
utils.replaceGetterSetter = (obj, propName, handlerGetterSetter) => {
|
||||
const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)
|
||||
const handler = { ...ownPropertyDescriptor }
|
||||
|
||||
if (handlerGetterSetter.get !== undefined) {
|
||||
const nativeFn = ownPropertyDescriptor.get
|
||||
handler.get = function() {
|
||||
return handlerGetterSetter.get.call(this, nativeFn.bind(this))
|
||||
}
|
||||
utils.redirectToString(handler.get, nativeFn)
|
||||
}
|
||||
|
||||
if (handlerGetterSetter.set !== undefined) {
|
||||
const nativeFn = ownPropertyDescriptor.set
|
||||
handler.set = function(newValue) {
|
||||
handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))
|
||||
}
|
||||
utils.redirectToString(handler.set, nativeFn)
|
||||
}
|
||||
|
||||
Object.defineProperty(obj, propName, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps.
|
||||
*
|
||||
* Will stealthify these aspects (strip error stack traces, redirect toString, etc).
|
||||
*
|
||||
* @example
|
||||
* mockWithProxy(chrome.runtime, 'sendMessage', function sendMessage() {}, proxyHandler)
|
||||
*
|
||||
* @param {object} obj - The object which has the property to replace
|
||||
* @param {string} propName - The name of the property to replace or create
|
||||
* @param {object} pseudoTarget - The JS Proxy target to use as a basis
|
||||
* @param {object} handler - The JS Proxy handler to use
|
||||
*/
|
||||
utils.mockWithProxy = (obj, propName, pseudoTarget, handler) => {
|
||||
const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))
|
||||
|
||||
utils.replaceProperty(obj, propName, { value: proxyObj })
|
||||
utils.patchToString(proxyObj)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* All-in-one method to create a new JS Proxy with stealth tweaks.
|
||||
*
|
||||
* This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property.
|
||||
*
|
||||
* Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc).
|
||||
*
|
||||
* @example
|
||||
* createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy
|
||||
*
|
||||
* @param {object} pseudoTarget - The JS Proxy target to use as a basis
|
||||
* @param {object} handler - The JS Proxy handler to use
|
||||
*/
|
||||
utils.createProxy = (pseudoTarget, handler) => {
|
||||
const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))
|
||||
utils.patchToString(proxyObj)
|
||||
|
||||
return proxyObj
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to split a full path to an Object into the first part and property.
|
||||
*
|
||||
* @example
|
||||
* splitObjPath(`HTMLMediaElement.prototype.canPlayType`)
|
||||
* // => {objName: "HTMLMediaElement.prototype", propName: "canPlayType"}
|
||||
*
|
||||
* @param {string} objPath - The full path to an object as dot notation string
|
||||
*/
|
||||
utils.splitObjPath = objPath => ({
|
||||
// Remove last dot entry (property) ==> `HTMLMediaElement.prototype`
|
||||
objName: objPath.split('.').slice(0, -1).join('.'),
|
||||
// Extract last dot entry ==> `canPlayType`
|
||||
propName: objPath.split('.').slice(-1)[0]
|
||||
})
|
||||
|
||||
/**
|
||||
* Convenience method to replace a property with a JS Proxy using the provided objPath.
|
||||
*
|
||||
* Supports a full path (dot notation) to the object as string here, in case that makes it easier.
|
||||
*
|
||||
* @example
|
||||
* replaceObjPathWithProxy('WebGLRenderingContext.prototype.getParameter', proxyHandler)
|
||||
*
|
||||
* @param {string} objPath - The full path to an object (dot notation string) to replace
|
||||
* @param {object} handler - The JS Proxy handler to use
|
||||
*/
|
||||
utils.replaceObjPathWithProxy = (objPath, handler) => {
|
||||
const { objName, propName } = utils.splitObjPath(objPath)
|
||||
const obj = eval(objName) // eslint-disable-line no-eval
|
||||
return utils.replaceWithProxy(obj, propName, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse nested properties of an object recursively and apply the given function on a whitelist of value types.
|
||||
*
|
||||
* @param {object} obj
|
||||
* @param {array} typeFilter - e.g. `['function']`
|
||||
* @param {Function} fn - e.g. `utils.patchToString`
|
||||
*/
|
||||
utils.execRecursively = (obj = {}, typeFilter = [], fn) => {
|
||||
function recurse(obj) {
|
||||
for (const key in obj) {
|
||||
if (obj[key] === undefined) {
|
||||
continue
|
||||
}
|
||||
if (obj[key] && typeof obj[key] === 'object') {
|
||||
recurse(obj[key])
|
||||
} else {
|
||||
if (obj[key] && typeFilter.includes(typeof obj[key])) {
|
||||
fn.call(this, obj[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
recurse(obj)
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one.
|
||||
* That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter.
|
||||
*
|
||||
* Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process.
|
||||
* This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings.
|
||||
*
|
||||
* We use this to pass down our utility functions as well as any other functions (to be able to split up code better).
|
||||
*
|
||||
* @see utils.materializeFns
|
||||
*
|
||||
* @param {object} fnObj - An object containing functions as properties
|
||||
*/
|
||||
utils.stringifyFns = (fnObj = { hello: () => 'world' }) => {
|
||||
// Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine
|
||||
// https://github.com/feross/fromentries
|
||||
function fromEntries(iterable) {
|
||||
return [...iterable].reduce((obj, [key, val]) => {
|
||||
obj[key] = val
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
return (Object.fromEntries || fromEntries)(
|
||||
Object.entries(fnObj)
|
||||
.filter(([key, value]) => typeof value === 'function')
|
||||
.map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to reverse the process of `utils.stringifyFns`.
|
||||
* Will materialize an object with stringified functions (supports classic and fat arrow functions).
|
||||
*
|
||||
* @param {object} fnStrObj - An object containing stringified functions as properties
|
||||
*/
|
||||
utils.materializeFns = (fnStrObj = { hello: "() => 'world'" }) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(fnStrObj).map(([key, value]) => {
|
||||
if (value.startsWith('function')) {
|
||||
// some trickery is needed to make oldschool functions work :-)
|
||||
return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval
|
||||
} else {
|
||||
// arrow functions just work
|
||||
return [key, eval(value)] // eslint-disable-line no-eval
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// Proxy handler templates for re-usability
|
||||
utils.makeHandler = () => ({
|
||||
// Used by simple `navigator` getter evasions
|
||||
getterValue: value => ({
|
||||
apply(target, ctx, args) {
|
||||
// Let's fetch the value first, to trigger and escalate potential errors
|
||||
// Illegal invocations like `navigator.__proto__.vendor` will throw here
|
||||
utils.cache.Reflect.apply(...arguments)
|
||||
return value
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Compare two arrays.
|
||||
*
|
||||
* @param {array} array1 - First array
|
||||
* @param {array} array2 - Second array
|
||||
*/
|
||||
utils.arrayEquals = (array1, array2) => {
|
||||
if (array1.length !== array2.length) {
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < array1.length; ++i) {
|
||||
if (array1[i] !== array2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the method return according to its arguments.
|
||||
*
|
||||
* @param {Function} fn - A function that will be cached
|
||||
*/
|
||||
utils.memoize = fn => {
|
||||
const cache = []
|
||||
return function(...args) {
|
||||
if (!cache.some(c => utils.arrayEquals(c.key, args))) {
|
||||
cache.push({ key: args, value: fn.apply(this, args) })
|
||||
}
|
||||
return cache.find(c => utils.arrayEquals(c.key, args)).value
|
||||
}
|
||||
}
|
||||
|
||||
// --
|
||||
// Stuff starting below this line is NodeJS specific.
|
||||
// --
|
||||
module.exports = utils
|
||||
709
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/index.test.js
generated
vendored
Normal file
709
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/index.test.js
generated
vendored
Normal file
@@ -0,0 +1,709 @@
|
||||
const test = require('ava')
|
||||
|
||||
const { vanillaPuppeteer } = require('../../test/util')
|
||||
|
||||
const utils = require('.')
|
||||
const withUtils = require('./withUtils')
|
||||
|
||||
/* global HTMLMediaElement WebGLRenderingContext */
|
||||
|
||||
test('splitObjPath: will do what it says', async t => {
|
||||
const { objName, propName } = utils.splitObjPath(
|
||||
'HTMLMediaElement.prototype.canPlayType'
|
||||
)
|
||||
t.is(objName, 'HTMLMediaElement.prototype')
|
||||
t.is(propName, 'canPlayType')
|
||||
})
|
||||
|
||||
test('makeNativeString: will do what it says', async t => {
|
||||
utils.init()
|
||||
t.is(utils.makeNativeString('bob'), 'function bob() { [native code] }')
|
||||
t.is(
|
||||
utils.makeNativeString('toString'),
|
||||
'function toString() { [native code] }'
|
||||
)
|
||||
t.is(utils.makeNativeString(), 'function () { [native code] }')
|
||||
})
|
||||
|
||||
test('replaceWithProxy: will work correctly', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const test1 = await withUtils(page).evaluate(utils => {
|
||||
const dummyProxyHandler = {
|
||||
get(target, param) {
|
||||
if (param && param === 'ping') {
|
||||
return 'pong'
|
||||
}
|
||||
return utils.cache.Reflect.get(...(arguments || []))
|
||||
},
|
||||
apply() {
|
||||
return utils.cache.Reflect.apply(...arguments)
|
||||
}
|
||||
}
|
||||
utils.replaceWithProxy(
|
||||
HTMLMediaElement.prototype,
|
||||
'canPlayType',
|
||||
dummyProxyHandler
|
||||
)
|
||||
return {
|
||||
toString: HTMLMediaElement.prototype.canPlayType.toString(),
|
||||
ping: HTMLMediaElement.prototype.canPlayType.ping
|
||||
}
|
||||
})
|
||||
t.deepEqual(test1, {
|
||||
toString: 'function canPlayType() { [native code] }',
|
||||
ping: 'pong'
|
||||
})
|
||||
})
|
||||
|
||||
test('replaceObjPathWithProxy: will work correctly', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const test1 = await withUtils(page).evaluate(utils => {
|
||||
const dummyProxyHandler = {
|
||||
get(target, param) {
|
||||
if (param && param === 'ping') {
|
||||
return 'pong'
|
||||
}
|
||||
return utils.cache.Reflect.get(...(arguments || []))
|
||||
},
|
||||
apply() {
|
||||
return utils.cache.Reflect.apply(...arguments)
|
||||
}
|
||||
}
|
||||
utils.replaceObjPathWithProxy(
|
||||
'HTMLMediaElement.prototype.canPlayType',
|
||||
dummyProxyHandler
|
||||
)
|
||||
return {
|
||||
toString: HTMLMediaElement.prototype.canPlayType.toString(),
|
||||
ping: HTMLMediaElement.prototype.canPlayType.ping
|
||||
}
|
||||
})
|
||||
t.deepEqual(test1, {
|
||||
toString: 'function canPlayType() { [native code] }',
|
||||
ping: 'pong'
|
||||
})
|
||||
})
|
||||
|
||||
test('redirectToString: is battle hardened', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Patch all documents including iframes
|
||||
await withUtils(page).evaluateOnNewDocument(utils => {
|
||||
// We redirect toString calls targeted at `canPlayType` to `getParameter`,
|
||||
// so if everything works correctly we expect `getParameter` as response.
|
||||
const proxyObj = HTMLMediaElement.prototype.canPlayType
|
||||
const originalObj = WebGLRenderingContext.prototype.getParameter
|
||||
|
||||
utils.redirectToString(proxyObj, originalObj)
|
||||
})
|
||||
await page.goto('about:blank')
|
||||
|
||||
const result = await withUtils(page).evaluate(utils => {
|
||||
const iframe = document.createElement('iframe')
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
return {
|
||||
target: {
|
||||
raw: HTMLMediaElement.prototype.canPlayType + '',
|
||||
rawiframe:
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType + '',
|
||||
raw2: HTMLMediaElement.prototype.canPlayType.toString(),
|
||||
rawiframe2:
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString(),
|
||||
direct: Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithdirect: Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
)
|
||||
},
|
||||
toString: {
|
||||
obj: HTMLMediaElement.prototype.canPlayType.toString + '',
|
||||
objiframe:
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType.toString +
|
||||
'',
|
||||
raw: Function.prototype.toString + '',
|
||||
rawiframe: iframe.contentWindow.Function.prototype.toString + '',
|
||||
direct: Function.prototype.toString.call(Function.prototype.toString),
|
||||
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
Function.prototype.toString
|
||||
),
|
||||
iframeWithdirect: Function.prototype.toString.call(
|
||||
iframe.contentWindow.Function.prototype.toString
|
||||
),
|
||||
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
iframe.contentWindow.Function.prototype.toString
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.deepEqual(result, {
|
||||
target: {
|
||||
raw: 'function getParameter() { [native code] }',
|
||||
raw2: 'function getParameter() { [native code] }',
|
||||
rawiframe: 'function getParameter() { [native code] }',
|
||||
rawiframe2: 'function getParameter() { [native code] }',
|
||||
direct: 'function getParameter() { [native code] }',
|
||||
directWithiframe: 'function getParameter() { [native code] }',
|
||||
iframeWithdirect: 'function getParameter() { [native code] }',
|
||||
iframeWithiframe: 'function getParameter() { [native code] }'
|
||||
},
|
||||
toString: {
|
||||
obj: 'function toString() { [native code] }',
|
||||
objiframe: 'function toString() { [native code] }',
|
||||
raw: 'function toString() { [native code] }',
|
||||
rawiframe: 'function toString() { [native code] }',
|
||||
direct: 'function toString() { [native code] }',
|
||||
directWithiframe: 'function toString() { [native code] }',
|
||||
iframeWithdirect: 'function toString() { [native code] }',
|
||||
iframeWithiframe: 'function toString() { [native code] }'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('redirectToString: has proper errors', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Patch all documents including iframes
|
||||
await withUtils(page).evaluateOnNewDocument(utils => {
|
||||
// We redirect toString calls targeted at `canPlayType` to `getParameter`,
|
||||
// so if everything works correctly we expect `getParameter` as response.
|
||||
const proxyObj = HTMLMediaElement.prototype.canPlayType
|
||||
const originalObj = WebGLRenderingContext.prototype.getParameter
|
||||
|
||||
utils.redirectToString(proxyObj, originalObj)
|
||||
})
|
||||
await page.goto('about:blank')
|
||||
|
||||
const result = await withUtils(page).evaluate(utils => {
|
||||
const evalErr = (str = '') => {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
return eval(str)
|
||||
} catch (err) {
|
||||
return err.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blank: evalErr(`Function.prototype.toString.apply()`),
|
||||
null: evalErr(`Function.prototype.toString.apply(null)`),
|
||||
undef: evalErr(`Function.prototype.toString.apply(undefined)`),
|
||||
emptyObject: evalErr(`Function.prototype.toString.apply({})`)
|
||||
}
|
||||
})
|
||||
t.deepEqual(result, {
|
||||
blank:
|
||||
"TypeError: Function.prototype.toString requires that 'this' be a Function",
|
||||
null: "TypeError: Function.prototype.toString requires that 'this' be a Function",
|
||||
undef:
|
||||
"TypeError: Function.prototype.toString requires that 'this' be a Function",
|
||||
emptyObject:
|
||||
"TypeError: Function.prototype.toString requires that 'this' be a Function"
|
||||
})
|
||||
})
|
||||
|
||||
test('patchToString: will work correctly', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Test verbatim string replacement
|
||||
const test1 = await withUtils(page).evaluate(utils => {
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob')
|
||||
return HTMLMediaElement.prototype.canPlayType.toString()
|
||||
})
|
||||
t.is(test1, 'bob')
|
||||
|
||||
// Test automatic mode derived from `.name`
|
||||
const test2 = await withUtils(page).evaluate(utils => {
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType)
|
||||
return HTMLMediaElement.prototype.canPlayType.toString()
|
||||
})
|
||||
t.is(test2, 'function canPlayType() { [native code] }')
|
||||
|
||||
// Make sure automatic mode derived from `.name` works with proxies
|
||||
const test3 = await withUtils(page).evaluate(utils => {
|
||||
HTMLMediaElement.prototype.canPlayType = new Proxy(
|
||||
HTMLMediaElement.prototype.canPlayType,
|
||||
{}
|
||||
)
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType)
|
||||
return HTMLMediaElement.prototype.canPlayType.toString()
|
||||
})
|
||||
t.is(test3, 'function canPlayType() { [native code] }')
|
||||
|
||||
// Actually verify there's an issue when using vanilla Proxies
|
||||
const test4 = await withUtils(page).evaluate(utils => {
|
||||
HTMLMediaElement.prototype.canPlayType = new Proxy(
|
||||
HTMLMediaElement.prototype.canPlayType,
|
||||
{}
|
||||
)
|
||||
return HTMLMediaElement.prototype.canPlayType.toString()
|
||||
})
|
||||
t.is(test4, 'function () { [native code] }')
|
||||
})
|
||||
|
||||
function toStringTest(obj) {
|
||||
obj = eval(obj) // eslint-disable-line no-eval
|
||||
return `
|
||||
- obj.toString(): ${obj.toString()}
|
||||
- obj.name: ${obj.name}
|
||||
- obj.toString + "": ${obj.toString + ''}
|
||||
- obj.toString.name: ${obj.toString.name}
|
||||
- obj.valueOf + "": ${obj.valueOf + ''}
|
||||
- obj.valueOf().name: ${obj.valueOf().name}
|
||||
- Object.prototype.toString.apply(obj): ${Object.prototype.toString.apply(obj)}
|
||||
- Function.prototype.toString.call(obj): ${Function.prototype.toString.call(
|
||||
obj
|
||||
)}
|
||||
- Function.prototype.valueOf.call(obj) + "": ${
|
||||
Function.prototype.valueOf.call(obj) + ''
|
||||
}
|
||||
- obj.toString === Function.prototype.toString: ${
|
||||
obj.toString === Function.prototype.toString
|
||||
}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
test('patchToString: passes all toString tests', async t => {
|
||||
const toStringVanilla = await (async function () {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
return page.evaluate(toStringTest, 'HTMLMediaElement.prototype.canPlayType')
|
||||
})()
|
||||
const toStringStealth = await (async function () {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await withUtils(page).evaluate(utils => {
|
||||
HTMLMediaElement.prototype.canPlayType = function canPlayType() {}
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType)
|
||||
})
|
||||
return page.evaluate(toStringTest, 'HTMLMediaElement.prototype.canPlayType')
|
||||
})()
|
||||
|
||||
// Check that the unmodified results are as expected
|
||||
t.is(
|
||||
toStringVanilla,
|
||||
`
|
||||
- obj.toString(): function canPlayType() { [native code] }
|
||||
- obj.name: canPlayType
|
||||
- obj.toString + "": function toString() { [native code] }
|
||||
- obj.toString.name: toString
|
||||
- obj.valueOf + "": function valueOf() { [native code] }
|
||||
- obj.valueOf().name: canPlayType
|
||||
- Object.prototype.toString.apply(obj): [object Function]
|
||||
- Function.prototype.toString.call(obj): function canPlayType() { [native code] }
|
||||
- Function.prototype.valueOf.call(obj) + "": function canPlayType() { [native code] }
|
||||
- obj.toString === Function.prototype.toString: true
|
||||
`.trim()
|
||||
)
|
||||
|
||||
// Make sure our customizations leave no trace
|
||||
t.is(toStringVanilla, toStringStealth)
|
||||
})
|
||||
|
||||
test('patchToString: passes stack trace tests', async t => {
|
||||
const toStringStackTrace = () => {
|
||||
try {
|
||||
Object.create(
|
||||
Object.getOwnPropertyDescriptor(Function.prototype, 'toString').get
|
||||
).toString()
|
||||
} catch (err) {
|
||||
return err.stack.split('\n').slice(0, 2).join('|')
|
||||
}
|
||||
return 'error not thrown'
|
||||
}
|
||||
|
||||
const toStringVanilla = await (async function () {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
return page.evaluate(toStringStackTrace)
|
||||
})()
|
||||
const toStringStealth = await (async function () {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await withUtils(page).evaluate(utils => {
|
||||
HTMLMediaElement.prototype.canPlayType = function canPlayType() {}
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType)
|
||||
})
|
||||
return page.evaluate(toStringStackTrace)
|
||||
})()
|
||||
|
||||
// Check that the unmodified results are as expected
|
||||
t.is(
|
||||
toStringVanilla,
|
||||
`TypeError: Object prototype may only be an Object or null: undefined| at Function.create (<anonymous>)`.trim()
|
||||
)
|
||||
|
||||
// Make sure our customizations leave no trace
|
||||
t.is(toStringVanilla, toStringStealth)
|
||||
})
|
||||
|
||||
test('patchToString: vanilla has iframe issues', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Only patch the main window
|
||||
const result = await withUtils(page).evaluate(utils => {
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'bob')
|
||||
|
||||
const iframe = document.createElement('iframe')
|
||||
document.body.appendChild(iframe)
|
||||
return {
|
||||
direct: Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithdirect: Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
)
|
||||
}
|
||||
})
|
||||
t.deepEqual(result, {
|
||||
direct: 'bob',
|
||||
directWithiframe: 'function canPlayType() { [native code] }',
|
||||
iframeWithdirect: 'function canPlayType() { [native code] }',
|
||||
iframeWithiframe: 'function canPlayType() { [native code] }'
|
||||
})
|
||||
})
|
||||
|
||||
test('patchToString: stealth has no iframe issues', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Patch all documents including iframes
|
||||
await withUtils(page).evaluateOnNewDocument(utils => {
|
||||
utils.patchToString(HTMLMediaElement.prototype.canPlayType, 'alice')
|
||||
})
|
||||
await page.goto('about:blank')
|
||||
|
||||
const result = await withUtils(page).evaluate(utils => {
|
||||
const iframe = document.createElement('iframe')
|
||||
document.body.appendChild(iframe)
|
||||
return {
|
||||
direct: Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
directWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithdirect: Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
),
|
||||
iframeWithiframe: iframe.contentWindow.Function.prototype.toString.call(
|
||||
iframe.contentWindow.HTMLMediaElement.prototype.canPlayType
|
||||
)
|
||||
}
|
||||
})
|
||||
t.deepEqual(result, {
|
||||
direct: 'alice',
|
||||
directWithiframe: 'alice',
|
||||
iframeWithdirect: 'alice',
|
||||
iframeWithiframe: 'alice'
|
||||
})
|
||||
})
|
||||
|
||||
test('stripProxyFromErrors: will work correctly', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
const getStack = prop => {
|
||||
try {
|
||||
prop.caller() // Will throw (HTMLMediaElement.prototype.canPlayType.caller)
|
||||
return false
|
||||
} catch (err) {
|
||||
return err.stack
|
||||
}
|
||||
}
|
||||
/** We need traps to show up in the error stack */
|
||||
const dummyProxyHandler = {
|
||||
get() {
|
||||
return utils.cache.Reflect.get(...(arguments || []))
|
||||
},
|
||||
apply() {
|
||||
return utils.cache.Reflect.apply(...arguments)
|
||||
}
|
||||
}
|
||||
const vanillaProxy = new Proxy(
|
||||
HTMLMediaElement.prototype.canPlayType,
|
||||
dummyProxyHandler
|
||||
)
|
||||
const stealthProxy = new Proxy(
|
||||
HTMLMediaElement.prototype.canPlayType,
|
||||
utils.stripProxyFromErrors(dummyProxyHandler)
|
||||
)
|
||||
|
||||
const stacks = {
|
||||
vanilla: getStack(HTMLMediaElement.prototype.canPlayType),
|
||||
vanillaProxy: getStack(vanillaProxy),
|
||||
stealthProxy: getStack(stealthProxy)
|
||||
}
|
||||
return stacks
|
||||
})
|
||||
|
||||
// Check that the untouched stuff behaves as expected
|
||||
t.true(results.vanilla.includes(`TypeError: 'caller'`))
|
||||
t.false(results.vanilla.includes(`at Object.get`))
|
||||
|
||||
// Regression test: Make sure vanilla JS Proxies leak the stack trace
|
||||
t.true(results.vanillaProxy.includes(`TypeError: 'caller'`))
|
||||
t.true(results.vanillaProxy.includes(`at Object.get`))
|
||||
|
||||
// Stealth tests
|
||||
t.true(results.stealthProxy.includes(`TypeError: 'caller'`))
|
||||
t.false(results.stealthProxy.includes(`at Object.get`))
|
||||
})
|
||||
|
||||
test('replaceProperty: will work without traces', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
utils.replaceProperty(Object.getPrototypeOf(navigator), 'languages', {
|
||||
get: () => ['de-DE']
|
||||
})
|
||||
return {
|
||||
propNames: Object.getOwnPropertyNames(navigator)
|
||||
}
|
||||
})
|
||||
t.false(results.propNames.includes('languages'))
|
||||
})
|
||||
|
||||
test('cache: will prevent leaks through overriding methods', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
const sniffResults = {
|
||||
vanilla: false,
|
||||
stealth: false
|
||||
}
|
||||
|
||||
const vanillaProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return Reflect.get(...arguments)
|
||||
}
|
||||
}
|
||||
)
|
||||
Reflect.get = () => (sniffResults.vanilla = true)
|
||||
// trigger get trap
|
||||
vanillaProxy.foo // eslint-disable-line
|
||||
|
||||
const stealthProxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get() {
|
||||
return utils.cache.Reflect.get(...arguments) // using cached copy
|
||||
}
|
||||
}
|
||||
)
|
||||
Reflect.get = () => (sniffResults.stealth = true)
|
||||
// trigger get trap
|
||||
stealthProxy.foo // eslint-disable-line
|
||||
|
||||
return sniffResults
|
||||
})
|
||||
|
||||
t.deepEqual(results, {
|
||||
vanilla: true,
|
||||
stealth: false
|
||||
})
|
||||
})
|
||||
|
||||
test('replaceWithProxy: will throw prototype errors', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await page.goto('about:blank')
|
||||
|
||||
const result = await withUtils(page).evaluate(utils => {
|
||||
utils.replaceWithProxy(HTMLMediaElement.prototype, 'canPlayType', {})
|
||||
|
||||
const evalErr = (str = '') => {
|
||||
try {
|
||||
// eslint-disable-next-line no-eval
|
||||
return eval(str)
|
||||
} catch (err) {
|
||||
return err.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
same: evalErr(
|
||||
`Object.setPrototypeOf(HTMLMediaElement.prototype.canPlayType, HTMLMediaElement.prototype.canPlayType) + ""`
|
||||
),
|
||||
sameString: evalErr(
|
||||
`Object.setPrototypeOf(Function.prototype.toString, Function.prototype.toString) + ""`
|
||||
),
|
||||
null: evalErr(
|
||||
`Object.setPrototypeOf(Function.prototype.toString, null) + ""`
|
||||
),
|
||||
undef: evalErr(
|
||||
`Object.setPrototypeOf(Function.prototype.toString, undefined) + ""`
|
||||
),
|
||||
none: evalErr(`Object.setPrototypeOf(Function.prototype.toString) + ""`)
|
||||
}
|
||||
})
|
||||
t.deepEqual(result, {
|
||||
same: 'TypeError: Cyclic __proto__ value',
|
||||
sameString: 'TypeError: Cyclic __proto__ value',
|
||||
null: 'TypeError: Cannot convert object to primitive value',
|
||||
undef:
|
||||
'TypeError: Object prototype may only be an Object or null: undefined',
|
||||
none: 'TypeError: Object prototype may only be an Object or null: undefined'
|
||||
})
|
||||
})
|
||||
|
||||
test('replaceGetterSetter', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await page.goto('about:blank')
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
const getDetails = a => ({
|
||||
href: a.href,
|
||||
typeof: typeof a.href,
|
||||
in: 'href' in a,
|
||||
keys: Object.keys(a),
|
||||
// eslint-disable-next-line no-undef
|
||||
prototypeKeys: Object.keys(HTMLAnchorElement.prototype),
|
||||
getOwnPropertyNames: Object.getOwnPropertyNames(a),
|
||||
prototypeGetOwnPropertyNames: Object.getOwnPropertyNames(
|
||||
// eslint-disable-next-line no-undef
|
||||
HTMLAnchorElement.prototype
|
||||
),
|
||||
ownPropertyDescriptor:
|
||||
undefined === Object.getOwnPropertyDescriptor(a, 'href'),
|
||||
prototypeOwnPropertyDescriptor: Object.getOwnPropertyDescriptor(
|
||||
// eslint-disable-next-line no-undef
|
||||
HTMLAnchorElement.prototype,
|
||||
'href'
|
||||
),
|
||||
ownPropertyDescriptors: Object.getOwnPropertyDescriptors(a, 'href'),
|
||||
prototypeOwnPropertyDescriptors: Object.getOwnPropertyDescriptors(
|
||||
// eslint-disable-next-line no-undef
|
||||
HTMLAnchorElement.prototype,
|
||||
'href'
|
||||
),
|
||||
getToString: Object.getOwnPropertyDescriptor(
|
||||
// eslint-disable-next-line no-undef
|
||||
HTMLAnchorElement.prototype,
|
||||
'href'
|
||||
).get.toString(),
|
||||
setToString: Object.getOwnPropertyDescriptor(
|
||||
// eslint-disable-next-line no-undef
|
||||
HTMLAnchorElement.prototype,
|
||||
'href'
|
||||
).set.toString()
|
||||
})
|
||||
|
||||
// Use native a.href.
|
||||
const a1 = document.createElement('a')
|
||||
a1.href = 'http://foo.com/'
|
||||
const details1 = getDetails(a1)
|
||||
|
||||
// Override a.href.
|
||||
let href = ''
|
||||
// eslint-disable-next-line no-undef
|
||||
utils.replaceGetterSetter(HTMLAnchorElement.prototype, 'href', {
|
||||
get: function() {
|
||||
return href
|
||||
},
|
||||
set: function(newValue) {
|
||||
href = newValue
|
||||
}
|
||||
})
|
||||
|
||||
// Use overrided a.href.
|
||||
const a2 = document.createElement('a')
|
||||
a2.href = 'http://foo.com/'
|
||||
const details2 = getDetails(a2)
|
||||
|
||||
return [details1, details2]
|
||||
})
|
||||
|
||||
t.deepEqual(results[1], results[0])
|
||||
})
|
||||
|
||||
test('arrayEquals', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await page.goto('about:blank')
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
const obj = { foo: 'bar' }
|
||||
return {
|
||||
a: utils.arrayEquals(['a', 'Alpha'], ['a', 'Alpha']),
|
||||
b: !utils.arrayEquals(['b', 'Beta'], ['b', 'Blue']),
|
||||
c: !utils.arrayEquals(['c', { foo: 'bar' }], ['c', { foo: 'bar' }]),
|
||||
d: utils.arrayEquals(['d', obj], ['d', obj]),
|
||||
e: utils.arrayEquals([null], [null]),
|
||||
f: utils.arrayEquals([undefined], [undefined]),
|
||||
g: utils.arrayEquals([false], [false])
|
||||
}
|
||||
})
|
||||
|
||||
t.deepEqual(results, {
|
||||
a: true,
|
||||
b: true,
|
||||
c: true,
|
||||
d: true,
|
||||
e: true,
|
||||
f: true,
|
||||
g: true
|
||||
})
|
||||
})
|
||||
|
||||
test('memoize', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
await page.goto('about:blank')
|
||||
|
||||
const results = await withUtils(page).evaluate(utils => {
|
||||
const objectify = utils.memoize((valueAdded, valueIgnored) => {
|
||||
return { valueAdded }
|
||||
})
|
||||
|
||||
const obj = { foo: 'bar' }
|
||||
/* eslint-disable no-self-compare */
|
||||
return {
|
||||
a: objectify('a', 'Alpha') === objectify('a', 'Alpha'),
|
||||
b: objectify('b', 'Beta') !== objectify('b', 'Blue'),
|
||||
c: objectify('c', { foo: 'bar' }) !== objectify('c', { foo: 'bar' }),
|
||||
d: objectify('d', obj) === objectify('d', obj),
|
||||
e: objectify(null) === objectify(null),
|
||||
f: objectify(undefined) === objectify(undefined),
|
||||
g: objectify(false) === objectify(false)
|
||||
}
|
||||
/* eslint-enable no-self-compare */
|
||||
})
|
||||
|
||||
t.deepEqual(results, {
|
||||
a: true,
|
||||
b: true,
|
||||
c: true,
|
||||
d: true,
|
||||
e: true,
|
||||
f: true,
|
||||
g: true
|
||||
})
|
||||
})
|
||||
288
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/readme.md
generated
vendored
Normal file
288
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/readme.md
generated
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
## API
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
#### Table of Contents
|
||||
|
||||
- [utils()](#utils)
|
||||
- [.stripProxyFromErrors(handler)](#stripproxyfromerrorshandler)
|
||||
- [.stripErrorWithAnchor(err, anchor)](#striperrorwithanchorerr-anchor)
|
||||
- [.replaceProperty(obj, propName, descriptorOverrides)](#replacepropertyobj-propname-descriptoroverrides)
|
||||
- [.preloadCache()](#preloadcache)
|
||||
- [.makeNativeString(name?)](#makenativestringname)
|
||||
- [.patchToString(obj, str)](#patchtostringobj-str)
|
||||
- [.patchToStringNested(obj)](#patchtostringnestedobj)
|
||||
- [.redirectToString(proxyObj, originalObj)](#redirecttostringproxyobj-originalobj)
|
||||
- [.replaceWithProxy(obj, propName, handler)](#replacewithproxyobj-propname-handler)
|
||||
- [.mockWithProxy(obj, propName, pseudoTarget, handler)](#mockwithproxyobj-propname-pseudotarget-handler)
|
||||
- [.createProxy(pseudoTarget, handler)](#createproxypseudotarget-handler)
|
||||
- [.splitObjPath(objPath)](#splitobjpathobjpath)
|
||||
- [.replaceObjPathWithProxy(objPath, handler)](#replaceobjpathwithproxyobjpath-handler)
|
||||
- [.execRecursively(obj, typeFilter, fn)](#execrecursivelyobj-typefilter-fn)
|
||||
- [.stringifyFns(fnObj)](#stringifyfnsfnobj)
|
||||
- [.materializeFns(fnStrObj)](#materializefnsfnstrobj)
|
||||
|
||||
### [utils()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L12-L12)
|
||||
|
||||
A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces.
|
||||
|
||||
Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser).
|
||||
|
||||
Note: If for whatever reason you need to use this outside of `puppeteer-extra`:
|
||||
Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context.
|
||||
|
||||
Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities.
|
||||
|
||||
---
|
||||
|
||||
#### .[stripProxyFromErrors(handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L21-L82)
|
||||
|
||||
- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to wrap (optional, default `{}`)
|
||||
|
||||
Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw.
|
||||
|
||||
The presence of a JS Proxy can be revealed as it shows up in error stack traces.
|
||||
|
||||
---
|
||||
|
||||
#### .[stripErrorWithAnchor(err, anchor)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L90-L101)
|
||||
|
||||
- `err` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The error to sanitize
|
||||
- `anchor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The string the anchor line starts with
|
||||
|
||||
Strip error lines from stack traces until (and including) a known line the stack.
|
||||
|
||||
---
|
||||
|
||||
#### .[replaceProperty(obj, propName, descriptorOverrides)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L120-L127)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace
|
||||
- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The property name to replace
|
||||
- `descriptorOverrides` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** e.g. { value: "alice" } (optional, default `{}`)
|
||||
|
||||
Replace the property of an object in a stealthy way.
|
||||
|
||||
Note: You also want to work on the prototype of an object most often,
|
||||
as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)).
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
replaceProperty(WebGLRenderingContext.prototype, 'getParameter', {
|
||||
value: 'alice'
|
||||
})
|
||||
// or
|
||||
replaceProperty(Object.getPrototypeOf(navigator), 'languages', {
|
||||
get: () => ['en-US', 'en']
|
||||
})
|
||||
```
|
||||
|
||||
- **See: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty>**
|
||||
|
||||
---
|
||||
|
||||
#### .[preloadCache()](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L137-L150)
|
||||
|
||||
Preload a cache of function copies and data.
|
||||
|
||||
For a determined enough observer it would be possible to overwrite and sniff usage of functions
|
||||
we use in our internal Proxies, to combat that we use a cached copy of those functions.
|
||||
|
||||
This is evaluated once per execution context (e.g. window)
|
||||
|
||||
---
|
||||
|
||||
#### .[makeNativeString(name?)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L169-L173)
|
||||
|
||||
- `name` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Optional function name (optional, default `''`)
|
||||
|
||||
Utility function to generate a cross-browser `toString` result representing native code.
|
||||
|
||||
There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings.
|
||||
To future-proof this we use an existing native toString result as the basis.
|
||||
|
||||
The only advantage we have over the other team is that our JS runs first, hence we cache the result
|
||||
of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it.
|
||||
|
||||
Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before,
|
||||
by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups).
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
makeNativeString('foobar') // => `function foobar() { [native code] }`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[patchToString(obj, str)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L189-L218)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object for which to modify the `toString()` representation
|
||||
- `str` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Optional string used as a return value (optional, default `''`)
|
||||
|
||||
Helper function to modify the `toString()` result of the provided object.
|
||||
|
||||
Note: Use `utils.redirectToString` instead when possible.
|
||||
|
||||
There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object.
|
||||
If no string is provided we will generate a `[native code]` thing based on the name of the property object.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
patchToString(
|
||||
WebGLRenderingContext.prototype.getParameter,
|
||||
'function getParameter() { [native code] }'
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[patchToStringNested(obj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L225-L227)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)
|
||||
|
||||
Make all nested functions of an object native.
|
||||
|
||||
---
|
||||
|
||||
#### .[redirectToString(proxyObj, originalObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L235-L272)
|
||||
|
||||
- `proxyObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object that toString will be called on
|
||||
- `originalObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which toString result we wan to return
|
||||
|
||||
Redirect toString requests from one object to another.
|
||||
|
||||
---
|
||||
|
||||
#### .[replaceWithProxy(obj, propName, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L287-L296)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace
|
||||
- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the property to replace
|
||||
- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use
|
||||
|
||||
All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps.
|
||||
|
||||
Will stealthify these aspects (strip error stack traces, redirect toString, etc).
|
||||
Note: This is meant to modify native Browser APIs and works best with prototype objects.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[mockWithProxy(obj, propName, pseudoTarget, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L311-L319)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The object which has the property to replace
|
||||
- `propName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the property to replace or create
|
||||
- `pseudoTarget` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy target to use as a basis
|
||||
- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use
|
||||
|
||||
All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps.
|
||||
|
||||
Will stealthify these aspects (strip error stack traces, redirect toString, etc).
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
mockWithProxy(
|
||||
chrome.runtime,
|
||||
'sendMessage',
|
||||
function sendMessage() {},
|
||||
proxyHandler
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[createProxy(pseudoTarget, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L334-L340)
|
||||
|
||||
- `pseudoTarget` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy target to use as a basis
|
||||
- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use
|
||||
|
||||
All-in-one method to create a new JS Proxy with stealth tweaks.
|
||||
|
||||
This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property.
|
||||
|
||||
Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc).
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[splitObjPath(objPath)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L351-L359)
|
||||
|
||||
- `objPath` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The full path to an object as dot notation string
|
||||
|
||||
Helper function to split a full path to an Object into the first part and property.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
splitObjPath(`HTMLMediaElement.prototype.canPlayType`)
|
||||
// => {objName: "HTMLMediaElement.prototype", propName: "canPlayType"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[replaceObjPathWithProxy(objPath, handler)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L372-L376)
|
||||
|
||||
- `objPath` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The full path to an object (dot notation string) to replace
|
||||
- `handler` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** The JS Proxy handler to use
|
||||
|
||||
Convenience method to replace a property with a JS Proxy using the provided objPath.
|
||||
|
||||
Supports a full path (dot notation) to the object as string here, in case that makes it easier.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
replaceObjPathWithProxy(
|
||||
'WebGLRenderingContext.prototype.getParameter',
|
||||
proxyHandler
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### .[execRecursively(obj, typeFilter, fn)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L385-L402)
|
||||
|
||||
- `obj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** (optional, default `{}`)
|
||||
- `typeFilter` **[array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** e.g. `['function']` (optional, default `[]`)
|
||||
- `fn` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** e.g. `utils.patchToString`
|
||||
|
||||
Traverse nested properties of an object recursively and apply the given function on a whitelist of value types.
|
||||
|
||||
---
|
||||
|
||||
#### .[stringifyFns(fnObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L417-L431)
|
||||
|
||||
- `fnObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object containing functions as properties (optional, default `{hello:()=>'world'}`)
|
||||
|
||||
Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one.
|
||||
That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter.
|
||||
|
||||
Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process.
|
||||
This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings.
|
||||
|
||||
We use this to pass down our utility functions as well as any other functions (to be able to split up code better).
|
||||
|
||||
- **See: utils.materializeFns**
|
||||
|
||||
---
|
||||
|
||||
#### .[materializeFns(fnStrObj)](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L439-L451)
|
||||
|
||||
- `fnStrObj` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An object containing stringified functions as properties (optional, default `{hello:"() => 'world'"}`)
|
||||
|
||||
Utility function to reverse the process of `utils.stringifyFns`.
|
||||
Will materialize an object with stringified functions (supports classic and fat arrow functions).
|
||||
|
||||
---
|
||||
49
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/withUtils.js
generated
vendored
Normal file
49
node_modules/puppeteer-extra-plugin-stealth/evasions/_utils/withUtils.js
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
const utils = require('./index')
|
||||
|
||||
/**
|
||||
* Wrap a page with utilities.
|
||||
*
|
||||
* @param {Puppeteer.Page} page
|
||||
*/
|
||||
module.exports = page => ({
|
||||
/**
|
||||
* Simple `page.evaluate` replacement to preload utils
|
||||
*/
|
||||
evaluate: async function (mainFunction, ...args) {
|
||||
return page.evaluate(
|
||||
({ _utilsFns, _mainFunction, _args }) => {
|
||||
// Add this point we cannot use our utililty functions as they're just strings, we need to materialize them first
|
||||
const utils = Object.fromEntries(
|
||||
Object.entries(_utilsFns).map(([key, value]) => [key, eval(value)]) // eslint-disable-line no-eval
|
||||
)
|
||||
utils.init()
|
||||
return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval
|
||||
},
|
||||
{
|
||||
_utilsFns: utils.stringifyFns(utils),
|
||||
_mainFunction: mainFunction.toString(),
|
||||
_args: args || []
|
||||
}
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Simple `page.evaluateOnNewDocument` replacement to preload utils
|
||||
*/
|
||||
evaluateOnNewDocument: async function (mainFunction, ...args) {
|
||||
return page.evaluateOnNewDocument(
|
||||
({ _utilsFns, _mainFunction, _args }) => {
|
||||
// Add this point we cannot use our utililty functions as they're just strings, we need to materialize them first
|
||||
const utils = Object.fromEntries(
|
||||
Object.entries(_utilsFns).map(([key, value]) => [key, eval(value)]) // eslint-disable-line no-eval
|
||||
)
|
||||
utils.init()
|
||||
return eval(_mainFunction)(utils, ..._args) // eslint-disable-line no-eval
|
||||
},
|
||||
{
|
||||
_utilsFns: utils.stringifyFns(utils),
|
||||
_mainFunction: mainFunction.toString(),
|
||||
_args: args || []
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user