mirror of
https://github.com/pgsty/minio.git
synced 2026-09-05 18:16:16 +03:00
fix: align multipart completion checksum errors
Return AWS-compatible errors for CompleteMultipartUpload checksum failures without changing the global streaming checksum mapping. Compare explicit multipart checksum types symmetrically, distinguish missing composite part checksums, and preserve the CRC64NVME canonicalization pending a direct AWS probe. Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -2169,6 +2169,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
err = unwrapAll(err)
|
||||
|
||||
switch err {
|
||||
case errCompleteMultipartChecksumMismatch, errCompleteMultipartChecksumTypeMismatch:
|
||||
apiErr = ErrBadDigest
|
||||
case errMissingPartChecksum:
|
||||
apiErr = ErrInvalidRequest
|
||||
case errInvalidArgument:
|
||||
apiErr = ErrAdminInvalidArgument
|
||||
case errNoSuchPolicy:
|
||||
@@ -2465,6 +2469,14 @@ func toAPIError(ctx context.Context, err error) APIError {
|
||||
}
|
||||
|
||||
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
||||
switch {
|
||||
case errors.Is(err, errCompleteMultipartChecksumMismatch):
|
||||
apiErr.Description = strings.TrimPrefix(err.Error(), errCompleteMultipartChecksumMismatch.Error()+": ")
|
||||
case errors.Is(err, errCompleteMultipartChecksumTypeMismatch):
|
||||
apiErr.Description = strings.TrimPrefix(err.Error(), errCompleteMultipartChecksumTypeMismatch.Error()+": ")
|
||||
case errors.Is(err, errMissingPartChecksum):
|
||||
apiErr.Description = strings.TrimPrefix(err.Error(), errMissingPartChecksum.Error()+": ")
|
||||
}
|
||||
switch apiErr.Code {
|
||||
case "NotImplemented":
|
||||
apiErr = APIError{
|
||||
|
||||
@@ -39,6 +39,10 @@ var toAPIErrorTests = []struct {
|
||||
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
||||
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||
{err: errCompleteMultipartChecksumMismatch, errCode: ErrBadDigest},
|
||||
{err: errCompleteMultipartChecksumTypeMismatch, errCode: ErrBadDigest},
|
||||
{err: errMissingPartChecksum, errCode: ErrInvalidRequest},
|
||||
{err: hash.ChecksumMismatch{}, errCode: ErrContentChecksumMismatch},
|
||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -139,13 +140,18 @@ func completeMultipartUploadHTTP(t *testing.T, apiRouter http.Handler, creds aut
|
||||
return rec
|
||||
}
|
||||
|
||||
func apiErrorCode(t *testing.T, rec *httptest.ResponseRecorder) string {
|
||||
func apiError(t *testing.T, rec *httptest.ResponseRecorder) APIErrorResponse {
|
||||
t.Helper()
|
||||
var e APIErrorResponse
|
||||
if err := xml.Unmarshal(rec.Body.Bytes(), &e); err != nil {
|
||||
t.Fatalf("unable to decode error response %q: %v", rec.Body.String(), err)
|
||||
}
|
||||
return e.Code
|
||||
return e
|
||||
}
|
||||
|
||||
func apiErrorCode(t *testing.T, rec *httptest.ResponseRecorder) string {
|
||||
t.Helper()
|
||||
return apiError(t, rec).Code
|
||||
}
|
||||
|
||||
// TestAPICompleteMultipartFullObjectChecksum covers pgsty/silo#31.
|
||||
@@ -243,12 +249,12 @@ func testAPICompleteMultipartFullObjectChecksumMismatch(obj ObjectLayer, instanc
|
||||
t.Fatalf("%s: CompleteMultipartUpload with a bad full object checksum returned %d, want 400",
|
||||
instanceType, rec.Code)
|
||||
}
|
||||
// NOTE: AWS S3 documents BadDigest for a full object checksum mismatch on
|
||||
// CompleteMultipartUpload. MinIO reports XAmzContentChecksumMismatch. That
|
||||
// deviation is tracked separately; assert the current code so a future
|
||||
// change to it is a deliberate one.
|
||||
if got := apiErrorCode(t, rec); got != "XAmzContentChecksumMismatch" {
|
||||
t.Fatalf("%s: expected XAmzContentChecksumMismatch, got %q", instanceType, got)
|
||||
apiErr := apiError(t, rec)
|
||||
if apiErr.Code != "BadDigest" {
|
||||
t.Fatalf("%s: expected BadDigest, got %q", instanceType, apiErr.Code)
|
||||
}
|
||||
if want := "The CRC32 checksum you specified did not match the calculated checksum."; apiErr.Message != want {
|
||||
t.Fatalf("%s: expected message %q, got %q", instanceType, want, apiErr.Message)
|
||||
}
|
||||
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
@@ -288,8 +294,33 @@ func testAPICompleteMultipartCompositeStillRequiresPartChecksums(obj ObjectLayer
|
||||
t.Fatalf("%s/%s: composite CompleteMultipartUpload without part checksums returned %d, want 400",
|
||||
instanceType, typ.String(), rec.Code)
|
||||
}
|
||||
if got := apiErrorCode(t, rec); got != "InvalidPart" {
|
||||
t.Fatalf("%s/%s: expected InvalidPart, got %q", instanceType, typ.String(), got)
|
||||
apiErr := apiError(t, rec)
|
||||
if apiErr.Code != "InvalidRequest" {
|
||||
t.Fatalf("%s/%s: expected InvalidRequest, got %q", instanceType, typ.String(), apiErr.Code)
|
||||
}
|
||||
wantMessage := fmt.Sprintf("The upload was created using a %s checksum. The complete request must include the checksum for each part. It was missing for part 1 in the request.", strings.ToLower(typ.String()))
|
||||
if apiErr.Message != wantMessage {
|
||||
t.Fatalf("%s/%s: expected message %q, got %q", instanceType, typ.String(), wantMessage, apiErr.Message)
|
||||
}
|
||||
|
||||
// A retry that supplies part 1 but omits part 2 must name the actual
|
||||
// missing part, not merely the first part in the upload.
|
||||
completedParts := []CompletePart{
|
||||
completePartWithChecksum(typ, 1, etags[0], mustChecksum(t, typ, partData[0])),
|
||||
{PartNumber: 2, ETag: etags[1]},
|
||||
}
|
||||
rec = completePartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, completedParts, nil)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("%s/%s: composite completion missing part 2 checksum returned %d, want 400",
|
||||
instanceType, typ.String(), rec.Code)
|
||||
}
|
||||
apiErr = apiError(t, rec)
|
||||
if apiErr.Code != "InvalidRequest" {
|
||||
t.Fatalf("%s/%s: expected InvalidRequest, got %q", instanceType, typ.String(), apiErr.Code)
|
||||
}
|
||||
wantMessage = fmt.Sprintf("The upload was created using a %s checksum. The complete request must include the checksum for each part. It was missing for part 2 in the request.", strings.ToLower(typ.String()))
|
||||
if apiErr.Message != wantMessage {
|
||||
t.Fatalf("%s/%s: expected message %q, got %q", instanceType, typ.String(), wantMessage, apiErr.Message)
|
||||
}
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
t.Fatalf("%s/%s: object was created despite a rejected completion", instanceType, typ.String())
|
||||
@@ -297,6 +328,145 @@ func testAPICompleteMultipartCompositeStillRequiresPartChecksums(obj ObjectLayer
|
||||
}
|
||||
}
|
||||
|
||||
// TestAPICompleteMultipartCompositeChecksumMismatch covers the composite
|
||||
// object-checksum path independently from full object checksum merging.
|
||||
func TestAPICompleteMultipartCompositeChecksumMismatch(t *testing.T) {
|
||||
defer DetectTestLeak(t)()
|
||||
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{
|
||||
t: t,
|
||||
objAPITest: testAPICompleteMultipartCompositeChecksumMismatch,
|
||||
endpoints: []string{"NewMultipart", "PutObjectPart", "CompleteMultipart"},
|
||||
})
|
||||
}
|
||||
|
||||
func testAPICompleteMultipartCompositeChecksumMismatch(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||
credentials auth.Credentials, t *testing.T,
|
||||
) {
|
||||
typ := hash.ChecksumCRC32
|
||||
partData, _ := multipartChecksumTestData()
|
||||
objectName := "uploads/composite-object-mismatch"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), xhttp.AmzChecksumTypeComposite)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
partCS := []string{mustChecksum(t, typ, partData[0]), mustChecksum(t, typ, partData[1])}
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, partCS,
|
||||
map[string]string{
|
||||
typ.Key(): mustChecksum(t, typ, []byte("wrong composite checksum")) + "-2",
|
||||
xhttp.AmzChecksumType: xhttp.AmzChecksumTypeComposite,
|
||||
})
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("%s: composite checksum mismatch returned %d, want 400", instanceType, rec.Code)
|
||||
}
|
||||
apiErr := apiError(t, rec)
|
||||
if apiErr.Code != "BadDigest" {
|
||||
t.Fatalf("%s: expected BadDigest, got %q", instanceType, apiErr.Code)
|
||||
}
|
||||
if want := "The CRC32 checksum you specified did not match the calculated checksum."; apiErr.Message != want {
|
||||
t.Fatalf("%s: expected message %q, got %q", instanceType, want, apiErr.Message)
|
||||
}
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
t.Fatalf("%s: object was created despite a failed composite checksum validation", instanceType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAPICompleteMultipartChecksumTypeMismatch verifies the type comparison in
|
||||
// both directions. ChecksumType is a bitmask, so a containment check alone
|
||||
// incorrectly accepts COMPOSITE uploads completed as FULL_OBJECT.
|
||||
func TestAPICompleteMultipartChecksumTypeMismatch(t *testing.T) {
|
||||
defer DetectTestLeak(t)()
|
||||
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{
|
||||
t: t,
|
||||
objAPITest: testAPICompleteMultipartChecksumTypeMismatch,
|
||||
endpoints: []string{"NewMultipart", "PutObjectPart", "CompleteMultipart"},
|
||||
})
|
||||
}
|
||||
|
||||
func testAPICompleteMultipartChecksumTypeMismatch(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
|
||||
credentials auth.Credentials, t *testing.T,
|
||||
) {
|
||||
typ := hash.ChecksumCRC32
|
||||
partData, full := multipartChecksumTestData()
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
createdType string
|
||||
providedType string
|
||||
}{
|
||||
{name: "full-to-composite", createdType: xhttp.AmzChecksumTypeFullObject, providedType: xhttp.AmzChecksumTypeComposite},
|
||||
{name: "composite-to-full", createdType: xhttp.AmzChecksumTypeComposite, providedType: xhttp.AmzChecksumTypeFullObject},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
objectName := "type-mismatch/" + test.name
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), test.createdType)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
partCS := []string{mustChecksum(t, typ, partData[0]), mustChecksum(t, typ, partData[1])}
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, partCS,
|
||||
map[string]string{
|
||||
typ.Key(): mustChecksum(t, typ, full),
|
||||
xhttp.AmzChecksumType: test.providedType,
|
||||
})
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("%s: checksum type mismatch returned %d, want 400", instanceType, rec.Code)
|
||||
}
|
||||
apiErr := apiError(t, rec)
|
||||
if apiErr.Code != "BadDigest" {
|
||||
t.Fatalf("%s: expected BadDigest, got %q", instanceType, apiErr.Code)
|
||||
}
|
||||
wantMessage := fmt.Sprintf("The checksum type %s does not match the multipart upload checksum type %s.", test.providedType, test.createdType)
|
||||
if apiErr.Message != wantMessage {
|
||||
t.Fatalf("%s: expected message %q, got %q", instanceType, wantMessage, apiErr.Message)
|
||||
}
|
||||
if _, err := obj.GetObjectInfo(t.Context(), bucketName, objectName, ObjectOptions{}); err == nil {
|
||||
t.Fatalf("%s: object was created despite a rejected checksum type", instanceType)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("omitted-type-is-not-composite", func(t *testing.T) {
|
||||
objectName := "type-mismatch/omitted-type"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), xhttp.AmzChecksumTypeFullObject)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, nil,
|
||||
map[string]string{typ.Key(): mustChecksum(t, typ, full)})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("%s: completion without an explicit checksum type returned %d %s",
|
||||
instanceType, rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("algorithm-mismatch-remains-invalid-argument", func(t *testing.T) {
|
||||
objectName := "type-mismatch/algorithm"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
typ.String(), xhttp.AmzChecksumTypeFullObject)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, typ, partData)
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, nil,
|
||||
map[string]string{
|
||||
hash.ChecksumCRC32C.Key(): mustChecksum(t, hash.ChecksumCRC32C, full),
|
||||
xhttp.AmzChecksumType: xhttp.AmzChecksumTypeFullObject,
|
||||
})
|
||||
if rec.Code != http.StatusBadRequest || apiErrorCode(t, rec) != "InvalidArgument" {
|
||||
t.Fatalf("%s: algorithm mismatch returned %d %s", instanceType, rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("crc64nvme-composite-remains-canonicalized", func(t *testing.T) {
|
||||
crc64Type := hash.ChecksumCRC64NVME
|
||||
objectName := "type-mismatch/crc64nvme-composite"
|
||||
uploadID := newMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName,
|
||||
crc64Type.String(), xhttp.AmzChecksumTypeComposite)
|
||||
etags := uploadPartsHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, crc64Type, partData)
|
||||
rec := completeMultipartUploadHTTP(t, apiRouter, credentials, bucketName, objectName, uploadID, etags, nil,
|
||||
map[string]string{
|
||||
crc64Type.Key(): mustChecksum(t, crc64Type, full),
|
||||
xhttp.AmzChecksumType: xhttp.AmzChecksumTypeComposite,
|
||||
})
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("%s: CRC64NVME canonicalization changed: %d %s", instanceType, rec.Code, rec.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestAPICompleteMultipartFullObjectVariants pins down the surrounding
|
||||
// behavior of the relaxation: what may be omitted, what must still match, and
|
||||
// that a zero length object is handled like any other.
|
||||
|
||||
+23
-18
@@ -1173,11 +1173,18 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
var checksumType hash.ChecksumType
|
||||
if cs := fi.Metadata[hash.MinIOMultipartChecksum]; cs != "" {
|
||||
checksumType = hash.NewChecksumType(cs, fi.Metadata[hash.MinIOMultipartChecksumType])
|
||||
if opts.WantChecksum != nil && !opts.WantChecksum.Type.Is(checksumType) {
|
||||
if opts.WantChecksum != nil {
|
||||
providedType := opts.WantChecksum.Type | hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
expectedType := checksumType | hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
if providedType.Base() != expectedType.Base() {
|
||||
return oi, InvalidArgument{
|
||||
Bucket: bucket,
|
||||
Object: fi.Name,
|
||||
Err: fmt.Errorf("checksum type mismatch. got %q (%s) expected %q (%s)", checksumType.String(), checksumType.ObjType(), opts.WantChecksum.Type.String(), opts.WantChecksum.Type.ObjType()),
|
||||
Err: fmt.Errorf("checksum algorithm mismatch. got %q expected %q", providedType.String(), expectedType.String()),
|
||||
}
|
||||
}
|
||||
if opts.wantChecksumTypeSet && providedType.ObjType() != expectedType.ObjType() {
|
||||
return oi, completeMultipartChecksumTypeMismatch(providedType.ObjType(), expectedType.ObjType())
|
||||
}
|
||||
}
|
||||
checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
|
||||
@@ -1308,19 +1315,18 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
break
|
||||
}
|
||||
}
|
||||
// Part checksums are optional in the CompleteMultipartUpload body when
|
||||
// the upload was created with a full object checksum type: clients send
|
||||
// the object level checksum instead and do not retain part checksums.
|
||||
// A part that carries any checksum at all is still validated against
|
||||
// what we stored - including one sent under the wrong algorithm, which
|
||||
// cannot match and is rejected. The object level checksum, if supplied,
|
||||
// is verified against the merged part checksums below.
|
||||
allowMissingPartCS := checksumType.FullObjectRequested() && !suppliedAnyCS
|
||||
if !allowMissingPartCS && gotCS != crc {
|
||||
// Full object completions may omit part checksums. Composite
|
||||
// completions may not. Any checksum that is supplied is still
|
||||
// validated, including one sent under the wrong algorithm.
|
||||
if !suppliedAnyCS {
|
||||
if !checksumType.FullObjectRequested() {
|
||||
return oi, missingPartChecksum(checksumType.String(), part.PartNumber)
|
||||
}
|
||||
} else if gotCS != crc {
|
||||
return oi, InvalidPart{
|
||||
PartNumber: part.PartNumber,
|
||||
ExpETag: gotCS,
|
||||
GotETag: crc,
|
||||
ExpETag: crc,
|
||||
GotETag: gotCS,
|
||||
}
|
||||
}
|
||||
cs := hash.NewChecksumString(checksumType.String(), crc)
|
||||
@@ -1370,15 +1376,14 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
if opts.WantChecksum != nil {
|
||||
if checksumType.FullObjectRequested() {
|
||||
if opts.WantChecksum.Encoded != checksum.Encoded {
|
||||
err := hash.ChecksumMismatch{
|
||||
Want: opts.WantChecksum.Encoded,
|
||||
Got: checksum.Encoded,
|
||||
}
|
||||
return oi, err
|
||||
return oi, completeMultipartChecksumMismatch(checksumType.String())
|
||||
}
|
||||
} else {
|
||||
err := opts.WantChecksum.Matches(checksumCombined, len(parts))
|
||||
if err != nil {
|
||||
if hash.IsChecksumMismatch(err) {
|
||||
return oi, completeMultipartChecksumMismatch(checksumType.String())
|
||||
}
|
||||
return oi, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Converts underlying storage error. Convenience function written to
|
||||
@@ -653,6 +654,41 @@ func (e InvalidPart) Error() string {
|
||||
e.PartNumber, e.ExpETag, e.GotETag)
|
||||
}
|
||||
|
||||
var (
|
||||
errCompleteMultipartChecksumMismatch = errors.New("complete multipart checksum mismatch")
|
||||
errCompleteMultipartChecksumTypeMismatch = errors.New("complete multipart checksum type mismatch")
|
||||
errMissingPartChecksum = errors.New("missing multipart part checksum")
|
||||
)
|
||||
|
||||
// completeMultipartChecksumMismatch reports an object checksum mismatch
|
||||
// detected while completing a multipart upload. It is distinct from
|
||||
// hash.ChecksumMismatch because AWS maps completion failures to BadDigest,
|
||||
// while streaming PutObject and UploadPart failures keep using
|
||||
// XAmzContentChecksumMismatch.
|
||||
func completeMultipartChecksumMismatch(algorithm string) error {
|
||||
description := "The checksum you specified did not match the calculated checksum."
|
||||
if algorithm != "" {
|
||||
description = fmt.Sprintf("The %s checksum you specified did not match the calculated checksum.", algorithm)
|
||||
}
|
||||
return fmt.Errorf("%w: %s", errCompleteMultipartChecksumMismatch, description)
|
||||
}
|
||||
|
||||
// completeMultipartChecksumTypeMismatch reports an explicit checksum type that
|
||||
// differs from the type selected when the multipart upload was initiated.
|
||||
func completeMultipartChecksumTypeMismatch(providedType, expectedType string) error {
|
||||
description := fmt.Sprintf("The checksum type %s does not match the multipart upload checksum type %s.",
|
||||
providedType, expectedType)
|
||||
return fmt.Errorf("%w: %s", errCompleteMultipartChecksumTypeMismatch, description)
|
||||
}
|
||||
|
||||
// missingPartChecksum reports a part whose checksum is absent from a
|
||||
// composite CompleteMultipartUpload request.
|
||||
func missingPartChecksum(algorithm string, partNumber int) error {
|
||||
description := fmt.Sprintf("The upload was created using a %s checksum. The complete request must include the checksum for each part. It was missing for part %d in the request.",
|
||||
strings.ToLower(algorithm), partNumber)
|
||||
return fmt.Errorf("%w: %s", errMissingPartChecksum, description)
|
||||
}
|
||||
|
||||
// PartTooSmall - error if part size is less than 5MB.
|
||||
type PartTooSmall struct {
|
||||
PartSize int64
|
||||
|
||||
@@ -85,6 +85,7 @@ type ObjectOptions struct {
|
||||
LifecycleAuditEvent lcAuditEvent
|
||||
|
||||
WantChecksum *hash.Checksum // x-amz-checksum-XXX checksum sent to PutObject/ CompleteMultipartUpload.
|
||||
wantChecksumTypeSet bool // x-amz-checksum-type was explicitly set on CompleteMultipartUpload.
|
||||
|
||||
WantServerSideChecksumType hash.ChecksumType // if set, we compute a server-side checksum of this type
|
||||
|
||||
|
||||
@@ -473,6 +473,7 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.wantChecksumTypeSet = r.Header.Get(xhttp.AmzChecksumType) != ""
|
||||
opts.MTime = mtime
|
||||
opts.UserDefined = make(map[string]string)
|
||||
// Transfer SSEC key in opts.EncryptFn
|
||||
|
||||
Reference in New Issue
Block a user