Files
TaskPool/internal/utils/archive.go
T
admin e6956aa001 Initial commit: TaskPool React panel
- React frontend with route-level code splitting
- Backend rebranded from Baihu to TaskPool
- DB brand migration script and local compatibility
2026-07-26 08:43:52 +08:00

226 lines
4.0 KiB
Go

package utils
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
)
func CreateZip(dst io.Writer, basePaths []string) (err error) {
w := zip.NewWriter(dst)
defer func() {
if closeErr := w.Close(); err == nil {
err = closeErr
}
}()
for _, basePath := range basePaths {
info, err := os.Lstat(basePath)
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
continue
}
if info.Mode().IsRegular() {
if err := addZipFile(w, basePath, filepath.Base(basePath), info); err != nil {
return err
}
continue
}
if info.IsDir() {
baseName := filepath.Base(basePath)
if err := filepath.WalkDir(basePath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.Type()&fs.ModeSymlink != 0 {
return nil
}
rel, err := filepath.Rel(basePath, path)
if err != nil || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
return nil
}
name := baseName
if rel != "." {
name = filepath.Join(baseName, rel)
}
name = filepath.ToSlash(name)
info, err := d.Info()
if err != nil {
return err
}
if d.IsDir() {
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = name + "/"
_, err = w.CreateHeader(header)
return err
}
if info.Mode().IsRegular() {
return addZipFile(w, path, name, info)
}
return nil
}); err != nil {
return err
}
}
}
return nil
}
func addZipFile(w *zip.Writer, path, name string, info os.FileInfo) error {
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = filepath.ToSlash(name)
header.Method = zip.Deflate
writer, err := w.CreateHeader(header)
if err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
}
func ExtractZip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
// 安全检查:防止路径遍历 (ZipSlip)
rel, err := filepath.Rel(dest, fpath)
if err != nil || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
continue
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, 0755)
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
func ExtractTar(src, dest string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
return extractTarReader(tar.NewReader(file), dest)
}
func ExtractTarGz(src, dest string) error {
file, err := os.Open(src)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
return extractTarReader(tar.NewReader(gzr), dest)
}
func extractTarReader(tr *tar.Reader, dest string) error {
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
fpath := filepath.Join(dest, header.Name)
// 安全检查:防止路径遍历
rel, err := filepath.Rel(dest, fpath)
if err != nil || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." {
continue
}
switch header.Typeflag {
case tar.TypeDir:
os.MkdirAll(fpath, 0755)
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
return err
}
outFile, err := os.Create(fpath)
if err != nil {
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return err
}
outFile.Close()
os.Chmod(fpath, os.FileMode(header.Mode))
}
}
return nil
}