Skip to content

Commit 8d074f6

Browse files
ianlancetaylorgopherbot
authored andcommitted
archive/zip: error if using io/fs on zip with duplicate entries
Fixes #50390 Change-Id: I92787cdb3fa198ff88dcaadeccfcb49a3a6a88cf Reviewed-on: https://go-review.googlesource.com/c/go/+/374954 Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Joseph Tsai <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 960ffa9 commit 8d074f6

File tree

3 files changed

+131
-13
lines changed

3 files changed

+131
-13
lines changed

src/archive/zip/reader.go

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -662,18 +662,22 @@ type fileListEntry struct {
662662
name string
663663
file *File
664664
isDir bool
665+
isDup bool
665666
}
666667

667668
type fileInfoDirEntry interface {
668669
fs.FileInfo
669670
fs.DirEntry
670671
}
671672

672-
func (e *fileListEntry) stat() fileInfoDirEntry {
673+
func (e *fileListEntry) stat() (fileInfoDirEntry, error) {
674+
if e.isDup {
675+
return nil, errors.New(e.name + ": duplicate entries in zip file")
676+
}
673677
if !e.isDir {
674-
return headerFileInfo{&e.file.FileHeader}
678+
return headerFileInfo{&e.file.FileHeader}, nil
675679
}
676-
return e
680+
return e, nil
677681
}
678682

679683
// Only used for directories.
@@ -708,35 +712,61 @@ func toValidName(name string) string {
708712

709713
func (r *Reader) initFileList() {
710714
r.fileListOnce.Do(func() {
715+
// files and knownDirs map from a file/directory name
716+
// to an index into the r.fileList entry that we are
717+
// building. They are used to mark duplicate entries.
718+
files := make(map[string]int)
719+
knownDirs := make(map[string]int)
720+
721+
// dirs[name] is true if name is known to be a directory,
722+
// because it appears as a prefix in a path.
711723
dirs := make(map[string]bool)
712-
knownDirs := make(map[string]bool)
724+
713725
for _, file := range r.File {
714726
isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/'
715727
name := toValidName(file.Name)
716728
if name == "" {
717729
continue
718730
}
731+
732+
if idx, ok := files[name]; ok {
733+
r.fileList[idx].isDup = true
734+
continue
735+
}
736+
if idx, ok := knownDirs[name]; ok {
737+
r.fileList[idx].isDup = true
738+
continue
739+
}
740+
719741
for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
720742
dirs[dir] = true
721743
}
744+
745+
idx := len(r.fileList)
722746
entry := fileListEntry{
723747
name: name,
724748
file: file,
725749
isDir: isDir,
726750
}
727751
r.fileList = append(r.fileList, entry)
728752
if isDir {
729-
knownDirs[name] = true
753+
knownDirs[name] = idx
754+
} else {
755+
files[name] = idx
730756
}
731757
}
732758
for dir := range dirs {
733-
if !knownDirs[dir] {
734-
entry := fileListEntry{
735-
name: dir,
736-
file: nil,
737-
isDir: true,
759+
if _, ok := knownDirs[dir]; !ok {
760+
if idx, ok := files[dir]; ok {
761+
r.fileList[idx].isDup = true
762+
} else {
763+
entry := fileListEntry{
764+
name: dir,
765+
file: nil,
766+
isDir: true,
767+
}
768+
r.fileList = append(r.fileList, entry)
738769
}
739-
r.fileList = append(r.fileList, entry)
740770
}
741771
}
742772

@@ -831,7 +861,7 @@ type openDir struct {
831861
}
832862

833863
func (d *openDir) Close() error { return nil }
834-
func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat(), nil }
864+
func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat() }
835865

836866
func (d *openDir) Read([]byte) (int, error) {
837867
return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errors.New("is a directory")}
@@ -850,7 +880,11 @@ func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
850880
}
851881
list := make([]fs.DirEntry, n)
852882
for i := range list {
853-
list[i] = d.files[d.offset+i].stat()
883+
s, err := d.files[d.offset+i].stat()
884+
if err != nil {
885+
return nil, err
886+
}
887+
list[i] = s
854888
}
855889
d.offset += n
856890
return list, nil

src/archive/zip/reader_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,35 @@ var tests = []ZipTest{
505505
},
506506
},
507507
},
508+
{
509+
Name: "dupdir.zip",
510+
File: []ZipTestFile{
511+
{
512+
Name: "a/",
513+
Content: []byte{},
514+
Mode: fs.ModeDir | 0666,
515+
Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),
516+
},
517+
{
518+
Name: "a/b",
519+
Content: []byte{},
520+
Mode: 0666,
521+
Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),
522+
},
523+
{
524+
Name: "a/b/",
525+
Content: []byte{},
526+
Mode: fs.ModeDir | 0666,
527+
Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),
528+
},
529+
{
530+
Name: "a/b/c",
531+
Content: []byte{},
532+
Mode: 0666,
533+
Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),
534+
},
535+
},
536+
},
508537
}
509538

510539
func TestReader(t *testing.T) {
@@ -1141,6 +1170,7 @@ func TestFS(t *testing.T) {
11411170
[]string{"a/b/c"},
11421171
},
11431172
} {
1173+
test := test
11441174
t.Run(test.file, func(t *testing.T) {
11451175
t.Parallel()
11461176
z, err := OpenReader(test.file)
@@ -1155,6 +1185,60 @@ func TestFS(t *testing.T) {
11551185
}
11561186
}
11571187

1188+
func TestFSWalk(t *testing.T) {
1189+
for _, test := range []struct {
1190+
file string
1191+
want []string
1192+
wantErr bool
1193+
}{
1194+
{
1195+
file: "testdata/unix.zip",
1196+
want: []string{".", "dir", "dir/bar", "dir/empty", "hello", "readonly"},
1197+
},
1198+
{
1199+
file: "testdata/subdir.zip",
1200+
want: []string{".", "a", "a/b", "a/b/c"},
1201+
},
1202+
{
1203+
file: "testdata/dupdir.zip",
1204+
wantErr: true,
1205+
},
1206+
} {
1207+
test := test
1208+
t.Run(test.file, func(t *testing.T) {
1209+
t.Parallel()
1210+
z, err := OpenReader(test.file)
1211+
if err != nil {
1212+
t.Fatal(err)
1213+
}
1214+
var files []string
1215+
sawErr := false
1216+
err = fs.WalkDir(z, ".", func(path string, d fs.DirEntry, err error) error {
1217+
if err != nil {
1218+
if !test.wantErr {
1219+
t.Errorf("%s: %v", path, err)
1220+
}
1221+
sawErr = true
1222+
return nil
1223+
}
1224+
files = append(files, path)
1225+
return nil
1226+
})
1227+
if err != nil {
1228+
t.Errorf("fs.WalkDir error: %v", err)
1229+
}
1230+
if test.wantErr && !sawErr {
1231+
t.Error("succeeded but want error")
1232+
} else if !test.wantErr && sawErr {
1233+
t.Error("unexpected error")
1234+
}
1235+
if test.want != nil && !reflect.DeepEqual(files, test.want) {
1236+
t.Errorf("got %v want %v", files, test.want)
1237+
}
1238+
})
1239+
}
1240+
}
1241+
11581242
func TestFSModTime(t *testing.T) {
11591243
t.Parallel()
11601244
z, err := OpenReader("testdata/subdir.zip")

src/archive/zip/testdata/dupdir.zip

458 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)