From 61308d2fbf23b2566e927f20a05f4128d8239775 Mon Sep 17 00:00:00 2001 From: niksis02 Date: Tue, 30 Dec 2025 12:02:49 +0400 Subject: [PATCH] fix: return NoSuchKey if a precondition header is present and object doesn't exist in PutObject, CompleteMultipartUpload Fixes #1709 If any precondition header is present(`If-Match`, `If-None-Match`) in `PutObject` and `CompleteMultipartUpload` and there's no object in the bucket with the given key, a `NoSuchKey` error is now returned. Previously the headers were simply ignored and new object creation was allowed. --- backend/azure/azure.go | 7 +------ backend/posix/posix.go | 6 ++++++ tests/integration/CompleteMultipartUpload.go | 15 ++++++++------- tests/integration/PutObject.go | 15 ++++++++------- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/backend/azure/azure.go b/backend/azure/azure.go index 276cb19..ac674a4 100644 --- a/backend/azure/azure.go +++ b/backend/azure/azure.go @@ -2085,8 +2085,6 @@ func (az *Azure) evaluateWritePreconditions(ctx context.Context, bucket, object, return nil } // call HeadObject to evaluate preconditions - // if object doesn't exist, move forward with the object creation - // otherwise return the error _, err := az.HeadObject(ctx, &s3.HeadObjectInput{ Bucket: bucket, Key: object, @@ -2096,11 +2094,8 @@ func (az *Azure) evaluateWritePreconditions(ctx context.Context, bucket, object, if errors.Is(err, s3err.GetAPIError(s3err.ErrNotModified)) { return s3err.GetAPIError(s3err.ErrPreconditionFailed) } - if err != nil && !errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) { - return err - } - return nil + return err } func getAclFromMetadata(meta map[string]*string, key key) (*auth.ACL, error) { diff --git a/backend/posix/posix.go b/backend/posix/posix.go index bf6021a..54cce0d 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -1473,6 +1473,9 @@ func (p *Posix) CompleteMultipartUploadWithCopy(ctx context.Context, input *s3.C } b, err := p.meta.RetrieveAttribute(nil, bucket, object, etagkey) + if errors.Is(err, fs.ErrNotExist) && (input.IfMatch != nil || input.IfNoneMatch != nil) { + return s3response.CompleteMultipartUploadResult{}, "", s3err.GetAPIError(s3err.ErrNoSuchKey) + } if err == nil { err = backend.EvaluateMatchPreconditions(string(b), input.IfMatch, input.IfNoneMatch) if err != nil { @@ -2913,6 +2916,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje // evaluate preconditions etagBytes, err := p.meta.RetrieveAttribute(nil, *po.Bucket, *po.Key, etagkey) + if errors.Is(err, fs.ErrNotExist) && (po.IfMatch != nil || po.IfNoneMatch != nil) { + return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrNoSuchKey) + } if err == nil { err := backend.EvaluateMatchPreconditions(string(etagBytes), po.IfMatch, po.IfNoneMatch) if err != nil { diff --git a/tests/integration/CompleteMultipartUpload.go b/tests/integration/CompleteMultipartUpload.go index 119e993..5e44964 100644 --- a/tests/integration/CompleteMultipartUpload.go +++ b/tests/integration/CompleteMultipartUpload.go @@ -1377,6 +1377,7 @@ func CompleteMultipartUpload_conditional_writes(s *S3Conf) error { var etagTrimmed string incorrectEtag := getPtr("incorrect_etag") errPrecond := s3err.GetAPIError(s3err.ErrPreconditionFailed) + errNoSuchKey := s3err.GetAPIError(s3err.ErrNoSuchKey) for i, test := range []struct { obj string @@ -1393,13 +1394,13 @@ func CompleteMultipartUpload_conditional_writes(s *S3Conf) error { {obj, nil, incorrectEtag, nil}, {obj, nil, etag, errPrecond}, {obj, nil, nil, nil}, - // should ignore the precondition headers if - // an object with the given name doesn't exist - {"obj-1", incorrectEtag, etag, nil}, - {"obj-2", etag, etag, nil}, - {"obj-3", etag, incorrectEtag, nil}, - {"obj-4", incorrectEtag, nil, nil}, - {"obj-5", nil, etag, nil}, + // should return NoSuchKey error, if any precondition + // header is present, but object doesn't exist + {"obj-1", incorrectEtag, etag, errNoSuchKey}, + {"obj-2", etag, etag, errNoSuchKey}, + {"obj-3", etag, incorrectEtag, errNoSuchKey}, + {"obj-4", incorrectEtag, nil, errNoSuchKey}, + {"obj-5", nil, etag, errNoSuchKey}, // precondtion headers without quotes {obj, &etagTrimmed, nil, nil}, diff --git a/tests/integration/PutObject.go b/tests/integration/PutObject.go index 68ce81d..516e085 100644 --- a/tests/integration/PutObject.go +++ b/tests/integration/PutObject.go @@ -316,6 +316,7 @@ func PutObject_conditional_writes(s *S3Conf) error { etagTrimmed := strings.Trim(*etag, `"`) incorrectEtag := getPtr("incorrect_etag") errPrecond := s3err.GetAPIError(s3err.ErrPreconditionFailed) + errNoSuchKey := s3err.GetAPIError(s3err.ErrNoSuchKey) for i, test := range []struct { obj string @@ -340,13 +341,13 @@ func PutObject_conditional_writes(s *S3Conf) error { {obj, incorrectEtag, &etagTrimmed, errPrecond}, {obj, nil, &etagTrimmed, errPrecond}, - // should ignore the precondition headers if - // an object with the given name doesn't exist - {"obj-1", incorrectEtag, etag, nil}, - {"obj-2", etag, etag, nil}, - {"obj-3", etag, incorrectEtag, nil}, - {"obj-4", incorrectEtag, nil, nil}, - {"obj-5", nil, etag, nil}, + // should return NoSuchKey error, if any precondition + // header is present, but object doesn't exist + {"obj-1", incorrectEtag, etag, errNoSuchKey}, + {"obj-2", etag, etag, errNoSuchKey}, + {"obj-3", etag, incorrectEtag, errNoSuchKey}, + {"obj-4", incorrectEtag, nil, errNoSuchKey}, + {"obj-5", nil, etag, errNoSuchKey}, } { res, err := putObjectWithData(0, &s3.PutObjectInput{ Bucket: &bucket,