本文转载自微信公众号《全栈修仙之路》,作者阿宝哥。转载本文请联系全栈修真之路公众号。在日常工作中,文件上传是一个很常见的功能。在某些情况下,我们希望限制上传文件的类型,比如限制只能上传PNG格式的图片。针对这个问题,我们会想到通过input元素的accept属性限制上传的文件类型:虽然这种方案可以满足大部分场景,但是如果用户将JPEG格式图片的后缀名改为.png,就可以成功突破这个限制。那么如何解决这个问题呢?事实上,我们可以通过读取文件的二进制数据来识别正确的文件类型。在介绍具体的实施方案之前,阿宝哥先以图片类型的文件为例介绍一下相关知识。一、如何查看图片的二进制数据要查看图片对应的二进制数据,我们可以使用一些现成的编辑器,比如Windows平台下的WinHex或者SynalyzeIt!macOS平台下的Pro十六进制编辑器。这里我们使用编辑器SynalyzeIt!Pro以十六进制格式查看阿宝哥头像对应的二进制数据。2、如何区分图片的类型计算机不是通过图片的后缀名来区分不同图片类型的,而是通过“幻数”(MagicNumber)来区分的。对于某些类型的文件,前几个字节的内容是固定的,根据这几个字节的内容就可以判断文件的类型。常见图片类型对应的幻数如下表:文件类型文件后缀幻数JPEGjpg/jpeg0xFFD8FFPNGpng0x89504E470D0A1A0AGIFgif0x47494638(GIF8)BMPbmp0x424D同样使用SynalyzeIt!Pro是验证阿宝头像(abao.png)类型是否正确的编辑器:从上图可以看出,PNG类型图片的前8个字节为0x89504E470D0A1A0A。将abao.png文件修改为abao.jpeg,然后用编辑器打开查看图片的二进制内容,会发现文件的前8个字节没有变化。但如果使用input[type="file"]输入框读取文件信息,则会输出如下结果:显然,无法通过文件扩展名或文件的MIME类型来识别正确的文件类型。下面阿宝哥给大家介绍一下上传图片时如何通过读取图片的二进制信息来保证图片类型正确。3、如何检测图片的类型3.1定义readBuffer函数获取到文件对象后,我们就可以通过FileReaderAPI读取文件的内容了。因为我们不需要读取文件的完整信息,所以阿宝哥封装了一个readBuffer函数来读取文件中指定范围的二进制数据。函数readBuffer(file,start=0,end=2){returnnewPromise((resolve,reject)=>{constreader=newFileReader();reader.onload=()=>{resolve(reader.result);};reader.onerror=reject;reader.readAsArrayBuffer(file.slice(start,end));});}对于PNG类型的图片,文件的前8个字节是0x89504E470D0A1A0A。因此,我们在检测选择的文件是否为PNG类型的图片时,只需要读取数据的前8个字节,逐个判断每个字节的内容是否一致。3.2定义校验函数为了实现逐字节比较,更好的复用,阿宝哥定义了一个校验函数:functioncheck(headers){return(buffers,options={offset:0})=>headers。every((header,index)=>header===buffers[options.offset+index]);}3.3检测PNG图片类型基于上面定义的readBuffer和check函数,我们可以实现检测PNG图片的功能:3.3.1html代码
3.3.2JS代码constisPNG=check([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a]);//PNG图片对应的幻数constrealFileElement=document.querySelector("#realFileType");asyncfunctionhandleChange(event){constfile=event.target.files[0];constbuffers=awaitreadBuffer(file,0,8);constuint8Array=newUint8Array(buffers);realFileElement.innerText=`${file.name}文件的类型是:${isPNG(uint8Array)?"image/png":file.type}`;}上面的例子运行成功后,对应的检测结果如下图所示:从上图我们可以成功检测到正确的图片格式。如果要检查JPEG文件格式,只需要定义一个isJPEG函数:constisJPEG=check([0xff,0xd8,0xff])但是如果要检查其他类型的文件,比如PDF文件,应该怎么办你做?这里我们首先使用SynalyzeIt!Pro编辑器浏览PDF文件的二进制内容:观察上图,我们可以看到PDF文件的前4个字节是0x25504446,对应的字符串是%PDF。为了让用户更直观的识别检测类型,阿宝哥定义了一个stringToBytes函数:functionstringToBytes(string){return[...string].map((character)=>character.charCodeAt(0));}基于stringToBytes函数,我们可以很容易的定义一个isPDF函数,如下:constisPDF=check(stringToBytes("%PDF"));通过isPDF函数,可以实现PDF文件检测功能。但是在实际工作中,会遇到各种类型的文件。针对这种情况,可以使用现成的第三方库来实现文件检测的功能,比如文件类型库。实际上,基于文件的二进制数据,除了检测文件的类型外,我们还可以读取文件相关的元信息,如文件的大小、位深度、颜色类型、压缩算法等。图像。我们继续以阿宝哥的头像(abao.png)为例,我们来看看实际情况:OK,下面介绍前端检测文件类型的方法。在实际项目中,对于文件上传场景,出于安全考虑,建议大家在开发过程中限制文件上传的类型。对于比较苛刻的场景,可以考虑使用阿宝哥介绍的验证文件类型的方法。此外,如果您对前端如何处理二进制数据感兴趣,可以阅读PlayingwithFrontendBinary。4.参考资源玩转前端二进制MDN——FileReader