抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

今天突然想起来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给提交了,具体就不说了哈哈哈

参考资料:

评论