Go解压ZIP必须校验路径防Zip Slip、及时关闭单条目句柄防资源泄露、流式io.Copy防OOM、UTF-8中文名需手动解码;zip.OpenReader前须检查文件完整性,避免unexpected EOF。

Go 用 archive/zip 解压 zip 包,别直接读文件流
直接 zip.OpenReader 然后遍历 Reader.File 列表是最稳妥的起点,但很多人卡在路径处理和权限还原上。zip 文件里路径可能含 ../,不校验会写到任意目录;文件权限也默认是 0600,不是原始权限。
- 先用
zip.OpenReader("a.zip")打开,别用os.Open+zip.NewReader—— 前者自动处理 EOF 和 CRC 校验,后者容易漏错 - 遍历
zr.File时,对每个f调用f.Open()得到io.ReadCloser,再用io.Copy写入目标文件 - 必须校验
f.Name:用strings.HasPrefix(f.Name, "../")或path.Clean(f.Name) != f.Name拒绝路径逃逸 - 创建目录用
os.MkdirAll(path.Dir(dstPath), 0755),写文件用os.O_CREATE | os.O_WRONLY | os.O_TRUNC,权限设为f.Mode().Perm()(注意 zip 里权限位常不全,可 fallback 到 0644)
解 tar.gz 要两层包装:gzip + tar,顺序不能反
archive/tar 本身不处理压缩,tar.gz 是 gzip 压缩后的 tar 流,必须先解 gzip 再喂给 tar.NewReader。反过来或跳过 gzip 层,会得到 “invalid tar header” 错误。
- 打开文件后,先套一层
gzip.NewReader(f),再把它的io.Reader传给tar.NewReader - tar header 的
Header.Typeflag决定类型:普通文件是tar.TypeReg,目录是tar.TypeDir,软链是tar.TypeSymlink,要分别处理 - 写文件前,对目录调用
os.MkdirAll并设权限为hdr.FileInfo().Mode();对普通文件,用os.O_CREATE | os.O_WRONLY | os.O_TRUNC,权限同样取自hdr.FileInfo().Mode() - 注意
hdr.Size是文件长度,io.CopyN比io.Copy更安全,避免读超
常见错误:invalid tar header 和 zip: not a valid zip file
这两个错误几乎都源于输入流没重置或被提前消费。比如用 bytes.NewReader 构造测试数据后,没 Seek(0, io.SeekStart) 就传给解压函数;或者同一个 io.ReadCloser 被多次调用 Read 导致后续读不到头。
- 检查输入源是否支持
io.Seeker:zip 要求随机访问,tar.gz 只需顺序读,所以 tar.gz 可以用管道流,zip 不行 - 如果从 HTTP body 解压,务必用
io.Copy先写到临时bytes.Buffer或os.TempFile,再打开 ——http.Response.Body不支持Seek - gzip 层出错时,错误常被吞掉,加一层
defer g.Close()后打印g.Close()返回值,能捕获 CRC 校验失败
性能与兼容性:小包用内存,大包流式处理
解压逻辑本身不耗 CPU,瓶颈在磁盘 I/O 和路径操作。10MB 以下 zip/tar.gz 直接读进 []byte 处理没问题;超过 50MB 就该用流式,否则 GC 压力大、启动慢。
立即学习“go语言免费学习笔记(深入)”;
- 流式解压时,tar 的
Next()会反复复用同一个Header实例,别保存指针,要hdr := *hdr拷贝 - zip 的
File.Open()返回的ReadCloser必须显式Close(),否则底层zip.Reader的 buffer 不释放 - Windows 下注意路径分隔符:zip 习惯用
/,tar 用/,但os.MkdirAll在 Windows 接受/,不用转\


















