談談我是如何得到知乎的前端源碼的

文章首發於個人我的項目SunHuawei/SourceDetectorjavascript

緣起

此前我在回答這個問題的時候提到,「我偶然間得到了知乎的源碼」。本文將解釋我是如何「偶然獲取」的。另外本repo便是由此而生的一個chrome extension。html

Source Map

前端工程化的一個重要部分就是就是源碼轉換,一方面壓縮體積,另外一方面合併文件。固然還有多是爲了轉換Typescript、ES6+或其餘代碼。但一般轉換完的代碼難以閱讀和調試。Source Map就是爲了解決這個問題而出現的。前端

關於Source Map的詳細信息,推薦阮一峯的這篇文章-JavaScript Source Map 詳解java

故事

話說,某天我在逛知乎的時候習慣性的打開了Chrome Dev-tools,在Sources欄下居然發現了一個webpack://目錄。用過webpack source map的前端應該馬上就會反應過來-哇,有源碼!因而我便如此「偶然得到」了文件zfeedback.js.mapnode

故事還沒完。webpack

事實上,我此時是能夠直接查看各個源碼文件的。只是如何將其保存到本地呢?我嘗試點擊右鍵,貌似並無保存整個目錄到本地的選項,看起來只能一個一個文件的保存,好累。git

受好奇心驅使,我在github上搜了一圈,找到了一個開源項目-mozilla/source-map。因而本身手動寫了些代碼便將整個目錄下載到了本地。啊哈~github

源碼以下,可用node app.js執行。web

// app.js
const fs = require('fs-extra')
const https = require('https')
const crypto = require('crypto')

const SourceMapConsumer = require('source-map').SourceMapConsumer

const analyse = (srcMapURL) => {
    const BASE_CACHE_PATH = __dirname + '/cache/'
    const BASE_OUTPUT_PATH = __dirname + '/output/' + srcMapURL.substr(srcMapURL.lastIndexOf('/') + 1) + '/'
    const BASE_OUTPUT_LIB_PATH = BASE_OUTPUT_PATH + 'node_modules/'

    const md5 = (content) => {
        let md5Maker = crypto.createHash('md5');
        md5Maker.update(content);
        return md5Maker.digest('hex');
    }

    const download = (url, callback) => {
        const hash = md5(url)
        const cacheFileName = BASE_CACHE_PATH + hash
        if (fs.existsSync(cacheFileName)) {
            fs.readFile(cacheFileName, 'utf8', (err, data) => {
                console.log("From cache")
                callback(data)
            })
        } else {
            return https.get(url, function(response) {
                let body = '';

                let totalSize = parseInt(response.headers['content-length'])

                response.on('data', function(d) {
                    body += d
                    printDownloading(body, totalSize)
                });

                response.on('end', function() {
                    printFinishDownload(body)
                    fs.outputFile(cacheFileName, body, error => {
                        callback(body)
                    })
                });
            });
        }
    }

    const printDownloading = (body, totalSize) => {
        let statusLine = '\r'
        statusLine += 'Downloading '
        statusLine += srcMapURL.substr(srcMapURL.lastIndexOf('/') + 1)
        statusLine += ' '
        statusLine += (body.length / totalSize * 100).toFixed(2)
        statusLine += '%'
        process.stdout.write(statusLine)
    }

    const printFinishDownload = (body) => {
        let statusLine = 'Finish Download '
        statusLine += srcMapURL.substr(srcMapURL.lastIndexOf('/') + 1)
        statusLine += ' total size: '
        statusLine += body.length
        statusLine += 'bytes'
        console.log('\n' + statusLine)
    }

    download(srcMapURL, (rawSourceMap) => {
        try {
            const consumer = new SourceMapConsumer(rawSourceMap);

            if (consumer.hasContentsOfAllSources()) {
                consumer.sources.forEach(fileName => {
                    if (fileName.indexOf('webpack://') !== 0) {
                        return
                    }

                    let fileContent = consumer.sourceContentFor(fileName)
                    fileName = fileName.replace(/^webpack:\/\//, '')
                    fileName = fileName.replace(/^\//, BASE_OUTPUT_PATH)
                    fileName = fileName.replace(/^.*\/\~\//, BASE_OUTPUT_LIB_PATH)
                    fs.outputFile(fileName, fileContent, error => {
                        // console.log(error) // TODO, debug code, to delete before commit
                    })
                })

                console.log('Please check here for sources: ', BASE_OUTPUT_PATH)
            } else {
                console.log('TODO')
            }
        } catch (e) {
            console.log("Failed to parse", srcMapURL) // TODO, debug code, to delete before commit
        }
    })
}

let jsURLs = `
https://zhstatic.zhihu.com/assets/zfeedback/3.0.13/zfeedback.js
`

jsURLs.split('\n').filter(Boolean).forEach(jsURL => {
    const srcMapURL = jsURL + '.map'
    analyse(srcMapURL)
})

以後的故事是,我將分析源碼的過程寫到了這個回答。以後知乎某員工詢問我如何獲取的源碼,建議我與知乎開發及安全團隊取得聯繫,我解釋了該過程,而後知乎修復了問題。chrome

過後

不過依然不過癮。這樣只能是當我有了某個.map文件時能夠解析出源文件。若是能有一個工具隨時提醒我,我訪問的某個網站有源碼,並幫我下載下來就更完美了。因而便有了這個Chrome extension

安裝

Chrome web store

安裝地址https://chrome.google.com/webstore/detail/source-detecotor/aioimldmpakibclgckpdfpfkadbflfkn?hl=zh-CN&gl=CN

源碼安裝

  1. git clone https://github.com/SunHuawei/SourceDetector.git

  2. npm install

  3. bower install

  4. gulp

  5. 打開Chrome設置-擴展程序

  6. 點擊"加載已解壓的擴展程序..."

  7. 選擇path/to/source-detector/dist目錄

source detector install.png

以後你在瀏覽任何網頁時,該插件將自動檢測是否有.map文件。其會自動按網站分組顯示源碼文件,並可點擊下載所有或部分源碼文件。

source detector-popup.png

進入webpack首頁,查看右上角的小圖標吧~

有問題?有建議?

歡迎說出你的想法。歡迎issue和PR。

相關文章
相關標籤/搜索