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

Generated by: LCOV version 1.14