2020-08-19 23:35:51 +00:00
package handler
import (
"net/http"
"net/url"
2022-08-24 13:12:05 +00:00
"regexp"
2020-08-19 23:35:51 +00:00
"time"
2023-03-07 14:38:08 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
2023-07-05 14:05:45 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
2023-08-23 11:07:52 +00:00
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
2020-08-19 23:35:51 +00:00
"go.uber.org/zap"
)
2021-07-01 07:45:55 +00:00
type copyObjectArgs struct {
2021-08-06 15:08:09 +00:00
Conditional * conditionalArgs
MetadataDirective string
2022-08-23 14:13:59 +00:00
TaggingDirective string
2021-07-01 07:45:55 +00:00
}
2022-08-23 14:13:59 +00:00
const (
replaceDirective = "REPLACE"
copyDirective = "COPY"
)
2021-08-06 15:08:09 +00:00
2022-08-24 13:12:05 +00:00
var copySourceMatcher = auth . NewRegexpMatcher ( regexp . MustCompile ( ` ^/?(?P<bucket_name>[a-z0-9.\-] { 3,63})/(?P<object_name>.+)$ ` ) )
2022-04-13 16:56:58 +00:00
// path2BucketObject returns a bucket and an object.
2022-08-24 13:12:05 +00:00
func path2BucketObject ( path string ) ( string , string , error ) {
matches := copySourceMatcher . GetSubmatches ( path )
if len ( matches ) != 2 {
return "" , "" , errors . GetAPIError ( errors . ErrInvalidRequest )
2020-08-19 23:35:51 +00:00
}
2022-08-24 13:12:05 +00:00
return matches [ "bucket_name" ] , matches [ "object_name" ] , nil
2020-08-19 23:35:51 +00:00
}
func ( h * handler ) CopyObjectHandler ( w http . ResponseWriter , r * http . Request ) {
var (
2024-05-28 12:50:34 +00:00
err error
versionID string
metadata map [ string ] string
tagSet map [ string ] string
2020-08-19 23:35:51 +00:00
2023-06-09 13:19:23 +00:00
ctx = r . Context ( )
2023-07-05 14:05:45 +00:00
reqInfo = middleware . GetReqInfo ( ctx )
2022-06-03 13:37:38 +00:00
2024-03-19 13:56:32 +00:00
cannedACLStatus = aclHeadersStatus ( r )
2020-08-19 23:35:51 +00:00
)
2022-08-23 14:13:59 +00:00
src := r . Header . Get ( api . AmzCopySource )
2020-08-19 23:35:51 +00:00
// Check https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectVersioning.html
// Regardless of whether you have enabled versioning, each object in your bucket
// has a version ID. If you have not enabled versioning, Amazon S3 sets the value
// of the version ID to null. If you have enabled versioning, Amazon S3 assigns a
// unique version ID value for the object.
if u , err := url . Parse ( src ) ; err == nil {
2021-08-19 06:55:22 +00:00
versionID = u . Query ( ) . Get ( api . QueryVersionID )
2020-08-19 23:35:51 +00:00
src = u . Path
}
2022-08-24 13:12:05 +00:00
srcBucket , srcObject , err := path2BucketObject ( src )
if err != nil {
h . logAndSendError ( w , "invalid source copy" , reqInfo , err )
return
}
2020-08-19 23:35:51 +00:00
2022-10-14 14:36:43 +00:00
srcObjPrm := & layer . HeadObjectParams {
2021-08-10 10:03:09 +00:00
Object : srcObject ,
2021-08-19 06:55:22 +00:00
VersionID : versionID ,
2021-08-10 10:03:09 +00:00
}
2021-07-01 07:45:55 +00:00
2022-10-14 14:36:43 +00:00
if srcObjPrm . BktInfo , err = h . getBucketAndCheckOwner ( r , srcBucket , api . AmzSourceExpectedBucketOwner ) ; err != nil {
2022-03-18 13:04:09 +00:00
h . logAndSendError ( w , "couldn't get source bucket" , reqInfo , err )
2021-08-23 08:19:41 +00:00
return
}
2022-03-18 13:04:09 +00:00
dstBktInfo , err := h . getBucketAndCheckOwner ( r , reqInfo . BucketName )
if err != nil {
h . logAndSendError ( w , "couldn't get target bucket" , reqInfo , err )
2021-08-23 08:19:41 +00:00
return
}
2023-06-09 13:19:23 +00:00
settings , err := h . obj . GetBucketSettings ( ctx , dstBktInfo )
2022-08-29 13:08:05 +00:00
if err != nil {
h . logAndSendError ( w , "could not get bucket settings" , reqInfo , err )
return
}
2024-05-28 12:50:34 +00:00
if cannedACLStatus == aclStatusYes {
2024-03-19 13:56:32 +00:00
h . logAndSendError ( w , "acl not supported for this bucket" , reqInfo , errors . GetAPIError ( errors . ErrAccessControlListNotSupported ) )
return
}
2023-06-09 13:19:23 +00:00
extendedSrcObjInfo , err := h . obj . GetExtendedObjectInfo ( ctx , srcObjPrm )
2022-06-28 13:35:05 +00:00
if err != nil {
2021-08-05 09:18:52 +00:00
h . logAndSendError ( w , "could not find object" , reqInfo , err )
2021-07-01 07:45:55 +00:00
return
}
2022-10-14 14:36:43 +00:00
srcObjInfo := extendedSrcObjInfo . ObjectInfo
2021-07-01 07:45:55 +00:00
2023-10-19 14:22:26 +00:00
srcEncryptionParams , err := formCopySourceEncryptionParams ( r )
if err != nil {
h . logAndSendError ( w , "invalid sse headers" , reqInfo , err )
return
}
dstEncryptionParams , err := formEncryptionParams ( r )
2023-07-12 09:08:56 +00:00
if err != nil {
h . logAndSendError ( w , "invalid sse headers" , reqInfo , err )
return
}
2023-10-19 14:22:26 +00:00
if err = srcEncryptionParams . MatchObjectEncryption ( layer . FormEncryptionInfo ( srcObjInfo . Headers ) ) ; err != nil {
if errors . IsS3Error ( err , errors . ErrInvalidEncryptionParameters ) || errors . IsS3Error ( err , errors . ErrSSEEncryptedObject ) ||
errors . IsS3Error ( err , errors . ErrInvalidSSECustomerParameters ) {
h . logAndSendError ( w , "encryption doesn't match object" , reqInfo , err , zap . Error ( err ) )
return
}
2023-07-12 09:08:56 +00:00
h . logAndSendError ( w , "encryption doesn't match object" , reqInfo , errors . GetAPIError ( errors . ErrBadRequest ) , zap . Error ( err ) )
return
}
2023-10-19 14:22:26 +00:00
var dstSize uint64
2024-02-16 15:24:27 +00:00
srcSize , err := layer . GetObjectSize ( srcObjInfo )
if err != nil {
2023-07-12 09:08:56 +00:00
h . logAndSendError ( w , "failed to get source object size" , reqInfo , err )
return
2024-02-16 15:24:27 +00:00
} else if srcSize > layer . UploadMaxSize { // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
2023-07-12 09:08:56 +00:00
h . logAndSendError ( w , "too bid object to copy with single copy operation, use multipart upload copy instead" , reqInfo , errors . GetAPIError ( errors . ErrInvalidRequestLargeCopy ) )
return
}
2024-02-16 15:24:27 +00:00
dstSize = srcSize
2023-07-12 09:08:56 +00:00
2022-08-23 14:13:59 +00:00
args , err := parseCopyObjectArgs ( r . Header )
if err != nil {
h . logAndSendError ( w , "could not parse request params" , reqInfo , err )
return
}
2022-08-29 13:08:05 +00:00
if isCopyingToItselfForbidden ( reqInfo , srcBucket , srcObject , settings , args ) {
h . logAndSendError ( w , "copying to itself without changing anything" , reqInfo , errors . GetAPIError ( errors . ErrInvalidCopyDest ) )
return
}
2022-08-23 14:13:59 +00:00
if args . MetadataDirective == replaceDirective {
metadata = parseMetadata ( r )
}
if args . TaggingDirective == replaceDirective {
tagSet , err = parseTaggingHeader ( r . Header )
if err != nil {
h . logAndSendError ( w , "could not parse tagging header" , reqInfo , err )
return
}
} else {
2024-04-10 06:41:07 +00:00
tagPrm := & data . GetObjectTaggingParams {
ObjectVersion : & data . ObjectVersion {
2022-10-14 14:36:43 +00:00
BktInfo : srcObjPrm . BktInfo ,
ObjectName : srcObject ,
VersionID : srcObjInfo . VersionID ( ) ,
} ,
NodeVersion : extendedSrcObjInfo . NodeVersion ,
2022-08-23 14:13:59 +00:00
}
2023-06-09 13:19:23 +00:00
_ , tagSet , err = h . obj . GetObjectTagging ( ctx , tagPrm )
2022-08-23 14:13:59 +00:00
if err != nil {
h . logAndSendError ( w , "could not get object tagging" , reqInfo , err )
return
}
}
2023-10-27 15:15:33 +00:00
if err = checkPreconditions ( srcObjInfo , args . Conditional , h . cfg . MD5Enabled ( ) ) ; err != nil {
2021-08-09 08:53:58 +00:00
h . logAndSendError ( w , "precondition failed" , reqInfo , errors . GetAPIError ( errors . ErrPreconditionFailed ) )
2021-07-01 07:45:55 +00:00
return
}
2021-08-06 15:08:09 +00:00
if metadata == nil {
2022-10-14 14:36:43 +00:00
if len ( srcObjInfo . ContentType ) > 0 {
srcObjInfo . Headers [ api . ContentType ] = srcObjInfo . ContentType
2021-08-06 15:08:09 +00:00
}
2023-07-12 09:08:56 +00:00
metadata = makeCopyMap ( srcObjInfo . Headers )
2023-10-19 14:22:26 +00:00
filterMetadataMap ( metadata )
2021-08-06 15:08:09 +00:00
} else if contentType := r . Header . Get ( api . ContentType ) ; len ( contentType ) > 0 {
metadata [ api . ContentType ] = contentType
}
2023-04-24 23:49:12 +00:00
params := & layer . CopyObjectParams {
2023-10-19 14:22:26 +00:00
SrcVersioned : srcObjPrm . Versioned ( ) ,
SrcObject : srcObjInfo ,
ScrBktInfo : srcObjPrm . BktInfo ,
DstBktInfo : dstBktInfo ,
DstObject : reqInfo . ObjectName ,
DstSize : dstSize ,
Header : metadata ,
SrcEncryption : srcEncryptionParams ,
DstEncryption : dstEncryptionParams ,
2023-04-24 23:49:12 +00:00
}
2023-11-21 08:51:07 +00:00
params . CopiesNumbers , err = h . pickCopiesNumbers ( metadata , reqInfo . Namespace , dstBktInfo . LocationConstraint )
2022-08-17 11:18:36 +00:00
if err != nil {
h . logAndSendError ( w , "invalid copies number" , reqInfo , err )
return
}
2023-06-09 13:19:23 +00:00
params . Lock , err = formObjectLock ( ctx , dstBktInfo , settings . LockConfiguration , r . Header )
2022-03-01 15:07:15 +00:00
if err != nil {
2022-02-28 10:22:07 +00:00
h . logAndSendError ( w , "could not form object lock" , reqInfo , err )
return
}
2021-08-06 15:08:09 +00:00
additional := [ ] zap . Field { zap . String ( "src_bucket_name" , srcBucket ) , zap . String ( "src_object_name" , srcObject ) }
2023-06-09 13:19:23 +00:00
extendedDstObjInfo , err := h . obj . CopyObject ( ctx , params )
2022-10-14 14:36:43 +00:00
if err != nil {
2021-08-06 15:08:09 +00:00
h . logAndSendError ( w , "couldn't copy object" , reqInfo , err , additional ... )
2020-10-24 13:09:22 +00:00
return
2022-10-14 14:36:43 +00:00
}
dstObjInfo := extendedDstObjInfo . ObjectInfo
2023-10-27 15:15:33 +00:00
if err = middleware . EncodeToResponse ( w , & CopyObjectResponse {
LastModified : dstObjInfo . Created . UTC ( ) . Format ( time . RFC3339 ) ,
ETag : data . Quote ( dstObjInfo . ETag ( h . cfg . MD5Enabled ( ) ) ) ,
} ) ; err != nil {
2021-08-06 15:08:09 +00:00
h . logAndSendError ( w , "something went wrong" , reqInfo , err , additional ... )
2020-10-24 13:09:22 +00:00
return
2020-08-19 23:35:51 +00:00
}
2020-10-24 13:09:22 +00:00
2022-08-23 14:13:59 +00:00
if tagSet != nil {
2024-04-10 06:41:07 +00:00
tagPrm := & data . PutObjectTaggingParams {
ObjectVersion : & data . ObjectVersion {
2022-10-14 14:36:43 +00:00
BktInfo : dstBktInfo ,
ObjectName : reqInfo . ObjectName ,
VersionID : dstObjInfo . VersionID ( ) ,
} ,
TagSet : tagSet ,
NodeVersion : extendedDstObjInfo . NodeVersion ,
2022-08-23 14:13:59 +00:00
}
2024-06-25 12:24:29 +00:00
if err = h . obj . PutObjectTagging ( ctx , tagPrm ) ; err != nil {
2022-08-23 14:13:59 +00:00
h . logAndSendError ( w , "could not upload object tagging" , reqInfo , err )
return
}
}
2023-08-23 11:07:52 +00:00
h . reqLogger ( ctx ) . Info ( logs . ObjectIsCopied , zap . Stringer ( "object_id" , dstObjInfo . ID ) )
2022-03-31 06:21:38 +00:00
2023-10-19 14:22:26 +00:00
if dstEncryptionParams . Enabled ( ) {
2022-08-01 16:52:09 +00:00
addSSECHeaders ( w . Header ( ) , r . Header )
}
2020-08-19 23:35:51 +00:00
}
2021-07-01 07:45:55 +00:00
2023-07-12 09:08:56 +00:00
func makeCopyMap ( headers map [ string ] string ) map [ string ] string {
res := make ( map [ string ] string , len ( headers ) )
for key , val := range headers {
res [ key ] = val
}
return res
}
2023-10-19 14:22:26 +00:00
func filterMetadataMap ( metadata map [ string ] string ) {
delete ( metadata , layer . MultipartObjectSize ) // object payload will be real one rather than list of compound parts
for key := range layer . EncryptionMetadata {
delete ( metadata , key )
}
}
2023-07-05 14:05:45 +00:00
func isCopyingToItselfForbidden ( reqInfo * middleware . ReqInfo , srcBucket string , srcObject string , settings * data . BucketSettings , args * copyObjectArgs ) bool {
2022-08-29 13:08:05 +00:00
if reqInfo . BucketName != srcBucket || reqInfo . ObjectName != srcObject {
return false
}
if ! settings . Unversioned ( ) {
return false
}
return args . MetadataDirective != replaceDirective
}
2021-07-01 07:45:55 +00:00
func parseCopyObjectArgs ( headers http . Header ) ( * copyObjectArgs , error ) {
var err error
2021-07-02 16:21:53 +00:00
args := & conditionalArgs {
2023-10-27 15:15:33 +00:00
IfMatch : data . UnQuote ( headers . Get ( api . AmzCopyIfMatch ) ) ,
IfNoneMatch : data . UnQuote ( headers . Get ( api . AmzCopyIfNoneMatch ) ) ,
2021-07-02 16:21:53 +00:00
}
2021-07-01 07:45:55 +00:00
if args . IfModifiedSince , err = parseHTTPTime ( headers . Get ( api . AmzCopyIfModifiedSince ) ) ; err != nil {
return nil , err
}
if args . IfUnmodifiedSince , err = parseHTTPTime ( headers . Get ( api . AmzCopyIfUnmodifiedSince ) ) ; err != nil {
return nil , err
}
2021-08-06 15:08:09 +00:00
copyArgs := & copyObjectArgs { Conditional : args }
2022-08-23 14:13:59 +00:00
2021-08-06 15:08:09 +00:00
copyArgs . MetadataDirective = headers . Get ( api . AmzMetadataDirective )
2022-08-23 14:13:59 +00:00
if ! isValidDirective ( copyArgs . MetadataDirective ) {
return nil , errors . GetAPIError ( errors . ErrInvalidMetadataDirective )
}
copyArgs . TaggingDirective = headers . Get ( api . AmzTaggingDirective )
if ! isValidDirective ( copyArgs . TaggingDirective ) {
return nil , errors . GetAPIError ( errors . ErrInvalidTaggingDirective )
}
2021-08-06 15:08:09 +00:00
return copyArgs , nil
2021-07-01 07:45:55 +00:00
}
2022-08-23 14:13:59 +00:00
func isValidDirective ( directive string ) bool {
return len ( directive ) == 0 ||
directive == replaceDirective || directive == copyDirective
}