From 95476b1c9ca04b5daa4dbf94f0d3bd1c2684a099 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Fri, 2 Jul 2021 19:21:53 +0300 Subject: [PATCH] [#95] Supported copy match headers Supported x-amz-copy-source-if-match and x-amz-copy-source-if-none-match. Signed-off-by: Denis Kirillov --- api/handler/copy.go | 19 +++++++++---------- api/handler/get.go | 14 +++++++++----- api/handler/get_test.go | 30 +++++++++++++++--------------- api/headers.go | 2 ++ 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/api/handler/copy.go b/api/handler/copy.go index 67d5ebb5..cb477639 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -13,8 +13,7 @@ import ( ) type copyObjectArgs struct { - IfModifiedSince *time.Time - IfUnmodifiedSince *time.Time + Conditional *conditionalArgs } // path2BucketObject returns bucket and object. @@ -74,12 +73,9 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { return } - if args.IfModifiedSince != nil && inf.Created.Before(*args.IfModifiedSince) { - w.WriteHeader(http.StatusNotModified) - return - } - if args.IfUnmodifiedSince != nil && inf.Created.After(*args.IfUnmodifiedSince) { - w.WriteHeader(http.StatusPreconditionFailed) + status := checkPreconditions(inf, args.Conditional) + if status != http.StatusOK { + w.WriteHeader(status) return } @@ -124,7 +120,10 @@ func writeErrorCopy(w http.ResponseWriter, r *http.Request, log *zap.Logger, msg func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) { var err error - args := ©ObjectArgs{} + args := &conditionalArgs{ + IfMatch: headers.Get(api.AmzCopyIfMatch), + IfNoneMatch: headers.Get(api.AmzCopyIfNoneMatch), + } if args.IfModifiedSince, err = parseHTTPTime(headers.Get(api.AmzCopyIfModifiedSince)); err != nil { return nil, err @@ -133,5 +132,5 @@ func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) { return nil, err } - return args, nil + return ©ObjectArgs{Conditional: args}, nil } diff --git a/api/handler/get.go b/api/handler/get.go index 65475d46..7958b23e 100644 --- a/api/handler/get.go +++ b/api/handler/get.go @@ -13,13 +13,17 @@ import ( "go.uber.org/zap" ) -type getObjectArgs struct { +type conditionalArgs struct { IfModifiedSince *time.Time IfUnmodifiedSince *time.Time IfMatch string IfNoneMatch string } +type getObjectArgs struct { + Conditional *conditionalArgs +} + func fetchRangeHeader(headers http.Header, fullSize uint64) (*layer.RangeParams, error) { const prefix = "bytes=" rangeHeader := headers.Get("Range") @@ -92,7 +96,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { return } - status := checkGetPreconditions(inf, args) + status := checkPreconditions(inf, args.Conditional) if status != http.StatusOK { w.WriteHeader(status) return @@ -118,7 +122,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { } } -func checkGetPreconditions(inf *layer.ObjectInfo, args *getObjectArgs) int { +func checkPreconditions(inf *layer.ObjectInfo, args *conditionalArgs) int { if len(args.IfMatch) > 0 && args.IfMatch != inf.HashSum { return http.StatusPreconditionFailed } @@ -139,7 +143,7 @@ func checkGetPreconditions(inf *layer.ObjectInfo, args *getObjectArgs) int { func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) { var err error - args := &getObjectArgs{ + args := &conditionalArgs{ IfMatch: headers.Get(api.IfMatch), IfNoneMatch: headers.Get(api.IfNoneMatch), } @@ -151,7 +155,7 @@ func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) { return nil, err } - return args, nil + return &getObjectArgs{Conditional: args}, nil } func parseHTTPTime(data string) (*time.Time, error) { diff --git a/api/handler/get_test.go b/api/handler/get_test.go index 7a6b55b6..9ebc51d5 100644 --- a/api/handler/get_test.go +++ b/api/handler/get_test.go @@ -57,84 +57,84 @@ func TestPreconditions(t *testing.T) { for _, tc := range []struct { name string info *layer.ObjectInfo - args *getObjectArgs + args *conditionalArgs expected int }{ { name: "no conditions", info: new(layer.ObjectInfo), - args: new(getObjectArgs), + args: new(conditionalArgs), expected: http.StatusOK, }, { name: "IfMatch true", info: newInfo(etag, today), - args: &getObjectArgs{IfMatch: etag}, + args: &conditionalArgs{IfMatch: etag}, expected: http.StatusOK, }, { name: "IfMatch false", info: newInfo(etag, today), - args: &getObjectArgs{IfMatch: etag2}, + args: &conditionalArgs{IfMatch: etag2}, expected: http.StatusPreconditionFailed}, { name: "IfNoneMatch true", info: newInfo(etag, today), - args: &getObjectArgs{IfNoneMatch: etag2}, + args: &conditionalArgs{IfNoneMatch: etag2}, expected: http.StatusOK}, { name: "IfNoneMatch false", info: newInfo(etag, today), - args: &getObjectArgs{IfNoneMatch: etag}, + args: &conditionalArgs{IfNoneMatch: etag}, expected: http.StatusNotModified}, { name: "IfModifiedSince true", info: newInfo(etag, today), - args: &getObjectArgs{IfModifiedSince: &yesterday}, + args: &conditionalArgs{IfModifiedSince: &yesterday}, expected: http.StatusOK}, { name: "IfModifiedSince false", info: newInfo(etag, yesterday), - args: &getObjectArgs{IfModifiedSince: &today}, + args: &conditionalArgs{IfModifiedSince: &today}, expected: http.StatusNotModified}, { name: "IfUnmodifiedSince true", info: newInfo(etag, yesterday), - args: &getObjectArgs{IfUnmodifiedSince: &today}, + args: &conditionalArgs{IfUnmodifiedSince: &today}, expected: http.StatusOK}, { name: "IfUnmodifiedSince false", info: newInfo(etag, today), - args: &getObjectArgs{IfUnmodifiedSince: &yesterday}, + args: &conditionalArgs{IfUnmodifiedSince: &yesterday}, expected: http.StatusPreconditionFailed}, { name: "IfMatch true, IfUnmodifiedSince false", info: newInfo(etag, today), - args: &getObjectArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday}, + args: &conditionalArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday}, expected: http.StatusOK, }, { name: "IfMatch false, IfUnmodifiedSince true", info: newInfo(etag, yesterday), - args: &getObjectArgs{IfMatch: etag2, IfUnmodifiedSince: &today}, + args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today}, expected: http.StatusPreconditionFailed, }, { name: "IfNoneMatch false, IfModifiedSince true", info: newInfo(etag, today), - args: &getObjectArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday}, + args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday}, expected: http.StatusNotModified, }, { name: "IfNoneMatch true, IfModifiedSince false", info: newInfo(etag, yesterday), - args: &getObjectArgs{IfNoneMatch: etag2, IfModifiedSince: &today}, + args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today}, expected: http.StatusNotModified, }, } { t.Run(tc.name, func(t *testing.T) { - actual := checkGetPreconditions(tc.info, tc.args) + actual := checkPreconditions(tc.info, tc.args) require.Equal(t, tc.expected, actual) }) } diff --git a/api/headers.go b/api/headers.go index 81782f6d..e983a342 100644 --- a/api/headers.go +++ b/api/headers.go @@ -29,4 +29,6 @@ const ( AmzCopyIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since" AmzCopyIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since" + AmzCopyIfMatch = "X-Amz-Copy-Source-If-Match" + AmzCopyIfNoneMatch = "X-Amz-Copy-Source-If-None-Match" )