今天学到一个奇淫技巧,感觉挺有意思。 日常工作中,文件上传是非常常见的功能,限制上传的文件类型一般用的是以下几种方式。
input 的 accept
<input type="file" id="inputFile" accept="image/png" />
这个解决办法已经能够满足大部分的场景了,但是还是不够那么完美。假如我们把 hello.js 改成 hello.png 这样我们也能上传成功。
js 判断 MIME
还有就是通过 File.type 来获取文件 MIME 来做判断。但是这个方法依然存在上述的问题。
那我们有没有更严谨些的判断方式?
通过文件的二进制数据判断 ⭐
首先使用一些编辑器,比如 Windows 平台下的 WinHex
或 Synalyze It
! 或者用 VS Code 中的插件 Binary Viewer
来查看对应的二进制数据。
和人不一样,计算机不是通过图片的后缀名来区分不同的图片类型,而是通过“Magic Number”
来区分。对于某些类型的文件,前几个字节的内容是固定的,可以根据这些字节的内容判断文件的类型。
常见图像类型对应的 Magic Number 如下, 注意 89 50 这些是 16 进制的:
我们查看下 png 文件的二进制数据:
然后把它改成 jpg 再看下:
发现没有,虽然我们修改了文件的后缀,但是二进制的前几个字节的内容依然没有变化。知道了原理,下面就开始 coding 。
代码环节
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>文件类型判断</title>
</head>
<body>
<input type="file" />
<button onclick="detect()">校验</button>
<script>
function isPng(buffers) {
// PNG类型图像的前8个字节
const pngHeadersBinary = [
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
];
return pngHeadersBinary.every(
(binary, index) => binary === buffers[index]
);
}
function detect() {
var input = document.getElementsByTagName("input")[0];
const reader = new FileReader();
reader.onload = () => {
const uint8Array = new Uint8Array(reader.result);
// [137, 80, 78, 71, 13, 10, 26, 10] 是10进制形式
// 0x89 === 137 , 0x50 === 80
console.log(uint8Array); // Uint8Array(8) [137, 80, 78, 71, 13, 10, 26, 10, buffer: ArrayBuffer(8), byteLength: 8, ...]
console.log(isPng(uint8Array));
};
reader.readAsArrayBuffer(input.files[0].slice(0, 8)); // 只取前 8 位
}
</script>
</body>
</html>
感兴趣的同学可以继续深入研究,但在实际工作中,会遇到各种类型的文件。对于这种情况,您可以使用文件类型库,比如file-type