利用 input 的 accept 屬性進行限制
accept 屬性規定了可通過檔案上傳送出的伺服器接受的檔案類型。
<input type="file">
不限制,上傳這裡的話就會顯示所有的檔案。
限制格式1
<input type="file" accept=".pdf, .xls, .txt">
限制格式2
<input type="file" accept="image/gif, video/mp4">
加了之後就會變成自定義檔案類型的選擇
<input type="file" accept="video/*, image/*">
屬性值
值 | 描述 |
audio/* | 接受所有的聲音檔案。 |
video/* | 接受所有的視訊檔案。 |
image/* | 接受所有的圖像檔案。 |
MIME_type | 一個有效的 MIME 類型,不帶參數。請參閱 IANA MIME 類型,獲得标準 MIME 類型的完整清單。 |
标準 MIME 類型
該屬性隻能用于提示使用者選擇什麼樣的類型,因為使用者還是可以選擇所有檔案進行其他類型檔案的上傳。
利用上傳檔案的對象去限制
<!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>上傳限制檔案類型</title>
</head>
<body>
<input type="file" onchange="handleChange(event)">
<script>function handleChange(event) {
console.log(event.target.files)
}</script>
</body>
</html>
我們可以通過 file 對象的 name 或者 type 屬性去校驗檔案類型進行限制。
上面兩種,單純的添加屬性,以及以檔案字尾名(擴充名)、檔案類型進行截取的方式來進行限制,很容易繞過限制,進行上傳。為了避免這些情況,我們可以根據檔案的頭資訊,來判斷檔案真正的格式。
下面先了解一下魔數(magic number)。
什麼是魔數(magic number)?
魔數這個詞在不同領域代表不同的含義。在計算機領域,魔數有兩個含義,一指用來判斷檔案類型的魔數;二指程式代碼中的魔數,也稱魔法值。
判斷檔案類型的魔數
很多類型的檔案,其起始的幾個位元組的内容是固定的(或是有意填充,或是本就如此)。是以這幾個位元組的内容也被稱為魔數 (magic number),因為根據這幾個位元組的内容就可以确定檔案類型。
例如:
- FreeBSD 上 ELF 檔案的 magic number 就是檔案的前四個位元組依次為"7f 45 4c 46",對應的 ascii 字元串即 “^?ELF”。
- tar 檔案的 magic number 是從第 257 個位元組起為 “ustar”。
- PE 檔案中,在 DOS-根之後是一個 32 位的簽名以及魔數
(IMAGE_NT_SIGNATURE) 意為“NT簽名”,也就是PE簽名;十六進制數 45 和 50 分别代表 ASCII 碼字母 P 和 E,它使任何 PE 檔案都是一個有效的 MS-DOS 可執行檔案。0x00004550
- Java 的
檔案,開頭的 4 個位元組.class
,這是 Java 初代開發小組最喜歡的一種咖啡 Peet’s Coffee 的愛稱。0xCAFEBABE
Linux 作業系統下沒有檔案擴充名,判斷檔案類型憑借的就是檔案的内容,也就是魔數。
linux 指令 file 實作原理:大緻就是讀取一個檔案的前面 1024 個位元組,然後根據 magic (
/etc/magic
或者
/usr/share/misc/magic
) 裡對應的規則分析出檔案頭,并列印輸出。
檔案頭标志大全
參考:檔案頭标志大全
比如:png 的頭檔案辨別
利用十六進制編輯器檢視魔數
大家可以先安裝十六進制編輯器【軟體推薦】免費的Windows十六進制編輯器推薦,我這邊安裝的是 HxD 編輯器。
下面打開一個 png 檔案看看。
點選檔案,選擇打開,選擇一張 png 圖檔,點選打開。
打開之後我們可以看到界面的資料如下:
我們可以看到前面 8 個
89 50 4E 47 0D 0A 1A 0A
符合頭檔案辨別裡的 png 類型
我們再看一個 flv 檔案,我們可以看到前面 4 個
46 4C 56 01
符合頭檔案辨別裡的 flv 類型
怎麼擷取檔案的十六進制資料進行限制?
File 對象是特殊類型的 Blob,且可以用在任意的 Blob 類型的 context 中。比如說,
FileReader, URL.createObjectURL(), createImageBitmap() (en-US), 及 XMLHttpRequest.send()
都能處理 Blob 和 File。
<!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>上傳限制檔案類型</title>
</head>
<body>
<input type="file" onchange="handleChange(event)">
<script>function handleChange(event) {
console.log(event.target.files)
const [ file ] = event.target.files;
console.log(file);
const fileReader = new FileReader();
// readAsArrayBuffer 開始讀取指定的 Blob中的内容,一旦完成,result 屬性中儲存的将是被讀取檔案的 ArrayBuffer 資料對象。
fileReader.readAsArrayBuffer(file);
// 該事件在讀取操作完成時觸發。
fileReader.onload = function(e) {
console.log("讀取檔案 ArrayBuffer 資料成功", e);
console.log(fileReader.result);
}
// 該事件在讀取操作發生錯誤時觸發。
fileReader.onerror = function(err) {
console.error("讀取檔案 ArrayBuffer 資料失敗", err);
}
}</script>
</body>
</html>
我們上傳一個 png 的圖檔,看看列印的資料:
接下來需要将這個 ArrayBuffer 資料轉化成16進制的資料,我們改造一下代碼:
<!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>上傳限制檔案類型</title>
</head>
<body>
<input type="file" onchange="handleChange(event)">
<script>// ArrayBuffer 轉 16進度字元串
function ab2hex(buffer){
// Uint8Array 數組類型表示一個 8 位無符号整型數組,建立時内容被初始化為 0。建立完後,可以以對象的方式或使用數組下标索引的方式引用數組中的元素。
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
console.log(bit)
return ("00" + bit.toString(16)).slice(-2).toUpperCase();
}
)
return hexArr.join(" ");
}
function handleChange(event) {
console.log(event.target.files)
const [ file ] = event.target.files;
console.log(file);
const fileReader = new FileReader();
// readAsArrayBuffer 開始讀取指定的 Blob中的内容,一旦完成,result 屬性中儲存的将是被讀取檔案的 ArrayBuffer 資料對象。
fileReader.readAsArrayBuffer(file.slice(0, 8));
// 該事件在讀取操作完成時觸發。
fileReader.onload = function(e) {
console.log("讀取檔案 ArrayBuffer 資料成功", e);
const hex = ab2hex(fileReader.result);
console.log(hex);
console.log("檔案類型是png嗎?", hex === "89 50 4E 47 0D 0A 1A 0A");
}
// 該事件在讀取操作發生錯誤時觸發。
fileReader.onerror = function(err) {
console.error("讀取檔案 ArrayBuffer 資料失敗", err);
}
}</script>
</body>
</html>
上傳一張 png,測試結果如下,發現沒有問題
上傳一個 flv ,結果如下:輸出了 hex 為
46 4C 56 01 01 00 00 00
,前面 4 個滿足 flv 類型。
我們可以自己封裝方法去校驗檔案的類型,比如,通過 file 跟截取的長度去封裝一個傳回 hex 的公共函數,然後其他的類型函數做判斷處理。
我用 vue 實作一下類似的方法:
<template>
<div class="about">
<h1>kaimo 上傳限制檔案類型</h1>
<input type="file" @change="handleChange($event)" />
</div>
</template>
<script>export default {
name: "home",
data() {
return {};
},
methods: {
// ArrayBuffer 轉 16進度字元串
ab2hex(buffer) {
// Uint8Array 數組類型表示一個 8 位無符号整型數組,建立時内容被初始化為 0。建立完後,可以以對象的方式或使用數組下标索引的方式引用數組中的元素。
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
console.log(bit);
return ("00" + bit.toString(16)).slice(-2).toUpperCase();
}
);
return hexArr.join(" ");
},
// 擷取檔案 16 進度字元串
getFileHex(file,) {
return new Promise((resolve,) => {
const fileReader = new FileReader();
// readAsArrayBuffer 開始讀取指定的 Blob中的内容,一旦完成,result 屬性中儲存的将是被讀取檔案的 ArrayBuffer 資料對象。
fileReader.readAsArrayBuffer(file.slice(0, num));
// 該事件在讀取操作完成時觸發。
fileReader.onload = e => {
console.log("讀取檔案 ArrayBuffer 資料成功", e);
const hex = this.ab2hex(fileReader.result);
console.log(hex);
resolve(hex);
};
// 該事件在讀取操作發生錯誤時觸發。
fileReader.onerror = err => {
console.error("讀取檔案 ArrayBuffer 資料失敗", err);
reject(err);
};
});
},
// 校驗是否是png
async checkFileIsPng(file) {
const res = await this.getFileHex(file, 8);
console.log("校驗 png hex", res);
return res === "89 50 4E 47 0D 0A 1A 0A";
},
// 校驗是否是flv
async checkFileIsFlv(file) {
const res = await this.getFileHex(file, 4);
console.log("校驗 flv hex", res);
return res === "46 4C 56 01";
},
// 上傳檔案改變
async handleChange(event) {
console.log(event.target.files);
const [file] = event.target.files;
console.log(file);
console.log("檔案類型是png嗎?", await this.checkFileIsPng(file));
console.log("檔案類型是flv嗎?", await this.checkFileIsFlv(file));
},
},
};</script>
上傳一張 png 圖檔
上傳一個 flv 檔案
這樣一個一個寫有點麻煩,有沒有更好的方式去檢測二進制的檔案格式。有,那就是使用
file-type
這個庫。
利用 file-type 庫進行檔案類型的限制
file-type 庫用于檢測
Buffer/Uint8Array/ArrayBuffer
的檔案類型。檢測基于二進制的檔案格式,而不是基于文本的格式,如
.txt、.csv、.svg
等。
原理:通過檢查緩沖區的魔數來檢測檔案類型。
安裝依賴:
npm
這裡我們使用
16.0.0
https://github.com/sindresorhus/file-type/tree/v16.0.0版本的,我試了一下
17.1.4
版本的,使用
file-type
的時候報錯如下:
可能是我 node 版本太低,我的node版本是
12.13.0
用法:從緩沖區确定檔案類型,緩沖區可能是檔案開頭的一部分:
代碼如下:
<template>
<div class="file-type">
<h1>kaimo 測試 file-type 庫</h1>
<input type="file" @change="handleChange($event)" />
</div>
</template>
<script>const FileType = require('file-type');
export default {
methods: {
// 上傳檔案改變
handleChange(event) {
console.log(event.target.files);
const [file] = event.target.files;
console.log(file);
const fileReader = new FileReader();
// readAsArrayBuffer 開始讀取指定的 Blob中的内容,一旦完成,result 屬性中儲存的将是被讀取檔案的 ArrayBuffer 資料對象。
fileReader.readAsArrayBuffer(file);
// 該事件在讀取操作完成時觸發。
fileReader.onload = (async (e) => {
console.log("讀取檔案 ArrayBuffer 資料成功", e);
console.log(fileReader.result);
console.log(await FileType.fromBuffer(fileReader.result));
});
// 該事件在讀取操作發生錯誤時觸發。
fileReader.onerror = (err) => {
console.error("讀取檔案 ArrayBuffer 資料失敗", err);
};
},
},
};</script>
測試結果:
上傳 png 檔案,列印出來了:
{ext: 'png', mime: 'image/png'}
上傳 flv 檔案,列印出來了:
{ext: 'flv', mime: 'video/x-flv'}
這樣就很友善我們去判斷上傳的檔案類型。
參考資料
- Web前端 Js檔案上傳類型限制(根據檔案頭資訊判斷)
- IANA MIME 類型
- 菜鳥教程:HTML <input>
- W3school:HTML <input>
- 魔數
- linux file 指令原理
- 檔案頭标志大全
- 【軟體推薦】免費的Windows十六進制編輯器推薦
- MDN:File
- MDN:FileReader
- JavaScript toString() 方法
- Uint8Array
- javaScript中十六進制轉浮點、字元串轉為ArrayBuffer對象、ArrayBuffer轉16進度字元串、16進制轉10進制、crc校驗位、十六進制轉包含中文的字元串(包含小程式和浏覽器)
- file-type
拓展資料
- padStart()方法,padEnd()方法
- String.prototype.charCodeAt()