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.
5 // HTTP file system request handler
21 // Heuristic: b is text if it is valid UTF-8 and doesn't
22 // contain any unprintable ASCII or Unicode characters.
23 func isText(b []byte) bool {
24 for len(b) > 0 && utf8.FullRune(b) {
25 rune, size := utf8.DecodeRune(b)
26 if size == 1 && rune == utf8.RuneError {
30 if 0x7F <= rune && rune <= 0x9F {
35 case '\n', '\r', '\t':
47 func dirList(w ResponseWriter, f *os.File) {
48 fmt.Fprintf(w, "<pre>\n")
50 dirs, err := f.Readdir(100)
51 if err != nil || len(dirs) == 0 {
54 for _, d := range dirs {
60 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
63 fmt.Fprintf(w, "</pre>\n")
66 func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
67 const indexPage = "/index.html"
69 // redirect .../index.html to .../
70 if strings.HasSuffix(r.URL.Path, indexPage) {
71 Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently)
75 f, err := os.Open(name, os.O_RDONLY, 0)
77 // TODO expose actual error?
85 // TODO expose actual error?
91 // redirect to canonical path: / at end of directory url
92 // r.URL.Path always begins with /
95 if url[len(url)-1] != '/' {
96 Redirect(w, r, url+"/", StatusMovedPermanently)
100 if url[len(url)-1] == '/' {
101 Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently)
107 if t, _ := time.Parse(TimeFormat, r.Header["If-Modified-Since"]); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
108 w.WriteHeader(StatusNotModified)
111 w.SetHeader("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
113 // use contents of index.html for directory, if present
115 index := name + indexPage
116 ff, err := os.Open(index, os.O_RDONLY, 0)
137 // use extension to find content type.
138 ext := path.Ext(name)
139 if ctype := mime.TypeByExtension(ext); ctype != "" {
140 w.SetHeader("Content-Type", ctype)
142 // read first chunk to decide between utf-8 text and binary
144 n, _ := io.ReadFull(f, buf[:])
147 w.SetHeader("Content-Type", "text-plain; charset=utf-8")
149 w.SetHeader("Content-Type", "application/octet-stream") // generic binary
151 f.Seek(0, 0) // rewind to output whole file
154 // handle Content-Range header.
155 // TODO(adg): handle multiple ranges
156 ranges, err := parseRange(r.Header["Range"], size)
157 if err != nil || len(ranges) > 1 {
158 Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
161 if len(ranges) == 1 {
163 if _, err := f.Seek(ra.start, 0); err != nil {
164 Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
168 code = StatusPartialContent
169 w.SetHeader("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
172 w.SetHeader("Accept-Ranges", "bytes")
173 w.SetHeader("Content-Length", strconv.Itoa64(size))
177 if r.Method != "HEAD" {
182 // ServeFile replies to the request with the contents of the named file or directory.
183 func ServeFile(w ResponseWriter, r *Request, name string) {
184 serveFile(w, r, name, false)
187 type fileHandler struct {
192 // FileServer returns a handler that serves HTTP requests
193 // with the contents of the file system rooted at root.
194 // It strips prefix from the incoming requests before
195 // looking up the file name in the file system.
196 func FileServer(root, prefix string) Handler { return &fileHandler{root, prefix} }
198 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
200 if !strings.HasPrefix(path, f.prefix) {
204 path = path[len(f.prefix):]
205 serveFile(w, r, f.root+"/"+path, true)
208 // httpRange specifies the byte range to be sent to the client.
209 type httpRange struct {
213 // parseRange parses a Range header string as per RFC 2616.
214 func parseRange(s string, size int64) ([]httpRange, os.Error) {
216 return nil, nil // header not present
219 if !strings.HasPrefix(s, b) {
220 return nil, os.NewError("invalid range")
222 var ranges []httpRange
223 for _, ra := range strings.Split(s[len(b):], ",", -1) {
224 i := strings.Index(ra, "-")
226 return nil, os.NewError("invalid range")
228 start, end := ra[:i], ra[i+1:]
231 // If no start is specified, end specifies the
232 // range start relative to the end of the file.
233 i, err := strconv.Atoi64(end)
235 return nil, os.NewError("invalid range")
241 r.length = size - r.start
243 i, err := strconv.Atoi64(start)
244 if err != nil || i > size || i < 0 {
245 return nil, os.NewError("invalid range")
249 // If no end is specified, range extends to end of the file.
250 r.length = size - r.start
252 i, err := strconv.Atoi64(end)
253 if err != nil || r.start > i {
254 return nil, os.NewError("invalid range")
259 r.length = i - r.start + 1
262 ranges = append(ranges, r)