- Add zip folder API endpoint - Add download button to folder rows - Enforce max archive size limit - Document endpoint in OpenAPI
108 lines
2.0 KiB
Go
108 lines
2.0 KiB
Go
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)
|
|
}
|