forked from TrueCloudLab/frostfs-s3-gw
[#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:
parent
568d7ac810
commit
95476b1c9c
4 changed files with 35 additions and 30 deletions
|
@ -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 := ©ObjectArgs{}
|
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 ©ObjectArgs{Conditional: args}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue