finfo_file() 是最可靠的 MIME 类型检测方式,应使用 FILEINFO_MIME_TYPE 标志并指定系统 magic 路径;空文件需单独判断;需结合 mb_detect_encoding() 和 mb_check_encoding() 验证编码有效性;不可依赖扩展名或 is_readable();生产环境须分层过滤、宁可误杀。

用 finfo_file() 检测 MIME 类型最可靠
直接读文件头比扩展名靠谱得多,finfo_file() 是 PHP 官方推荐的 MIME 探测方式,能识别 UTF-8、ISO-8859-1、UTF-16 等编码下的文本内容,也能区分 text/plain 和 text/html 等子类型。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 必须用
FILEINFO_MIME_TYPE标志,避免用FILEINFO_MIME(它返回带 charset 的完整字符串,解析麻烦) - 初始化
finfo时指定系统 magic 文件路径更稳妥:finfo_open(FILEINFO_MIME_TYPE, '/usr/share/misc/magic'),否则可能 fallback 到内置精简库,对某些编码识别不准 - 注意:空文件会被判为
application/octet-stream,需单独判断filesize() === 0
用 mb_detect_encoding() 辅助验证是否可读文本
MIME 类型只是“声称”,真正能否当文本处理还得看字节序列是否符合常见编码规则。比如一个二进制文件可能被误标为 text/plain,但实际含大量非法 UTF-8 字节。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 传入常见编码数组:
mb_detect_encoding($content, ['UTF-8', 'GB2312', 'BIG5', 'ISO-8859-1'], true),第三个参数true表示严格模式(跳过非法字节则返回false) - 不要只依赖返回值,要结合
mb_check_encoding($content, $encoding)二次确认 - 大文件慎用:需读全内容到内存,建议限制前 64KB 检测
避免用 is_readable() 或扩展名判断文本性
is_readable() 只检查权限和存在性,pathinfo($file, PATHINFO_EXTENSION) 更不可靠——report.pdf.exe 也能叫 .txt。
常见错误现象:
- 用户上传名为
payload.txt的 WebShell,实际是 PHP 脚本,仅靠扩展名放行导致代码执行 - JSON 文件被当成二进制(因为某些工具导出时没加换行,
finfo判为application/json,但其实是纯文本) - Windows 记事本保存的 UTF-16 文件无 BOM,
finfo常误判为application/octet-stream
生产环境建议组合判断逻辑
单一方法总有盲区,真实场景需要分层过滤:
- 先用
finfo_file()排除明显非文本类型(如application/pdf、image/png) - 对
text/*类型再抽样检测前 8192 字节的编码有效性(mb_check_encoding()) - 对疑似 UTF-16/UTF-32 且无 BOM 的文件,尝试用
mb_convert_encoding($content, 'UTF-8', 'UTF-16LE')转码并检查是否产生乱码 - 最终仍存疑?记录日志并拒绝,而不是降级处理
文本性不是非黑即白的属性,尤其在用户可控输入场景下,BOM、混合编码、控制字符都会让边界模糊——宁可误杀,不放过一个二进制伪装体。







