diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index c768684a..f10bafbd 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -5,11 +5,13 @@ import ( "encoding/json" "encoding/xml" "net/http" + "net/url" "strconv" "time" "github.com/google/uuid" "github.com/nspcc-dev/neofs-s3-gw/api" + "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/api/errors" "github.com/nspcc-dev/neofs-s3-gw/api/layer" "go.uber.org/zap" @@ -251,6 +253,8 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) { } var ( + versionID string + queryValues = reqInfo.URL.Query() uploadID = queryValues.Get(uploadIDHeaderName) additional = []zap.Field{zap.String("uploadID", uploadID), zap.String("Key", reqInfo.ObjectName)} @@ -263,6 +267,10 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) { } src := r.Header.Get(api.AmzCopySource) + if u, err := url.Parse(src); err == nil { + versionID = u.Query().Get(api.QueryVersionID) + src = u.Path + } srcBucket, srcObject := path2BucketObject(src) srcRange, err := parseRange(r.Header.Get(api.AmzCopySourceRange)) @@ -273,13 +281,29 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) { } srcInfo, err := h.obj.GetObjectInfo(r.Context(), &layer.HeadObjectParams{ - Bucket: srcBucket, - Object: srcObject, + Bucket: srcBucket, + Object: srcObject, + VersionID: versionID, }) if err != nil { + if errors.IsS3Error(err, errors.ErrNoSuchKey) && versionID != "" { + h.logAndSendError(w, "could not head source object version", reqInfo, + errors.GetAPIError(errors.ErrBadRequest), additional...) + return + } h.logAndSendError(w, "could not head source object", reqInfo, err, additional...) return } + if isDeleted(srcInfo) { + if versionID != "" { + h.logAndSendError(w, "could not head source object version", reqInfo, + errors.GetAPIError(errors.ErrBadRequest), additional...) + return + } + h.logAndSendError(w, "could not head source object", reqInfo, + errors.GetAPIError(errors.ErrNoSuchKey), additional...) + return + } args, err := parseCopyObjectArgs(r.Header) if err != nil { @@ -643,3 +667,7 @@ func encodeListPartsToResponse(info *layer.ListPartsInfo, params *layer.ListPart Parts: info.Parts, } } + +func isDeleted(objInfo *data.ObjectInfo) bool { + return objInfo.Headers[layer.VersionsDeleteMarkAttr] == layer.DelMarkFullObject +} diff --git a/api/layer/layer.go b/api/layer/layer.go index 432aa8ca..374f9c42 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -571,7 +571,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, obj *Ver p := &PutObjectParams{ Object: obj.Name, Reader: bytes.NewReader(nil), - Header: map[string]string{versionsDeleteMarkAttr: obj.VersionID}, + Header: map[string]string{VersionsDeleteMarkAttr: obj.VersionID}, } if len(obj.VersionID) != 0 { version, err := n.checkVersionsExist(ctx, bkt, obj) @@ -580,13 +580,13 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, obj *Ver return obj } ids = []*object.ID{version.ID} - if version.Headers[versionsDeleteMarkAttr] == delMarkFullObject { + if version.Headers[VersionsDeleteMarkAttr] == DelMarkFullObject { obj.DeleteMarkVersion = version.Version() } p.Header[versionsDelAttr] = obj.VersionID } else { - p.Header[versionsDeleteMarkAttr] = delMarkFullObject + p.Header[VersionsDeleteMarkAttr] = DelMarkFullObject } objInfo, err := n.objectPut(ctx, bkt, p) if err != nil { diff --git a/api/layer/object.go b/api/layer/object.go index f5fa8d72..020dcc7a 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -171,7 +171,7 @@ func (n *layer) objectPut(ctx context.Context, bkt *data.BucketInfo, p *PutObjec return nil, err } - if p.Header[versionsDeleteMarkAttr] == delMarkFullObject { + if p.Header[VersionsDeleteMarkAttr] == DelMarkFullObject { if last := versions.getLast(); last != nil { n.objCache.Delete(last.Address()) } diff --git a/api/layer/versioning.go b/api/layer/versioning.go index bf5ebd20..49fda85a 100644 --- a/api/layer/versioning.go +++ b/api/layer/versioning.go @@ -21,14 +21,15 @@ type objectVersions struct { } const ( + VersionsDeleteMarkAttr = "S3-Versions-delete-mark" + DelMarkFullObject = "*" + unversionedObjectVersionID = "null" objectSystemAttributeName = "S3-System-name" attrVersionsIgnore = "S3-Versions-ignore" attrSettingsVersioningEnabled = "S3-Settings-Versioning-enabled" versionsDelAttr = "S3-Versions-del" versionsAddAttr = "S3-Versions-add" - versionsDeleteMarkAttr = "S3-Versions-delete-mark" - delMarkFullObject = "*" ) func newObjectVersions(name string) *objectVersions { @@ -169,11 +170,11 @@ func (v *objectVersions) getLast() *data.ObjectInfo { existedVersions := v.existedVersions() for i := len(v.objects) - 1; i >= 0; i-- { if contains(existedVersions, v.objects[i].Version()) { - delMarkHeader := v.objects[i].Headers[versionsDeleteMarkAttr] + delMarkHeader := v.objects[i].Headers[VersionsDeleteMarkAttr] if delMarkHeader == "" { return v.objects[i] } - if delMarkHeader == delMarkFullObject { + if delMarkHeader == DelMarkFullObject { return nil } } @@ -203,8 +204,8 @@ func (v *objectVersions) getFiltered(reverse bool) []*data.ObjectInfo { res := make([]*data.ObjectInfo, 0, len(v.objects)) for _, version := range v.objects { - delMark := version.Headers[versionsDeleteMarkAttr] - if contains(existedVersions, version.Version()) && (delMark == delMarkFullObject || delMark == "") { + delMark := version.Headers[VersionsDeleteMarkAttr] + if contains(existedVersions, version.Version()) && (delMark == DelMarkFullObject || delMark == "") { res = append(res, version) } } @@ -335,7 +336,7 @@ func triageVersions(objVersions []*ObjectVersionInfo) ([]*ObjectVersionInfo, []* var resDelMarkVersions []*ObjectVersionInfo for _, version := range objVersions { - if version.Object.Headers[versionsDeleteMarkAttr] == delMarkFullObject { + if version.Object.Headers[VersionsDeleteMarkAttr] == DelMarkFullObject { resDelMarkVersions = append(resDelMarkVersions, version) } else { resVersion = append(resVersion, version) diff --git a/api/layer/versioning_test.go b/api/layer/versioning_test.go index 408b7bbf..3ff6544d 100644 --- a/api/layer/versioning_test.go +++ b/api/layer/versioning_test.go @@ -672,7 +672,7 @@ func getTestObjectInfo(id byte, addAttr, delAttr, delMarkAttr string) *data.Obje headers[versionsDelAttr] = delAttr } if delMarkAttr != "" { - headers[versionsDeleteMarkAttr] = delMarkAttr + headers[VersionsDeleteMarkAttr] = delMarkAttr } return &data.ObjectInfo{