1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package zipfs
19
20 import (
21 "archive/zip"
22 "fmt"
23 "go/build"
24 "io"
25 "os"
26 "path"
27 "path/filepath"
28 "sort"
29 "strings"
30 "time"
31
32 "golang.org/x/tools/godoc/vfs"
33 )
34
35
36 type zipFI struct {
37 name string
38 file *zip.File
39 }
40
41 func (fi zipFI) Name() string {
42 return fi.name
43 }
44
45 func (fi zipFI) Size() int64 {
46 if f := fi.file; f != nil {
47 return int64(f.UncompressedSize)
48 }
49 return 0
50 }
51
52 func (fi zipFI) ModTime() time.Time {
53 if f := fi.file; f != nil {
54 return f.ModTime()
55 }
56 return time.Time{}
57 }
58
59 func (fi zipFI) Mode() os.FileMode {
60 if fi.file == nil {
61
62 return os.ModeDir | 0555
63 }
64 return 0444
65 }
66
67 func (fi zipFI) IsDir() bool {
68 return fi.file == nil
69 }
70
71 func (fi zipFI) Sys() interface{} {
72 return nil
73 }
74
75
76 type zipFS struct {
77 *zip.ReadCloser
78 list zipList
79 name string
80 }
81
82 func (fs *zipFS) String() string {
83 return "zip(" + fs.name + ")"
84 }
85
86 func (fs *zipFS) RootType(abspath string) vfs.RootType {
87 var t vfs.RootType
88 switch {
89 case exists(path.Join(vfs.GOROOT, abspath)):
90 t = vfs.RootTypeGoRoot
91 case isGoPath(abspath):
92 t = vfs.RootTypeGoPath
93 }
94 return t
95 }
96
97 func isGoPath(abspath string) bool {
98 for _, p := range filepath.SplitList(build.Default.GOPATH) {
99 if exists(path.Join(p, abspath)) {
100 return true
101 }
102 }
103 return false
104 }
105
106 func exists(path string) bool {
107 _, err := os.Stat(path)
108 return err == nil
109 }
110
111 func (fs *zipFS) Close() error {
112 fs.list = nil
113 return fs.ReadCloser.Close()
114 }
115
116 func zipPath(name string) (string, error) {
117 name = path.Clean(name)
118 if !path.IsAbs(name) {
119 return "", fmt.Errorf("stat: not an absolute path: %s", name)
120 }
121 return name[1:], nil
122 }
123
124 func isRoot(abspath string) bool {
125 return path.Clean(abspath) == "/"
126 }
127
128 func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
129 if isRoot(abspath) {
130 return 0, zipFI{
131 name: "",
132 file: nil,
133 }, nil
134 }
135 zippath, err := zipPath(abspath)
136 if err != nil {
137 return 0, zipFI{}, err
138 }
139 i, exact := fs.list.lookup(zippath)
140 if i < 0 {
141
142 return -1, zipFI{}, &os.PathError{Path: "/" + zippath, Err: os.ErrNotExist}
143 }
144 _, name := path.Split(zippath)
145 var file *zip.File
146 if exact {
147 file = fs.list[i]
148 }
149 return i, zipFI{name, file}, nil
150 }
151
152 func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) {
153 _, fi, err := fs.stat(abspath)
154 if err != nil {
155 return nil, err
156 }
157 if fi.IsDir() {
158 return nil, fmt.Errorf("Open: %s is a directory", abspath)
159 }
160 r, err := fi.file.Open()
161 if err != nil {
162 return nil, err
163 }
164 return &zipSeek{fi.file, r}, nil
165 }
166
167 type zipSeek struct {
168 file *zip.File
169 io.ReadCloser
170 }
171
172 func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
173 if whence == 0 && offset == 0 {
174 r, err := f.file.Open()
175 if err != nil {
176 return 0, err
177 }
178 f.Close()
179 f.ReadCloser = r
180 return 0, nil
181 }
182 return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
183 }
184
185 func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
186 _, fi, err := fs.stat(abspath)
187 return fi, err
188 }
189
190 func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
191 _, fi, err := fs.stat(abspath)
192 return fi, err
193 }
194
195 func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
196 i, fi, err := fs.stat(abspath)
197 if err != nil {
198 return nil, err
199 }
200 if !fi.IsDir() {
201 return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
202 }
203
204 var list []os.FileInfo
205
206
207
208
209
210 var dirname string
211 if isRoot(abspath) {
212 dirname = ""
213 } else {
214 zippath, err := zipPath(abspath)
215 if err != nil {
216 return nil, err
217 }
218 dirname = zippath + "/"
219 }
220 prevname := ""
221 for _, e := range fs.list[i:] {
222 if !strings.HasPrefix(e.Name, dirname) {
223 break
224 }
225 name := e.Name[len(dirname):]
226 file := e
227 if i := strings.IndexRune(name, '/'); i >= 0 {
228
229
230 name = name[0:i]
231 file = nil
232 }
233
234
235
236
237 if name != prevname {
238 list = append(list, zipFI{name, file})
239 prevname = name
240 }
241 }
242
243 return list, nil
244 }
245
246 func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
247 list := make(zipList, len(rc.File))
248 copy(list, rc.File)
249 sort.Sort(list)
250 return &zipFS{rc, list, name}
251 }
252
253 type zipList []*zip.File
254
255
256 func (z zipList) Len() int { return len(z) }
257 func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
258 func (z zipList) Swap(i, j int) { z[i], z[j] = z[j], z[i] }
259
260
261
262
263 func (z zipList) lookup(name string) (index int, exact bool) {
264
265 i := sort.Search(len(z), func(i int) bool {
266 return name <= z[i].Name
267 })
268 if i >= len(z) {
269 return -1, false
270 }
271
272 if z[i].Name == name {
273 return i, true
274 }
275
276
277 z = z[i:]
278 name += "/"
279 j := sort.Search(len(z), func(i int) bool {
280 return name <= z[i].Name
281 })
282 if j >= len(z) {
283 return -1, false
284 }
285
286 if strings.HasPrefix(z[j].Name, name) {
287 return i + j, false
288 }
289
290 return -1, false
291 }
292
View as plain text