前言
本文篇幅较短,用时约为5分钟,偏向实用性,不讲分析。
背景
有个小项目中有一个上传功能,因为小,所以本次后端是没有做任何校验的。校验全交给前端,测试有个case是:改了后缀名的的文件是否能绕过上传。本着对Antd的信任,我对QA拍胸口表示可以。结果后面发现其实不行,为了不被diss,想方设法完成了这个功能。
原因
修改文件类型后,浏览器会将其对应的file.type也会随着更改。如下代码中的file.type ,完全可以改为根据文件最后的后缀名进行判断。
// 官方demo
function beforeUpload(file) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('You can only upload JPG/PNG file!');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
return isJpgOrPng && isLt2M;
}
解决方法
核心实现是通过二进制读取文件,校验真实文件内容的前4位的16进制。本代码中允许上传的文件是xlsx类型,其余文件二进制头可以自行查找,比如二进制头字节对应的文件类型。
getFileMimeType: (file) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
return new Promise((resolve, reject) => {
reader.onload = (event) => {
try {
let buffer = [...Buffer.from(event.target.result)];
// 仅要文件的前四位就够了
buffer = buffer.splice(0, 4);
buffer.forEach((num, i, arr) => {
arr[i] = num.toString(16).padStart(2, '0');
});
// 504b0304 是 xlsx 的文件头
resolve(buffer.join('') === '504b0304');
} catch (e) {
// 读取文件头出错 默认不是合法文件类型
reject();
}
};
});
}
接下来是Antd upload组件的beforeUpload,稍微修改下官方demo即可
beforeUpload: (file) => {
return new Promise(async (resolve, reject) => {
const isExcel = await getFileMimeType(file); // 调用上面代码
if (!isExcel) {
message.error('上传失败!仅支持文件类型为xlsx的文件');
reject();
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('上传文件不能超过 10MB!');
reject();
}
resolve();
});
}
注意点
- readAsArrayBuffer是异步的,因此个人转换成了Promise 用于同步写法
- 一开始beforeUpload中是直接这样写的:
beforeUpload: async (file) => {
const isExcel = await getFileMimeType(file); // 调用上面代码
if (!isExcel) {
message.error('上传失败!仅支持文件类型为xlsx的文件');
return false;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('上传文件不能超过 10MB!');
return false;
}
return true;
}
用这种写法文件都会上传成功,因此改造了beforeUpload,让其返回Promise。