fix: add test cases and fix behavior for head/get range requests

This adds a bunch of test cases for non-0 len object, 0 len
object, and directory objects to match verified AWS responses
for the various range bytes cases.

This fixes the posix head/get range responses for these test
cases as well.
This commit is contained in:
Ben McClelland
2025-08-12 09:57:46 -07:00
parent 01760fdf1c
commit e134f63ebc
5 changed files with 318 additions and 78 deletions

View File

@@ -95,40 +95,43 @@ var (
// for invalid inputs, it returns no error, but isValid=false
// `InvalidRange` error is returnd, only if startoffset is greater than the object size
func ParseObjectRange(size int64, acceptRange string) (int64, int64, bool, error) {
// Return full object (invalid range, no error) if header empty
if acceptRange == "" {
return 0, size, false, nil
}
rangeKv := strings.Split(acceptRange, "=")
if len(rangeKv) != 2 {
return 0, size, false, nil
}
if rangeKv[0] != "bytes" {
if rangeKv[0] != "bytes" { // unsupported unit -> ignore
return 0, size, false, nil
}
bRange := strings.Split(rangeKv[1], "-")
if len(bRange) != 2 {
if len(bRange) != 2 { // malformed / multi-range
return 0, size, false, nil
}
// Parse start; empty start indicates a suffix-byte-range-spec (e.g. bytes=-100)
startOffset, err := strconv.ParseInt(bRange[0], 10, strconv.IntSize)
if startOffset > int64(math.MaxInt) || startOffset < int64(math.MinInt) {
return 0, size, false, errInvalidRange
}
if err != nil && bRange[0] != "" {
if err != nil && bRange[0] != "" { // invalid numeric start (non-empty) -> ignore range
return 0, size, false, nil
}
// If end part missing (e.g. bytes=100-)
if bRange[1] == "" {
if bRange[0] == "" {
if bRange[0] == "" { // bytes=- (meaningless) -> ignore
return 0, size, false, nil
}
// start beyond or at size is unsatisfiable -> error (RequestedRangeNotSatisfiable)
if startOffset >= size {
return 0, 0, false, errInvalidRange
}
// bytes=100- => from start to end
return startOffset, size - startOffset, true, nil
}
@@ -136,28 +139,37 @@ func ParseObjectRange(size int64, acceptRange string) (int64, int64, bool, error
if endOffset > int64(math.MaxInt) {
return 0, size, false, errInvalidRange
}
if err != nil {
if err != nil { // invalid numeric end -> ignore range
return 0, size, false, nil
}
if startOffset > endOffset {
return 0, size, false, nil
}
// for ranges like 'bytes=-100' return the last bytes specified with 'endOffset'
// Suffix range handling (bRange[0] == "")
if bRange[0] == "" {
// Disallow -0 (always unsatisfiable)
if endOffset == 0 {
return 0, 0, false, errInvalidRange
}
// For zero-sized objects any positive suffix is treated as invalid (ignored, no error)
if size == 0 {
return 0, size, false, nil
}
// Clamp to object size (request more bytes than exist -> entire object)
endOffset = min(endOffset, size)
return size - endOffset, endOffset, true, nil
}
// Normal range (start-end)
if startOffset > endOffset { // start > end -> ignore
return 0, size, false, nil
}
// Start beyond or at end of object -> error
if startOffset >= size {
return 0, 0, false, errInvalidRange
}
// Adjust end beyond object size (trim)
if endOffset >= size {
endOffset = size - 1
}
return startOffset, endOffset - startOffset + 1, true, nil
}

View File

@@ -3484,6 +3484,11 @@ func (p *Posix) GetObject(_ context.Context, input *s3.GetObjectInput) (*s3.GetO
}
if fid.IsDir() {
_, _, _, err := backend.ParseObjectRange(0, *input.Range)
if err != nil {
return nil, err
}
userMetaData := make(map[string]string)
objMeta := p.loadObjectMetaData(nil, bucket, object, &fid, userMetaData)
@@ -3794,6 +3799,9 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
}
size := fi.Size()
if fi.IsDir() {
size = 0
}
startOffset, length, isValid, err := backend.ParseObjectRange(size, getString(input.Range))
if err != nil {