diff --git a/weed/s3api/s3api_object_handlers_conditional_write_test.go b/weed/s3api/s3api_object_handlers_conditional_write_test.go new file mode 100644 index 0000000000..fb329c06cb --- /dev/null +++ b/weed/s3api/s3api_object_handlers_conditional_write_test.go @@ -0,0 +1,58 @@ +package s3api + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" +) + +// A conditional write with If-Match has nothing to compare against when the key has +// no current version; AWS answers 404 there, not 412. +func TestValidateConditionalHeadersMissingObject(t *testing.T) { + s3a := &S3ApiServer{} + + existing := &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Mtime: time.Now().Unix()}, + Extended: map[string][]byte{s3_constants.ExtETagKey: []byte("d41d8cd98f00b204e9800998ecf8427e")}, + } + deleteMarker := &filer_pb.Entry{ + Attributes: &filer_pb.FuseAttributes{Mtime: time.Now().Unix()}, + Extended: map[string][]byte{s3_constants.ExtDeleteMarkerKey: []byte("true")}, + } + + testCases := []struct { + name string + header string + value string + entry *filer_pb.Entry + want s3err.ErrorCode + }{ + {"if-match on missing object", s3_constants.IfMatch, "d41d8cd98f00b204e9800998ecf8427e", nil, s3err.ErrNoSuchKey}, + {"if-match star on missing object", s3_constants.IfMatch, "*", nil, s3err.ErrNoSuchKey}, + {"if-match on delete marker", s3_constants.IfMatch, "d41d8cd98f00b204e9800998ecf8427e", deleteMarker, s3err.ErrNoSuchKey}, + {"if-match mismatch on existing object", s3_constants.IfMatch, "0000", existing, s3err.ErrPreconditionFailed}, + {"if-match hit on existing object", s3_constants.IfMatch, "d41d8cd98f00b204e9800998ecf8427e", existing, s3err.ErrNone}, + {"if-none-match star on missing object", s3_constants.IfNoneMatch, "*", nil, s3err.ErrNone}, + {"if-none-match star on delete marker", s3_constants.IfNoneMatch, "*", deleteMarker, s3err.ErrNone}, + {"if-none-match star on existing object", s3_constants.IfNoneMatch, "*", existing, s3err.ErrPreconditionFailed}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := httptest.NewRequest(http.MethodPut, "/bucket/object", nil) + r.Header.Set(tc.header, tc.value) + headers, errCode := parseConditionalHeaders(r) + if errCode != s3err.ErrNone { + t.Fatalf("parseConditionalHeaders: %v", errCode) + } + if got := s3a.validateConditionalHeaders(r, headers, tc.entry, "bucket", "object"); got != tc.want { + t.Errorf("got %v, want %v", got, tc.want) + } + }) + } +} diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index e8530950e0..3b895f9260 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -2796,8 +2796,9 @@ func (s3a *S3ApiServer) validateConditionalHeaders(r *http.Request, headers cond // 1. Check If-Match if headers.ifMatch != "" { + // AWS answers 404 when there is no current version (or it is a delete marker) if !objectExists { - return s3err.ErrPreconditionFailed + return s3err.ErrNoSuchKey } // If `ifMatch` is "*", the condition is met if the object exists. // Otherwise, we need to check the ETag.