sevenzip (Go)
| Language | Go |
|---|---|
| License | BSD 3-clause |
| Repository | github.com/bodgit/sevenzip |
| Documentation | pkg.go.dev/github.com/bodgit/sevenzip |
| Depends on | pure Go (uses Go compression packages for LZMA, Zstandard, Brotli, LZ4 and others) |
| Read / write | yes / no; LZMA, LZMA2, PPMd, BZip2, Deflate, Zstandard, Brotli, LZ4, Copy, Delta, BCJ, BCJ2, ARM, ARM64, PPC, SPARC; AES-256 |
sevenzip reads 7z archives with an API modeled on archive/zip: open the archive, loop over r.File, call f.Open() to get an io.ReadCloser. It handles compressed and encrypted headers, multi-volume archives (.7z.001) and self-extracting .exe archives, verifies CRC-32 values, and implements fs.FS so you can use an archive with fs.WalkDir, http.FileServer or template.ParseFS.
It only reads. Files in a 7z folder (solid block) share one compressed stream; the library caches the stream so that extracting files in archive order is fast, as long as you close each file before opening the next.
Installation#
go get github.com/bodgit/sevenzip
Basic usage: extract a 7z archive#
package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/bodgit/sevenzip"
)
func extractFile(f *sevenzip.File, dest string) error {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close() // close before opening the next file: keeps the stream cache working
path := filepath.Join(dest, f.Name)
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("illegal file path: %s", f.Name)
}
if f.FileInfo().IsDir() {
return os.MkdirAll(path, 0o755)
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
out, err := os.Create(path)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, rc)
return err
}
func main() {
r, err := sevenzip.OpenReader("archive.7z")
if err != nil {
log.Fatal(err)
}
defer r.Close()
for _, f := range r.File {
fmt.Printf("%s %d bytes %s\n", f.Name, f.UncompressedSize, f.Modified)
if err := extractFile(f, "output"); err != nil {
log.Fatal(err)
}
}
}
How to open a password-protected 7z archive#
r, err := sevenzip.OpenReaderWithPassword("secret.7z", "password")
if err != nil {
var e *sevenzip.ReadError
if errors.As(err, &e) && e.Encrypted {
log.Fatal("wrong or missing password")
}
log.Fatal(err)
}
defer r.Close()
A wrong password can’t always be detected reliably (AES-CBC has no check value). ReadError.Encrypted is a hint; a CRC mismatch while reading a file is the other sign.
How to read a 7z archive from memory or an io.ReaderAt#
data, err := os.ReadFile("archive.7z") // or bytes from a download
if err != nil {
log.Fatal(err)
}
r, err := sevenzip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
log.Fatal(err)
}
for _, f := range r.File {
fmt.Println(f.Name)
}
NewReaderWithPassword(r, size, password) is the encrypted variant.
How to use a 7z archive as an fs.FS#
r, err := sevenzip.OpenReader("site.7z")
if err != nil {
log.Fatal(err)
}
defer r.Close()
// read one file by path
b, err := fs.ReadFile(r, "docs/readme.txt")
// walk all files
fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error {
fmt.Println(path)
return err
})
How to open a multi-volume archive#
Pass the first volume; the library finds .002, .003 and so on:
r, err := sevenzip.OpenReader("backup.7z.001")
if err != nil {
log.Fatal(err)
}
defer r.Close()
fmt.Println(r.Volumes()) // all volume file names
Tips#
- Extract in the order of
r.File. Random order, or extracting in parallel from one stream, makes the library decompress the same data again. - To parallelize, group files by
f.Stream(the folder index) and give each group its own goroutine. - Register extra decompressors for custom method IDs with
sevenzip.RegisterDecompressor.