diff options
Diffstat (limited to 'src/pkg/http/fs.go')
-rw-r--r-- | src/pkg/http/fs.go | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/src/pkg/http/fs.go b/src/pkg/http/fs.go new file mode 100644 index 000000000..108734c47 --- /dev/null +++ b/src/pkg/http/fs.go @@ -0,0 +1,184 @@ +// Copyright 2009 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. + +// HTTP file system request handler + +package http + +import ( + "fmt"; + "http"; + "io"; + "os"; + "path"; + "strings"; + "utf8"; +) + +// TODO this should be in a mime package somewhere +var contentByExt = map[string] string { + ".css": "text/css", + ".gif": "image/gif", + ".html": "text/html; charset=utf-8", + ".jpg": "image/jpeg", + ".js": "application/x-javascript", + ".png": "image/png", +} + +// Heuristic: b is text if it is valid UTF-8 and doesn't +// contain any unprintable ASCII or Unicode characters. +func isText(b []byte) bool { + for len(b) > 0 && utf8.FullRune(b) { + rune, size := utf8.DecodeRune(b); + if size == 1 && rune == utf8.RuneError { + // decoding error + return false; + } + if 0x80 <= rune && rune <= 0x9F { + return false; + } + if rune < ' ' { + switch rune { + case '\n', '\r', '\t': + // okay + default: + // binary garbage + return false; + } + } + b = b[size:len(b)]; + } + return true; +} + +func dirList(c *Conn, f *os.File) { + fmt.Fprintf(c, "<pre>\n"); + for { + dirs, err := f.Readdir(100); + if err != nil || len(dirs) == 0 { + break + } + for i, d := range dirs { + name := d.Name; + if d.IsDirectory() { + name += "/" + } + // TODO htmlescape + fmt.Fprintf(c, "<a href=\"%s\">%s</a>\n", name, name); + } + } + fmt.Fprintf(c, "</pre>\n"); +} + + +func serveFileInternal(c *Conn, r *Request, name string, redirect bool) { + const indexPage = "/index.html"; + + // redirect to strip off any index.html + n := len(name) - len(indexPage); + if n >= 0 && name[n:len(name)] == indexPage { + http.Redirect(c, name[0:n+1], StatusMovedPermanently); + return; + } + + f, err := os.Open(name, os.O_RDONLY, 0); + if err != nil { + // TODO expose actual error? + NotFound(c, r); + return; + } + defer f.Close(); + + d, err1 := f.Stat(); + if err1 != nil { + // TODO expose actual error? + NotFound(c, r); + return; + } + + if redirect { + // redirect to canonical path: / at end of directory url + // r.Url.Path always begins with / + url := r.Url.Path; + if d.IsDirectory() { + if url[len(url)-1] != '/' { + http.Redirect(c, url + "/", StatusMovedPermanently); + return; + } + } else { + if url[len(url)-1] == '/' { + http.Redirect(c, url[0:len(url)-1], StatusMovedPermanently); + return; + } + } + } + + // use contents of index.html for directory, if present + if d.IsDirectory() { + index := name + indexPage; + ff, err := os.Open(index, os.O_RDONLY, 0); + if err == nil { + defer ff.Close(); + dd, err := ff.Stat(); + if err == nil { + name = index; + d = dd; + f = ff; + } + } + } + + if d.IsDirectory() { + dirList(c, f); + return; + } + + // serve file + // use extension to find content type. + ext := path.Ext(name); + if ctype, ok := contentByExt[ext]; ok { + c.SetHeader("Content-Type", ctype); + } else { + // read first chunk to decide between utf-8 text and binary + var buf [1024]byte; + n, err := io.FullRead(f, &buf); + b := buf[0:n]; + if isText(b) { + c.SetHeader("Content-Type", "text-plain; charset=utf-8"); + } else { + c.SetHeader("Content-Type", "application/octet-stream"); // generic binary + } + c.Write(b); + } + io.Copy(f, c); +} + +// ServeFile replies to the request with the contents of the named file or directory. +func ServeFile(c *Conn, r *Request, name string) { + serveFileInternal(c, r, name, false); +} + +type fileHandler struct { + root string; + prefix string; +} + +// FileServer returns a handler that serves HTTP requests +// with the contents of the file system rooted at root. +// It strips prefix from the incoming requests before +// looking up the file name in the file system. +func FileServer(root, prefix string) Handler { + return &fileHandler{root, prefix}; +} + +func (f *fileHandler) ServeHTTP(c *Conn, r *Request) { + path := r.Url.Path; + if !strings.HasPrefix(path, f.prefix) { + NotFound(c, r); + return; + } + path = path[len(f.prefix):len(path)]; + serveFileInternal(c, r, f.root + "/" + path, true); +} + |