github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/vfs/zipfs/zipfs.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package zipfs file provides an implementation of the FileSystem 6 // interface based on the contents of a .zip file. 7 // 8 // Assumptions: 9 // 10 // - The file paths stored in the zip file must use a slash ('/') as path 11 // separator; and they must be relative (i.e., they must not start with 12 // a '/' - this is usually the case if the file was created w/o special 13 // options). 14 // - The zip file system treats the file paths found in the zip internally 15 // like absolute paths w/o a leading '/'; i.e., the paths are considered 16 // relative to the root of the file system. 17 // - All path arguments to file system methods must be absolute paths. 18 package zipfs // import "golang.org/x/tools/godoc/vfs/zipfs" 19 20 import ( 21 "archive/zip" 22 "fmt" 23 "io" 24 "os" 25 "path" 26 "sort" 27 "strings" 28 "time" 29 30 "golang.org/x/tools/godoc/vfs" 31 ) 32 33 // zipFI is the zip-file based implementation of FileInfo 34 type zipFI struct { 35 name string // directory-local name 36 file *zip.File // nil for a directory 37 } 38 39 func (fi zipFI) Name() string { 40 return fi.name 41 } 42 43 func (fi zipFI) Size() int64 { 44 if f := fi.file; f != nil { 45 return int64(f.UncompressedSize) 46 } 47 return 0 // directory 48 } 49 50 func (fi zipFI) ModTime() time.Time { 51 if f := fi.file; f != nil { 52 return f.ModTime() 53 } 54 return time.Time{} // directory has no modified time entry 55 } 56 57 func (fi zipFI) Mode() os.FileMode { 58 if fi.file == nil { 59 // Unix directories typically are executable, hence 555. 60 return os.ModeDir | 0555 61 } 62 return 0444 63 } 64 65 func (fi zipFI) IsDir() bool { 66 return fi.file == nil 67 } 68 69 func (fi zipFI) Sys() interface{} { 70 return nil 71 } 72 73 // zipFS is the zip-file based implementation of FileSystem 74 type zipFS struct { 75 *zip.ReadCloser 76 list zipList 77 name string 78 } 79 80 func (fs *zipFS) String() string { 81 return "zip(" + fs.name + ")" 82 } 83 84 func (fs *zipFS) Close() error { 85 fs.list = nil 86 return fs.ReadCloser.Close() 87 } 88 89 func zipPath(name string) (string, error) { 90 name = path.Clean(name) 91 if !path.IsAbs(name) { 92 return "", fmt.Errorf("stat: not an absolute path: %s", name) 93 } 94 return name[1:], nil // strip leading '/' 95 } 96 97 func isRoot(abspath string) bool { 98 return path.Clean(abspath) == "/" 99 } 100 101 func (fs *zipFS) stat(abspath string) (int, zipFI, error) { 102 if isRoot(abspath) { 103 return 0, zipFI{ 104 name: "", 105 file: nil, 106 }, nil 107 } 108 zippath, err := zipPath(abspath) 109 if err != nil { 110 return 0, zipFI{}, err 111 } 112 i, exact := fs.list.lookup(zippath) 113 if i < 0 { 114 // zippath has leading '/' stripped - print it explicitly 115 return -1, zipFI{}, fmt.Errorf("file not found: /%s", zippath) 116 } 117 _, name := path.Split(zippath) 118 var file *zip.File 119 if exact { 120 file = fs.list[i] // exact match found - must be a file 121 } 122 return i, zipFI{name, file}, nil 123 } 124 125 func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) { 126 _, fi, err := fs.stat(abspath) 127 if err != nil { 128 return nil, err 129 } 130 if fi.IsDir() { 131 return nil, fmt.Errorf("Open: %s is a directory", abspath) 132 } 133 r, err := fi.file.Open() 134 if err != nil { 135 return nil, err 136 } 137 return &zipSeek{fi.file, r}, nil 138 } 139 140 type zipSeek struct { 141 file *zip.File 142 io.ReadCloser 143 } 144 145 func (f *zipSeek) Seek(offset int64, whence int) (int64, error) { 146 if whence == 0 && offset == 0 { 147 r, err := f.file.Open() 148 if err != nil { 149 return 0, err 150 } 151 f.Close() 152 f.ReadCloser = r 153 return 0, nil 154 } 155 return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name) 156 } 157 158 func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) { 159 _, fi, err := fs.stat(abspath) 160 return fi, err 161 } 162 163 func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) { 164 _, fi, err := fs.stat(abspath) 165 return fi, err 166 } 167 168 func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) { 169 i, fi, err := fs.stat(abspath) 170 if err != nil { 171 return nil, err 172 } 173 if !fi.IsDir() { 174 return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath) 175 } 176 177 var list []os.FileInfo 178 179 // make dirname the prefix that file names must start with to be considered 180 // in this directory. we must special case the root directory because, per 181 // the spec of this package, zip file entries MUST NOT start with /, so we 182 // should not append /, as we would in every other case. 183 var dirname string 184 if isRoot(abspath) { 185 dirname = "" 186 } else { 187 zippath, err := zipPath(abspath) 188 if err != nil { 189 return nil, err 190 } 191 dirname = zippath + "/" 192 } 193 prevname := "" 194 for _, e := range fs.list[i:] { 195 if !strings.HasPrefix(e.Name, dirname) { 196 break // not in the same directory anymore 197 } 198 name := e.Name[len(dirname):] // local name 199 file := e 200 if i := strings.IndexRune(name, '/'); i >= 0 { 201 // We infer directories from files in subdirectories. 202 // If we have x/y, return a directory entry for x. 203 name = name[0:i] // keep local directory name only 204 file = nil 205 } 206 // If we have x/y and x/z, don't return two directory entries for x. 207 // TODO(gri): It should be possible to do this more efficiently 208 // by determining the (fs.list) range of local directory entries 209 // (via two binary searches). 210 if name != prevname { 211 list = append(list, zipFI{name, file}) 212 prevname = name 213 } 214 } 215 216 return list, nil 217 } 218 219 func New(rc *zip.ReadCloser, name string) vfs.FileSystem { 220 list := make(zipList, len(rc.File)) 221 copy(list, rc.File) // sort a copy of rc.File 222 sort.Sort(list) 223 return &zipFS{rc, list, name} 224 } 225 226 type zipList []*zip.File 227 228 // zipList implements sort.Interface 229 func (z zipList) Len() int { return len(z) } 230 func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name } 231 func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] } 232 233 // lookup returns the smallest index of an entry with an exact match 234 // for name, or an inexact match starting with name/. If there is no 235 // such entry, the result is -1, false. 236 func (z zipList) lookup(name string) (index int, exact bool) { 237 // look for exact match first (name comes before name/ in z) 238 i := sort.Search(len(z), func(i int) bool { 239 return name <= z[i].Name 240 }) 241 if i >= len(z) { 242 return -1, false 243 } 244 // 0 <= i < len(z) 245 if z[i].Name == name { 246 return i, true 247 } 248 249 // look for inexact match (must be in z[i:], if present) 250 z = z[i:] 251 name += "/" 252 j := sort.Search(len(z), func(i int) bool { 253 return name <= z[i].Name 254 }) 255 if j >= len(z) { 256 return -1, false 257 } 258 // 0 <= j < len(z) 259 if strings.HasPrefix(z[j].Name, name) { 260 return i + j, false 261 } 262 263 return -1, false 264 }