diff --git a/sdk/core/azure-core/src/http/curl/curl.cpp b/sdk/core/azure-core/src/http/curl/curl.cpp index 121de00ad..9b78dced2 100644 --- a/sdk/core/azure-core/src/http/curl/curl.cpp +++ b/sdk/core/azure-core/src/http/curl/curl.cpp @@ -446,7 +446,18 @@ void CurlSession::ParseChunkSize(Context const& context) if (i > 1 && this->m_readBuffer[index] == '\n') { // get chunk size. Chunk size comes in Hex value - this->m_chunkSize = static_cast(std::stoull(strChunkSize, nullptr, 16)); + try + { + this->m_chunkSize = static_cast(std::stoull(strChunkSize, nullptr, 16)); + } + catch (std::invalid_argument& err) + { + (void)err; + // Server can return something like `\n\r\n` for a chunk of zero length data. This is + // allowed by RFC. `stoull` will throw invalid_argument if there is not at least one hex + // digit to be parsed. For those cases, we consider the response as zero-lenght. + this->m_chunkSize = 0; + } if (this->m_chunkSize == 0) { // Response with no content. end of chunk diff --git a/sdk/core/azure-core/test/ut/curl_session.hpp b/sdk/core/azure-core/test/ut/curl_session.hpp index 1e11b1611..9911d31e6 100644 --- a/sdk/core/azure-core/test/ut/curl_session.hpp +++ b/sdk/core/azure-core/test/ut/curl_session.hpp @@ -3,10 +3,10 @@ /** * @file - * @brief The base class for testing a curl session. - * + * @brief The base class for testing a curl session. + * * @remark The curl connection mock is defined here. - * + * */ #include @@ -39,6 +39,12 @@ namespace Azure { namespace Core { namespace Test { SendBuffer, (Context const& context, uint8_t const* buffer, size_t bufferSize), (override)); + + /* This is a way to test we are calling the destructor + * Adding an extra mock method that is called from the destructor + */ + MOCK_METHOD(void, DestructObj, ()); + virtual ~MockCurlNetworkConnection() { DestructObj(); } }; }}} // namespace Azure::Core::Test diff --git a/sdk/core/azure-core/test/ut/curl_session_test.cpp b/sdk/core/azure-core/test/ut/curl_session_test.cpp index b6abf1bfb..46e218f4b 100644 --- a/sdk/core/azure-core/test/ut/curl_session_test.cpp +++ b/sdk/core/azure-core/test/ut/curl_session_test.cpp @@ -9,6 +9,7 @@ using ::testing::_; using ::testing::DoAll; using ::testing::Return; +using ::testing::ReturnRef; using ::testing::SetArrayArgument; namespace Azure { namespace Core { namespace Test { @@ -41,4 +42,40 @@ namespace Azure { namespace Core { namespace Test { EXPECT_NO_THROW(session->Perform(Azure::Core::GetApplicationContext())); } + + TEST_F(CurlSession, chunkResponseSizeZero) + { + // chunked response with no content and no size + std::string response("HTTP/1.1 200 Ok\r\ntransfer-encoding: chunked\r\n\r\n\n\r\n"); + std::string host("sample-host"); + + // Can't mock the curMock directly from a unique ptr, heap allocate it first and then make a + // unique ptr for it + MockCurlNetworkConnection* curlMock = new MockCurlNetworkConnection(); + EXPECT_CALL(*curlMock, SendBuffer(_, _, _)).WillOnce(Return(CURLE_OK)); + EXPECT_CALL(*curlMock, ReadFromSocket(_, _, _)) + .WillOnce(DoAll( + SetArrayArgument<1>(response.data(), response.data() + response.size()), + Return(response.size()))); + EXPECT_CALL(*curlMock, GetHost()).WillRepeatedly(ReturnRef(host)); + EXPECT_CALL(*curlMock, updateLastUsageTime()); + EXPECT_CALL(*curlMock, DestructObj()); + + // Create the unique ptr to take care about memory free at the end + std::unique_ptr uniqueCurlMock(curlMock); + + // Simulate a request to be sent + Azure::Core::Http::Url url("http://microsoft.com"); + Azure::Core::Http::Request request(Azure::Core::Http::HttpMethod::Get, url); + + { + // Create the session inside scope so it is released and the connection is moved to the pool + auto session + = std::make_unique(request, std::move(uniqueCurlMock)); + + EXPECT_NO_THROW(session->Perform(Azure::Core::GetApplicationContext())); + } + // Clear the connections from the pool to invoke clean routine + Azure::Core::Http::CurlConnectionPool::ConnectionPoolIndex.clear(); + } }}} // namespace Azure::Core::Test