今天突然想起来ArrayBuffer学了还没实际应用一下呢,正好今天有空!
前情提要
记得有一次上传证件照,当时那网站只要jpg格式的,我当时只有png格式,不让我上传,聪明(睿智)的我直接改后缀名为jpg,上传!“请上传jpg格式图片”。当时我又检查了一遍,是jpg啊,怎么回事,这网站难不成后边有人在监控我?
直到现在我才明白原来是个文件都会在内容里标识自己的信息,那些编码方法和压缩算法都写在文件里面,正常打开是看到的data区域的内容,得用二进制文件编辑器才能看到隐藏的二进制信息,文件头里的文件类型信息就藏在二进制里面。
小试锋芒
今天就以png格式为例子,其他格式类似:
首先安装vscode插件:hexdump
它可以帮助我们将文件以二进制打开,能看到内部的结构,界面很清晰,好用!
点击这里就可以看到文件的二进制信息了!
这八个字节就是文件标志头,前四个字节是“.PNG”的ASCII码,这个就是今天我们要找的文件类型信息了,现在只要使用JavaScript从文件中读取前8个字节,拼成串和“89 50 4E 47 0D 0A 1A 0A”比较一下就好了,其余的格式嘛,自己去网上查表,都差不多的
最佳实践
代码实现:
const getTypeCode = async (file,digit) => {
return await new Promise((resolve, reject) => {
try {
let reads = new FileReader();
reads.readAsArrayBuffer(file);
reads.onload = () => {
// 这里的result是ArrayBuffer,为其创建一个视图
const dv = new DataView(reads.result)
let type_code = ''
for (let i = 0; i < digit; i++) {
// 一个字节一个字节地读(小端字节序),再转成16进制,不够两位前面补0
type_code += dv.getUint8(i, true).toString(16).padStart(2, '0')
}
resolve(type_code.toUpperCase())
}
} catch (e) {
reject(e)
}
})
}
(async () => {
const file = document.getElementById('file_input').files[0]
const isPng = await getTypeCode(file, 8) === '89504E470D0A1A0A' // true
})()
封装一下:
// 文件类型比较的基类
class fileTypeComparator {
constructor() {
this.code = ''
this.length = 0
}
async getTypeCode(file, digit) {
return await new Promise((resolve, reject) => {
try {
let reads = new FileReader();
reads.readAsArrayBuffer(file);
reads.onload = function (e) {
const dv = new DataView(reads.result)
let type_code = ''
for (let i = 0; i < digit; i++) {
type_code += dv.getUint8(i, true).toString(16).padStart(2, '0')
}
resolve(type_code.toLowerCase())
}
} catch (e) {
reject(e)
}
})
}
async check(typeCode) {
return this.code === await this.getTypeCode(typeCode, this.length)
}
}
// png格式
class pngComparator extends fileTypeComparator {
constructor(props) {
super()
this.code = '89504e470d0a1a0a'
this.length = Math.floor(this.code.length / 2)
}
}
// jpg格式
class jpgComparator extends fileTypeComparator {
constructor() {
super()
this.code = 'FFD8FF'
this.length = Math.floor(this.code.length / 2)
}
}
测试:
(async () => {
const files = document.getElementById('file_input').files[0]
const jpgCmp = new pngComparator()
console.log(await jpgCmp.check(files)) // true
})()
融会贯通
总结一下:前端可以实现操作二进制数据之后,减轻了后端负担,虽然可以阻挡大部分用户,但是后端还是要再做一遍,防止黑客大佬直接通过POST给提交了,具体就不说了哈哈哈
参考资料: