Files
LocalAI/pkg/utils/untar.go
2026-05-15 11:12:13 +02:00

120 lines
2.5 KiB
Go

package utils
import (
"archive/tar"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/klauspost/compress/zip"
"github.com/mholt/archiver/v3"
)
func IsArchive(file string) bool {
uaIface, err := archiver.ByExtension(file)
if err != nil {
return false
}
_, ok := uaIface.(archiver.Unarchiver)
return ok
}
func ExtractArchive(archive, dst string) error {
uaIface, err := archiver.ByExtension(archive)
if err != nil {
return err
}
un, ok := uaIface.(archiver.Unarchiver)
if !ok {
return fmt.Errorf("format specified by source filename is not an archive format: %s (%T)", archive, uaIface)
}
mytar := &archiver.Tar{
OverwriteExisting: true,
MkdirAll: true,
ImplicitTopLevelFolder: false,
ContinueOnError: true,
}
switch v := uaIface.(type) {
case *archiver.Tar:
uaIface = mytar
case *archiver.TarBrotli:
v.Tar = mytar
case *archiver.TarBz2:
v.Tar = mytar
case *archiver.TarGz:
v.Tar = mytar
case *archiver.TarLz4:
v.Tar = mytar
case *archiver.TarSz:
v.Tar = mytar
case *archiver.TarXz:
v.Tar = mytar
case *archiver.TarZstd:
v.Tar = mytar
}
extractRoot, err := filepath.Abs(dst)
if err != nil {
return err
}
err = archiver.Walk(archive, func(f archiver.File) error {
if err := validateArchiveMemberPath(extractRoot, archiveMemberName(f)); err != nil {
return err
}
if f.FileInfo.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("archive contains a symlink")
}
return nil
})
if err != nil {
return err
}
return un.Unarchive(archive, dst)
}
func archiveMemberName(f archiver.File) string {
switch h := f.Header.(type) {
case tar.Header:
return h.Name
case *tar.Header:
return h.Name
case zip.FileHeader:
return h.Name
case *zip.FileHeader:
return h.Name
default:
return f.Name()
}
}
func validateArchiveMemberPath(root, name string) error {
if name == "" {
return fmt.Errorf("archive contains an empty path")
}
normalizedName := filepath.FromSlash(strings.ReplaceAll(name, "\\", "/"))
cleanedName := filepath.Clean(normalizedName)
if filepath.IsAbs(cleanedName) || cleanedName == ".." || strings.HasPrefix(cleanedName, ".."+string(os.PathSeparator)) {
return fmt.Errorf("archive contains an unsafe path: %s", name)
}
targetPath := filepath.Join(root, cleanedName)
relativePath, err := filepath.Rel(root, targetPath)
if err != nil {
return err
}
if relativePath == ".." || strings.HasPrefix(relativePath, ".."+string(os.PathSeparator)) || filepath.IsAbs(relativePath) {
return fmt.Errorf("archive contains an unsafe path: %s", name)
}
return nil
}