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