HTML的accept属性仅提示浏览器过滤文件类型,无法限制大小或阻止非法文件;真实校验须在JS中通过files[0].size和FileReader读取文件头(Magic Number)实现,并必须服务端重复验证。

HTML原生accept属性只能限制文件类型,不能校验大小
很多人以为给<input type="file">加accept="image/png,image/jpeg"就能阻止用户选中非图片文件——其实这只是提示浏览器弹出过滤后的文件选择器,用户仍可手动切换到“所有文件”并选中.exe或.zip。更关键的是:accept对文件大小完全无约束,哪怕设了max=5mb(这本身也不是合法属性),浏览器也直接忽略。
真正有效的格式与大小校验必须在JS层完成,且需在change事件中读取files[0]的type和size属性。
-
files[0].type由浏览器根据文件头+扩展名推测,不可信但可作第一道快速过滤(如"application/pdf") -
files[0].size单位是字节,精确可靠,比如限制5MB就写if (file.size > 5 * 1024 * 1024) - 不要依赖
file.name后缀判断类型——用户可轻易把hack.exe重命名为report.pdf
用FileReader读取文件头做二次MIME校验(防伪造)
仅靠file.type不够安全:浏览器可能因扩展名缺失返回空字符串,或被刻意误导。要确认真实类型,得读取文件前几百字节比对Magic Number。例如PDF固定以%PDF开头,PNG以\x89PNG开头。
实际操作中不需要完整解析,用FileReader读取slice(0, 4)即可覆盖大部分常见格式:
立即学习“前端免费学习笔记(深入)”;
const reader = new FileReader();
reader.onload = () => {
const bytes = new Uint8Array(reader.result);
// PDF: %PDF, PNG: PNG, JPEG: ÿØÿà or ÿØÿÛ
if (bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46) {
console.log("confirmed PDF");
}
};
reader.readAsArrayBuffer(file.slice(0, 4));- 此步骤会触发异步,不适合放在表单提交同步校验流里,建议提前在
change时预检 - 若校验失败,立刻
input.value = ""清空控件,否则用户重复提交仍会携带旧文件 - 注意
FileReader错误回调onerror需捕获,比如用户取消读取或文件被锁定
服务端必须重复校验,前端校验只是用户体验优化
所有前端检查都可被绕过:禁用JS、改写submit事件、用curl直接POST。所以accept、size、FileReader这些全属于“防君子不防小人”的体验层手段。
- Node.js(Express)中用
multer时,配置limits.fileSize和fileFilter函数是必须的 - PHP中
$_FILES['file']['tmp_name']拿到后,必须用finfo_file()而非pathinfo()判断真实类型 - 即使前端显示“文件大小超出限制”,服务端收到超大文件仍可能触发
413 Payload Too Large,需提前在Nginx/Apache配client_max_body_size
移动端iOS Safari对accept的支持有严重缺陷
iOS 16.4之前,accept="image/*"在Safari里根本不会过滤相册选项,用户能直接选中视频甚至录音文件;即使写了accept=".jpg,.jpeg,.png",它也会忽略点号,当成image/*处理。
这意味着在iOS上,你不能指望accept带来任何实质性保护,所有校验逻辑必须无条件执行:
- 别用
accept="image/*"替代MIME检查,它在iOS上形同虚设 - 测试时务必真机连接Safari调试器,
console.log(file.type)常返回空字符串 - 如果业务强依赖图片上传,考虑引导用户使用
capture="camera"强制调起相机(但会牺牲从相册选择的能力)
文件上传校验链条里,最易被跳过的环节是服务端二次验证,以及对iOS怪异行为的兜底处理。只要漏掉其中一环,所谓“格式与大小控制”就只剩心理安慰。



















