Files
go-worm/lib/files/zip.go
T

108 lines
2.0 KiB
Go
Raw Normal View History

2026-09-01 20:20:15 +03:00
package files
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
var ErrZipTooLarge = errors.New("folder archive exceeds size limit")
// ZipDirectory writes dir into a temp .zip file and returns its path.
func ZipDirectory(dir string, maxBytes int64) (string, error) {
info, err := os.Stat(dir)
if err != nil {
return "", err
}
if !info.IsDir() {
return "", errors.New("path is not a directory")
}
tmp, err := os.CreateTemp("", "win64_mp-zip-*.zip")
if err != nil {
return "", err
}
path := tmp.Name()
cleanup := func() { _ = os.Remove(path) }
zw := zip.NewWriter(tmp)
var written int64
err = filepath.WalkDir(dir, func(current string, entry os.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.Type()&os.ModeSymlink != 0 {
return nil
}
if !isWithin(dir, current) {
return ErrOutsideRoot
}
rel, err := filepath.Rel(dir, current)
if err != nil {
return err
}
if rel == "." {
return nil
}
if entry.IsDir() {
return nil
}
info, err := entry.Info()
if err != nil {
return err
}
if maxBytes > 0 && written+info.Size() > maxBytes {
return ErrZipTooLarge
}
hdr, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
hdr.Name = filepath.ToSlash(rel)
hdr.Method = zip.Deflate
w, err := zw.CreateHeader(hdr)
if err != nil {
return err
}
f, err := os.Open(current)
if err != nil {
return err
}
n, err := io.Copy(w, f)
_ = f.Close()
if err != nil {
return err
}
written += n
return nil
})
if err != nil {
_ = zw.Close()
_ = tmp.Close()
cleanup()
return "", err
}
if err := zw.Close(); err != nil {
_ = tmp.Close()
cleanup()
return "", err
}
if err := tmp.Close(); err != nil {
cleanup()
return "", err
}
return path, nil
}
// ZipArchiveName returns a safe download filename for a directory path.
func ZipArchiveName(dir string) string {
base := filepath.Base(dir)
if base == "" || base == "." || base == string(os.PathSeparator) {
return "folder.zip"
}
return fmt.Sprintf("%s.zip", base)
}