Files
cloudsave/pkg/tools/archive/archive.go
Aurélie DELHAIE 62911f2405
Some checks failed
CloudSave/pipeline/head There was a failure building this commit
CloudSave/pipeline/pr-main There was a failure building this commit
fix access right
2025-09-07 19:02:26 +02:00

141 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package archive
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
)
const (
// Tune these to your apps needs
maxCompressedUpload = 500 << 20 // 500 MiB compressed
maxUncompressedOutput = 1000 << 20 // 100 MiB after inflate
)
func Untar(file io.Reader, path string) error {
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
switch {
// if no more files are found return
case err == io.EOF:
return nil
// return any other error
case err != nil:
return err
// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
}
// the target location where the dir/file should be created
target := filepath.Clean(filepath.Join(path, filepath.Clean(header.Name)))
// the following switch could also be done using fi.Mode(), not sure if there
// a benefit of using one vs. the other.
// fi := header.FileInfo()
// check the file type
switch header.Typeflag {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0740); err != nil {
return err
}
}
// if it's a file create it
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, header.FileInfo().Mode())
if err != nil {
return err
}
limited := &io.LimitedReader{R: gzr, N: maxUncompressedOutput}
// copy over contents
if _, err := io.Copy(f, limited); err != nil {
return err
}
// manually close here after each file operation; defering would cause each file close
// to wait until all operations have completed.
if err := f.Close(); err != nil {
slog.Error("failed to close file", "err", err)
}
if limited.N == 0 {
// Limit exhausted → likely bomb
return fmt.Errorf("payload too large after decompression")
}
}
}
}
func Tar(file io.Writer, root string) error {
gw := gzip.NewWriter(file)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
// Walk again to add files
err := filepath.Walk(root, func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return fmt.Errorf("failed to walk through the directory: %w", walkErr)
}
relpath, err := filepath.Rel(root, path)
if err != nil {
return fmt.Errorf("failed to make relative path: %w", err)
}
// Create tar header
header, err := tar.FileInfoHeader(info, path)
if err != nil {
return fmt.Errorf("failed to make file info header: %w", err)
}
header.Name = relpath
if err := tw.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write header: %w", err)
}
if !info.Mode().IsRegular() {
return nil
}
file, err := os.Open(filepath.Clean(path))
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return fmt.Errorf("failed to copy file: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("writing tar entries: %w", err)
}
return nil
}