OLD | NEW |
| (Empty) |
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 http.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 are considered relative to | |
18 // the root specified with NewHttpZipFS (even if the paths start with a '/'). | |
19 | |
20 // TODO(gri) Should define a commonly used FileSystem API that is the same | |
21 // for http and godoc. Then we only need one zip-file based file | |
22 // system implementation. | |
23 | |
24 package main | |
25 | |
26 import ( | |
27 "archive/zip" | |
28 "fmt" | |
29 "io" | |
30 "net/http" | |
31 "os" | |
32 "path" | |
33 "sort" | |
34 "strings" | |
35 "time" | |
36 ) | |
37 | |
38 type fileInfo struct { | |
39 name string | |
40 mode os.FileMode | |
41 size int64 | |
42 mtime time.Time | |
43 } | |
44 | |
45 func (fi *fileInfo) Name() string { return fi.name } | |
46 func (fi *fileInfo) Mode() os.FileMode { return fi.mode } | |
47 func (fi *fileInfo) Size() int64 { return fi.size } | |
48 func (fi *fileInfo) ModTime() time.Time { return fi.mtime } | |
49 func (fi *fileInfo) IsDir() bool { return fi.mode.IsDir() } | |
50 func (fi *fileInfo) Sys() interface{} { return nil } | |
51 | |
52 // httpZipFile is the zip-file based implementation of http.File | |
53 type httpZipFile struct { | |
54 path string // absolute path within zip FS without leading '/' | |
55 info os.FileInfo | |
56 io.ReadCloser // nil for directory | |
57 list zipList | |
58 } | |
59 | |
60 func (f *httpZipFile) Close() error { | |
61 if !f.info.IsDir() { | |
62 return f.ReadCloser.Close() | |
63 } | |
64 f.list = nil | |
65 return nil | |
66 } | |
67 | |
68 func (f *httpZipFile) Stat() (os.FileInfo, error) { | |
69 return f.info, nil | |
70 } | |
71 | |
72 func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, error) { | |
73 var list []os.FileInfo | |
74 dirname := f.path + "/" | |
75 prevname := "" | |
76 for i, e := range f.list { | |
77 if count == 0 { | |
78 f.list = f.list[i:] | |
79 break | |
80 } | |
81 if !strings.HasPrefix(e.Name, dirname) { | |
82 f.list = nil | |
83 break // not in the same directory anymore | |
84 } | |
85 name := e.Name[len(dirname):] // local name | |
86 var mode os.FileMode | |
87 var size int64 | |
88 var mtime time.Time | |
89 if i := strings.IndexRune(name, '/'); i >= 0 { | |
90 // We infer directories from files in subdirectories. | |
91 // If we have x/y, return a directory entry for x. | |
92 name = name[0:i] // keep local directory name only | |
93 mode = os.ModeDir | |
94 // no size or mtime for directories | |
95 } else { | |
96 mode = 0 | |
97 size = int64(e.UncompressedSize) | |
98 mtime = e.ModTime() | |
99 } | |
100 // If we have x/y and x/z, don't return two directory entries fo
r x. | |
101 // TODO(gri): It should be possible to do this more efficiently | |
102 // by determining the (fs.list) range of local directory entries | |
103 // (via two binary searches). | |
104 if name != prevname { | |
105 list = append(list, &fileInfo{ | |
106 name, | |
107 mode, | |
108 size, | |
109 mtime, | |
110 }) | |
111 prevname = name | |
112 count-- | |
113 } | |
114 } | |
115 | |
116 if count >= 0 && len(list) == 0 { | |
117 return nil, io.EOF | |
118 } | |
119 | |
120 return list, nil | |
121 } | |
122 | |
123 func (f *httpZipFile) Seek(offset int64, whence int) (int64, error) { | |
124 return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.in
fo.Name()) | |
125 } | |
126 | |
127 // httpZipFS is the zip-file based implementation of http.FileSystem | |
128 type httpZipFS struct { | |
129 *zip.ReadCloser | |
130 list zipList | |
131 root string | |
132 } | |
133 | |
134 func (fs *httpZipFS) Open(name string) (http.File, error) { | |
135 // fs.root does not start with '/'. | |
136 path := path.Join(fs.root, name) // path is clean | |
137 index, exact := fs.list.lookup(path) | |
138 if index < 0 || !strings.HasPrefix(path, fs.root) { | |
139 // file not found or not under root | |
140 return nil, fmt.Errorf("file not found: %s", name) | |
141 } | |
142 | |
143 if exact { | |
144 // exact match found - must be a file | |
145 f := fs.list[index] | |
146 rc, err := f.Open() | |
147 if err != nil { | |
148 return nil, err | |
149 } | |
150 return &httpZipFile{ | |
151 path, | |
152 &fileInfo{ | |
153 name, | |
154 0, | |
155 int64(f.UncompressedSize), | |
156 f.ModTime(), | |
157 }, | |
158 rc, | |
159 nil, | |
160 }, nil | |
161 } | |
162 | |
163 // not an exact match - must be a directory | |
164 return &httpZipFile{ | |
165 path, | |
166 &fileInfo{ | |
167 name, | |
168 os.ModeDir, | |
169 0, // no size for directory | |
170 time.Time{}, // no mtime for directory | |
171 }, | |
172 nil, | |
173 fs.list[index:], | |
174 }, nil | |
175 } | |
176 | |
177 func (fs *httpZipFS) Close() error { | |
178 fs.list = nil | |
179 return fs.ReadCloser.Close() | |
180 } | |
181 | |
182 // NewHttpZipFS creates a new http.FileSystem based on the contents of | |
183 // the zip file rc restricted to the directory tree specified by root; | |
184 // root must be an absolute path. | |
185 func NewHttpZipFS(rc *zip.ReadCloser, root string) http.FileSystem { | |
186 list := make(zipList, len(rc.File)) | |
187 copy(list, rc.File) // sort a copy of rc.File | |
188 sort.Sort(list) | |
189 return &httpZipFS{rc, list, zipPath(root)} | |
190 } | |
OLD | NEW |