如何准确的获取文件的类型
2022-03-30 00:54:58 # fontend

背景描述

前端的小伙伴们应该很熟悉上传或者导入的场景,我也是最近在做一个导入文件的需求时,想通过前端准确的限制导入文件的类型,所以这里就延展出来一个需求:前端能怎样准确的获取文件的类型?

常见方法

先说说针对选择文件常见的前端处理有哪些:

1.通过 input file 标签的 accept 的属性来限制选择文件的默认类型

1
<input type="file" accept="image/*"/>

这个操作其实很简单,但是在实际操作中你会发现,在选择文件时,这个文件类型其实是可以切换为所有文件的,所以对于文件的限制,其实并没有起到太大作用。

2.通过获取 File 对象的 type 字段来获取文件类型

这里的 type 字段,是个只读字段。返回文件的 多用途互联网邮件扩展类型(MIME Type)

看看 File.type 的定义:

字符串,包含媒体类型(MIME),表示文本是什么类型,例如 PNG 图像是 “image/png”。

但是这个属性并不是获取的文件的真正类型:

注: 基于当前的实现,浏览器不会实际读取文件的字节流,来判断它的媒体类型。它基于文件扩展来假设;重命名为 .txt 的 PNG 图像文件为 “text/plain” 而不是 “image/png” 。而且,file.type 仅仅对常见文件类型可靠。例如图像、文档、音频和视频。不常见的文件扩展名会返回空字符串。开发者最好不要依靠这个属性,作为唯一的验证方案。

这里说的问题在应用中我就遇到过,比如比较常见的 .md, .opml 就没有 type 字段,通过 type 字段判断所有类型是不靠谱的,如果只是一些常见的图像类型还是可以的。

另外,type 字段的兼容性问题:

fileType

3.获取文件名的后缀

这个方式也比较普遍,通过剪切获取后缀名来判断文件类型,这个其实更不靠谱,因为文件名是可以修改的。

总结以上的获取文件类型的方法,优点是简单快捷,缺点就是都不能百分之百保证能准确的获取到文件的真实类型。

其他方法?

想到如何获取文件的类型,其实就想每个文件在电脑里面又是怎么区分的了,既然他们能区分,那肯定就有获取类型的方法。另外一个就是文件如果要存储文件类型的话,他能存在哪里,文件一般都是裸奔,估计只有文件本身里面能存储了吧,类比图片信息。所以这里还有一种获取文件类型的方式,就是解读文件信息,一般文件头部字段里面会标识当前文件的类型。

获取文件签名

web API 里面的 FileReader 可以读取文件信息,先读取文件的 buffer 类型数据 ,然后通过 DataView 转化为可查看或者操作的对象,根据不同文件类型独一无二的文件签名 file signature,然后根据 List of file signatures 或者 filesignatures 确定文件类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fileDom.addEventListener('change', (event) => {
const file = event.target.files[0]
const fileReader = new FileReader();
fileReader.onload = (event) => {
const view = new DataView(event.target.result);
handleFileType(view);
}
fileReader.readAsArrayBuffer(file);
});

const handleFileType = (view) => {
let first4Bytes = view.getUint32(0, false);
let first4BytesHex = Number(first4Bytes).toString(16).toUpperCase(); // 文件签名的16进制 eg: png -> 89504E47
// TODO: 根据文件签名白名单获取具体的文件类型
}

说明:

  1. 这里可以使用 Typed Array 或者 DataView 将 ArrayBuffer 转化成可以查看、操作的对象,为啥不用 Typed Array,是因为不同平台存在不同的字节序的问题,具体可查看: 字节序
  2. 此方法其实也并不是特别完美,并且使用此方法需要读取文件。

还是不行?

通过上面的方法貌似可以确定文件类型,但是实际经验告诉我并没有,发现文件签名里面的类型没有 html, md, csv, txt, 这种。这里就需要搞清楚为啥没有这些文件类型?

这里就要说到文件类型的表示方法,有上面 accept 或者 type 字段的格式,也就是 MIME 类型, 格式就是 type/subtype。还有 其他传送文件类型 的方法,就是:

MIME 类型不是传达文档类型信息的唯一方式:
有时会使用名称后缀,特别是在 Microsoft Windows 系统上。并非所有的操作系统都认为这些后缀是有意义的(特别是 Linux 和 Mac OS),并且像外部 MIME 类型一样,不能保证它们是正确的。
魔术数字。不同类型的文件的语法通过查看结构来允许文件类型推断。例如,每个 GIF 文件以 47 49 46 38 十六进制值 [GIF89] 或 89 50 4E 47 [.PNG] 的 PNG 文件开头。并非所有类型的文件都有幻数,所以这也不是 100%可靠的方式。

上面提到的文件签名的方法应该是 魔术数字 。但是不是所有类型的文件都有幻数。那为什么有些文件没有幻数呢,因为文件其实分两种大类:文本文件和二进制文件。基于二进制格式的文件就会有幻数,但是基于文本的就没有,而 html csv txt 这种就都属于基于文本格式的文件类型。

总结

从以上信息可以看出,其实每个方法都不算太完美的解决此问题,所以针对获取文件类型的判断可以分场景结合多种方法来判断,先是 file.type 字段或者文件后缀名,如果有值可以根据此判断,如果值为空则继续通过读取文件签名的方式判断。并且在读取大文件的时候,要优化好用户体验。