diff --git a/zipfs/file.go b/zipfs/file.go index 355f5f45..ccc1a7e5 100644 --- a/zipfs/file.go +++ b/zipfs/file.go @@ -13,6 +13,7 @@ import ( type File struct { fs *Fs zipfile *zip.File + pseudodir *pseudoDir reader io.ReadCloser offset int64 isdir, closed bool @@ -106,6 +107,9 @@ func (f *File) WriteAt(p []byte, off int64) (n int, err error) { return 0, sysca func (f *File) Name() string { if f.zipfile == nil { + if f.pseudodir != nil { + return f.pseudodir.path + } return string(filepath.Separator) } return filepath.Join(splitpath(f.zipfile.Name)) @@ -128,8 +132,12 @@ func (f *File) Readdir(count int) (fi []os.FileInfo, err error) { if err != nil { return nil, err } - for _, zipfile := range zipfiles { - fi = append(fi, zipfile.FileInfo()) + for d, zipfile := range zipfiles { + if zipfile == nil { + fi = append(fi, pseudoDir{d}) + } else { + fi = append(fi, zipfile.FileInfo()) + } if count > 0 && len(fi) >= count { break } @@ -153,6 +161,9 @@ func (f *File) Readdirnames(count int) (names []string, err error) { func (f *File) Stat() (os.FileInfo, error) { if f.zipfile == nil { + if f.pseudodir != nil { + return f.pseudodir, nil + } return &pseudoRoot{}, nil } return f.zipfile.FileInfo(), nil diff --git a/zipfs/fs.go b/zipfs/fs.go index d08b7f5d..953044e6 100644 --- a/zipfs/fs.go +++ b/zipfs/fs.go @@ -42,6 +42,21 @@ func New(r *zip.Reader) afero.Fs { fs.files[dirname] = make(map[string]*zip.File) } } + + dv := d + for { + d, f = splitpath(dv) + if _, ok := fs.files[d]; !ok { + fs.files[d] = make(map[string]*zip.File) + } + if f == "" { + break + } + if _, ok := fs.files[d][f]; !ok { + fs.files[d][f] = nil + } + dv = d + } } return fs } @@ -64,6 +79,13 @@ func (fs *Fs) Open(name string) (afero.File, error) { if !ok { return nil, &os.PathError{Op: "stat", Path: name, Err: syscall.ENOENT} } + if file == nil { + return &File{ + fs: fs, + pseudodir: &pseudoDir{path: filepath.Join(d, f)}, + isdir: true, + }, nil + } return &File{fs: fs, zipfile: file, isdir: file.FileInfo().IsDir()}, nil } @@ -89,6 +111,17 @@ func (p *pseudoRoot) ModTime() time.Time { return time.Now() } func (p *pseudoRoot) IsDir() bool { return true } func (p *pseudoRoot) Sys() interface{} { return nil } +type pseudoDir struct { + path string +} + +func (fi pseudoDir) Name() string { return filepath.Base(fi.path) } +func (fi pseudoDir) Size() int64 { return 0 } +func (fi pseudoDir) IsDir() bool { return true } +func (fi pseudoDir) ModTime() time.Time { return time.Now() } +func (fi pseudoDir) Mode() os.FileMode { return os.ModeDir | os.ModePerm } +func (fi pseudoDir) Sys() interface{} { return nil } + func (fs *Fs) Stat(name string) (os.FileInfo, error) { d, f := splitpath(name) if f == "" { @@ -101,6 +134,9 @@ func (fs *Fs) Stat(name string) (os.FileInfo, error) { if !ok { return nil, &os.PathError{Op: "stat", Path: name, Err: syscall.ENOENT} } + if file == nil { + return pseudoDir{name}, nil + } return file.FileInfo(), nil } diff --git a/zipfs/testdata/no_dir_entry.zip b/zipfs/testdata/no_dir_entry.zip new file mode 100644 index 00000000..04345bec Binary files /dev/null and b/zipfs/testdata/no_dir_entry.zip differ diff --git a/zipfs/zipfs_test.go b/zipfs/zipfs_test.go index bf781cc0..dfea1524 100644 --- a/zipfs/zipfs_test.go +++ b/zipfs/zipfs_test.go @@ -1,12 +1,13 @@ package zipfs import ( - "github.com/spf13/afero" - "archive/zip" + "os" "path/filepath" "reflect" "testing" + + "github.com/spf13/afero" ) func TestZipFS(t *testing.T) { @@ -101,3 +102,62 @@ func TestZipFS(t *testing.T) { } } } + +func TestZipFsNoDirEntry(t *testing.T) { + zrc, err := zip.OpenReader("testdata/no_dir_entry.zip") + if err != nil { + t.Fatal(err) + } + zfs := New(&zrc.Reader) + + // Test Walk + expected := map[string]bool{ + "": true, + "sub": true, + "sub/testDir2": true, + "sub/testDir2/testFile": false, + "testDir1": true, + "testDir1/testFile": false, + } + err = afero.Walk(zfs, "", func(path string, info os.FileInfo, err error) error { + path = filepath.ToSlash(path) + if isDir, ok := expected[path]; ok { + if isDir != info.IsDir() { + t.Error("file", path, "isDir:", info.IsDir(), "but expected:", isDir) + } + delete(expected, path) + } else { + t.Error("Unexpected file", path, "isDir:", info.IsDir()) + } + return nil + }) + if err != nil { + t.Error(err) + } + if len(expected) > 0 { + t.Errorf("Files %v is not found in zip", expected) + } + + // Test ReadDir + dir, err := afero.ReadDir(zfs, "/") + if err != nil { + t.Fatal(err) + } + expected = map[string]bool{ + "sub": true, + "testDir1": true, + } + for _, d := range dir { + if isDir, ok := expected[d.Name()]; ok { + if isDir != d.IsDir() { + t.Error("file", d.Name(), "isDir:", d.IsDir(), "but expected:", isDir) + } + delete(expected, d.Name()) + } else { + t.Error("Unexpected file", d.Name(), "isDir:", d.IsDir()) + } + } + if len(expected) > 0 { + t.Errorf("Files %v is not found in zip", expected) + } +}