package archive import ( "archive/tar" "compress/gzip" "fmt" "io" "log/slog" "os" "path/filepath" ) const ( // Tune these to your app’s 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, 0600); 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 }