Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit b95d6b6

Browse files
authored
fs: Fix nested dir listing for archives without explicit dirs (#339)
1 parent 3869ede commit b95d6b6

File tree

7 files changed

+155
-4
lines changed

7 files changed

+155
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
_gitignore
2+
__debug_bin

archiver.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ func nameOnDiskToNameInArchive(nameOnDisk, rootOnDisk, rootInArchive string) str
148148
//
149149
// For example, "a/b/c" => "b/c".
150150
func trimTopDir(dir string) string {
151+
if len(dir) > 0 && dir[0] == '/' {
152+
dir = dir[1:]
153+
}
151154
if pos := strings.Index(dir, "/"); pos >= 0 {
152155
return dir[pos+1:]
153156
}
@@ -159,6 +162,9 @@ func trimTopDir(dir string) string {
159162
//
160163
// For example, "a/b/c" => "a".
161164
func topDir(dir string) string {
165+
if len(dir) > 0 && dir[0] == '/' {
166+
dir = dir[1:]
167+
}
162168
if pos := strings.Index(dir, "/"); pos >= 0 {
163169
return dir[:pos]
164170
}

archiver_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,46 @@ import (
77
"testing"
88
)
99

10+
func TestTrimTopDir(t *testing.T) {
11+
for _, tc := range []struct {
12+
input string
13+
want string
14+
}{
15+
{input: "a/b/c", want: "b/c"},
16+
{input: "a", want: "a"},
17+
{input: "abc/def", want: "def"},
18+
{input: "/abc/def", want: "def"},
19+
} {
20+
tc := tc
21+
t.Run(tc.input, func(t *testing.T) {
22+
got := trimTopDir(tc.input)
23+
if got != tc.want {
24+
t.Errorf("want: '%s', got: '%s')", tc.want, got)
25+
}
26+
})
27+
}
28+
}
29+
30+
func TestTopDir(t *testing.T) {
31+
for _, tc := range []struct {
32+
input string
33+
want string
34+
}{
35+
{input: "a/b/c", want: "a"},
36+
{input: "a", want: "a"},
37+
{input: "abc/def", want: "abc"},
38+
{input: "/abc/def", want: "abc"},
39+
} {
40+
tc := tc
41+
t.Run(tc.input, func(t *testing.T) {
42+
got := topDir(tc.input)
43+
if got != tc.want {
44+
t.Errorf("want: '%s', got: '%s')", tc.want, got)
45+
}
46+
})
47+
}
48+
}
49+
1050
func TestFileIsIncluded(t *testing.T) {
1151
for i, tc := range []struct {
1252
included []string

fs.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,17 @@ func (f ArchiveFS) ReadDir(name string) ([]fs.DirEntry, error) {
458458
// leaving them to be inferred from the names of files instead (issue #330)
459459
// so as we traverse deeper, we need to implicitly find subfolders within
460460
// this current directory and add fake entries to the output
461-
remainingPath := strings.TrimPrefix(file.NameInArchive, name)
461+
remainingPath := file.NameInArchive
462+
463+
if name != "." {
464+
remainingPath = strings.TrimPrefix(file.NameInArchive, name)
465+
}
462466
nextDir := topDir(remainingPath) // if name in archive is "a/b/c" and root is "a", this becomes "b" (the implied folder to add)
463467
implicitDir := path.Join(name, nextDir) // the full path of the implied directory
464468

465469
// create fake entry only if no entry currently exists (don't overwrite a real entry)
466470
if _, ok := entries[implicitDir]; !ok {
467-
entries[implicitDir] = implicitDirEntry{implicitDir}
471+
entries[implicitDir] = implicitDirEntry{nextDir}
468472
}
469473

470474
return fs.SkipDir

fs_test.go

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"log"
1010
"net/http"
1111
"path"
12+
"reflect"
13+
"sort"
1214
"testing"
1315
)
1416

@@ -40,9 +42,14 @@ func TestPathWithoutTopDir(t *testing.T) {
4042
}
4143

4244
//go:generate zip testdata/test.zip go.mod
45+
//go:generate zip -qr9 testdata/nodir.zip archiver.go go.mod cmd/arc/main.go .github/ISSUE_TEMPLATE/bug_report.md .github/FUNDING.yml README.md .github/workflows/ubuntu-latest.yml
4346

44-
//go:embed testdata/test.zip
45-
var testZIP []byte
47+
var (
48+
//go:embed testdata/test.zip
49+
testZIP []byte
50+
//go:embed testdata/nodir.zip
51+
nodirZIP []byte
52+
)
4653

4754
func ExampleArchiveFS_Stream() {
4855
fsys := ArchiveFS{
@@ -70,3 +77,96 @@ func ExampleArchiveFS_Stream() {
7077
// go.mod
7178
// true
7279
}
80+
81+
func TestArchiveFS_ReadDir(t *testing.T) {
82+
for _, tc := range []struct {
83+
name string
84+
archive ArchiveFS
85+
want map[string][]string
86+
}{
87+
{
88+
name: "test.zip",
89+
archive: ArchiveFS{
90+
Stream: io.NewSectionReader(bytes.NewReader(testZIP), 0, int64(len(testZIP))),
91+
Format: Zip{},
92+
},
93+
// unzip -l testdata/test.zip
94+
want: map[string][]string{
95+
".": {"go.mod"},
96+
},
97+
},
98+
{
99+
name: "nodir.zip",
100+
archive: ArchiveFS{
101+
Stream: io.NewSectionReader(bytes.NewReader(nodirZIP), 0, int64(len(nodirZIP))),
102+
Format: Zip{},
103+
},
104+
// unzip -l testdata/nodir.zip
105+
want: map[string][]string{
106+
".": {".github", "README.md", "archiver.go", "cmd", "go.mod"},
107+
".github": {"FUNDING.yml", "ISSUE_TEMPLATE", "workflows"},
108+
"cmd": {"arc"},
109+
},
110+
},
111+
} {
112+
tc := tc
113+
t.Run(tc.name, func(t *testing.T) {
114+
t.Parallel()
115+
fsys := tc.archive
116+
for baseDir, wantLS := range tc.want {
117+
baseDir := baseDir
118+
wantLS := wantLS
119+
t.Run(fmt.Sprintf("ReadDir(%s)", baseDir), func(t *testing.T) {
120+
dis, err := fsys.ReadDir(baseDir)
121+
if err != nil {
122+
t.Error(err)
123+
}
124+
125+
dirs := []string{}
126+
for _, di := range dis {
127+
dirs = append(dirs, di.Name())
128+
}
129+
130+
// Stabilize the sort order
131+
sort.Strings(dirs)
132+
133+
if !reflect.DeepEqual(wantLS, dirs) {
134+
t.Errorf("ReadDir() got: %v, want: %v", dirs, wantLS)
135+
}
136+
})
137+
138+
// Uncomment to reproduce https://github.com/mholt/archiver/issues/340.
139+
/*
140+
t.Run(fmt.Sprintf("Open(%s)", baseDir), func(t *testing.T) {
141+
f, err := fsys.Open(baseDir)
142+
if err != nil {
143+
t.Error(err)
144+
}
145+
146+
rdf, ok := f.(fs.ReadDirFile)
147+
if !ok {
148+
t.Fatalf("'%s' did not return a fs.ReadDirFile, %+v", baseDir, rdf)
149+
}
150+
151+
dis, err := rdf.ReadDir(-1)
152+
if err != nil {
153+
t.Fatal(err)
154+
}
155+
156+
dirs := []string{}
157+
for _, di := range dis {
158+
dirs = append(dirs, di.Name())
159+
}
160+
161+
// Stabilize the sort order
162+
sort.Strings(dirs)
163+
164+
if !reflect.DeepEqual(wantLS, dirs) {
165+
t.Errorf("Open().ReadDir(-1) got: %v, want: %v", dirs, wantLS)
166+
}
167+
})
168+
*/
169+
}
170+
})
171+
}
172+
}

testdata/nodir.zip

9.23 KB
Binary file not shown.

testdata/test.zip

13 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)