1
0
mirror of https://github.com/golang/go synced 2024-11-26 09:38:10 -07:00

archive/zip: only return directory once via io/fs.FS

While we're here fix the ModTime value for directories.

Fixes #43872
Fixes #45345

Change-Id: I155e6517713ef6a9482b9431f1167a44337c6ad2
Reviewed-on: https://go-review.googlesource.com/c/go/+/311530
Trust: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jeremy Faller <jeremy@golang.org>
This commit is contained in:
Ian Lance Taylor 2021-04-19 13:51:53 -07:00
parent 6d95e5a4ff
commit 87e4dcd446
3 changed files with 92 additions and 18 deletions

View File

@ -628,10 +628,11 @@ func (b *readBuf) sub(n int) readBuf {
} }
// A fileListEntry is a File and its ename. // A fileListEntry is a File and its ename.
// If file == nil, the fileListEntry describes a directory, without metadata. // If file == nil, the fileListEntry describes a directory without metadata.
type fileListEntry struct { type fileListEntry struct {
name string name string
file *File // nil for directories file *File
isDir bool
} }
type fileInfoDirEntry interface { type fileInfoDirEntry interface {
@ -640,7 +641,7 @@ type fileInfoDirEntry interface {
} }
func (e *fileListEntry) stat() fileInfoDirEntry { func (e *fileListEntry) stat() fileInfoDirEntry {
if e.file != nil { if !e.isDir {
return headerFileInfo{&e.file.FileHeader} return headerFileInfo{&e.file.FileHeader}
} }
return e return e
@ -649,12 +650,18 @@ func (e *fileListEntry) stat() fileInfoDirEntry {
// Only used for directories. // Only used for directories.
func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem } func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem }
func (f *fileListEntry) Size() int64 { return 0 } func (f *fileListEntry) Size() int64 { return 0 }
func (f *fileListEntry) ModTime() time.Time { return time.Time{} }
func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 } func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 }
func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir } func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir }
func (f *fileListEntry) IsDir() bool { return true } func (f *fileListEntry) IsDir() bool { return true }
func (f *fileListEntry) Sys() interface{} { return nil } func (f *fileListEntry) Sys() interface{} { return nil }
func (f *fileListEntry) ModTime() time.Time {
if f.file == nil {
return time.Time{}
}
return f.file.FileHeader.Modified.UTC()
}
func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil } func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil }
// toValidName coerces name to be a valid name for fs.FS.Open. // toValidName coerces name to be a valid name for fs.FS.Open.
@ -673,15 +680,32 @@ func toValidName(name string) string {
func (r *Reader) initFileList() { func (r *Reader) initFileList() {
r.fileListOnce.Do(func() { r.fileListOnce.Do(func() {
dirs := make(map[string]bool) dirs := make(map[string]bool)
knownDirs := make(map[string]bool)
for _, file := range r.File { for _, file := range r.File {
isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/'
name := toValidName(file.Name) name := toValidName(file.Name)
for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) { for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
dirs[dir] = true dirs[dir] = true
} }
r.fileList = append(r.fileList, fileListEntry{name, file}) entry := fileListEntry{
name: name,
file: file,
isDir: isDir,
}
r.fileList = append(r.fileList, entry)
if isDir {
knownDirs[name] = true
}
} }
for dir := range dirs { for dir := range dirs {
r.fileList = append(r.fileList, fileListEntry{dir + "/", nil}) if !knownDirs[dir] {
entry := fileListEntry{
name: dir,
file: nil,
isDir: true,
}
r.fileList = append(r.fileList, entry)
}
} }
sort.Slice(r.fileList, func(i, j int) bool { return fileEntryLess(r.fileList[i].name, r.fileList[j].name) }) sort.Slice(r.fileList, func(i, j int) bool { return fileEntryLess(r.fileList[i].name, r.fileList[j].name) })
@ -705,7 +729,7 @@ func (r *Reader) Open(name string) (fs.File, error) {
if e == nil || !fs.ValidPath(name) { if e == nil || !fs.ValidPath(name) {
return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
} }
if e.file == nil || strings.HasSuffix(e.file.Name, "/") { if e.isDir {
return &openDir{e, r.openReadDir(name), 0}, nil return &openDir{e, r.openReadDir(name), 0}, nil
} }
rc, err := e.file.Open() rc, err := e.file.Open()
@ -730,7 +754,7 @@ func split(name string) (dir, elem string, isDir bool) {
return name[:i], name[i+1:], isDir return name[:i], name[i+1:], isDir
} }
var dotFile = &fileListEntry{name: "./"} var dotFile = &fileListEntry{name: "./", isDir: true}
func (r *Reader) openLookup(name string) *fileListEntry { func (r *Reader) openLookup(name string) *fileListEntry {
if name == "." { if name == "." {

View File

@ -1073,13 +1073,63 @@ func TestIssue12449(t *testing.T) {
} }
func TestFS(t *testing.T) { func TestFS(t *testing.T) {
z, err := OpenReader("testdata/unix.zip") for _, test := range []struct {
file string
want []string
}{
{
"testdata/unix.zip",
[]string{"hello", "dir/bar", "readonly"},
},
{
"testdata/subdir.zip",
[]string{"a/b/c"},
},
} {
t.Run(test.file, func(t *testing.T) {
t.Parallel()
z, err := OpenReader(test.file)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := fstest.TestFS(z, "hello", "dir/bar", "dir/empty", "readonly"); err != nil { defer z.Close()
if err := fstest.TestFS(z, test.want...); err != nil {
t.Error(err)
}
})
}
}
func TestFSModTime(t *testing.T) {
t.Parallel()
z, err := OpenReader("testdata/subdir.zip")
if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer z.Close()
for _, test := range []struct {
name string
want time.Time
}{
{
"a",
time.Date(2021, 4, 19, 12, 29, 56, 0, timeZone(-7*time.Hour)).UTC(),
},
{
"a/b/c",
time.Date(2021, 4, 19, 12, 29, 59, 0, timeZone(-7*time.Hour)).UTC(),
},
} {
fi, err := fs.Stat(z, test.name)
if err != nil {
t.Errorf("%s: %v", test.name, err)
continue
}
if got := fi.ModTime(); !got.Equal(test.want) {
t.Errorf("%s: got modtime %v, want %v", test.name, got, test.want)
}
}
} }
func TestCVE202127919(t *testing.T) { func TestCVE202127919(t *testing.T) {

BIN
src/archive/zip/testdata/subdir.zip vendored Normal file

Binary file not shown.