用 archive/zip 创建 zip 文件需先创建 os.file 并初始化 zip.writer,对每个文件调用 createheader(name 不以 / 开头,可设 method 为 store 或 deflate),再 io.copy 写入内容,最后必须调用 w.close() 写入中央目录;解压时须 filepath.clean() 校验路径防遍历,并检查是否含 "..";fileheader 时间戳和权限需通过 fi.modtime() 和 fi.mode() 显式设置。

如何用 archive/zip 创建 ZIP 文件
Go 标准库的 archive/zip 不支持直接“添加文件到已有 ZIP”,必须从头构建。常见错误是试图复用已打开的 *zip.Writer 写入多个独立文件而不调用 Flush() 或忽略 Close() —— 这会导致末尾数据丢失,解压时提示“invalid zip file”或只解出部分文件。
关键步骤:创建 os.File → 用它初始化 zip.NewWriter → 对每个待压缩文件调用 CreateHeader(注意设置 FileHeader.Name 路径不能以 / 开头,否则某些解压工具会拒绝)→ io.Copy 写入内容 → 最后必须调用 w.Close()(它内部会写入中央目录,缺了就不是合法 ZIP)。
示例要点:
-
os.Open("input.txt")后记得defer f.Close() -
fh := &zip.FileHeader{Name: "input.txt", Method: zip.Deflate},Method设为zip.Store可跳过压缩(适合已压缩文件如.jpg) - 写入前用
w.CreateHeader(fh),别用w.Create("name")(它默认设为Store且不支持自定义时间戳) - 压缩大文件时,
zip.Writer默认缓冲小,可传入带更大 buffer 的bufio.Writer提升性能
如何安全地解压 ZIP 文件并防止路径遍历
直接用 z.File[i].Open() + filepath.Join(dst, file.Name) 是高危操作 —— 攻击者构造 ../../../etc/passwd 类型文件名就能写入任意路径。标准做法是:对每个 file.Name 先调用 filepath.Clean(),再检查是否仍以 ".." 开头或包含 ".." 路径段。
立即学习“go语言免费学习笔记(深入)”;
实际解压流程:
- 用
zip.OpenReader("a.zip")打开,别用zip.NewReader(它需要自己处理 reader 的 EOF 和 offset) - 遍历
z.File列表,对每个f:先cleanName := filepath.Clean(f.Name),再if strings.HasPrefix(cleanName, "..") || strings.Contains(cleanName, string(filepath.Separator)+".."+string(filepath.Separator))就跳过 - 创建目标路径前,用
os.MkdirAll(filepath.Dir(dstPath), 0755),而非假设父目录存在 - 写入时用
os.O_CREATE | os.O_WRONLY | os.O_TRUNC,避免O_APPEND导致意外追加
zip.FileHeader 中时间戳与权限字段为何常被忽略
zip.FileHeader.ModTime 默认是 Unix 零值(1970-01-01),解压后文件时间全变成这个;FileHeader.Mode() 返回的权限在 Windows 上无效,但在 Linux/macOS 解压时若没显式设置,文件可能丢失可执行位(如脚本变不可运行)。
修复方式:
- 读取源文件信息:
fi, _ := os.Stat("src.txt"),然后fh.SetModTime(fi.ModTime()) - 设置权限:
fh.SetMode(fi.Mode())(注意:仅对普通文件/目录有效,设备文件等特殊类型会被忽略) - 若需兼容老版 Go(SetMode 不可用,则手动赋值:
fh.Extra = []byte{0, 0, uint8(fi.Mode() & 0o777), 0}(Unix 扩展字段,非通用) - Windows 下无法还原 UID/GID,所以
SetMode对 owner/group 位无意义,只影响rwx位
为什么解压时遇到 zip: not a valid zip file 却能用其他工具打开
这通常不是 Go 库 bug,而是 ZIP 文件本身有“非标准但兼容”的结构:比如末尾多出签名(如 self-extracting EXE 尾部)、中央目录前有额外数据、或使用了 Go 尚未支持的压缩算法(如 BZIP2、LZMA)。archive/zip 严格遵循 ZIP spec,拒绝这些变体。
排查建议:
- 用
hexdump -C file.zip | head -20检查开头是否为50 4b 03 04(PK\003\004) - 用
unzip -t file.zip确认是否真损坏,或只是格式宽松 - 若确认是 SFX ZIP,先用
dd截掉头部(如dd if=sfx.zip of=clean.zip bs=1 skip=XXX)再用 Go 处理 - 不推荐改源码绕过校验——Go 团队明确表示不会放宽验证,这是设计选择而非缺陷
真正难处理的是跨平台 ZIP 兼容性:macOS 归档实用工具生成的 ZIP 常含 __MACOSX 元数据目录,而 Go 解压时不会自动跳过它,需在遍历时过滤掉以 __ 开头的条目。










