summaryrefslogtreecommitdiff
path: root/src/pkg/http/fs.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/http/fs.go')
-rw-r--r--src/pkg/http/fs.go304
1 files changed, 0 insertions, 304 deletions
diff --git a/src/pkg/http/fs.go b/src/pkg/http/fs.go
deleted file mode 100644
index 0b830053a..000000000
--- a/src/pkg/http/fs.go
+++ /dev/null
@@ -1,304 +0,0 @@
-// 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"
- "io"
- "mime"
- "os"
- "path"
- "path/filepath"
- "strconv"
- "strings"
- "time"
- "utf8"
-)
-
-// A Dir implements http.FileSystem using the native file
-// system restricted to a specific directory tree.
-type Dir string
-
-func (d Dir) Open(name string) (File, os.Error) {
- if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
- return nil, os.NewError("http: invalid character in file path")
- }
- f, err := os.Open(filepath.Join(string(d), filepath.FromSlash(path.Clean("/"+name))))
- if err != nil {
- return nil, err
- }
- return f, nil
-}
-
-// A FileSystem implements access to a collection of named files.
-// The elements in a file path are separated by slash ('/', U+002F)
-// characters, regardless of host operating system convention.
-type FileSystem interface {
- Open(name string) (File, os.Error)
-}
-
-// A File is returned by a FileSystem's Open method and can be
-// served by the FileServer implementation.
-type File interface {
- Close() os.Error
- Stat() (*os.FileInfo, os.Error)
- Readdir(count int) ([]os.FileInfo, os.Error)
- Read([]byte) (int, os.Error)
- Seek(offset int64, whence int) (int64, os.Error)
-}
-
-// 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 0x7F <= rune && rune <= 0x9F {
- return false
- }
- if rune < ' ' {
- switch rune {
- case '\n', '\r', '\t':
- // okay
- default:
- // binary garbage
- return false
- }
- }
- b = b[size:]
- }
- return true
-}
-
-func dirList(w ResponseWriter, f File) {
- fmt.Fprintf(w, "<pre>\n")
- for {
- dirs, err := f.Readdir(100)
- if err != nil || len(dirs) == 0 {
- break
- }
- for _, d := range dirs {
- name := d.Name
- if d.IsDirectory() {
- name += "/"
- }
- // TODO htmlescape
- fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
- }
- }
- fmt.Fprintf(w, "</pre>\n")
-}
-
-// name is '/'-separated, not filepath.Separator.
-func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
- const indexPage = "/index.html"
-
- // redirect .../index.html to .../
- if strings.HasSuffix(r.URL.Path, indexPage) {
- Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently)
- return
- }
-
- f, err := fs.Open(name)
- if err != nil {
- // TODO expose actual error?
- NotFound(w, r)
- return
- }
- defer f.Close()
-
- d, err1 := f.Stat()
- if err1 != nil {
- // TODO expose actual error?
- NotFound(w, 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] != '/' {
- Redirect(w, r, url+"/", StatusMovedPermanently)
- return
- }
- } else {
- if url[len(url)-1] == '/' {
- Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently)
- return
- }
- }
- }
-
- if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
- w.WriteHeader(StatusNotModified)
- return
- }
- w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
-
- // use contents of index.html for directory, if present
- if d.IsDirectory() {
- index := name + filepath.FromSlash(indexPage)
- ff, err := fs.Open(index)
- if err == nil {
- defer ff.Close()
- dd, err := ff.Stat()
- if err == nil {
- name = index
- d = dd
- f = ff
- }
- }
- }
-
- if d.IsDirectory() {
- dirList(w, f)
- return
- }
-
- // serve file
- size := d.Size
- code := StatusOK
-
- // If Content-Type isn't set, use the file's extension to find it.
- if w.Header().Get("Content-Type") == "" {
- ctype := mime.TypeByExtension(filepath.Ext(name))
- if ctype == "" {
- // read a chunk to decide between utf-8 text and binary
- var buf [1024]byte
- n, _ := io.ReadFull(f, buf[:])
- b := buf[:n]
- if isText(b) {
- ctype = "text/plain; charset=utf-8"
- } else {
- // generic binary
- ctype = "application/octet-stream"
- }
- f.Seek(0, os.SEEK_SET) // rewind to output whole file
- }
- w.Header().Set("Content-Type", ctype)
- }
-
- // handle Content-Range header.
- // TODO(adg): handle multiple ranges
- ranges, err := parseRange(r.Header.Get("Range"), size)
- if err == nil && len(ranges) > 1 {
- err = os.NewError("multiple ranges not supported")
- }
- if err != nil {
- Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
- return
- }
- if len(ranges) == 1 {
- ra := ranges[0]
- if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
- Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
- return
- }
- size = ra.length
- code = StatusPartialContent
- w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
- }
-
- w.Header().Set("Accept-Ranges", "bytes")
- if w.Header().Get("Content-Encoding") == "" {
- w.Header().Set("Content-Length", strconv.Itoa64(size))
- }
-
- w.WriteHeader(code)
-
- if r.Method != "HEAD" {
- io.Copyn(w, f, size)
- }
-}
-
-// ServeFile replies to the request with the contents of the named file or directory.
-func ServeFile(w ResponseWriter, r *Request, name string) {
- serveFile(w, r, Dir(name), "", false)
-}
-
-type fileHandler struct {
- root FileSystem
-}
-
-// FileServer returns a handler that serves HTTP requests
-// with the contents of the file system rooted at root.
-//
-// To use the operating system's file system implementation,
-// use http.Dir:
-//
-// http.Handle("/", http.FileServer(http.Dir("/tmp")))
-func FileServer(root FileSystem) Handler {
- return &fileHandler{root}
-}
-
-func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
- serveFile(w, r, f.root, path.Clean(r.URL.Path), true)
-}
-
-// httpRange specifies the byte range to be sent to the client.
-type httpRange struct {
- start, length int64
-}
-
-// parseRange parses a Range header string as per RFC 2616.
-func parseRange(s string, size int64) ([]httpRange, os.Error) {
- if s == "" {
- return nil, nil // header not present
- }
- const b = "bytes="
- if !strings.HasPrefix(s, b) {
- return nil, os.NewError("invalid range")
- }
- var ranges []httpRange
- for _, ra := range strings.Split(s[len(b):], ",") {
- i := strings.Index(ra, "-")
- if i < 0 {
- return nil, os.NewError("invalid range")
- }
- start, end := ra[:i], ra[i+1:]
- var r httpRange
- if start == "" {
- // If no start is specified, end specifies the
- // range start relative to the end of the file.
- i, err := strconv.Atoi64(end)
- if err != nil {
- return nil, os.NewError("invalid range")
- }
- if i > size {
- i = size
- }
- r.start = size - i
- r.length = size - r.start
- } else {
- i, err := strconv.Atoi64(start)
- if err != nil || i > size || i < 0 {
- return nil, os.NewError("invalid range")
- }
- r.start = i
- if end == "" {
- // If no end is specified, range extends to end of the file.
- r.length = size - r.start
- } else {
- i, err := strconv.Atoi64(end)
- if err != nil || r.start > i {
- return nil, os.NewError("invalid range")
- }
- if i >= size {
- i = size - 1
- }
- r.length = i - r.start + 1
- }
- }
- ranges = append(ranges, r)
- }
- return ranges, nil
-}