147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
package workspace
|
|
|
|
import (
|
|
"archive/tar"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ExtractTar extracts a tar archive from a reader to a directory.
|
|
func ExtractTar(reader io.Reader, destDir string) error {
|
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create destination directory: %w", err)
|
|
}
|
|
|
|
tarReader := tar.NewReader(reader)
|
|
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read tar header: %w", err)
|
|
}
|
|
|
|
// Sanitize path to prevent directory traversal
|
|
targetPath := filepath.Join(destDir, header.Name)
|
|
if !strings.HasPrefix(filepath.Clean(targetPath), filepath.Clean(destDir)+string(os.PathSeparator)) {
|
|
return fmt.Errorf("invalid file path in tar: %s", header.Name)
|
|
}
|
|
|
|
switch header.Typeflag {
|
|
case tar.TypeDir:
|
|
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
|
|
return fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
case tar.TypeReg:
|
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
|
return fmt.Errorf("failed to create parent directory: %w", err)
|
|
}
|
|
|
|
outFile, err := os.Create(targetPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create file: %w", err)
|
|
}
|
|
|
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
|
outFile.Close()
|
|
return fmt.Errorf("failed to write file: %w", err)
|
|
}
|
|
outFile.Close()
|
|
|
|
if err := os.Chmod(targetPath, os.FileMode(header.Mode)); err != nil {
|
|
log.Printf("Warning: failed to set file permissions: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExtractTarStripPrefix extracts a tar archive, stripping the top-level directory.
|
|
// Useful for Blender archives like "blender-4.2.3-linux-x64/".
|
|
func ExtractTarStripPrefix(reader io.Reader, destDir string) error {
|
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
tarReader := tar.NewReader(reader)
|
|
stripPrefix := ""
|
|
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Determine strip prefix from first entry (e.g., "blender-4.2.3-linux-x64/")
|
|
if stripPrefix == "" {
|
|
parts := strings.SplitN(header.Name, "/", 2)
|
|
if len(parts) > 0 {
|
|
stripPrefix = parts[0] + "/"
|
|
}
|
|
}
|
|
|
|
// Strip the top-level directory
|
|
name := strings.TrimPrefix(header.Name, stripPrefix)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
|
|
targetPath := filepath.Join(destDir, name)
|
|
|
|
switch header.Typeflag {
|
|
case tar.TypeDir:
|
|
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
|
|
return err
|
|
}
|
|
|
|
case tar.TypeReg:
|
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
|
return err
|
|
}
|
|
outFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := io.Copy(outFile, tarReader); err != nil {
|
|
outFile.Close()
|
|
return err
|
|
}
|
|
outFile.Close()
|
|
|
|
case tar.TypeSymlink:
|
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
|
return err
|
|
}
|
|
os.Remove(targetPath) // Remove existing symlink if present
|
|
if err := os.Symlink(header.Linkname, targetPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExtractTarFile extracts a tar file to a directory.
|
|
func ExtractTarFile(tarPath, destDir string) error {
|
|
file, err := os.Open(tarPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open tar file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
return ExtractTar(file, destDir)
|
|
}
|
|
|