一、利用唯一文件类型说明符限制上传文件类型
唯一文件类型说明符,表示在 file 类型的 input 元素中用户可以选择的文件类型。每个唯一文件类型说明符可以采用下列形式之一:
- 一个以英文句号(“.”)开头的合法的不区分大小写的文件名扩展名。例如:
.jpg
、.pdf
或.doc
。 - 一个不带扩展名的 MIME 类型字符串。
- 字符串
audio/*
,表示“任何音频文件”。 - 字符串
video/*
,表示“任何视频文件”。 - 字符串
image/*
,表示“任何图片文件”。
accept
属性的值是包含一个或多个(用逗号分隔)唯一文件类型说明符的字符串。例如,一个文件选择器需要能被表示成一张图片的内容,包括标准的图片格式和 PDF 文件,大概是这样的:
1 | <input type="file" accept="image/*,.pdf" /> |
这种方式,在 Mac OS 系统中表现为:不被允许的文件类型在上传文件框中呈现为灰色不可选状态,无法选中上传。在 Windows 系统中表现为:上传文件框中只展示可被上传的文件类型。而这个限制在 Windows 系统中可以被轻松绕过(选择上传文件框右下角的 All Files 选项)。
二、利用 MIME 类型验证文件类型
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型)是一种标准,用来表示文档、文件或字节流的性质和格式。
通用结构(语法):
1 | type/subtype |
我们可以在用户上传文件后,从 File 对象的 type 字段拿到文件的 MIME 类型,来验证文件类型是否合法,这种方式可以规避掉唯一文件类型说明符限制上传文件类型被 Windows 系统绕过的缺点。
例如,我们来验证用户上传 .png
类型的图片:
1 | const input = document.querySelector('input') |
这种方式同样也有缺点。用户可以修改文件的后缀名来实现修改文件 MIME 类型的方法,绕开检查,这种方式相对来说安全性也不是很高。
三、利用文件 Magic number 验证文件类型
Magic number ,又称之为 魔术数字 。在文件中,魔术数字指:
在特定文件格式中加入固定数值和固定字符串,然后便可以通过检查文件是否包含这些数据来快速地识别文件格式。
例如:GIF文件开头会包含GIF89a(47 49 46 38 39 61)或GIF87a(47 49 46 38 37 61)这两种字符串。
我们可以利用这个特性,通过读取并比对文件头部的 Macic number 来验证文件的格式,大多数文件类型都能做到在同一种文件类型下的不同文件,它的 Magic number 一致,但是也有例外如:.csv
、.json
格式的文件。
并非所有类型的文件都有 Magic number ,所以这也不是 100%可靠的方式。
还是上传文件的场景,我们在拿到用户上传的文件后,可以利用 FileReader 接口提供的 readAsArrayBuffer 方法来读取文件的内容,这会返回一个 ArrayBuffer
对象以表示所读取文件的数据,我们将这个 ArrayBuffer 对象转换成16进制的字符串,去和对应类型的 Magic number 做比对,即可分辨出用户上传的文件是否为目标类型。
各文件类型的 Magic number 示例可以在这里查看到。
下面我们拆解流程来一步步看。首先定义一个 ArrayBuffer
对象转16进制的方法:
1 | /** |
然后,我们模拟一个 <input type="file" />
上传标签拿到用户上传文件后并获取文件类型 Magic number 的过程:
1 | const input = document.querySelector('input') |
这里需要注意,通常文件类型 Magic number 会标识在文件的最前面(具体类型查看维基百科示例),例如 .png
格式的文件,示例里描述说该类型的文件以8字节的签名文件开始(89 50 4E 47 0D 0A 1A 0A),以此标识为 .png
格式。所以,如果要验证这种类型的文件,我们只需要利用 File
接口的 slice
方法提取文件前8字节的数据做转换判断即可,而无需将整个文件都参与到验证中来,这样在处理较大文件时会节省很多性能消耗。
最后,我们只需要将最后转换好的16进制 Magic number 与 .png
格式文件对应的 Magic number 比对即可。
在实际使用中,我们可以把验证具体类型文件的需求封装成具体的方法,应用在项目中即可。我们来封装一个 isPdf
方法,来验证用户上传的文件是否为 pdf 格式。
1 | /** |