http_client_libcurl.c
Go to the documentation of this file.
1 
11 #include "http_client_libcurl.h"
12 #include "string_util.h"
13 #include "http_definitions.h"
14 #include "mcl_core/mcl_assert.h"
15 #include "mcl_core/mcl_memory.h"
16 #include <openssl/ssl.h>
17 #include <openssl/err.h>
18 
19 #define CARRIAGE_RETURN '\r'
20 #define LINE_FEED '\n'
21 #define DOMAIN_SEPERATOR '\\'
22 #define CRLF_LENGTH 2
23 
24 #define SUPPORTED_CIPHERS_LIST \
25  "AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:"\
26  "ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:"
27 
28 // 2GB is the limit for CURLOPT_INFILESIZE.
29 #define CURL_2GB_LIMIT 0x80000000UL
30 
31 // Data structure to be passed as an argument to libcurl callback set with CURLOPT_WRITEFUNCTION option.
32 typedef struct libcurl_payload_t
33 {
37 
38 // Data structure for certificates.
39 typedef struct mcl_certificate_t
40 {
41  char *content;
44 
46 {
50 
52 
53 static CURLcode _ssl_context_callback(CURL *curl, void *ssl_context, void *certificates);
54 static mcl_size_t _response_payload_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_payload);
55 static mcl_size_t _response_header_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_header);
56 static mcl_size_t _request_payload_callback_for_put(char *buffer, mcl_size_t size, mcl_size_t count, void *user_context);
57 static mcl_bool_t _is_empty_line(char *line);
58 static struct curl_slist *_set_request_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context);
59 static struct curl_slist *_set_payload_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context,
60  struct curl_slist *header_list);
61 static void _set_in_file_size(CURL *curl, mcl_size_t payload_size);
62 static mcl_error_t _convert_to_mcl_return_code(CURLcode curl_code);
63 static void _header_list_destroy_callback(void **item);
64 static void _certificate_list_destroy_callback(mcl_certificate_t **certificate);
65 
66 #if MCL_LOG_ENABLED_COMPILE_TIME(MCL_LOG_LEVEL_DEBUG)
67 static int _curl_debug_callback(CURL *curl, curl_infotype info_type, char *data, mcl_size_t size, void *debug_data);
68 #endif
69 
71 {
72  mcl_error_t code = MCL_OK;
73 
74  MCL_DEBUG_ENTRY("mcl_http_client_configuration_t *configuration = <%p>, mcl_http_client_t *http_client = <%p>", configuration, http_client);
75 
76  MCL_ASSERT_NOT_NULL(http_client, code);
77 
78  *http_client = MCL_NULL;
79 
80  MCL_ASSERT_NOT_NULL(configuration, code);
81 
82  // Create http client.
83  MCL_NEW(*http_client);
84  MCL_ASSERT_CODE_MESSAGE(MCL_NULL != *http_client, MCL_OUT_OF_MEMORY, "Memory can not be allocated for http client.");
85 
86  (*http_client)->certificates = MCL_NULL;
87  (*http_client)->curl = MCL_NULL;
88 
89  code = mcl_list_initialize(&(*http_client)->certificates);
90 
91  if (MCL_OK == code)
92  {
93  // SSL_library_init always returns 1, safe to ignore.
94  (void)SSL_library_init();
95 
96  // Initialize curl memory functions.
98  {
100  curl_global_init_mem(CURL_GLOBAL_DEFAULT, mcl_memory_malloc, mcl_memory_free, mcl_memory_realloc, string_util_strdup, mcl_memory_calloc);
101  }
102 
103  // Initialize curl object.
104  (*http_client)->curl = curl_easy_init();
105 
106  if (MCL_NULL == (*http_client)->curl)
107  {
108  MCL_ERROR_STRING("Libcurl easy interface can not be initialized.");
109  code = MCL_FAIL;
110  }
111  }
112 
113  if (MCL_OK == code)
114  {
115  // Declare a local curl instance for code clarity.
116  CURL *curl = (*http_client)->curl;
117 
118  // Set remote port number to work with.
119  curl_easy_setopt(curl, CURLOPT_PORT, configuration->port);
120 
121  // Set timeout values.
122  curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long) configuration->http_request_timeout);
123  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, (long) configuration->http_request_timeout);
124 
125  // Set supported ciphers and tls version.
126  curl_easy_setopt(curl, CURLOPT_SSL_CIPHER_LIST, SUPPORTED_CIPHERS_LIST);
127  curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
128 
129  // Verify the server's SSL certificate.
130  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
131  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
132 
133 #if MCL_LOG_ENABLED_COMPILE_TIME(MCL_LOG_LEVEL_DEBUG)
134  // Set logging if enabled and level set at least DEBUG.
135  curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
136  curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, _curl_debug_callback);
137 #endif
138 
139  // Make sure header of the response is processed by a different callback from its payload.
140  curl_easy_setopt(curl, CURLOPT_HEADER, 0);
141 
142  // Set callback for processing the received header.
143  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _response_header_callback);
144 
145  // Set callback for processing the received payload.
146  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _response_payload_callback);
147 
148  // The headers specified in CURLOPT_HEADER will be used in requests to both servers and proxies.
149  curl_easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_UNIFIED);
150 
151  // Close the connection when done with the transfer.
152  curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
153 
154  // Set user-agent.
155  curl_easy_setopt(curl, CURLOPT_USERAGENT, configuration->user_agent);
156 
157  // Set certificate.
158  if (MCL_NULL != configuration->certificate)
159  {
160  code = mcl_http_client_add_certificate(*http_client, configuration->certificate, configuration->certificate_is_file);
161  }
162  }
163 
164  // Set proxy options if proxy is used.
165  if ((MCL_OK == code) && (MCL_NULL != configuration->proxy_hostname))
166  {
167  // Declare a local curl instance for code clarity.
168  CURL *curl = (*http_client)->curl;
169 
170  curl_easy_setopt(curl, CURLOPT_PROXY, configuration->proxy_hostname);
171  curl_easy_setopt(curl, CURLOPT_PROXYPORT, configuration->proxy_port);
172  curl_easy_setopt(curl, CURLOPT_PROXYTYPE, configuration->proxy_type);
173  curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
174 
175  if (MCL_NULL != configuration->proxy_username)
176  {
177  if (MCL_NULL != configuration->proxy_domain)
178  {
179  char *proxy_username;
180 
181  mcl_size_t proxy_domain_length = string_util_strlen(configuration->proxy_domain);
182  mcl_size_t proxy_username_length = string_util_strlen(configuration->proxy_username);
183 
184  // +1 for DOMAIN_SEPARATOR.
185  mcl_size_t proxy_entire_username_length = proxy_domain_length + proxy_username_length + 1;
186 
187  proxy_username = MCL_MALLOC(proxy_entire_username_length + MCL_NULL_CHAR_SIZE);
188 
189  if (MCL_NULL == proxy_username)
190  {
191  code = MCL_OUT_OF_MEMORY;
192  }
193  else
194  {
195  // Concatenate domain and username.
196  string_util_memcpy(proxy_username, configuration->proxy_domain, proxy_domain_length);
197  proxy_username[proxy_domain_length] = DOMAIN_SEPERATOR;
198  string_util_memcpy(proxy_username + proxy_domain_length + 1, configuration->proxy_username, proxy_username_length + MCL_NULL_CHAR_SIZE);
199 
200  curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, proxy_username);
201  free(proxy_username);
202  }
203  }
204  else
205  {
206  curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, configuration->proxy_username);
207  }
208 
209  curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, configuration->proxy_password);
210  }
211  }
212 
214  if (MCL_OK != code)
215  {
216  mcl_http_client_destroy(http_client);
217  }
218 
219  MCL_DEBUG_LEAVE("retVal = <%d>", code);
220  return code;
221 }
222 
223 mcl_error_t mcl_http_client_add_certificate(mcl_http_client_t *http_client, const char *certificate, mcl_bool_t is_file)
224 {
225  mcl_certificate_t *certificate_item = MCL_NULL;
226  mcl_error_t code = MCL_OK;
227 
228  MCL_DEBUG_ENTRY("mcl_http_client_t *http_client = <%p>, const char *certificate = <%p>, mcl_bool_t certificate_is_file = <%u>",
229  http_client, certificate, is_file);
230 
231  MCL_ASSERT_NOT_NULL(http_client, code);
232  MCL_ASSERT_NOT_NULL(certificate, code);
233 
234  MCL_NEW(certificate_item);
235 
236  if (MCL_NULL != certificate_item)
237  {
238  certificate_item->content = string_util_strdup(certificate);
239  certificate_item->is_file = is_file;
240 
241  if (MCL_NULL != certificate_item->content)
242  {
243  code = mcl_list_add(http_client->certificates, certificate_item);
244  }
245  else
246  {
247  code = MCL_OUT_OF_MEMORY;
248  }
249  }
250  else
251  {
252  code = MCL_OUT_OF_MEMORY;
253  }
254 
255  if (MCL_OK == code)
256  {
257  if ((1 == http_client->certificates->count) && (MCL_TRUE == is_file))
258  {
259  curl_easy_setopt(http_client->curl, CURLOPT_CAINFO, certificate_item->content);
260  }
261  else
262  {
263  curl_easy_setopt(http_client->curl, CURLOPT_SSL_CTX_DATA, http_client->certificates);
264  curl_easy_setopt(http_client->curl, CURLOPT_SSL_CTX_FUNCTION, _ssl_context_callback);
265  }
266  }
267 
269 
270  if (MCL_OK != code)
271  {
272  _certificate_list_destroy_callback(&certificate_item);
273  }
274 
275  MCL_DEBUG_LEAVE("retVal = <%d>", code);
276  return code;
277 }
278 
280 {
281  mcl_error_t return_code;
282  struct curl_slist *request_header_list;
283  mcl_list_t *response_header = MCL_NULL;
284  libcurl_payload_t *response_payload = MCL_NULL;
285  CURLcode curl_code;
286  mcl_int64_t response_code;
287 
288  // default_callback_user_context will be used only for PUT requests without user callback function.
289  default_callback_user_context_t default_callback_user_context;
290 
291  MCL_DEBUG_ENTRY("mcl_http_client_t *http_client = <%p>, mcl_http_request_t *http_request = <%p>, mcl_http_response_t **http_response = <%p>",
292  http_client, http_request, http_response);
293 
294  // Set request options. If there are no request headers, this function returns null but the other options for the request are set anyway.
295  request_header_list = _set_request_options(http_client->curl, http_request, &default_callback_user_context);
296 
297  // Initialize a header list to store response header, clear the list of request headers if this initialization fails and return.
298  return_code = mcl_list_initialize(&response_header);
299 
300  if (MCL_OK == return_code)
301  {
302  // Set pointer passed to the _response_header_callback function as fourth argument.
303  curl_easy_setopt(http_client->curl, CURLOPT_HEADERDATA, response_header);
304 
305  // Set pointer passed to the _response_callback function as fourth argument.
306  MCL_NEW(response_payload);
307  if (MCL_NULL != response_payload)
308  {
309  response_payload->data = MCL_NULL;
310  response_payload->size = 0;
311  curl_easy_setopt(http_client->curl, CURLOPT_WRITEDATA, response_payload);
312  }
313  else
314  {
315  return_code = MCL_OUT_OF_MEMORY;
316  }
317  }
318 
319  if (MCL_OK == return_code)
320  {
321  // Perform the transfer.
322  MCL_INFO("Sending HTTP request...");
323 
324  curl_code = curl_easy_perform(http_client->curl);
325  return_code = _convert_to_mcl_return_code(curl_code);
326  MCL_INFO("HTTP request sent. Result code = <%u>", return_code);
327  }
328 
329  // Free the list of http request header.
330  curl_slist_free_all(request_header_list);
331 
332  if (MCL_OK == return_code)
333  {
334  // Gather response into http_response object.
335  curl_easy_getinfo(http_client->curl, CURLINFO_RESPONSE_CODE, &response_code);
336 
337  return_code = mcl_http_response_initialize(response_header, response_payload->data, response_payload->size,
338  (E_MCL_HTTP_STATUS_CODE) response_code, http_response);
339  }
340 
341  if (MCL_OK != return_code)
342  {
344 
345  if (MCL_NULL != response_payload)
346  {
347  MCL_FREE(response_payload->data);
348  }
349  }
350  MCL_FREE(response_payload);
351 
352  MCL_DEBUG_LEAVE("retVal = <%d>", return_code);
353  return return_code;
354 }
355 
357 {
358  MCL_DEBUG_ENTRY("mcl_http_client_t **http_client = <%p>", http_client);
359 
360  if ((MCL_NULL != http_client) && (MCL_NULL != *http_client))
361  {
362  if (MCL_NULL != (*http_client)->curl)
363  {
364  curl_easy_cleanup((*http_client)->curl);
365  }
366 
368  MCL_FREE(*http_client);
369 
370  MCL_DEBUG("Http client handle is destroyed.");
371  }
372  else
373  {
374  MCL_DEBUG("Http client is already null.");
375  }
376 
377  MCL_DEBUG_LEAVE("retVal = void");
378 }
379 
380 // This function is the callback which is called to set the SSL certificate given by the parameter "certificate".
381 static CURLcode _ssl_context_callback(CURL *curl, void *ssl_context, void *certificates)
382 {
383  CURLcode curl_code = CURLE_OK;
384  X509_STORE *store;
385  mcl_list_t *certificate_list_local = (mcl_list_t *)certificates;
386  mcl_list_node_t *certificate_node = MCL_NULL;
387 
388  MCL_DEBUG_ENTRY("CURL *curl = <%p>, void *ssl_context = <%p>, void *certificate_list = <%p>", curl, ssl_context, certificates);
389 
390  // Unused parameter.
391  (void) curl;
392 
393  if (0 == certificate_list_local->count)
394  {
395  MCL_INFO("No certificate is provided by the user for peer verification. Continuing with the existing CA certificate store.");
396  return CURLE_OK;
397  }
398 
399  // Get a pointer to the X509 certificate store (which may be empty!).
400  store = SSL_CTX_get_cert_store((SSL_CTX *)ssl_context);
401 
402  mcl_list_reset(certificate_list_local);
403 
404  while ((MCL_OK == mcl_list_next(certificate_list_local, &certificate_node)) && (CURLE_OK == curl_code))
405  {
406  BIO *bio;
407  int index;
408  int certificates_number = 0;
409  struct stack_st_X509_INFO *certificate_info = MCL_NULL;
410  mcl_certificate_t *certificate = (mcl_certificate_t *) certificate_node->data;
411 
412  if (MCL_TRUE == certificate->is_file)
413  {
414  bio = BIO_new_file(certificate->content, "r");
415 
416  if (MCL_NULL == bio)
417  {
418  MCL_ERROR("Certificate file could not be read: <%s>", certificate->content);
419  curl_code = CURLE_READ_ERROR;
420  }
421  }
422  else
423  {
424  bio = BIO_new_mem_buf(certificate->content, (int) string_util_strlen(certificate->content));
425 
426  if (MCL_NULL == bio)
427  {
428  curl_code = CURLE_OUT_OF_MEMORY;
429  }
430  }
431 
432  if (CURLE_OK == curl_code)
433  {
434  // Read certificate info.
435  certificate_info = PEM_X509_INFO_read_bio(bio, MCL_NULL, MCL_NULL, MCL_NULL);
436  BIO_free(bio);
437 
438  MCL_ASSERT_CODE_MESSAGE(MCL_NULL != certificate_info, CURLE_SSL_CERTPROBLEM, "Certificate info can not be read from memory.");
439 
440  // Read all PEM formatted certificates from memory into an X509 structure that SSL can use.
441  certificates_number = sk_X509_INFO_num(certificate_info);
442  }
443 
444  for (index = 0; (index < certificates_number) && (CURLE_OK == curl_code); ++index)
445  {
446  X509_INFO *temp_info = sk_X509_INFO_value(certificate_info, index);
447  if (MCL_NULL != temp_info)
448  {
449  if (MCL_NULL != temp_info->x509)
450  {
451  if (0 == X509_STORE_add_cert(store, temp_info->x509))
452  {
453  // Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the certificate is already in the store.
454  // That could happen if libcurl already loaded the certificate from a ca cert bundle set at libcurl build-time or runtime.
455  unsigned long error = ERR_peek_last_error();
456 
457  if ((ERR_GET_LIB(error) != ERR_LIB_X509) || (ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE))
458  {
459  MCL_ERROR_STRING("Certificate can not be added to store.");
460  curl_code = CURLE_SSL_CERTPROBLEM;
461  }
462  else
463  {
464  MCL_INFO("Did not add the certificate to store since it is already in the store.");
465  }
466  }
467  }
468  else if ((MCL_NULL != temp_info->crl) && (0 == X509_STORE_add_crl(store, temp_info->crl)))
469  {
470  MCL_ERROR_STRING("Certificate can not be added to store.");
471  curl_code = CURLE_SSL_CERTPROBLEM;
472  }
473  }
474  else
475  {
476  MCL_ERROR_STRING("Certificate info value can not be read from memory.");
477  curl_code = CURLE_SSL_CERTPROBLEM;
478  }
479  }
480 
481  // Clean up.
482  sk_X509_INFO_pop_free(certificate_info, X509_INFO_free);
483  }
484 
485  MCL_DEBUG_LEAVE("retVal = <%d>", curl_code);
486  return curl_code;
487 }
488 
489 // This function is the callback which is called once for the payload of the received response.
490 // The function initializes a string from the received data in the buffer "received_data"
491 // composed of "count" elements of each "size" bytes long and copies this string to the string array "response_payload".
492 static mcl_size_t _response_payload_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_payload)
493 {
494  mcl_size_t received_data_size = size * count;
495  libcurl_payload_t *payload = (libcurl_payload_t *) response_payload;
496 
497  MCL_DEBUG_ENTRY("void *received_data = <%p>, mcl_size_t size = <%u>, mcl_size_t count = <%u>, void *response_payload = <%p>",
498  received_data, size, count, response_payload);
499 
500  if (payload->data)
501  {
502  MCL_RESIZE(payload->data, payload->size + received_data_size);
503  MCL_ASSERT_CODE_MESSAGE(MCL_NULL != payload->data, 0, "Memory re-allocation for payload data failed!");
504  }
505  else
506  {
507  payload->data = MCL_MALLOC(received_data_size);
508  MCL_ASSERT_CODE_MESSAGE(MCL_NULL != payload->data, 0, "Memory allocation for payload data failed!");
509  }
510 
511  string_util_memcpy(payload->data + payload->size, received_data, received_data_size);
512 
513  payload->size += received_data_size;
514 
515  MCL_DEBUG_LEAVE("retVal = <%d>", received_data_size);
516  return received_data_size;
517 }
518 
519 // This function is the callback which is called once for every header line of the received http response.
520 // The function initializes a string from the received data in the buffer "received_data"
521 // composed of "count" elements of each "size" bytes long and copies this string to the string array "response_header".
522 static mcl_size_t _response_header_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_header)
523 {
524  mcl_list_t *header_list = (mcl_list_t *) response_header;
525  char *header_line = MCL_NULL;
526  mcl_size_t received_data_size = size * count;
527 
528  MCL_DEBUG_ENTRY("void *received_data = <%p>, mcl_size_t size = <%u>, mcl_size_t count = <%u>, void *response_header = <%p>",
529  received_data, size, count, response_header);
530 
531  // Eliminate empty line.
532  if ((CRLF_LENGTH == received_data_size) && (MCL_TRUE == _is_empty_line((char *) received_data)))
533  {
534  MCL_DEBUG_LEAVE("retVal = <%d>", received_data_size);
535  return received_data_size;
536  }
537 
538  // Create an buffer for the received header line (CRLF excluded), -2 for CRLF, +1 for NULL.
539  header_line = (char *) MCL_MALLOC(received_data_size - 1);
540 
541  if (MCL_NULL == header_line)
542  {
543  // Could not allocate.
544  MCL_DEBUG_LEAVE("retVal = <0>");
545  return 0;
546  }
547 
548  // Received data length is received_data_size - CRLF_LENGTH (CRLF excluded).
549  string_util_memcpy(header_line, received_data, received_data_size - CRLF_LENGTH);
550 
551  // Make sure the header line is null terminated.
552  header_line[received_data_size - CRLF_LENGTH] = MCL_NULL_CHAR;
553 
554  // Add header line to header list, destroy header line if the add operation is unsuccessful.
555  if (MCL_OK != mcl_list_add(header_list, header_line))
556  {
557  MCL_FREE(header_line);
558  MCL_DEBUG_LEAVE("retVal = <0>");
559  return 0;
560  }
561 
562  MCL_DEBUG_LEAVE("retVal = <%d>", received_data_size);
563  return received_data_size;
564 }
565 
566 // This function is the callback which is called when HTTP PUT is requested.
567 // The function takes in parameter "http_request" of type http_request_t and copies its payload to "buffer".
568 static mcl_size_t _request_payload_callback_for_put(char *buffer, mcl_size_t size, mcl_size_t count, void *user_context)
569 {
570  default_callback_user_context_t *default_callback_user_context = (default_callback_user_context_t *) user_context;
571  mcl_size_t buffer_size = size * count;
572  mcl_size_t remaining_size = default_callback_user_context->http_request->payload_size - default_callback_user_context->callback_offset;
573  mcl_size_t payload_size = (buffer_size > remaining_size) ? remaining_size : buffer_size;
574 
575  MCL_DEBUG_ENTRY("char *buffer = <%s>, mcl_size_t size = <%u>, mcl_size_t count = <%u>, void *user_context = <%p>", buffer, size, count, user_context);
576 
577  if (0 == payload_size)
578  {
579  MCL_DEBUG("Remaining payload size is zero. Nothing will be copied.");
580  }
581  else
582  {
583  string_util_memcpy(buffer, default_callback_user_context->http_request->payload + default_callback_user_context->callback_offset, payload_size);
584  }
585 
586  default_callback_user_context->callback_offset += payload_size;
587 
588  MCL_DEBUG_LEAVE("retVal = <%u>", payload_size);
589  return payload_size;
590 }
591 
592 // This function checks if the given line is an empty line or not.
593 static mcl_bool_t _is_empty_line(char *line)
594 {
595  mcl_bool_t is_empty;
596 
597  MCL_DEBUG_ENTRY("char *line = <%s>", line);
598 
599  is_empty = ((CARRIAGE_RETURN == line[0]) && (LINE_FEED == line[1])) ? MCL_TRUE : MCL_FALSE;
600 
601  MCL_DEBUG_LEAVE("retVal = <%d>", is_empty);
602  return is_empty;
603 }
604 
605 // This function sets the options for the http request and returns the curl list of request headers.
606 static struct curl_slist *_set_request_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context)
607 {
608  struct curl_slist *request_header_list = MCL_NULL;
609  char *request_header_line = MCL_NULL;
610  mcl_size_t index;
611 
612  MCL_DEBUG_ENTRY("CURL *curl = <%p>, mcl_http_request_t *http_request = <%p>, default_callback_user_context_t *user_context = <%p>",
613  curl, http_request, user_context);
614 
615  // Set the URL to use in the request.
616  // Reminder: Request URI is an absolute path including the host name.
617  curl_easy_setopt(curl, CURLOPT_URL, http_request->uri);
618 
619  // Set http method and data for post and put methods.
620  curl_easy_setopt(curl, CURLOPT_HTTPGET, 0);
621  curl_easy_setopt(curl, CURLOPT_POST, 0);
622  curl_easy_setopt(curl, CURLOPT_UPLOAD, 0);
623  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, MCL_NULL);
624 
625  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1);
626  curl_easy_setopt(curl, CURLOPT_INFILESIZE, -1);
627  curl_easy_setopt(curl, CURLOPT_READDATA, MCL_NULL);
628  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, MCL_NULL);
629 
630  switch (http_request->method)
631  {
632  case MCL_HTTP_GET :
633  curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
634  break;
635 
636  case MCL_HTTP_POST :
637  curl_easy_setopt(curl, CURLOPT_POST, 1);
638 
639  // If a callback function is present, use Transfer-Encoding : chunked.
640  if (MCL_NULL == http_request->stream_callback)
641  {
642  // Normal http transfer without chunked encoding.
643  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (void *)http_request->payload);
644 
645  // If post field size is not set, you have to use transfer-encoding:chunked otherwise no data will be sent.
646  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, http_request->payload_size);
647  }
648  else
649  {
650  // Transfer-Encoding: chunked.
651  curl_easy_setopt(curl, CURLOPT_READFUNCTION, http_request->stream_callback);
652  curl_easy_setopt(curl, CURLOPT_READDATA, http_request->stream_data);
653  request_header_list = curl_slist_append(request_header_list, http_header_names[HTTP_HEADER_TRANSFER_ENCODING_CHUNKED]);
654  }
655  break;
656 
657  case MCL_HTTP_PUT :
658  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
659  request_header_list = _set_payload_options(curl, http_request, user_context, request_header_list);
660  break;
661 
662  case MCL_HTTP_PATCH:
663  curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
664  curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
665  request_header_list = _set_payload_options(curl, http_request, user_context, request_header_list);
666  break;
667 
668  default :
669  MCL_ERROR_RETURN(MCL_NULL, "Unsupported HTTP method is requested.");
670  }
671 
672  // Set request header.
673  mcl_list_reset(http_request->header);
674  for (index = 0; (index < http_request->header->count) && (MCL_NULL != http_request->header->current); ++index)
675  {
676  mcl_list_node_t *header_node = http_request->header->current;
677  request_header_line = header_node->data;
678  request_header_list = curl_slist_append(request_header_list, request_header_line);
679  http_request->header->current = http_request->header->current->next;
680  }
681 
682  // Add expect header to prevent that libcurl adds it automatically (HTTP continue bug).
683  request_header_list = curl_slist_append(request_header_list, "Expect:");
684 
685  // Separate HTTP headers for MindSphere and Proxy.
686  curl_easy_setopt(curl, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE);
687  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_header_list);
688  curl_easy_setopt(curl, CURLOPT_PROXYHEADER, MCL_NULL);
689 
690  MCL_DEBUG_LEAVE("retVal = <%p>", request_header_list);
691  return request_header_list;
692 }
693 
694 // This functions set options related to the payload of the request and updates the request header list if necessary.
695 static struct curl_slist *_set_payload_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context,
696  struct curl_slist *header_list)
697 {
698  struct curl_slist *new_header_list = MCL_NULL;
699 
700  MCL_DEBUG_ENTRY("CURL *curl = <%p>, mcl_http_request_t *http_request = <%p>, default_callback_user_context_t *user_context = <%p>, "\
701  "struct curl_slist *header_list = <%p>", curl, http_request, user_context, header_list);
702 
703  if (MCL_NULL == http_request->stream_callback)
704  {
705  // Set user context parameters.
706  user_context->http_request = http_request;
707  user_context->callback_offset = 0;
708 
709  // Set curl callback and user context.
710  curl_easy_setopt(curl, CURLOPT_READFUNCTION, _request_payload_callback_for_put);
711  curl_easy_setopt(curl, CURLOPT_READDATA, user_context);
712 
713  _set_in_file_size(curl, http_request->payload_size);
714  }
715  else
716  {
717  curl_easy_setopt(curl, CURLOPT_READFUNCTION, http_request->stream_callback);
718  curl_easy_setopt(curl, CURLOPT_READDATA, http_request->stream_data);
719 
720  if (0 == http_request->payload_size)
721  {
722  // Transfer-Encoding: chunked.
723  new_header_list = curl_slist_append(header_list, http_header_names[HTTP_HEADER_TRANSFER_ENCODING_CHUNKED]);
724  }
725  else
726  {
727  _set_in_file_size(curl, http_request->payload_size);
728  }
729  }
730 
731  MCL_DEBUG_LEAVE("retVal = <%p>", new_header_list);
732  return new_header_list;
733 }
734 
735 static void _set_in_file_size(CURL *curl, mcl_size_t payload_size)
736 {
737  MCL_DEBUG_ENTRY("CURL *curl = <%p>, mcl_size_t payload_size = <%u>", curl, payload_size);
738 
739  if (payload_size < CURL_2GB_LIMIT)
740  {
741  curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long) payload_size);
742  }
743  else
744  {
745  curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) payload_size);
746  }
747 
748  MCL_DEBUG_LEAVE("retVal = void");
749 }
750 
751 #if MCL_LOG_ENABLED_COMPILE_TIME(MCL_LOG_LEVEL_DEBUG)
752 static int _curl_debug_callback(CURL *curl, curl_infotype info_type, char *data, mcl_size_t size, void *debug_data)
753 {
754  // Unused parameters.
755  (void) curl;
756  (void) debug_data;
757 
758  switch (info_type)
759  {
760  case CURLINFO_TEXT :
761  MCL_DEBUG("Curl debug info: %.*s", (int) size, data);
762  break;
763 
764  case CURLINFO_HEADER_OUT :
765  MCL_DEBUG("Curl: sent header.");
766  break;
767 
768  case CURLINFO_DATA_OUT :
769  MCL_DEBUG("Curl: sent protocol data.");
770  break;
771 
772  case CURLINFO_SSL_DATA_OUT :
773  MCL_DEBUG("Curl: sent SSL/TLS (binary) data.");
774  break;
775 
776  case CURLINFO_HEADER_IN :
777  MCL_DEBUG("Curl: received header.");
778  break;
779 
780  case CURLINFO_DATA_IN :
781  MCL_DEBUG("Curl: received protocol data.");
782  break;
783 
784  case CURLINFO_SSL_DATA_IN :
785  MCL_DEBUG("Curl: received SSL/TLS (binary) data.");
786  break;
787 
788  default :
789  break;
790  }
791 
792  return CURLE_OK;
793 }
794 #endif
795 
796 static mcl_error_t _convert_to_mcl_return_code(CURLcode curl_code)
797 {
798  mcl_error_t mcl_code;
799 
800  MCL_DEBUG_ENTRY("CURLcode curl_code = <%d>", curl_code);
801 
802  switch (curl_code)
803  {
804  case CURLE_OK :
805  mcl_code = MCL_OK;
806  break;
807 
808  case CURLE_COULDNT_RESOLVE_PROXY :
809  mcl_code = MCL_COULD_NOT_RESOLVE_PROXY;
810  break;
811 
812  case CURLE_COULDNT_RESOLVE_HOST :
813  mcl_code = MCL_COULD_NOT_RESOLVE_HOST;
814  break;
815 
816  case CURLE_COULDNT_CONNECT :
817  mcl_code = MCL_COULD_NOT_CONNECT;
818  break;
819 
820  case CURLE_OUT_OF_MEMORY :
821  mcl_code = MCL_OUT_OF_MEMORY;
822  break;
823 
824  case CURLE_SSL_CONNECT_ERROR :
825  mcl_code = MCL_SSL_HANDSHAKE_FAIL;
826  break;
827 
828  case CURLE_PEER_FAILED_VERIFICATION :
830  break;
831 
832  case CURLE_SEND_ERROR :
833  mcl_code = MCL_NETWORK_SEND_FAIL;
834  break;
835 
836  case CURLE_RECV_ERROR :
837  mcl_code = MCL_NETWORK_RECEIVE_FAIL;
838  break;
839 
840  case CURLE_SSL_CERTPROBLEM :
841  mcl_code = MCL_IMPROPER_CERTIFICATE;
842  break;
843 
844  case CURLE_OPERATION_TIMEDOUT :
845  mcl_code = MCL_REQUEST_TIMEOUT;
846  break;
847 
848  default :
849  mcl_code = MCL_FAIL;
850  break;
851  }
852 
853  MCL_DEBUG_LEAVE("retVal = <%d>", mcl_code);
854  return mcl_code;
855 }
856 
857 static void _header_list_destroy_callback(void **item)
858 {
859  MCL_FREE(*item);
860 }
861 
863 {
864  MCL_DEBUG_ENTRY("mcl_certificate_t **certificate = <%p>", certificate);
865 
866  if ((MCL_NULL != certificate) && (MCL_NULL != *certificate))
867  {
868  MCL_FREE((*certificate)->content);
869  MCL_FREE(*certificate);
870  }
871 
872  MCL_DEBUG_LEAVE("retVal = <void>");
873 }
HTTP definitions module header file.
MCL failed to connect to the host or proxy.
#define MCL_FUNCTION_LEAVE_LABEL
MCL_CORE_EXPORT void * mcl_memory_calloc(mcl_size_t count, mcl_size_t bytes)
Definition: memory.c:24
size_t mcl_size_t
char * string_util_strdup(const char *string)
Definition: string_util.c:274
Assert module header file.
Success.
static mcl_size_t _response_payload_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_payload)
mcl_error_t mcl_http_client_initialize(mcl_http_client_configuration_t *configuration, mcl_http_client_t **http_client)
#define MCL_DEBUG(...)
Definition: mcl_log_util.h:114
static void _header_list_destroy_callback(void **item)
#define CRLF_LENGTH
char * uri
Uri of http request.
static mcl_size_t _response_header_callback(void *received_data, mcl_size_t size, mcl_size_t count, void *response_header)
static void _set_in_file_size(CURL *curl, mcl_size_t payload_size)
MCL_CORE_EXPORT mcl_error_t mcl_http_response_initialize(mcl_list_t *header, mcl_uint8_t *payload, mcl_size_t payload_size, E_MCL_HTTP_STATUS_CODE status_code, mcl_http_response_t **http_response)
Definition: http_response.c:17
A problem occured during SSL/TLS handshake.
Http patch method.
mcl_int32_t mcl_error_t
void mcl_http_client_destroy(mcl_http_client_t **http_client)
#define MCL_DEBUG_ENTRY(...)
Definition: mcl_log_util.h:115
Http put method.
mcl_size_t payload_size
Payload size of http request.
void * stream_data
Stream data.
void(* mcl_list_item_destroy_callback)(void **item)
Definition: mcl_list.h:61
#define SUPPORTED_CIPHERS_LIST
#define MCL_FALSE
const char * user_agent
User agent.
static mcl_size_t _request_payload_callback_for_put(char *buffer, mcl_size_t size, mcl_size_t count, void *user_context)
const char * proxy_username
Proxy username. Optional if proxy host name is set, ineffective otherwise.
E_MCL_PROXY proxy_type
Proxy type E_MCL_PROXY. Mandatory if proxy host name is set, ineffective otherwise.
String utility module header file.
const char * http_header_names[HTTP_HEADER_NAMES_END]
mcl_error_t mcl_http_client_add_certificate(mcl_http_client_t *http_client, const char *certificate, mcl_bool_t is_file)
MCL_CORE_EXPORT mcl_error_t mcl_list_next(mcl_list_t *list, mcl_list_node_t **node)
Definition: list.c:76
#define MCL_ERROR_STRING(string)
Definition: mcl_log_util.h:143
MCL_CORE_EXPORT void * mcl_memory_realloc(void *p, mcl_size_t bytes)
Definition: memory.c:34
static struct curl_slist * _set_payload_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context, struct curl_slist *header_list)
#define MCL_ASSERT_CODE_MESSAGE(condition, return_code,...)
Definition: mcl_assert.h:77
HTTP client libcurl module header file.
#define MCL_NEW(p)
Definition: mcl_memory.h:55
mcl_bool_t certificate_is_file
Flag to check if certificate is given as file or string.
mcl_uint16_t proxy_port
Proxy port number. Mandatory if proxy host name is set, ineffective otherwise.
#define MCL_NULL
Mindsphere certificate was not verified.
#define MCL_ERROR(...)
Definition: mcl_log_util.h:142
struct mcl_list_node_t * next
Next node in the list.
Definition: mcl_list.h:29
Host name given as a configuration parameter could not be resolved.
Http get method.
const char * certificate
Certificate. If it is NULL, default CA certificate store will be used (if available).
MCL_CORE_EXPORT mcl_error_t mcl_list_add(mcl_list_t *list, void *data)
Definition: list.c:34
#define MCL_FREE(p)
Definition: mcl_memory.h:59
static void _certificate_list_destroy_callback(mcl_certificate_t **certificate)
static mcl_error_t _convert_to_mcl_return_code(CURLcode curl_code)
static CURLcode _ssl_context_callback(CURL *curl, void *ssl_context, void *certificates)
#define MCL_RESIZE(p, bytes)
Definition: mcl_memory.h:58
void string_util_memcpy(void *destination, const void *source, mcl_size_t count)
Definition: string_util.c:229
E_MCL_HTTP_METHOD method
Http method of http request.
MCL_CORE_EXPORT void mcl_list_destroy_with_content(mcl_list_t **list, mcl_list_item_destroy_callback callback)
Definition: list.c:302
MCL_CORE_EXPORT void mcl_memory_free(void *p)
Definition: memory.c:45
uint8_t mcl_uint8_t
mcl_error_t mcl_http_client_send(mcl_http_client_t *http_client, mcl_http_request_t *http_request, mcl_http_response_t **http_response)
#define LINE_FEED
#define MCL_ASSERT_NOT_NULL(argument, return_variable)
Definition: mcl_assert.h:38
A problem occured when sending data to the network.
#define CURL_2GB_LIMIT
MCL_CORE_EXPORT void mcl_list_reset(mcl_list_t *list)
Definition: list.c:276
The server did not respond within a timeout period.
#define MCL_ERROR_RETURN(return_value,...)
Definition: mcl_assert.h:26
mcl_uint32_t http_request_timeout
Timeout value (in seconds) for HTTP requests. Default timeout is 300 seconds.
static struct curl_slist * _set_request_options(CURL *curl, mcl_http_request_t *http_request, default_callback_user_context_t *user_context)
Http post method.
The server certificate provided is in improper format and it can not be parsed.
#define DOMAIN_SEPERATOR
static mcl_bool_t _is_empty_line(char *line)
int64_t mcl_int64_t
Http transfer encoding chunked header.
A problem occured when receiving data from the network.
mcl_http_payload_callback stream_callback
Callback to be used with chunked Transfer-Encoding. If not used, it must be NULL. ...
const char * proxy_domain
Proxy domain. Optional if proxy host name and proxy username are set, ineffective otherwise...
mcl_uint8_t mcl_bool_t
const char * proxy_password
Proxy password. Mandatory if proxy host name and proxy username are set, ineffective otherwise...
mcl_uint16_t port
Port number.
mcl_uint8_t * payload
Payload of http request.
mcl_list_node_t * current
Current node of the list.
Definition: mcl_list.h:39
#define CARRIAGE_RETURN
CURL * curl
Curl handle.
Memory allocation fail.
MCL_CORE_EXPORT mcl_error_t mcl_list_initialize(mcl_list_t **list)
Definition: list.c:13
#define MCL_NULL_CHAR_SIZE
mcl_size_t count
Node count of the list.
Definition: mcl_list.h:40
#define MCL_MALLOC(bytes)
Definition: mcl_memory.h:54
mcl_list_t * certificates
List of server certificates.
const char * proxy_hostname
Proxy hostname. Optional.
#define MCL_DEBUG_LEAVE(...)
Definition: mcl_log_util.h:116
#define MCL_TRUE
MCL_CORE_EXPORT void * mcl_memory_malloc(mcl_size_t size)
Definition: memory.c:14
mcl_list_t * header
Header of http request.
Internal failure in MCL.
Proxy host name given as a configuration parameter could not be resolved.
static mcl_bool_t curl_global_initialized
void * data
Data of the node.
Definition: mcl_list.h:27
mcl_size_t string_util_strlen(const char *buffer)
Definition: string_util.c:35
#define MCL_INFO(...)
Definition: mcl_log_util.h:126
#define MCL_NULL_CHAR
E_MCL_HTTP_STATUS_CODE
Memory module interface header file.