LCOV - code coverage report
Current view: top level - root/contrail/src/contrail-common/http/client - http_curl.cc (source / functions) Hit Total Coverage
Test: OpenSDN C/C++ coverage (all TARGET_SET jobs) Lines: 10 263 3.8 %
Date: 2026-08-03 02:19:58 Functions: 1 31 3.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /***************************************************************************
       2             :  *                                  _   _ ____  _
       3             :  * Copyright (C) 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
       4             :  *
       5             :  * This software is licensed as described in the file COPYING, which
       6             :  * you should have received as part of this distribution. The terms
       7             :  * are also available at http://curl.haxx.se/docs/copyright.html.
       8             :  *
       9             :  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
      10             :  * copies of the Software, and permit persons to whom the Software is
      11             :  * furnished to do so, under the terms of the COPYING file.
      12             :  *
      13             :  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
      14             :  * KIND, either express or implied.
      15             :  *
      16             :  ***************************************************************************/
      17             : 
      18             : /*
      19             :  * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
      20             :  */
      21             : 
      22             : #include "http_client.h"
      23             : #include "http_curl.h"
      24             : #include <curl/curl.h>
      25             : #include <boost/asio.hpp>
      26             : #include <boost/bind/bind.hpp>
      27             : #include <boost/intrusive_ptr.hpp>
      28             : 
      29             : using namespace boost::placeholders;
      30             : 
      31             : #define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */
      32             : 
      33             : const CurlErrorCategory curl_error_category;
      34             : 
      35             : /* Update the event timer after curl_multi library calls */
      36           0 : static int multi_timer_cb(CURLM *multi, long timeout_ms, HttpClient *client)
      37             : {
      38             : 
      39           0 :   if ( timeout_ms > 0 )
      40             :   {
      41           0 :     client->StartTimer(timeout_ms);
      42             :   }
      43             :   else
      44             :   {
      45           0 :     client->CancelTimer();
      46           0 :     timer_cb(client->GlobalInfo());
      47             :   }
      48             : 
      49           0 :   return 0;
      50             : }
      51             : 
      52             : /* Die if we get a bad CURLMcode somewhere */
      53           0 : static void mcode_or_die(const char *where, CURLMcode code)
      54             : {
      55           0 :   if ( CURLM_OK != code )
      56             :   {
      57             :     const char *s;
      58           0 :     switch ( code )
      59             :     {
      60           0 :     case CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break;
      61           0 :     case CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
      62           0 :     case CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
      63           0 :     case CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
      64           0 :     case CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
      65           0 :     case CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
      66           0 :     case CURLM_LAST:               s="CURLM_LAST";               break;
      67           0 :     case CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";         break;
      68           0 :     default:                       s="CURLM_unknown";            break;
      69             :     }
      70             : 
      71           0 :     fprintf(MSG_OUT, "\nERROR: %s returns %s", where, s);
      72             :   }
      73           0 : }
      74             : 
      75             : /* Check for completed transfers, and remove their easy handles */
      76           0 : static void check_multi_info(GlobalInfo *g)
      77             : {
      78             :   char *eff_url;
      79             :   CURLMsg *msg;
      80             :   int msgs_left;
      81             :   ConnInfo *conn;
      82             :   CURL *easy;
      83             :   CURLcode res;
      84             : 
      85           0 :   while ((msg = curl_multi_info_read(g->multi, &msgs_left)))
      86             :   {
      87           0 :     if (msg->msg == CURLMSG_DONE)
      88             :     {
      89           0 :       easy = msg->easy_handle;
      90           0 :       res = msg->data.result;
      91           0 :       curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
      92           0 :       curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
      93             : 
      94           0 :       if (conn) {
      95           0 :         boost::system::error_code error(res, curl_error_category);
      96           0 :         std::string empty_str("");
      97           0 :         if (conn->connection->HttpClientCb() != NULL)
      98           0 :             conn->connection->HttpClientCb()(empty_str, error);
      99           0 :       }
     100             :     }
     101             :   }
     102           0 : }
     103             : 
     104             : typedef boost::intrusive_ptr<HttpClientSession> TcpSessionPtr;
     105             : 
     106           0 : static void event_cb_impl(GlobalInfo *g, TcpSessionPtr session, int action,
     107             :                           const boost::system::error_code &error, 
     108             :                           std::size_t bytes_transferred)
     109             : {
     110             : 
     111           0 :   if (session->IsClosed()) return;
     112             : 
     113           0 :   if (g->client->IsErrorHard(error)) return;
     114             : 
     115             :   CURLMcode rc;
     116           0 :   rc = curl_multi_socket_action(g->multi, session->socket()->native_handle(), action, &g->still_running);
     117             : 
     118           0 :   mcode_or_die("event_cb: curl_multi_socket_action", rc);
     119           0 :   check_multi_info(g);
     120             : 
     121           0 :   if ( g->still_running <= 0 )
     122             :   {
     123           0 :     g->client->CancelTimer();
     124             :   }
     125             : }
     126             : 
     127             : /* Called by asio when there is an action on a socket */
     128           0 : static void event_cb(GlobalInfo *g, TcpSessionPtr session, int action,
     129             :                      const boost::system::error_code &error, std::size_t bytes_transferred)
     130             : {
     131           0 :   std::scoped_lock lock(session->mutex());
     132             : 
     133             :   // Ignore if the connection is already deleted.
     134           0 :   if (!session->Connection()) return;
     135             : 
     136           0 :   HttpClient *client = session->Connection()->client();
     137             :   // Ignore if httpclient is null when we get this from meta_data args from VM
     138           0 :   if (!client) {
     139           0 :     LOG(ERROR,"HttpClient:unable to call ProcessEvent() as HttpClient is NULL" <<
     140             :                     ", session :" << session <<
     141             :                     ", session->Connection(): " << session->Connection() <<
     142             :                     ", HttpClient :" << session->Connection()->client());             
     143           0 :     return;
     144             :   }
     145             : 
     146           0 :   client->ProcessEvent(boost::bind(&event_cb_impl, g, session, action, error, 
     147             :                                    bytes_transferred));
     148           0 : }
     149             : 
     150             : /* Called by asio when our timeout expires */
     151           0 : bool timer_cb(GlobalInfo *g)
     152             : {
     153             :     CURLMcode rc;
     154           0 :     rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0, &g->still_running);
     155           0 :     mcode_or_die("timer_cb: curl_multi_socket_action", rc);
     156             : 
     157             :     // When timeout happens, call multi_perform to check if we still have
     158             :     // pending handles; if yes, continue running the timer
     159           0 :     rc = curl_multi_perform(g->multi, &g->still_running);
     160           0 :     mcode_or_die("timer_cb: curl_multi_perform", rc);
     161             : 
     162           0 :     check_multi_info(g);
     163           0 :     return (g->still_running > 0);
     164             : }
     165             : 
     166             : /* Clean up any data */
     167           0 : static void remsock(SockInfo *sock_info, GlobalInfo *g)
     168             : {
     169           0 :   if ( sock_info )
     170             :   {
     171           0 :     free(sock_info);
     172             :   }
     173           0 : }
     174             : 
     175           0 : static bool setsock(SockInfo *sock_info, curl_socket_t s, CURL*e, int act, GlobalInfo *g)
     176             : {
     177           0 :   if (!sock_info || !sock_info->conn_info || !sock_info->conn_info->connection)
     178             :   {
     179           0 :     return false;
     180             :   }
     181             : 
     182           0 :   HttpClientSession *session = sock_info->conn_info->connection->session();
     183           0 :   if (!session || session->IsClosed())
     184           0 :        return false;
     185             : 
     186           0 :   boost::asio::ip::tcp::socket * tcp_socket = session->socket();
     187             : 
     188           0 :   sock_info->action = act;
     189             : 
     190           0 :   if ( act == CURL_POLL_IN )
     191             :   {
     192           0 :     tcp_socket->async_read_some(boost::asio::null_buffers(),
     193           0 :                                 boost::bind(&event_cb, g, TcpSessionPtr(session), 
     194             :                                 act, _1, _2));
     195             :   }
     196           0 :   else if ( act == CURL_POLL_OUT )
     197             :   {
     198           0 :     tcp_socket->async_write_some(boost::asio::null_buffers(),
     199           0 :                                  boost::bind(&event_cb, g, TcpSessionPtr(session), 
     200             :                                  act, _1, _2));
     201             :   }
     202           0 :   else if ( act == CURL_POLL_INOUT )
     203             :   {
     204           0 :     tcp_socket->async_read_some(boost::asio::null_buffers(),
     205           0 :                                 boost::bind(&event_cb, g, TcpSessionPtr(session),
     206             :                                 act, _1, _2));
     207           0 :     tcp_socket->async_write_some(boost::asio::null_buffers(),
     208           0 :                                  boost::bind(&event_cb, g, TcpSessionPtr(session),
     209             :                                  act, _1, _2));
     210             :   }
     211           0 :   return true;
     212             : }
     213             : 
     214             : 
     215           0 : static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
     216             : {
     217             :   // store socket details in SockInfo
     218           0 :   SockInfo *sock_info = (SockInfo *)calloc(sizeof(SockInfo), 1);
     219           0 :   curl_easy_getinfo(easy, CURLINFO_PRIVATE, &sock_info->conn_info);
     220             : 
     221           0 :   if (!setsock(sock_info, s, easy, action, g)) {
     222           0 :       free(sock_info);
     223           0 :       return;
     224             :   }
     225             : 
     226           0 :   curl_multi_assign(g->multi, s, sock_info);
     227             : }
     228             : 
     229             : /* CURLMOPT_SOCKETFUNCTION */
     230           0 : static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
     231             : {
     232           0 :   GlobalInfo *g = (GlobalInfo*) cbp;
     233           0 :   SockInfo *sock_info = (SockInfo *)sockp;
     234             : 
     235           0 :   if ( what == CURL_POLL_REMOVE )
     236             :   {
     237           0 :     remsock(sock_info, g);
     238             :   }
     239             :   else
     240             :   {
     241           0 :     if ( !sockp )
     242             :     {
     243           0 :       addsock(s, e, what, g);
     244             :     }
     245             :     else
     246             :     {
     247           0 :       setsock(sock_info, s, e, what, g);
     248             :     }
     249             :   }
     250           0 :   return 0;
     251             : }
     252             : 
     253             : 
     254             : /* CURLOPT_WRITEFUNCTION */
     255           0 : static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
     256             : {
     257           0 :   size_t written = size * nmemb;
     258             : 
     259           0 :   HttpConnection *conn = static_cast<HttpConnection *>(data);
     260           0 :   conn->AssignData((char *)ptr, written);
     261           0 :   return written;
     262             : }
     263             : 
     264             : /* CURLOPT_HEADERFUNCTION */
     265           0 : static size_t header_cb(void *ptr, size_t size, size_t nmemb, void *data)
     266             : {
     267           0 :   size_t written = size * nmemb;
     268             : 
     269           0 :   HttpConnection *conn = static_cast<HttpConnection *>(data);
     270           0 :   conn->AssignHeader((char *)ptr, written);
     271           0 :   return written;
     272             : }
     273             : 
     274           0 : static size_t read_cb(void *ptr, size_t size, size_t nmemb, void *data)
     275             : {
     276           0 :   HttpConnection *conn = static_cast<HttpConnection *>(data);
     277           0 :   ConnInfo *curl_handle = conn->curl_handle();
     278           0 :   char *str = curl_handle->post;
     279             : 
     280           0 :   size_t maxb = size*nmemb;
     281           0 :   size_t offset = conn->GetOffset();
     282           0 :   size_t datasize = 0;
     283           0 :   if (curl_handle->post_len > offset)
     284           0 :       datasize = curl_handle->post_len - offset;
     285             : 
     286           0 :   if (maxb >= datasize) {
     287           0 :       memcpy(ptr, str + offset, datasize);
     288           0 :       conn->UpdateOffset(datasize);
     289           0 :       return datasize;
     290             :   } else {
     291           0 :       memcpy(ptr, str + offset, maxb);
     292           0 :       conn->UpdateOffset(maxb);
     293           0 :       return maxb;
     294             :   }
     295             : 
     296             :   return 0;
     297             : }
     298             : 
     299             : 
     300             : /* CURLOPT_PROGRESSFUNCTION */
     301           0 : static int prog_cb (void *p, double dltotal, double dlnow, double ult,
     302             :                     double uln)
     303             : {
     304             :   (void)ult;
     305             :   (void)uln;
     306             : 
     307           0 :   return 0;
     308             : }
     309             : 
     310             : /* CURLOPT_OPENSOCKETFUNCTION */
     311           0 : static curl_socket_t open_socket(void *data,
     312             :                                 curlsocktype purpose,
     313             :                                 struct curl_sockaddr *address)
     314             : {
     315           0 :   HttpConnection *conn = static_cast<HttpConnection *>(data);
     316             :  
     317           0 :   curl_socket_t sockfd = CURL_SOCKET_BAD;
     318             : 
     319             :   /* restrict to ipv4 */
     320           0 :   if (purpose == CURLSOCKTYPE_IPCXN && address->family == AF_INET)
     321             :   {
     322           0 :       HttpClientSession *session = conn->CreateSession();
     323           0 :       if (session) {
     324           0 :           sockfd = session->socket()->native_handle();
     325           0 :           conn->set_session(session);
     326             :       }
     327             :   }
     328             : 
     329           0 :   return sockfd;
     330             : }
     331             : 
     332             : /* CURLOPT_CLOSESOCKETFUNCTION */
     333           0 : static int close_socket(void *clientp, curl_socket_t item)
     334             : {
     335           0 :   HttpConnection *conn = static_cast<HttpConnection *>(clientp);
     336           0 :   conn->delete_session();
     337           0 :   return 0;
     338             : }
     339             : 
     340           0 : static int send_perform(ConnInfo *conn, GlobalInfo *g) {
     341             :     // add the handle
     342           0 :     CURLMcode m_rc = curl_multi_add_handle(g->multi, conn->easy);
     343           0 :     if (m_rc != CURLM_OK)
     344           0 :         return m_rc;
     345             : 
     346             :     // start sending data rightaway; use timer to re-invoke multi_perform
     347           0 :     int counter = 0;
     348           0 :     CURLMcode rc = curl_multi_perform(g->multi, &counter);
     349           0 :     if (rc == CURLM_OK && counter <= 0) {
     350             :         // send done; invoke callback to indicate this
     351           0 :         if (conn->connection && conn->connection->session()) {
     352           0 :             const boost::system::error_code ec;
     353           0 :             event_cb(g, TcpSessionPtr(conn->connection->session()), 0, ec, 0);
     354             :         }
     355           0 :     } else {
     356             :         // start timer and check for send completion on timeout
     357           0 :         g->client->StartTimer(HttpClient::kDefaultTimeout);
     358             :     }
     359             : 
     360           0 :     return rc;
     361             : }
     362             : 
     363           0 : void del_conn(HttpConnection *connection, GlobalInfo *g) {
     364             : 
     365           0 :     if (connection->session()) {
     366           0 :         std::scoped_lock lock(connection->session()->mutex());
     367           0 :         connection->session()->SetConnection(NULL);
     368           0 :     }
     369             : 
     370           0 :     struct _ConnInfo *curl_handle = connection->curl_handle();
     371           0 :     if (curl_handle) {
     372           0 :         connection->set_curl_handle(NULL);
     373           0 :         del_curl_handle(curl_handle, g);
     374             :     }
     375           0 : }
     376             : 
     377           0 : void del_curl_handle(ConnInfo *curl_handle, GlobalInfo *g) {
     378           0 :     if (curl_handle) {
     379           0 :         curl_easy_setopt(curl_handle->easy, CURLOPT_PRIVATE, NULL);
     380           0 :         curl_multi_remove_handle(g->multi, curl_handle->easy);
     381           0 :         curl_slist_free_all(curl_handle->headers);
     382           0 :         free(curl_handle->post);
     383           0 :         free(curl_handle->url);
     384           0 :         curl_easy_cleanup(curl_handle->easy);
     385           0 :         free(curl_handle);
     386             :     }   
     387           0 : }
     388             : 
     389             : /* Create a new easy handle, and add it to the global curl_multi */
     390           0 : ConnInfo *new_conn(HttpConnection *connection, GlobalInfo *g,
     391             :                    bool header, bool short_timeout, bool reuse)
     392             : {
     393           0 :   ConnInfo *conn = (ConnInfo *)calloc(1, sizeof(ConnInfo));
     394           0 :   memset(conn, 0, sizeof(ConnInfo));
     395           0 :   conn->error[CURL_ERROR_SIZE]='\0';
     396             : 
     397           0 :   conn->easy = curl_easy_init();
     398             : 
     399           0 :   if ( !conn->easy ) {
     400           0 :     free(conn);
     401           0 :     return NULL;
     402             :   }
     403           0 :   conn->global = g;
     404           0 :   curl_easy_setopt(conn->easy, CURLOPT_FOLLOWLOCATION, 1L);
     405           0 :   curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
     406           0 :   curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, connection);
     407           0 :   curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION, header_cb);
     408           0 :   curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, connection);
     409           0 :   curl_easy_setopt(conn->easy, CURLOPT_READFUNCTION, read_cb);
     410           0 :   curl_easy_setopt(conn->easy, CURLOPT_READDATA, connection);
     411           0 :   curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
     412           0 :   curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
     413           0 :   curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 1L);
     414           0 :   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
     415           0 :   curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
     416           0 :   curl_easy_setopt(conn->easy, CURLOPT_CONNECTTIMEOUT, 4L); // in secs
     417           0 :   if (short_timeout) {
     418             :       /* set the timeout limits to abort the connection */
     419           0 :       curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 3L);
     420           0 :       curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 10L);
     421             :   } else {
     422             :       /* set longer timeout limits */
     423           0 :       curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 30L);
     424           0 :       curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 10L);
     425             :   }
     426           0 :   curl_easy_setopt(conn->easy, CURLOPT_FORBID_REUSE, 1L);
     427             : 
     428             :   /* call this function to get a socket */
     429           0 :   curl_easy_setopt(conn->easy, CURLOPT_OPENSOCKETFUNCTION, open_socket);
     430           0 :   curl_easy_setopt(conn->easy, CURLOPT_OPENSOCKETDATA, connection);
     431             : 
     432             :   /* call this function to close a socket */
     433           0 :   curl_easy_setopt(conn->easy, CURLOPT_CLOSESOCKETFUNCTION, close_socket);
     434           0 :   curl_easy_setopt(conn->easy, CURLOPT_CLOSESOCKETDATA, connection);
     435             : 
     436           0 :   return conn;
     437             : }
     438             : 
     439           0 : void set_url(ConnInfo *conn, const char *url) {
     440           0 :   conn->url = strdup(url);
     441           0 :   curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
     442           0 : }
     443             : 
     444           0 : void set_header_options(ConnInfo *conn, const char *options) { 
     445           0 :     conn->headers = curl_slist_append(conn->headers, options);
     446           0 :     curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->headers);
     447           0 : }
     448             : 
     449           0 : void set_ssl_options(ConnInfo *conn, const char *client_cert,
     450             :                      const char *client_cert_type, const char *client_key,
     451             :                      const char *ca_cert) {
     452           0 :     curl_easy_setopt(conn->easy, CURLOPT_SSLCERT, client_cert);
     453           0 :     curl_easy_setopt(conn->easy, CURLOPT_SSLCERTTYPE, client_cert_type);
     454           0 :     curl_easy_setopt(conn->easy, CURLOPT_SSLKEY, client_key);
     455           0 :     curl_easy_setopt(conn->easy, CURLOPT_CAINFO, ca_cert);
     456           0 :     if (ca_cert[0] == '\0') {
     457             :         // Disable certificate validation if CA certificate path doesnt exist
     458           0 :         curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYPEER, 0L);
     459           0 :         curl_easy_setopt(conn->easy, CURLOPT_SSL_VERIFYHOST, 0L);
     460             :     }
     461           0 : }
     462             : 
     463           0 : void set_post_string(ConnInfo *conn, const char *post, uint32_t len) {
     464           0 :     conn->post = (char *) malloc(len);
     465           0 :     memcpy(conn->post, post, len);
     466           0 :     conn->post_len = len;
     467           0 :     curl_easy_setopt(conn->easy, CURLOPT_POST, 1);
     468           0 :     curl_easy_setopt(conn->easy, CURLOPT_POSTFIELDS, conn->post);
     469           0 :     curl_easy_setopt(conn->easy, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len);
     470           0 : }
     471             : 
     472           0 : void set_put_string(ConnInfo *conn, const char *put, uint32_t len) {
     473           0 :     conn->post = (char *) malloc(len);
     474           0 :     memcpy(conn->post, put, len);
     475           0 :     conn->post_len = len;
     476           0 :     curl_easy_setopt(conn->easy, CURLOPT_UPLOAD, 1);
     477           0 :     curl_easy_setopt(conn->easy, CURLOPT_PUT, 1);
     478           0 :     curl_easy_setopt(conn->easy, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)len);
     479           0 : }
     480             : 
     481           0 : int http_get(ConnInfo *conn, GlobalInfo *g) {
     482           0 :     CURLMcode rc = curl_multi_add_handle(g->multi, conn->easy);
     483           0 :     return (int)rc;
     484             : }
     485             : 
     486           0 : int http_head(ConnInfo *conn, GlobalInfo *g) {
     487           0 :     curl_easy_setopt(conn->easy, CURLOPT_CUSTOMREQUEST, "HEAD");
     488           0 :     CURLMcode rc = curl_multi_add_handle(g->multi, conn->easy);
     489           0 :     return (int)rc;
     490             : }
     491             : 
     492           0 : int http_put(ConnInfo *conn, GlobalInfo *g) {
     493           0 :     return send_perform(conn, g);
     494             : }
     495             : 
     496           0 : int http_post(ConnInfo *conn, GlobalInfo *g) {
     497           0 :     return send_perform(conn, g);
     498             : }
     499             : 
     500           0 : int http_delete(ConnInfo *conn, GlobalInfo *g) {
     501           0 :     curl_easy_setopt(conn->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
     502           0 :     CURLMcode rc = curl_multi_add_handle(g->multi, conn->easy);
     503           0 :     return (int)rc;
     504             : }
     505             : 
     506           1 : int curl_init(HttpClient *client)
     507             : {
     508           1 :   struct _GlobalInfo *g = client->GlobalInfo();
     509             : 
     510           1 :   memset(g, 0, sizeof(GlobalInfo));
     511           1 :   g->multi = curl_multi_init();
     512           1 :   g->client = client;
     513             : 
     514           1 :   curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
     515           1 :   curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
     516           1 :   curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
     517           1 :   curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, client);
     518             : 
     519           1 :   return 0;
     520             : }

Generated by: LCOV version 1.14