在日常工作中,文件上传是一个很常见的功能。在某些情况下,我们希望能限制文件上传的类型,比如限制只能上传 PNG 格式的图片。针对这个问题,我们会想到通过 input
元素的 accept
属性来限制上传的文件类型:
<input type="file" id="inputFile" accept="image/png" />
这种方案虽然可以满足大多数场景,但如果用户把 JPEG 格式的图片后缀名更改为 .png
的话,就可以成功突破这个限制。因为通过 文件后缀名或文件的 MIME 类型 并不能识别出正确的文件类型。我们可以通过读取文件的二进制数据来识别正确的文件类型。
计算机并不是通过图片的后缀名来区分不同的图片类型,而是通过 Magic Number 来区分。 对于某一些类型的文件,起始的几个字节内容都是固定的,根据这几个字节的内容就可以判断文件的类型。
常见图片类型对应的 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 |
在获取文件对象后,我们可以通过 FileReader API 来读取文件的内容。因为我们并不需要读取文件的完整信息,所以阿宝哥封装了一个 readBuffer
函数,用于读取文件中指定范围的二进制数据。
function 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)); }); }
对于 PNG 类型的图片来说,该文件的前 8 个字节是 0x89 50 4E 47 0D 0A 1A 0A。因此,我们在检测已选择的文件是否为 PNG 类型的图片时,只需要读取前 8 个字节的数据,并逐一判断每个字节的内容是否一致。基于前面定义的 readBuffer
和 check
函数,我们就可以实现检测 PNG 图片的功能:
HTML 代码
<div> 选择文件:<input type="file" id="inputFile" accept="image/*" onchange="handleChange(event)" /> <p id="realFileType"></p> </div>
JS 代码
// 逐字节比对工具函数 function check(headers) { return (buffers, options = { offset: 0 }) => headers.every( (header, index) => header === buffers[options.offset + index] ); } // 检测 PNG 函数 const isPNG = check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const realFileElement = document.querySelector("#realFileType"); async function handleChange(event) { const file = event.target.files[0]; const buffers = await readBuffer(file, 0, 8); const uint8Array = new Uint8Array(buffers); realFileElement.innerText = `${file.name}文件的类型是:${ isPNG(uint8Array) ? "image/png" : file.type }`; }
检测 JEPG 类型 isJPEG
函数:
const isJPEG = check([0xff, 0xd8, 0xff])
检测 PDF 类型
PDF 文件的头 4 个字节的是 0x25 50 44 46,对应的字符串是 %PDF。为了让用户能更直观地辨别出检测的类型,可定义了一个 stringToBytes
函数:
function stringToBytes(string) { return [...string].map((character) => character.charCodeAt(0)); }
基于 stringToBytes
函数,我们就可以很容易的定义一个 isPDF
函数,具体如下所示:
const isPDF = check(stringToBytes("%PDF"));
本文作者:前端小毛
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!