1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
|
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file provides an implementation of the http.FileSystem
// interface based on the contents of a .zip file.
//
// Assumptions:
//
// - The file paths stored in the zip file must use a slash ('/') as path
// separator; and they must be relative (i.e., they must not start with
// a '/' - this is usually the case if the file was created w/o special
// options).
// - The zip file system treats the file paths found in the zip internally
// like absolute paths w/o a leading '/'; i.e., the paths are considered
// relative to the root of the file system.
// - All path arguments to file system methods are considered relative to
// the root specified with NewHttpZipFS (even if the paths start with a '/').
// TODO(gri) Should define a commonly used FileSystem API that is the same
// for http and godoc. Then we only need one zip-file based file
// system implementation.
package main
import (
"archive/zip"
"fmt"
"http"
"io"
"os"
"path"
"sort"
"strings"
)
// We cannot import syscall on app engine.
// TODO(gri) Once we have a truly abstract FileInfo implementation
// this won't be needed anymore.
const (
S_IFDIR = 0x4000 // == syscall.S_IFDIR
S_IFREG = 0x8000 // == syscall.S_IFREG
)
// httpZipFile is the zip-file based implementation of http.File
type httpZipFile struct {
path string // absolute path within zip FS without leading '/'
info os.FileInfo
io.ReadCloser // nil for directory
list zipList
}
func (f *httpZipFile) Close() os.Error {
if f.info.IsRegular() {
return f.ReadCloser.Close()
}
f.list = nil
return nil
}
func (f *httpZipFile) Stat() (*os.FileInfo, os.Error) {
return &f.info, nil
}
func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) {
var list []os.FileInfo
dirname := f.path + "/"
prevname := ""
for i, e := range f.list {
if count == 0 {
f.list = f.list[i:]
break
}
if !strings.HasPrefix(e.Name, dirname) {
f.list = nil
break // not in the same directory anymore
}
name := e.Name[len(dirname):] // local name
var mode uint32
var size, mtime_ns int64
if i := strings.IndexRune(name, '/'); i >= 0 {
// We infer directories from files in subdirectories.
// If we have x/y, return a directory entry for x.
name = name[0:i] // keep local directory name only
mode = S_IFDIR
// no size or mtime_ns for directories
} else {
mode = S_IFREG
size = int64(e.UncompressedSize)
mtime_ns = e.Mtime_ns()
}
// If we have x/y and x/z, don't return two directory entries for x.
// TODO(gri): It should be possible to do this more efficiently
// by determining the (fs.list) range of local directory entries
// (via two binary searches).
if name != prevname {
list = append(list, os.FileInfo{
Name: name,
Mode: mode,
Size: size,
Mtime_ns: mtime_ns,
})
prevname = name
count--
}
}
if count >= 0 && len(list) == 0 {
return nil, os.EOF
}
return list, nil
}
func (f *httpZipFile) Seek(offset int64, whence int) (int64, os.Error) {
return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.info.Name)
}
// httpZipFS is the zip-file based implementation of http.FileSystem
type httpZipFS struct {
*zip.ReadCloser
list zipList
root string
}
func (fs *httpZipFS) Open(name string) (http.File, os.Error) {
// fs.root does not start with '/'.
path := path.Join(fs.root, name) // path is clean
index, exact := fs.list.lookup(path)
if index < 0 || !strings.HasPrefix(path, fs.root) {
// file not found or not under root
return nil, fmt.Errorf("file not found: %s", name)
}
if exact {
// exact match found - must be a file
f := fs.list[index]
rc, err := f.Open()
if err != nil {
return nil, err
}
return &httpZipFile{
path,
os.FileInfo{
Name: name,
Mode: S_IFREG,
Size: int64(f.UncompressedSize),
Mtime_ns: f.Mtime_ns(),
},
rc,
nil,
}, nil
}
// not an exact match - must be a directory
return &httpZipFile{
path,
os.FileInfo{
Name: name,
Mode: S_IFDIR,
// no size or mtime_ns for directories
},
nil,
fs.list[index:],
}, nil
}
func (fs *httpZipFS) Close() os.Error {
fs.list = nil
return fs.ReadCloser.Close()
}
// NewHttpZipFS creates a new http.FileSystem based on the contents of
// the zip file rc restricted to the directory tree specified by root;
// root must be an absolute path.
func NewHttpZipFS(rc *zip.ReadCloser, root string) http.FileSystem {
list := make(zipList, len(rc.File))
copy(list, rc.File) // sort a copy of rc.File
sort.Sort(list)
return &httpZipFS{rc, list, zipPath(root)}
}
|