582b5a6b08
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
181 lines
4.2 KiB
Go
181 lines
4.2 KiB
Go
package archive
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/ulikunitz/xz"
|
|
)
|
|
|
|
// buildTar writes a small dir+file tree as a tar stream.
|
|
func buildTar(t *testing.T, w io.Writer) {
|
|
t.Helper()
|
|
tw := tar.NewWriter(w)
|
|
if err := tw.WriteHeader(&tar.Header{Name: "factorio/", Typeflag: tar.TypeDir, Mode: 0o755}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := []byte("#!/bin/sh\necho hi\n")
|
|
if err := tw.WriteHeader(&tar.Header{Name: "factorio/bin/x64/factorio", Typeflag: tar.TypeReg, Mode: 0o755, Size: int64(len(body))}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := tw.Write(body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := tw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func makeTarXz(t *testing.T) []byte {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
xw, err := xz.NewWriter(&buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
buildTar(t, xw)
|
|
if err := xw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func makeTarGz(t *testing.T) []byte {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
gw := gzip.NewWriter(&buf)
|
|
buildTar(t, gw)
|
|
if err := gw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func makeZip(t *testing.T) []byte {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
zw := zip.NewWriter(&buf)
|
|
f, err := zw.Create("factorio/bin/x64/factorio")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.Write([]byte("#!/bin/sh\necho hi\n")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := zw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func TestDetectFormatMagicBytes(t *testing.T) {
|
|
// Extensionless names — magic bytes must carry detection (factorio's
|
|
// download URL has no file extension).
|
|
cases := []struct {
|
|
data []byte
|
|
want string
|
|
}{
|
|
{makeTarXz(t), "tar.xz"},
|
|
{makeTarGz(t), "tar.gz"},
|
|
{makeZip(t), "zip"},
|
|
{[]byte("plain text, not an archive"), ""},
|
|
}
|
|
for _, c := range cases {
|
|
if got := DetectFormat(c.data, "https://example.com/get-download/stable/headless/linux64"); got != c.want {
|
|
t.Errorf("DetectFormat = %q, want %q", got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDetectFormatExtensionFallback(t *testing.T) {
|
|
// Content too short for magic — extension hint decides.
|
|
if got := DetectFormat([]byte{}, "thing.tar.xz"); got != "tar.xz" {
|
|
t.Errorf("extension fallback = %q, want tar.xz", got)
|
|
}
|
|
if got := DetectFormat([]byte{}, "thing.tgz"); got != "tar.gz" {
|
|
t.Errorf("extension fallback = %q, want tar.gz", got)
|
|
}
|
|
}
|
|
|
|
func TestWalkAllFormats(t *testing.T) {
|
|
for name, data := range map[string][]byte{
|
|
"tar.xz": makeTarXz(t),
|
|
"tar.gz": makeTarGz(t),
|
|
"zip": makeZip(t),
|
|
} {
|
|
var files []string
|
|
var content []byte
|
|
entries, _, err := Walk(data, "blob", func(n string, mode int64, isDir bool, body io.Reader) error {
|
|
if isDir {
|
|
return nil
|
|
}
|
|
files = append(files, n)
|
|
b, err := io.ReadAll(body)
|
|
content = b
|
|
return err
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("%s: Walk: %v", name, err)
|
|
}
|
|
if entries == 0 || len(files) != 1 || files[0] != "factorio/bin/x64/factorio" {
|
|
t.Fatalf("%s: unexpected entries %d files %v", name, entries, files)
|
|
}
|
|
if string(content) != "#!/bin/sh\necho hi\n" {
|
|
t.Fatalf("%s: content mismatch: %q", name, content)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteAsTarRoundTrip(t *testing.T) {
|
|
var out bytes.Buffer
|
|
entries, bytesOut, err := WriteAsTar(makeTarXz(t), "blob", &out)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if entries != 2 {
|
|
t.Fatalf("entries = %d, want 2", entries)
|
|
}
|
|
if bytesOut == 0 {
|
|
t.Fatal("bytesOut = 0")
|
|
}
|
|
// Re-walk the emitted tar and check the file mode survived.
|
|
tr := tar.NewReader(&out)
|
|
var sawFile bool
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if hdr.Typeflag == tar.TypeReg {
|
|
sawFile = true
|
|
if hdr.Name != "factorio/bin/x64/factorio" {
|
|
t.Fatalf("name = %q", hdr.Name)
|
|
}
|
|
if hdr.Mode&0o111 == 0 {
|
|
t.Fatalf("exec bit lost: mode %o", hdr.Mode)
|
|
}
|
|
}
|
|
}
|
|
if !sawFile {
|
|
t.Fatal("no regular file in re-emitted tar")
|
|
}
|
|
}
|
|
|
|
func TestSafeEntryPathRejectsTraversal(t *testing.T) {
|
|
for _, bad := range []string{"../evil", "a/../../evil", "..", ""} {
|
|
if got := SafeEntryPath(bad); got != "" {
|
|
t.Errorf("SafeEntryPath(%q) = %q, want rejection", bad, got)
|
|
}
|
|
}
|
|
if got := SafeEntryPath("/abs/path"); got != "abs/path" {
|
|
t.Errorf("leading slash strip = %q", got)
|
|
}
|
|
}
|