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