fix: reject an empty start-after and continuation-token in ListObjects (V1)

This commit is contained in:
dualfroz committed 2026-10-12 14:30:16 +02:00
1 parent 613c71290f
commit 276128b42b
4 files changed
+74 -7

No files matched your search

+11 -2
View File
@@ -615,8 +615,17 @@ func (c S3ApiController) ListObjects(ctx fiber.Ctx) (*Response, error) {
region = defaultRegion region = defaultRegion
} }
// start-after is a ListObjectsV2 argument, S3 rejects it on a V1 listing // continuation-token and start-after are ListObjectsV2 arguments, S3
if ctx.Query("start-after") != "" { // rejects either one on a V1 listing, with or without a value
queryArgs := ctx.Request().URI().QueryArgs()
if queryArgs.Has("continuation-token") {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, s3err.GetInvalidArgumentErr(s3err.InvalidArgContinuationToken, "")
}
if queryArgs.Has("start-after") {
return &Response{ return &Response{
MetaOpts: &MetaOptions{ MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner, BucketOwner: parsedAcl.Owner,
+36
View File
@@ -1213,6 +1213,42 @@ func TestS3ApiController_ListObjects(t *testing.T) {
err: s3err.GetInvalidArgumentErr(s3err.InvalidArgStartAfter, ""), err: s3err.GetInvalidArgumentErr(s3err.InvalidArgStartAfter, ""),
}, },
}, },
{
name: "empty start-after is not supported",
input: testInput{
locals: defaultLocals,
beRes: listResult,
queries: map[string]string{
"start-after": "",
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetInvalidArgumentErr(s3err.InvalidArgStartAfter, ""),
},
},
{
name: "continuation-token is not supported",
input: testInput{
locals: defaultLocals,
beRes: listResult,
queries: map[string]string{
"continuation-token": "my-token",
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetInvalidArgumentErr(s3err.InvalidArgContinuationToken, ""),
},
},
{ {
name: "invalid max keys", name: "invalid max keys",
input: testInput{ input: testInput{
+5
View File
@@ -27,6 +27,7 @@ const (
InvalidArgMaxBuckets InvalidArgErrorCode = iota InvalidArgMaxBuckets InvalidArgErrorCode = iota
InvalidArgNegativeMaxKeys InvalidArgNegativeMaxKeys
InvalidArgStartAfter InvalidArgStartAfter
InvalidArgContinuationToken
InvalidArgObjectAttributes InvalidArgObjectAttributes
InvalidArgPartNumber InvalidArgPartNumber
InvalidArgCompleteMpPartNumber InvalidArgCompleteMpPartNumber
@@ -95,6 +96,10 @@ var invalidArgErrResponses = map[InvalidArgErrorCode]InvalidArgumentError{
Description: "startAfter only supported in REST.GET.BUCKET with list-type=2", Description: "startAfter only supported in REST.GET.BUCKET with list-type=2",
ArgumentName: "start-after", ArgumentName: "start-after",
}, },
InvalidArgContinuationToken: {
Description: "continuation-token only supported in REST.GET.BUCKET with list-type=2",
ArgumentName: "continuation-token",
},
InvalidArgObjectAttributes: { InvalidArgObjectAttributes: {
Description: "Invalid attribute name specified.", Description: "Invalid attribute name specified.",
ArgumentName: "x-amz-object-attributes", ArgumentName: "x-amz-object-attributes",
+22 -5
View File
@@ -195,9 +195,9 @@ func ListObjects_invalid_max_keys(s *S3Conf) error {
}) })
} }
// start-after belongs to ListObjectsV2, so S3 rejects it on a V1 listing // start-after and continuation-token belong to ListObjectsV2, so S3 rejects
// instead of ignoring it. The SDK ListObjects input has no StartAfter field, // either one on a V1 listing, with or without a value. The SDK ListObjects
// so the request is signed and sent by hand. // input has neither field, so the requests are signed and sent by hand.
func ListObjects_start_after(s *S3Conf) error { func ListObjects_start_after(s *S3Conf) error {
testName := "ListObjects_start_after" testName := "ListObjects_start_after"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
@@ -206,8 +206,20 @@ func ListObjects_start_after(s *S3Conf) error {
return err return err
} }
startAfterErr := s3err.GetInvalidArgumentErr(s3err.InvalidArgStartAfter, "")
contTokenErr := s3err.GetInvalidArgumentErr(s3err.InvalidArgContinuationToken, "")
for _, test := range []struct {
query string
expected s3err.S3Error
}{
{"start-after=bar", startAfterErr},
{"start-after=", startAfterErr},
{"start-after", startAfterErr},
{"continuation-token=bar", contTokenErr},
{"continuation-token=", contTokenErr},
} {
req, err := createSignedReq(http.MethodGet, s.endpoint, req, err := createSignedReq(http.MethodGet, s.endpoint,
fmt.Sprintf("%v?start-after=bar", bucket), fmt.Sprintf("%v?%v", bucket, test.query),
s.awsID, s.awsSecret, "s3", s.awsRegion, "", nil, time.Now(), nil) s.awsID, s.awsSecret, "s3", s.awsRegion, "", nil, time.Now(), nil)
if err != nil { if err != nil {
return err return err
@@ -218,7 +230,12 @@ func ListObjects_start_after(s *S3Conf) error {
return err return err
} }
return checkHTTPResponseApiErr(resp, s3err.GetInvalidArgumentErr(s3err.InvalidArgStartAfter, "")) if err := checkHTTPResponseApiErr(resp, test.expected); err != nil {
return fmt.Errorf("%v: %w", test.query, err)
}
}
return nil
}) })
} }