github.com/cs3org/reva/v2@v2.27.7/pkg/rhttp/datatx/utils/download/range.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package download 20 21 import ( 22 "errors" 23 "fmt" 24 "mime/multipart" 25 "net/textproto" 26 "strconv" 27 "strings" 28 ) 29 30 // taken from https://golang.org/src/net/http/fs.go 31 32 // ErrSeeker is returned by ServeContent's sizeFunc when the content 33 // doesn't seek properly. The underlying Seeker's error text isn't 34 // included in the sizeFunc reply so it's not sent over HTTP to end 35 // users. 36 var ErrSeeker = errors.New("seeker can't seek") 37 38 // ErrNoOverlap is returned by serveContent's parseRange if first-byte-pos of 39 // all of the byte-range-spec values is greater than the content size. 40 var ErrNoOverlap = errors.New("invalid range: failed to overlap") 41 42 // HTTPRange specifies the byte range to be sent to the client. 43 type HTTPRange struct { 44 Start, Length int64 45 } 46 47 // ContentRange formats a Range header string as per RFC 7233. 48 func (r HTTPRange) ContentRange(size int64) string { 49 return fmt.Sprintf("bytes %d-%d/%d", r.Start, r.Start+r.Length-1, size) 50 } 51 52 // MimeHeader creates range relevant MimeHeaders 53 func (r HTTPRange) MimeHeader(contentType string, size int64) textproto.MIMEHeader { 54 return textproto.MIMEHeader{ 55 "Content-Range": {r.ContentRange(size)}, 56 "Content-Type": {contentType}, 57 } 58 } 59 60 // ParseRange parses a Range header string as per RFC 7233. 61 // errNoOverlap is returned if none of the ranges overlap. 62 func ParseRange(s string, size int64) ([]HTTPRange, error) { 63 if s == "" { 64 return nil, nil // header not present 65 } 66 const b = "bytes=" 67 if !strings.HasPrefix(s, b) { 68 return nil, errors.New("invalid range") 69 } 70 ranges := []HTTPRange{} 71 noOverlap := false 72 for _, ra := range strings.Split(s[len(b):], ",") { 73 ra = textproto.TrimString(ra) 74 if ra == "" { 75 continue 76 } 77 i := strings.Index(ra, "-") 78 if i < 0 { 79 return nil, errors.New("invalid range") 80 } 81 start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:]) 82 var r HTTPRange 83 if start == "" { 84 // If no start is specified, end specifies the 85 // range start relative to the end of the file. 86 i, err := strconv.ParseInt(end, 10, 64) 87 if err != nil { 88 return nil, errors.New("invalid range") 89 } 90 if i > size { 91 i = size 92 } 93 r.Start = size - i 94 r.Length = size - r.Start 95 } else { 96 i, err := strconv.ParseInt(start, 10, 64) 97 if err != nil || i < 0 { 98 return nil, errors.New("invalid range") 99 } 100 if i >= size { 101 // If the range begins after the size of the content, 102 // then it does not overlap. 103 noOverlap = true 104 continue 105 } 106 r.Start = i 107 if end == "" { 108 // If no end is specified, range extends to end of the file. 109 r.Length = size - r.Start 110 } else { 111 i, err := strconv.ParseInt(end, 10, 64) 112 if err != nil || r.Start > i { 113 return nil, errors.New("invalid range") 114 } 115 if i >= size { 116 i = size - 1 117 } 118 r.Length = i - r.Start + 1 119 } 120 } 121 ranges = append(ranges, r) 122 } 123 if noOverlap && len(ranges) == 0 { 124 // The specified ranges did not overlap with the content. 125 return nil, ErrNoOverlap 126 } 127 return ranges, nil 128 } 129 130 // countingWriter counts how many bytes have been written to it. 131 type countingWriter int64 132 133 func (w *countingWriter) Write(p []byte) (n int, err error) { 134 *w += countingWriter(len(p)) 135 return len(p), nil 136 } 137 138 // RangesMIMESize returns the number of bytes it takes to encode the 139 // provided ranges as a multipart response. 140 func RangesMIMESize(ranges []HTTPRange, contentType string, contentSize int64) (encSize int64) { 141 var w countingWriter 142 mw := multipart.NewWriter(&w) 143 for _, ra := range ranges { 144 // CreatePart might return an error if the io.Copy for the boundaries fails 145 // here parts are not filled, so we assume for now thet this will always succeed 146 _, _ = mw.CreatePart(ra.MimeHeader(contentType, contentSize)) 147 encSize += ra.Length 148 } 149 mw.Close() 150 encSize += int64(w) 151 return 152 } 153 154 // SumRangesSize adds up the length of all ranges 155 func SumRangesSize(ranges []HTTPRange) (size int64) { 156 for _, ra := range ranges { 157 size += ra.Length 158 } 159 return 160 }