基礎解壓函數
function unzip_file(string $zipPath, string $destDir): bool {
if (!file_exists($zipPath)) throw new Exception("ZIP 不存在: $zipPath");
if (!is_dir($destDir)) mkdir($destDir, 0755, true);
$zip = new ZipArchive();
$res = $zip->open($zipPath);
if ($res !== true) throw new Exception("無法打開 ZIP: 錯誤碼 $res");
// 安全:檢查路徑遍歷
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (strpos($name, '..') !== false || $name[0] === '/') {
$zip->close();
throw new Exception("檢測到路徑遍歷攻擊: $name");
}
}
$ok = $zip->extractTo($destDir);
$zip->close();
return $ok;
}
中文檔名編碼處理
function unzip_chinese(string $zipPath, string $destDir): bool {
$zip = new ZipArchive();
$zip->open($zipPath);
// PHP 8.2+ 可用 ZipArchive::FL_ENC_UTF_8 / FL_ENC_CP437
// 舊版本需手動轉換:GBK (Windows) / UTF-8 (Linux/Info-ZIP)
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
$name = $stat['name'];
// 嘗試偵測編碼
if (!mb_check_encoding($name, 'UTF-8')) {
$name = iconv('GBK', 'UTF-8', $name); // 常見 Windows 壓縮
}
$stat['name'] = $name; // 注意:ZipArchive 無法直接修改名稱,需 extract 後 rename
}
$zip->extractTo($destDir);
// 二次重命名修正
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($destDir)) as $file) {
$old = $file->getPathname();
$new = $destDir . DIRECTORY_SEPARATOR . $file->getFilename(); // 簡化,實際需保留目錄結構
if ($old !== $new) @rename($old, $new);
}
$zip->close();
return true;
}
進階:顯示解壓進度
function unzip_with_progress(string $zipPath, string $destDir, callable $progress): bool {
$zip = new ZipArchive();
$zip->open($zipPath);
$total = $zip->numFiles;
$count = 0;
for ($i = 0; $i < $total; $i++) {
$name = $zip->getNameIndex($i);
$zip->extractTo($destDir, [$name]); // 逐個解壓
$progress(++$count, $total, $name);
}
$zip->close();
return true;
}
// 使用
unzip_with_progress('big.zip', 'out', function($done, $total, $name) {
echo "\r[$done/$total] $name";
});
壓縮函數 (對應 66186)
function create_zip(array $files, string $dest, bool $overwrite = false): bool {
if (file_exists($dest) && !$overwrite) return false;
$zip = new ZipArchive();
$flags = $overwrite ? ZipArchive::OVERWRITE : ZipArchive::CREATE;
if ($zip->open($dest, $flags) !== true) return false;
foreach ($files as $file) {
if (!file_exists($file)) continue;
$zip->addFile($file, basename($file)); // 僅存檔名,不含路徑
}
$zip->close();
return file_exists($dest);
}
安全檢查清單
- 限制上傳 ZIP 大小(
upload_max_filesize, post_max_size)。
- 解壓前掃描惡意腳本(.php, .phtml, .exe 等)。
- 解壓目錄禁止腳本執行權限 (Nginx/Apache 配置)。
- 防止 Zip Bomb:限制解壓後總大小、檔案數、壓縮比(>100 警惕)。
- 臨時目錄使用隨機名稱,處理完畢立即清理。