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) } }