应使用 finfo_file() 替代已废弃的 mime_content_type(),通过魔数校验真实 MIME 类型,并结合扩展名白名单、文件头检查及上传 error 优先判断,实现安全可靠的大文件类型验证。

用 finfo_file() 替代 mime_content_type() 避免扩展名欺骗
只检查文件后缀(比如 .jpg)完全不可靠,用户改个后缀就能绕过。真正安全的验证必须读取文件头(magic bytes)。PHP 的 finfo_file() 是目前最稳定的选择,mime_content_type() 在 PHP 8.3+ 已被废弃,且不支持自定义规则。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 初始化时固定使用
FILEINFO_MIME_TYPE模式,避免返回带编码的完整 MIME(如text/plain; charset=utf-8),否则后续比对容易出错 - 传入绝对路径,相对路径在 CLI 或不同工作目录下可能失效;可用
realpath($file)先标准化 - 如果验证前已通过
move_uploaded_file()存到临时位置,直接传那个路径;别传$_FILES['x']['tmp_name']——它可能已被清理
批量验证时用 foreach + 提前退出,别堆 array_map
批量不是为了炫技写一行函数式代码,而是要控制错误反馈粒度和资源占用。用 array_map() 会强制执行全部,哪怕第一个文件就损坏,你也得等完才报错。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 逐个调用
finfo_file(),每次验证后立即检查返回值是否为false或不在白名单中 - 遇到非法文件立刻
return ['valid' => false, 'error' => "File {$i} invalid: ..."],别收集所有错误再统一抛——用户更关心“哪几个坏了”,而不是“总共坏了几个” - 大文件(>10MB)可加
stream_context_create(['http' => ['timeout' => 1]])防卡死,但finfo_file()本身不走网络,这步通常不需要
白名单别硬编码 MIME,用 in_array() + 常量数组
MIME 类型有细微变体:比如图片可能是 image/jpeg、image/pjpeg(IE 遗留),PDF 可能是 application/pdf 或 application/x-pdf。全匹配字符串容易漏判。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 定义允许类型时用数组,例如:
$allowed = ['image/jpeg', 'image/png', 'application/pdf'] - 用
in_array($mimeType, $allowed, true),第三个参数true启用严格比较,防止'image/jpg'被误当成'image/jpeg' - 不要用
strpos($mimeType, 'image/') === 0这类模糊匹配——image/svg+xml和image/webp都合法,但image/x-xcf(GIMP 源文件)就不该放行
上传前校验失败,$_FILES 的 error 值比 MIME 更优先
很多开发者跳过 $_FILES['file']['error'] 直接做 MIME 验证,这是危险的。PHP 上传机制本身就会在超限、无文件、临时目录不可写等情况下设 error 值为非零,此时 tmp_name 可能为空或无效,finfo_file() 会直接报警告甚至崩溃。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 循环处理前先检查
$_FILES['files']['error'][$i]是否为UPLOAD_ERR_OK - 若为
UPLOAD_ERR_INI_SIZE或UPLOAD_ERR_FORM_SIZE,说明是 PHP 或表单限制,应提示“文件超过最大允许大小”,而不是“格式不支持” - 只要
error !== UPLOAD_ERR_OK,连finfo都不该调用——省资源,也避免finfo_file('')报Warning: Empty filename or path
真正的难点不在怎么读 MIME,而在于把上传流程的每个环节拆开判断:先看 PHP 层有没有拦住,再看业务层要不要拦,最后才是用户感知层怎么反馈。漏掉任意一层,都可能让错误信息错位,或者让非法文件悄悄落地。



















