finfo_file() 是判断文件真实类型的最可靠方式,基于魔数识别,需启用 fileinfo 扩展并使用 FILEINFO_MIME_TYPE 模式,检查 application/zip 等 MIME 类型,避免依赖扩展名或废弃的 mime_content_type()。

用 finfo_file() 判断文件真实类型最可靠
仅靠扩展名(如 .zip、.rar)判断是否为压缩包完全不可信——用户可随意改名,甚至上传伪造的 report.pdf.zip 实际是木马。PHP 的 finfo_file() 基于文件魔数(magic bytes)识别真实格式,是唯一推荐方式。
实操要点:
- 确保
fileinfo扩展已启用(php -m | grep fileinfo验证) - 使用
FILEINFO_MIME_TYPE模式,避免返回冗长的 MIME 描述 - 对常见压缩格式,检查返回值是否匹配:
application/zip、application/x-rar-compressed、application/x-7z-compressed、application/gzip、application/x-bzip2 - 注意:某些 tar 包(未压缩)返回
application/x-tar,不属于“压缩包”,需按需排除
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, '/path/to/file');
finfo_close($finfo);
if (in_array($mimeType, [
'application/zip',
'application/x-rar-compressed',
'application/x-7z-compressed',
'application/gzip',
'application/x-bzip2'
])) {
// 确认为压缩包
}
为什么不用 mime_content_type()?
mime_content_type() 是 finfo_file() 的旧版封装,底层调用相同逻辑,但存在两个硬伤:
- PHP 8.0+ 已废弃,未来版本会移除
- 无法自定义 magic 数据库路径,遇到非标准压缩格式(如某些自定义加密归档)时识别率更低
- 不支持
FILEINFO_MIME_TYPE以外的精细控制,比如跳过文本编码检测
直接替换为 finfo_file() 几乎零成本,且兼容 PHP 5.3+ 所有版本。
立即学习“PHP免费学习笔记(深入)”;
遇到 application/octet-stream 怎么办?
这是最常踩的坑:文件头被截断、权限不足读取、或格式太冷门(如 .lz4、.zst),finfo 无法识别时统一返回 application/octet-stream。此时不能简单判定“不是压缩包”,而应结合:
- 扩展名白名单二次过滤(仅限可信来源上传)
- 尝试用对应解压命令做轻量探测(如
zip -T $file 2>/dev/null,注意权限与超时) - 记录该文件路径和大小,人工抽检样本更新 magic 库(
file -C编译新magic文件)
切忌把 octet-stream 当作安全依据——很多恶意压缩包就靠这个绕过检测。
Web 上传场景下必须校验 $_FILES['file']['tmp_name']
用户提交的 $_FILES['file']['name'] 是客户端传来的原始文件名,不可信;真正要分析的是临时文件路径 $_FILES['file']['tmp_name']。漏掉这步会导致:
- 用错路径,
finfo_file()返回false - 误判空文件或已删除临时文件(
move_uploaded_file()后原路径失效) - 在多线程上传中,因 race condition 读到其他用户的临时文件(极少见,但路径拼接错误时可能)
务必在 is_uploaded_file() 通过后立即执行类型识别,不要先移动再判断。
outer.zip 里含 inner.rar)。这些不在格式识别范畴,但容易被当成“识别完成”就放松警惕。











