Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZipFs: Fix #317 - Support zip files with no directory entries #358

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions zipfs/file.go
Expand Up @@ -13,6 +13,7 @@ import (
type File struct {
fs *Fs
zipfile *zip.File
pseudodir *pseudoDir
reader io.ReadCloser
offset int64
isdir, closed bool
Expand Down Expand Up @@ -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))
Expand All @@ -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
}
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions zipfs/fs.go
Expand Up @@ -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
}
Expand All @@ -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
}

Expand All @@ -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 == "" {
Expand All @@ -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
}

Expand Down
Binary file added zipfs/testdata/no_dir_entry.zip
Binary file not shown.
64 changes: 62 additions & 2 deletions 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) {
Expand Down Expand Up @@ -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)
}
}