php 8.5 中 ziparchive 行为与 8.0+ 基本一致,但默认仍不启用中文文件名 utf-8 支持;需确认 zip 扩展已启用、用 addfromstring() 处理中文名、解压前严格路径净化、手动遍历压缩目录。

ZipArchive 在 PHP 8.5 中行为与 PHP 8.0+ 基本一致,**但默认仍不启用中文文件名 UTF-8 支持**——别被“新版就自动好使”误导。PHP 8.5 没有新增 ZipArchive 方法,所有坑和解法都延续自 8.0,只是错误提示更明确、部分底层校验更严格。
确认 zip 扩展真的在运行
很多“Class 'ZipArchive' not found”报错,不是没装,而是没生效:
- 运行
php -m | grep zip(命令行)或脚本里var_dump(extension_loaded('zip')),必须返回bool(true) - Docker 用户注意:
docker-php-ext-install zip后,别被后续的disable_functions = zip_open,zip_close拦截(这类禁用会让open()静默失败) - 共享主机常禁用底层函数,此时
$zip->open($file, ZipArchive::CREATE)可能返回ZIPARCHIVE::ER_OPEN而非抛异常,必须显式判断:if ($zip->open($file, ZipArchive::CREATE) !== TRUE)
压缩中文文件名:addFile() 会乱码,addFromString() 是唯一稳解
Windows 资源管理器、macOS 归档工具解压时中文变 ???.txt,根源是 addFile() 把文件系统路径直接当 CP437 编码写入 ZIP 元数据,而现代系统期望 UTF-8。
-
addFile('/path/中文.txt', '中文.txt')→ 危险!'中文.txt'必须是 UTF-8 字节流,且服务器 locale 不能是 C/POSIX(否则 iconv 失效) - 更可靠做法:
$zip->addFromString('中文.txt', file_get_contents($realpath))—— 绕过文件系统编码层,直接写入原始字节 + UTF-8 文件名 - 若内容是二进制(如 GD 生成的 PNG),确保
file_get_contents()返回的是原始字节,不是 base64 解码后的内容 - 注意:
addFromString()的第一个参数(内部路径)不能以/开头,否则 macOS 归档工具可能跳过该条目
解压前必须做路径净化,extractTo() 不校验任何东西
extractTo() 是“照单全收”,遇到 ../../etc/passwd 就真往根目录写——这是真实线上漏洞。
- 不能只检查
str_contains($name, '..'),因为foo/../../bar或././../etc/passwd同样危险 - 正确做法:对每个
$zip->getNameIndex($i)得到的路径$name,执行:$safeName = ltrim($name, '/.'); $safeName = preg_replace('#/{2,}#', '/', $safeName);
再拼接目标目录:$destPath = realpath($targetDir . '/' . $safeName);
最后验证:strpos($destPath, realpath($targetDir)) === 0 - 更安全替代:不用
extractTo(),改用getFromIndex()读取内容,手动file_put_contents()到白名单路径下 - 别依赖
mkdir($targetDir, 0755, true)自动建深层目录——它可能创建/tmp/evil/../etc这类路径
压缩整个目录:addFile() 不递归,必须手写遍历逻辑
$zip->addFile('/var/www') 会静默失败或报 File not found,因为 addFile() 只接受真实文件路径。
- 用
RecursiveDirectoryIterator+RecursiveIteratorIterator扫描子项 - 对每个
SplFileInfo实例,先isFile()过滤掉目录、.、..、符号链接 - 计算相对路径:
$relPath = substr($file->getPathname(), strlen($srcDir) + 1),否则所有文件都挤在 ZIP 根目录 - 显式跳过敏感目录:
if (in_array($file->getBasename(), ['.git', '.env', 'node_modules'])) continue; - 大目录建议分批处理,避免内存爆掉;PHP 8.5 默认
memory_limit=256M,超了就ini_set('memory_limit', '512M')或改用系统zip命令
ZipArchive 没有魔法修复路径遍历或中文编码问题,它只是更严格地暴露了旧版忽略的错误码。真正关键的不是版本号,而是你是否在 open() 后检查返回值、是否对每个 getNameIndex() 做路径净化、是否用 addFromString() 替代 addFile() 处理动态内容——这些点漏掉任意一个,线上就可能出空包、乱码或 RCE。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











