mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Add test for tarfile extractall
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +20,152 @@ type TarExtractOptions struct {
|
|||||||
DontPreservePermissions bool
|
DontPreservePermissions bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func volnamelen(path string) int {
|
||||||
|
return len(filepath.VolumeName(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalSymlinksThatExist(path string) (string, error) {
|
||||||
|
volLen := volnamelen(path)
|
||||||
|
pathSeparator := string(os.PathSeparator)
|
||||||
|
|
||||||
|
if volLen < len(path) && os.IsPathSeparator(path[volLen]) {
|
||||||
|
volLen++
|
||||||
|
}
|
||||||
|
vol := path[:volLen]
|
||||||
|
dest := vol
|
||||||
|
linksWalked := 0
|
||||||
|
for start, end := volLen, volLen; start < len(path); start = end {
|
||||||
|
for start < len(path) && os.IsPathSeparator(path[start]) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
end = start
|
||||||
|
for end < len(path) && !os.IsPathSeparator(path[end]) {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
// On Windows, "." can be a symlink.
|
||||||
|
// We look it up, and use the value if it is absolute.
|
||||||
|
// If not, we just return ".".
|
||||||
|
isWindowsDot := runtime.GOOS == "windows" && path[volnamelen(path):] == "."
|
||||||
|
|
||||||
|
// The next path component is in path[start:end].
|
||||||
|
if end == start {
|
||||||
|
// No more path components.
|
||||||
|
break
|
||||||
|
} else if path[start:end] == "." && !isWindowsDot {
|
||||||
|
// Ignore path component ".".
|
||||||
|
continue
|
||||||
|
} else if path[start:end] == ".." {
|
||||||
|
// Back up to previous component if possible.
|
||||||
|
// Note that volLen includes any leading slash.
|
||||||
|
|
||||||
|
// Set r to the index of the last slash in dest,
|
||||||
|
// after the volume.
|
||||||
|
var r int
|
||||||
|
for r = len(dest) - 1; r >= volLen; r-- {
|
||||||
|
if os.IsPathSeparator(dest[r]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r < volLen || dest[r+1:] == ".." {
|
||||||
|
// Either path has no slashes
|
||||||
|
// (it's empty or just "C:")
|
||||||
|
// or it ends in a ".." we had to keep.
|
||||||
|
// Either way, keep this "..".
|
||||||
|
if len(dest) > volLen {
|
||||||
|
dest += pathSeparator
|
||||||
|
}
|
||||||
|
dest += ".."
|
||||||
|
} else {
|
||||||
|
// Discard everything since the last slash.
|
||||||
|
dest = dest[:r]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordinary path component. Add it to result.
|
||||||
|
|
||||||
|
if len(dest) > volnamelen(dest) && !os.IsPathSeparator(dest[len(dest)-1]) {
|
||||||
|
dest += pathSeparator
|
||||||
|
}
|
||||||
|
|
||||||
|
dest += path[start:end]
|
||||||
|
|
||||||
|
// Resolve symlink.
|
||||||
|
|
||||||
|
fi, err := os.Lstat(dest)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if end < len(path) {
|
||||||
|
dest += path[end:]
|
||||||
|
}
|
||||||
|
return filepath.Clean(dest), nil
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&fs.ModeSymlink == 0 {
|
||||||
|
if !fi.Mode().IsDir() && end < len(path) {
|
||||||
|
return "", fmt.Errorf("%s is not a directory while resolving symlinks in %s", dest, path)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found symlink.
|
||||||
|
|
||||||
|
linksWalked++
|
||||||
|
if linksWalked > 255 {
|
||||||
|
return "", fmt.Errorf("EvalSymlinksThatExist: too many symlinks in %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := os.Readlink(dest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isWindowsDot && !filepath.IsAbs(link) {
|
||||||
|
// On Windows, if "." is a relative symlink,
|
||||||
|
// just return ".".
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
path = link + path[end:]
|
||||||
|
|
||||||
|
v := volnamelen(link)
|
||||||
|
if v > 0 {
|
||||||
|
// Symlink to drive name is an absolute path.
|
||||||
|
if v < len(link) && os.IsPathSeparator(link[v]) {
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
vol = link[:v]
|
||||||
|
dest = vol
|
||||||
|
end = len(vol)
|
||||||
|
} else if len(link) > 0 && os.IsPathSeparator(link[0]) {
|
||||||
|
// Symlink to absolute path.
|
||||||
|
dest = link[:1]
|
||||||
|
end = 1
|
||||||
|
vol = link[:1]
|
||||||
|
volLen = 1
|
||||||
|
} else {
|
||||||
|
// Symlink to relative path; replace last
|
||||||
|
// path component in dest.
|
||||||
|
var r int
|
||||||
|
for r = len(dest) - 1; r >= volLen; r-- {
|
||||||
|
if os.IsPathSeparator(dest[r]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r < volLen {
|
||||||
|
dest = vol
|
||||||
|
} else {
|
||||||
|
dest = dest[:r]
|
||||||
|
}
|
||||||
|
end = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Clean(dest), nil
|
||||||
|
}
|
||||||
|
|
||||||
func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOptions) (count int, err error) {
|
func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOptions) (count int, err error) {
|
||||||
opts := TarExtractOptions{}
|
opts := TarExtractOptions{}
|
||||||
if len(optss) > 0 {
|
if len(optss) > 0 {
|
||||||
@@ -55,17 +202,11 @@ func ExtractAllFromTar(tr *tar.Reader, dest_path string, optss ...TarExtractOpti
|
|||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
dest := hdr.Name
|
dest := hdr.Name
|
||||||
dest = strings.TrimLeft(dest, "/")
|
if !filepath.IsAbs(dest) {
|
||||||
if !filepath.IsLocal(dest) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dest = filepath.Join(dest_path, dest)
|
dest = filepath.Join(dest_path, dest)
|
||||||
if dest, err = filepath.EvalSymlinks(dest); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = nil
|
|
||||||
} else {
|
|
||||||
return count, err
|
|
||||||
}
|
}
|
||||||
|
if dest, err = EvalSymlinksThatExist(dest); err != nil {
|
||||||
|
return count, err
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(filepath.Clean(dest), filepath.Clean(dest_path)+string(os.PathSeparator)) {
|
if !strings.HasPrefix(filepath.Clean(dest), filepath.Clean(dest_path)+string(os.PathSeparator)) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
79
tools/utils/tar_test.go
Normal file
79
tools/utils/tar_test.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Print
|
||||||
|
|
||||||
|
func TestTarExtract(t *testing.T) {
|
||||||
|
tdir := t.TempDir()
|
||||||
|
a, b := filepath.Join(tdir, "a"), filepath.Join(tdir, "b")
|
||||||
|
if err := os.Mkdir(a, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Mkdir(b, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tw := tar.NewWriter(&buf)
|
||||||
|
var files = []struct {
|
||||||
|
name, body string
|
||||||
|
}{
|
||||||
|
{"s/one.txt", "This archive contains some text files."},
|
||||||
|
{"b", b},
|
||||||
|
{"b/two.txt", "Get animal handling license."},
|
||||||
|
{"../b/three.txt", "Get animal handling license."},
|
||||||
|
{"nested/dir/", ""},
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: file.name,
|
||||||
|
Mode: 0600,
|
||||||
|
Size: int64(len(file.body)),
|
||||||
|
}
|
||||||
|
if file.name == "b" {
|
||||||
|
hdr.Linkname = file.body
|
||||||
|
hdr.Typeflag = tar.TypeSymlink
|
||||||
|
hdr.Size = 0
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Typeflag != tar.TypeSymlink && len(file.body) > 0 {
|
||||||
|
if _, err := tw.Write([]byte(file.body)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tw.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tr := tar.NewReader(&buf)
|
||||||
|
count, err := ExtractAllFromTar(tr, a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count != len(files)-2 {
|
||||||
|
t.Fatalf("Incorrect count of extracted files: %d != %d", count, len(files)-2)
|
||||||
|
}
|
||||||
|
entries := []string{}
|
||||||
|
if err = fs.WalkDir(os.DirFS(tdir), ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
entries = append(entries, path)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff([]string{".", "a", "a/b", "a/nested", "a/nested/dir", "a/s", "a/s/one.txt", "b"}, entries); diff != "" {
|
||||||
|
t.Fatalf("Directory contents not as expected: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user