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>
