From fb9041449ff750ff302b0909286237884df162af Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 6 Apr 2026 16:50:49 +0100 Subject: [PATCH 1/2] Fix duplicate directory entries in ListDir when archives have explicit dir entries Zip and tar archives can contain explicit directory entries (e.g. GitHub zipball downloads). ListDir was appending these entries directly but not tracking them in seenDirs, so the synthetic subdirectory logic would add a second entry for the same path. --- archives_test.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ tar.go | 3 +++ zip.go | 3 +++ 3 files changed, 67 insertions(+) diff --git a/archives_test.go b/archives_test.go index 4bd8225..6e8ff49 100644 --- a/archives_test.go +++ b/archives_test.go @@ -6,6 +6,7 @@ import ( "bytes" "compress/gzip" "io" + "os" "strings" "testing" "time" @@ -382,6 +383,66 @@ func TestOpenWithPrefix(t *testing.T) { } } +// createTestZipWithDirEntries creates a zip with explicit directory entries, +// like GitHub zipball downloads produce. +func createTestZipWithDirEntries() []byte { + buf := new(bytes.Buffer) + w := zip.NewWriter(buf) + + // Add explicit directory entry (GitHub zipballs do this) + header := &zip.FileHeader{ + Name: "project-abc123/", + Method: zip.Store, + Modified: time.Date(2026, 3, 30, 8, 14, 47, 0, time.UTC), + } + header.SetMode(0755 | os.ModeDir) + _, _ = w.CreateHeader(header) + + // Add files inside that directory + files := []struct { + name string + content string + }{ + {"project-abc123/README.md", "# Test"}, + {"project-abc123/src/main.go", "package main"}, + {"project-abc123/src/util.go", "package main"}, + } + + for _, file := range files { + f, _ := w.Create(file.name) + _, _ = f.Write([]byte(file.content)) + } + + _ = w.Close() + return buf.Bytes() +} + +func TestZipListDirNoDuplicatesWithExplicitDirEntries(t *testing.T) { + data := createTestZipWithDirEntries() + reader, err := openZip(bytes.NewReader(data)) + if err != nil { + t.Fatalf("openZip failed: %v", err) + } + defer func() { _ = reader.Close() }() + + files, err := reader.ListDir("") + if err != nil { + t.Fatalf("ListDir failed: %v", err) + } + + // Should have exactly one entry for the top-level directory + seen := map[string]int{} + for _, f := range files { + seen[f.Path]++ + } + + for path, count := range seen { + if count > 1 { + t.Errorf("ListDir root has %d entries for %q, want 1", count, path) + } + } +} + func TestGetStripPrefixNpm(t *testing.T) { // Create npm-style archive buf := new(bytes.Buffer) diff --git a/tar.go b/tar.go index 6c50884..a69633b 100644 --- a/tar.go +++ b/tar.go @@ -100,6 +100,9 @@ func (t *tarReader) ListDir(dirPath string) ([]FileInfo, error) { // Check if this file/dir is directly in the requested directory if isInDir(path, dirPath) { + if f.info.IsDir { + seenDirs[path] = true + } files = append(files, f.info) continue } diff --git a/zip.go b/zip.go index 09e1bd9..45fad14 100644 --- a/zip.go +++ b/zip.go @@ -54,6 +54,9 @@ func (z *zipReader) ListDir(dirPath string) ([]FileInfo, error) { // Check if this file/dir is directly in the requested directory if isInDir(path, dirPath) { + if f.FileInfo().IsDir() { + seenDirs[path] = true + } files = append(files, fileInfoFromZip(f)) continue } From b590339071ca270b6d4388295cbefcbf38baa4c3 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Mon, 6 Apr 2026 16:58:41 +0100 Subject: [PATCH 2/2] Add tar duplicate dir test and subdirectory coverage for ListDir --- archives_test.go | 97 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/archives_test.go b/archives_test.go index 6e8ff49..6094d9a 100644 --- a/archives_test.go +++ b/archives_test.go @@ -398,6 +398,15 @@ func createTestZipWithDirEntries() []byte { header.SetMode(0755 | os.ModeDir) _, _ = w.CreateHeader(header) + // Add explicit src/ subdirectory entry + srcHeader := &zip.FileHeader{ + Name: "project-abc123/src/", + Method: zip.Store, + Modified: time.Date(2026, 3, 30, 8, 14, 47, 0, time.UTC), + } + srcHeader.SetMode(0755 | os.ModeDir) + _, _ = w.CreateHeader(srcHeader) + // Add files inside that directory files := []struct { name string @@ -417,6 +426,60 @@ func createTestZipWithDirEntries() []byte { return buf.Bytes() } +// createTestTarGzWithDirEntries creates a tar.gz with explicit directory entries, +// like GitHub tarball downloads produce. +func createTestTarGzWithDirEntries() []byte { + buf := new(bytes.Buffer) + gw := gzip.NewWriter(buf) + tw := tar.NewWriter(gw) + + // Add explicit directory entries + for _, dir := range []string{"project-abc123/", "project-abc123/src/"} { + _ = tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeDir, + Name: dir, + Mode: 0755, + ModTime: time.Date(2026, 3, 30, 8, 14, 47, 0, time.UTC), + }) + } + + files := []struct { + name string + content string + }{ + {"project-abc123/README.md", "# Test"}, + {"project-abc123/src/main.go", "package main"}, + {"project-abc123/src/util.go", "package main"}, + } + + for _, file := range files { + _ = tw.WriteHeader(&tar.Header{ + Name: file.name, + Size: int64(len(file.content)), + Mode: 0644, + ModTime: time.Date(2026, 3, 30, 8, 14, 47, 0, time.UTC), + }) + _, _ = tw.Write([]byte(file.content)) + } + + _ = tw.Close() + _ = gw.Close() + return buf.Bytes() +} + +func assertNoDuplicates(t *testing.T, label string, files []FileInfo) { + t.Helper() + seen := map[string]int{} + for _, f := range files { + seen[f.Path]++ + } + for path, count := range seen { + if count > 1 { + t.Errorf("%s: %d entries for %q, want 1", label, count, path) + } + } +} + func TestZipListDirNoDuplicatesWithExplicitDirEntries(t *testing.T) { data := createTestZipWithDirEntries() reader, err := openZip(bytes.NewReader(data)) @@ -427,20 +490,36 @@ func TestZipListDirNoDuplicatesWithExplicitDirEntries(t *testing.T) { files, err := reader.ListDir("") if err != nil { - t.Fatalf("ListDir failed: %v", err) + t.Fatalf("ListDir root failed: %v", err) } + assertNoDuplicates(t, "ListDir root", files) - // Should have exactly one entry for the top-level directory - seen := map[string]int{} - for _, f := range files { - seen[f.Path]++ + files, err = reader.ListDir("project-abc123/") + if err != nil { + t.Fatalf("ListDir subdir failed: %v", err) } + assertNoDuplicates(t, "ListDir project-abc123/", files) +} - for path, count := range seen { - if count > 1 { - t.Errorf("ListDir root has %d entries for %q, want 1", count, path) - } +func TestTarListDirNoDuplicatesWithExplicitDirEntries(t *testing.T) { + data := createTestTarGzWithDirEntries() + reader, err := openTar(bytes.NewReader(data), "gzip") + if err != nil { + t.Fatalf("openTar failed: %v", err) + } + defer func() { _ = reader.Close() }() + + files, err := reader.ListDir("") + if err != nil { + t.Fatalf("ListDir root failed: %v", err) + } + assertNoDuplicates(t, "ListDir root", files) + + files, err = reader.ListDir("project-abc123/") + if err != nil { + t.Fatalf("ListDir subdir failed: %v", err) } + assertNoDuplicates(t, "ListDir project-abc123/", files) } func TestGetStripPrefixNpm(t *testing.T) {