Skip to content

Commit aa6b5db

Browse files
authored
Add Checksum to PutObject (#2002)
Requires TrailingHeaders to be enabled on the client. Mint test updated. Verified against AWS: ``` {"time":"2024-09-19T12:54:20.5068834+02:00","level":"INFO","name":"minio-go: testPutObjectWithChecksums","duration":3504,"function":"PutObject(bucketName, objectName, reader,size, opts)","args":{"bucketName":"minio-go-test-qgn9l4slibugev06","checksum":"SHA256","objectName":"9oady6pvtp30mjatsc2141txeex5vc","opts":"minio.PutObjectOptions{UserMetadata: metadata, Progress: progress}"},"status":"PASS"} {"time":"2024-09-19T12:54:24.1432974+02:00","level":"INFO","name":"minio-go: testPutObjectWithTrailingChecksums","duration":2742,"function":"PutObject(bucketName, objectName, reader,size, opts)","args":{"bucketName":"minio-go-test-wxrdafbi5xofe9pq","checksum":"SHA256","objectName":"ih64zss0yjp4g2v5xt1atg6y9ouwwk","opts":"minio.PutObjectOptions{UserMetadata: metadata, Progress: progress, TrailChecksum: xxx}"},"status":"PASS"} {"time":"2024-09-19T12:58:44.9830104+02:00","level":"INFO","name":"minio-go: testPutMultipartObjectWithChecksums","duration":259866,"function":"PutObject(bucketName, objectName, reader,size, opts)","args":{"bucketName":"minio-go-test-40wbkvvk55n5whld","checksum":"SHA256","objectName":"c4kvdfsdcvzk94j1oudy9th6es6cvy","opts":"minio.PutObjectOptions{UserMetadata: metadata, Progress: progress Checksum: false}"},"status":"PASS"} {"time":"2024-09-19T12:59:22.7002636+02:00","level":"INFO","name":"minio-go: testPutMultipartObjectWithChecksums","duration":36731,"function":"PutObject(bucketName, objectName, reader,size, opts)","args":{"bucketName":"minio-go-test-mwfrpr43lfe6sc6o","checksum":"SHA256","objectName":"1m3fzred3ldar5gl253gspan0ltzma","opts":"minio.PutObjectOptions{UserMetadata: metadata, Progress: progress Checksum: true}"},"status":"PASS"} ``` - Fix up testGetObjectAttributes using non-explicit checksum - Fix TLS transports, with SKIP_CERT_VALIDATION
1 parent 5a98518 commit aa6b5db

5 files changed

+277
-55
lines changed

api-put-object-streaming.go

+32-17
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ func (c *Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketN
108108
if err != nil {
109109
return UploadInfo{}, err
110110
}
111-
111+
if opts.Checksum.IsSet() {
112+
opts.AutoChecksum = opts.Checksum
113+
}
112114
withChecksum := c.trailingHeaderSupport
113115
if withChecksum {
114116
if opts.UserMetadata == nil {
@@ -304,6 +306,11 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b
304306
return UploadInfo{}, err
305307
}
306308

309+
if opts.Checksum.IsSet() {
310+
opts.AutoChecksum = opts.Checksum
311+
opts.SendContentMd5 = false
312+
}
313+
307314
if !opts.SendContentMd5 {
308315
if opts.UserMetadata == nil {
309316
opts.UserMetadata = make(map[string]string, 1)
@@ -463,7 +470,10 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam
463470
if err = s3utils.CheckValidObjectName(objectName); err != nil {
464471
return UploadInfo{}, err
465472
}
466-
473+
if opts.Checksum.IsSet() {
474+
opts.SendContentMd5 = false
475+
opts.AutoChecksum = opts.Checksum
476+
}
467477
if !opts.SendContentMd5 {
468478
if opts.UserMetadata == nil {
469479
opts.UserMetadata = make(map[string]string, 1)
@@ -555,7 +565,7 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam
555565
// Calculate md5sum.
556566
customHeader := make(http.Header)
557567
if !opts.SendContentMd5 {
558-
// Add CRC32C instead.
568+
// Add Checksum instead.
559569
crc.Reset()
560570
crc.Write(buf[:length])
561571
cSum := crc.Sum(nil)
@@ -677,6 +687,9 @@ func (c *Client) putObject(ctx context.Context, bucketName, objectName string, r
677687
if opts.SendContentMd5 && s3utils.IsGoogleEndpoint(*c.endpointURL) && size < 0 {
678688
return UploadInfo{}, errInvalidArgument("MD5Sum cannot be calculated with size '-1'")
679689
}
690+
if opts.Checksum.IsSet() {
691+
opts.SendContentMd5 = false
692+
}
680693

681694
var readSeeker io.Seeker
682695
if size > 0 {
@@ -746,17 +759,6 @@ func (c *Client) putObjectDo(ctx context.Context, bucketName, objectName string,
746759
// Set headers.
747760
customHeader := opts.Header()
748761

749-
// Add CRC when client supports it, MD5 is not set, not Google and we don't add SHA256 to chunks.
750-
addCrc := c.trailingHeaderSupport && md5Base64 == "" && !s3utils.IsGoogleEndpoint(*c.endpointURL) && (opts.DisableContentSha256 || c.secure)
751-
752-
if addCrc {
753-
// If user has added checksums, don't add them ourselves.
754-
for k := range opts.UserMetadata {
755-
if strings.HasPrefix(strings.ToLower(k), "x-amz-checksum-") {
756-
addCrc = false
757-
}
758-
}
759-
}
760762
// Populate request metadata.
761763
reqMetadata := requestMetadata{
762764
bucketName: bucketName,
@@ -768,10 +770,23 @@ func (c *Client) putObjectDo(ctx context.Context, bucketName, objectName string,
768770
contentSHA256Hex: sha256Hex,
769771
streamSha256: !opts.DisableContentSha256,
770772
}
771-
if addCrc {
772-
opts.AutoChecksum.SetDefault(ChecksumCRC32C)
773-
reqMetadata.addCrc = &opts.AutoChecksum
773+
// Add CRC when client supports it, MD5 is not set, not Google and we don't add SHA256 to chunks.
774+
addCrc := c.trailingHeaderSupport && md5Base64 == "" && !s3utils.IsGoogleEndpoint(*c.endpointURL) && (opts.DisableContentSha256 || c.secure)
775+
if opts.Checksum.IsSet() {
776+
reqMetadata.addCrc = &opts.Checksum
777+
} else if addCrc {
778+
// If user has added checksums, don't add them ourselves.
779+
for k := range opts.UserMetadata {
780+
if strings.HasPrefix(strings.ToLower(k), "x-amz-checksum-") {
781+
addCrc = false
782+
}
783+
}
784+
if addCrc {
785+
opts.AutoChecksum.SetDefault(ChecksumCRC32C)
786+
reqMetadata.addCrc = &opts.AutoChecksum
787+
}
774788
}
789+
775790
if opts.Internal.SourceVersionID != "" {
776791
if opts.Internal.SourceVersionID != nullVersionID {
777792
if _, err := uuid.Parse(opts.Internal.SourceVersionID); err != nil {

api-put-object.go

+24-2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ type PutObjectOptions struct {
9494
// If none is specified CRC32C is used, since it is generally the fastest.
9595
AutoChecksum ChecksumType
9696

97+
// Checksum will force a checksum of the specific type.
98+
// This requires that the client was created with "TrailingHeaders:true" option,
99+
// and that the destination server supports it.
100+
// Unavailable with V2 signatures & Google endpoints.
101+
// This will disable content MD5 checksums if set.
102+
Checksum ChecksumType
103+
97104
// ConcurrentStreamParts will create NumThreads buffers of PartSize bytes,
98105
// fill them serially and upload them in parallel.
99106
// This can be used for faster uploads on non-seekable or slow-to-seek input.
@@ -240,7 +247,7 @@ func (opts PutObjectOptions) Header() (header http.Header) {
240247
}
241248

242249
// validate() checks if the UserMetadata map has standard headers or and raises an error if so.
243-
func (opts PutObjectOptions) validate() (err error) {
250+
func (opts PutObjectOptions) validate(c *Client) (err error) {
244251
for k, v := range opts.UserMetadata {
245252
if !httpguts.ValidHeaderFieldName(k) || isStandardHeader(k) || isSSEHeader(k) || isStorageClassHeader(k) || isMinioHeader(k) {
246253
return errInvalidArgument(k + " unsupported user defined metadata name")
@@ -255,6 +262,17 @@ func (opts PutObjectOptions) validate() (err error) {
255262
if opts.LegalHold != "" && !opts.LegalHold.IsValid() {
256263
return errInvalidArgument(opts.LegalHold.String() + " unsupported legal-hold status")
257264
}
265+
if opts.Checksum.IsSet() {
266+
switch {
267+
case !c.trailingHeaderSupport:
268+
return errInvalidArgument("Checksum requires Client with TrailingHeaders enabled")
269+
case c.overrideSignerType.IsV2():
270+
return errInvalidArgument("Checksum cannot be used with v2 signatures")
271+
case s3utils.IsGoogleEndpoint(*c.endpointURL):
272+
return errInvalidArgument("Checksum cannot be used with GCS endpoints")
273+
}
274+
}
275+
258276
return nil
259277
}
260278

@@ -291,7 +309,7 @@ func (c *Client) PutObject(ctx context.Context, bucketName, objectName string, r
291309
return UploadInfo{}, errors.New("object size must be provided with disable multipart upload")
292310
}
293311

294-
err = opts.validate()
312+
err = opts.validate(c)
295313
if err != nil {
296314
return UploadInfo{}, err
297315
}
@@ -362,6 +380,10 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam
362380
return UploadInfo{}, err
363381
}
364382

383+
if opts.Checksum.IsSet() {
384+
opts.SendContentMd5 = false
385+
opts.AutoChecksum = opts.Checksum
386+
}
365387
if !opts.SendContentMd5 {
366388
if opts.UserMetadata == nil {
367389
opts.UserMetadata = make(map[string]string, 1)

api-put-object_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestPutObjectOptionsValidate(t *testing.T) {
6161
for i, testCase := range testCases {
6262
err := PutObjectOptions{UserMetadata: map[string]string{
6363
testCase.name: testCase.value,
64-
}}.validate()
64+
}}.validate(nil)
6565
if testCase.shouldPass && err != nil {
6666
t.Errorf("Test %d - output did not match with reference results, %s", i+1, err)
6767
}

api-putobject-snowball.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ type readSeekCloser interface {
107107
// Total size should be < 5TB.
108108
// This function blocks until 'objs' is closed and the content has been uploaded.
109109
func (c Client) PutObjectsSnowball(ctx context.Context, bucketName string, opts SnowballOptions, objs <-chan SnowballObject) (err error) {
110-
err = opts.Opts.validate()
110+
err = opts.Opts.validate(&c)
111111
if err != nil {
112112
return err
113113
}

0 commit comments

Comments
 (0)