在上传文件时,在某些情况下我们希望能限制文件上传的类型,比如限制只能上传 PNG 格式的图片。我们可以通过 input
元素的 accept
属性来限制上传的文件类型,
例如:
<input type="file" accept="image/png" />
这种方案是通过识别文件到后缀名 .png 来限制。如果用户把 JPEG 格式的图片后缀名更改为 .png
的话,就可以成功突破这个限制。为了更严格的限制,我们可以通过读取文件的二进制数据来识别正确的文件类型。
一、如何查看文件的二进制数据
要查看文件对应的二进制数据,我们可以借助一些现成的编辑器,比如 Windows 平台下的 WinHex 或 macOS 平台下的 Synalyze It! Pro 十六进制编辑器。这里我们使用 Synalyze It! Pro 这个编辑器,以十六进制的形式来查看PNG 图片对应的二进制数据。
二、如何区分图片的类型
计算机并不是通过图片的后缀名来区分不同的图片类型,而是通过 “魔数”(Magic Number)来区分。 对于某一些类型的文件,起始的几个字节内容都是固定的,根据这几个字节的内容就可以判断文件的类型。
常见图片类型对应的魔数如下表所示:
文件类型 | 文件后缀 | 魔数 |
---|---|---|
JPEG | jpg/jpeg | 0xFF D8 FF |
PNG | png | 0x89 50 4E 47 0D 0A 1A 0A |
GIF | gif | 0x47 49 46 38(GIF8) |
BMP | bmp | 0x42 4D |
由上图可知,PNG 类型的图片前 8 个字节是 0x89 50 4E 47 0D 0A 1A 0A。当你把 .png
文件修改为 .jpeg
后,再用编辑器打开查看图片的二进制内容,文件的前 8 个字节还是保持不变。
三、Javascript 如何检测图片类型
<div>
选择文件:
<input type="file" id="inputFile" accept="image/*" @change="handleChange" />
<p>{{ fileType }}</p>
</div>
data() {
return {
fileType: ''
}
},
methods: {
readBuffer(file, start = 0, end = 2) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(file.slice(start, end));
});
},
check(headers) {
return (buffers, options = { offset: 0 }) =>
headers.every(
(header, index) => header === buffers[options.offset + index]
);
},
async handleChange(event) {
const isPNG = this.check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // PNG图片对应的魔数
const file = event.target.files[0];
const buffers = await this.readBuffer(file, 0, 8);
const uint8Array = new Uint8Array(buffers);
this.fileType = `${file.name}文件的类型是:${isPNG(uint8Array) ? "image/png" : file.type}`
console.log(file)
}
}
以上示例将 PNG 图片后缀改为 .jpg 后,对应的检测结果如下图所示:
但在实际工作中,遇到的文件类型是多种多样的,可以使用现成的第三库来实现文件检测的功能,比如 file-type 这个库。