1
2
3
4
5
6
7 package http
8
9 import (
10 "fmt"
11 "io"
12 "mime"
13 "os"
14 "path"
15 "path/filepath"
16 "strconv"
17 "strings"
18 "time"
19 "utf8"
20 )
21
22
23
24 type Dir string
25
26 func (d Dir) Open(name string) (File, os.Error) {
27 if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
28 return nil, os.NewError("http: invalid character in file path")
29 }
30 f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
31 if err != nil {
32 return nil, err
33 }
34 return f, nil
35 }
36
37
38
39
40 type FileSystem interface {
41 Open(name string) (File, os.Error)
42 }
43
44
45
46 type File interface {
47 Close() os.Error
48 Stat() (*os.FileInfo, os.Error)
49 Readdir(count int) ([]os.FileInfo, os.Error)
50 Read([]byte) (int, os.Error)
51 Seek(offset int64, whence int) (int64, os.Error)
52 }
53
54
55
56 func isText(b []byte) bool {
57 for len(b) > 0 && utf8.FullRune(b) {
58 rune, size := utf8.DecodeRune(b)
59 if size == 1 && rune == utf8.RuneError {
60
61 return false
62 }
63 if 0x7F <= rune && rune <= 0x9F {
64 return false
65 }
66 if rune < ' ' {
67 switch rune {
68 case '\n', '\r', '\t':
69
70 default:
71
72 return false
73 }
74 }
75 b = b[size:]
76 }
77 return true
78 }
79
80 func dirList(w ResponseWriter, f File) {
81 w.Header().Set("Content-Type", "text/html; charset=utf-8")
82 fmt.Fprintf(w, "<pre>\n")
83 for {
84 dirs, err := f.Readdir(100)
85 if err != nil || len(dirs) == 0 {
86 break
87 }
88 for _, d := range dirs {
89 name := d.Name
90 if d.IsDirectory() {
91 name += "/"
92 }
93
94 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
95 }
96 }
97 fmt.Fprintf(w, "</pre>\n")
98 }
99
100
101 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
102 const indexPage = "/index.html"
103
104
105
106
107 if strings.HasSuffix(r.URL.Path, indexPage) {
108 localRedirect(w, r, "./")
109 return
110 }
111
112 f, err := fs.Open(name)
113 if err != nil {
114
115 NotFound(w, r)
116 return
117 }
118 defer f.Close()
119
120 d, err1 := f.Stat()
121 if err1 != nil {
122
123 NotFound(w, r)
124 return
125 }
126
127 if redirect {
128
129
130 url := r.URL.Path
131 if d.IsDirectory() {
132 if url[len(url)-1] != '/' {
133 localRedirect(w, r, path.Base(url)+"/")
134 return
135 }
136 } else {
137 if url[len(url)-1] == '/' {
138 localRedirect(w, r, "../"+path.Base(url))
139 return
140 }
141 }
142 }
143
144 if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
145 w.WriteHeader(StatusNotModified)
146 return
147 }
148 w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
149
150
151 if d.IsDirectory() {
152 index := name + indexPage
153 ff, err := fs.Open(index)
154 if err == nil {
155 defer ff.Close()
156 dd, err := ff.Stat()
157 if err == nil {
158 name = index
159 d = dd
160 f = ff
161 }
162 }
163 }
164
165 if d.IsDirectory() {
166 dirList(w, f)
167 return
168 }
169
170
171 size := d.Size
172 code := StatusOK
173
174
175 if w.Header().Get("Content-Type") == "" {
176 ctype := mime.TypeByExtension(filepath.Ext(name))
177 if ctype == "" {
178
179 var buf [1024]byte
180 n, _ := io.ReadFull(f, buf[:])
181 b := buf[:n]
182 if isText(b) {
183 ctype = "text/plain; charset=utf-8"
184 } else {
185
186 ctype = "application/octet-stream"
187 }
188 f.Seek(0, os.SEEK_SET)
189 }
190 w.Header().Set("Content-Type", ctype)
191 }
192
193
194
195 ranges, err := parseRange(r.Header.Get("Range"), size)
196 if err == nil && len(ranges) > 1 {
197 err = os.NewError("multiple ranges not supported")
198 }
199 if err != nil {
200 Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
201 return
202 }
203 if len(ranges) == 1 {
204 ra := ranges[0]
205 if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
206 Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
207 return
208 }
209 size = ra.length
210 code = StatusPartialContent
211 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
212 }
213
214 w.Header().Set("Accept-Ranges", "bytes")
215 if w.Header().Get("Content-Encoding") == "" {
216 w.Header().Set("Content-Length", strconv.Itoa64(size))
217 }
218
219 w.WriteHeader(code)
220
221 if r.Method != "HEAD" {
222 io.Copyn(w, f, size)
223 }
224 }
225
226
227
228 func localRedirect(w ResponseWriter, r *Request, newPath string) {
229 if q := r.URL.RawQuery; q != "" {
230 newPath += "?" + q
231 }
232 w.Header().Set("Location", newPath)
233 w.WriteHeader(StatusMovedPermanently)
234 }
235
236
237 func ServeFile(w ResponseWriter, r *Request, name string) {
238 dir, file := filepath.Split(name)
239 serveFile(w, r, Dir(dir), file, false)
240 }
241
242 type fileHandler struct {
243 root FileSystem
244 }
245
246
247
248
249
250
251
252
253 func FileServer(root FileSystem) Handler {
254 return &fileHandler{root}
255 }
256
257 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
258 upath := r.URL.Path
259 if !strings.HasPrefix(upath, "/") {
260 upath = "/" + upath
261 r.URL.Path = upath
262 }
263 serveFile(w, r, f.root, path.Clean(upath), true)
264 }
265
266
267 type httpRange struct {
268 start, length int64
269 }
270
271
272 func parseRange(s string, size int64) ([]httpRange, os.Error) {
273 if s == "" {
274 return nil, nil
275 }
276 const b = "bytes="
277 if !strings.HasPrefix(s, b) {
278 return nil, os.NewError("invalid range")
279 }
280 var ranges []httpRange
281 for _, ra := range strings.Split(s[len(b):], ",") {
282 i := strings.Index(ra, "-")
283 if i < 0 {
284 return nil, os.NewError("invalid range")
285 }
286 start, end := ra[:i], ra[i+1:]
287 var r httpRange
288 if start == "" {
289
290
291 i, err := strconv.Atoi64(end)
292 if err != nil {
293 return nil, os.NewError("invalid range")
294 }
295 if i > size {
296 i = size
297 }
298 r.start = size - i
299 r.length = size - r.start
300 } else {
301 i, err := strconv.Atoi64(start)
302 if err != nil || i > size || i < 0 {
303 return nil, os.NewError("invalid range")
304 }
305 r.start = i
306 if end == "" {
307
308 r.length = size - r.start
309 } else {
310 i, err := strconv.Atoi64(end)
311 if err != nil || r.start > i {
312 return nil, os.NewError("invalid range")
313 }
314 if i >= size {
315 i = size - 1
316 }
317 r.length = i - r.start + 1
318 }
319 }
320 ranges = append(ranges, r)
321 }
322 return ranges, nil
323 }