[#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 <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2021-07-02 19:21:53 +03:00
parent 568d7ac810
commit 95476b1c9c
4 changed files with 35 additions and 30 deletions

View file

@ -13,8 +13,7 @@ import (
) )
type copyObjectArgs struct { type copyObjectArgs struct {
IfModifiedSince *time.Time Conditional *conditionalArgs
IfUnmodifiedSince *time.Time
} }
// path2BucketObject returns bucket and object. // path2BucketObject returns bucket and object.
@ -74,12 +73,9 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
if args.IfModifiedSince != nil && inf.Created.Before(*args.IfModifiedSince) { status := checkPreconditions(inf, args.Conditional)
w.WriteHeader(http.StatusNotModified) if status != http.StatusOK {
return w.WriteHeader(status)
}
if args.IfUnmodifiedSince != nil && inf.Created.After(*args.IfUnmodifiedSince) {
w.WriteHeader(http.StatusPreconditionFailed)
return return
} }
@ -124,7 +120,10 @@ func writeErrorCopy(w http.ResponseWriter, r *http.Request, log *zap.Logger, msg
func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) { func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
var err error var err error
args := &copyObjectArgs{} args := &conditionalArgs{
IfMatch: headers.Get(api.AmzCopyIfMatch),
IfNoneMatch: headers.Get(api.AmzCopyIfNoneMatch),
}
if args.IfModifiedSince, err = parseHTTPTime(headers.Get(api.AmzCopyIfModifiedSince)); err != nil { if args.IfModifiedSince, err = parseHTTPTime(headers.Get(api.AmzCopyIfModifiedSince)); err != nil {
return nil, err return nil, err
@ -133,5 +132,5 @@ func parseCopyObjectArgs(headers http.Header) (*copyObjectArgs, error) {
return nil, err return nil, err
} }
return args, nil return &copyObjectArgs{Conditional: args}, nil
} }

View file

@ -13,13 +13,17 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type getObjectArgs struct { type conditionalArgs struct {
IfModifiedSince *time.Time IfModifiedSince *time.Time
IfUnmodifiedSince *time.Time IfUnmodifiedSince *time.Time
IfMatch string IfMatch string
IfNoneMatch string IfNoneMatch string
} }
type getObjectArgs struct {
Conditional *conditionalArgs
}
func fetchRangeHeader(headers http.Header, fullSize uint64) (*layer.RangeParams, error) { func fetchRangeHeader(headers http.Header, fullSize uint64) (*layer.RangeParams, error) {
const prefix = "bytes=" const prefix = "bytes="
rangeHeader := headers.Get("Range") rangeHeader := headers.Get("Range")
@ -92,7 +96,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
status := checkGetPreconditions(inf, args) status := checkPreconditions(inf, args.Conditional)
if status != http.StatusOK { if status != http.StatusOK {
w.WriteHeader(status) w.WriteHeader(status)
return 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 { if len(args.IfMatch) > 0 && args.IfMatch != inf.HashSum {
return http.StatusPreconditionFailed return http.StatusPreconditionFailed
} }
@ -139,7 +143,7 @@ func checkGetPreconditions(inf *layer.ObjectInfo, args *getObjectArgs) int {
func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) { func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) {
var err error var err error
args := &getObjectArgs{ args := &conditionalArgs{
IfMatch: headers.Get(api.IfMatch), IfMatch: headers.Get(api.IfMatch),
IfNoneMatch: headers.Get(api.IfNoneMatch), IfNoneMatch: headers.Get(api.IfNoneMatch),
} }
@ -151,7 +155,7 @@ func parseGetObjectArgs(headers http.Header) (*getObjectArgs, error) {
return nil, err return nil, err
} }
return args, nil return &getObjectArgs{Conditional: args}, nil
} }
func parseHTTPTime(data string) (*time.Time, error) { func parseHTTPTime(data string) (*time.Time, error) {

View file

@ -57,84 +57,84 @@ func TestPreconditions(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
info *layer.ObjectInfo info *layer.ObjectInfo
args *getObjectArgs args *conditionalArgs
expected int expected int
}{ }{
{ {
name: "no conditions", name: "no conditions",
info: new(layer.ObjectInfo), info: new(layer.ObjectInfo),
args: new(getObjectArgs), args: new(conditionalArgs),
expected: http.StatusOK, expected: http.StatusOK,
}, },
{ {
name: "IfMatch true", name: "IfMatch true",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfMatch: etag}, args: &conditionalArgs{IfMatch: etag},
expected: http.StatusOK, expected: http.StatusOK,
}, },
{ {
name: "IfMatch false", name: "IfMatch false",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfMatch: etag2}, args: &conditionalArgs{IfMatch: etag2},
expected: http.StatusPreconditionFailed}, expected: http.StatusPreconditionFailed},
{ {
name: "IfNoneMatch true", name: "IfNoneMatch true",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfNoneMatch: etag2}, args: &conditionalArgs{IfNoneMatch: etag2},
expected: http.StatusOK}, expected: http.StatusOK},
{ {
name: "IfNoneMatch false", name: "IfNoneMatch false",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfNoneMatch: etag}, args: &conditionalArgs{IfNoneMatch: etag},
expected: http.StatusNotModified}, expected: http.StatusNotModified},
{ {
name: "IfModifiedSince true", name: "IfModifiedSince true",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfModifiedSince: &yesterday}, args: &conditionalArgs{IfModifiedSince: &yesterday},
expected: http.StatusOK}, expected: http.StatusOK},
{ {
name: "IfModifiedSince false", name: "IfModifiedSince false",
info: newInfo(etag, yesterday), info: newInfo(etag, yesterday),
args: &getObjectArgs{IfModifiedSince: &today}, args: &conditionalArgs{IfModifiedSince: &today},
expected: http.StatusNotModified}, expected: http.StatusNotModified},
{ {
name: "IfUnmodifiedSince true", name: "IfUnmodifiedSince true",
info: newInfo(etag, yesterday), info: newInfo(etag, yesterday),
args: &getObjectArgs{IfUnmodifiedSince: &today}, args: &conditionalArgs{IfUnmodifiedSince: &today},
expected: http.StatusOK}, expected: http.StatusOK},
{ {
name: "IfUnmodifiedSince false", name: "IfUnmodifiedSince false",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfUnmodifiedSince: &yesterday}, args: &conditionalArgs{IfUnmodifiedSince: &yesterday},
expected: http.StatusPreconditionFailed}, expected: http.StatusPreconditionFailed},
{ {
name: "IfMatch true, IfUnmodifiedSince false", name: "IfMatch true, IfUnmodifiedSince false",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday}, args: &conditionalArgs{IfMatch: etag, IfUnmodifiedSince: &yesterday},
expected: http.StatusOK, expected: http.StatusOK,
}, },
{ {
name: "IfMatch false, IfUnmodifiedSince true", name: "IfMatch false, IfUnmodifiedSince true",
info: newInfo(etag, yesterday), info: newInfo(etag, yesterday),
args: &getObjectArgs{IfMatch: etag2, IfUnmodifiedSince: &today}, args: &conditionalArgs{IfMatch: etag2, IfUnmodifiedSince: &today},
expected: http.StatusPreconditionFailed, expected: http.StatusPreconditionFailed,
}, },
{ {
name: "IfNoneMatch false, IfModifiedSince true", name: "IfNoneMatch false, IfModifiedSince true",
info: newInfo(etag, today), info: newInfo(etag, today),
args: &getObjectArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday}, args: &conditionalArgs{IfNoneMatch: etag, IfModifiedSince: &yesterday},
expected: http.StatusNotModified, expected: http.StatusNotModified,
}, },
{ {
name: "IfNoneMatch true, IfModifiedSince false", name: "IfNoneMatch true, IfModifiedSince false",
info: newInfo(etag, yesterday), info: newInfo(etag, yesterday),
args: &getObjectArgs{IfNoneMatch: etag2, IfModifiedSince: &today}, args: &conditionalArgs{IfNoneMatch: etag2, IfModifiedSince: &today},
expected: http.StatusNotModified, expected: http.StatusNotModified,
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { 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) require.Equal(t, tc.expected, actual)
}) })
} }

View file

@ -29,4 +29,6 @@ const (
AmzCopyIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since" AmzCopyIfModifiedSince = "X-Amz-Copy-Source-If-Modified-Since"
AmzCopyIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since" AmzCopyIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since"
AmzCopyIfMatch = "X-Amz-Copy-Source-If-Match"
AmzCopyIfNoneMatch = "X-Amz-Copy-Source-If-None-Match"
) )