aboutsummaryrefslogtreecommitdiff
path: root/src/archive/zip/reader.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/archive/zip/reader.go')
-rw-r--r--src/archive/zip/reader.go189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/archive/zip/reader.go b/src/archive/zip/reader.go
index 13ff9ddcf4..5c9f3dea28 100644
--- a/src/archive/zip/reader.go
+++ b/src/archive/zip/reader.go
@@ -11,7 +11,12 @@ import (
"hash"
"hash/crc32"
"io"
+ "io/fs"
"os"
+ "path"
+ "sort"
+ "strings"
+ "sync"
"time"
)
@@ -21,18 +26,28 @@ var (
ErrChecksum = errors.New("zip: checksum error")
)
+// A Reader serves content from a ZIP archive.
type Reader struct {
r io.ReaderAt
File []*File
Comment string
decompressors map[uint16]Decompressor
+
+ // fileList is a list of files sorted by ename,
+ // for use by the Open method.
+ fileListOnce sync.Once
+ fileList []fileListEntry
}
+// A ReadCloser is a Reader that must be closed when no longer needed.
type ReadCloser struct {
f *os.File
Reader
}
+// A File is a single file in a ZIP archive.
+// The file information is in the embedded FileHeader.
+// The file content can be accessed by calling Open.
type File struct {
FileHeader
zip *Reader
@@ -187,6 +202,10 @@ type checksumReader struct {
err error // sticky error
}
+func (r *checksumReader) Stat() (fs.FileInfo, error) {
+ return headerFileInfo{&r.f.FileHeader}, nil
+}
+
func (r *checksumReader) Read(b []byte) (n int, err error) {
if r.err != nil {
return 0, r.err
@@ -607,3 +626,173 @@ func (b *readBuf) sub(n int) readBuf {
*b = (*b)[n:]
return b2
}
+
+// A fileListEntry is a File and its ename.
+// If file == nil, the fileListEntry describes a directory, without metadata.
+type fileListEntry struct {
+ name string
+ file *File // nil for directories
+}
+
+type fileInfoDirEntry interface {
+ fs.FileInfo
+ fs.DirEntry
+}
+
+func (e *fileListEntry) stat() fileInfoDirEntry {
+ if e.file != nil {
+ return headerFileInfo{&e.file.FileHeader}
+ }
+ return e
+}
+
+// Only used for directories.
+func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem }
+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) Type() fs.FileMode { return fs.ModeDir }
+func (f *fileListEntry) IsDir() bool { return true }
+func (f *fileListEntry) Sys() interface{} { return nil }
+
+func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil }
+
+// toValidName coerces name to be a valid name for fs.FS.Open.
+func toValidName(name string) string {
+ name = strings.ReplaceAll(name, `\`, `/`)
+ p := path.Clean(name)
+ if strings.HasPrefix(p, "/") {
+ p = p[len("/"):]
+ }
+ for strings.HasPrefix(name, "../") {
+ p = p[len("../"):]
+ }
+ return p
+}
+
+func (r *Reader) initFileList() {
+ r.fileListOnce.Do(func() {
+ dirs := make(map[string]bool)
+ for _, file := range r.File {
+ name := toValidName(file.Name)
+ for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
+ dirs[dir] = true
+ }
+ r.fileList = append(r.fileList, fileListEntry{name, file})
+ }
+ for dir := range dirs {
+ r.fileList = append(r.fileList, fileListEntry{dir + "/", nil})
+ }
+
+ sort.Slice(r.fileList, func(i, j int) bool { return fileEntryLess(r.fileList[i].name, r.fileList[j].name) })
+ })
+}
+
+func fileEntryLess(x, y string) bool {
+ xdir, xelem, _ := split(x)
+ ydir, yelem, _ := split(y)
+ return xdir < ydir || xdir == ydir && xelem < yelem
+}
+
+// Open opens the named file in the ZIP archive,
+// using the semantics of io.FS.Open:
+// paths are always slash separated, with no
+// leading / or ../ elements.
+func (r *Reader) Open(name string) (fs.File, error) {
+ r.initFileList()
+
+ e := r.openLookup(name)
+ if e == nil || !fs.ValidPath(name) {
+ return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
+ }
+ if e.file == nil || strings.HasSuffix(e.file.Name, "/") {
+ return &openDir{e, r.openReadDir(name), 0}, nil
+ }
+ rc, err := e.file.Open()
+ if err != nil {
+ return nil, err
+ }
+ return rc.(fs.File), nil
+}
+
+func split(name string) (dir, elem string, isDir bool) {
+ if name[len(name)-1] == '/' {
+ isDir = true
+ name = name[:len(name)-1]
+ }
+ i := len(name) - 1
+ for i >= 0 && name[i] != '/' {
+ i--
+ }
+ if i < 0 {
+ return ".", name, isDir
+ }
+ return name[:i], name[i+1:], isDir
+}
+
+var dotFile = &fileListEntry{name: "./"}
+
+func (r *Reader) openLookup(name string) *fileListEntry {
+ if name == "." {
+ return dotFile
+ }
+
+ dir, elem, _ := split(name)
+ files := r.fileList
+ i := sort.Search(len(files), func(i int) bool {
+ idir, ielem, _ := split(files[i].name)
+ return idir > dir || idir == dir && ielem >= elem
+ })
+ if i < len(files) {
+ fname := files[i].name
+ if fname == name || len(fname) == len(name)+1 && fname[len(name)] == '/' && fname[:len(name)] == name {
+ return &files[i]
+ }
+ }
+ return nil
+}
+
+func (r *Reader) openReadDir(dir string) []fileListEntry {
+ files := r.fileList
+ i := sort.Search(len(files), func(i int) bool {
+ idir, _, _ := split(files[i].name)
+ return idir >= dir
+ })
+ j := sort.Search(len(files), func(j int) bool {
+ jdir, _, _ := split(files[j].name)
+ return jdir > dir
+ })
+ return files[i:j]
+}
+
+type openDir struct {
+ e *fileListEntry
+ files []fileListEntry
+ offset int
+}
+
+func (d *openDir) Close() error { return nil }
+func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat(), nil }
+
+func (d *openDir) Read([]byte) (int, error) {
+ return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errors.New("is a directory")}
+}
+
+func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
+ n := len(d.files) - d.offset
+ if count > 0 && n > count {
+ n = count
+ }
+ if n == 0 {
+ if count <= 0 {
+ return nil, nil
+ }
+ return nil, io.EOF
+ }
+ list := make([]fs.DirEntry, n)
+ for i := range list {
+ list[i] = d.files[d.offset+i].stat()
+ }
+ d.offset += n
+ return list, nil
+}