archive/zip
| Language | Go |
|---|---|
| License | BSD 3-clause (Go standard library) |
| Repository | go.googlesource.com/go |
| Documentation | pkg.go.dev/archive/zip |
| Depends on | nothing (standard library) |
| Read / write | yes / yes, ZIP64, stored and DEFLATE; other methods with RegisterCompressor; no encryption |
archive/zip reads and writes ZIP archives with stored and DEFLATE entries and handles ZIP64 automatically. zip.Reader (from zip.OpenReader or zip.NewReader on any io.ReaderAt) implements fs.FS, so fs.WalkDir, fs.ReadFile and http.FS work on an archive. zip.Writer writes to any io.Writer, and Writer.AddFS (Go 1.22+) adds a whole directory tree.
It doesn’t support encryption, and it can’t modify an archive in place: create a new one and copy entries with Writer.Copy, which copies compressed data without recompressing.
Installation#
Nothing to install: import "archive/zip".
Basic usage: extract a ZIP file#
package main
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
r, err := zip.OpenReader("in.zip")
if err != nil {
log.Fatal(err)
}
defer r.Close()
target := "out"
for _, f := range r.File {
fmt.Printf("%-40s %10d -> %10d\n", f.Name, f.UncompressedSize64, f.CompressedSize64)
dest := filepath.Join(target, f.Name)
// Zip Slip protection: the path must stay inside target
if !strings.HasPrefix(dest, filepath.Clean(target)+string(os.PathSeparator)) {
log.Fatalf("bad entry name: %s", f.Name)
}
if f.FileInfo().IsDir() {
os.MkdirAll(dest, 0o755)
continue
}
if err := extractFile(f, dest); err != nil {
log.Fatal(err)
}
}
}
func extractFile(f *zip.File, dest string) error {
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return err
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode().Perm())
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, rc) // the reader returns zip.ErrChecksum on a CRC mismatch
return err
}
How to create a ZIP file#
package main
import (
"archive/zip"
"io"
"log"
"os"
"time"
)
func main() {
out, err := os.Create("out.zip")
if err != nil {
log.Fatal(err)
}
defer out.Close()
w := zip.NewWriter(out)
// simple entry: DEFLATE, no modification time
f, err := w.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
io.WriteString(f, "hello from Go\n")
// entry with metadata
hdr := &zip.FileHeader{
Name: "docs/report.pdf",
Method: zip.Store, // already compressed, don't deflate again
Modified: time.Now(),
}
hdr.SetMode(0o644)
f, err = w.CreateHeader(hdr)
if err != nil {
log.Fatal(err)
}
src, err := os.Open("report.pdf")
if err != nil {
log.Fatal(err)
}
io.Copy(f, src)
src.Close()
if err := w.Close(); err != nil { // writes the central directory
log.Fatal(err)
}
}
Only the most recently created entry can be written to; Create / CreateHeader finish the previous one.
How to zip a whole directory#
w := zip.NewWriter(out)
if err := w.AddFS(os.DirFS("photos")); err != nil { // Go 1.22+
log.Fatal(err)
}
w.Close()
How to read one file from a ZIP#
r, err := zip.OpenReader("in.zip")
if err != nil {
log.Fatal(err)
}
defer r.Close()
data, err := fs.ReadFile(r, "docs/readme.txt") // zip.ReadCloser is an fs.FS
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
How to use better or other compression#
Register a compressor for the Writer, for example a faster DEFLATE at maximum level or Zstandard from klauspost/compress:
import (
"archive/zip"
"compress/flate"
"io"
"github.com/klauspost/compress/zstd"
)
w := zip.NewWriter(out)
w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
return flate.NewWriter(out, flate.BestCompression)
})
// Zstandard: method 93; readers must register the matching decompressor
w.RegisterCompressor(zstd.ZipMethodWinZip, zstd.ZipCompressor())
For reading, zip.RegisterDecompressor(zstd.ZipMethodWinZip, zstd.ZipDecompressor()).
Tips#
zip.NewReader(bytes.NewReader(data), int64(len(data)))reads an archive in memory.- File names without the UTF-8 flag are returned as raw bytes in a string;
FileHeader.NonUTF8tells you that. - Set
GODEBUG=zipinsecurepath=0to makeNewReaderreturnzip.ErrInsecurePathfor archives with..or absolute paths. Writer.SetCommentsets the archive comment;Writer.SetOffsetlets you append a ZIP to an existing file (for self-extracting binaries).