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