github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/godoc/zip.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 // This 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 19 package main 20 21 import ( 22 "archive/zip" 23 "fmt" 24 "io" 25 "os" 26 "path" 27 "sort" 28 "strings" 29 "time" 30 ) 31 32 // zipFI is the zip-file based implementation of FileInfo 33 type zipFI struct { 34 name string // directory-local name 35 file *zip.File // nil for a directory 36 } 37 38 func (fi zipFI) Name() string { 39 return fi.name 40 } 41 42 func (fi zipFI) Size() int64 { 43 if f := fi.file; f != nil { 44 return int64(f.UncompressedSize) 45 } 46 return 0 // directory 47 } 48 49 func (fi zipFI) ModTime() time.Time { 50 if f := fi.file; f != nil { 51 return f.ModTime() 52 } 53 return time.Time{} // directory has no modified time entry 54 } 55 56 func (fi zipFI) Mode() os.FileMode { 57 if fi.file == nil { 58 // Unix directories typically are executable, hence 555. 59 return os.ModeDir | 0555 60 } 61 return 0444 62 } 63 64 func (fi zipFI) IsDir() bool { 65 return fi.file == nil 66 } 67 68 func (fi zipFI) Sys() interface{} { 69 return nil 70 } 71 72 // zipFS is the zip-file based implementation of FileSystem 73 type zipFS struct { 74 *zip.ReadCloser 75 list zipList 76 name string 77 } 78 79 func (fs *zipFS) String() string { 80 return "zip(" + fs.name + ")" 81 } 82 83 func (fs *zipFS) Close() error { 84 fs.list = nil 85 return fs.ReadCloser.Close() 86 } 87 88 func zipPath(name string) string { 89 name = path.Clean(name) 90 if !path.IsAbs(name) { 91 panic(fmt.Sprintf("stat: not an absolute path: %s", name)) 92 } 93 return name[1:] // strip leading '/' 94 } 95 96 func (fs *zipFS) stat(abspath string) (int, zipFI, error) { 97 i, exact := fs.list.lookup(abspath) 98 if i < 0 { 99 // abspath has leading '/' stripped - print it explicitly 100 return -1, zipFI{}, fmt.Errorf("file not found: /%s", abspath) 101 } 102 _, name := path.Split(abspath) 103 var file *zip.File 104 if exact { 105 file = fs.list[i] // exact match found - must be a file 106 } 107 return i, zipFI{name, file}, nil 108 } 109 110 func (fs *zipFS) Open(abspath string) (readSeekCloser, error) { 111 _, fi, err := fs.stat(zipPath(abspath)) 112 if err != nil { 113 return nil, err 114 } 115 if fi.IsDir() { 116 return nil, fmt.Errorf("Open: %s is a directory", abspath) 117 } 118 r, err := fi.file.Open() 119 if err != nil { 120 return nil, err 121 } 122 return &zipSeek{fi.file, r}, nil 123 } 124 125 type zipSeek struct { 126 file *zip.File 127 io.ReadCloser 128 } 129 130 func (f *zipSeek) Seek(offset int64, whence int) (int64, error) { 131 if whence == 0 && offset == 0 { 132 r, err := f.file.Open() 133 if err != nil { 134 return 0, err 135 } 136 f.Close() 137 f.ReadCloser = r 138 return 0, nil 139 } 140 return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name) 141 } 142 143 func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) { 144 _, fi, err := fs.stat(zipPath(abspath)) 145 return fi, err 146 } 147 148 func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) { 149 _, fi, err := fs.stat(zipPath(abspath)) 150 return fi, err 151 } 152 153 func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) { 154 path := zipPath(abspath) 155 i, fi, err := fs.stat(path) 156 if err != nil { 157 return nil, err 158 } 159 if !fi.IsDir() { 160 return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath) 161 } 162 163 var list []os.FileInfo 164 dirname := path + "/" 165 prevname := "" 166 for _, e := range fs.list[i:] { 167 if !strings.HasPrefix(e.Name, dirname) { 168 break // not in the same directory anymore 169 } 170 name := e.Name[len(dirname):] // local name 171 file := e 172 if i := strings.IndexRune(name, '/'); i >= 0 { 173 // We infer directories from files in subdirectories. 174 // If we have x/y, return a directory entry for x. 175 name = name[0:i] // keep local directory name only 176 file = nil 177 } 178 // If we have x/y and x/z, don't return two directory entries for x. 179 // TODO(gri): It should be possible to do this more efficiently 180 // by determining the (fs.list) range of local directory entries 181 // (via two binary searches). 182 if name != prevname { 183 list = append(list, zipFI{name, file}) 184 prevname = name 185 } 186 } 187 188 return list, nil 189 } 190 191 func NewZipFS(rc *zip.ReadCloser, name string) FileSystem { 192 list := make(zipList, len(rc.File)) 193 copy(list, rc.File) // sort a copy of rc.File 194 sort.Sort(list) 195 return &zipFS{rc, list, name} 196 } 197 198 type zipList []*zip.File 199 200 // zipList implements sort.Interface 201 func (z zipList) Len() int { return len(z) } 202 func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name } 203 func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] } 204 205 // lookup returns the smallest index of an entry with an exact match 206 // for name, or an inexact match starting with name/. If there is no 207 // such entry, the result is -1, false. 208 func (z zipList) lookup(name string) (index int, exact bool) { 209 // look for exact match first (name comes before name/ in z) 210 i := sort.Search(len(z), func(i int) bool { 211 return name <= z[i].Name 212 }) 213 if i >= len(z) { 214 return -1, false 215 } 216 // 0 <= i < len(z) 217 if z[i].Name == name { 218 return i, true 219 } 220 221 // look for inexact match (must be in z[i:], if present) 222 z = z[i:] 223 name += "/" 224 j := sort.Search(len(z), func(i int) bool { 225 return name <= z[i].Name 226 }) 227 if j >= len(z) { 228 return -1, false 229 } 230 // 0 <= j < len(z) 231 if strings.HasPrefix(z[j].Name, name) { 232 return i + j, false 233 } 234 235 return -1, false 236 }