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  }