Merge pull request #1931 from versity/sis/object-metadata-limits

fix: add request headers and metadata headers limit
This commit is contained in:
Ben McClelland
2026-03-06 13:17:14 -08:00
committed by GitHub
13 changed files with 462 additions and 28 deletions
+9 -1
View File
@@ -154,7 +154,6 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error
contentEncoding := ctx.Get("Content-Encoding")
tagging := ctx.Get("X-Amz-Tagging")
expires := ctx.Get("Expires")
metadata := utils.GetUserMetaData(&ctx.Request().Header)
// context locals
acct := utils.ContextKeyAccount.Get(ctx).(auth.Account)
isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool)
@@ -180,6 +179,15 @@ func (c S3ApiController) CreateMultipartUpload(ctx *fiber.Ctx) (*Response, error
}, err
}
metadata, err := utils.GetUserMetaData(&ctx.Request().Header)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
objLockState, err := utils.ParsObjectLockHdrs(ctx)
if err != nil {
return &Response{
+18
View File
@@ -18,6 +18,7 @@ import (
"bufio"
"context"
"encoding/xml"
"strings"
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3"
@@ -235,6 +236,23 @@ func TestS3ApiController_CreateMultipartUpload(t *testing.T) {
err: s3err.GetAPIError(s3err.ErrAccessDenied),
},
},
{
name: "invalid metadata header",
input: testInput{
locals: defaultLocals,
headers: map[string]string{
"x-amz-meta-key": strings.Repeat("a", 2048),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMetadataTooLarge),
},
},
{
name: "invalid object lock headers",
input: testInput{
+18 -4
View File
@@ -551,7 +551,14 @@ func (c S3ApiController) CopyObject(ctx *fiber.Ctx) (*Response, error) {
}, s3err.GetAPIError(s3err.ErrNonEmptyRequestBody)
}
metadata := utils.GetUserMetaData(&ctx.Request().Header)
metadata, err := utils.GetUserMetaData(&ctx.Request().Header)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
if metaDirective != "" && metaDirective != types.MetadataDirectiveCopy && metaDirective != types.MetadataDirectiveReplace {
debuglogger.Logf("invalid metadata directive: %v", metaDirective)
@@ -676,9 +683,6 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
contentLengthStr = decodedLength
}
// load the meta headers
metadata := utils.GetUserMetaData(&ctx.Request().Header)
err := auth.VerifyAccess(ctx.Context(), c.be,
auth.AccessOptions{
Readonly: c.readonly,
@@ -700,6 +704,16 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) {
}, err
}
// load the meta headers
metadata, err := utils.GetUserMetaData(&ctx.Request().Header)
if err != nil {
return &Response{
MetaOpts: &MetaOptions{
BucketOwner: parsedAcl.Owner,
},
}, err
}
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []types.ObjectIdentifier{{Key: &key}}, true, IsBucketPublic, c.be, true)
if err != nil {
return &Response{
+36
View File
@@ -963,6 +963,24 @@ func TestS3ApiController_CopyObject(t *testing.T) {
err: s3err.GetAPIError(s3err.ErrNonEmptyRequestBody),
},
},
{
name: "invalid metadata header",
input: testInput{
locals: defaultLocals,
headers: map[string]string{
"X-Amz-Copy-Source": "bucket/object",
"x-amz-meta-key": strings.Repeat("b", 2048),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMetadataTooLarge),
},
},
{
name: "invalid metadata directive",
input: testInput{
@@ -1208,6 +1226,24 @@ func TestS3ApiController_PutObject(t *testing.T) {
err: s3err.GetAPIError(s3err.ErrInvalidRequest),
},
},
{
name: "invalid metadata header",
input: testInput{
locals: defaultLocals,
extraMockErr: s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound),
headers: map[string]string{
"x-amz-meta-something": strings.Repeat("c", 2050),
},
},
output: testOutput{
response: &Response{
MetaOpts: &MetaOptions{
BucketOwner: "root",
},
},
err: s3err.GetAPIError(s3err.ErrMetadataTooLarge),
},
},
{
name: "invalid object lock headers",
input: testInput{
+11
View File
@@ -91,6 +91,10 @@ func New(
DisableStartupMessage: true,
ErrorHandler: globalErrorHandler,
Concurrency: server.maxConnections,
// Sets buffer limit to read/parse incoming requests
// if the limit is reached, fiber/fasthttp will throw an error
// in the global error handler
ReadBufferSize: 8 * 1024, // 8 KB
})
server.app = app
@@ -259,13 +263,20 @@ func globalErrorHandler(ctx *fiber.Ctx, er error) error {
// handle the fiber specific errors
var fiberErr *fiber.Error
if errors.As(er, &fiberErr) {
if errors.Is(fiberErr, fiber.ErrRequestHeaderFieldsTooLarge) {
debuglogger.Logf("total request headers size exceeds the allowed 8KB")
ctx.Status(http.StatusBadRequest)
return nil
}
if strings.Contains(fiberErr.Message, "cannot parse Content-Length") {
debuglogger.Logf("failed to parse Content-Length")
ctx.Status(http.StatusBadRequest)
return nil
}
if strings.Contains(fiberErr.Message, "error when reading request headers") {
// This error means fiber failed to parse the incoming request
// which is a malfoedmed one. Return a BadRequest in this case
debuglogger.Logf("failed to parse the http request")
err := s3err.GetAPIError(s3err.ErrCannotParseHTTPRequest)
ctx.Status(err.HTTPStatusCode)
return ctx.Send(s3err.GetAPIErrorResponse(err, "", "", ""))
+37 -5
View File
@@ -55,20 +55,52 @@ func SetBucketNameValidationStrict(strict bool) {
strictBucketNameValidation.Store(strict)
}
func GetUserMetaData(headers *fasthttp.RequestHeader) (metadata map[string]string) {
metadata = make(map[string]string)
// maximum allowed size (2 KB) for all user-defined
// object metadata combined, excluded the 'x-amz-meta-' prefix
const maxMetadataSize = 2048
// GetUserMetaData extracts user metadata from headers with the "x-amz-meta-" prefix.
// Keys are normalized to lowercase and duplicate headers are merged as
// comma-separated values. The total metadata size is validated against the
// 2 KB S3 limit.
func GetUserMetaData(headers *fasthttp.RequestHeader) (map[string]string, error) {
metadata := make(map[string]string)
var metadataSize int
headers.DisableNormalizing()
defer headers.EnableNormalizing()
for key, value := range headers.AllInOrder() {
hKey := string(key)
if strings.HasPrefix(strings.ToLower(hKey), "x-amz-meta-") {
trimmedKey := strings.ToLower(hKey[11:])
headerValue := string(value)
metadata[trimmedKey] = headerValue
if existingVal, ok := metadata[trimmedKey]; ok {
// S3 combines multiple metadata headers with the same key.
// For example:
// x-amz-meta-Key: value1
// x-amz-meta-kEy: value2
// x-amz-meta-keY: value3
//
// These are merged into a single lowercased key with the values
// joined as a comma-separated list:
// key: value1,value2,value3
metadataSize += len(headerValue) + 1
metadata[trimmedKey] = existingVal + "," + headerValue
} else {
metadataSize += len(trimmedKey) + len(headerValue)
metadata[trimmedKey] = headerValue
}
if metadataSize > maxMetadataSize {
debuglogger.Logf("total meta headers size exceeded the maximum allowed: (size): %v, (max): %v", metadataSize, maxMetadataSize)
return nil, s3err.GetAPIError(s3err.ErrMetadataTooLarge)
}
}
}
headers.EnableNormalizing()
return
return metadata, nil
}
func createHttpRequestFromCtx(ctx *fiber.Ctx, signedHdrs []string, contentLength int64, streamBody bool) (*http.Request, error) {
+119 -18
View File
@@ -15,12 +15,14 @@
package utils
import (
"bufio"
"bytes"
"encoding/xml"
"errors"
"math/rand"
"net/http"
"reflect"
"strings"
"testing"
"time"
@@ -94,35 +96,134 @@ func TestCreateHttpRequestFromCtx(t *testing.T) {
}
}
func TestGetUserMetaData(t *testing.T) {
type args struct {
headers *fasthttp.RequestHeader
// a helper method to construct a raw http request with the given http request headers
// to further parse with fasthttp.Request.Read and return fasthttp.RequestHeader
func createHeadersFromRawRequest(t *testing.T, hdrs [][2]string) *fasthttp.RequestHeader {
t.Helper()
var b strings.Builder
b.WriteString("PUT / HTTP/1.1\r\n")
b.WriteString("Host: example.com\r\n")
for _, kv := range hdrs {
b.WriteString(kv[0])
b.WriteString(": ")
b.WriteString(kv[1])
b.WriteString("\r\n")
}
b.WriteString("\r\n")
app := fiber.New()
// Case 1
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
req := ctx.Request()
var req fasthttp.Request
if err := req.Read(bufio.NewReader(bytes.NewReader([]byte(b.String())))); err != nil {
t.Fatalf("failed to parse raw request: %v", err)
}
return &req.Header
}
func TestGetUserMetaData(t *testing.T) {
tests := []struct {
name string
args args
wantMetadata map[string]string
name string
hdrs [][2]string
want map[string]string
wantErr error
}{
{
name: "Success-empty-response",
args: args{
headers: &req.Header,
name: "no metadata headers",
hdrs: [][2]string{
{"Content-Type", "application/json"},
},
want: map[string]string{},
},
{
name: "single metadata header",
hdrs: [][2]string{
{"x-amz-meta-foo", "bar"},
},
want: map[string]string{"foo": "bar"},
},
{
name: "multiple metadata headers",
hdrs: [][2]string{
{"x-amz-meta-foo", "bar"},
{"x-amz-meta-baz", "qux"},
},
want: map[string]string{"foo": "bar", "baz": "qux"},
},
{
name: "case-insensitive prefix and key lowercasing",
hdrs: [][2]string{
{"X-Amz-Meta-TestKey", "Value"},
},
want: map[string]string{"testkey": "Value"},
},
{
name: "ignores non-metadata headers",
hdrs: [][2]string{
{"authorization", "token"},
{"x-amz-meta-foo", "bar"},
},
want: map[string]string{"foo": "bar"},
},
{
name: "metadata size exceeds limit (single header)",
hdrs: [][2]string{
{"x-amz-meta-big", strings.Repeat("a", maxMetadataSize+1)},
},
wantErr: s3err.GetAPIError(s3err.ErrMetadataTooLarge),
},
{
name: "metadata cumulative size exceeds limit (multiple headers)",
hdrs: [][2]string{
{"x-amz-meta-a", strings.Repeat("a", maxMetadataSize/2)},
{"x-amz-meta-b", strings.Repeat("b", maxMetadataSize/2+10)},
},
wantErr: s3err.GetAPIError(s3err.ErrMetadataTooLarge),
},
{
name: "duplicate keys combined",
hdrs: [][2]string{
{"x-amz-meta-Foo", "first"},
{"x-amz-meta-foo", "second"},
},
want: map[string]string{"foo": "first,second"},
},
{
name: "duplicate same value keys combined",
hdrs: [][2]string{
{"x-amz-meta-Foo", "value"},
{"x-amz-meta-foo", "value"},
},
want: map[string]string{"foo": "value,value"},
},
{
name: "mixed keys",
hdrs: [][2]string{
{"x-amz-meta-Foo", "value2"},
{"x-amz-meta-fOo", "value1"},
{"x-amz-meta-foO", "value3"},
{"x-amz-meta-bar", "baz"},
{"x-amz-meta-quxx", "efg"},
{"x-amz-meta-abc", "value"},
{"x-amz-meta-Abc", "value"},
{"x-amz-meta-aBc", "value"},
{"x-amz-meta-abC", "value"},
{"x-amz-meta-ABC", "value"},
},
want: map[string]string{
"foo": "value2,value1,value3",
"bar": "baz",
"quxx": "efg",
"abc": "value,value,value,value,value",
},
wantMetadata: map[string]string{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotMetadata := GetUserMetaData(tt.args.headers); !reflect.DeepEqual(gotMetadata, tt.wantMetadata) {
t.Errorf("GetUserMetaData() = %v, want %v", gotMetadata, tt.wantMetadata)
}
h := createHeadersFromRawRequest(t, tt.hdrs)
got, err := GetUserMetaData(h)
assert.Equal(t, tt.wantErr, err)
assert.Equal(t, tt.want, got)
})
}
}
+7
View File
@@ -185,6 +185,7 @@ const (
ErrMalformedTrailer
ErrInvalidChunkSize
ErrSlowDown
ErrMetadataTooLarge
// Non-AWS errors
ErrExistingObjectIsDirectory
@@ -836,6 +837,12 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "Please reduce your request rate.",
HTTPStatusCode: http.StatusServiceUnavailable,
},
ErrMetadataTooLarge: {
// TODO: should have 'Size' and 'MaxSizeAllowed' properties
Code: "MetadataTooLarge",
Description: "Your metadata headers exceed the maximum allowed metadata size",
HTTPStatusCode: http.StatusBadRequest,
},
// non aws errors
ErrExistingObjectIsDirectory: {
+27
View File
@@ -424,6 +424,33 @@ func CopyObject_to_itself_with_new_metadata(s *S3Conf) error {
})
}
func CopyObject_long_metadata(s *S3Conf) error {
testName := "CopyObject_long_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
srcObj, dstObj := "source-object", "destination-object"
_, err := putObjectWithData(9, &s3.PutObjectInput{
Bucket: &bucket,
Key: &srcObj,
}, s3client)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{
Bucket: &bucket,
Key: &dstObj,
CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)),
Metadata: map[string]string{
strings.Repeat("s", 2048): "value",
},
})
cancel()
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMetadataTooLarge))
})
}
func CopyObject_copy_source_starting_with_slash(s *S3Conf) error {
testName := "CopyObject_CopySource_starting_with_slash"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
@@ -38,6 +38,24 @@ func CreateMultipartUpload_non_existing_bucket(s *S3Conf) error {
})
}
func CreateMultipartUpload_long_metadata(s *S3Conf) error {
testName := "CreateMultipartUpload_long_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
_, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
Bucket: &bucket,
Key: getPtr("obj"),
Metadata: map[string]string{
"key": "value",
"key2": strings.Repeat("b", 2040),
},
})
cancel()
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMetadataTooLarge))
})
}
func CreateMultipartUpload_with_metadata(s *S3Conf) error {
testName := "CreateMultipartUpload_with_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+89
View File
@@ -23,6 +23,7 @@ import (
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/versity/versitygw/s3err"
@@ -466,6 +467,94 @@ func PutObject_conditional_writes(s *S3Conf) error {
})
}
func PutObject_should_combine_metadata(s *S3Conf) error {
testName := "PutObject_should_combine_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
obj := "my-object"
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("%s/%s/%s", s.endpoint, bucket, obj), strings.NewReader("dummy data"))
if err != nil {
return err
}
req.Header.Set("x-amz-content-sha256", "UNSIGNED-PAYLOAD")
req.Header.Add("x-amz-meta-key", "value-1")
req.Header.Add("x-amz-meta-key", "value-2")
req.Header.Add("x-amz-meta-key", "value-3")
req.Header.Add("x-amz-meta-Key", "value-4")
req.Header.Add("x-amz-meta-keY", "value-5")
req.Header.Add("x-amz-meta-foo", "bar")
req.Header.Add("x-amz-meta-baz", "abc")
req.Header.Add("x-amz-meta-xyzz", "xxx-3")
req.Header.Add("x-amz-meta-Xyzz", "xxx-1")
req.Header.Add("x-amz-meta-xyzz", "xxx-2")
req.Header.Add("x-amz-meta-hello", "world")
req.Header.Add("x-amz-meta-boo", "bar")
req.Header.Add("x-amz-meta-asdf", "ghk")
signer := v4.NewSigner()
err = signer.SignHTTP(req.Context(), aws.Credentials{AccessKeyID: s.awsID, SecretAccessKey: s.awsSecret}, req, "UNSIGNED-PAYLOAD", "s3", s.awsRegion, time.Now())
if err != nil {
return fmt.Errorf("failed to sign the request: %w", err)
}
resp, err := s.httpClient.Do(req)
cancel()
if err != nil {
return fmt.Errorf("send request: %w", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("expected the response status code to be %v, instead got %v", http.StatusOK, resp.StatusCode)
}
ctx, cancel = context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &obj,
})
cancel()
if err != nil {
return err
}
expectedMeta := map[string]string{
"key": "value-1,value-2,value-3,value-4,value-5",
"foo": "bar",
"baz": "abc",
"xyzz": "xxx-3,xxx-1,xxx-2",
"hello": "world",
"boo": "bar",
"asdf": "ghk",
}
if !areMapsSame(expectedMeta, out.Metadata) {
return fmt.Errorf("expected the object metadata to be %v, instead got %v", expectedMeta, out.Metadata)
}
return nil
})
}
func PutObject_long_metadata(s *S3Conf) error {
testName := "PutObject_long_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
_, err := putObjectWithData(10, &s3.PutObjectInput{
Bucket: &bucket,
Key: getPtr("obj"),
Metadata: map[string]string{
"key": "value",
"foo": "bar",
"baz": "quxx",
"something_long": strings.Repeat("a", 2048),
},
}, s3client)
return checkApiErr(err, s3err.GetAPIError(s3err.ErrMetadataTooLarge))
})
}
func PutObject_with_metadata(s *S3Conf) error {
testName := "PutObject_with_metadata"
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
+15
View File
@@ -171,6 +171,7 @@ func TestPutObject(ts *TestState) {
ts.Run(PutObject_past_retain_until_date)
ts.Run(PutObject_invalid_retain_until_date)
ts.Run(PutObject_conditional_writes)
ts.Run(PutObject_should_combine_metadata)
//TODO: remove the condition after implementing checksums in azure
if !ts.conf.azureTests {
ts.Run(PutObject_checksum_algorithm_and_header_mismatch)
@@ -194,6 +195,7 @@ func TestPutObject(ts *TestState) {
ts.Run(PutObject_invalid_credentials)
ts.Run(PutObject_invalid_object_names)
ts.Run(PutObject_object_acl_not_supported)
ts.Run(PutObject_long_metadata)
}
func TestHeadObject(ts *TestState) {
@@ -331,6 +333,7 @@ func TestCopyObject(ts *TestState) {
ts.Run(CopyObject_should_copy_tagging)
ts.Run(CopyObject_invalid_tagging_directive)
ts.Run(CopyObject_to_itself_with_new_metadata)
ts.Run(CopyObject_long_metadata)
ts.Run(CopyObject_copy_source_starting_with_slash)
ts.Run(CopyObject_invalid_copy_source)
ts.Run(CopyObject_non_existing_dir_object)
@@ -383,6 +386,7 @@ func TestDeleteObjectTagging(ts *TestState) {
func TestCreateMultipartUpload(ts *TestState) {
ts.Run(CreateMultipartUpload_non_existing_bucket)
ts.Run(CreateMultipartUpload_long_metadata)
ts.Run(CreateMultipartUpload_with_metadata)
ts.Run(CreateMultipartUpload_with_tagging)
ts.Run(CreateMultipartUpload_with_object_lock)
@@ -847,6 +851,7 @@ func TestFullFlow(ts *TestState) {
TestVersioning(ts)
}
TestIAM(ts)
TestServer(ts)
}
func TestPosix(ts *TestState) {
@@ -1135,6 +1140,11 @@ func TestRouter(ts *TestState) {
ts.Run(RouterListVersionsWithKey)
}
func TestServer(ts *TestState) {
// TestServer tests s3 api server specific behaviors
ts.Run(Server_large_http_header)
}
func TestUnsignedStreaminPayloadTrailer(ts *TestState) {
// azure doesn't support checksums
if !ts.conf.azureTests {
@@ -1241,6 +1251,8 @@ func GetIntTests() IntTests {
"PutObject_past_retain_until_date": PutObject_past_retain_until_date,
"PutObject_invalid_retain_until_date": PutObject_invalid_retain_until_date,
"PutObject_conditional_writes": PutObject_conditional_writes,
"PutObject_should_combine_metadata": PutObject_should_combine_metadata,
"PutObject_long_metadata": PutObject_long_metadata,
"PutObject_with_metadata": PutObject_with_metadata,
"PutObject_invalid_credentials": PutObject_invalid_credentials,
"PutObject_checksum_algorithm_and_header_mismatch": PutObject_checksum_algorithm_and_header_mismatch,
@@ -1410,6 +1422,7 @@ func GetIntTests() IntTests {
"CopyObject_should_replace_tagging": CopyObject_should_replace_tagging,
"CopyObject_should_copy_tagging": CopyObject_should_copy_tagging,
"CopyObject_invalid_tagging_directive": CopyObject_invalid_tagging_directive,
"CopyObject_long_metadata": CopyObject_long_metadata,
"CopyObject_to_itself_with_new_metadata": CopyObject_to_itself_with_new_metadata,
"CopyObject_copy_source_starting_with_slash": CopyObject_copy_source_starting_with_slash,
"CopyObject_invalid_copy_source": CopyObject_invalid_copy_source,
@@ -1447,6 +1460,7 @@ func GetIntTests() IntTests {
"DeleteObjectTagging_success": DeleteObjectTagging_success,
"DeleteObjectTagging_expected_bucket_owner": DeleteObjectTagging_expected_bucket_owner,
"CreateMultipartUpload_non_existing_bucket": CreateMultipartUpload_non_existing_bucket,
"CreateMultipartUpload_long_metadata": CreateMultipartUpload_long_metadata,
"CreateMultipartUpload_with_metadata": CreateMultipartUpload_with_metadata,
"CreateMultipartUpload_with_tagging": CreateMultipartUpload_with_tagging,
"CreateMultipartUpload_with_object_lock": CreateMultipartUpload_with_object_lock,
@@ -1887,5 +1901,6 @@ func GetIntTests() IntTests {
"SignedStreamingPayloadTrailer_success": SignedStreamingPayloadTrailer_success,
"NoAclMode_CreateBucket_with_acl": NoAclMode_CreateBucket_with_acl,
"NoAclMode_PutBucketAcl": NoAclMode_PutBucketAcl,
"Server_large_http_header": Server_large_http_header,
}
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2026 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package integration
import (
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func Server_large_http_header(s *S3Conf) error {
testName := "Server_large_http_header"
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
req, err := createSignedReq(http.MethodPut, s.endpoint, "/bucket/object", s.awsID, s.awsSecret, "s3", s.awsRegion, nil, time.Now(), map[string]string{
"x-amz-custom-header": strings.Repeat("d", 1024*8),
})
if err != nil {
return err
}
resp, err := s.httpClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusBadRequest {
return fmt.Errorf("expected the response status to be %v, instead got %v", http.StatusBadRequest, resp.StatusCode)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if len(body) != 0 {
return fmt.Errorf("expected empty response body, instead got %s", body)
}
return nil
})
}