前端判断文件格式

1,676 阅读5分钟

判断方式

判断文件格式是一个比较常见的需求,他可以提醒用户上传正确的文件,也避免了一些恶意攻击。通常会通过以下两种方式实现:

  • 文件名后缀

通过读取文件的后缀名,来判断文件格式

优点: 简单高效

缺点: 因为是通过读取后缀名来判断的,如果用户手动改变了后缀名,就会判断区分真实的文件格式,从而可能导致上传了不符合要求的文件

  • Magic Number

一些特定格式的文件,会在文件开头或者特定位置,指定一些特殊的字符来标识文件格式,可以通过读取文件的二进制数据,结合这些标识,来判断文件真实的格式是什么

可以通过 filesignatures.net/ 这个网站查询到指定文件格式的 Magic Number

image.png

优点: 因为改动后缀名并不会改变文件的二进制数据,所以这种方式不会受到文件名后缀的影响,是一种比较安全可靠的方式

缺点: 需要读取文件的二进制数据,过程会比较消耗性能

通常可以结合这两种方式,先通过文件名后缀,筛选掉不符合格式要求的文件,再通过 Magic Number 来判断真实的文件格式

file-type

前端判断文件格式,可以使用 file-type 这个包来实现,它是通过 magic number 来做格式判断的

image.png 接下来就让我们进入 file-type 源码,看看他是怎么实现的

file-type 源码分析

  • 首先,通过 FileReader API,将文件 blob 数据转化为 buffer,之后调用 fromBuffer 开始做文件类型判断 image.png

  • 接着使用 strtok3 这个包,创建一个 buffer 读取器。strtok3 内部维护了offset、position 等属性,可以传入位置和长度,读取 target 上指定位置的数据 image.png

  • 接着就是开始对比文件的魔数了。先创建了是三个判断方法:check:传入二进制数据,判断 target 的是否以这段二进制数据开头checkString:传入 ASCII 编码数据,判断 target 是否以这段文本开头checkSequence:判断是否包含这段序列号之后是一大串的 if 语句,开始使用不同的 header 来判断文件格式 image.png 以 PDF 文件为例。pdf 文件的开头是 25 50 44 46,对应的十进制是 37 80 68 70,而 %PDF 通过 checkString 转化为 unicode 后为 37 80 68 70 二者相同 image.png image.png

docx、pptx、xlsx 格式判断

最近的一个需求是判断一些 office 的文件格式,因此研究了一下 file-type 中对这类格式的判断,发现它稍微有些特殊,这里拉出来单独分析一下

前置知识:zip 文件格式

docx、pptx、xixs 其实是一种 zip 格式,如果我们用解压工具打开这种文件,就可以看到原本的文件列表

image.png zip 文件都的是以 [50, 4B, 03, 04] 开头(PK\x03\x04),通过这个特征可以判断一个文件是不是 zip 格式

image.png

zip 文件二进制数据的内容,可以总结为:[文件头+文件数据+数据描述符]{此处可重复n次}+核心目录+目录结束标识。其中 n 代表 zip 中包含的文件数量

image.png zip 文件头包含的内容(en.wikipedia.org/wiki/ZIP_(f…

image.png docx 跟 pdf 格式有什么不同?

你可能会好奇,问什么 file-type 需要特殊处理 docx、pptx、xlsx 文件,不能跟 pdf 文件那样直接通过固定位置的 magic number 来判断吗?其实通过查询一下他们的 file signatures 你就能找到答案了 image.png image.png image.png 可以看到,他们的 file signatures 都是相同的,并没有特殊的标识位可以直接区分。所以对于这种文件,需要特殊处理

file-type 判断 docx/pptx/xlsx 格式源码

那么要怎么判断 docx 这种文件格式呢?其实可以结合前置知识,根据 zip 文件格式的特征,读取 zip 中文件列表的所有文件,判断他们的文件名,是否包含对应的后缀即可

image.png

步骤:

  1. 通过 header 中 filename 的起始位置和长度,就可以读出文件名
  2. 文件名中包含了文件格式信息,可以通过它来判断是否符合条件
  3. 如果符合,得出结论。如果不符合,判断下一个文件
  4. 通过 header 长度、扩展区长度、内容区长度,就可以知道出每次遍历要读取的内容,从而计算出下一次遍历开始的位置

通过这种方式,就可以判断出 docx/pptx/xlsx 了

doc、ppt、xls 格式的判断

通过 file-type 支持的文件格式可以发现,它并不支持 doc、ppt、xls 这种旧的 office 文件格式,并且作者也不打算支持这种过时的格式:github.com/sindresorhu… 所以如果需要判断 office 格式,并且具备良好的兼容性,除了直接使用 file-type,还要配合 file-signunature 这种方式(doc、ppt、xls 的魔数是不同的,可以通过这种方式判断)。具体代码就不贴上了,网上有很多类似的实现可以参考。