用 archive/tar 打包目录时权限丢失,因手动构造 tar.Header 未设 Mode/Uid/Gid;正确做法是用 tar.FileInfoHeader(fi, "") 生成后再改 Name 为相对路径,并为目录设 TypeDir 和末尾 /。

用 archive/tar 打包目录时,为什么解压后权限全丢了?
因为 tar.FileInfoHeader 虽能自动填充 Mode、Uid、Gid 等字段,但如果你手动构造 tar.Header 并只设了 Name 和 Size,就等于放弃了所有元数据。解压工具看到空权限,只能按默认值(如 0644)创建文件。
- ✅ 正确做法:始终用
tar.FileInfoHeader(fi, "")生成 header,再手动覆盖hdr.Name为相对路径(如"src/main.go") - ⚠️ 注意:
fi.Name()是文件名,不是完整路径;不修正hdr.Name会导致所有文件都写到 tar 根目录下 - ? 目录条目必须设
hdr.Typeflag = tar.TypeDir,且hdr.Name末尾带/,否则解压器可能忽略它 - ? 权限位(如
0755)会原样保留,但 Windows 上无意义;Linux/macOS 解压时可直接生效
用 archive/zip 压缩中文文件名,为什么解压乱码?
ZIP 规范允许 UTF-8 编码文件名,但默认关闭。Go 的 zip.FileHeader 不自动启用它,多数解压工具(尤其是 Windows 自带的)仍按 CP437 解码,结果就是一堆 或乱码。
- ✅ 必须显式设置:
header.Flags = 1(即zip.UseUTF8标志),否则中文名无效 - ⚠️ 路径分隔符必须用
/,哪怕在 Windows 上调用filepath.ToSlash()—— 用\会导致 7-Zip 等工具无法识别子目录 - ? 打包目录时,
filepath.Walk会钻进.git、node_modules,得手动过滤:if strings.HasPrefix(fi.Name(), ".") || fi.Mode()&os.ModeSymlink != 0 - ? 小文件可用
w.Create("a.txt")返回的io.Writer;大文件务必用w.CreateHeader(header)+io.Copy,避免内存暴涨
tar + gzip 合成 .tar.gz,为什么解压报 invalid checksum?
因为 gzip 是流式压缩,尾部有校验数据;如果没等 tar.Writer 写完就关了 gzip.Writer,这部分就丢了。错误顺序或漏掉某层 Close() 都会触发这个错。
- ✅ 正确链路:
file → gzip.NewWriter(file) → tar.NewWriter(gzWriter) - ✅ 关闭顺序必须严格倒序:
tarWriter.Close()→gzipWriter.Close()→file.Close() - ⚠️ 千万别省略
tarWriter.Close():它负责写入 tar 结束块(两个 512 字节零块),缺了会导致部分解压器卡住或截断 - ⚡ 想更高压缩率?把
gzip.NewWriter换成zstd.NewWriter(需引入github.com/klauspost/compress/zstd),API 完全一致
解压 ZIP 或 TAR 时,如何防止 ../../etc/passwd 这类路径遍历攻击?
用户提供的 ZIP/TAR 文件不可信。直接拼接 destDir + header.Name 可能跳出目标目录,覆盖系统关键文件。
立即学习“go语言免费学习笔记(深入)”;
- ✅ 解压前必须清洗路径:
cleanPath := filepath.Clean(header.Name) - ❌ 拒绝两种情况:
cleanPath != header.Name(含..或多余/),或strings.HasPrefix(cleanPath, "..") - ? 创建目录前,先
os.MkdirAll(filepath.Dir(dstPath), 0755);写文件前确认!fi.IsDir() - ?
zip.File和tar.Header都不保证路径安全 —— 这是调用方的责任,标准库不代劳
最易被忽略的点:所有归档操作都依赖准确的 Size 字段(tar.Header.Size / zip.FileHeader.UncompressedSize64)。设错会导致解压时内容截断或阻塞,而 Go 标准库不会校验它是否匹配实际读取字节数 —— 你得自己确保 io.Copy 写入量和 header 里填的一致。










