@@ -819,6 +819,121 @@ void S3CrtClient::InitCommonCrtRequestOption(CrtRequestCallbackUserData* userDat
819819 options->finish_callback = S3CrtRequestFinishCallback;
820820}
821821
822+ Model::CopyObjectOutcome S3CrtClient::PopulateCopyObjectProperties (const Model::CopyObjectRequest& request,
823+ const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest) const {
824+ const bool copyMetadata = request.MetadataDirectiveHasBeenSet () && request.GetMetadataDirective () == Model::MetadataDirective::COPY ;
825+ const bool copyTags = request.TaggingDirectiveHasBeenSet () && request.GetTaggingDirective () == Model::TaggingDirective::COPY ;
826+
827+ if (!copyMetadata && !copyTags) {
828+ return Model::CopyObjectOutcome (Model::CopyObjectResult ());
829+ }
830+
831+ // Parse "[/]bucket/key[?versionId=...]" from x-amz-copy-source. rfind: keys may contain '?'.
832+ Aws::String copySource = request.GetCopySource ();
833+ Aws::String sourceVersionId;
834+ const auto queryPos = copySource.rfind (' ?' );
835+ if (queryPos != Aws::String::npos) {
836+ const Aws::String query = copySource.substr (queryPos + 1 );
837+ copySource = copySource.substr (0 , queryPos);
838+ const auto versionPos = query.find (" versionId=" );
839+ if (versionPos != Aws::String::npos) {
840+ sourceVersionId = query.substr (versionPos + Aws::String (" versionId=" ).size ());
841+ const auto ampPos = sourceVersionId.find (' &' );
842+ if (ampPos != Aws::String::npos) {
843+ sourceVersionId = sourceVersionId.substr (0 , ampPos);
844+ }
845+ }
846+ }
847+ if (!copySource.empty () && copySource.front () == ' /' ) {
848+ copySource = copySource.substr (1 );
849+ }
850+ const auto slashPos = copySource.find (' /' );
851+ if (slashPos == Aws::String::npos) {
852+ return Model::CopyObjectOutcome (Aws::Client::AWSError<S3CrtErrors>(S3CrtErrors::INVALID_PARAMETER_VALUE , " INVALID_PARAMETER_VALUE" ,
853+ " Could not parse bucket and key from CopySource for property copy" ,
854+ false ));
855+ }
856+ const Aws::String sourceBucket = copySource.substr (0 , slashPos);
857+ const Aws::String sourceKey = copySource.substr (slashPos + 1 );
858+
859+ Model::HeadObjectRequest headRequest;
860+ headRequest.SetBucket (sourceBucket);
861+ headRequest.SetKey (sourceKey);
862+ if (!sourceVersionId.empty ()) {
863+ headRequest.SetVersionId (sourceVersionId);
864+ }
865+ auto headOutcome = HeadObject (headRequest);
866+ if (!headOutcome.IsSuccess ()) {
867+ return Model::CopyObjectOutcome (headOutcome.GetError ());
868+ }
869+ const auto & head = headOutcome.GetResult ();
870+ const Aws::String sourceETag = head.GetETag ();
871+
872+ // Pin the source version so UploadPartCopy reads a stable object.
873+ if (sourceVersionId.empty () && !head.GetVersionId ().empty ()) {
874+ sourceVersionId = head.GetVersionId ();
875+ httpRequest->SetHeaderValue (" x-amz-copy-source" , Aws::Http::URI::URLEncodePath (copySource) + " ?versionId=" + sourceVersionId);
876+ }
877+ if (!httpRequest->HasHeader (" x-amz-copy-source-if-match" ) && !sourceETag.empty ()) {
878+ httpRequest->SetHeaderValue (" x-amz-copy-source-if-match" , sourceETag);
879+ }
880+
881+ if (copyMetadata) {
882+ if (!head.GetContentType ().empty ()) {
883+ httpRequest->SetHeaderValue (" content-type" , head.GetContentType ());
884+ }
885+ if (!head.GetContentEncoding ().empty ()) {
886+ httpRequest->SetHeaderValue (" content-encoding" , head.GetContentEncoding ());
887+ }
888+ if (!head.GetContentDisposition ().empty ()) {
889+ httpRequest->SetHeaderValue (" content-disposition" , head.GetContentDisposition ());
890+ }
891+ if (!head.GetContentLanguage ().empty ()) {
892+ httpRequest->SetHeaderValue (" content-language" , head.GetContentLanguage ());
893+ }
894+ if (!head.GetCacheControl ().empty ()) {
895+ httpRequest->SetHeaderValue (" cache-control" , head.GetCacheControl ());
896+ }
897+ if (!head.GetExpiresString ().empty ()) {
898+ httpRequest->SetHeaderValue (" expires" , head.GetExpires ().ToGmtString (Aws::Utils::DateFormat::RFC822 ));
899+ }
900+ for (const auto & item : head.GetMetadata ()) {
901+ httpRequest->SetHeaderValue (" x-amz-meta-" + item.first , item.second );
902+ }
903+ // REPLACE so CreateMultipartUpload emits these headers.
904+ httpRequest->SetHeaderValue (" x-amz-metadata-directive" , " REPLACE" );
905+ }
906+
907+ if (copyTags) {
908+ Model::GetObjectTaggingRequest taggingRequest;
909+ taggingRequest.SetBucket (sourceBucket);
910+ taggingRequest.SetKey (sourceKey);
911+ if (!sourceVersionId.empty ()) {
912+ taggingRequest.SetVersionId (sourceVersionId);
913+ }
914+ auto taggingOutcome = GetObjectTagging (taggingRequest);
915+ if (!taggingOutcome.IsSuccess ()) {
916+ return Model::CopyObjectOutcome (taggingOutcome.GetError ());
917+ }
918+ Aws::StringStream tagStream;
919+ bool firstTag = true ;
920+ for (const auto & tag : taggingOutcome.GetResult ().GetTagSet ()) {
921+ if (!firstTag) {
922+ tagStream << " &" ;
923+ }
924+ tagStream << Aws::Utils::StringUtils::URLEncode (tag.GetKey ().c_str ()) << " ="
925+ << Aws::Utils::StringUtils::URLEncode (tag.GetValue ().c_str ());
926+ firstTag = false ;
927+ }
928+ if (!firstTag) {
929+ httpRequest->SetHeaderValue (" x-amz-tagging" , tagStream.str ());
930+ httpRequest->SetHeaderValue (" x-amz-tagging-directive" , " REPLACE" );
931+ }
932+ }
933+
934+ return Model::CopyObjectOutcome (Model::CopyObjectResult ());
935+ }
936+
822937static void CopyObjectRequestShutdownCallback (void * user_data) {
823938 if (!user_data) {
824939 AWS_LOGSTREAM_ERROR (" CopyObject" , " user data passed is NULL " );
@@ -915,6 +1030,12 @@ void S3CrtClient::CopyObjectAsync(const CopyObjectRequest& request, const CopyOb
9151030 " Unable to create s3 meta request" , false )),
9161031 handlerContext);
9171032 }
1033+ {
1034+ auto copyPropertiesOutcome = PopulateCopyObjectProperties (request, userData->request );
1035+ if (!copyPropertiesOutcome.IsSuccess ()) {
1036+ return handler (this , request, copyPropertiesOutcome, handlerContext);
1037+ }
1038+ }
9181039 if (handlerContext) {
9191040 handlerContext->GetMonitorContext ().StartMonitorContext (Aws::String{" S3CrtClient" }, request.GetServiceRequestName (), userData->request );
9201041 }
0 commit comments