PS:此文章來源於https://juejin.cn/post/7046002905278578725
文件上傳是工作中常見的業務需求,很多情況下,我們需要限制文件的上傳類型,比如只能上傳圖片。通常我們是通過input
元素的accept
屬性來限制文件的類型:
<input id="file" type="file" accept="image/*" />
或者通過截取文件名后綴的方式來判斷:
const ext = file.name.substring(file.name.lastIndexOf('.') + 1);
這樣做看似沒有毛病,但如果把其他文件的后綴名改為圖片格式,就可以成功突破這個限制。以上兩種方式都不嚴謹,存在一定的安全隱患。那么應該如何解決這個問題呢?
一、查看文件的頭信息
所有文件在計算機中都是以二進制形式進行存儲的,但二進制數據是不方便做判斷的,我們可以利用 vscode 插件hexdump for VSCode
以十六進制的形式查看二進制文件。安裝完成后,點擊右上角的小圖標,即可查看文件的十六進制信息:
那么,我們分別查看一下jpg png gif
的十六進制頭信息:
多打開幾個文件試試,你會發現同一種類型的文件,他們的頭信息是完全相同的。接下來,我們就可以根據頭信息來判斷文件類型了。
二、根據頭信息判斷文件類型
1. 將文件轉為十六進制字符串
在獲取文件對象后,我們可以通過FileReader API
來讀取文件的內容,然后將結果轉為Unicode
編碼,再轉為十六進制,以下是我封裝的將文件轉為十六進制字符串的方法:
async blobToString(blob) { return new Promise(resolve => { const reader = new FileReader() reader.onload = function() { const res = reader.result .split("") // 將讀取結果分割為數組 .map(v => v.charCodeAt()) // 轉為 Unicode 編碼 .map(v => v.toString(16).toUpperCase()) // 轉為十六進制,再轉大寫 .map(v => v.padStart(2, "0")) // 個位數補0 .join(" "); // 轉為字符串 resolve(res) } reader.readAsBinaryString(blob) // 將文件讀取為二進制字符串 }) }
2. 判斷文件類型
其實沒有必要將整個文件轉為十六進制,我們只需要截取文件的前幾個字節,然后將截取后的文件轉為十六進制,再進行比對就可以了
// 判斷是否為 jpg 格式 async function isJpg(file) { const res = await blobToString(file.slice(0, 3)) return res === 'FF D8 FF' } // 判斷是否為 png 格式 async function isPng(file) { const res = await blobToString(file.slice(0, 4)) return res === '89 50 4E 47' } // 判斷是否為 gif 格式 async function isGif(file) { const res = await blobToString(file.slice(0, 4)) return res === '47 49 46 38' }
3.完整代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <input id="file" type="file" /> <script> file.addEventListener('change', async e => { const file = e.target.files[0] const flag = await isImage(file) if (flag) { alert('上傳格式通過!') } else { alert('請上傳正確的格式!') } }) // 判斷是否為圖片 async function isImage(file) { return (await isGif(file)) || (await isPng(file)) || (await isJpg(file)) } // 判斷是否為 jpg 格式 async function isJpg(file) { const res = await blobToString(file.slice(0, 3)) return res === 'FF D8 FF' } // 判斷是否為 png 格式 async function isPng(file) { const res = await blobToString(file.slice(0, 4)) return res === '89 50 4E 47' } // 判斷是否為 gif 格式 async function isGif(file) { const res = await blobToString(file.slice(0, 4)) return res === '47 49 46 38' } // 將文件轉為十六進制字符串 async function blobToString(blob) { return new Promise(resolve => { const reader = new FileReader() reader.onload = function () { const res = reader.result .split('') // 將讀取結果分割為數組 .map(v => v.charCodeAt()) // 轉為 Unicode 編碼 .map(v => v.toString(16).toUpperCase()) // 轉為十六進制,再轉大寫 .map(v => v.padStart(2, '0')) // 個位數補0 .join(' ') // 轉為字符串 resolve(res) } reader.readAsBinaryString(blob) // 將文件讀取為二進制字符串 }) } </script> </body> </html>