天天看點

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

利用 input 的 accept 屬性進行限制

accept 屬性規定了可通過檔案上傳送出的伺服器接受的檔案類型。

<input type="file">      

不限制,上傳這裡的話就會顯示所有的檔案。

限制格式1

<input type="file" accept=".pdf, .xls, .txt">      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

限制格式2

<input type="file" accept="image/gif, video/mp4">      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

加了之後就會變成自定義檔案類型的選擇

<input type="file" accept="video/*, image/*">      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

屬性值

描述
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>      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

我們可以通過 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 位的簽名以及魔數​

    ​0x00004550​

    ​ (IMAGE_NT_SIGNATURE) 意為“NT簽名”,也就是PE簽名;十六進制數 45 和 50 分别代表 ASCII 碼字母 P 和 E,它使任何 PE 檔案都是一個有效的 MS-DOS 可執行檔案。
  • Java 的​

    ​.class​

    ​​ 檔案,開頭的 4 個位元組​

    ​0xCAFEBABE​

    ​,這是 Java 初代開發小組最喜歡的一種咖啡 Peet’s Coffee 的愛稱。

Linux 作業系統下沒有檔案擴充名,判斷檔案類型憑借的就是檔案的内容,也就是魔數。

linux 指令 file 實作原理:大緻就是讀取一個檔案的前面 1024 個位元組,然後根據 magic (​

​/etc/magic​

​ 或者 ​

​/usr/share/misc/magic​

​) 裡對應的規則分析出檔案頭,并列印輸出。

檔案頭标志大全

​​參考:檔案頭标志大全​​

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

比如:png 的頭檔案辨別

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

利用十六進制編輯器檢視魔數

大家可以先安裝十六進制編輯器​​【軟體推薦】免費的Windows十六進制編輯器推薦​​,我這邊安裝的是 HxD 編輯器。

下面打開一個 png 檔案看看。

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

點選檔案,選擇打開,選擇一張 png 圖檔,點選打開。

打開之後我們可以看到界面的資料如下:

我們可以看到前面 8 個 ​

​89 50 4E 47 0D 0A 1A 0A​

​ 符合頭檔案辨別裡的 png 類型

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

我們再看一個 flv 檔案,我們可以看到前面 4 個 ​

​46 4C 56 01​

​ 符合頭檔案辨別裡的 flv 類型

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

怎麼擷取檔案的十六進制資料進行限制?

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 的圖檔,看看列印的資料:

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

接下來需要将這個 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,測試結果如下,發現沒有問題

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

上傳一個 flv ,結果如下:輸出了 hex 為 ​

​46 4C 56 01 01 00 00 00​

​,前面 4 個滿足 flv 類型。

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

我們可以自己封裝方法去校驗檔案的類型,比如,通過 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 圖檔

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

上傳一個 flv 檔案

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

這樣一個一個寫有點麻煩,有沒有更好的方式去檢測二進制的檔案格式。有,那就是使用 ​

​file-type​

​ 這個庫。

利用 file-type 庫進行檔案類型的限制

file-type 庫用于檢測 ​

​Buffer/Uint8Array/ArrayBuffer​

​​ 的檔案類型。檢測基于二進制的檔案格式,而不是基于文本的格式,如 ​

​.txt、.csv、.svg ​

​等。

原理:通過檢查緩沖區的魔數來檢測檔案類型。

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

安裝依賴:

npm      

這裡我們使用 ​

​16.0.0​

​​ ​​https://github.com/sindresorhus/file-type/tree/v16.0.0​​​版本的,我試了一下 ​

​17.1.4​

​​ 版本的,使用 ​

​file-type​

​ 的時候報錯如下:

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

可能是我 node 版本太低,我的node版本是 ​

​12.13.0​

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

用法:從緩沖區确定檔案類型,緩沖區可能是檔案開頭的一部分:

vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

代碼如下:

<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'}      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

上傳 flv 檔案,列印出來了:

{ext: 'flv', mime: 'video/x-flv'}      
vue 裡怎麼通過魔數(magic number)去限制上傳檔案類型?

這樣就很友善我們去判斷上傳的檔案類型。

參考資料

  • ​​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()​​

繼續閱讀