前端web開發--如何嚴格判斷文件上傳類型?而不是單純靠文件的后綴名


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>

  

三、總結

通過文件頭信息,我們除了可以判斷文件的類型,還可以讀取文件相關的元信息,比如圖片的尺寸、位深度、色彩類型和壓縮算法等,只是這些信息所在的位置不一樣。

按照以上方式,大家同樣可以判斷其他格式的文件,常用文件的文件頭。如果你還嫌麻煩,可以使用現成的第三庫來實現這個功能,比如 file-type 這個庫,有興趣的同學可以試一試。

PS:此文章來源於稀土掘金:
作者:前端阿飛
鏈接:https://juejin.cn/post/7046002905278578725


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM