OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libgo/] [go/] [net/] [http/] [fs.go] - Blame information for rev 747

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 747 jeremybenn
// 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
        "errors"
11
        "fmt"
12
        "io"
13
        "mime"
14
        "os"
15
        "path"
16
        "path/filepath"
17
        "strconv"
18
        "strings"
19
        "time"
20
        "unicode/utf8"
21
)
22
 
23
// A Dir implements http.FileSystem using the native file
24
// system restricted to a specific directory tree.
25
//
26
// An empty Dir is treated as ".".
27
type Dir string
28
 
29
func (d Dir) Open(name string) (File, error) {
30
        if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
31
                return nil, errors.New("http: invalid character in file path")
32
        }
33
        dir := string(d)
34
        if dir == "" {
35
                dir = "."
36
        }
37
        f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
38
        if err != nil {
39
                return nil, err
40
        }
41
        return f, nil
42
}
43
 
44
// A FileSystem implements access to a collection of named files.
45
// The elements in a file path are separated by slash ('/', U+002F)
46
// characters, regardless of host operating system convention.
47
type FileSystem interface {
48
        Open(name string) (File, error)
49
}
50
 
51
// A File is returned by a FileSystem's Open method and can be
52
// served by the FileServer implementation.
53
type File interface {
54
        Close() error
55
        Stat() (os.FileInfo, error)
56
        Readdir(count int) ([]os.FileInfo, error)
57
        Read([]byte) (int, error)
58
        Seek(offset int64, whence int) (int64, error)
59
}
60
 
61
// Heuristic: b is text if it is valid UTF-8 and doesn't
62
// contain any unprintable ASCII or Unicode characters.
63
func isText(b []byte) bool {
64
        for len(b) > 0 && utf8.FullRune(b) {
65
                rune, size := utf8.DecodeRune(b)
66
                if size == 1 && rune == utf8.RuneError {
67
                        // decoding error
68
                        return false
69
                }
70
                if 0x7F <= rune && rune <= 0x9F {
71
                        return false
72
                }
73
                if rune < ' ' {
74
                        switch rune {
75
                        case '\n', '\r', '\t':
76
                                // okay
77
                        default:
78
                                // binary garbage
79
                                return false
80
                        }
81
                }
82
                b = b[size:]
83
        }
84
        return true
85
}
86
 
87
func dirList(w ResponseWriter, f File) {
88
        w.Header().Set("Content-Type", "text/html; charset=utf-8")
89
        fmt.Fprintf(w, "
\n")
90
        for {
91
                dirs, err := f.Readdir(100)
92
                if err != nil || len(dirs) == 0 {
93
                        break
94
                }
95
                for _, d := range dirs {
96
                        name := d.Name()
97
                        if d.IsDir() {
98
                                name += "/"
99
                        }
100
                        // TODO htmlescape
101
                        fmt.Fprintf(w, "%s\n", name, name)
102
                }
103
        }
104
        fmt.Fprintf(w, "
\n")
105
}
106
 
107
// name is '/'-separated, not filepath.Separator.
108
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
109
        const indexPage = "/index.html"
110
 
111
        // redirect .../index.html to .../
112
        // can't use Redirect() because that would make the path absolute,
113
        // which would be a problem running under StripPrefix
114
        if strings.HasSuffix(r.URL.Path, indexPage) {
115
                localRedirect(w, r, "./")
116
                return
117
        }
118
 
119
        f, err := fs.Open(name)
120
        if err != nil {
121
                // TODO expose actual error?
122
                NotFound(w, r)
123
                return
124
        }
125
        defer f.Close()
126
 
127
        d, err1 := f.Stat()
128
        if err1 != nil {
129
                // TODO expose actual error?
130
                NotFound(w, r)
131
                return
132
        }
133
 
134
        if redirect {
135
                // redirect to canonical path: / at end of directory url
136
                // r.URL.Path always begins with /
137
                url := r.URL.Path
138
                if d.IsDir() {
139
                        if url[len(url)-1] != '/' {
140
                                localRedirect(w, r, path.Base(url)+"/")
141
                                return
142
                        }
143
                } else {
144
                        if url[len(url)-1] == '/' {
145
                                localRedirect(w, r, "../"+path.Base(url))
146
                                return
147
                        }
148
                }
149
        }
150
 
151
        if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && !d.ModTime().After(t) {
152
                w.WriteHeader(StatusNotModified)
153
                return
154
        }
155
        w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
156
 
157
        // use contents of index.html for directory, if present
158
        if d.IsDir() {
159
                index := name + indexPage
160
                ff, err := fs.Open(index)
161
                if err == nil {
162
                        defer ff.Close()
163
                        dd, err := ff.Stat()
164
                        if err == nil {
165
                                name = index
166
                                d = dd
167
                                f = ff
168
                        }
169
                }
170
        }
171
 
172
        if d.IsDir() {
173
                dirList(w, f)
174
                return
175
        }
176
 
177
        // serve file
178
        size := d.Size()
179
        code := StatusOK
180
 
181
        // If Content-Type isn't set, use the file's extension to find it.
182
        if w.Header().Get("Content-Type") == "" {
183
                ctype := mime.TypeByExtension(filepath.Ext(name))
184
                if ctype == "" {
185
                        // read a chunk to decide between utf-8 text and binary
186
                        var buf [1024]byte
187
                        n, _ := io.ReadFull(f, buf[:])
188
                        b := buf[:n]
189
                        if isText(b) {
190
                                ctype = "text/plain; charset=utf-8"
191
                        } else {
192
                                // generic binary
193
                                ctype = "application/octet-stream"
194
                        }
195
                        f.Seek(0, os.SEEK_SET) // rewind to output whole file
196
                }
197
                w.Header().Set("Content-Type", ctype)
198
        }
199
 
200
        // handle Content-Range header.
201
        // TODO(adg): handle multiple ranges
202
        ranges, err := parseRange(r.Header.Get("Range"), size)
203
        if err == nil && len(ranges) > 1 {
204
                err = errors.New("multiple ranges not supported")
205
        }
206
        if err != nil {
207
                Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
208
                return
209
        }
210
        if len(ranges) == 1 {
211
                ra := ranges[0]
212
                if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
213
                        Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
214
                        return
215
                }
216
                size = ra.length
217
                code = StatusPartialContent
218
                w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size()))
219
        }
220
 
221
        w.Header().Set("Accept-Ranges", "bytes")
222
        if w.Header().Get("Content-Encoding") == "" {
223
                w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
224
        }
225
 
226
        w.WriteHeader(code)
227
 
228
        if r.Method != "HEAD" {
229
                io.CopyN(w, f, size)
230
        }
231
}
232
 
233
// localRedirect gives a Moved Permanently response.
234
// It does not convert relative paths to absolute paths like Redirect does.
235
func localRedirect(w ResponseWriter, r *Request, newPath string) {
236
        if q := r.URL.RawQuery; q != "" {
237
                newPath += "?" + q
238
        }
239
        w.Header().Set("Location", newPath)
240
        w.WriteHeader(StatusMovedPermanently)
241
}
242
 
243
// ServeFile replies to the request with the contents of the named file or directory.
244
func ServeFile(w ResponseWriter, r *Request, name string) {
245
        dir, file := filepath.Split(name)
246
        serveFile(w, r, Dir(dir), file, false)
247
}
248
 
249
type fileHandler struct {
250
        root FileSystem
251
}
252
 
253
// FileServer returns a handler that serves HTTP requests
254
// with the contents of the file system rooted at root.
255
//
256
// To use the operating system's file system implementation,
257
// use http.Dir:
258
//
259
//     http.Handle("/", http.FileServer(http.Dir("/tmp")))
260
func FileServer(root FileSystem) Handler {
261
        return &fileHandler{root}
262
}
263
 
264
func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
265
        upath := r.URL.Path
266
        if !strings.HasPrefix(upath, "/") {
267
                upath = "/" + upath
268
                r.URL.Path = upath
269
        }
270
        serveFile(w, r, f.root, path.Clean(upath), true)
271
}
272
 
273
// httpRange specifies the byte range to be sent to the client.
274
type httpRange struct {
275
        start, length int64
276
}
277
 
278
// parseRange parses a Range header string as per RFC 2616.
279
func parseRange(s string, size int64) ([]httpRange, error) {
280
        if s == "" {
281
                return nil, nil // header not present
282
        }
283
        const b = "bytes="
284
        if !strings.HasPrefix(s, b) {
285
                return nil, errors.New("invalid range")
286
        }
287
        var ranges []httpRange
288
        for _, ra := range strings.Split(s[len(b):], ",") {
289
                i := strings.Index(ra, "-")
290
                if i < 0 {
291
                        return nil, errors.New("invalid range")
292
                }
293
                start, end := ra[:i], ra[i+1:]
294
                var r httpRange
295
                if start == "" {
296
                        // If no start is specified, end specifies the
297
                        // range start relative to the end of the file.
298
                        i, err := strconv.ParseInt(end, 10, 64)
299
                        if err != nil {
300
                                return nil, errors.New("invalid range")
301
                        }
302
                        if i > size {
303
                                i = size
304
                        }
305
                        r.start = size - i
306
                        r.length = size - r.start
307
                } else {
308
                        i, err := strconv.ParseInt(start, 10, 64)
309
                        if err != nil || i > size || i < 0 {
310
                                return nil, errors.New("invalid range")
311
                        }
312
                        r.start = i
313
                        if end == "" {
314
                                // If no end is specified, range extends to end of the file.
315
                                r.length = size - r.start
316
                        } else {
317
                                i, err := strconv.ParseInt(end, 10, 64)
318
                                if err != nil || r.start > i {
319
                                        return nil, errors.New("invalid range")
320
                                }
321
                                if i >= size {
322
                                        i = size - 1
323
                                }
324
                                r.length = i - r.start + 1
325
                        }
326
                }
327
                ranges = append(ranges, r)
328
        }
329
        return ranges, nil
330
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.