Source file src/net/http/fs.go
1
2
3
4
5
6
7 package http
8
9 import (
10 "errors"
11 "fmt"
12 "io"
13 "mime"
14 "mime/multipart"
15 "net/textproto"
16 "net/url"
17 "os"
18 "path"
19 "path/filepath"
20 "sort"
21 "strconv"
22 "strings"
23 "time"
24 )
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 type Dir string
41
42
43
44
45 func mapDirOpenError(originalErr error, name string) error {
46 if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
47 return originalErr
48 }
49
50 parts := strings.Split(name, string(filepath.Separator))
51 for i := range parts {
52 if parts[i] == "" {
53 continue
54 }
55 fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
56 if err != nil {
57 return originalErr
58 }
59 if !fi.IsDir() {
60 return os.ErrNotExist
61 }
62 }
63 return originalErr
64 }
65
66
67
68 func (d Dir) Open(name string) (File, error) {
69 if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
70 return nil, errors.New("http: invalid character in file path")
71 }
72 dir := string(d)
73 if dir == "" {
74 dir = "."
75 }
76 fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
77 f, err := os.Open(fullName)
78 if err != nil {
79 return nil, mapDirOpenError(err, fullName)
80 }
81 return f, nil
82 }
83
84
85
86
87 type FileSystem interface {
88 Open(name string) (File, error)
89 }
90
91
92
93
94
95 type File interface {
96 io.Closer
97 io.Reader
98 io.Seeker
99 Readdir(count int) ([]os.FileInfo, error)
100 Stat() (os.FileInfo, error)
101 }
102
103 func dirList(w ResponseWriter, r *Request, f File) {
104 dirs, err := f.Readdir(-1)
105 if err != nil {
106 logf(r, "http: error reading directory: %v", err)
107 Error(w, "Error reading directory", StatusInternalServerError)
108 return
109 }
110 sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
111
112 w.Header().Set("Content-Type", "text/html; charset=utf-8")
113 fmt.Fprintf(w, "<pre>\n")
114 for _, d := range dirs {
115 name := d.Name()
116 if d.IsDir() {
117 name += "/"
118 }
119
120
121
122 url := url.URL{Path: name}
123 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
124 }
125 fmt.Fprintf(w, "</pre>\n")
126 }
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
154 sizeFunc := func() (int64, error) {
155 size, err := content.Seek(0, io.SeekEnd)
156 if err != nil {
157 return 0, errSeeker
158 }
159 _, err = content.Seek(0, io.SeekStart)
160 if err != nil {
161 return 0, errSeeker
162 }
163 return size, nil
164 }
165 serveContent(w, req, name, modtime, sizeFunc, content)
166 }
167
168
169
170
171
172 var errSeeker = errors.New("seeker can't seek")
173
174
175
176 var errNoOverlap = errors.New("invalid range: failed to overlap")
177
178
179
180
181
182 func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
183 setLastModified(w, modtime)
184 done, rangeReq := checkPreconditions(w, r, modtime)
185 if done {
186 return
187 }
188
189 code := StatusOK
190
191
192
193 ctypes, haveType := w.Header()["Content-Type"]
194 var ctype string
195 if !haveType {
196 ctype = mime.TypeByExtension(filepath.Ext(name))
197 if ctype == "" {
198
199 var buf [sniffLen]byte
200 n, _ := io.ReadFull(content, buf[:])
201 ctype = DetectContentType(buf[:n])
202 _, err := content.Seek(0, io.SeekStart)
203 if err != nil {
204 Error(w, "seeker can't seek", StatusInternalServerError)
205 return
206 }
207 }
208 w.Header().Set("Content-Type", ctype)
209 } else if len(ctypes) > 0 {
210 ctype = ctypes[0]
211 }
212
213 size, err := sizeFunc()
214 if err != nil {
215 Error(w, err.Error(), StatusInternalServerError)
216 return
217 }
218
219
220 sendSize := size
221 var sendContent io.Reader = content
222 if size >= 0 {
223 ranges, err := parseRange(rangeReq, size)
224 if err != nil {
225 if err == errNoOverlap {
226 w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
227 }
228 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
229 return
230 }
231 if sumRangesSize(ranges) > size {
232
233
234
235
236 ranges = nil
237 }
238 switch {
239 case len(ranges) == 1:
240
241
242
243
244
245
246
247
248
249
250
251 ra := ranges[0]
252 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
253 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
254 return
255 }
256 sendSize = ra.length
257 code = StatusPartialContent
258 w.Header().Set("Content-Range", ra.contentRange(size))
259 case len(ranges) > 1:
260 sendSize = rangesMIMESize(ranges, ctype, size)
261 code = StatusPartialContent
262
263 pr, pw := io.Pipe()
264 mw := multipart.NewWriter(pw)
265 w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
266 sendContent = pr
267 defer pr.Close()
268 go func() {
269 for _, ra := range ranges {
270 part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
271 if err != nil {
272 pw.CloseWithError(err)
273 return
274 }
275 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
276 pw.CloseWithError(err)
277 return
278 }
279 if _, err := io.CopyN(part, content, ra.length); err != nil {
280 pw.CloseWithError(err)
281 return
282 }
283 }
284 mw.Close()
285 pw.Close()
286 }()
287 }
288
289 w.Header().Set("Accept-Ranges", "bytes")
290 if w.Header().Get("Content-Encoding") == "" {
291 w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
292 }
293 }
294
295 w.WriteHeader(code)
296
297 if r.Method != "HEAD" {
298 io.CopyN(w, sendContent, sendSize)
299 }
300 }
301
302
303
304
305 func scanETag(s string) (etag string, remain string) {
306 s = textproto.TrimString(s)
307 start := 0
308 if strings.HasPrefix(s, "W/") {
309 start = 2
310 }
311 if len(s[start:]) < 2 || s[start] != '"' {
312 return "", ""
313 }
314
315
316 for i := start + 1; i < len(s); i++ {
317 c := s[i]
318 switch {
319
320 case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
321 case c == '"':
322 return s[:i+1], s[i+1:]
323 default:
324 return "", ""
325 }
326 }
327 return "", ""
328 }
329
330
331
332 func etagStrongMatch(a, b string) bool {
333 return a == b && a != "" && a[0] == '"'
334 }
335
336
337
338 func etagWeakMatch(a, b string) bool {
339 return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
340 }
341
342
343
344 type condResult int
345
346 const (
347 condNone condResult = iota
348 condTrue
349 condFalse
350 )
351
352 func checkIfMatch(w ResponseWriter, r *Request) condResult {
353 im := r.Header.Get("If-Match")
354 if im == "" {
355 return condNone
356 }
357 for {
358 im = textproto.TrimString(im)
359 if len(im) == 0 {
360 break
361 }
362 if im[0] == ',' {
363 im = im[1:]
364 continue
365 }
366 if im[0] == '*' {
367 return condTrue
368 }
369 etag, remain := scanETag(im)
370 if etag == "" {
371 break
372 }
373 if etagStrongMatch(etag, w.Header().get("Etag")) {
374 return condTrue
375 }
376 im = remain
377 }
378
379 return condFalse
380 }
381
382 func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
383 ius := r.Header.Get("If-Unmodified-Since")
384 if ius == "" || isZeroTime(modtime) {
385 return condNone
386 }
387 if t, err := ParseTime(ius); err == nil {
388
389
390 if modtime.Before(t.Add(1 * time.Second)) {
391 return condTrue
392 }
393 return condFalse
394 }
395 return condNone
396 }
397
398 func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
399 inm := r.Header.get("If-None-Match")
400 if inm == "" {
401 return condNone
402 }
403 buf := inm
404 for {
405 buf = textproto.TrimString(buf)
406 if len(buf) == 0 {
407 break
408 }
409 if buf[0] == ',' {
410 buf = buf[1:]
411 }
412 if buf[0] == '*' {
413 return condFalse
414 }
415 etag, remain := scanETag(buf)
416 if etag == "" {
417 break
418 }
419 if etagWeakMatch(etag, w.Header().get("Etag")) {
420 return condFalse
421 }
422 buf = remain
423 }
424 return condTrue
425 }
426
427 func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
428 if r.Method != "GET" && r.Method != "HEAD" {
429 return condNone
430 }
431 ims := r.Header.Get("If-Modified-Since")
432 if ims == "" || isZeroTime(modtime) {
433 return condNone
434 }
435 t, err := ParseTime(ims)
436 if err != nil {
437 return condNone
438 }
439
440
441 if modtime.Before(t.Add(1 * time.Second)) {
442 return condFalse
443 }
444 return condTrue
445 }
446
447 func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
448 if r.Method != "GET" && r.Method != "HEAD" {
449 return condNone
450 }
451 ir := r.Header.get("If-Range")
452 if ir == "" {
453 return condNone
454 }
455 etag, _ := scanETag(ir)
456 if etag != "" {
457 if etagStrongMatch(etag, w.Header().Get("Etag")) {
458 return condTrue
459 } else {
460 return condFalse
461 }
462 }
463
464
465 if modtime.IsZero() {
466 return condFalse
467 }
468 t, err := ParseTime(ir)
469 if err != nil {
470 return condFalse
471 }
472 if t.Unix() == modtime.Unix() {
473 return condTrue
474 }
475 return condFalse
476 }
477
478 var unixEpochTime = time.Unix(0, 0)
479
480
481 func isZeroTime(t time.Time) bool {
482 return t.IsZero() || t.Equal(unixEpochTime)
483 }
484
485 func setLastModified(w ResponseWriter, modtime time.Time) {
486 if !isZeroTime(modtime) {
487 w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
488 }
489 }
490
491 func writeNotModified(w ResponseWriter) {
492
493
494
495
496
497 h := w.Header()
498 delete(h, "Content-Type")
499 delete(h, "Content-Length")
500 if h.Get("Etag") != "" {
501 delete(h, "Last-Modified")
502 }
503 w.WriteHeader(StatusNotModified)
504 }
505
506
507
508 func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader string) {
509
510 ch := checkIfMatch(w, r)
511 if ch == condNone {
512 ch = checkIfUnmodifiedSince(r, modtime)
513 }
514 if ch == condFalse {
515 w.WriteHeader(StatusPreconditionFailed)
516 return true, ""
517 }
518 switch checkIfNoneMatch(w, r) {
519 case condFalse:
520 if r.Method == "GET" || r.Method == "HEAD" {
521 writeNotModified(w)
522 return true, ""
523 } else {
524 w.WriteHeader(StatusPreconditionFailed)
525 return true, ""
526 }
527 case condNone:
528 if checkIfModifiedSince(r, modtime) == condFalse {
529 writeNotModified(w)
530 return true, ""
531 }
532 }
533
534 rangeHeader = r.Header.get("Range")
535 if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
536 rangeHeader = ""
537 }
538 return false, rangeHeader
539 }
540
541
542 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
543 const indexPage = "/index.html"
544
545
546
547
548 if strings.HasSuffix(r.URL.Path, indexPage) {
549 localRedirect(w, r, "./")
550 return
551 }
552
553 f, err := fs.Open(name)
554 if err != nil {
555 msg, code := toHTTPError(err)
556 Error(w, msg, code)
557 return
558 }
559 defer f.Close()
560
561 d, err := f.Stat()
562 if err != nil {
563 msg, code := toHTTPError(err)
564 Error(w, msg, code)
565 return
566 }
567
568 if redirect {
569
570
571 url := r.URL.Path
572 if d.IsDir() {
573 if url[len(url)-1] != '/' {
574 localRedirect(w, r, path.Base(url)+"/")
575 return
576 }
577 } else {
578 if url[len(url)-1] == '/' {
579 localRedirect(w, r, "../"+path.Base(url))
580 return
581 }
582 }
583 }
584
585
586 if d.IsDir() {
587 url := r.URL.Path
588 if url[len(url)-1] != '/' {
589 localRedirect(w, r, path.Base(url)+"/")
590 return
591 }
592 }
593
594
595 if d.IsDir() {
596 index := strings.TrimSuffix(name, "/") + indexPage
597 ff, err := fs.Open(index)
598 if err == nil {
599 defer ff.Close()
600 dd, err := ff.Stat()
601 if err == nil {
602 name = index
603 d = dd
604 f = ff
605 }
606 }
607 }
608
609
610 if d.IsDir() {
611 if checkIfModifiedSince(r, d.ModTime()) == condFalse {
612 writeNotModified(w)
613 return
614 }
615 w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
616 dirList(w, r, f)
617 return
618 }
619
620
621 sizeFunc := func() (int64, error) { return d.Size(), nil }
622 serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
623 }
624
625
626
627
628
629
630 func toHTTPError(err error) (msg string, httpStatus int) {
631 if os.IsNotExist(err) {
632 return "404 page not found", StatusNotFound
633 }
634 if os.IsPermission(err) {
635 return "403 Forbidden", StatusForbidden
636 }
637
638 return "500 Internal Server Error", StatusInternalServerError
639 }
640
641
642
643 func localRedirect(w ResponseWriter, r *Request, newPath string) {
644 if q := r.URL.RawQuery; q != "" {
645 newPath += "?" + q
646 }
647 w.Header().Set("Location", newPath)
648 w.WriteHeader(StatusMovedPermanently)
649 }
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672 func ServeFile(w ResponseWriter, r *Request, name string) {
673 if containsDotDot(r.URL.Path) {
674
675
676
677
678
679 Error(w, "invalid URL path", StatusBadRequest)
680 return
681 }
682 dir, file := filepath.Split(name)
683 serveFile(w, r, Dir(dir), file, false)
684 }
685
686 func containsDotDot(v string) bool {
687 if !strings.Contains(v, "..") {
688 return false
689 }
690 for _, ent := range strings.FieldsFunc(v, isSlashRune) {
691 if ent == ".." {
692 return true
693 }
694 }
695 return false
696 }
697
698 func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
699
700 type fileHandler struct {
701 root FileSystem
702 }
703
704
705
706
707
708
709
710
711
712
713
714
715 func FileServer(root FileSystem) Handler {
716 return &fileHandler{root}
717 }
718
719 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
720 upath := r.URL.Path
721 if !strings.HasPrefix(upath, "/") {
722 upath = "/" + upath
723 r.URL.Path = upath
724 }
725 serveFile(w, r, f.root, path.Clean(upath), true)
726 }
727
728
729 type httpRange struct {
730 start, length int64
731 }
732
733 func (r httpRange) contentRange(size int64) string {
734 return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
735 }
736
737 func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
738 return textproto.MIMEHeader{
739 "Content-Range": {r.contentRange(size)},
740 "Content-Type": {contentType},
741 }
742 }
743
744
745
746 func parseRange(s string, size int64) ([]httpRange, error) {
747 if s == "" {
748 return nil, nil
749 }
750 const b = "bytes="
751 if !strings.HasPrefix(s, b) {
752 return nil, errors.New("invalid range")
753 }
754 var ranges []httpRange
755 noOverlap := false
756 for _, ra := range strings.Split(s[len(b):], ",") {
757 ra = strings.TrimSpace(ra)
758 if ra == "" {
759 continue
760 }
761 i := strings.Index(ra, "-")
762 if i < 0 {
763 return nil, errors.New("invalid range")
764 }
765 start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
766 var r httpRange
767 if start == "" {
768
769
770 i, err := strconv.ParseInt(end, 10, 64)
771 if err != nil {
772 return nil, errors.New("invalid range")
773 }
774 if i > size {
775 i = size
776 }
777 r.start = size - i
778 r.length = size - r.start
779 } else {
780 i, err := strconv.ParseInt(start, 10, 64)
781 if err != nil || i < 0 {
782 return nil, errors.New("invalid range")
783 }
784 if i >= size {
785
786
787 noOverlap = true
788 continue
789 }
790 r.start = i
791 if end == "" {
792
793 r.length = size - r.start
794 } else {
795 i, err := strconv.ParseInt(end, 10, 64)
796 if err != nil || r.start > i {
797 return nil, errors.New("invalid range")
798 }
799 if i >= size {
800 i = size - 1
801 }
802 r.length = i - r.start + 1
803 }
804 }
805 ranges = append(ranges, r)
806 }
807 if noOverlap && len(ranges) == 0 {
808
809 return nil, errNoOverlap
810 }
811 return ranges, nil
812 }
813
814
815 type countingWriter int64
816
817 func (w *countingWriter) Write(p []byte) (n int, err error) {
818 *w += countingWriter(len(p))
819 return len(p), nil
820 }
821
822
823
824 func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
825 var w countingWriter
826 mw := multipart.NewWriter(&w)
827 for _, ra := range ranges {
828 mw.CreatePart(ra.mimeHeader(contentType, contentSize))
829 encSize += ra.length
830 }
831 mw.Close()
832 encSize += int64(w)
833 return
834 }
835
836 func sumRangesSize(ranges []httpRange) (size int64) {
837 for _, ra := range ranges {
838 size += ra.length
839 }
840 return
841 }
842
View as plain text