From 07c3a429bfed433e49018cb0f78a52145d4bedeb Mon Sep 17 00:00:00 2001 From: M Alvee Date: Sun, 7 Sep 2025 09:13:09 -0700 Subject: [PATCH] fix: conditional checks write for multipart (#21567) --- cmd/erasure-multipart.go | 12 ++++++++++++ cmd/erasure-object.go | 5 +++-- cmd/object-api-interface.go | 1 + cmd/object-handlers.go | 3 +++ cmd/object-multipart-handlers.go | 6 ++++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cmd/erasure-multipart.go b/cmd/erasure-multipart.go index 0a2f527b7..f748f1f3e 100644 --- a/cmd/erasure-multipart.go +++ b/cmd/erasure-multipart.go @@ -393,6 +393,12 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string, if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !isErrReadQuorum(err) { return nil, err } + + // if object doesn't exist and not a replication request return error for If-Match conditional requests + // If-None-Match should be allowed to proceed for non-existent objects + if err != nil && !opts.ReplicationRequest && opts.HasIfMatch && (isErrObjectNotFound(err) || isErrVersionNotFound(err)) { + return nil, err + } } userDefined := cloneMSS(opts.UserDefined) @@ -1111,6 +1117,12 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) && !isErrReadQuorum(err) { return ObjectInfo{}, err } + + // if object doesn't exist and not a replication request return error for If-Match conditional requests + // If-None-Match should be allowed to proceed for non-existent objects + if err != nil && !opts.ReplicationRequest && opts.HasIfMatch && (isErrObjectNotFound(err) || isErrVersionNotFound(err)) { + return ObjectInfo{}, err + } } fi, partsMetadata, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, true) diff --git a/cmd/erasure-object.go b/cmd/erasure-object.go index 6f603564c..26ca3022b 100644 --- a/cmd/erasure-object.go +++ b/cmd/erasure-object.go @@ -1278,8 +1278,9 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st return objInfo, err } - // if object doesn't exist and not a replication request return error for conditional requests - if err != nil && !opts.ReplicationRequest { + // if object doesn't exist and not a replication request return error for If-Match conditional requests + // If-None-Match should be allowed to proceed for non-existent objects + if err != nil && !opts.ReplicationRequest && opts.HasIfMatch && (isErrObjectNotFound(err) || isErrVersionNotFound(err)) { return objInfo, err } } diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go index 501fa3a94..603cc38f2 100644 --- a/cmd/object-api-interface.go +++ b/cmd/object-api-interface.go @@ -91,6 +91,7 @@ type ObjectOptions struct { NoDecryption bool // indicates if the stream must be decrypted. PreserveETag string // preserves this etag during a PUT call. NoLock bool // indicates to lower layers if the caller is expecting to hold locks. + HasIfMatch bool // indicates if the request has If-Match header ProxyRequest bool // only set for GET/HEAD in active-active replication scenario ProxyHeaderSet bool // only set for GET/HEAD in active-active replication scenario ReplicationRequest bool // true only if replication request diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 7879300be..9663e99b4 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -2014,6 +2014,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req pReader := NewPutObjReader(rawReader) opts.IndexCB = idxCb + if r.Header.Get(xhttp.IfMatch) != "" { + opts.HasIfMatch = true + } if opts.PreserveETag != "" || r.Header.Get(xhttp.IfMatch) != "" || r.Header.Get(xhttp.IfNoneMatch) != "" { diff --git a/cmd/object-multipart-handlers.go b/cmd/object-multipart-handlers.go index c7edaf76f..12a99b312 100644 --- a/cmd/object-multipart-handlers.go +++ b/cmd/object-multipart-handlers.go @@ -200,6 +200,9 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r return } + if r.Header.Get(xhttp.IfMatch) != "" { + opts.HasIfMatch = true + } if opts.PreserveETag != "" || r.Header.Get(xhttp.IfMatch) != "" || r.Header.Get(xhttp.IfNoneMatch) != "" { @@ -1014,6 +1017,9 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite multipartETag := etag.Multipart(completeETags...) opts.UserDefined["etag"] = multipartETag.String() + if r.Header.Get(xhttp.IfMatch) != "" { + opts.HasIfMatch = true + } if opts.PreserveETag != "" || r.Header.Get(xhttp.IfMatch) != "" || r.Header.Get(xhttp.IfNoneMatch) != "" {