# 如何检测文件类型

# 背景

  • 希望限制文件上传的类型。比如在【Epub电子书阅读器】项目中,只希望解析的是epub格式的文件。
  • 虽然可以使用后缀名可以解决,但是也可以通过读取文件的二进制数据来判断文件的类型。

# 如何获取文件的二进制数据

  • 编辑器工具
    • Windows: WindHex
    • MacOS: Synalyze It! Pro十六进制编辑器

# 怎么区分不同文件类型的

  • 通过魔数(Magic Number),某一类型文件的起始几个字节内容都是固定的,根据这些字节内容就可以判断文件类型
  • 例如:PNG图片的前8个字节是0x89 50 4E 47 0D 0A 1A 0A,修改为JPEG格式后,图片前8个字节依旧不变

# 具体实现

  选择文件:<input type="file" name="" id="inputFile" accept="image/*" onchange="handleChange(event)">
  <p id="realFileType"></p>
1
2
    const isPNG = check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);//PNG图片对应的魔数
    const realFileElement = document.querySelector('#realFileType');

    async function handleChange(event) {
      const file = event.target.files[0];
      const buffers = await readBuffer(file, 0, 8);//ArrayBuffer数据对象
      const uint8Array = new Uint8Array(buffers)
      realFileElement.innerHTML = `${file.name}文件的类型是:${isPNG(uint8Array) ? "image/png" : file.type}`
    }

    function readBuffer(file, start = 0, end = 2) {//读取文件指定范围的二进制数据
      return new Promise((resolve, reject) => {
        const reader = new FileReader();  //通过FileReader读取文件内容
        reader.onload = () => {
          resolve(reader.result)
        }
        reader.onerror = reject;
        reader.readAsArrayBuffer(file.slice(start, end))
      })
    }
    function check(headers) {
      return (buffers, options = { offset: 0 }) =>
        headers.every(
          (header, index) => header === buffers[options.offset + index]
        );
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 涉及到的背景知识

  • FileReader.readAsArrayBuffer()
    • FileReader异步读取用户计算机上的文件
    • readAsArrayBuffer一旦完成,result属性中保存的是将被读取文件的ArrayBuffer数据对象
  • Unit8Array
    • new Uint8Array(buffer [, byteOffset [, length]]);
    • 8位无符号整型数组,创建完成后可以以对象/数组下标索引方式引用数组中的元素
    • Uint8Array.prototype.every()
  • promise;async/await
  • 柯里化
    • check函数为了实现逐字节对比+复用
    function check(headers){
      return (buffers,options)=>{}
    }
    
    1
    2
    3

# 参考

-JavaScript 如何检测文件的类型?