Create Zip Archive with Traversal Guard
Walk a directory into a zip file while rejecting entries that escape the root, preserving relative paths with forward slashes.
Code
package main
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func WriteZip(sourceDir, target string) error {
out, err := os.Create(target)
if err != nil {
return err
}
defer out.Close()
archive := zip.NewWriter(out)
defer archive.Close()
root := filepath.Clean(sourceDir)
return filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
return nil
}
relative, err := filepath.Rel(root, path)
if err != nil {
return err
}
if relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
return fmt.Errorf("refusing path outside root: %s", relative)
}
info, err := entry.Info()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(relative)
header.Method = zip.Deflate
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
_, copyErr := io.Copy(writer, file)
closeErr := file.Close()
if copyErr != nil {
return copyErr
}
return closeErr
})
}
func main() {
source, err := os.MkdirTemp("", "zip-source-")
if err != nil {
panic(err)
}
defer os.RemoveAll(source)
if err := os.WriteFile(filepath.Join(source, "hello.txt"), []byte("hello"), 0o644); err != nil {
panic(err)
}
target := filepath.Join(os.TempDir(), "devexamples-demo.zip")
defer os.Remove(target)
if err := WriteZip(source, target); err != nil {
panic(err)
}
reader, err := zip.OpenReader(target)
if err != nil {
panic(err)
}
defer reader.Close()
for _, file := range reader.File {
fmt.Println("archived:", file.Name)
}
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.