~john-koepi/ubuntu/trusty/golang/default

« back to all changes in this revision

Viewing changes to src/pkg/http/fs.go

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-04-20 17:36:48 UTC
  • Revision ID: james.westby@ubuntu.com-20110420173648-ifergoxyrm832trd
Tags: upstream-2011.03.07.1
Import upstream version 2011.03.07.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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, 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.Get("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 + filepath.FromSlash(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 := filepath.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.Get("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, filepath.Join(f.root, filepath.FromSlash(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
}