From c6d747472144fd4de6a0cdd9d04f6f3055a34110 Mon Sep 17 00:00:00 2001 From: Evgenii Baidakov Date: Mon, 30 Sep 2024 15:55:48 +0400 Subject: [PATCH] *: Use chunked according to headers Closes #1009. The amazon docs https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html chunked payload maybe sent without `content-encoding:aws-chunked` header. Meanwhile the header `x-amz-content-sha256:STREAMING-AWS4-HMAC-SHA256-PAYLOAD` still should exists. Signed-off-by: Evgenii Baidakov --- api/auth/center.go | 6 +++++- api/handler/put.go | 8 +++++++- api/handler/util.go | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/api/auth/center.go b/api/auth/center.go index 406898ae..866b40fe 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -74,10 +74,12 @@ const ( AmzSignedHeaders = "X-Amz-SignedHeaders" AmzExpires = "X-Amz-Expires" AmzDate = "X-Amz-Date" + AmzContentSha256 = "X-Amz-Content-Sha256" AuthorizationHdr = "Authorization" ContentTypeHdr = "Content-Type" ContentEncodingHdr = "Content-Encoding" ContentEncodingAwsChunked = "aws-chunked" + ContentEncodingChunked = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" timeFormatISO8601 = "20060102T150405Z" ) @@ -209,7 +211,9 @@ func (c *center) Authenticate(r *http.Request) (*Box, error) { return nil, err } - if hdr := r.Header.Get(ContentEncodingHdr); hdr == ContentEncodingAwsChunked { + amzContent := r.Header.Get(AmzContentSha256) + + if contentEncodingHdr := r.Header.Get(ContentEncodingHdr); contentEncodingHdr == ContentEncodingAwsChunked || amzContent == ContentEncodingChunked { sig, err := hex.DecodeString(authHdr.SignatureV4) if err != nil { return nil, fmt.Errorf("DecodeString: %w", err) diff --git a/api/handler/put.go b/api/handler/put.go index 484845fd..6eeebb97 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -224,11 +224,17 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { return } + cl, err := contentLengthFromRequest(r) + if err != nil { + h.logAndSendError(w, "content length parse failed", reqInfo, err) + return + } + params := &layer.PutObjectParams{ BktInfo: bktInfo, Object: reqInfo.ObjectName, Reader: r.Body, - Size: r.ContentLength, + Size: cl, Header: metadata, Encryption: encryptionParams, CopiesNumber: copiesNumber, diff --git a/api/handler/util.go b/api/handler/util.go index 982a32ad..d64ef480 100644 --- a/api/handler/util.go +++ b/api/handler/util.go @@ -3,6 +3,7 @@ package handler import ( "context" "errors" + "fmt" "net/http" "strconv" "strings" @@ -15,6 +16,10 @@ import ( "go.uber.org/zap" ) +const ( + xAmzDecodedContentLength = "X-Amz-Decoded-Content-Length" +) + func (h *handler) logAndSendError(w http.ResponseWriter, logText string, reqInfo *api.ReqInfo, err error, additional ...zap.Field) { code := api.WriteErrorResponse(w, reqInfo, transformToS3Error(err)) fields := []zap.Field{ @@ -127,3 +132,19 @@ func getSessionTokenSetEACL(ctx context.Context) (*session.Container, error) { return sessionToken, nil } + +func contentLengthFromRequest(r *http.Request) (int64, error) { + var ( + cl = r.ContentLength + err error + ) + + if value := r.Header.Get(xAmzDecodedContentLength); value != "" { + cl, err = strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid content length value %q inside %s header", value, xAmzDecodedContentLength) + } + } + + return cl, nil +}