Rename to hkt.sh
This commit is contained in:
48
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/data.json
generated
vendored
Normal file
48
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/data.json
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"mimeTypes": [
|
||||
{
|
||||
"type": "application/pdf",
|
||||
"suffixes": "pdf",
|
||||
"description": "",
|
||||
"__pluginName": "Chrome PDF Viewer"
|
||||
},
|
||||
{
|
||||
"type": "application/x-google-chrome-pdf",
|
||||
"suffixes": "pdf",
|
||||
"description": "Portable Document Format",
|
||||
"__pluginName": "Chrome PDF Plugin"
|
||||
},
|
||||
{
|
||||
"type": "application/x-nacl",
|
||||
"suffixes": "",
|
||||
"description": "Native Client Executable",
|
||||
"__pluginName": "Native Client"
|
||||
},
|
||||
{
|
||||
"type": "application/x-pnacl",
|
||||
"suffixes": "",
|
||||
"description": "Portable Native Client Executable",
|
||||
"__pluginName": "Native Client"
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "Chrome PDF Plugin",
|
||||
"filename": "internal-pdf-viewer",
|
||||
"description": "Portable Document Format",
|
||||
"__mimeTypes": ["application/x-google-chrome-pdf"]
|
||||
},
|
||||
{
|
||||
"name": "Chrome PDF Viewer",
|
||||
"filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai",
|
||||
"description": "",
|
||||
"__mimeTypes": ["application/pdf"]
|
||||
},
|
||||
{
|
||||
"name": "Native Client",
|
||||
"filename": "internal-nacl-plugin",
|
||||
"description": "",
|
||||
"__mimeTypes": ["application/x-nacl", "application/x-pnacl"]
|
||||
}
|
||||
]
|
||||
}
|
||||
50
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/functionMocks.js
generated
vendored
Normal file
50
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/functionMocks.js
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* `navigator.{plugins,mimeTypes}` share similar custom functions to look up properties
|
||||
*
|
||||
* Note: This is meant to be run in the context of the page.
|
||||
*/
|
||||
module.exports.generateFunctionMocks = utils => (
|
||||
proto,
|
||||
itemMainProp,
|
||||
dataArray
|
||||
) => ({
|
||||
/** Returns the MimeType object with the specified index. */
|
||||
item: utils.createProxy(proto.item, {
|
||||
apply(target, ctx, args) {
|
||||
if (!args.length) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'item' on '${
|
||||
proto[Symbol.toStringTag]
|
||||
}': 1 argument required, but only 0 present.`
|
||||
)
|
||||
}
|
||||
// Special behavior alert:
|
||||
// - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup
|
||||
// - If anything else than an integer (including as string) is provided it will return the first entry
|
||||
const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer
|
||||
// Note: Vanilla never returns `undefined`
|
||||
return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null
|
||||
}
|
||||
}),
|
||||
/** Returns the MimeType object with the specified name. */
|
||||
namedItem: utils.createProxy(proto.namedItem, {
|
||||
apply(target, ctx, args) {
|
||||
if (!args.length) {
|
||||
throw new TypeError(
|
||||
`Failed to execute 'namedItem' on '${
|
||||
proto[Symbol.toStringTag]
|
||||
}': 1 argument required, but only 0 present.`
|
||||
)
|
||||
}
|
||||
return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`!
|
||||
}
|
||||
}),
|
||||
/** Does nothing and shall return nothing */
|
||||
refresh: proto.refresh
|
||||
? utils.createProxy(proto.refresh, {
|
||||
apply(target, ctx, args) {
|
||||
return undefined
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
})
|
||||
101
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js
generated
vendored
Normal file
101
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
'use strict'
|
||||
|
||||
const { PuppeteerExtraPlugin } = require('puppeteer-extra-plugin')
|
||||
|
||||
const utils = require('../_utils')
|
||||
const withUtils = require('../_utils/withUtils')
|
||||
|
||||
const { generateMimeTypeArray } = require('./mimeTypes')
|
||||
const { generatePluginArray } = require('./plugins')
|
||||
const { generateMagicArray } = require('./magicArray')
|
||||
const { generateFunctionMocks } = require('./functionMocks')
|
||||
|
||||
const data = require('./data.json')
|
||||
|
||||
/**
|
||||
* In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.
|
||||
* This plugin emulates both of these with functional mocks to match regular headful Chrome.
|
||||
*
|
||||
* Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray
|
||||
*/
|
||||
class Plugin extends PuppeteerExtraPlugin {
|
||||
constructor(opts = {}) {
|
||||
super(opts)
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'stealth/evasions/navigator.plugins'
|
||||
}
|
||||
|
||||
async onPageCreated(page) {
|
||||
await withUtils(page).evaluateOnNewDocument(
|
||||
(utils, { fns, data }) => {
|
||||
fns = utils.materializeFns(fns)
|
||||
|
||||
// That means we're running headful
|
||||
const hasPlugins = 'plugins' in navigator && navigator.plugins.length
|
||||
if (hasPlugins) {
|
||||
return // nothing to do here
|
||||
}
|
||||
|
||||
const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)
|
||||
const plugins = fns.generatePluginArray(utils, fns)(data.plugins)
|
||||
|
||||
// Plugin and MimeType cross-reference each other, let's do that now
|
||||
// Note: We're looping through `data.plugins` here, not the generated `plugins`
|
||||
for (const pluginData of data.plugins) {
|
||||
pluginData.__mimeTypes.forEach((type, index) => {
|
||||
plugins[pluginData.name][index] = mimeTypes[type]
|
||||
|
||||
Object.defineProperty(plugins[pluginData.name], type, {
|
||||
value: mimeTypes[type],
|
||||
writable: false,
|
||||
enumerable: false, // Not enumerable
|
||||
configurable: true
|
||||
})
|
||||
Object.defineProperty(mimeTypes[type], 'enabledPlugin', {
|
||||
value:
|
||||
type === 'application/x-pnacl'
|
||||
? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks
|
||||
: new Proxy(plugins[pluginData.name], {}), // Prevent circular references
|
||||
writable: false,
|
||||
enumerable: false, // Important: `JSON.stringify(navigator.plugins)`
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const patchNavigator = (name, value) =>
|
||||
utils.replaceProperty(Object.getPrototypeOf(navigator), name, {
|
||||
get() {
|
||||
return value
|
||||
}
|
||||
})
|
||||
|
||||
patchNavigator('mimeTypes', mimeTypes)
|
||||
patchNavigator('plugins', plugins)
|
||||
|
||||
// All done
|
||||
},
|
||||
{
|
||||
// We pass some functions to evaluate to structure the code more nicely
|
||||
fns: utils.stringifyFns({
|
||||
generateMimeTypeArray,
|
||||
generatePluginArray,
|
||||
generateMagicArray,
|
||||
generateFunctionMocks
|
||||
}),
|
||||
data
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(pluginConfig) {
|
||||
return new Plugin(pluginConfig)
|
||||
}
|
||||
56
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.test.js
generated
vendored
Normal file
56
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.test.js
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
const test = require('ava')
|
||||
|
||||
const {
|
||||
getVanillaFingerPrint,
|
||||
getStealthFingerPrint
|
||||
} = require('../../test/util')
|
||||
const { vanillaPuppeteer, addExtra } = require('../../test/util')
|
||||
|
||||
const Plugin = require('.')
|
||||
|
||||
test('vanilla: empty plugins, empty mimetypes', async t => {
|
||||
const { plugins, mimeTypes } = await getVanillaFingerPrint()
|
||||
t.is(plugins.length, 0)
|
||||
t.is(mimeTypes.length, 0)
|
||||
})
|
||||
|
||||
test('vanilla: will not have modifications', async t => {
|
||||
const browser = await vanillaPuppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const test1 = await page.evaluate(() => ({
|
||||
mimeTypes: Object.getOwnPropertyDescriptor(navigator, 'mimeTypes'), // Must be undefined if native
|
||||
plugins: Object.getOwnPropertyDescriptor(navigator, 'plugins') // Must be undefined if native
|
||||
}))
|
||||
t.is(test1.mimeTypes, undefined)
|
||||
t.is(test1.plugins, undefined)
|
||||
|
||||
const test2 = await page.evaluate(
|
||||
() => Object.getOwnPropertyNames(navigator) // Must be an empty array if native
|
||||
)
|
||||
t.false(test2.includes('plugins'))
|
||||
})
|
||||
|
||||
test('stealth: has plugin, has mimetypes', async t => {
|
||||
const { plugins, mimeTypes } = await getStealthFingerPrint(Plugin)
|
||||
t.is(plugins.length, 3)
|
||||
t.is(mimeTypes.length, 4)
|
||||
})
|
||||
|
||||
test('stealth: will not leak modifications', async t => {
|
||||
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const test1 = await page.evaluate(() => ({
|
||||
mimeTypes: Object.getOwnPropertyDescriptor(navigator, 'mimeTypes'), // Must be undefined if native
|
||||
plugins: Object.getOwnPropertyDescriptor(navigator, 'plugins') // Must be undefined if native
|
||||
}))
|
||||
t.is(test1.mimeTypes, undefined)
|
||||
t.is(test1.plugins, undefined)
|
||||
|
||||
const test2 = await page.evaluate(
|
||||
() => Object.getOwnPropertyNames(navigator) // Must be an empty array if native
|
||||
)
|
||||
t.false(test2.includes('plugins'))
|
||||
})
|
||||
144
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/magicArray.js
generated
vendored
Normal file
144
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/magicArray.js
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
/* global MimeType MimeTypeArray Plugin PluginArray */
|
||||
|
||||
/**
|
||||
* Generate a convincing and functional MimeType or Plugin array from scratch.
|
||||
* They're so similar that it makes sense to use a single generator here.
|
||||
*
|
||||
* Note: This is meant to be run in the context of the page.
|
||||
*/
|
||||
module.exports.generateMagicArray = (utils, fns) =>
|
||||
function(
|
||||
dataArray = [],
|
||||
proto = MimeTypeArray.prototype,
|
||||
itemProto = MimeType.prototype,
|
||||
itemMainProp = 'type'
|
||||
) {
|
||||
// Quick helper to set props with the same descriptors vanilla is using
|
||||
const defineProp = (obj, prop, value) =>
|
||||
Object.defineProperty(obj, prop, {
|
||||
value,
|
||||
writable: false,
|
||||
enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)`
|
||||
configurable: true
|
||||
})
|
||||
|
||||
// Loop over our fake data and construct items
|
||||
const makeItem = data => {
|
||||
const item = {}
|
||||
for (const prop of Object.keys(data)) {
|
||||
if (prop.startsWith('__')) {
|
||||
continue
|
||||
}
|
||||
defineProp(item, prop, data[prop])
|
||||
}
|
||||
return patchItem(item, data)
|
||||
}
|
||||
|
||||
const patchItem = (item, data) => {
|
||||
let descriptor = Object.getOwnPropertyDescriptors(item)
|
||||
|
||||
// Special case: Plugins have a magic length property which is not enumerable
|
||||
// e.g. `navigator.plugins[i].length` should always be the length of the assigned mimeTypes
|
||||
if (itemProto === Plugin.prototype) {
|
||||
descriptor = {
|
||||
...descriptor,
|
||||
length: {
|
||||
value: data.__mimeTypes.length,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to spoof a specific `MimeType` or `Plugin` object
|
||||
const obj = Object.create(itemProto, descriptor)
|
||||
|
||||
// Virtually all property keys are not enumerable in vanilla
|
||||
const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']
|
||||
return new Proxy(obj, {
|
||||
ownKeys(target) {
|
||||
return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))
|
||||
},
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
if (blacklist.includes(prop)) {
|
||||
return undefined
|
||||
}
|
||||
return Reflect.getOwnPropertyDescriptor(target, prop)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const magicArray = []
|
||||
|
||||
// Loop through our fake data and use that to create convincing entities
|
||||
dataArray.forEach(data => {
|
||||
magicArray.push(makeItem(data))
|
||||
})
|
||||
|
||||
// Add direct property access based on types (e.g. `obj['application/pdf']`) afterwards
|
||||
magicArray.forEach(entry => {
|
||||
defineProp(magicArray, entry[itemMainProp], entry)
|
||||
})
|
||||
|
||||
// This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)`
|
||||
const magicArrayObj = Object.create(proto, {
|
||||
...Object.getOwnPropertyDescriptors(magicArray),
|
||||
|
||||
// There's one ugly quirk we unfortunately need to take care of:
|
||||
// The `MimeTypeArray` prototype has an enumerable `length` property,
|
||||
// but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`.
|
||||
// To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap.
|
||||
length: {
|
||||
value: magicArray.length,
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length`
|
||||
}
|
||||
})
|
||||
|
||||
// Generate our functional function mocks :-)
|
||||
const functionMocks = fns.generateFunctionMocks(utils)(
|
||||
proto,
|
||||
itemMainProp,
|
||||
magicArray
|
||||
)
|
||||
|
||||
// We need to overlay our custom object with a JS Proxy
|
||||
const magicArrayObjProxy = new Proxy(magicArrayObj, {
|
||||
get(target, key = '') {
|
||||
// Redirect function calls to our custom proxied versions mocking the vanilla behavior
|
||||
if (key === 'item') {
|
||||
return functionMocks.item
|
||||
}
|
||||
if (key === 'namedItem') {
|
||||
return functionMocks.namedItem
|
||||
}
|
||||
if (proto === PluginArray.prototype && key === 'refresh') {
|
||||
return functionMocks.refresh
|
||||
}
|
||||
// Everything else can pass through as normal
|
||||
return utils.cache.Reflect.get(...arguments)
|
||||
},
|
||||
ownKeys(target) {
|
||||
// There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sense
|
||||
// This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length`
|
||||
// My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly
|
||||
// For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing
|
||||
// Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing
|
||||
const keys = []
|
||||
const typeProps = magicArray.map(mt => mt[itemMainProp])
|
||||
typeProps.forEach((_, i) => keys.push(`${i}`))
|
||||
typeProps.forEach(propName => keys.push(propName))
|
||||
return keys
|
||||
},
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
if (prop === 'length') {
|
||||
return undefined
|
||||
}
|
||||
return Reflect.getOwnPropertyDescriptor(target, prop)
|
||||
}
|
||||
})
|
||||
|
||||
return magicArrayObjProxy
|
||||
}
|
||||
18
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.js
generated
vendored
Normal file
18
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/* global MimeType MimeTypeArray */
|
||||
|
||||
/**
|
||||
* Generate a convincing and functional MimeTypeArray (with mime types) from scratch.
|
||||
*
|
||||
* Note: This is meant to be run in the context of the page.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray
|
||||
*/
|
||||
module.exports.generateMimeTypeArray = (utils, fns) => mimeTypesData => {
|
||||
return fns.generateMagicArray(utils, fns)(
|
||||
mimeTypesData,
|
||||
MimeTypeArray.prototype,
|
||||
MimeType.prototype,
|
||||
'type'
|
||||
)
|
||||
}
|
||||
208
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.test.js
generated
vendored
Normal file
208
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/mimeTypes.test.js
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
const test = require('ava')
|
||||
|
||||
const { vanillaPuppeteer, addExtra } = require('../../test/util')
|
||||
|
||||
const Plugin = require('.')
|
||||
|
||||
test('stealth: will have convincing mimeTypes', async t => {
|
||||
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await page.evaluate(() => {
|
||||
// We need to help serializing the error or it won't survive being sent back from `page.evaluate`
|
||||
const catchErr = function(fn, ...args) {
|
||||
try {
|
||||
return fn.apply(this, args)
|
||||
} catch ({ name, message, stack }) {
|
||||
return { name, message, stack, str: stack.split('\n')[0] }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mimeTypes: {
|
||||
exists: 'mimeTypes' in navigator,
|
||||
isArray: Array.isArray(navigator.mimeTypes),
|
||||
length: navigator.mimeTypes.length,
|
||||
// value: navigator.mimeTypes,
|
||||
toString: navigator.mimeTypes.toString(),
|
||||
toStringProto: navigator.mimeTypes.__proto__.toString(), // eslint-disable-line no-proto
|
||||
protoSymbol: navigator.mimeTypes.__proto__[Symbol.toStringTag], // eslint-disable-line no-proto
|
||||
// valueOf: navigator.mimeTypes.valueOf(),
|
||||
valueOfSame: navigator.mimeTypes.valueOf() === navigator.mimeTypes,
|
||||
json: JSON.stringify(navigator.mimeTypes),
|
||||
hasPropPush: 'push' in navigator.mimeTypes,
|
||||
hasPropLength: 'length' in navigator.mimeTypes,
|
||||
hasLengthDescriptor: !!Object.getOwnPropertyDescriptor(
|
||||
navigator.mimeTypes,
|
||||
'length'
|
||||
),
|
||||
propertyNames: JSON.stringify(
|
||||
Object.getOwnPropertyNames(navigator.mimeTypes)
|
||||
),
|
||||
lengthInProps: Object.getOwnPropertyNames(navigator.mimeTypes).includes(
|
||||
'length'
|
||||
),
|
||||
keys: JSON.stringify(Object.keys(navigator.mimeTypes)),
|
||||
namedPropsAuthentic: (function() {
|
||||
navigator.mimeTypes.alice = 'bob'
|
||||
return navigator.mimeTypes.namedItem('alice') === null // true on chrome
|
||||
})(),
|
||||
loopResult: (function() {
|
||||
let res = ''
|
||||
for (var bK = 0; bK < window.navigator.mimeTypes.length; bK++)
|
||||
bK === window.navigator.mimeTypes.length - 1
|
||||
? (res += window.navigator.mimeTypes[bK].type)
|
||||
: (res += window.navigator.mimeTypes[bK].type + ',')
|
||||
return res
|
||||
})()
|
||||
},
|
||||
namedItem: {
|
||||
exists: 'namedItem' in navigator.mimeTypes,
|
||||
toString: navigator.mimeTypes.namedItem.toString(),
|
||||
resultNotFound: navigator.mimeTypes.namedItem('foo'),
|
||||
resultFound: navigator.mimeTypes // eslint-disable-line no-proto
|
||||
.namedItem('application/pdf')
|
||||
.__proto__.toString(),
|
||||
errors: {
|
||||
// For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.mimeTypes`
|
||||
noArgs: catchErr.bind(navigator.mimeTypes)(
|
||||
navigator.mimeTypes.namedItem
|
||||
).str,
|
||||
noStackLeaks: !catchErr
|
||||
.bind(navigator.mimeTypes)(navigator.mimeTypes.namedItem)
|
||||
.stack.includes(`.apply`),
|
||||
protoCall: catchErr.bind(navigator.mimeTypes)(
|
||||
navigator.mimeTypes.__proto__.namedItem // eslint-disable-line no-proto
|
||||
).str
|
||||
}
|
||||
},
|
||||
item: {
|
||||
exists: 'item' in navigator.mimeTypes,
|
||||
toString: navigator.mimeTypes.item.toString(),
|
||||
resultNotFound: navigator.mimeTypes.item('madness').type,
|
||||
resultNotFoundNumberString: navigator.mimeTypes.item('777'),
|
||||
resultEmptyString: navigator.mimeTypes.item('').type,
|
||||
resultByNumberString: navigator.mimeTypes.item('2').type,
|
||||
resultByNumberStringZero: navigator.mimeTypes.item('0').type,
|
||||
resultByNumber: navigator.mimeTypes.item(2).type,
|
||||
resultNull: navigator.mimeTypes.item(null).type,
|
||||
resultFound: navigator.mimeTypes.item('application/x-nacl').type,
|
||||
resultBrackets: navigator.mimeTypes['application/x-pnacl'].type,
|
||||
errors: {
|
||||
// For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.mimeTypes`
|
||||
noArgs: catchErr.bind(navigator.mimeTypes)(navigator.mimeTypes.item)
|
||||
.str,
|
||||
noStackLeaks: !catchErr
|
||||
.bind(navigator.mimeTypes)(navigator.mimeTypes.item)
|
||||
.stack.includes(`.apply`),
|
||||
protoCall: catchErr.bind(navigator.mimeTypes)(
|
||||
navigator.mimeTypes.__proto__.item // eslint-disable-line no-proto
|
||||
).str
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.deepEqual(results.mimeTypes, {
|
||||
exists: true,
|
||||
hasPropPush: false,
|
||||
hasPropLength: true,
|
||||
hasLengthDescriptor: false,
|
||||
isArray: false,
|
||||
json: `{"0":{},"1":{},"2":{},"3":{}}`,
|
||||
keys: `["0","1","2","3"]`,
|
||||
length: 4,
|
||||
lengthInProps: false,
|
||||
loopResult:
|
||||
'application/pdf,application/x-google-chrome-pdf,application/x-nacl,application/x-pnacl',
|
||||
namedPropsAuthentic: true,
|
||||
propertyNames: `["0","1","2","3","application/pdf","application/x-google-chrome-pdf","application/x-nacl","application/x-pnacl"]`,
|
||||
protoSymbol: 'MimeTypeArray',
|
||||
toString: '[object MimeTypeArray]',
|
||||
toStringProto: '[object MimeTypeArray]',
|
||||
valueOfSame: true
|
||||
})
|
||||
|
||||
t.deepEqual(results.namedItem, {
|
||||
exists: true,
|
||||
toString: 'function namedItem() { [native code] }',
|
||||
resultFound: '[object MimeType]',
|
||||
resultNotFound: null,
|
||||
|
||||
errors: {
|
||||
noArgs:
|
||||
"TypeError: Failed to execute 'namedItem' on 'MimeTypeArray': 1 argument required, but only 0 present.",
|
||||
noStackLeaks: true,
|
||||
protoCall: 'TypeError: Illegal invocation'
|
||||
}
|
||||
})
|
||||
|
||||
t.deepEqual(results.item, {
|
||||
exists: true,
|
||||
resultBrackets: 'application/x-pnacl',
|
||||
resultByNumber: 'application/x-nacl',
|
||||
resultByNumberString: 'application/x-nacl',
|
||||
resultByNumberStringZero: 'application/pdf',
|
||||
resultEmptyString: 'application/pdf',
|
||||
resultFound: 'application/pdf',
|
||||
resultNotFound: 'application/pdf',
|
||||
resultNotFoundNumberString: null,
|
||||
resultNull: 'application/pdf',
|
||||
toString: 'function item() { [native code] }',
|
||||
errors: {
|
||||
noArgs:
|
||||
"TypeError: Failed to execute 'item' on 'MimeTypeArray': 1 argument required, but only 0 present.",
|
||||
noStackLeaks: true,
|
||||
protoCall: 'TypeError: Illegal invocation'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('stealth: will have convincing mimeType entry', async t => {
|
||||
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await page.evaluate(() => ({
|
||||
mimeType: {
|
||||
exists: !!navigator.mimeTypes[0],
|
||||
toString: navigator.mimeTypes[0].toString(),
|
||||
toStringProto: navigator.mimeTypes[0].__proto__.toString(), // eslint-disable-line no-proto
|
||||
protoSymbol: navigator.mimeTypes[0].__proto__[Symbol.toStringTag], // eslint-disable-line no-proto
|
||||
enabledPlugin: !!navigator.mimeTypes[0].enabledPlugin, // should not throw
|
||||
enabledPlugin2: !!navigator.mimeTypes['application/pdf'].enabledPlugin, // should not throw
|
||||
enabledPlugins: !!navigator.mimeTypes[0].enabledPlugins, // regression: should not exist (anymore)
|
||||
pdfPlugin: JSON.stringify(
|
||||
navigator.mimeTypes['application/pdf'].enabledPlugin
|
||||
),
|
||||
length: !!navigator.mimeTypes[0].length, // should not throw and return mimeTypes length
|
||||
lengthDescriptor: !!Object.getOwnPropertyDescriptor(
|
||||
navigator.mimeTypes[0],
|
||||
'length'
|
||||
),
|
||||
json: JSON.stringify(navigator.mimeTypes[0]),
|
||||
propertyNames: JSON.stringify(
|
||||
Object.getOwnPropertyNames(navigator.mimeTypes[0])
|
||||
),
|
||||
nested:
|
||||
navigator.mimeTypes['application/pdf'].enabledPlugin[0].enabledPlugin[0]
|
||||
.enabledPlugin[0].enabledPlugin[0].enabledPlugin[0].suffixes
|
||||
}
|
||||
}))
|
||||
t.deepEqual(results.mimeType, {
|
||||
exists: true,
|
||||
protoSymbol: 'MimeType',
|
||||
toString: '[object MimeType]',
|
||||
toStringProto: '[object MimeType]',
|
||||
enabledPlugin: true,
|
||||
enabledPlugin2: true,
|
||||
enabledPlugins: false,
|
||||
pdfPlugin: '{"0":{}}',
|
||||
length: false,
|
||||
lengthDescriptor: false,
|
||||
json: '{}',
|
||||
propertyNames: '[]',
|
||||
nested: 'pdf'
|
||||
})
|
||||
})
|
||||
4
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/package.json
generated
vendored
Normal file
4
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"main": "index.js"
|
||||
}
|
||||
18
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.js
generated
vendored
Normal file
18
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/* global Plugin PluginArray */
|
||||
|
||||
/**
|
||||
* Generate a convincing and functional PluginArray (with plugins) from scratch.
|
||||
*
|
||||
* Note: This is meant to be run in the context of the page.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/PluginArray
|
||||
*/
|
||||
module.exports.generatePluginArray = (utils, fns) => pluginsData => {
|
||||
return fns.generateMagicArray(utils, fns)(
|
||||
pluginsData,
|
||||
PluginArray.prototype,
|
||||
Plugin.prototype,
|
||||
'name'
|
||||
)
|
||||
}
|
||||
184
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.test.js
generated
vendored
Normal file
184
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/plugins.test.js
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
const test = require('ava')
|
||||
|
||||
const { vanillaPuppeteer, addExtra } = require('../../test/util')
|
||||
|
||||
const Plugin = require('.')
|
||||
|
||||
test('stealth: will have convincing plugins', async t => {
|
||||
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await page.evaluate(() => {
|
||||
// We need to help serializing the error or it won't survive being sent back from `page.evaluate`
|
||||
const catchErr = function(fn, ...args) {
|
||||
try {
|
||||
return fn.apply(this, args)
|
||||
} catch ({ name, message, stack }) {
|
||||
return { name, message, stack, str: stack.split('\n')[0] }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
plugins: {
|
||||
exists: 'plugins' in navigator,
|
||||
isArray: Array.isArray(navigator.plugins),
|
||||
length: navigator.plugins.length,
|
||||
// value: navigator.plugins,
|
||||
toString: navigator.plugins.toString(),
|
||||
toStringProto: navigator.plugins.__proto__.toString(), // eslint-disable-line no-proto
|
||||
protoSymbol: navigator.plugins.__proto__[Symbol.toStringTag], // eslint-disable-line no-proto
|
||||
// valueOf: navigator.plugins.valueOf(),
|
||||
valueOfSame: navigator.plugins.valueOf() === navigator.plugins,
|
||||
json: JSON.stringify(navigator.plugins),
|
||||
hasPropPush: 'push' in navigator.plugins,
|
||||
hasPropLength: 'length' in navigator.plugins,
|
||||
hasLengthDescriptor: !!Object.getOwnPropertyDescriptor(
|
||||
navigator.plugins,
|
||||
'length'
|
||||
),
|
||||
propertyNames: JSON.stringify(
|
||||
Object.getOwnPropertyNames(navigator.plugins)
|
||||
),
|
||||
lengthInProps: Object.getOwnPropertyNames(navigator.plugins).includes(
|
||||
'length'
|
||||
),
|
||||
keys: JSON.stringify(Object.keys(navigator.plugins)),
|
||||
loopResult: [...navigator.plugins].map(p => p.name).join(',')
|
||||
},
|
||||
namedItem: {
|
||||
exists: 'namedItem' in navigator.plugins,
|
||||
toString: navigator.plugins.namedItem.toString(),
|
||||
resultNotFound: navigator.plugins.namedItem('foo'),
|
||||
resultFound: navigator.plugins // eslint-disable-line no-proto
|
||||
.namedItem('Chrome PDF Viewer')
|
||||
.__proto__.toString(),
|
||||
errors: {
|
||||
// For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.plugins`
|
||||
noArgs: catchErr.bind(navigator.plugins)(navigator.plugins.namedItem)
|
||||
.str,
|
||||
noStackLeaks: !catchErr
|
||||
.bind(navigator.plugins)(navigator.plugins.namedItem)
|
||||
.stack.includes(`.apply`),
|
||||
protoCall: catchErr.bind(navigator.plugins)(
|
||||
navigator.plugins.__proto__.namedItem // eslint-disable-line no-proto
|
||||
).str
|
||||
}
|
||||
},
|
||||
item: {
|
||||
exists: 'item' in navigator.plugins,
|
||||
toString: navigator.plugins.item.toString(),
|
||||
resultNotFound: navigator.plugins.item('madness').name,
|
||||
resultNotFoundNumberString: navigator.plugins.item('777'),
|
||||
resultEmptyString: navigator.plugins.item('').name,
|
||||
resultByNumberString: navigator.plugins.item('2').name,
|
||||
resultByNumberStringZero: navigator.plugins.item('0').name,
|
||||
resultByNumber: navigator.plugins.item(2).name,
|
||||
resultNull: navigator.plugins.item(null).name,
|
||||
resultFound: navigator.plugins.item('application/x-nacl').name,
|
||||
errors: {
|
||||
// For whatever weird reason the normal context doesn't suffice, we need to bind this to `navigator.plugins`
|
||||
noArgs: catchErr.bind(navigator.plugins)(navigator.plugins.item).str,
|
||||
noStackLeaks: !catchErr
|
||||
.bind(navigator.plugins)(navigator.plugins.item)
|
||||
.stack.includes(`.apply`),
|
||||
protoCall: catchErr.bind(navigator.plugins)(
|
||||
navigator.plugins.__proto__.item // eslint-disable-line no-proto
|
||||
).str
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.deepEqual(results.plugins, {
|
||||
exists: true,
|
||||
hasPropLength: true,
|
||||
hasLengthDescriptor: false,
|
||||
hasPropPush: false,
|
||||
isArray: false,
|
||||
json: `{"0":{"0":{}},"1":{"0":{}},"2":{"0":{},"1":{}}}`,
|
||||
keys: `["0","1","2"]`,
|
||||
length: 3,
|
||||
lengthInProps: false,
|
||||
loopResult: 'Chrome PDF Plugin,Chrome PDF Viewer,Native Client',
|
||||
propertyNames: `["0","1","2","Chrome PDF Plugin","Chrome PDF Viewer","Native Client"]`,
|
||||
protoSymbol: 'PluginArray',
|
||||
toString: '[object PluginArray]',
|
||||
toStringProto: '[object PluginArray]',
|
||||
valueOfSame: true
|
||||
})
|
||||
|
||||
t.deepEqual(results.namedItem, {
|
||||
exists: true,
|
||||
toString: 'function namedItem() { [native code] }',
|
||||
resultFound: '[object Plugin]',
|
||||
resultNotFound: null,
|
||||
|
||||
errors: {
|
||||
noArgs:
|
||||
"TypeError: Failed to execute 'namedItem' on 'PluginArray': 1 argument required, but only 0 present.",
|
||||
noStackLeaks: true,
|
||||
protoCall: 'TypeError: Illegal invocation'
|
||||
}
|
||||
})
|
||||
|
||||
t.deepEqual(results.item, {
|
||||
exists: true,
|
||||
resultByNumber: 'Native Client',
|
||||
resultByNumberString: 'Native Client',
|
||||
resultByNumberStringZero: 'Chrome PDF Plugin',
|
||||
resultEmptyString: 'Chrome PDF Plugin',
|
||||
resultFound: 'Chrome PDF Plugin',
|
||||
resultNotFound: 'Chrome PDF Plugin',
|
||||
resultNotFoundNumberString: null,
|
||||
resultNull: 'Chrome PDF Plugin',
|
||||
toString: 'function item() { [native code] }',
|
||||
errors: {
|
||||
noArgs:
|
||||
"TypeError: Failed to execute 'item' on 'PluginArray': 1 argument required, but only 0 present.",
|
||||
noStackLeaks: true,
|
||||
protoCall: 'TypeError: Illegal invocation'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('stealth: will have convincing plugin entry', async t => {
|
||||
const puppeteer = addExtra(vanillaPuppeteer).use(Plugin())
|
||||
const browser = await puppeteer.launch({ headless: true })
|
||||
const page = await browser.newPage()
|
||||
|
||||
const results = await page.evaluate(() => ({
|
||||
plugins: {
|
||||
exists: !!navigator.plugins[0],
|
||||
toString: navigator.plugins[0].toString(),
|
||||
toStringProto: navigator.plugins[0].__proto__.toString(), // eslint-disable-line no-proto
|
||||
protoSymbol: navigator.plugins[0].__proto__[Symbol.toStringTag], // eslint-disable-line no-proto
|
||||
length: navigator.plugins[0].length, // should not throw and return mimeTypes length
|
||||
lengthDescriptor: Object.getOwnPropertyDescriptor(
|
||||
navigator.plugins[0],
|
||||
'length'
|
||||
)
|
||||
},
|
||||
plugin: {
|
||||
mtIndex: !!navigator.plugins[0][0], // mimeType should be accessible through index
|
||||
mtNamed: !!navigator.plugins[0]['application/x-google-chrome-pdf'], // mimeType should be accessible through name
|
||||
json: JSON.stringify(navigator.plugins[0]),
|
||||
propertyNames: JSON.stringify(
|
||||
Object.getOwnPropertyNames(navigator.plugins[0])
|
||||
)
|
||||
}
|
||||
}))
|
||||
t.deepEqual(results.plugins, {
|
||||
exists: true,
|
||||
protoSymbol: 'Plugin',
|
||||
toString: '[object Plugin]',
|
||||
toStringProto: '[object Plugin]',
|
||||
length: 1
|
||||
})
|
||||
t.deepEqual(results.plugin, {
|
||||
mtIndex: true,
|
||||
mtNamed: true,
|
||||
json: '{"0":{}}',
|
||||
propertyNames: '["0","application/x-google-chrome-pdf"]'
|
||||
})
|
||||
})
|
||||
25
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/readme.md
generated
vendored
Normal file
25
node_modules/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/readme.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
## API
|
||||
|
||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||
|
||||
#### Table of Contents
|
||||
|
||||
- [class: Plugin](#class-plugin)
|
||||
|
||||
### class: [Plugin](https://github.com/berstend/puppeteer-extra/blob/e6133619b051febed630ada35241664eba59b9fa/packages/puppeteer-extra-plugin-stealth/evasions/navigator.plugins/index.js#L26-L88)
|
||||
|
||||
- `opts` (optional, default `{}`)
|
||||
|
||||
**Extends: PuppeteerExtraPlugin**
|
||||
|
||||
In headless mode `navigator.mimeTypes` and `navigator.plugins` are empty.
|
||||
This plugin emulates both of these with functional mocks to match regular headful Chrome.
|
||||
|
||||
Note: mimeTypes and plugins cross-reference each other, so it makes sense to do them at the same time.
|
||||
|
||||
- **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/mimeTypes>**
|
||||
- **See: <https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray>**
|
||||
- **See: <https://developer.mozilla.org/en-US/docs/Web/API/NavigatorPlugins/plugins>**
|
||||
- **See: <https://developer.mozilla.org/en-US/docs/Web/API/PluginArray>**
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user