OSDN Git Service

eb0c67dfa198e8012b85618d7d5587d814c03b55
[pf3gnuchains/gcc-fork.git] / libgo / go / http / fs.go
1 // Copyright 2009 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 // HTTP file system request handler
6
7 package http
8
9 import (
10         "errors"
11         "fmt"
12         "io"
13         "mime"
14         "os"
15         "path"
16         "path/filepath"
17         "strconv"
18         "strings"
19         "time"
20         "utf8"
21 )
22
23 // A Dir implements http.FileSystem using the native file
24 // system restricted to a specific directory tree.
25 type Dir string
26
27 func (d Dir) Open(name string) (File, error) {
28         if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
29                 return nil, errors.New("http: invalid character in file path")
30         }
31         f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
32         if err != nil {
33                 return nil, err
34         }
35         return f, nil
36 }
37
38 // A FileSystem implements access to a collection of named files.
39 // The elements in a file path are separated by slash ('/', U+002F)
40 // characters, regardless of host operating system convention.
41 type FileSystem interface {
42         Open(name string) (File, error)
43 }
44
45 // A File is returned by a FileSystem's Open method and can be
46 // served by the FileServer implementation.
47 type File interface {
48         Close() error
49         Stat() (*os.FileInfo, error)
50         Readdir(count int) ([]os.FileInfo, error)
51         Read([]byte) (int, error)
52         Seek(offset int64, whence int) (int64, error)
53 }
54
55 // Heuristic: b is text if it is valid UTF-8 and doesn't
56 // contain any unprintable ASCII or Unicode characters.
57 func isText(b []byte) bool {
58         for len(b) > 0 && utf8.FullRune(b) {
59                 rune, size := utf8.DecodeRune(b)
60                 if size == 1 && rune == utf8.RuneError {
61                         // decoding error
62                         return false
63                 }
64                 if 0x7F <= rune && rune <= 0x9F {
65                         return false
66                 }
67                 if rune < ' ' {
68                         switch rune {
69                         case '\n', '\r', '\t':
70                                 // okay
71                         default:
72                                 // binary garbage
73                                 return false
74                         }
75                 }
76                 b = b[size:]
77         }
78         return true
79 }
80
81 func dirList(w ResponseWriter, f File) {
82         w.Header().Set("Content-Type", "text/html; charset=utf-8")
83         fmt.Fprintf(w, "<pre>\n")
84         for {
85                 dirs, err := f.Readdir(100)
86                 if err != nil || len(dirs) == 0 {
87                         break
88                 }
89                 for _, d := range dirs {
90                         name := d.Name
91                         if d.IsDirectory() {
92                                 name += "/"
93                         }
94                         // TODO htmlescape
95                         fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
96                 }
97         }
98         fmt.Fprintf(w, "</pre>\n")
99 }
100
101 // name is '/'-separated, not filepath.Separator.
102 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
103         const indexPage = "/index.html"
104
105         // redirect .../index.html to .../
106         // can't use Redirect() because that would make the path absolute,
107         // which would be a problem running under StripPrefix
108         if strings.HasSuffix(r.URL.Path, indexPage) {
109                 localRedirect(w, r, "./")
110                 return
111         }
112
113         f, err := fs.Open(name)
114         if err != nil {
115                 // TODO expose actual error?
116                 NotFound(w, r)
117                 return
118         }
119         defer f.Close()
120
121         d, err1 := f.Stat()
122         if err1 != nil {
123                 // TODO expose actual error?
124                 NotFound(w, r)
125                 return
126         }
127
128         if redirect {
129                 // redirect to canonical path: / at end of directory url
130                 // r.URL.Path always begins with /
131                 url := r.URL.Path
132                 if d.IsDirectory() {
133                         if url[len(url)-1] != '/' {
134                                 localRedirect(w, r, path.Base(url)+"/")
135                                 return
136                         }
137                 } else {
138                         if url[len(url)-1] == '/' {
139                                 localRedirect(w, r, "../"+path.Base(url))
140                                 return
141                         }
142                 }
143         }
144
145         if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
146                 w.WriteHeader(StatusNotModified)
147                 return
148         }
149         w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
150
151         // use contents of index.html for directory, if present
152         if d.IsDirectory() {
153                 index := name + indexPage
154                 ff, err := fs.Open(index)
155                 if err == nil {
156                         defer ff.Close()
157                         dd, err := ff.Stat()
158                         if err == nil {
159                                 name = index
160                                 d = dd
161                                 f = ff
162                         }
163                 }
164         }
165
166         if d.IsDirectory() {
167                 dirList(w, f)
168                 return
169         }
170
171         // serve file
172         size := d.Size
173         code := StatusOK
174
175         // If Content-Type isn't set, use the file's extension to find it.
176         if w.Header().Get("Content-Type") == "" {
177                 ctype := mime.TypeByExtension(filepath.Ext(name))
178                 if ctype == "" {
179                         // read a chunk to decide between utf-8 text and binary
180                         var buf [1024]byte
181                         n, _ := io.ReadFull(f, buf[:])
182                         b := buf[:n]
183                         if isText(b) {
184                                 ctype = "text/plain; charset=utf-8"
185                         } else {
186                                 // generic binary
187                                 ctype = "application/octet-stream"
188                         }
189                         f.Seek(0, os.SEEK_SET) // rewind to output whole file
190                 }
191                 w.Header().Set("Content-Type", ctype)
192         }
193
194         // handle Content-Range header.
195         // TODO(adg): handle multiple ranges
196         ranges, err := parseRange(r.Header.Get("Range"), size)
197         if err == nil && len(ranges) > 1 {
198                 err = errors.New("multiple ranges not supported")
199         }
200         if err != nil {
201                 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
202                 return
203         }
204         if len(ranges) == 1 {
205                 ra := ranges[0]
206                 if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
207                         Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
208                         return
209                 }
210                 size = ra.length
211                 code = StatusPartialContent
212                 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
213         }
214
215         w.Header().Set("Accept-Ranges", "bytes")
216         if w.Header().Get("Content-Encoding") == "" {
217                 w.Header().Set("Content-Length", strconv.Itoa64(size))
218         }
219
220         w.WriteHeader(code)
221
222         if r.Method != "HEAD" {
223                 io.CopyN(w, f, size)
224         }
225 }
226
227 // localRedirect gives a Moved Permanently response.
228 // It does not convert relative paths to absolute paths like Redirect does.
229 func localRedirect(w ResponseWriter, r *Request, newPath string) {
230         if q := r.URL.RawQuery; q != "" {
231                 newPath += "?" + q
232         }
233         w.Header().Set("Location", newPath)
234         w.WriteHeader(StatusMovedPermanently)
235 }
236
237 // ServeFile replies to the request with the contents of the named file or directory.
238 func ServeFile(w ResponseWriter, r *Request, name string) {
239         dir, file := filepath.Split(name)
240         serveFile(w, r, Dir(dir), file, false)
241 }
242
243 type fileHandler struct {
244         root FileSystem
245 }
246
247 // FileServer returns a handler that serves HTTP requests
248 // with the contents of the file system rooted at root.
249 //
250 // To use the operating system's file system implementation,
251 // use http.Dir:
252 //
253 //     http.Handle("/", http.FileServer(http.Dir("/tmp")))
254 func FileServer(root FileSystem) Handler {
255         return &fileHandler{root}
256 }
257
258 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
259         upath := r.URL.Path
260         if !strings.HasPrefix(upath, "/") {
261                 upath = "/" + upath
262                 r.URL.Path = upath
263         }
264         serveFile(w, r, f.root, path.Clean(upath), true)
265 }
266
267 // httpRange specifies the byte range to be sent to the client.
268 type httpRange struct {
269         start, length int64
270 }
271
272 // parseRange parses a Range header string as per RFC 2616.
273 func parseRange(s string, size int64) ([]httpRange, error) {
274         if s == "" {
275                 return nil, nil // header not present
276         }
277         const b = "bytes="
278         if !strings.HasPrefix(s, b) {
279                 return nil, errors.New("invalid range")
280         }
281         var ranges []httpRange
282         for _, ra := range strings.Split(s[len(b):], ",") {
283                 i := strings.Index(ra, "-")
284                 if i < 0 {
285                         return nil, errors.New("invalid range")
286                 }
287                 start, end := ra[:i], ra[i+1:]
288                 var r httpRange
289                 if start == "" {
290                         // If no start is specified, end specifies the
291                         // range start relative to the end of the file.
292                         i, err := strconv.Atoi64(end)
293                         if err != nil {
294                                 return nil, errors.New("invalid range")
295                         }
296                         if i > size {
297                                 i = size
298                         }
299                         r.start = size - i
300                         r.length = size - r.start
301                 } else {
302                         i, err := strconv.Atoi64(start)
303                         if err != nil || i > size || i < 0 {
304                                 return nil, errors.New("invalid range")
305                         }
306                         r.start = i
307                         if end == "" {
308                                 // If no end is specified, range extends to end of the file.
309                                 r.length = size - r.start
310                         } else {
311                                 i, err := strconv.Atoi64(end)
312                                 if err != nil || r.start > i {
313                                         return nil, errors.New("invalid range")
314                                 }
315                                 if i >= size {
316                                         i = size - 1
317                                 }
318                                 r.length = i - r.start + 1
319                         }
320                 }
321                 ranges = append(ranges, r)
322         }
323         return ranges, nil
324 }