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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章