Skip to content

Commit f4fc394

Browse files
authored
Merge branch 'main' into transfer-manager-checksums-upload
2 parents 9bbb419 + cb96480 commit f4fc394

File tree

5 files changed

+195
-3
lines changed

5 files changed

+195
-3
lines changed

cmake/sdksCommon.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ list(APPEND SDK_TEST_PROJECT_LIST "s3control:tests/aws-cpp-sdk-s3control-integra
112112
list(APPEND SDK_TEST_PROJECT_LIST "sns:tests/aws-cpp-sdk-sns-integration-tests")
113113
list(APPEND SDK_TEST_PROJECT_LIST "sqs:tests/aws-cpp-sdk-sqs-integration-tests")
114114
list(APPEND SDK_TEST_PROJECT_LIST "sqs:tests/aws-cpp-sdk-sqs-unit-tests")
115-
list(APPEND SDK_TEST_PROJECT_LIST "transfer:tests/aws-cpp-sdk-transfer-tests")
115+
list(APPEND SDK_TEST_PROJECT_LIST "transfer:tests/aws-cpp-sdk-transfer-tests,tests/aws-cpp-sdk-transfer-unit-tests")
116116
list(APPEND SDK_TEST_PROJECT_LIST "text-to-speech:tests/aws-cpp-sdk-text-to-speech-tests,tests/aws-cpp-sdk-polly-sample")
117117
list(APPEND SDK_TEST_PROJECT_LIST "timestream-query:tests/aws-cpp-sdk-timestream-query-unit-tests")
118118
list(APPEND SDK_TEST_PROJECT_LIST "transcribestreaming:tests/aws-cpp-sdk-transcribestreaming-integ-tests")

src/aws-cpp-sdk-transfer/source/transfer/TransferManager.cpp

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,35 @@ namespace Aws
860860
return rangeStream.str();
861861
}
862862

863+
static bool VerifyContentRange(const Aws::String& requestedRange, const Aws::String& responseContentRange)
864+
{
865+
if (requestedRange.empty() || responseContentRange.empty())
866+
{
867+
return false;
868+
}
869+
870+
const auto requestPrefix = "bytes=";
871+
if (requestedRange.substr(0, strlen(requestPrefix)) != requestPrefix)
872+
{
873+
return false;
874+
}
875+
Aws::String requestRange = requestedRange.substr(strlen(requestPrefix));
876+
877+
const auto responsePrefix = "bytes ";
878+
if (responseContentRange.substr(0, strlen(responsePrefix)) != responsePrefix)
879+
{
880+
return false;
881+
}
882+
Aws::String responseRange = responseContentRange.substr(strlen(responsePrefix));
883+
size_t slashPos = responseRange.find('/');
884+
if (slashPos != Aws::String::npos)
885+
{
886+
responseRange = responseRange.substr(0, slashPos);
887+
}
888+
889+
return requestRange == responseRange;
890+
}
891+
863892
void TransferManager::DoSinglePartDownload(const std::shared_ptr<TransferHandle>& handle)
864893
{
865894
auto queuedParts = handle->GetQueuedParts();
@@ -1126,7 +1155,6 @@ namespace Aws
11261155
const std::shared_ptr<const Aws::Client::AsyncCallerContext>& context)
11271156
{
11281157
AWS_UNREFERENCED_PARAM(client);
1129-
AWS_UNREFERENCED_PARAM(request);
11301158

11311159
std::shared_ptr<TransferHandleAsyncContext> transferContext =
11321160
std::const_pointer_cast<TransferHandleAsyncContext>(std::static_pointer_cast<const TransferHandleAsyncContext>(context));
@@ -1143,8 +1171,32 @@ namespace Aws
11431171
handle->SetError(outcome.GetError());
11441172
TriggerErrorCallback(handle, outcome.GetError());
11451173
}
1146-
else
1174+
else if (request.RangeHasBeenSet())
11471175
{
1176+
const auto& requestedRange = request.GetRange();
1177+
const auto& responseContentRange = outcome.GetResult().GetContentRange();
1178+
1179+
if (responseContentRange.empty() || !VerifyContentRange(requestedRange, responseContentRange)) {
1180+
Aws::Client::AWSError<Aws::S3::S3Errors> error(Aws::S3::S3Errors::INTERNAL_FAILURE,
1181+
"ContentRangeMismatch",
1182+
"ContentRange in response does not match requested range",
1183+
false);
1184+
AWS_LOGSTREAM_ERROR(CLASS_TAG, "Transfer handle [" << handle->GetId()
1185+
<< "] ContentRange mismatch. Requested: [" << requestedRange
1186+
<< "] Received: [" << responseContentRange << "]");
1187+
handle->ChangePartToFailed(partState);
1188+
handle->SetError(error);
1189+
TriggerErrorCallback(handle, error);
1190+
handle->Cancel();
1191+
1192+
if(partState->GetDownloadBuffer())
1193+
{
1194+
m_bufferManager.Release(partState->GetDownloadBuffer());
1195+
partState->SetDownloadBuffer(nullptr);
1196+
}
1197+
return;
1198+
}
1199+
11481200
if(handle->ShouldContinue())
11491201
{
11501202
Aws::IOStream* bufferStream = partState->GetDownloadPartStream();

tests/aws-cpp-sdk-transfer-tests/TransferTests.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2344,6 +2344,40 @@ TEST_P(TransferTests, TransferManager_TestRelativePrefix)
23442344
}
23452345
}
23462346

2347+
TEST_P(TransferTests, TransferManager_ContentRangeVerificationTest)
2348+
{
2349+
const Aws::String RandomFileName = Aws::Utils::UUID::RandomUUID();
2350+
Aws::String testFileName = MakeFilePath(RandomFileName.c_str());
2351+
ScopedTestFile testFile(testFileName, MEDIUM_TEST_SIZE, testString);
2352+
2353+
TransferManagerConfiguration transferManagerConfig(m_executor.get());
2354+
transferManagerConfig.s3Client = m_s3Clients[GetParam()];
2355+
auto transferManager = TransferManager::Create(transferManagerConfig);
2356+
2357+
std::shared_ptr<TransferHandle> uploadPtr = transferManager->UploadFile(testFileName, GetTestBucketName(), RandomFileName, "text/plain", Aws::Map<Aws::String, Aws::String>());
2358+
uploadPtr->WaitUntilFinished();
2359+
ASSERT_EQ(TransferStatus::COMPLETED, uploadPtr->GetStatus());
2360+
ASSERT_TRUE(WaitForObjectToPropagate(GetTestBucketName(), RandomFileName.c_str()));
2361+
2362+
auto downloadFileName = MakeDownloadFileName(RandomFileName);
2363+
auto createStreamFn = [=](){
2364+
#ifdef _MSC_VER
2365+
return Aws::New<Aws::FStream>(ALLOCATION_TAG, Aws::Utils::StringUtils::ToWString(downloadFileName.c_str()).c_str(), std::ios_base::out | std::ios_base::in | std::ios_base::binary | std::ios_base::trunc);
2366+
#else
2367+
return Aws::New<Aws::FStream>(ALLOCATION_TAG, downloadFileName.c_str(), std::ios_base::out | std::ios_base::in | std::ios_base::binary | std::ios_base::trunc);
2368+
#endif
2369+
};
2370+
2371+
uint64_t offset = 1024;
2372+
uint64_t partSize = 2048;
2373+
std::shared_ptr<TransferHandle> downloadPtr = transferManager->DownloadFile(GetTestBucketName(), RandomFileName, offset, partSize, createStreamFn);
2374+
2375+
downloadPtr->WaitUntilFinished();
2376+
ASSERT_EQ(TransferStatus::COMPLETED, downloadPtr->GetStatus());
2377+
ASSERT_EQ(partSize, downloadPtr->GetBytesTotalSize());
2378+
ASSERT_EQ(partSize, downloadPtr->GetBytesTransferred());
2379+
}
2380+
23472381
INSTANTIATE_TEST_SUITE_P(Https, TransferTests, testing::Values(TestType::Https));
23482382
INSTANTIATE_TEST_SUITE_P(Http, TransferTests, testing::Values(TestType::Http));
23492383

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
add_project(aws-cpp-sdk-transfer-unit-tests
2+
"Unit Tests for the Transfer Manager"
3+
aws-cpp-sdk-transfer
4+
aws-cpp-sdk-s3
5+
testing-resources
6+
aws_test_main
7+
aws-cpp-sdk-core)
8+
9+
add_definitions(-DRESOURCES_DIR="${CMAKE_CURRENT_SOURCE_DIR}/resources")
10+
11+
if(MSVC AND BUILD_SHARED_LIBS)
12+
add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1)
13+
endif()
14+
15+
enable_testing()
16+
17+
if(PLATFORM_ANDROID AND BUILD_SHARED_LIBS)
18+
add_library(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/TransferUnitTests.cpp)
19+
else()
20+
add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/TransferUnitTests.cpp)
21+
endif()
22+
23+
set_compiler_flags(${PROJECT_NAME})
24+
set_compiler_warnings(${PROJECT_NAME})
25+
26+
target_link_libraries(${PROJECT_NAME} ${PROJECT_LIBS})
27+
28+
if(MSVC AND BUILD_SHARED_LIBS)
29+
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/DELAYLOAD:aws-cpp-sdk-transfer.dll /DELAYLOAD:aws-cpp-sdk-core.dll")
30+
target_link_libraries(${PROJECT_NAME} delayimp.lib)
31+
endif()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <gtest/gtest.h>
2+
#include <aws/core/Aws.h>
3+
#include <aws/core/utils/threading/PooledThreadExecutor.h>
4+
#include <aws/s3/S3Client.h>
5+
#include <aws/s3/model/GetObjectRequest.h>
6+
#include <aws/s3/model/GetObjectResult.h>
7+
#include <aws/transfer/TransferManager.h>
8+
#include <aws/testing/AwsTestHelpers.h>
9+
#include <aws/testing/MemoryTesting.h>
10+
#include <sstream>
11+
12+
using namespace Aws;
13+
using namespace Aws::S3;
14+
using namespace Aws::S3::Model;
15+
using namespace Aws::Transfer;
16+
using namespace Aws::Utils::Threading;
17+
18+
const char* ALLOCATION_TAG = "TransferUnitTest";
19+
20+
class MockS3Client : public S3Client {
21+
public:
22+
MockS3Client() : S3Client(){};
23+
24+
GetObjectOutcome GetObject(const GetObjectRequest& request) const override {
25+
GetObjectResult result;
26+
27+
if (request.RangeHasBeenSet()) {
28+
// Always return mismatched range to trigger validation failure
29+
result.SetContentRange("bytes 1024-2047/2048");
30+
}
31+
32+
auto stream = Aws::New<std::stringstream>(ALLOCATION_TAG);
33+
*stream << "mock data";
34+
result.ReplaceBody(stream);
35+
return GetObjectOutcome(std::move(result));
36+
}
37+
};
38+
39+
class TransferUnitTest : public testing::Test {
40+
protected:
41+
void SetUp() override {
42+
executor = Aws::MakeShared<PooledThreadExecutor>(ALLOCATION_TAG, 1);
43+
mockS3Client = Aws::MakeShared<MockS3Client>(ALLOCATION_TAG);
44+
}
45+
46+
static void SetUpTestSuite() {
47+
InitAPI(_options);
48+
}
49+
50+
static void TearDownTestSuite() {
51+
ShutdownAPI(_options);
52+
}
53+
54+
std::shared_ptr<PooledThreadExecutor> executor;
55+
std::shared_ptr<MockS3Client> mockS3Client;
56+
static SDKOptions _options;
57+
};
58+
59+
SDKOptions TransferUnitTest::_options;
60+
61+
TEST_F(TransferUnitTest, ContentValidationShouldFail) {
62+
TransferManagerConfiguration config(executor.get());
63+
config.s3Client = mockS3Client;
64+
auto transferManager = TransferManager::Create(config);
65+
66+
auto createStreamFn = []() {
67+
return Aws::New<std::stringstream>(ALLOCATION_TAG);
68+
};
69+
70+
// Request bytes 0-1023 but mock returns 1024-2047, should fail validation
71+
auto handle = transferManager->DownloadFile("test-bucket", "test-key", 0, 1024, createStreamFn);
72+
handle->WaitUntilFinished();
73+
74+
EXPECT_EQ(TransferStatus::FAILED, handle->GetStatus());
75+
}

0 commit comments

Comments
 (0)