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.Get("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 + filepath.FromSlash(indexPage)
116
ff, err := os.Open(index, os.O_RDONLY, 0)
137
// use extension to find content type.
138
ext := filepath.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.Get("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, filepath.Join(f.root, filepath.FromSlash(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)