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:
parent
6d95e5a4ff
commit
87e4dcd446
@ -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 == "." {
|
||||||
|
@ -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
BIN
src/archive/zip/testdata/subdir.zip
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user