1 /**
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  * SPDX-License-Identifier: Apache-2.0.
4  */
5 
6 #include <aws/core/http/curl/CurlHttpClient.h>
7 #include <aws/core/http/HttpRequest.h>
8 #include <aws/core/http/standard/StandardHttpResponse.h>
9 #include <aws/core/utils/StringUtils.h>
10 #include <aws/core/utils/logging/LogMacros.h>
11 #include <aws/core/utils/ratelimiter/RateLimiterInterface.h>
12 #include <aws/core/utils/DateTime.h>
13 #include <aws/core/monitoring/HttpClientMetrics.h>
14 #include <cassert>
15 #include <algorithm>
16 
17 
18 using namespace Aws::Client;
19 using namespace Aws::Http;
20 using namespace Aws::Http::Standard;
21 using namespace Aws::Utils;
22 using namespace Aws::Utils::Logging;
23 using namespace Aws::Monitoring;
24 
25 #ifdef AWS_CUSTOM_MEMORY_MANAGEMENT
26 
27 static const char* MemTag = "libcurl";
28 static size_t offset = sizeof(size_t);
29 
malloc_callback(size_t size)30 static void* malloc_callback(size_t size)
31 {
32     char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, size + offset));
33     std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
34     *pointerToSize = size;
35     return reinterpret_cast<void*>(newMem + offset);
36 }
37 
free_callback(void * ptr)38 static void free_callback(void* ptr)
39 {
40     if(ptr)
41     {
42         char* shiftedMemory = reinterpret_cast<char*>(ptr);
43         Aws::Free(shiftedMemory - offset);
44     }
45 }
46 
realloc_callback(void * ptr,size_t size)47 static void* realloc_callback(void* ptr, size_t size)
48 {
49     if(!ptr)
50     {
51         return malloc_callback(size);
52     }
53 
54 
55     if(!size && ptr)
56     {
57         free_callback(ptr);
58         return nullptr;
59     }
60 
61     char* originalLenCharPtr = reinterpret_cast<char*>(ptr) - offset;
62     size_t originalLen = *reinterpret_cast<size_t*>(originalLenCharPtr);
63 
64     char* rawMemory = reinterpret_cast<char*>(Aws::Malloc(MemTag, size + offset));
65     if(rawMemory)
66     {
67         std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(rawMemory);
68         *pointerToSize = size;
69 
70         size_t copyLength = (std::min)(originalLen, size);
71 #ifdef _MSC_VER
72         memcpy_s(rawMemory + offset, size, ptr, copyLength);
73 #else
74         memcpy(rawMemory + offset, ptr, copyLength);
75 #endif
76         free_callback(ptr);
77         return reinterpret_cast<void*>(rawMemory + offset);
78     }
79     else
80     {
81         return ptr;
82     }
83 
84 }
85 
calloc_callback(size_t nmemb,size_t size)86 static void* calloc_callback(size_t nmemb, size_t size)
87 {
88     size_t dataSize = nmemb * size;
89     char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, dataSize + offset));
90     std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
91     *pointerToSize = dataSize;
92 #ifdef _MSC_VER
93     memset_s(newMem + offset, dataSize, 0, dataSize);
94 #else
95     memset(newMem + offset, 0, dataSize);
96 #endif
97 
98     return reinterpret_cast<void*>(newMem + offset);
99 }
100 
strdup_callback(const char * str)101 static char* strdup_callback(const char* str)
102 {
103     size_t len = strlen(str) + 1;
104     size_t newLen = len + offset;
105     char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, newLen));
106 
107     if(newMem)
108     {
109         std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
110         *pointerToSize = len;
111 #ifdef _MSC_VER
112         memcpy_s(newMem + offset, len, str, len);
113 #else
114         memcpy(newMem + offset, str, len);
115 #endif
116         return newMem + offset;
117     }
118     return nullptr;
119 }
120 
121 #endif
122 
123 struct CurlWriteCallbackContext
124 {
CurlWriteCallbackContextCurlWriteCallbackContext125     CurlWriteCallbackContext(const CurlHttpClient* client,
126                              HttpRequest* request,
127                              HttpResponse* response,
128                              Aws::Utils::RateLimits::RateLimiterInterface* rateLimiter) :
129         m_client(client),
130         m_request(request),
131         m_response(response),
132         m_rateLimiter(rateLimiter),
133         m_numBytesResponseReceived(0)
134     {}
135 
136     const CurlHttpClient* m_client;
137     HttpRequest* m_request;
138     HttpResponse* m_response;
139     Aws::Utils::RateLimits::RateLimiterInterface* m_rateLimiter;
140     int64_t m_numBytesResponseReceived;
141 };
142 
143 struct CurlReadCallbackContext
144 {
CurlReadCallbackContextCurlReadCallbackContext145     CurlReadCallbackContext(const CurlHttpClient* client, CURL* curlHandle, HttpRequest* request, Aws::Utils::RateLimits::RateLimiterInterface* limiter) :
146         m_client(client),
147         m_curlHandle(curlHandle),
148         m_rateLimiter(limiter),
149         m_request(request)
150     {}
151 
152     const CurlHttpClient* m_client;
153     CURL* m_curlHandle;
154     Aws::Utils::RateLimits::RateLimiterInterface* m_rateLimiter;
155     HttpRequest* m_request;
156 };
157 
158 static const char* CURL_HTTP_CLIENT_TAG = "CurlHttpClient";
159 
WriteData(char * ptr,size_t size,size_t nmemb,void * userdata)160 static size_t WriteData(char* ptr, size_t size, size_t nmemb, void* userdata)
161 {
162     if (ptr)
163     {
164         CurlWriteCallbackContext* context = reinterpret_cast<CurlWriteCallbackContext*>(userdata);
165 
166         const CurlHttpClient* client = context->m_client;
167         if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
168         {
169             return 0;
170         }
171 
172         HttpResponse* response = context->m_response;
173         size_t sizeToWrite = size * nmemb;
174         if (context->m_rateLimiter)
175         {
176             context->m_rateLimiter->ApplyAndPayForCost(static_cast<int64_t>(sizeToWrite));
177         }
178 
179         response->GetResponseBody().write(ptr, static_cast<std::streamsize>(sizeToWrite));
180         if (context->m_request->IsEventStreamRequest())
181         {
182             response->GetResponseBody().flush();
183         }
184         auto& receivedHandler = context->m_request->GetDataReceivedEventHandler();
185         if (receivedHandler)
186         {
187             receivedHandler(context->m_request, context->m_response, static_cast<long long>(sizeToWrite));
188         }
189 
190         AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, sizeToWrite << " bytes written to response.");
191         context->m_numBytesResponseReceived += sizeToWrite;
192         return sizeToWrite;
193     }
194     return 0;
195 }
196 
WriteHeader(char * ptr,size_t size,size_t nmemb,void * userdata)197 static size_t WriteHeader(char* ptr, size_t size, size_t nmemb, void* userdata)
198 {
199     if (ptr)
200     {
201         CurlWriteCallbackContext* context = reinterpret_cast<CurlWriteCallbackContext*>(userdata);
202         AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, ptr);
203         HttpResponse* response = context->m_response;
204         Aws::String headerLine(ptr);
205         Aws::Vector<Aws::String> keyValuePair = StringUtils::Split(headerLine, ':', 2);
206 
207         if (keyValuePair.size() == 2)
208         {
209             response->AddHeader(StringUtils::Trim(keyValuePair[0].c_str()), StringUtils::Trim(keyValuePair[1].c_str()));
210         }
211 
212         return size * nmemb;
213     }
214     return 0;
215 }
216 
217 
ReadBody(char * ptr,size_t size,size_t nmemb,void * userdata)218 static size_t ReadBody(char* ptr, size_t size, size_t nmemb, void* userdata)
219 {
220     CurlReadCallbackContext* context = reinterpret_cast<CurlReadCallbackContext*>(userdata);
221     if(context == nullptr)
222     {
223         return 0;
224     }
225 
226     const CurlHttpClient* client = context->m_client;
227     if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
228     {
229         return CURL_READFUNC_ABORT;
230     }
231 
232     HttpRequest* request = context->m_request;
233     const std::shared_ptr<Aws::IOStream>& ioStream = request->GetContentBody();
234 
235     const size_t amountToRead = size * nmemb;
236     if (ioStream != nullptr && amountToRead > 0)
237     {
238         if (request->IsEventStreamRequest())
239         {
240             if (ioStream->readsome(ptr, amountToRead) == 0 && !ioStream->eof())
241             {
242                 return CURL_READFUNC_PAUSE;
243             }
244         }
245         else
246         {
247             ioStream->read(ptr, amountToRead);
248         }
249         size_t amountRead = static_cast<size_t>(ioStream->gcount());
250         auto& sentHandler = request->GetDataSentEventHandler();
251         if (sentHandler)
252         {
253             sentHandler(request, static_cast<long long>(amountRead));
254         }
255 
256         if (context->m_rateLimiter)
257         {
258             context->m_rateLimiter->ApplyAndPayForCost(static_cast<int64_t>(amountRead));
259         }
260 
261         return amountRead;
262     }
263 
264     return 0;
265 }
266 
SeekBody(void * userdata,curl_off_t offset,int origin)267 static size_t SeekBody(void* userdata, curl_off_t offset, int origin)
268 {
269     CurlReadCallbackContext* context = reinterpret_cast<CurlReadCallbackContext*>(userdata);
270     if(context == nullptr)
271     {
272         return CURL_SEEKFUNC_FAIL;
273     }
274 
275     const CurlHttpClient* client = context->m_client;
276     if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
277     {
278         return CURL_SEEKFUNC_FAIL;
279     }
280 
281     HttpRequest* request = context->m_request;
282     const std::shared_ptr<Aws::IOStream>& ioStream = request->GetContentBody();
283 
284     std::ios_base::seekdir dir;
285     switch(origin)
286     {
287         case SEEK_SET:
288             dir = std::ios_base::beg;
289             break;
290         case SEEK_CUR:
291             dir = std::ios_base::cur;
292             break;
293         case SEEK_END:
294             dir = std::ios_base::end;
295             break;
296         default:
297             return CURL_SEEKFUNC_FAIL;
298     }
299 
300     ioStream->clear();
301     ioStream->seekg(offset, dir);
302     if (ioStream->fail()) {
303         return CURL_SEEKFUNC_CANTSEEK;
304     }
305 
306     return CURL_SEEKFUNC_OK;
307 }
308 #if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
CurlProgressCallback(void * userdata,curl_off_t,curl_off_t,curl_off_t,curl_off_t)309 static int CurlProgressCallback(void *userdata, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
310 #else
311 static int CurlProgressCallback(void *userdata, double, double, double, double)
312 #endif
313 {
314     CurlReadCallbackContext* context = reinterpret_cast<CurlReadCallbackContext*>(userdata);
315 
316     const std::shared_ptr<Aws::IOStream>& ioStream = context->m_request->GetContentBody();
317     if (ioStream->eof())
318     {
319         curl_easy_pause(context->m_curlHandle, CURLPAUSE_CONT);
320         return 0;
321     }
322     char output[1];
323     if (ioStream->readsome(output, 1) > 0)
324     {
325         ioStream->unget();
326         if (!ioStream->good())
327         {
328             AWS_LOGSTREAM_WARN(CURL_HTTP_CLIENT_TAG, "Input stream failed to perform unget().");
329         }
330         curl_easy_pause(context->m_curlHandle, CURLPAUSE_CONT);
331     }
332 
333     return 0;
334 }
335 
SetOptCodeForHttpMethod(CURL * requestHandle,const std::shared_ptr<HttpRequest> & request)336 void SetOptCodeForHttpMethod(CURL* requestHandle, const std::shared_ptr<HttpRequest>& request)
337 {
338     switch (request->GetMethod())
339     {
340         case HttpMethod::HTTP_GET:
341             curl_easy_setopt(requestHandle, CURLOPT_HTTPGET, 1L);
342             break;
343         case HttpMethod::HTTP_POST:
344             if (request->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER) && request->GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0")
345             {
346                 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "POST");
347             }
348             else
349             {
350                 curl_easy_setopt(requestHandle, CURLOPT_POST, 1L);
351             }
352             break;
353         case HttpMethod::HTTP_PUT:
354             if ((!request->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER) || request->GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0") &&
355                  !request->HasHeader(Aws::Http::TRANSFER_ENCODING_HEADER))
356             {
357                 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PUT");
358             }
359             else
360             {
361                 curl_easy_setopt(requestHandle, CURLOPT_PUT, 1L);
362             }
363             break;
364         case HttpMethod::HTTP_HEAD:
365             curl_easy_setopt(requestHandle, CURLOPT_HTTPGET, 1L);
366             curl_easy_setopt(requestHandle, CURLOPT_NOBODY, 1L);
367             break;
368         case HttpMethod::HTTP_PATCH:
369             if ((!request->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER)|| request->GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0") &&
370                  !request->HasHeader(Aws::Http::TRANSFER_ENCODING_HEADER))
371             {
372                 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
373             }
374             else
375             {
376                 curl_easy_setopt(requestHandle, CURLOPT_POST, 1L);
377                 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
378             }
379 
380             break;
381         case HttpMethod::HTTP_DELETE:
382             curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "DELETE");
383             break;
384         default:
385             assert(0);
386             curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "GET");
387             break;
388     }
389 }
390 
391 
392 std::atomic<bool> CurlHttpClient::isInit(false);
393 
InitGlobalState()394 void CurlHttpClient::InitGlobalState()
395 {
396     if (!isInit)
397     {
398         auto curlVersionData = curl_version_info(CURLVERSION_NOW);
399         AWS_LOGSTREAM_INFO(CURL_HTTP_CLIENT_TAG, "Initializing Curl library with version: " << curlVersionData->version
400             << ", ssl version: " << curlVersionData->ssl_version);
401         isInit = true;
402 #ifdef AWS_CUSTOM_MEMORY_MANAGEMENT
403         curl_global_init_mem(CURL_GLOBAL_ALL, &malloc_callback, &free_callback, &realloc_callback, &strdup_callback, &calloc_callback);
404 #else
405         curl_global_init(CURL_GLOBAL_ALL);
406 #endif
407     }
408 }
409 
410 
CleanupGlobalState()411 void CurlHttpClient::CleanupGlobalState()
412 {
413     curl_global_cleanup();
414 }
415 
CurlInfoTypeToString(curl_infotype type)416 Aws::String CurlInfoTypeToString(curl_infotype type)
417 {
418     switch(type)
419     {
420         case CURLINFO_TEXT:
421             return "Text";
422 
423         case CURLINFO_HEADER_IN:
424             return "HeaderIn";
425 
426         case CURLINFO_HEADER_OUT:
427             return "HeaderOut";
428 
429         case CURLINFO_DATA_IN:
430             return "DataIn";
431 
432         case CURLINFO_DATA_OUT:
433             return "DataOut";
434 
435         case CURLINFO_SSL_DATA_IN:
436             return "SSLDataIn";
437 
438         case CURLINFO_SSL_DATA_OUT:
439             return "SSLDataOut";
440 
441         default:
442             return "Unknown";
443     }
444 }
445 
CurlDebugCallback(CURL * handle,curl_infotype type,char * data,size_t size,void * userptr)446 int CurlDebugCallback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr)
447 {
448     AWS_UNREFERENCED_PARAM(handle);
449     AWS_UNREFERENCED_PARAM(userptr);
450 
451     if(type == CURLINFO_SSL_DATA_IN || type == CURLINFO_SSL_DATA_OUT)
452     {
453         AWS_LOGSTREAM_DEBUG("CURL", "(" << CurlInfoTypeToString(type) << ") " << size << "bytes");
454     }
455     else
456     {
457         Aws::String debugString(data, size);
458         AWS_LOGSTREAM_DEBUG("CURL", "(" << CurlInfoTypeToString(type) << ") " << debugString);
459     }
460 
461     return 0;
462 }
463 
464 
CurlHttpClient(const ClientConfiguration & clientConfig)465 CurlHttpClient::CurlHttpClient(const ClientConfiguration& clientConfig) :
466     Base(),
467     m_curlHandleContainer(clientConfig.maxConnections, clientConfig.httpRequestTimeoutMs, clientConfig.connectTimeoutMs, clientConfig.enableTcpKeepAlive,
468                           clientConfig.tcpKeepAliveIntervalMs, clientConfig.requestTimeoutMs, clientConfig.lowSpeedLimit),
469     m_isUsingProxy(!clientConfig.proxyHost.empty()), m_proxyUserName(clientConfig.proxyUserName),
470     m_proxyPassword(clientConfig.proxyPassword), m_proxyScheme(SchemeMapper::ToString(clientConfig.proxyScheme)), m_proxyHost(clientConfig.proxyHost),
471     m_proxySSLCertPath(clientConfig.proxySSLCertPath), m_proxySSLCertType(clientConfig.proxySSLCertType),
472     m_proxySSLKeyPath(clientConfig.proxySSLKeyPath), m_proxySSLKeyType(clientConfig.proxySSLKeyType),
473     m_proxyKeyPasswd(clientConfig.proxySSLKeyPassword),
474     m_proxyPort(clientConfig.proxyPort), m_verifySSL(clientConfig.verifySSL), m_caPath(clientConfig.caPath),
475     m_caFile(clientConfig.caFile),
476     m_disableExpectHeader(clientConfig.disableExpectHeader)
477 {
478     if (clientConfig.followRedirects == FollowRedirectsPolicy::NEVER ||
479        (clientConfig.followRedirects == FollowRedirectsPolicy::DEFAULT && clientConfig.region == Aws::Region::AWS_GLOBAL))
480     {
481         m_allowRedirects = false;
482     }
483     else
484     {
485         m_allowRedirects = true;
486     }
487     if(clientConfig.nonProxyHosts.GetLength() > 0)
488     {
489         Aws::StringStream ss;
490         ss << clientConfig.nonProxyHosts.GetItem(0);
491         for (auto i=1u; i < clientConfig.nonProxyHosts.GetLength(); i++)
492         {
493             ss << "," << clientConfig.nonProxyHosts.GetItem(i);
494         }
495         m_nonProxyHosts = ss.str();
496     }
497 }
498 
499 
MakeRequest(const std::shared_ptr<HttpRequest> & request,Aws::Utils::RateLimits::RateLimiterInterface * readLimiter,Aws::Utils::RateLimits::RateLimiterInterface * writeLimiter) const500 std::shared_ptr<HttpResponse> CurlHttpClient::MakeRequest(const std::shared_ptr<HttpRequest>& request,
501     Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
502     Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const
503 {
504     URI uri = request->GetUri();
505     Aws::String url = uri.GetURIString();
506     std::shared_ptr<HttpResponse> response = Aws::MakeShared<StandardHttpResponse>(CURL_HTTP_CLIENT_TAG, request);
507 
508     AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Making request to " << url);
509     struct curl_slist* headers = NULL;
510 
511     if (writeLimiter != nullptr)
512     {
513         writeLimiter->ApplyAndPayForCost(request->GetSize());
514     }
515 
516     Aws::StringStream headerStream;
517     HeaderValueCollection requestHeaders = request->GetHeaders();
518 
519     AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Including headers:");
520     for (auto& requestHeader : requestHeaders)
521     {
522         headerStream.str("");
523         headerStream << requestHeader.first << ": " << requestHeader.second;
524         Aws::String headerString = headerStream.str();
525         AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, headerString);
526         headers = curl_slist_append(headers, headerString.c_str());
527     }
528 
529     if (!request->HasHeader(Http::TRANSFER_ENCODING_HEADER))
530     {
531         headers = curl_slist_append(headers, "transfer-encoding:");
532     }
533 
534     if (!request->HasHeader(Http::CONTENT_LENGTH_HEADER))
535     {
536         headers = curl_slist_append(headers, "content-length:");
537     }
538 
539     if (!request->HasHeader(Http::CONTENT_TYPE_HEADER))
540     {
541         headers = curl_slist_append(headers, "content-type:");
542     }
543 
544     // Discard Expect header so as to avoid using multiple payloads to send a http request (header + body)
545     if (m_disableExpectHeader)
546     {
547         headers = curl_slist_append(headers, "Expect:");
548     }
549 
550     CURL* connectionHandle = m_curlHandleContainer.AcquireCurlHandle();
551 
552     if (connectionHandle)
553     {
554         AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Obtained connection handle " << connectionHandle);
555 
556         if (headers)
557         {
558             curl_easy_setopt(connectionHandle, CURLOPT_HTTPHEADER, headers);
559         }
560 
561         CurlWriteCallbackContext writeContext(this, request.get(), response.get(), readLimiter);
562         CurlReadCallbackContext readContext(this, connectionHandle, request.get(), writeLimiter);
563 
564         SetOptCodeForHttpMethod(connectionHandle, request);
565 
566         curl_easy_setopt(connectionHandle, CURLOPT_URL, url.c_str());
567         curl_easy_setopt(connectionHandle, CURLOPT_WRITEFUNCTION, WriteData);
568         curl_easy_setopt(connectionHandle, CURLOPT_WRITEDATA, &writeContext);
569         curl_easy_setopt(connectionHandle, CURLOPT_HEADERFUNCTION, WriteHeader);
570         curl_easy_setopt(connectionHandle, CURLOPT_HEADERDATA, &writeContext);
571 
572         //we only want to override the default path if someone has explicitly told us to.
573         if(!m_caPath.empty())
574         {
575             curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, m_caPath.c_str());
576         }
577         if(!m_caFile.empty())
578         {
579             curl_easy_setopt(connectionHandle, CURLOPT_CAINFO, m_caFile.c_str());
580         }
581 
582 	// only set by android test builds because the emulator is missing a cert needed for aws services
583 #ifdef TEST_CERT_PATH
584 	curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, TEST_CERT_PATH);
585 #endif // TEST_CERT_PATH
586 
587         if (m_verifySSL)
588         {
589             curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 1L);
590             curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 2L);
591 
592 #if LIBCURL_VERSION_MAJOR >= 7
593 #if LIBCURL_VERSION_MINOR >= 34
594             curl_easy_setopt(connectionHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
595 #endif //LIBCURL_VERSION_MINOR
596 #endif //LIBCURL_VERSION_MAJOR
597         }
598         else
599         {
600             curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 0L);
601             curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 0L);
602         }
603 
604         if (m_allowRedirects)
605         {
606             curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 1L);
607         }
608         else
609         {
610             curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 0L);
611         }
612 
613 #ifdef ENABLE_CURL_LOGGING
614         curl_easy_setopt(connectionHandle, CURLOPT_VERBOSE, 1);
615         curl_easy_setopt(connectionHandle, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
616 #endif
617         if (m_isUsingProxy)
618         {
619             Aws::StringStream ss;
620             ss << m_proxyScheme << "://" << m_proxyHost;
621             curl_easy_setopt(connectionHandle, CURLOPT_PROXY, ss.str().c_str());
622             curl_easy_setopt(connectionHandle, CURLOPT_PROXYPORT, (long) m_proxyPort);
623             if (!m_proxyUserName.empty() || !m_proxyPassword.empty())
624             {
625                 curl_easy_setopt(connectionHandle, CURLOPT_PROXYUSERNAME, m_proxyUserName.c_str());
626                 curl_easy_setopt(connectionHandle, CURLOPT_PROXYPASSWORD, m_proxyPassword.c_str());
627             }
628             curl_easy_setopt(connectionHandle, CURLOPT_NOPROXY, m_nonProxyHosts.c_str());
629 #ifdef CURL_HAS_TLS_PROXY
630             if (!m_proxySSLCertPath.empty())
631             {
632                 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERT, m_proxySSLCertPath.c_str());
633                 if (!m_proxySSLCertType.empty())
634                 {
635                     curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERTTYPE, m_proxySSLCertType.c_str());
636                 }
637             }
638             if (!m_proxySSLKeyPath.empty())
639             {
640                 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEY, m_proxySSLKeyPath.c_str());
641                 if (!m_proxySSLKeyType.empty())
642                 {
643                     curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEYTYPE, m_proxySSLKeyType.c_str());
644                 }
645                 if (!m_proxyKeyPasswd.empty())
646                 {
647                     curl_easy_setopt(connectionHandle, CURLOPT_PROXY_KEYPASSWD, m_proxyKeyPasswd.c_str());
648                 }
649             }
650 #endif //CURL_HAS_TLS_PROXY
651         }
652         else
653         {
654             curl_easy_setopt(connectionHandle, CURLOPT_PROXY, "");
655         }
656 
657         if (request->GetContentBody())
658         {
659             curl_easy_setopt(connectionHandle, CURLOPT_READFUNCTION, ReadBody);
660             curl_easy_setopt(connectionHandle, CURLOPT_READDATA, &readContext);
661             curl_easy_setopt(connectionHandle, CURLOPT_SEEKFUNCTION, SeekBody);
662             curl_easy_setopt(connectionHandle, CURLOPT_SEEKDATA, &readContext);
663             if (request->IsEventStreamRequest())
664             {
665                 curl_easy_setopt(connectionHandle, CURLOPT_NOPROGRESS, 0L);
666 #if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
667                 curl_easy_setopt(connectionHandle, CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);
668                 curl_easy_setopt(connectionHandle, CURLOPT_XFERINFODATA, &readContext);
669 #else
670                 curl_easy_setopt(connectionHandle, CURLOPT_PROGRESSFUNCTION, CurlProgressCallback);
671                 curl_easy_setopt(connectionHandle, CURLOPT_PROGRESSDATA, &readContext);
672 #endif
673             }
674         }
675 
676         OverrideOptionsOnConnectionHandle(connectionHandle);
677         Aws::Utils::DateTime startTransmissionTime = Aws::Utils::DateTime::Now();
678         CURLcode curlResponseCode = curl_easy_perform(connectionHandle);
679         bool shouldContinueRequest = ContinueRequest(*request);
680         if (curlResponseCode != CURLE_OK && shouldContinueRequest)
681         {
682             response->SetClientErrorType(CoreErrors::NETWORK_CONNECTION);
683             Aws::StringStream ss;
684             ss << "curlCode: " << curlResponseCode << ", " << curl_easy_strerror(curlResponseCode);
685             response->SetClientErrorMessage(ss.str());
686             AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Curl returned error code " << curlResponseCode
687                     << " - " << curl_easy_strerror(curlResponseCode));
688         }
689         else if(!shouldContinueRequest)
690         {
691             response->SetClientErrorType(CoreErrors::USER_CANCELLED);
692             response->SetClientErrorMessage("Request cancelled by user's continuation handler");
693         }
694         else
695         {
696             long responseCode;
697             curl_easy_getinfo(connectionHandle, CURLINFO_RESPONSE_CODE, &responseCode);
698             response->SetResponseCode(static_cast<HttpResponseCode>(responseCode));
699             AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned http response code " << responseCode);
700 
701             char* contentType = nullptr;
702             curl_easy_getinfo(connectionHandle, CURLINFO_CONTENT_TYPE, &contentType);
703             if (contentType)
704             {
705                 response->SetContentType(contentType);
706                 AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned content type " << contentType);
707             }
708 
709             if (request->GetMethod() != HttpMethod::HTTP_HEAD &&
710                 writeContext.m_client->IsRequestProcessingEnabled() &&
711                 response->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER))
712             {
713                 const Aws::String& contentLength = response->GetHeader(Aws::Http::CONTENT_LENGTH_HEADER);
714                 int64_t numBytesResponseReceived = writeContext.m_numBytesResponseReceived;
715                 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response content-length header: " << contentLength);
716                 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response body length: " << numBytesResponseReceived);
717                 if (StringUtils::ConvertToInt64(contentLength.c_str()) != numBytesResponseReceived)
718                 {
719                     response->SetClientErrorType(CoreErrors::NETWORK_CONNECTION);
720                     response->SetClientErrorMessage("Response body length doesn't match the content-length header.");
721                     AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Response body length doesn't match the content-length header.");
722                 }
723             }
724 
725             AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Releasing curl handle " << connectionHandle);
726         }
727 
728         double timep;
729         CURLcode ret = curl_easy_getinfo(connectionHandle, CURLINFO_NAMELOOKUP_TIME, &timep); // DNS Resolve Latency, seconds.
730         if (ret == CURLE_OK)
731         {
732             request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::DnsLatency), static_cast<int64_t>(timep * 1000));// to milliseconds
733         }
734 
735         ret = curl_easy_getinfo(connectionHandle, CURLINFO_STARTTRANSFER_TIME, &timep); // Connect Latency
736         if (ret == CURLE_OK)
737         {
738             request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::ConnectLatency), static_cast<int64_t>(timep * 1000));
739         }
740 
741         ret = curl_easy_getinfo(connectionHandle, CURLINFO_APPCONNECT_TIME, &timep); // Ssl Latency
742         if (ret == CURLE_OK)
743         {
744             request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::SslLatency), static_cast<int64_t>(timep * 1000));
745         }
746 
747         const char* ip = nullptr;
748         auto curlGetInfoResult = curl_easy_getinfo(connectionHandle, CURLINFO_PRIMARY_IP, &ip); // Get the IP address of the remote endpoint
749         if (curlGetInfoResult == CURLE_OK && ip)
750         {
751             request->SetResolvedRemoteHost(ip);
752         }
753         if (curlResponseCode != CURLE_OK)
754         {
755             m_curlHandleContainer.DestroyCurlHandle(connectionHandle);
756         }
757         else
758         {
759             m_curlHandleContainer.ReleaseCurlHandle(connectionHandle);
760         }
761         //go ahead and flush the response body stream
762         response->GetResponseBody().flush();
763         request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::RequestLatency), (DateTime::Now() - startTransmissionTime).count());
764     }
765 
766     if (headers)
767     {
768         curl_slist_free_all(headers);
769     }
770 
771     return response;
772 }
773