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 : }
|