Update to current version of Go library.
[platform/upstream/gcc.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         "fmt"
11         "io"
12         "mime"
13         "os"
14         "path/filepath"
15         "strconv"
16         "strings"
17         "time"
18         "utf8"
19 )
20
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 {
27                         // decoding error
28                         return false
29                 }
30                 if 0x7F <= rune && rune <= 0x9F {
31                         return false
32                 }
33                 if rune < ' ' {
34                         switch rune {
35                         case '\n', '\r', '\t':
36                                 // okay
37                         default:
38                                 // binary garbage
39                                 return false
40                         }
41                 }
42                 b = b[size:]
43         }
44         return true
45 }
46
47 func dirList(w ResponseWriter, f *os.File) {
48         fmt.Fprintf(w, "<pre>\n")
49         for {
50                 dirs, err := f.Readdir(100)
51                 if err != nil || len(dirs) == 0 {
52                         break
53                 }
54                 for _, d := range dirs {
55                         name := d.Name
56                         if d.IsDirectory() {
57                                 name += "/"
58                         }
59                         // TODO htmlescape
60                         fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
61                 }
62         }
63         fmt.Fprintf(w, "</pre>\n")
64 }
65
66 func serveFile(w ResponseWriter, r *Request, name string, redirect bool) {
67         const indexPage = "/index.html"
68
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)
72                 return
73         }
74
75         f, err := os.Open(name)
76         if err != nil {
77                 // TODO expose actual error?
78                 NotFound(w, r)
79                 return
80         }
81         defer f.Close()
82
83         d, err1 := f.Stat()
84         if err1 != nil {
85                 // TODO expose actual error?
86                 NotFound(w, r)
87                 return
88         }
89
90         if redirect {
91                 // redirect to canonical path: / at end of directory url
92                 // r.URL.Path always begins with /
93                 url := r.URL.Path
94                 if d.IsDirectory() {
95                         if url[len(url)-1] != '/' {
96                                 Redirect(w, r, url+"/", StatusMovedPermanently)
97                                 return
98                         }
99                 } else {
100                         if url[len(url)-1] == '/' {
101                                 Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently)
102                                 return
103                         }
104                 }
105         }
106
107         if t, _ := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
108                 w.WriteHeader(StatusNotModified)
109                 return
110         }
111         w.Header().Set("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat))
112
113         // use contents of index.html for directory, if present
114         if d.IsDirectory() {
115                 index := name + filepath.FromSlash(indexPage)
116                 ff, err := os.Open(index)
117                 if err == nil {
118                         defer ff.Close()
119                         dd, err := ff.Stat()
120                         if err == nil {
121                                 name = index
122                                 d = dd
123                                 f = ff
124                         }
125                 }
126         }
127
128         if d.IsDirectory() {
129                 dirList(w, f)
130                 return
131         }
132
133         // serve file
134         size := d.Size
135         code := StatusOK
136
137         // If Content-Type isn't set, use the file's extension to find it.
138         if w.Header().Get("Content-Type") == "" {
139                 ctype := mime.TypeByExtension(filepath.Ext(name))
140                 if ctype == "" {
141                         // read a chunk to decide between utf-8 text and binary
142                         var buf [1024]byte
143                         n, _ := io.ReadFull(f, buf[:])
144                         b := buf[:n]
145                         if isText(b) {
146                                 ctype = "text/plain; charset=utf-8"
147                         } else {
148                                 // generic binary
149                                 ctype = "application/octet-stream"
150                         }
151                         f.Seek(0, os.SEEK_SET) // rewind to output whole file
152                 }
153                 w.Header().Set("Content-Type", ctype)
154         }
155
156         // handle Content-Range header.
157         // TODO(adg): handle multiple ranges
158         ranges, err := parseRange(r.Header.Get("Range"), size)
159         if err == nil && len(ranges) > 1 {
160                 err = os.ErrorString("multiple ranges not supported")
161         }
162         if err != nil {
163                 Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
164                 return
165         }
166         if len(ranges) == 1 {
167                 ra := ranges[0]
168                 if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
169                         Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
170                         return
171                 }
172                 size = ra.length
173                 code = StatusPartialContent
174                 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
175         }
176
177         w.Header().Set("Accept-Ranges", "bytes")
178         w.Header().Set("Content-Length", strconv.Itoa64(size))
179
180         w.WriteHeader(code)
181
182         if r.Method != "HEAD" {
183                 io.Copyn(w, f, size)
184         }
185 }
186
187 // ServeFile replies to the request with the contents of the named file or directory.
188 func ServeFile(w ResponseWriter, r *Request, name string) {
189         serveFile(w, r, name, false)
190 }
191
192 type fileHandler struct {
193         root   string
194         prefix string
195 }
196
197 // FileServer returns a handler that serves HTTP requests
198 // with the contents of the file system rooted at root.
199 // It strips prefix from the incoming requests before
200 // looking up the file name in the file system.
201 func FileServer(root, prefix string) Handler { return &fileHandler{root, prefix} }
202
203 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
204         path := r.URL.Path
205         if !strings.HasPrefix(path, f.prefix) {
206                 NotFound(w, r)
207                 return
208         }
209         path = path[len(f.prefix):]
210         serveFile(w, r, filepath.Join(f.root, filepath.FromSlash(path)), true)
211 }
212
213 // httpRange specifies the byte range to be sent to the client.
214 type httpRange struct {
215         start, length int64
216 }
217
218 // parseRange parses a Range header string as per RFC 2616.
219 func parseRange(s string, size int64) ([]httpRange, os.Error) {
220         if s == "" {
221                 return nil, nil // header not present
222         }
223         const b = "bytes="
224         if !strings.HasPrefix(s, b) {
225                 return nil, os.NewError("invalid range")
226         }
227         var ranges []httpRange
228         for _, ra := range strings.Split(s[len(b):], ",", -1) {
229                 i := strings.Index(ra, "-")
230                 if i < 0 {
231                         return nil, os.NewError("invalid range")
232                 }
233                 start, end := ra[:i], ra[i+1:]
234                 var r httpRange
235                 if start == "" {
236                         // If no start is specified, end specifies the
237                         // range start relative to the end of the file.
238                         i, err := strconv.Atoi64(end)
239                         if err != nil {
240                                 return nil, os.NewError("invalid range")
241                         }
242                         if i > size {
243                                 i = size
244                         }
245                         r.start = size - i
246                         r.length = size - r.start
247                 } else {
248                         i, err := strconv.Atoi64(start)
249                         if err != nil || i > size || i < 0 {
250                                 return nil, os.NewError("invalid range")
251                         }
252                         r.start = i
253                         if end == "" {
254                                 // If no end is specified, range extends to end of the file.
255                                 r.length = size - r.start
256                         } else {
257                                 i, err := strconv.Atoi64(end)
258                                 if err != nil || r.start > i {
259                                         return nil, os.NewError("invalid range")
260                                 }
261                                 if i >= size {
262                                         i = size - 1
263                                 }
264                                 r.length = i - r.start + 1
265                         }
266                 }
267                 ranges = append(ranges, r)
268         }
269         return ranges, nil
270 }