accept属性仅是UI提示,无法真正过滤文件类型;必须双写MIME与扩展名(如application/pdf,.pdf),且严格小写无空格;前端需用文件名后缀+magic bytes二次校验;后端必须以文件头为准,不可信任何前端传来的类型信息。

accept 属性根本不是过滤器,只是界面提示
加了 accept=".pdf,.xlsx" 后用户照样能选 .exe、.html 甚至 .php——点“所有文件”、拖拽进来、或者用 DevTools 删掉 accept 属性,全部绕过。iOS Safari 甚至可能因 accept 值不规范直接禁用相册入口;华为 EMUI、小米 MIUI 的 WebView 常完全忽略它。它只影响系统文件选择框的默认筛选状态,不拦截上传、不修改 event.target.files、也不报错。
accept 值怎么写才在多数设备上真起作用
单写 MIME 类型或单写扩展名都容易失效,必须双写且格式严苛:
-
accept="application/pdf,.pdf"✅ iOS Safari、Chrome、Edge 全兼容 -
accept="image/png,image/jpeg,.png,.jpg"✅ 显式覆盖常见变体,避免image/pjpeg被漏 -
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,.xlsx"✅ .xlsx 的唯一可靠组合 -
accept="text/csv,.csv"✅ 避免用text/*,它会意外放行text/html -
accept="image/png , .jpg"❌ 逗号后有空格 → 旧版 Safari 直接丢弃整个属性 -
accept=".PDF,.XLSX"❌ 扩展名必须小写、带点号,大写或缺点号均无效
前端 JS 必须做二次校验,别信 file.type
file.type 是浏览器根据扩展名或注册表推测的,可为空、可伪造。把 hack.js 改名为 report.pdf,file.type 就可能变成 application/pdf,但内容仍是可执行脚本。
- 优先用
file.name.toLowerCase().endsWith('.pdf')或正则/\.(pdf|docx|xlsx)$/i - 关键业务必须加 magic bytes 校验:用
FileReader读前 4 字节,PDF 是%PDF(25 50 44 46),XLSX 是PK\x03\x04 - 设了
multiple就得遍历Array.from(event.target.files),不能只检查第一个 - 提示要具体:“已选 2 个不支持的文件(
scan.tif、data.zip)”,而不是笼统说“类型错误”
后端才是唯一可信边界,且必须以文件头为准
前端所有限制都可被绕过:curl、Postman、拖拽 Drop 区域、修改 DOM……服务端收到的 Content-Type 请求头、req.file.mimetype(Node.js)、$_FILES['file']['type'](PHP)全不可信。
立即学习“前端免费学习笔记(深入)”;
- 必须读取文件二进制流前几个字节(magic bytes):
%PDF、\x89PNG、PK\x03\x04才是真实依据 - 推荐库:
file-type(Node.js)、python-magic(Python) - 保存时务必重命名,剥离原始扩展名,由服务端根据检测结果附加安全后缀
- 上传目录禁止执行权限,静态资源走 CDN 或独立域名
change 事件触发之后。这时候文件已经进来了,JS 才能读、能判、能清空输入框、能阻止提交。而服务端连这个“之后”都没有,它面对的就是裸数据流。



















