OSDN Git Service

Remove the types float and complex.
[pf3gnuchains/gcc-fork.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"
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, os.O_RDONLY, 0)
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["If-Modified-Since"]); t != nil && d.Mtime_ns/1e9 <= t.Seconds() {
108                 w.WriteHeader(StatusNotModified)
109                 return
110         }
111         w.SetHeader("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 + indexPage
116                 ff, err := os.Open(index, os.O_RDONLY, 0)
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         // use extension to find content type.
138         ext := path.Ext(name)
139         if ctype := mime.TypeByExtension(ext); ctype != "" {
140                 w.SetHeader("Content-Type", ctype)
141         } else {
142                 // read first chunk to decide between utf-8 text and binary
143                 var buf [1024]byte
144                 n, _ := io.ReadFull(f, buf[:])
145                 b := buf[:n]
146                 if isText(b) {
147                         w.SetHeader("Content-Type", "text-plain; charset=utf-8")
148                 } else {
149                         w.SetHeader("Content-Type", "application/octet-stream") // generic binary
150                 }
151                 f.Seek(0, 0) // rewind to output whole file
152         }
153
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)
159                 return
160         }
161         if len(ranges) == 1 {
162                 ra := ranges[0]
163                 if _, err := f.Seek(ra.start, 0); err != nil {
164                         Error(w, err.String(), StatusRequestedRangeNotSatisfiable)
165                         return
166                 }
167                 size = ra.length
168                 code = StatusPartialContent
169                 w.SetHeader("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size))
170         }
171
172         w.SetHeader("Accept-Ranges", "bytes")
173         w.SetHeader("Content-Length", strconv.Itoa64(size))
174
175         w.WriteHeader(code)
176
177         if r.Method != "HEAD" {
178                 io.Copyn(w, f, size)
179         }
180 }
181
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)
185 }
186
187 type fileHandler struct {
188         root   string
189         prefix string
190 }
191
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} }
197
198 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
199         path := r.URL.Path
200         if !strings.HasPrefix(path, f.prefix) {
201                 NotFound(w, r)
202                 return
203         }
204         path = path[len(f.prefix):]
205         serveFile(w, r, f.root+"/"+path, true)
206 }
207
208 // httpRange specifies the byte range to be sent to the client.
209 type httpRange struct {
210         start, length int64
211 }
212
213 // parseRange parses a Range header string as per RFC 2616.
214 func parseRange(s string, size int64) ([]httpRange, os.Error) {
215         if s == "" {
216                 return nil, nil // header not present
217         }
218         const b = "bytes="
219         if !strings.HasPrefix(s, b) {
220                 return nil, os.NewError("invalid range")
221         }
222         var ranges []httpRange
223         for _, ra := range strings.Split(s[len(b):], ",", -1) {
224                 i := strings.Index(ra, "-")
225                 if i < 0 {
226                         return nil, os.NewError("invalid range")
227                 }
228                 start, end := ra[:i], ra[i+1:]
229                 var r httpRange
230                 if start == "" {
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)
234                         if err != nil {
235                                 return nil, os.NewError("invalid range")
236                         }
237                         if i > size {
238                                 i = size
239                         }
240                         r.start = size - i
241                         r.length = size - r.start
242                 } else {
243                         i, err := strconv.Atoi64(start)
244                         if err != nil || i > size || i < 0 {
245                                 return nil, os.NewError("invalid range")
246                         }
247                         r.start = i
248                         if end == "" {
249                                 // If no end is specified, range extends to end of the file.
250                                 r.length = size - r.start
251                         } else {
252                                 i, err := strconv.Atoi64(end)
253                                 if err != nil || r.start > i {
254                                         return nil, os.NewError("invalid range")
255                                 }
256                                 if i >= size {
257                                         i = size - 1
258                                 }
259                                 r.length = i - r.start + 1
260                         }
261                 }
262                 ranges = append(ranges, r)
263         }
264         return ranges, nil
265 }