[#642] Fix streaming empty body

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2025-02-13 16:31:17 +03:00
parent e184b333e4
commit e0a54fcbd3
4 changed files with 141 additions and 0 deletions

View file

@ -489,6 +489,58 @@ func TestPutObjectWithStreamEmptyBodyAWSExample(t *testing.T) {
require.Empty(t, res.Contents[0].Size)
}
func TestPutObjectWithStreamEmptyBody(t *testing.T) {
hc := prepareHandlerContext(t)
bktName := "bucket"
createTestBucket(hc, bktName)
t.Run("unsigned", func(t *testing.T) {
t.Run("trailer", func(t *testing.T) {
objName := "unsigned trailer"
w, req := getEmptyChunkedRequestUnsigned(hc.context, t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
d, h := getObject(hc, bktName, objName)
require.Empty(t, d)
require.Equal(t, "0", h.Get(api.ContentLength))
})
})
t.Run("sigv4", func(t *testing.T) {
t.Run("no trailer", func(t *testing.T) {
objName := "sigv4 no trailer"
w, req := getEmptyChunkedRequest(hc.context, t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
d, h := getObject(hc, bktName, objName)
require.Empty(t, d)
require.Equal(t, "0", h.Get(api.ContentLength))
})
})
t.Run("sigv4a", func(t *testing.T) {
t.Run("trailer", func(t *testing.T) {
objName := "sigv4a trailer"
w, req := getEmptyChunkedRequestSigv4a(hc.context, t, bktName, objName)
req.Header.Del(api.ContentType)
hc.Handler().PutObjectHandler(w, req)
assertStatus(t, w, http.StatusOK)
d, h := getObject(hc, bktName, objName)
require.Empty(t, d)
require.Equal(t, "0", h.Get(api.ContentLength))
})
})
}
func TestPutChunkedTestContentEncoding(t *testing.T) {
hc := prepareHandlerContext(t)
@ -818,6 +870,83 @@ func getEmptyChunkedRequest(ctx context.Context, t *testing.T, bktName, objName
return w, req
}
func getEmptyChunkedRequestUnsigned(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
reqBody := bytes.NewBufferString("0\r\nx-amz-checksum-crc64nvme:AAAAAAAAAAA=\r\n\r\n")
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, reqBody)
require.NoError(t, err)
req.Header.Set(api.Authorization, "AWS4-HMAC-SHA256 Credential=3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt/20250213/ru/s3/aws4_request, SignedHeaders=content-encoding;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-sdk-checksum-algorithm;x-amz-trailer, Signature=1231b012c0ac313770c5a95ccf77b95b6c9b1c3760d6aa24cb8309801d56eb4a")
req.Header.Set(api.ContentEncoding, api.AwsChunked)
req.Header.Set(api.AmzDate, "20250213T124858Z")
req.Header.Set(api.AmzContentSha256, api.StreamingUnsignedPayloadTrailer)
req.Header.Set(api.AmzDecodedContentLength, "0")
req.Header.Set("X-Amz-Trailer", "x-amz-checksum-crc64nvme")
req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC64NVME")
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "1231b012c0ac313770c5a95ccf77b95b6c9b1c3760d6aa24cb8309801d56eb4a",
Region: "ru",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
}
func getEmptyChunkedRequestSigv4a(ctx context.Context, t *testing.T, bktName, objName string) (*httptest.ResponseRecorder, *http.Request) {
AWSAccessKeyID := "3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt"
AWSSecretAccessKey := "f1a0d650b650149f1a83140418e88a3c5572a0103e912e326492a91c19c4488a"
body := "0;chunk-signature=3046022100ab9229a80d70f4d004768992881821a441a4ad4102e18de567e68216659bf497022100ec47a7a445351683557eedf893e6ed250c97af4b0415814671770b83766d69be\r\n" +
"x-amz-checksum-crc32:AAAAAA==\r\n" +
"x-amz-trailer-signature:3046022100a0a66c1adcee8d99460b4631b23c95fbad9eb4e6c56f1afb9e255715ba141169022100b2cfc8adc8036eb985f1ab0e770b575284c5fc8ca75c226558d3142cbaab83ce\r\n\r\n"
req, err := http.NewRequest("PUT", "http://localhost:8084/"+bktName+"/"+objName, bytes.NewBufferString(body))
require.NoError(t, err)
req.Header.Set(api.Authorization, "AWS4-ECDSA-P256-SHA256 Credential=3jNrmDtHtuj1uLcixaSMA4KNUhNYhv1EpUNdFnbTXgUP071pGdSZfHSLtoC8gzjF5HoD6sC3Scq33t1WvvEvjmPnt/20250213/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length;x-amz-region-set;x-amz-sdk-checksum-algorithm;x-amz-trailer, Signature=304402202e1f1efcc56c588d9a94a3d8f20368686df8bfd5e8aad01fc4eff569ff38f1800220215198e3f1ba785492fe6703c4722872909ce8a09e8c9a13da90a9230c7a24b7")
req.Header.Set("Amz-Sdk-Invocation-Id", "d42dc16d-7899-55fb-5b72-a654bd482f4f")
req.Header.Set("Amz-Sdk-Request", "attempt=1; max=2")
req.Header.Set(api.ContentEncoding, api.AwsChunked)
req.Header.Set(api.AmzDate, "20250213T132401Z")
req.Header.Set(api.AmzContentSha256, api.StreamingContentV4aSHA256Trailer)
req.Header.Set(api.AmzDecodedContentLength, "0")
req.Header.Set(api.ContentLength, "367")
req.Header.Set(api.ContentType, "text/plain: charset=UTF-8")
req.Header.Set("X-Amz-Region-Set", "use-east-1")
req.Header.Set("X-Amz-Trailer", "x-amz-checksum-crc32")
req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC32")
signTime, err := time.Parse("20060102T150405Z", req.Header.Get(api.AmzDate))
require.NoError(t, err)
w := httptest.NewRecorder()
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
ClientTime: signTime,
AuthHeaders: &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID,
SignatureV4: "304402202e1f1efcc56c588d9a94a3d8f20368686df8bfd5e8aad01fc4eff569ff38f1800220215198e3f1ba785492fe6703c4722872909ce8a09e8c9a13da90a9230c7a24b7",
Region: "us-east-1",
},
AccessBox: &accessbox.Box{Gate: &accessbox.GateData{SecretKey: AWSSecretAccessKey}},
}))
return w, req
}
func TestCreateBucket(t *testing.T) {
hc := prepareHandlerContext(t)
bktName := "bkt-name"

View file

@ -59,6 +59,10 @@ func (c *s3ChunkReader) Read(buf []byte) (num int, err error) {
buf = buf[num:]
}
if c.err != nil {
return 0, c.err
}
var size int
for {
b, err := c.reader.ReadByte()

View file

@ -31,6 +31,10 @@ func (c *s3UnsignedChunkReader) Read(buf []byte) (num int, err error) {
buf = buf[num:]
}
if c.err != nil {
return 0, c.err
}
var size int
var b byte
for {

View file

@ -46,6 +46,10 @@ func (c *s3v4aChunkReader) Read(buf []byte) (num int, err error) {
buf = buf[num:]
}
if c.err != nil {
return 0, c.err
}
var size int
for {
b, err := c.reader.ReadByte()