Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8533,6 +8533,9 @@ class AWS_S3CRT_API S3CrtClient : public Aws::Client::AWSXMLClient,
const Aws::AmazonWebServiceRequest* request, const Aws::Http::URI& uri,
Aws::Http::HttpMethod method) const;

Model::CopyObjectOutcome PopulateCopyObjectProperties(const Model::CopyObjectRequest& request,
const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest) const;

typedef Aws::Utils::Outcome<Aws::AmazonWebServiceResult<RESPONSE>, S3CrtError> InvokeOperationOutcome;

InvokeOperationOutcome InvokeServiceOperation(const AmazonWebServiceRequest& request,
Expand Down
127 changes: 127 additions & 0 deletions generated/src/aws-cpp-sdk-s3-crt/source/S3CrtClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,126 @@ void S3CrtClient::InitCommonCrtRequestOption(CrtRequestCallbackUserData* userDat
options->finish_callback = S3CrtRequestFinishCallback;
}

Model::CopyObjectOutcome S3CrtClient::PopulateCopyObjectProperties(const Model::CopyObjectRequest& request,
const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest) const {
const bool copyMetadata = request.MetadataDirectiveHasBeenSet() && request.GetMetadataDirective() == Model::MetadataDirective::COPY;
const bool copyTags = request.TaggingDirectiveHasBeenSet() && request.GetTaggingDirective() == Model::TaggingDirective::COPY;

if (!copyMetadata && !copyTags) {
return Model::CopyObjectOutcome(Model::CopyObjectResult());
}

// Parse "[/]bucket/key[?versionId=...]" from x-amz-copy-source. rfind: keys may contain '?'.
Aws::String copySource = request.GetCopySource();
Aws::String sourceVersionId;
const auto queryPos = copySource.rfind('?');
if (queryPos != Aws::String::npos) {
const Aws::String query = copySource.substr(queryPos + 1);
copySource = copySource.substr(0, queryPos);
const auto versionPos = query.find("versionId=");
if (versionPos != Aws::String::npos) {
sourceVersionId = query.substr(versionPos + Aws::String("versionId=").size());
const auto ampPos = sourceVersionId.find('&');
if (ampPos != Aws::String::npos) {
sourceVersionId = sourceVersionId.substr(0, ampPos);
}
}
}
if (!copySource.empty() && copySource.front() == '/') {
copySource = copySource.substr(1);
}
const auto slashPos = copySource.find('/');
if (slashPos == Aws::String::npos) {
return Model::CopyObjectOutcome(Aws::Client::AWSError<S3CrtErrors>(S3CrtErrors::INVALID_PARAMETER_VALUE, "INVALID_PARAMETER_VALUE",
"Could not parse bucket and key from CopySource for property copy",
false));
}
const Aws::String sourceBucket = copySource.substr(0, slashPos);
const Aws::String sourceKey = copySource.substr(slashPos + 1);

Model::HeadObjectRequest headRequest;
headRequest.SetBucket(sourceBucket);
headRequest.SetKey(sourceKey);
if (!sourceVersionId.empty()) {
headRequest.SetVersionId(sourceVersionId);
}
auto headOutcome = HeadObject(headRequest);
if (!headOutcome.IsSuccess()) {
return Model::CopyObjectOutcome(headOutcome.GetError());
}
const auto& head = headOutcome.GetResult();
const Aws::String sourceETag = head.GetETag();

// Pin the source version so UploadPartCopy reads a stable object.
if (sourceVersionId.empty() && !head.GetVersionId().empty()) {
sourceVersionId = head.GetVersionId();
httpRequest->SetHeaderValue("x-amz-copy-source", Aws::Http::URI::URLEncodePath(copySource) + "?versionId=" + sourceVersionId);
}
if (!httpRequest->HasHeader("x-amz-copy-source-if-match") && !sourceETag.empty()) {
httpRequest->SetHeaderValue("x-amz-copy-source-if-match", sourceETag);
}

// small objects are single-part copies where S3 honors the directives natively, skip injection
if (head.GetContentLength() < (1LL * 1024 * 1024 * 1024)) {
return Model::CopyObjectOutcome(Model::CopyObjectResult());
}

if (copyMetadata) {
if (!head.GetContentType().empty()) {
httpRequest->SetHeaderValue("content-type", head.GetContentType());
}
if (!head.GetContentEncoding().empty()) {
httpRequest->SetHeaderValue("content-encoding", head.GetContentEncoding());
}
if (!head.GetContentDisposition().empty()) {
httpRequest->SetHeaderValue("content-disposition", head.GetContentDisposition());
}
if (!head.GetContentLanguage().empty()) {
httpRequest->SetHeaderValue("content-language", head.GetContentLanguage());
}
if (!head.GetCacheControl().empty()) {
httpRequest->SetHeaderValue("cache-control", head.GetCacheControl());
}
if (!head.GetExpiresString().empty()) {
httpRequest->SetHeaderValue("expires", head.GetExpires().ToGmtString(Aws::Utils::DateFormat::RFC822));
}
for (const auto& item : head.GetMetadata()) {
httpRequest->SetHeaderValue("x-amz-meta-" + item.first, item.second);
}
// REPLACE so CreateMultipartUpload emits these headers.
httpRequest->SetHeaderValue("x-amz-metadata-directive", "REPLACE");
}

if (copyTags) {
Model::GetObjectTaggingRequest taggingRequest;
Comment thread
sbiscigl marked this conversation as resolved.
taggingRequest.SetBucket(sourceBucket);
taggingRequest.SetKey(sourceKey);
if (!sourceVersionId.empty()) {
taggingRequest.SetVersionId(sourceVersionId);
}
auto taggingOutcome = GetObjectTagging(taggingRequest);
if (!taggingOutcome.IsSuccess()) {
return Model::CopyObjectOutcome(taggingOutcome.GetError());
}
Aws::StringStream tagStream;
bool firstTag = true;
for (const auto& tag : taggingOutcome.GetResult().GetTagSet()) {
if (!firstTag) {
tagStream << "&";
}
tagStream << Aws::Utils::StringUtils::URLEncode(tag.GetKey().c_str()) << "="
<< Aws::Utils::StringUtils::URLEncode(tag.GetValue().c_str());
firstTag = false;
}
if (!firstTag) {
httpRequest->SetHeaderValue("x-amz-tagging", tagStream.str());
httpRequest->SetHeaderValue("x-amz-tagging-directive", "REPLACE");
}
}

return Model::CopyObjectOutcome(Model::CopyObjectResult());
}

static void CopyObjectRequestShutdownCallback(void* user_data) {
if (!user_data) {
AWS_LOGSTREAM_ERROR("CopyObject", "user data passed is NULL ");
Expand Down Expand Up @@ -915,6 +1035,13 @@ void S3CrtClient::CopyObjectAsync(const CopyObjectRequest& request, const CopyOb
"Unable to create s3 meta request", false)),
handlerContext);
}
{
auto copyPropertiesOutcome = PopulateCopyObjectProperties(request, userData->request);
if (!copyPropertiesOutcome.IsSuccess()) {
Comment thread
sbiscigl marked this conversation as resolved.
Aws::Delete(userData);
return handler(this, request, copyPropertiesOutcome, handlerContext);
}
}
if (handlerContext) {
handlerContext->GetMonitorContext().StartMonitorContext(Aws::String{"S3CrtClient"}, request.GetServiceRequestName(), userData->request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
#include <aws/s3-crt/model/SelectObjectContentRequest.h>
#include <aws/s3-crt/model/Tagging.h>
#include <aws/s3-crt/model/PutBucketTaggingRequest.h>
#include <aws/s3-crt/model/GetObjectTaggingRequest.h>
#include <aws/s3-crt/model/PutObjectTaggingRequest.h>
#include <aws/s3-crt/model/MetadataDirective.h>
#include <aws/s3-crt/model/TaggingDirective.h>
#include <aws/s3-crt/ClientConfiguration.h>
#include <aws/testing/ProxyConfig.h>
#include <aws/testing/platform/PlatformTesting.h>
Expand Down Expand Up @@ -988,6 +992,91 @@ namespace
AWS_ASSERT_SUCCESS(copyOutcome);
}

TEST_F(BucketAndObjectOperationTest, TestCopyObjectCopiesMetadataAndTags)
{
Aws::String fullBucketName = CalculateBucketName(BASE_OBJECTS_BUCKET_NAME.c_str());
SCOPED_TRACE(Aws::String("FullBucketName ") + fullBucketName);
CreateBucketRequest createBucketRequest;
createBucketRequest.SetBucket(fullBucketName);
createBucketRequest.SetACL(BucketCannedACL::private_);
CreateBucketOutcome createBucketOutcome = Client->CreateBucket(createBucketRequest);
AWS_ASSERT_SUCCESS(createBucketOutcome);
ASSERT_TRUE(WaitForBucketToPropagate(fullBucketName));
TagTestBucket(fullBucketName, Client);

const char* sourceKey = "copy-props-source";
const char* destKey = "copy-props-destination";

auto objectStream = Aws::MakeShared<Aws::StringStream>(ALLOCATION_TAG);
*objectStream << "high level copy metadata and tags test payload";
objectStream->flush();
PutObjectRequest putObjectRequest;
putObjectRequest.SetBucket(fullBucketName);
putObjectRequest.SetKey(sourceKey);
putObjectRequest.SetBody(objectStream);
putObjectRequest.SetContentLength(static_cast<long>(putObjectRequest.GetBody()->tellp()));
putObjectRequest.SetContentType("application/x-high-level-copy");
putObjectRequest.AddMetadata("project", "highlevelcopy");
putObjectRequest.AddMetadata("owner", "sdk-team");
PutObjectOutcome putObjectOutcome = Client->PutObject(putObjectRequest);
AWS_ASSERT_SUCCESS(putObjectOutcome);
ASSERT_TRUE(WaitForObjectToPropagate(fullBucketName, sourceKey));

{
PutObjectTaggingRequest putTaggingRequest;
putTaggingRequest.SetBucket(fullBucketName);
putTaggingRequest.SetKey(sourceKey);
Tagging tagging;
Tag t1; t1.SetKey("env"); t1.SetValue("test");
Tag t2; t2.SetKey("team"); t2.SetValue("sdk");
tagging.AddTagSet(t1);
tagging.AddTagSet(t2);
putTaggingRequest.SetTagging(tagging);
auto putTaggingOutcome = Client->PutObjectTagging(putTaggingRequest);
AWS_ASSERT_SUCCESS(putTaggingOutcome);
}

CopyObjectRequest copyRequest;
copyRequest.WithBucket(fullBucketName)
.WithKey(destKey)
.WithCopySource(fullBucketName + "/" + sourceKey)
.WithMetadataDirective(MetadataDirective::COPY)
.WithTaggingDirective(TaggingDirective::COPY);
auto copyOutcome = Client->CopyObject(copyRequest);
AWS_ASSERT_SUCCESS(copyOutcome);
ASSERT_TRUE(WaitForObjectToPropagate(fullBucketName, destKey));

{
HeadObjectRequest headRequest;
headRequest.SetBucket(fullBucketName);
headRequest.SetKey(destKey);
auto headOutcome = Client->HeadObject(headRequest);
AWS_ASSERT_SUCCESS(headOutcome);
const auto& metadata = headOutcome.GetResult().GetMetadata();
ASSERT_EQ(1u, metadata.count("project"));
ASSERT_STREQ("highlevelcopy", metadata.at("project").c_str());
ASSERT_EQ(1u, metadata.count("owner"));
ASSERT_STREQ("sdk-team", metadata.at("owner").c_str());
ASSERT_STREQ("application/x-high-level-copy", headOutcome.GetResult().GetContentType().c_str());
}

{
GetObjectTaggingRequest getTaggingRequest;
getTaggingRequest.SetBucket(fullBucketName);
getTaggingRequest.SetKey(destKey);
auto getTaggingOutcome = Client->GetObjectTagging(getTaggingRequest);
AWS_ASSERT_SUCCESS(getTaggingOutcome);
Aws::Map<Aws::String, Aws::String> tags;
for (const auto& tag : getTaggingOutcome.GetResult().GetTagSet())
{
tags[tag.GetKey()] = tag.GetValue();
}
ASSERT_EQ(2u, tags.size());
ASSERT_STREQ("test", tags["env"].c_str());
ASSERT_STREQ("sdk", tags["team"].c_str());
}
}

TEST_F(BucketAndObjectOperationTest, TestObjectOperationWithEventStream)
{
GTEST_SKIP() << "Select objects is not supported on new AWS accounts";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ namespace ${rootNamespace}
aws_s3_meta_request_options *options,
const Aws::AmazonWebServiceRequest *request,
const Aws::Http::URI &uri, Aws::Http::HttpMethod method) const;

Model::CopyObjectOutcome PopulateCopyObjectProperties(const Model::CopyObjectRequest &request,
const std::shared_ptr<Aws::Http::HttpRequest> &httpRequest) const;
#else
#if(!$serviceModel.endpointRules)
void init(const Aws::Client::ClientConfiguration& clientConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ namespace ${rootNamespace}
aws_s3_meta_request_options *options,
const Aws::AmazonWebServiceRequest *request,
const Aws::Http::URI &uri, Aws::Http::HttpMethod method) const;

Model::CopyObjectOutcome PopulateCopyObjectProperties(const Model::CopyObjectRequest &request,
const std::shared_ptr<Aws::Http::HttpRequest> &httpRequest) const;
#else
#if(!$serviceModel.endpointRules)
void init(const Aws::Client::ClientConfiguration& clientConfiguration);
Expand Down
Loading
Loading