Go 标准库支持 zip/gzip 但无目录压缩/解压高层封装;压缩需用 filepath.Rel 生成安全相对路径并校验路径穿越;解压须 clean 路径、join 目标目录并用 filepath.Rel 验证落点;gzip 适用于单文件压缩(如 HTTP 响应),zip 用于多文件归档;大文件解压应增大 io.Copy 缓冲区。
Go 标准库原生支持 zip 和 gzip,但不直接提供「压缩整个目录」或「自动解压嵌套子目录」的高层封装——这意味着你得自己遍历文件、处理路径、避免目录穿越漏洞,否则一不小心就会写错或被利用。
用 archive/zip 压缩目录时如何正确处理文件路径
标准库不会自动帮你把 os.FileInfo 转成 zip 内部路径;如果直接用 filepath.Abs() 或未清理的 filepath.Join(),压缩包里可能出现绝对路径(如 /home/user/project/file.txt),解压时可能覆盖系统文件。
- 始终用
filepath.Rel(rootDir, fullPath)获取相对于根目录的相对路径 - 对结果调用
strings.ReplaceAll(relPath, "\\", "/")统一为正斜杠(zip 规范要求) - 在写入
zip.FileHeader前检查strings.HasPrefix(relPath, ".."+string(filepath.Separator)),拒绝路径穿越 -
header.Name必须是相对路径且不含盘符或前导/,否则某些解压工具会报错或跳过
解压 zip 文件时如何防止目录穿越攻击
攻击者可构造恶意 zip 文件,让 header.Name 为 ../../../etc/passwd,若直接 os.Create(header.Name) 就会写到任意位置。
- 对每个
zip.FileHeader.Name,先调用filepath.Clean(),再检查是否仍以".."开头或包含".."+string(filepath.Separator) - 确保所有解压目标路径都基于你指定的
destDir,用filepath.Join(destDir, cleanName)构造最终路径 - 解压前用
os.Stat()检查目标路径是否仍在destDir内:比较filepath.Rel(destDir, absTargetPath)是否无错误且不以".."开头 - 跳过
header.FileInfo().IsDir()为true但名字不以"/"结尾的条目(非法 zip 目录标记)
gzip 和 zip 的适用场景怎么选
gzip 是单文件压缩,zip 是归档+压缩,二者不可互换使用。误用会导致程序静默失败或读取乱码。