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