mirror of
https://github.com/versity/versitygw.git
synced 2026-09-02 06:06:59 +00:00
fix: correct 206 Partial Content response status for ranged GetObject and HeadObject
Fixes #2052 Fixes #2056 Fixes #2057 Previously, GetObject and HeadObject used the request's `Range` header to determine the response status code, which caused incorrect 206 responses for invalid Range header values. The status is now driven by whether res.ContentRange is set in the response, rather than by the presence of a range in the request. Backends (posix and azure) now set Content-Range for PartNumber=1 on non-multipart objects, skipping zero-size objects where no range applies. HeadObject was also fixed to return 206 when Content-Range is present, and to only return checksums when the full object is requested.
This commit is contained in:
+21
-11
@@ -491,6 +491,11 @@ func (az *Azure) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.G
|
||||
}
|
||||
}
|
||||
|
||||
var objSize int64
|
||||
if resp.ContentLength != nil {
|
||||
objSize = *resp.ContentLength
|
||||
}
|
||||
|
||||
var opts *azblob.DownloadStreamOptions
|
||||
var partsCount *int32
|
||||
var contentRange *string
|
||||
@@ -530,14 +535,15 @@ func (az *Azure) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.G
|
||||
}
|
||||
} else if *input.PartNumber > 1 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
} else {
|
||||
// partNumber=1 on a non-multipart object: fall through and serve the
|
||||
// full object without a range (opts remains nil)
|
||||
if objSize != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize))
|
||||
}
|
||||
}
|
||||
// partNumber=1 on a non-multipart object: fall through and serve the
|
||||
// full object without a range (opts remains nil, contentRange stays nil).
|
||||
} else if *input.Range != "" {
|
||||
var objSize int64
|
||||
if resp.ContentLength != nil {
|
||||
objSize = *resp.ContentLength
|
||||
}
|
||||
offset, count, isValid, err := backend.ParseObjectRange(objSize, *input.Range)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -612,7 +618,7 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
size = *resp.ContentLength
|
||||
}
|
||||
|
||||
var contentRange string
|
||||
var contentRange *string
|
||||
var length int64
|
||||
var partsCount *int32
|
||||
|
||||
@@ -638,13 +644,17 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
startOffset = mpMeta.Parts[partNum-2]
|
||||
}
|
||||
length = mpMeta.Parts[partNum-1] - startOffset
|
||||
contentRange = fmt.Sprintf("bytes %d-%d/%d", startOffset, startOffset+length-1, size)
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %d-%d/%d", startOffset, startOffset+length-1, size))
|
||||
} else if *input.PartNumber > 1 {
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
} else {
|
||||
// partNumber=1 on a non-multipart object: return full object size,
|
||||
// no Content-Range, no PartsCount.
|
||||
length = size
|
||||
if length != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", size-1, size))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startOffset, lgth, isValid, err := backend.ParseObjectRange(size, getString(input.Range))
|
||||
@@ -653,13 +663,13 @@ func (az *Azure) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3
|
||||
}
|
||||
length = lgth
|
||||
if isValid {
|
||||
contentRange = fmt.Sprintf("bytes %v-%v/%v",
|
||||
startOffset, startOffset+length-1, size)
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %v-%v/%v",
|
||||
startOffset, startOffset+length-1, size))
|
||||
}
|
||||
}
|
||||
|
||||
result := &s3.HeadObjectOutput{
|
||||
ContentRange: &contentRange,
|
||||
ContentRange: contentRange,
|
||||
AcceptRanges: backend.GetPtrFromString("bytes"),
|
||||
ContentLength: &length,
|
||||
PartsCount: partsCount,
|
||||
|
||||
+11
-3
@@ -4446,6 +4446,10 @@ func (p *Posix) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.Ge
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
}
|
||||
length = objSize
|
||||
if objSize != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize))
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("retrieve mp metadata: %w", metaErr)
|
||||
}
|
||||
@@ -4474,7 +4478,7 @@ func (p *Posix) GetObject(ctx context.Context, input *s3.GetObjectInput) (*s3.Ge
|
||||
}
|
||||
|
||||
var checksums s3response.Checksum
|
||||
// Skip the checksums retreival if object isn't requested fully
|
||||
// Return checksums only when the full object is requested
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled && length-startOffset == objSize {
|
||||
checksums, err = p.retrieveChecksums(f, bucket, object)
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
@@ -4681,6 +4685,10 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
return nil, s3err.GetAPIError(s3err.ErrInvalidPartNumberRange)
|
||||
}
|
||||
length = size
|
||||
if length != 0 {
|
||||
// if object size is 0, the whole object is served, no content range should be set
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes 0-%d/%d", length-1, length))
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("retrieve mp metadata: %w", metaErr)
|
||||
}
|
||||
@@ -4690,7 +4698,6 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
return nil, err
|
||||
}
|
||||
startOffset, length = start, lgth
|
||||
|
||||
if isValid {
|
||||
contentRange = backend.GetPtrFromString(fmt.Sprintf("bytes %v-%v/%v", start, start+lgth-1, size))
|
||||
}
|
||||
@@ -4718,7 +4725,8 @@ func (p *Posix) HeadObject(ctx context.Context, input *s3.HeadObjectInput) (*s3.
|
||||
}
|
||||
|
||||
var checksums s3response.Checksum
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled {
|
||||
// Return checksums only when the full object is requested
|
||||
if input.ChecksumMode == types.ChecksumModeEnabled && length-startOffset == size {
|
||||
checksums, err = p.retrieveChecksums(nil, bucket, object)
|
||||
if err != nil && !errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, fmt.Errorf("get object checksums: %w", err)
|
||||
|
||||
@@ -516,7 +516,7 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
utils.SetMetaHeaders(ctx, res.Metadata)
|
||||
|
||||
status := http.StatusOK
|
||||
if acceptRange != "" {
|
||||
if res.ContentRange != nil && *res.ContentRange != "" {
|
||||
status = http.StatusPartialContent
|
||||
}
|
||||
|
||||
|
||||
@@ -813,12 +813,56 @@ func TestS3ApiController_GetObject(t *testing.T) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
Status: http.StatusOK,
|
||||
ContentLength: cLength,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful partial response",
|
||||
input: testInput{
|
||||
locals: defaultLocals,
|
||||
beRes: &s3.GetObjectOutput{
|
||||
ContentRange: utils.GetStringPtr("bytes 2-4/15"),
|
||||
},
|
||||
},
|
||||
output: testOutput{
|
||||
response: &Response{
|
||||
Headers: map[string]*string{
|
||||
"ETag": nil,
|
||||
"x-amz-restore": nil,
|
||||
"accept-ranges": nil,
|
||||
"Content-Range": utils.GetStringPtr("bytes 2-4/15"),
|
||||
"Content-Disposition": nil,
|
||||
"Content-Encoding": nil,
|
||||
"Content-Language": nil,
|
||||
"Cache-Control": nil,
|
||||
"Expires": nil,
|
||||
"x-amz-checksum-crc32": nil,
|
||||
"x-amz-checksum-crc64nvme": nil,
|
||||
"x-amz-checksum-crc32c": nil,
|
||||
"x-amz-checksum-sha1": nil,
|
||||
"x-amz-checksum-sha256": nil,
|
||||
"x-amz-version-id": nil,
|
||||
"x-amz-mp-parts-count": nil,
|
||||
"x-amz-object-lock-mode": nil,
|
||||
"x-amz-object-lock-legal-hold": nil,
|
||||
"x-amz-storage-class": nil,
|
||||
"x-amz-checksum-type": nil,
|
||||
"x-amz-object-lock-retain-until-date": nil,
|
||||
"Last-Modified": nil,
|
||||
"x-amz-tagging-count": nil,
|
||||
"Content-Type": nil,
|
||||
"Content-Length": nil,
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -163,6 +164,11 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
// Set the metadata headers
|
||||
utils.SetMetaHeaders(ctx, res.Metadata)
|
||||
|
||||
status := http.StatusOK
|
||||
if res.ContentRange != nil && *res.ContentRange != "" {
|
||||
status = http.StatusPartialContent
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Headers: map[string]*string{
|
||||
"Content-Range": res.ContentRange,
|
||||
@@ -193,6 +199,7 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
Status: status,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -202,6 +203,51 @@ func TestS3ApiController_HeadObject(t *testing.T) {
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusOK,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful partial response",
|
||||
input: testInput{
|
||||
locals: defaultLocals,
|
||||
beRes: &s3.HeadObjectOutput{
|
||||
ContentRange: utils.GetStringPtr("bytes 10-20/100"),
|
||||
},
|
||||
},
|
||||
output: testOutput{
|
||||
response: &Response{
|
||||
Headers: map[string]*string{
|
||||
"ETag": nil,
|
||||
"x-amz-restore": nil,
|
||||
"accept-ranges": nil,
|
||||
"Content-Range": utils.GetStringPtr("bytes 10-20/100"),
|
||||
"Content-Disposition": nil,
|
||||
"Content-Encoding": nil,
|
||||
"Content-Language": nil,
|
||||
"Cache-Control": nil,
|
||||
"Expires": nil,
|
||||
"x-amz-checksum-crc32": nil,
|
||||
"x-amz-checksum-crc64nvme": nil,
|
||||
"x-amz-checksum-crc32c": nil,
|
||||
"x-amz-checksum-sha1": nil,
|
||||
"x-amz-checksum-sha256": nil,
|
||||
"x-amz-version-id": nil,
|
||||
"x-amz-mp-parts-count": nil,
|
||||
"x-amz-object-lock-mode": nil,
|
||||
"x-amz-object-lock-legal-hold": nil,
|
||||
"x-amz-storage-class": nil,
|
||||
"x-amz-checksum-type": nil,
|
||||
"x-amz-object-lock-retain-until-date": nil,
|
||||
"Last-Modified": nil,
|
||||
"x-amz-tagging-count": nil,
|
||||
"Content-Type": nil,
|
||||
"Content-Length": nil,
|
||||
},
|
||||
MetaOpts: &MetaOptions{
|
||||
BucketOwner: "root",
|
||||
Status: http.StatusPartialContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+324
-22
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
@@ -775,8 +776,9 @@ func GetObject_directory_success(s *S3Conf) error {
|
||||
func GetObject_by_range_resp_status(s *S3Conf) error {
|
||||
testName := "GetObject_by_range_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj, dLen := "my-obj", int64(4000)
|
||||
_, err := putObjectWithData(dLen, &s3.PutObjectInput{
|
||||
obj := "my-obj"
|
||||
objLength := int64(100)
|
||||
_, err := putObjectWithData(objLength, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
@@ -784,32 +786,119 @@ func GetObject_by_range_resp_status(s *S3Conf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{
|
||||
"Range": "bytes=100-200",
|
||||
},
|
||||
)
|
||||
checkRangeStatus := func(rng string, expectedStatus int) error {
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{"Range": rng},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != expectedStatus {
|
||||
return fmt.Errorf("range %q: expected status %d, instead got %d", rng, expectedStatus, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
rng string
|
||||
expectedStatus int
|
||||
}{
|
||||
// Invalid/ignored ranges → full object, no Content-Range → 200
|
||||
{"bytes=,", http.StatusOK},
|
||||
{"bytes= -1", http.StatusOK},
|
||||
{"bytes=--1", http.StatusOK},
|
||||
{"bytes=0 -1", http.StatusOK},
|
||||
{"bytes=0--1", http.StatusOK},
|
||||
{"bytes=10-5", http.StatusOK},
|
||||
{"bytes=abc", http.StatusOK},
|
||||
{"bytes=a-z", http.StatusOK},
|
||||
{"foo=0-1", http.StatusOK},
|
||||
{"bytes=abc-xyz", http.StatusOK},
|
||||
{"bytes=100-x", http.StatusOK},
|
||||
{"bytes=0-0,1-2", http.StatusOK},
|
||||
{fmt.Sprintf("bytes=%v-%v", objLength+2, objLength-100), http.StatusOK},
|
||||
|
||||
// Valid ranges → partial content, non-empty Content-Range → 206
|
||||
{"bytes=00-01", http.StatusPartialContent},
|
||||
{"bytes=-1", http.StatusPartialContent},
|
||||
{"bytes=-2", http.StatusPartialContent},
|
||||
{"bytes=-10", http.StatusPartialContent},
|
||||
{"bytes=-100", http.StatusPartialContent},
|
||||
{"bytes=-101", http.StatusPartialContent},
|
||||
{"bytes=0-0", http.StatusPartialContent},
|
||||
{"bytes=0-99", http.StatusPartialContent},
|
||||
{"bytes=0-100", http.StatusPartialContent},
|
||||
{"bytes=0-999999", http.StatusPartialContent},
|
||||
{"bytes=1-99", http.StatusPartialContent},
|
||||
{"bytes=50-99", http.StatusPartialContent},
|
||||
{"bytes=50-", http.StatusPartialContent},
|
||||
{"bytes=0-", http.StatusPartialContent},
|
||||
{"bytes=99-99", http.StatusPartialContent},
|
||||
} {
|
||||
if err := checkRangeStatus(tc.rng, tc.expectedStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_not_enabled_checksum_mode(s *S3Conf) error {
|
||||
testName := "GetObject_not_enabled_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha1,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, func(o *s3.Options) {
|
||||
// config sdk to not automatically set the `x-amz-checksum-mode: ENABLED` header
|
||||
o.RequestChecksumCalculation = aws.RequestChecksumCalculationUnset
|
||||
o.ResponseChecksumValidation = aws.ResponseChecksumValidationUnset
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1474,9 +1563,10 @@ func GetObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
cancel()
|
||||
return fmt.Errorf("expected ContentLength %d, got %v", objSize, res.ContentLength)
|
||||
}
|
||||
if getString(res.ContentRange) != "" {
|
||||
expectedCRange := fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize)
|
||||
if getString(res.ContentRange) != expectedCRange {
|
||||
cancel()
|
||||
return fmt.Errorf("expected empty Content-Range for non-multipart object, got %q", getString(res.ContentRange))
|
||||
return fmt.Errorf("expected Content-Range to be %s, instead got %s", expectedCRange, getString(res.ContentRange))
|
||||
}
|
||||
if res.PartsCount != nil {
|
||||
cancel()
|
||||
@@ -1500,3 +1590,215 @@ func GetObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_empty_object_part_number_1(s *S3Conf) error {
|
||||
testName := "GetObject_empty_object_part_number_1"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "empty-obj"
|
||||
|
||||
out, err := putObjectWithData(0, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ContentRange != nil {
|
||||
return fmt.Errorf("expected nil Content-Range for empty object with partNumber=1, got %q", *res.ContentRange)
|
||||
}
|
||||
if res.ContentLength == nil || *res.ContentLength != 0 {
|
||||
return fmt.Errorf("expected ContentLength 0, got %v", res.ContentLength)
|
||||
}
|
||||
if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) {
|
||||
return fmt.Errorf("expected sha256 checksum %v, got %v",
|
||||
getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_mp_part_number_resp_status(s *S3Conf) error {
|
||||
testName := "GetObject_mp_part_number_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
out, err := createMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodGet,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v?partNumber=1", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetObject_ranged_with_checksum_mode(s *S3Conf) error {
|
||||
testName := "GetObject_ranged_with_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
checkNoChecksums := func(res *s3.GetObjectOutput) error {
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sub-test 1: regular object with Range header and ChecksumMode enabled
|
||||
regularObj := "regular-obj"
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
Range: getPtr("bytes=0-99"),
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ranged GET on regular object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(res); err != nil {
|
||||
return fmt.Errorf("ranged GET on regular object: %w", err)
|
||||
}
|
||||
|
||||
// Sub-test 2: multipart object with partNumber and ChecksumMode enabled
|
||||
mpObj := "mp-obj"
|
||||
mpOut, err := createMp(s3client, bucket, mpObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, mpObj, *mpOut.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
UploadId: mpOut.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), longTimeout)
|
||||
mpRes, err := s3client.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("partNumber GET on MP object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(mpRes); err != nil {
|
||||
return fmt.Errorf("partNumber GET on MP object: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1097,8 +1097,9 @@ func HeadObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
if res.ContentLength == nil || *res.ContentLength != objSize {
|
||||
return fmt.Errorf("expected ContentLength %d, got %v", objSize, res.ContentLength)
|
||||
}
|
||||
if getString(res.ContentRange) != "" {
|
||||
return fmt.Errorf("expected empty Content-Range for non-multipart object, got %q", getString(res.ContentRange))
|
||||
expectedCRange := fmt.Sprintf("bytes 0-%d/%d", objSize-1, objSize)
|
||||
if getString(res.ContentRange) != expectedCRange {
|
||||
return fmt.Errorf("expected Content-Range to be %s, instead got %s", expectedCRange, getString(res.ContentRange))
|
||||
}
|
||||
if res.PartsCount != nil {
|
||||
return fmt.Errorf("expected nil PartsCount for non-multipart object, got %d", *res.PartsCount)
|
||||
@@ -1111,6 +1112,304 @@ func HeadObject_non_mp_part_number_1_success(s *S3Conf) error {
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_by_range_resp_status(s *S3Conf) error {
|
||||
testName := "HeadObject_by_range_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
objLength := int64(100)
|
||||
_, err := putObjectWithData(objLength, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkRangeStatus := func(rng string, expectedStatus int) error {
|
||||
req, err := createSignedReq(
|
||||
http.MethodHead,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
map[string]string{"Range": rng},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != expectedStatus {
|
||||
return fmt.Errorf("range %q: expected status %d, instead got %d", rng, expectedStatus, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
rng string
|
||||
expectedStatus int
|
||||
}{
|
||||
// Invalid/ignored ranges → full object, no Content-Range → 200
|
||||
{"bytes=,", http.StatusOK},
|
||||
{"bytes= -1", http.StatusOK},
|
||||
{"bytes=--1", http.StatusOK},
|
||||
{"bytes=0 -1", http.StatusOK},
|
||||
{"bytes=0--1", http.StatusOK},
|
||||
{"bytes=10-5", http.StatusOK},
|
||||
{"bytes=abc", http.StatusOK},
|
||||
{"bytes=a-z", http.StatusOK},
|
||||
{"foo=0-1", http.StatusOK},
|
||||
{"bytes=abc-xyz", http.StatusOK},
|
||||
{"bytes=100-x", http.StatusOK},
|
||||
{"bytes=0-0,1-2", http.StatusOK},
|
||||
{fmt.Sprintf("bytes=%v-%v", objLength+2, objLength-100), http.StatusOK},
|
||||
|
||||
// Valid ranges → partial content, non-empty Content-Range → 206
|
||||
{"bytes=00-01", http.StatusPartialContent},
|
||||
{"bytes=-1", http.StatusPartialContent},
|
||||
{"bytes=-2", http.StatusPartialContent},
|
||||
{"bytes=-10", http.StatusPartialContent},
|
||||
{"bytes=-100", http.StatusPartialContent},
|
||||
{"bytes=-101", http.StatusPartialContent},
|
||||
{"bytes=0-0", http.StatusPartialContent},
|
||||
{"bytes=0-99", http.StatusPartialContent},
|
||||
{"bytes=0-100", http.StatusPartialContent},
|
||||
{"bytes=0-999999", http.StatusPartialContent},
|
||||
{"bytes=1-99", http.StatusPartialContent},
|
||||
{"bytes=50-99", http.StatusPartialContent},
|
||||
{"bytes=50-", http.StatusPartialContent},
|
||||
{"bytes=0-", http.StatusPartialContent},
|
||||
{"bytes=99-99", http.StatusPartialContent},
|
||||
} {
|
||||
if err := checkRangeStatus(tc.rng, tc.expectedStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_empty_object_part_number_1(s *S3Conf) error {
|
||||
testName := "HeadObject_empty_object_part_number_1"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "empty-obj"
|
||||
|
||||
out, err := putObjectWithData(0, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.ContentRange != nil {
|
||||
return fmt.Errorf("expected nil Content-Range for empty object with partNumber=1, got %q", *res.ContentRange)
|
||||
}
|
||||
if res.ContentLength == nil || *res.ContentLength != 0 {
|
||||
return fmt.Errorf("expected ContentLength 0, got %v", res.ContentLength)
|
||||
}
|
||||
if getString(res.ChecksumSHA256) != getString(out.res.ChecksumSHA256) {
|
||||
return fmt.Errorf("expected sha256 checksum %v, got %v",
|
||||
getString(out.res.ChecksumSHA256), getString(res.ChecksumSHA256))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_mp_part_number_resp_status(s *S3Conf) error {
|
||||
testName := "HeadObject_mp_part_number_resp_status"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
obj := "my-obj"
|
||||
out, err := createMp(s3client, bucket, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, obj, *out.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &obj,
|
||||
UploadId: out.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := createSignedReq(
|
||||
http.MethodHead,
|
||||
s.endpoint,
|
||||
fmt.Sprintf("%v/%v?partNumber=1", bucket, obj),
|
||||
s.awsID,
|
||||
s.awsSecret,
|
||||
"s3",
|
||||
s.awsRegion,
|
||||
nil,
|
||||
time.Now(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("expected response status to be %v, instead got %v",
|
||||
http.StatusPartialContent, resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_ranged_with_checksum_mode(s *S3Conf) error {
|
||||
testName := "HeadObject_ranged_with_checksum_mode"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
checkNoChecksums := func(res *s3.HeadObjectOutput) error {
|
||||
if res.ChecksumCRC32 != nil {
|
||||
return fmt.Errorf("expected nil crc32 checksum, instead got %v", *res.ChecksumCRC32)
|
||||
}
|
||||
if res.ChecksumCRC32C != nil {
|
||||
return fmt.Errorf("expected nil crc32c checksum, instead got %v", *res.ChecksumCRC32C)
|
||||
}
|
||||
if res.ChecksumSHA1 != nil {
|
||||
return fmt.Errorf("expected nil sha1 checksum, instead got %v", *res.ChecksumSHA1)
|
||||
}
|
||||
if res.ChecksumSHA256 != nil {
|
||||
return fmt.Errorf("expected nil sha256 checksum, instead got %v", *res.ChecksumSHA256)
|
||||
}
|
||||
if res.ChecksumCRC64NVME != nil {
|
||||
return fmt.Errorf("expected nil crc64nvme checksum, instead got %v", *res.ChecksumCRC64NVME)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sub-test 1: regular object with Range header and ChecksumMode enabled
|
||||
regularObj := "regular-obj"
|
||||
_, err := putObjectWithData(500, &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
ChecksumAlgorithm: types.ChecksumAlgorithmSha256,
|
||||
}, s3client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: ®ularObj,
|
||||
Range: getPtr("bytes=0-99"),
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ranged HEAD on regular object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(res); err != nil {
|
||||
return fmt.Errorf("ranged HEAD on regular object: %w", err)
|
||||
}
|
||||
|
||||
// Sub-test 2: multipart object with partNumber and ChecksumMode enabled
|
||||
mpObj := "mp-obj"
|
||||
mpOut, err := createMp(s3client, bucket, mpObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const partCount = int64(2)
|
||||
parts, _, err := uploadParts(s3client, partCount*5*1024*1024, partCount, bucket, mpObj, *mpOut.UploadId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compParts := make([]types.CompletedPart, len(parts))
|
||||
for i, p := range parts {
|
||||
compParts[i] = types.CompletedPart{
|
||||
ETag: p.ETag,
|
||||
PartNumber: p.PartNumber,
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err = s3client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
UploadId: mpOut.UploadId,
|
||||
MultipartUpload: &types.CompletedMultipartUpload{
|
||||
Parts: compParts,
|
||||
},
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pn := int32(1)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
|
||||
mpRes, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &mpObj,
|
||||
PartNumber: &pn,
|
||||
ChecksumMode: types.ChecksumModeEnabled,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("partNumber HEAD on MP object: %w", err)
|
||||
}
|
||||
if err := checkNoChecksums(mpRes); err != nil {
|
||||
return fmt.Errorf("partNumber HEAD on MP object: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HeadObject_overrides_fail_public(s *S3Conf) error {
|
||||
testName := "HeadObject_overrides_fail_public"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
|
||||
@@ -205,6 +205,7 @@ func TestHeadObject(ts *TestState) {
|
||||
ts.Run(HeadObject_non_existing_dir_object)
|
||||
ts.Run(HeadObject_invalid_parent_dir)
|
||||
ts.Run(HeadObject_with_range)
|
||||
ts.Run(HeadObject_by_range_resp_status)
|
||||
ts.Run(HeadObject_zero_len_with_range)
|
||||
ts.Run(HeadObject_dir_with_range)
|
||||
ts.Run(HeadObject_conditional_reads)
|
||||
@@ -212,6 +213,7 @@ func TestHeadObject(ts *TestState) {
|
||||
if !ts.conf.azureTests {
|
||||
ts.Run(HeadObject_not_enabled_checksum_mode)
|
||||
ts.Run(HeadObject_checksums)
|
||||
ts.Run(HeadObject_ranged_with_checksum_mode)
|
||||
}
|
||||
ts.Run(HeadObject_success)
|
||||
ts.Run(HeadObject_overrides_success)
|
||||
@@ -220,7 +222,9 @@ func TestHeadObject(ts *TestState) {
|
||||
ts.Run(HeadObject_range_and_part_number)
|
||||
ts.Run(HeadObject_mp_part_number_exceeds_parts_count)
|
||||
ts.Run(HeadObject_mp_part_number_success)
|
||||
ts.Run(HeadObject_mp_part_number_resp_status)
|
||||
ts.Run(HeadObject_non_mp_part_number_1_success)
|
||||
ts.Run(HeadObject_empty_object_part_number_1)
|
||||
}
|
||||
|
||||
func TestGetObjectAttributes(ts *TestState) {
|
||||
@@ -248,8 +252,10 @@ func TestGetObject(ts *TestState) {
|
||||
ts.Run(GetObject_conditional_reads)
|
||||
//TODO: remove the condition after implementing checksums in azure
|
||||
if !ts.conf.azureTests {
|
||||
ts.Run(GetObject_not_enabled_checksum_mode)
|
||||
ts.Run(GetObject_checksums)
|
||||
ts.Run(GetObject_dir_object_checksum)
|
||||
ts.Run(GetObject_ranged_with_checksum_mode)
|
||||
}
|
||||
ts.Run(GetObject_success)
|
||||
ts.Run(GetObject_directory_success)
|
||||
@@ -262,7 +268,9 @@ func TestGetObject(ts *TestState) {
|
||||
ts.Run(GetObject_range_and_part_number)
|
||||
ts.Run(GetObject_mp_part_number_exceeds_parts_count)
|
||||
ts.Run(GetObject_mp_part_number_success)
|
||||
ts.Run(GetObject_mp_part_number_resp_status)
|
||||
ts.Run(GetObject_non_mp_part_number_1_success)
|
||||
ts.Run(GetObject_empty_object_part_number_1)
|
||||
}
|
||||
|
||||
func TestListObjects(ts *TestState) {
|
||||
@@ -1395,11 +1403,13 @@ func GetIntTests() IntTests {
|
||||
"HeadObject_name_too_long": HeadObject_name_too_long,
|
||||
"HeadObject_invalid_parent_dir": HeadObject_invalid_parent_dir,
|
||||
"HeadObject_with_range": HeadObject_with_range,
|
||||
"HeadObject_by_range_resp_status": HeadObject_by_range_resp_status,
|
||||
"HeadObject_zero_len_with_range": HeadObject_zero_len_with_range,
|
||||
"HeadObject_dir_with_range": HeadObject_dir_with_range,
|
||||
"HeadObject_conditional_reads": HeadObject_conditional_reads,
|
||||
"HeadObject_not_enabled_checksum_mode": HeadObject_not_enabled_checksum_mode,
|
||||
"HeadObject_checksums": HeadObject_checksums,
|
||||
"HeadObject_ranged_with_checksum_mode": HeadObject_ranged_with_checksum_mode,
|
||||
"HeadObject_success": HeadObject_success,
|
||||
"HeadObject_overrides_success": HeadObject_overrides_success,
|
||||
"HeadObject_overrides_presign_success": HeadObject_overrides_presign_success,
|
||||
@@ -1407,7 +1417,9 @@ func GetIntTests() IntTests {
|
||||
"HeadObject_range_and_part_number": HeadObject_range_and_part_number,
|
||||
"HeadObject_mp_part_number_exceeds_parts_count": HeadObject_mp_part_number_exceeds_parts_count,
|
||||
"HeadObject_mp_part_number_success": HeadObject_mp_part_number_success,
|
||||
"HeadObject_mp_part_number_resp_status": HeadObject_mp_part_number_resp_status,
|
||||
"HeadObject_non_mp_part_number_1_success": HeadObject_non_mp_part_number_1_success,
|
||||
"HeadObject_empty_object_part_number_1": HeadObject_empty_object_part_number_1,
|
||||
"GetObjectAttributes_non_existing_bucket": GetObjectAttributes_non_existing_bucket,
|
||||
"GetObjectAttributes_non_existing_object": GetObjectAttributes_non_existing_object,
|
||||
"GetObjectAttributes_invalid_attrs": GetObjectAttributes_invalid_attrs,
|
||||
@@ -1424,8 +1436,10 @@ func GetIntTests() IntTests {
|
||||
"GetObject_invalid_parent": GetObject_invalid_parent,
|
||||
"GetObject_large_object": GetObject_large_object,
|
||||
"GetObject_conditional_reads": GetObject_conditional_reads,
|
||||
"GetObject_not_enabled_checksum_mode": GetObject_not_enabled_checksum_mode,
|
||||
"GetObject_checksums": GetObject_checksums,
|
||||
"GetObject_dir_object_checksum": GetObject_dir_object_checksum,
|
||||
"GetObject_ranged_with_checksum_mode": GetObject_ranged_with_checksum_mode,
|
||||
"GetObject_success": GetObject_success,
|
||||
"GetObject_directory_success": GetObject_directory_success,
|
||||
"GetObject_by_range_resp_status": GetObject_by_range_resp_status,
|
||||
@@ -1437,7 +1451,9 @@ func GetIntTests() IntTests {
|
||||
"GetObject_range_and_part_number": GetObject_range_and_part_number,
|
||||
"GetObject_mp_part_number_exceeds_parts_count": GetObject_mp_part_number_exceeds_parts_count,
|
||||
"GetObject_mp_part_number_success": GetObject_mp_part_number_success,
|
||||
"GetObject_mp_part_number_resp_status": GetObject_mp_part_number_resp_status,
|
||||
"GetObject_non_mp_part_number_1_success": GetObject_non_mp_part_number_1_success,
|
||||
"GetObject_empty_object_part_number_1": GetObject_empty_object_part_number_1,
|
||||
"ListObjects_non_existing_bucket": ListObjects_non_existing_bucket,
|
||||
"ListObjects_with_prefix": ListObjects_with_prefix,
|
||||
"ListObjects_truncated": ListObjects_truncated,
|
||||
|
||||
Reference in New Issue
Block a user