GoPLS Viewer

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