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