lib/rest: make ParseContentRange public
This commit is contained in:
+23
-19
@@ -10,45 +10,49 @@ import (
|
|||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contentRange struct {
|
// ContentRange is a parsed Content-Range response header.
|
||||||
start int64
|
type ContentRange struct {
|
||||||
end int64
|
Start int64 // first byte position of the range
|
||||||
size int64
|
End int64 // last byte position of the range (inclusive)
|
||||||
|
Size int64 // complete length of the representation, or -1 if unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseContentRange(value string) (contentRange, error) {
|
// ParseContentRange parses a Content-Range header of the form
|
||||||
|
// "bytes start-end/size" as returned with a ranged (206) response.
|
||||||
|
// The Size is -1 if the complete length is unknown ("*").
|
||||||
|
func ParseContentRange(value string) (ContentRange, error) {
|
||||||
const prefix = "bytes "
|
const prefix = "bytes "
|
||||||
if !strings.HasPrefix(value, prefix) {
|
if !strings.HasPrefix(value, prefix) {
|
||||||
return contentRange{}, fmt.Errorf("doesn't start with %q", prefix)
|
return ContentRange{}, fmt.Errorf("doesn't start with %q", prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
rangeAndSize := strings.Split(value[len(prefix):], "/")
|
rangeAndSize := strings.Split(value[len(prefix):], "/")
|
||||||
if len(rangeAndSize) != 2 {
|
if len(rangeAndSize) != 2 {
|
||||||
return contentRange{}, errors.New("must contain one '/'")
|
return ContentRange{}, errors.New("must contain one '/'")
|
||||||
}
|
}
|
||||||
bounds := strings.Split(rangeAndSize[0], "-")
|
bounds := strings.Split(rangeAndSize[0], "-")
|
||||||
if len(bounds) != 2 {
|
if len(bounds) != 2 {
|
||||||
return contentRange{}, errors.New("must contain one '-'")
|
return ContentRange{}, errors.New("must contain one '-'")
|
||||||
}
|
}
|
||||||
|
|
||||||
start, err := strconv.ParseInt(bounds[0], 10, 64)
|
start, err := strconv.ParseInt(bounds[0], 10, 64)
|
||||||
if err != nil || start < 0 {
|
if err != nil || start < 0 {
|
||||||
return contentRange{}, errors.New("invalid start")
|
return ContentRange{}, errors.New("invalid start")
|
||||||
}
|
}
|
||||||
end, err := strconv.ParseInt(bounds[1], 10, 64)
|
end, err := strconv.ParseInt(bounds[1], 10, 64)
|
||||||
if err != nil || end < start {
|
if err != nil || end < start {
|
||||||
return contentRange{}, errors.New("invalid end")
|
return ContentRange{}, errors.New("invalid end")
|
||||||
}
|
}
|
||||||
|
|
||||||
size := int64(-1)
|
size := int64(-1)
|
||||||
if rangeAndSize[1] != "*" {
|
if rangeAndSize[1] != "*" {
|
||||||
size, err = strconv.ParseInt(rangeAndSize[1], 10, 64)
|
size, err = strconv.ParseInt(rangeAndSize[1], 10, 64)
|
||||||
if err != nil || size < 0 || end >= size {
|
if err != nil || size < 0 || end >= size {
|
||||||
return contentRange{}, errors.New("invalid complete length")
|
return ContentRange{}, errors.New("invalid complete length")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentRange{start: start, end: end, size: size}, nil
|
return ContentRange{Start: start, End: end, Size: size}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckContentRange checks that a response satisfies a Range open option.
|
// CheckContentRange checks that a response satisfies a Range open option.
|
||||||
@@ -106,18 +110,18 @@ func CheckContentRange(resp *http.Response, options []fs.OpenOption, size int64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
responseRange := resp.Header.Get("Content-Range")
|
responseRange := resp.Header.Get("Content-Range")
|
||||||
got, err := parseContentRange(responseRange)
|
got, err := ParseContentRange(responseRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid Content-Range %q: %w", responseRange, err)
|
return fmt.Errorf("invalid Content-Range %q: %w", responseRange, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if size >= 0 && got.size >= 0 && got.size != size {
|
if size >= 0 && got.Size >= 0 && got.Size != size {
|
||||||
return fmt.Errorf("Content-Range %q does not match expected size %d", responseRange, size)
|
return fmt.Errorf("Content-Range %q does not match expected size %d", responseRange, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
rangeSize := size
|
rangeSize := size
|
||||||
if rangeSize < 0 {
|
if rangeSize < 0 {
|
||||||
rangeSize = got.size
|
rangeSize = got.Size
|
||||||
}
|
}
|
||||||
expectedStart := requested.Start
|
expectedStart := requested.Start
|
||||||
expectedEnd := requested.End
|
expectedEnd := requested.End
|
||||||
@@ -132,18 +136,18 @@ func CheckContentRange(resp *http.Response, options []fs.OpenOption, size int64)
|
|||||||
|
|
||||||
var matches bool
|
var matches bool
|
||||||
if requested.Start < 0 && rangeSize < 0 {
|
if requested.Start < 0 && rangeSize < 0 {
|
||||||
matches = got.end-got.start+1 <= requested.End
|
matches = got.End-got.Start+1 <= requested.End
|
||||||
} else {
|
} else {
|
||||||
matches = got.start == expectedStart
|
matches = got.Start == expectedStart
|
||||||
if expectedEnd >= 0 {
|
if expectedEnd >= 0 {
|
||||||
matches = matches && got.end == expectedEnd
|
matches = matches && got.End == expectedEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !matches {
|
if !matches {
|
||||||
return fmt.Errorf("Content-Range %q does not match requested range %q", responseRange, requestRange)
|
return fmt.Errorf("Content-Range %q does not match requested range %q", responseRange, requestRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentLength := got.end - got.start + 1
|
contentLength := got.End - got.Start + 1
|
||||||
if contentLength <= 0 {
|
if contentLength <= 0 {
|
||||||
return fmt.Errorf("invalid Content-Range %q: length overflows", responseRange)
|
return fmt.Errorf("invalid Content-Range %q: length overflows", responseRange)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user