navigator plugins与mimetyps的模拟实现分析

前言

为了模拟实现navigator plugins与mimetyps,大致需要做四件事,调整数组类型和数组成员类型、补充缺失的函数、修改toString方法及对象替换。如下的总结虽未模拟完全,但可作为一种启发参考。其它操作与此类似。

1 调整数组类型和数组成员类型

正常navigator.plugins对象数组中成员类型是Plugin,数组类型为PluginArray。正常navigator.mimeTypes数组成员类型是MimeType,数组类型mimeTypes。

PluginArray {0: Plugin, 1: Plugin, 2: Plugin, Chrome PDF Plugin: Plugin, Chrome PDF Viewer: Plugin, Native Client: Plugin, length: 3}
MimeTypeArray {0: MimeType, 1: MimeType, 2: MimeType, 3: MimeType, application/pdf: MimeType, application/x-google-chrome-pdf: MimeType, application/x-nacl: MimeType, application/x-pnacl: MimeType, length: 4}

Plugin,MimeType,PluginsArray,MimeTypeArray均是浏览器内置构造函数。为了模拟改造,所创建模拟数据和数组成员也应是对应的类型,首先创建普通对象数组。

let mimeTypes = [{
    "description": "",
    "enabledPlugin": {},
    "suffixes": "pdf",
    "type": "application/pdf"
}, {
    "description": "Portable Document Format",
    "enabledPlugin": {},
    "suffixes": "pdf",
    "type": "application/x-google-chrome-pdf"
}, {
    "description": "Native Client Executable",
    "enabledPlugin": {},
    "suffixes": "",
    "type": "application/x-nacl"
}, {
    "description": "Portable Native Client Executable",
    "enabledPlugin": {},
    "suffixes": "",
    "type": "application/x-pnacl"
}];

调整成员类型和数组类型,需要修改原型对象。

mimeTypes.map(o => Object.setPrototypeOf(o,MimeType.prototype));
Object.setPrototypeOf(mimeTypes,MimeTypeArray.prototype);

2 补充缺失的函数

这两个对象在原型链上还可以找到其它辅助函数。

navigator.plugins.__proto__

PluginArray {item: ƒ, namedItem: ƒ, refresh: ƒ, constructor: ƒ,}
length: (...)
item: ƒ item()
namedItem: ƒ namedItem()
refresh: ƒ refresh()
navigator.mimeTypes.__proto__

MimeTypeArray {Symbol(Symbol.toStringTag): "MimeTypeArray", item: ƒ, namedItem: ƒ, constructor: ƒ,}
length: (...)
item: ƒ item()
namedItem: ƒ namedItem()

这个功能通过给对象直接赋值即可mimeTypes.namedItem = function() {}

3 修改toString方法

正常的toString方法如下。

navigator.plugins.item.toString();
"function item() { [native code] }"

由于调用一个函数的toString方法,如果没提供覆盖实现,最终会通过原型链找到Function对象的toString方法。因此我们只要对Function的toString加一层代理。如果发现当前调用了我们自定义的方法,那么返回一个含有native的描述。如果调用的不是我们自定义的方法则放行。

  1. 如果调用了方法代理方法本身的String,那么返回一段toString描述。
  2. 如果是调用了我们自己实现的自定义方法,则返回一段自定义文本,并改变方法的名称。
  3. 如果调用的不是我们自定义方法,则放行,调原始方法。
const makeFnsNative = (fns = []) => {
          const oldCall = Function.prototype.call
          function call () {
            return oldCall.apply(this, arguments)
          }
          // eslint-disable-next-line
          Function.prototype.call = call

          const nativeToStringFunctionString = Error.toString().replace(
            /Error/g,
            'toString'
          )
          const oldToString = Function.prototype.toString

          function functionToString () {
            for (const fn of fns) {
              if (this === fn.ref) {
                return `function ${fn.name}() { [native code] }`
              }
            }

            if (this === functionToString) {
              return nativeToStringFunctionString
            }
            return oldCall.call(oldToString, this)
          }
          // eslint-disable-next-line
          Function.prototype.toString = functionToString
        }

4 对象替换

最后就是将我们修改的结果进行替换,即重新定时属性描述符的get方法。

  Object.defineProperty(navigator, 'plugins', {
    get: () => pluginArray
  })

5 总结

以上介绍了组略介绍了修改的内容,还未模拟完全。但通过这些作为一个简单的启发,那么其它内容也可以以此方式分析进行修改。

6 参考

[1].plugin的模拟实现,https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值