【文件上传那些事儿】 - 02 二进制级别的格式验证

541 阅读3分钟

前文链接

【文件上传那些事儿】- 01 简单的拖拽上传和进度条

V1.3:文件类型验证

在前面的文章中,我们已经实现了一个基础的文件上传,并在此基础上做了一些简单的优化:拖拽上传和进度条,其中拖拽上传需要注意 drag 相关事件,而进度条可以使用 axios 的 onUploadProgress 来实现。

今天我们将进一步优化这个小小的文件上传 demo,为其增加验证文件类型的功能。

基础版:后缀

说到验证文件类型,大家肯定都能想到,通常组件库会提供一个 beforeUpload 的方法,在这里验证文件的后缀,那我们就先用这种方式来验证一下文件类型吧。

首先看看 file 的数据类型是怎么样的:

img01

可以看到,这里有一个 name 字段,我们截取最后的 .xxx 就能进行验证了:

const filename = fileRef.value.name;
const ext = filename.split(".").pop();

到这里,一个简单的后缀验证已经实现,但基于后缀进行文件的验证是存在一定隐患的:比如用户刻意更改了文件的后缀。

img02

这里用户将 apk 文件后缀改成了 png,随后可见无论是 file.type 还是通过 name 都将其视为了图片,而如果用户在通过前端的验证之后进行抓包改包再次将文件类型改成 apk,就有可能将一个不安全的文件上传到服务器上去了。

对此感兴趣的读者可以了解一下 BurpSuite。

升级版:二进制

在实现功能之前,我们可以先下一个 winhex 或者直接在 vscode 中装一个 hexdump 来看看不同的文件有什么区别。

JPG

img03

PNG

img04

结论

其他类型也都类似,感兴趣的读者可以自行查看,这里直接上结论:

  • JPG:文件头为 FF D8 FF
  • PNG:文件头为 89 50 4E 47
  • GIF:文件头为 47 49 46 38

参考:各种类型文件头标准编码

这里我们就以 GIF 为例进行说明。

首先要封装一个方法,将文件头转成 16 进制编码的格式,细节看注释即可:

const blobToString = (blob: Blob): Promise<string> => {
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.onload = () => {
      const result = (reader.result as string)
        .split("")
				// 转化成 asc 码
        .map((v: string) => v.charCodeAt(0))
				// 转化成 16 进制
        .map((v: number) => v.toString(16))
				// 补齐 0
        .map((v: string) => v.padStart(2, "0"))
        .join(" ");
      resolve(result);
    };
    reader.readAsBinaryString(blob);
  });
};

那么如果我们要判断一个文件是否是 GIF,直接将前 4 个字节传进去即可:

const isGif = async (file: File): Promise<boolean> => {
  const ret = await blobToString(file.slice(0, 4));
  return ret === "47 49 46 38";
};

现在我们将 png 文件改成 gif 再次测试一下:

img05

结束语

大功告成,现在上传小 demo 已经有了拖拽上传,进度条和文件类型验证的功能,算是初具规模了,之后的文章中,我们将介绍如何实现诸如大文件切片上传,文件秒传以及断点续传等功能。

那么今天就到此为止,期待下一次的相遇~