github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/httputil/range.go (about) 1 package httputil 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 ) 9 10 var ErrBadRange = errors.New("invalid range") 11 var ErrUnsatisfiableRange = errors.New("unsatisfiable range") 12 13 // Range represents an RFC 2616 HTTP Range 14 type Range struct { 15 StartOffset int64 16 EndOffset int64 17 } 18 19 func (r Range) String() string { 20 return fmt.Sprintf("start=%d, end=%d (total=%d)", r.StartOffset, r.EndOffset, r.EndOffset-r.StartOffset+1) 21 } 22 23 func (r Range) Size() int64 { 24 return r.EndOffset - r.StartOffset + 1 25 } 26 27 // ParseRange parses an HTTP RFC 2616 Range header value and returns an Range object for the given object length 28 func ParseRange(spec string, length int64) (Range, error) { 29 // Amazon S3 doesn't support retrieving multiple ranges of data per GET request. 30 var r Range 31 if !strings.HasPrefix(spec, "bytes=") { 32 return r, ErrBadRange 33 } 34 spec = strings.TrimPrefix(spec, "bytes=") 35 parts := strings.Split(spec, "-") 36 const rangeParts = 2 37 if len(parts) != rangeParts { 38 return r, ErrBadRange 39 } 40 41 fromString := parts[0] 42 toString := parts[1] 43 if len(fromString) == 0 && len(toString) == 0 { 44 return r, ErrBadRange 45 } 46 // negative only 47 if len(fromString) == 0 { 48 endOffset, err := strconv.ParseInt(toString, 10, 64) //nolint: mnd 49 if err != nil { 50 return r, ErrBadRange 51 } 52 r.StartOffset = length - endOffset 53 if length-endOffset < 0 { 54 r.StartOffset = 0 55 } 56 r.EndOffset = length - 1 57 return r, nil 58 } 59 // positive only 60 if len(toString) == 0 { 61 beginOffset, err := strconv.ParseInt(fromString, 10, 64) //nolint: mnd 62 if err != nil { 63 return r, ErrBadRange 64 } else if beginOffset > length-1 { 65 return r, ErrUnsatisfiableRange 66 } 67 r.StartOffset = beginOffset 68 r.EndOffset = length - 1 69 return r, nil 70 } 71 // both set 72 beginOffset, err := strconv.ParseInt(fromString, 10, 64) //nolint: mnd 73 if err != nil { 74 return r, ErrBadRange 75 } 76 endOffset, err := strconv.ParseInt(toString, 10, 64) //nolint: mnd 77 if err != nil { 78 return r, ErrBadRange 79 } 80 // if endOffset exceeds length return length : this is how it works in s3 (presto for example uses range with a huge endOffset regardless to the file size) 81 if endOffset > length-1 { 82 endOffset = length - 1 83 } 84 // if the beginning offset is after the size, it is unsatisfiable 85 if beginOffset > length-1 || endOffset > length-1 { 86 return r, ErrUnsatisfiableRange 87 } 88 r.StartOffset = beginOffset 89 r.EndOffset = endOffset 90 return r, nil 91 }