accept属性仅对标准MIME类型(如image/jpeg)和带点扩展名(如.pdf)有效,逗号分隔且不可有空格;其他写法如jpg、image/jpg、image/*混搭等均无效,且前端校验可被绕过,必须依赖服务端magic bytes校验。

accept 属性支持哪些值?不是“能写什么”,而是“写了有没有用”
accept 属性只接受两类合法值:标准 MIME 类型(如 image/jpeg)和带点号的文件扩展名(如 .pdf),中间用逗号分隔,**逗号后不能有空格**。浏览器据此优化本地文件选择器界面,但不拦截或过滤文件。
常见有效组合包括:
-
image/jpeg,image/png,image/gif,image/webp—— 精确限定主流图片格式,比image/*更可控 -
.pdf,.docx,.xlsx—— 文档类推荐用扩展名,iOS Safari 对纯 MIME 支持弱 -
application/pdf,.pdf—— MIME + 扩展名双写,覆盖 Android WebView 和 iOS -
video/mp4,video/webm—— 避免video/*,防止意外放行video/quicktime(.mov)等难处理格式 -
text/plain,.txt,.log—— 不要用text/*,它会匹配text/html,带来 XSS 风险
以下写法基本无效:
-
jpg,png—— 缺少.或 MIME 前缀,被所有主流浏览器忽略 -
image/jpg—— 非标准 MIME,应为image/jpeg -
accept="image/*,.jpg"—— 通配符混搭扩展名,部分安卓 WebView 会丢弃整个属性
为什么 accept="image/*" 还能选到 .psd、.svg 甚至 .html?
因为 image/* 不是过滤器,只是提示。macOS 文件选择器默认隐藏非匹配项,但用户一点“显示所有文件”就全出来了;Safari 可能将 image/svg+xml 视为匹配,而 SVG 可含脚本;Chrome 甚至把 image/vnd.adobe.photoshop(即 .psd)也纳入范围。
立即学习“前端免费学习笔记(深入)”;
更关键的是:移动端行为更不可控。华为 EMUI、小米 MIUI 的 WebView 常直接忽略 accept;iOS Safari 则可能因校验过严,导致相册照片无法被识别为可选文件。
所以别指望靠 image/* 实现“只收 JPG/PNG”。它不校验内容、不干预 event.target.files 列表、也不阻止提交——你拿到的 File 对象里,完全可能出现 file.name === "hack.html" 且 file.type === "image/jpeg" 的伪装文件。
前端 JS 必须做的二次校验,绕不开
用户点击“打开”后,accept 就彻底退出舞台了。真正要处理的,是 change 事件里的 event.target.files。这时必须手动遍历每一项,不能只看第一个。
- 别信
file.type:它是浏览器根据扩展名或文件头推测的,可为空、可伪造。把malware.exe改名成report.jpg,file.type很可能就是image/jpeg - 优先用
file.name.toLowerCase().match(/\.(jpg|jpeg|png|pdf)$/i)做扩展名白名单比对 - 关键业务加 magic bytes 校验:用
FileReader读前 4 字节,比如 PDF 必须是%PDF(25 50 44 46),PNG 必须是\x89PNG(89 50 4E 47) - 提示要具体:“已选 2 个不支持的文件(
scan.tif、data.zip)”,而不是笼统弹“类型错误”
后端才是唯一可信防线,且必须校验文件头
前端所有限制都可被绕过:curl、Postman、DevTools 修改 DOM、拖拽 Drop 区域……服务端收到的 Content-Type 请求头、req.file.mimetype(Node.js)、$_FILES['file']['type'](PHP)全不可信。
必须做三件事:
- 读取上传文件二进制流前若干字节(magic bytes),用专业库识别真实类型:
file-type(Node.js)、python-magic(Python)、Apache Tika(Java) - 验证扩展名是否与真实 MIME 一致(防伪装),例如
.jpg文件头必须是image/jpeg - 保存时剥离原始扩展名,用服务端生成的随机名 + 白名单后缀(如
abc123.png),并确保上传目录无执行权限
最容易被忽略的是:移动端 WebView 的 accept 行为碎片化严重,且服务端若只比对扩展名或信任 Content-Type,等于把门钥匙交给了攻击者。



















