zip.writer默认使用zip.store不压缩,需显式设header.method = zip.deflate才能启用deflate压缩;go标准库仅支持该方法,无zip.bestcompression等常量。

zip.Writer默认不压缩,必须显式设header.Method = zip.Deflate
你生成的 .zip 文件体积没变小?不是代码写错了,是 zip.Writer 默认用 zip.Store —— 也就是只打包、不压缩。它把每个文件原样塞进 ZIP,连 zlib 都不调用。
每个文件写入前,必须手动设置 header.Method = zip.Deflate,否则就是“伪压缩包”。zip.Deflate 是 Go 标准库唯一支持的压缩方法,别指望 zip.BestCompression 这类不存在的常量。
- 空文件或极小文件(Deflate,底层 zlib 也可能自动回退到
Store,这不是 bug - 已压缩格式(
JPEG/PNG/MP4)再压基本无效,还可能略增体积;文本类(JSON/.go源码)才明显受益 - 如果只调
writer.Create("foo.txt"),它内部用的是Store;要压缩必须走writer.CreateHeader(header)并设好Method
解压时不做路径净化,../../../etc/passwd 会直接覆盖系统文件
archive/zip 对 FileHeader.Name 完全放行,不校验、不拦截、不警告。攻击者只要在 ZIP 里塞一个名字为 ../../config.yaml 的条目,你用 filepath.Join(dst, f.Name) 一拼,就可能覆盖服务配置甚至系统关键文件——这就是 Zip Slip 漏洞。
必须对每个 f.Name 先调用 filepath.Clean(f.Name) 归一化(./a/../b → b,../x → ../x),再检查:
-
cleanPath != f.Name(说明原始路径含.或..) -
strings.HasPrefix(cleanPath, "..")或strings.HasPrefix(cleanPath, "/") - 最后确认
dstPath := filepath.Join(outputDir, cleanPath)后,filepath.ToSlash(dstPath)确实以filepath.ToSlash(absDest)开头 - Windows 下还要统一转小写、替换
"\"为"/",否则"..\..\etc\passwd"可能绕过
中文文件名乱码,不是 Go 有问题,是 ZIP 打包方用了 GBK 编码
Windows 资源管理器或旧版压缩工具打的 ZIP,文件名默认用 GBK(或 GB18030)编码,但 archive/zip 始终按 UTF-8 解析 Header.Name,结果就是一堆 .txt。
最优解:让上游改用 UTF-8 编码打包(7-Zip 勾选 “UTF-8”、macOS 默认、zip -U)。若必须兼容,用 golang.org/x/text/encoding/simplifiedchinese 手动 decode:
decoded, _ := gbk.NewDecoder().String(f.Name)
Go 1.22+ 可尝试 zr.RegisterDecompressor(zip.FormatUTF8, ...),但非所有 ZIP 工具都设对 flags 位,不可依赖。
大文件解压卡死或 OOM,是因为误把整个 ZIP 读进内存
常见错误是 data, _ := io.ReadAll(zipFile) 再传给 zip.NewReader(bytes.NewReader(data), ...) —— 几百 MB 的 ZIP 直接吃光内存。
正确做法是直接用 zip.OpenReader 或 zip.NewReader 接原始 io.Reader(比如 os.File 或 HTTP body),逐个 f.Open() 流式读取内容,避免缓存全文到内存。
另外注意:zip.Writer 写入后必须调 writer.Close(),否则 ZIP 结尾结构损坏,解压会报 "invalid zip file";而 zip.Reader 不需要 Close,但其底层 os.File 必须 Close。
路径净化、压缩开关、编码适配、流式处理——这四点漏掉任意一个,生产环境都可能出事。尤其 filepath.Clean 和 header.Method,不写就是裸奔。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











