1
2
3
4
5
6
7 package http
8
9 import (
10 "errors"
11 "fmt"
12 "internal/godebug"
13 "io"
14 "io/fs"
15 "mime"
16 "mime/multipart"
17 "net/http/internal"
18 "net/http/internal/ascii"
19 "net/textproto"
20 "net/url"
21 "os"
22 "path"
23 "path/filepath"
24 "sort"
25 "strconv"
26 "strings"
27 "time"
28 )
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 type Dir string
44
45
46
47
48 func mapOpenError(originalErr error, name string, sep rune, stat func(string) (fs.FileInfo, error)) error {
49 if errors.Is(originalErr, fs.ErrNotExist) || errors.Is(originalErr, fs.ErrPermission) {
50 return originalErr
51 }
52
53 parts := strings.Split(name, string(sep))
54 for i := range parts {
55 if parts[i] == "" {
56 continue
57 }
58 fi, err := stat(strings.Join(parts[:i+1], string(sep)))
59 if err != nil {
60 return originalErr
61 }
62 if !fi.IsDir() {
63 return fs.ErrNotExist
64 }
65 }
66 return originalErr
67 }
68
69
70
71
72 var errInvalidUnsafePath = errors.New("http: invalid or unsafe file path")
73
74
75
76 func (d Dir) Open(name string) (File, error) {
77 path := path.Clean("/" + name)[1:]
78 if path == "" {
79 path = "."
80 }
81 path, err := filepath.Localize(path)
82 if err != nil {
83 return nil, errInvalidUnsafePath
84 }
85 dir := string(d)
86 if dir == "" {
87 dir = "."
88 }
89 fullName := filepath.Join(dir, path)
90 f, err := os.Open(fullName)
91 if err != nil {
92 return nil, mapOpenError(err, fullName, filepath.Separator, os.Stat)
93 }
94 return f, nil
95 }
96
97
98
99
100
101
102
103
104
105
106 type FileSystem interface {
107 Open(name string) (File, error)
108 }
109
110
111
112
113
114 type File interface {
115 io.Closer
116 io.Reader
117 io.Seeker
118 Readdir(count int) ([]fs.FileInfo, error)
119 Stat() (fs.FileInfo, error)
120 }
121
122 type anyDirs interface {
123 len() int
124 name(i int) string
125 isDir(i int) bool
126 }
127
128 type fileInfoDirs []fs.FileInfo
129
130 func (d fileInfoDirs) len() int { return len(d) }
131 func (d fileInfoDirs) isDir(i int) bool { return d[i].IsDir() }
132 func (d fileInfoDirs) name(i int) string { return d[i].Name() }
133
134 type dirEntryDirs []fs.DirEntry
135
136 func (d dirEntryDirs) len() int { return len(d) }
137 func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() }
138 func (d dirEntryDirs) name(i int) string { return d[i].Name() }
139
140 func dirList(w ResponseWriter, r *Request, f File) {
141
142
143
144 var dirs anyDirs
145 var err error
146 if d, ok := f.(fs.ReadDirFile); ok {
147 var list dirEntryDirs
148 list, err = d.ReadDir(-1)
149 dirs = list
150 } else {
151 var list fileInfoDirs
152 list, err = f.Readdir(-1)
153 dirs = list
154 }
155
156 if err != nil {
157 logf(r, "http: error reading directory: %v", err)
158 Error(w, "Error reading directory", StatusInternalServerError)
159 return
160 }
161 sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) })
162
163 w.Header().Set("Content-Type", "text/html; charset=utf-8")
164 fmt.Fprintf(w, "<!doctype html>\n")
165 fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">\n")
166 fmt.Fprintf(w, "<meta name=\"color-scheme\" content=\"light dark\">\n")
167 fmt.Fprintf(w, "<pre>\n")
168 for i, n := 0, dirs.len(); i < n; i++ {
169 name := dirs.name(i)
170 if dirs.isDir(i) {
171 name += "/"
172 }
173
174
175
176 url := url.URL{Path: name}
177 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
178 }
179 fmt.Fprintf(w, "</pre>\n")
180 }
181
182
183
184 var httpservecontentkeepheaders = godebug.New("httpservecontentkeepheaders")
185
186
187
188
189
190 func serveError(w ResponseWriter, text string, code int) {
191 h := w.Header()
192
193 nonDefault := false
194 for _, k := range []string{
195 "Cache-Control",
196 "Content-Encoding",
197 "Etag",
198 "Last-Modified",
199 } {
200 if !h.has(k) {
201 continue
202 }
203 if httpservecontentkeepheaders.Value() == "1" {
204 nonDefault = true
205 } else {
206 h.Del(k)
207 }
208 }
209 if nonDefault {
210 httpservecontentkeepheaders.IncNonDefault()
211 }
212
213 Error(w, text, code)
214 }
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247 func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
248 sizeFunc := func() (int64, error) {
249 size, err := content.Seek(0, io.SeekEnd)
250 if err != nil {
251 return 0, errSeeker
252 }
253 _, err = content.Seek(0, io.SeekStart)
254 if err != nil {
255 return 0, errSeeker
256 }
257 return size, nil
258 }
259 serveContent(w, req, name, modtime, sizeFunc, content)
260 }
261
262
263
264
265
266 var errSeeker = errors.New("seeker can't seek")
267
268
269
270 var errNoOverlap = errors.New("invalid range: failed to overlap")
271
272
273
274
275
276 func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, sizeFunc func() (int64, error), content io.ReadSeeker) {
277 setLastModified(w, modtime)
278 done, rangeReq := checkPreconditions(w, r, modtime)
279 if done {
280 return
281 }
282
283 code := StatusOK
284
285
286
287 ctypes, haveType := w.Header()["Content-Type"]
288 var ctype string
289 if !haveType {
290 ctype = mime.TypeByExtension(filepath.Ext(name))
291 if ctype == "" {
292
293 var buf [internal.SniffLen]byte
294 n, _ := io.ReadFull(content, buf[:])
295 ctype = DetectContentType(buf[:n])
296 _, err := content.Seek(0, io.SeekStart)
297 if err != nil {
298 serveError(w, "seeker can't seek", StatusInternalServerError)
299 return
300 }
301 }
302 w.Header().Set("Content-Type", ctype)
303 } else if len(ctypes) > 0 {
304 ctype = ctypes[0]
305 }
306
307 size, err := sizeFunc()
308 if err != nil {
309 serveError(w, err.Error(), StatusInternalServerError)
310 return
311 }
312 if size < 0 {
313
314 serveError(w, "negative content size computed", StatusInternalServerError)
315 return
316 }
317
318
319 sendSize := size
320 var sendContent io.Reader = content
321 ranges, err := parseRange(rangeReq, size)
322 switch err {
323 case nil:
324 case errNoOverlap:
325 if size == 0 {
326
327
328
329
330 ranges = nil
331 break
332 }
333 w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
334 fallthrough
335 default:
336 serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
337 return
338 }
339
340 if sumRangesSize(ranges) > size {
341
342
343
344
345 ranges = nil
346 }
347 switch {
348 case len(ranges) == 1:
349
350
351
352
353
354
355
356
357
358
359
360 ra := ranges[0]
361 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
362 serveError(w, err.Error(), StatusRequestedRangeNotSatisfiable)
363 return
364 }
365 sendSize = ra.length
366 code = StatusPartialContent
367 w.Header().Set("Content-Range", ra.contentRange(size))
368 case len(ranges) > 1:
369 sendSize = rangesMIMESize(ranges, ctype, size)
370 code = StatusPartialContent
371
372 pr, pw := io.Pipe()
373 mw := multipart.NewWriter(pw)
374 w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
375 sendContent = pr
376 defer pr.Close()
377 go func() {
378 for _, ra := range ranges {
379 part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
380 if err != nil {
381 pw.CloseWithError(err)
382 return
383 }
384 if _, err := content.Seek(ra.start, io.SeekStart); err != nil {
385 pw.CloseWithError(err)
386 return
387 }
388 if _, err := io.CopyN(part, content, ra.length); err != nil {
389 pw.CloseWithError(err)
390 return
391 }
392 }
393 mw.Close()
394 pw.Close()
395 }()
396 }
397
398 w.Header().Set("Accept-Ranges", "bytes")
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425 if len(ranges) > 0 || w.Header().Get("Content-Encoding") == "" {
426 w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
427 }
428 w.WriteHeader(code)
429
430 if r.Method != "HEAD" {
431 io.CopyN(w, sendContent, sendSize)
432 }
433 }
434
435
436
437
438 func scanETag(s string) (etag string, remain string) {
439 s = textproto.TrimString(s)
440 start := 0
441 if strings.HasPrefix(s, "W/") {
442 start = 2
443 }
444 if len(s[start:]) < 2 || s[start] != '"' {
445 return "", ""
446 }
447
448
449 for i := start + 1; i < len(s); i++ {
450 c := s[i]
451 switch {
452
453 case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
454 case c == '"':
455 return s[:i+1], s[i+1:]
456 default:
457 return "", ""
458 }
459 }
460 return "", ""
461 }
462
463
464
465 func etagStrongMatch(a, b string) bool {
466 return a == b && a != "" && a[0] == '"'
467 }
468
469
470
471 func etagWeakMatch(a, b string) bool {
472 return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
473 }
474
475
476
477 type condResult int
478
479 const (
480 condNone condResult = iota
481 condTrue
482 condFalse
483 )
484
485 func checkIfMatch(w ResponseWriter, r *Request) condResult {
486 im := r.Header.Get("If-Match")
487 if im == "" {
488 return condNone
489 }
490 for {
491 im = textproto.TrimString(im)
492 if len(im) == 0 {
493 break
494 }
495 if im[0] == ',' {
496 im = im[1:]
497 continue
498 }
499 if im[0] == '*' {
500 return condTrue
501 }
502 etag, remain := scanETag(im)
503 if etag == "" {
504 break
505 }
506 if etagStrongMatch(etag, w.Header().get("Etag")) {
507 return condTrue
508 }
509 im = remain
510 }
511
512 return condFalse
513 }
514
515 func checkIfUnmodifiedSince(r *Request, modtime time.Time) condResult {
516 ius := r.Header.Get("If-Unmodified-Since")
517 if ius == "" || isZeroTime(modtime) {
518 return condNone
519 }
520 t, err := ParseTime(ius)
521 if err != nil {
522 return condNone
523 }
524
525
526
527 modtime = modtime.Truncate(time.Second)
528 if ret := modtime.Compare(t); ret <= 0 {
529 return condTrue
530 }
531 return condFalse
532 }
533
534 func checkIfNoneMatch(w ResponseWriter, r *Request) condResult {
535 inm := r.Header.get("If-None-Match")
536 if inm == "" {
537 return condNone
538 }
539 buf := inm
540 for {
541 buf = textproto.TrimString(buf)
542 if len(buf) == 0 {
543 break
544 }
545 if buf[0] == ',' {
546 buf = buf[1:]
547 continue
548 }
549 if buf[0] == '*' {
550 return condFalse
551 }
552 etag, remain := scanETag(buf)
553 if etag == "" {
554 break
555 }
556 if etagWeakMatch(etag, w.Header().get("Etag")) {
557 return condFalse
558 }
559 buf = remain
560 }
561 return condTrue
562 }
563
564 func checkIfModifiedSince(r *Request, modtime time.Time) condResult {
565 if r.Method != "GET" && r.Method != "HEAD" {
566 return condNone
567 }
568 ims := r.Header.Get("If-Modified-Since")
569 if ims == "" || isZeroTime(modtime) {
570 return condNone
571 }
572 t, err := ParseTime(ims)
573 if err != nil {
574 return condNone
575 }
576
577
578 modtime = modtime.Truncate(time.Second)
579 if ret := modtime.Compare(t); ret <= 0 {
580 return condFalse
581 }
582 return condTrue
583 }
584
585 func checkIfRange(w ResponseWriter, r *Request, modtime time.Time) condResult {
586 if r.Method != "GET" && r.Method != "HEAD" {
587 return condNone
588 }
589 ir := r.Header.get("If-Range")
590 if ir == "" {
591 return condNone
592 }
593 etag, _ := scanETag(ir)
594 if etag != "" {
595 if etagStrongMatch(etag, w.Header().Get("Etag")) {
596 return condTrue
597 } else {
598 return condFalse
599 }
600 }
601
602
603 if modtime.IsZero() {
604 return condFalse
605 }
606 t, err := ParseTime(ir)
607 if err != nil {
608 return condFalse
609 }
610 if t.Unix() == modtime.Unix() {
611 return condTrue
612 }
613 return condFalse
614 }
615
616 var unixEpochTime = time.Unix(0, 0)
617
618
619 func isZeroTime(t time.Time) bool {
620 return t.IsZero() || t.Equal(unixEpochTime)
621 }
622
623 func setLastModified(w ResponseWriter, modtime time.Time) {
624 if !isZeroTime(modtime) {
625 w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
626 }
627 }
628
629 func writeNotModified(w ResponseWriter) {
630
631
632
633
634
635 h := w.Header()
636 delete(h, "Content-Type")
637 delete(h, "Content-Length")
638 delete(h, "Content-Encoding")
639 if h.Get("Etag") != "" {
640 delete(h, "Last-Modified")
641 }
642 w.WriteHeader(StatusNotModified)
643 }
644
645
646
647 func checkPreconditions(w ResponseWriter, r *Request, modtime time.Time) (done bool, rangeHeader string) {
648
649 ch := checkIfMatch(w, r)
650 if ch == condNone {
651 ch = checkIfUnmodifiedSince(r, modtime)
652 }
653 if ch == condFalse {
654 w.WriteHeader(StatusPreconditionFailed)
655 return true, ""
656 }
657 switch checkIfNoneMatch(w, r) {
658 case condFalse:
659 if r.Method == "GET" || r.Method == "HEAD" {
660 writeNotModified(w)
661 return true, ""
662 } else {
663 w.WriteHeader(StatusPreconditionFailed)
664 return true, ""
665 }
666 case condNone:
667 if checkIfModifiedSince(r, modtime) == condFalse {
668 writeNotModified(w)
669 return true, ""
670 }
671 }
672
673 rangeHeader = r.Header.get("Range")
674 if rangeHeader != "" && checkIfRange(w, r, modtime) == condFalse {
675 rangeHeader = ""
676 }
677 return false, rangeHeader
678 }
679
680
681 func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
682 const indexPage = "/index.html"
683
684
685
686
687 if strings.HasSuffix(r.URL.Path, indexPage) {
688 localRedirect(w, r, "./")
689 return
690 }
691
692 f, err := fs.Open(name)
693 if err != nil {
694 msg, code := toHTTPError(err)
695 serveError(w, msg, code)
696 return
697 }
698 defer f.Close()
699
700 d, err := f.Stat()
701 if err != nil {
702 msg, code := toHTTPError(err)
703 serveError(w, msg, code)
704 return
705 }
706
707 if redirect {
708
709
710 url := r.URL.Path
711 if d.IsDir() {
712 if url[len(url)-1] != '/' {
713 localRedirect(w, r, path.Base(url)+"/")
714 return
715 }
716 } else if url[len(url)-1] == '/' {
717 base := path.Base(url)
718 if base == "/" || base == "." {
719
720 msg := "http: attempting to traverse a non-directory"
721 serveError(w, msg, StatusInternalServerError)
722 return
723 }
724 localRedirect(w, r, "../"+base)
725 return
726 }
727 }
728
729 if d.IsDir() {
730 url := r.URL.Path
731
732 if url == "" || url[len(url)-1] != '/' {
733 localRedirect(w, r, path.Base(url)+"/")
734 return
735 }
736
737
738 index := strings.TrimSuffix(name, "/") + indexPage
739 ff, err := fs.Open(index)
740 if err == nil {
741 defer ff.Close()
742 dd, err := ff.Stat()
743 if err == nil {
744 d = dd
745 f = ff
746 }
747 }
748 }
749
750
751 if d.IsDir() {
752 if checkIfModifiedSince(r, d.ModTime()) == condFalse {
753 writeNotModified(w)
754 return
755 }
756 setLastModified(w, d.ModTime())
757 dirList(w, r, f)
758 return
759 }
760
761
762 sizeFunc := func() (int64, error) { return d.Size(), nil }
763 serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
764 }
765
766
767
768
769
770
771 func toHTTPError(err error) (msg string, httpStatus int) {
772 if errors.Is(err, fs.ErrNotExist) {
773 return "404 page not found", StatusNotFound
774 }
775 if errors.Is(err, fs.ErrPermission) {
776 return "403 Forbidden", StatusForbidden
777 }
778 if errors.Is(err, errInvalidUnsafePath) {
779 return "404 page not found", StatusNotFound
780 }
781
782 return "500 Internal Server Error", StatusInternalServerError
783 }
784
785
786
787 func localRedirect(w ResponseWriter, r *Request, newPath string) {
788
789
790 if p := r.URL.EscapedPath(); strings.Contains(p, "%2f") || strings.Contains(p, "%2F") {
791 NotFound(w, r)
792 return
793 }
794 if q := r.URL.RawQuery; q != "" {
795 newPath += "?" + q
796 }
797 w.Header().Set("Location", newPath)
798 w.WriteHeader(StatusMovedPermanently)
799 }
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822 func ServeFile(w ResponseWriter, r *Request, name string) {
823 if containsDotDot(r.URL.Path) {
824
825
826
827
828
829 serveError(w, "invalid URL path", StatusBadRequest)
830 return
831 }
832 dir, file := filepath.Split(name)
833 serveFile(w, r, Dir(dir), file, false)
834 }
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856 func ServeFileFS(w ResponseWriter, r *Request, fsys fs.FS, name string) {
857 if containsDotDot(r.URL.Path) {
858
859
860
861
862
863 serveError(w, "invalid URL path", StatusBadRequest)
864 return
865 }
866 serveFile(w, r, FS(fsys), name, false)
867 }
868
869 func containsDotDot(v string) bool {
870 if !strings.Contains(v, "..") {
871 return false
872 }
873 for ent := range strings.FieldsFuncSeq(v, isSlashRune) {
874 if ent == ".." {
875 return true
876 }
877 }
878 return false
879 }
880
881 func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
882
883 type fileHandler struct {
884 root FileSystem
885 }
886
887 type ioFS struct {
888 fsys fs.FS
889 }
890
891 type ioFile struct {
892 file fs.File
893 }
894
895 func (f ioFS) Open(name string) (File, error) {
896 if name == "/" {
897 name = "."
898 } else {
899 name = strings.TrimPrefix(name, "/")
900 }
901 file, err := f.fsys.Open(name)
902 if err != nil {
903 return nil, mapOpenError(err, name, '/', func(path string) (fs.FileInfo, error) {
904 return fs.Stat(f.fsys, path)
905 })
906 }
907 return ioFile{file}, nil
908 }
909
910 func (f ioFile) Close() error { return f.file.Close() }
911 func (f ioFile) Read(b []byte) (int, error) { return f.file.Read(b) }
912 func (f ioFile) Stat() (fs.FileInfo, error) { return f.file.Stat() }
913
914 var errMissingSeek = errors.New("io.File missing Seek method")
915 var errMissingReadDir = errors.New("io.File directory missing ReadDir method")
916
917 func (f ioFile) Seek(offset int64, whence int) (int64, error) {
918 s, ok := f.file.(io.Seeker)
919 if !ok {
920 return 0, errMissingSeek
921 }
922 return s.Seek(offset, whence)
923 }
924
925 func (f ioFile) ReadDir(count int) ([]fs.DirEntry, error) {
926 d, ok := f.file.(fs.ReadDirFile)
927 if !ok {
928 return nil, errMissingReadDir
929 }
930 return d.ReadDir(count)
931 }
932
933 func (f ioFile) Readdir(count int) ([]fs.FileInfo, error) {
934 d, ok := f.file.(fs.ReadDirFile)
935 if !ok {
936 return nil, errMissingReadDir
937 }
938 var list []fs.FileInfo
939 for {
940 dirs, err := d.ReadDir(count - len(list))
941 for _, dir := range dirs {
942 info, err := dir.Info()
943 if err != nil {
944
945 continue
946 }
947 list = append(list, info)
948 }
949 if err != nil {
950 return list, err
951 }
952 if count < 0 || len(list) >= count {
953 break
954 }
955 }
956 return list, nil
957 }
958
959
960
961
962
963
964 func FS(fsys fs.FS) FileSystem {
965 return ioFS{fsys}
966 }
967
968
969
970
971
972
973
974
975 func FileServer(root FileSystem) Handler {
976 return &fileHandler{root}
977 }
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992 func FileServerFS(fsys fs.FS) Handler {
993 return FileServer(FS(fsys))
994 }
995
996 func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
997 upath := r.URL.Path
998 if !strings.HasPrefix(upath, "/") {
999 upath = "/" + upath
1000 r.URL.Path = upath
1001 }
1002 serveFile(w, r, f.root, path.Clean(upath), true)
1003 }
1004
1005
1006 type httpRange struct {
1007 start, length int64
1008 }
1009
1010 func (r httpRange) contentRange(size int64) string {
1011 return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
1012 }
1013
1014 func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
1015 return textproto.MIMEHeader{
1016 "Content-Range": {r.contentRange(size)},
1017 "Content-Type": {contentType},
1018 }
1019 }
1020
1021
1022
1023 func parseRange(s string, size int64) ([]httpRange, error) {
1024 if s == "" {
1025 return nil, nil
1026 }
1027 const b = "bytes="
1028 if len(s) < len(b) || !ascii.EqualFold(s[:len(b)], b) {
1029 return nil, errors.New("invalid range")
1030 }
1031 var ranges []httpRange
1032 noOverlap := false
1033 for ra := range strings.SplitSeq(s[len(b):], ",") {
1034 ra = textproto.TrimString(ra)
1035 if ra == "" {
1036 continue
1037 }
1038 start, end, ok := strings.Cut(ra, "-")
1039 if !ok {
1040 return nil, errors.New("invalid range")
1041 }
1042 start, end = textproto.TrimString(start), textproto.TrimString(end)
1043 var r httpRange
1044 if start == "" {
1045
1046
1047
1048
1049
1050 if end == "" || end[0] == '-' {
1051 return nil, errors.New("invalid range")
1052 }
1053 i, err := strconv.ParseInt(end, 10, 64)
1054 if i < 0 || err != nil {
1055 return nil, errors.New("invalid range")
1056 }
1057 if i > size {
1058 i = size
1059 }
1060 r.start = size - i
1061 r.length = size - r.start
1062 } else {
1063 i, err := strconv.ParseInt(start, 10, 64)
1064 if err != nil || i < 0 {
1065 return nil, errors.New("invalid range")
1066 }
1067 if i >= size {
1068
1069
1070 noOverlap = true
1071 continue
1072 }
1073 r.start = i
1074 if end == "" {
1075
1076 r.length = size - r.start
1077 } else {
1078 i, err := strconv.ParseInt(end, 10, 64)
1079 if err != nil || r.start > i {
1080 return nil, errors.New("invalid range")
1081 }
1082 if i >= size {
1083 i = size - 1
1084 }
1085 r.length = i - r.start + 1
1086 }
1087 }
1088 ranges = append(ranges, r)
1089 }
1090 if noOverlap && len(ranges) == 0 {
1091
1092 return nil, errNoOverlap
1093 }
1094 return ranges, nil
1095 }
1096
1097
1098 type countingWriter int64
1099
1100 func (w *countingWriter) Write(p []byte) (n int, err error) {
1101 *w += countingWriter(len(p))
1102 return len(p), nil
1103 }
1104
1105
1106
1107 func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
1108 var w countingWriter
1109 mw := multipart.NewWriter(&w)
1110 for _, ra := range ranges {
1111 mw.CreatePart(ra.mimeHeader(contentType, contentSize))
1112 encSize += ra.length
1113 }
1114 mw.Close()
1115 encSize += int64(w)
1116 return
1117 }
1118
1119 func sumRangesSize(ranges []httpRange) (size int64) {
1120 for _, ra := range ranges {
1121 size += ra.length
1122 }
1123 return
1124 }
1125
View as plain text