文件上传是工作中常见的业务需要,很多状况下,咱们须要限度文件的上传类型,比方只能上传图片。通常咱们是通过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 这个库,有趣味的同学能够试一试。

看完记得点个赞呦!万事开头难,据说喜爱点赞的你在 2022 年将迎来一个开门红!