Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions cmd/gateway/s3/gateway-s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
package s3

import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"io"
"math/rand"
Expand Down Expand Up @@ -520,7 +523,25 @@ func (l *s3Objects) PutObject(ctx context.Context, bucket string, object string,
// we can set md5sum to be calculated always.
SendContentMd5: true,
}
ui, err := l.Client.PutObject(ctx, bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), putOpts)

// Core.PutObject passes md5Base64 directly to AWS without computing it.
// When the caller uses streaming SigV4 (no Content-MD5 header, e.g. Java
// AWS SDK / OpenSearch), MD5Base64String() returns "". Object Lock buckets
// require Content-MD5, so buffer the data and compute MD5 when missing.
md5Base64 := data.MD5Base64String()
sha256Hex := data.SHA256HexString()
var reader io.Reader = data
if md5Base64 == "" {
buf, err := io.ReadAll(data)
if err != nil {
return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
}
h := md5.New()
h.Write(buf)
md5Base64 = base64.StdEncoding.EncodeToString(h.Sum(nil))
reader = bytes.NewReader(buf)
}
ui, err := l.Client.PutObject(ctx, bucket, object, reader, data.Size(), md5Base64, sha256Hex, putOpts)
if err != nil {
return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
}
Expand Down Expand Up @@ -629,12 +650,31 @@ func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, objec
// PutObjectPart puts a part of object in bucket
func (l *s3Objects) PutObjectPart(ctx context.Context, bucket string, object string, uploadID string, partID int, r *minio.PutObjReader, opts minio.ObjectOptions) (pi minio.PartInfo, e error) {
data := r.Reader

md5Base64 := data.MD5Base64String()
sha256Hex := data.SHA256HexString()
var reader io.Reader = data

// AWS S3 requires Content-MD5 on every UploadPart request for Object Lock
// buckets. When the caller uses streaming SigV4 (no Content-MD5 header),
// MD5Base64String() returns "". Buffer the part and compute MD5 ourselves.
if md5Base64 == "" {
buf, err := io.ReadAll(data)
if err != nil {
return pi, minio.ErrorRespToObjectError(err, bucket, object)
}
h := md5.New()
h.Write(buf)
md5Base64 = base64.StdEncoding.EncodeToString(h.Sum(nil))
reader = bytes.NewReader(buf)
}

putOpts := miniogo.PutObjectPartOptions{
SSE: opts.ServerSideEncryption,
Md5Base64: data.MD5Base64String(),
Sha256Hex: data.SHA256HexString(),
Md5Base64: md5Base64,
Sha256Hex: sha256Hex,
}
info, err := l.Client.PutObjectPart(ctx, bucket, object, uploadID, partID, data, data.Size(), putOpts)
info, err := l.Client.PutObjectPart(ctx, bucket, object, uploadID, partID, reader, data.Size(), putOpts)
if err != nil {
return pi, minio.ErrorRespToObjectError(err, bucket, object)
}
Expand Down Expand Up @@ -718,7 +758,7 @@ func (l *s3Objects) AbortMultipartUpload(ctx context.Context, bucket string, obj

// CompleteMultipartUpload completes ongoing multipart upload and finalizes object
func (l *s3Objects) CompleteMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, uploadedParts []minio.CompletePart, opts minio.ObjectOptions) (oi minio.ObjectInfo, e error) {
uploadInfo, err := l.Client.CompleteMultipartUpload(ctx, bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts), miniogo.PutObjectOptions{})
uploadInfo, err := l.Client.CompleteMultipartUpload(ctx, bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts), miniogo.PutObjectOptions{SendContentMd5: true})
if err != nil {
return oi, minio.ErrorRespToObjectError(err, bucket, object)
}
Expand Down