zip.writer默认使用zip.store不压缩,需显式设置header.method = zip.deflate才能启用deflate压缩;go标准库仅支持该方法,且空或极小文件压缩效果不明显。

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 源码)才明显受益
解压时不做路径净化,../../../etc/passwd会直接覆盖系统文件
Go 的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 直接吃光内存。其实archive/zip本身支持流式读取,问题出在用法上。
- 从磁盘解压:用
zip.OpenReader(path),它内部基于os.File和io.Seeker,按需读取目录区和文件块 - 从网络流解压:必须先完整缓存(
io.ReadAll(resp.Body)),因为archive/zip不支持边读边解;别复用resp.Body - 写文件时永远用
io.Copy(dst, src)流式写入,别io.ReadAll(src)再os.WriteFile - 记得
defer r.Close(),否则文件描述符泄露,后续操作可能卡住
filepath.Clean之后要不要再strings.Contains查".."、os.MkdirAll时该不该信 ZIP 里的f.Mode()、以及为什么unzip -t报错而 Go 却静默成功——这些细节不踩一遍坑,根本没法上线。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











