tolerate additional IPv4 address now available for gnunet.org
[oweals/gnunet.git] / src / curl / curl.c
1 /*
2   This file is part of GNUnet
3   Copyright (C) 2014, 2015, 2016, 2018 GNUnet e.V.
4
5   GNUnet is free software: you can redistribute it and/or modify it
6   under the terms of the GNU Affero General Public License as published
7   by the Free Software Foundation, either version 3 of the License,
8   or (at your option) any later version.
9
10   GNUnet is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20 /**
21  * @file curl/curl.c
22  * @brief API for downloading JSON via CURL
23  * @author Sree Harsha Totakura <sreeharsha@totakura.in>
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include <jansson.h>
28 #include "gnunet_curl_lib.h"
29
30 #if ENABLE_BENCHMARK
31 #include "../util/benchmark.h"
32 #endif
33
34
35 /**
36  * Log error related to CURL operations.
37  *
38  * @param type log level
39  * @param function which function failed to run
40  * @param code what was the curl error code
41  */
42 #define CURL_STRERROR(type, function, code)      \
43  GNUNET_log (type,                               \
44              "Curl function `%s' has failed at `%s:%d' with error: %s\n", \
45              function, __FILE__, __LINE__, curl_easy_strerror (code));
46
47 /**
48  * Print JSON parsing related error information
49  */
50 #define JSON_WARN(error)                                                \
51     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,                              \
52                 "JSON parsing failed at %s:%u: %s (%s)\n",              \
53                 __FILE__, __LINE__, error.text, error.source)
54
55
56 /**
57  * Failsafe flag. Raised if our constructor fails to initialize
58  * the Curl library.
59  */
60 static int curl_fail;
61
62 /**
63  * Jobs are CURL requests running within a `struct GNUNET_CURL_Context`.
64  */
65 struct GNUNET_CURL_Job
66 {
67
68   /**
69    * We keep jobs in a DLL.
70    */
71   struct GNUNET_CURL_Job *next;
72
73   /**
74    * We keep jobs in a DLL.
75    */
76   struct GNUNET_CURL_Job *prev;
77
78   /**
79    * Easy handle of the job.
80    */
81   CURL *easy_handle;
82
83   /**
84    * Context this job runs in.
85    */
86   struct GNUNET_CURL_Context *ctx;
87
88   /**
89    * Function to call upon completion.
90    */
91   GNUNET_CURL_JobCompletionCallback jcc;
92
93   /**
94    * Closure for @e jcc.
95    */
96   void *jcc_cls;
97
98   /**
99    * Buffer for response received from CURL.
100    */
101   struct GNUNET_CURL_DownloadBuffer db;
102
103 };
104
105
106 /**
107  * Context
108  */
109 struct GNUNET_CURL_Context
110 {
111   /**
112    * Curl multi handle
113    */
114   CURLM *multi;
115
116   /**
117    * Curl share handle
118    */
119   CURLSH *share;
120
121   /**
122    * We keep jobs in a DLL.
123    */
124   struct GNUNET_CURL_Job *jobs_head;
125
126   /**
127    * We keep jobs in a DLL.
128    */
129   struct GNUNET_CURL_Job *jobs_tail;
130
131   /**
132    * HTTP header "application/json", created once and used
133    * for all requests that need it.
134    */
135   struct curl_slist *json_header;
136
137   /**
138    * Function we need to call whenever the event loop's
139    * socket set changed.
140    */
141   GNUNET_CURL_RescheduleCallback cb;
142
143   /**
144    * Closure for @e cb.
145    */
146   void *cb_cls;
147 };
148
149
150 /**
151  * Initialise this library.  This function should be called before using any of
152  * the following functions.
153  *
154  * @param cb function to call when rescheduling is required
155  * @param cb_cls closure for @a cb
156  * @return library context
157  */
158 struct GNUNET_CURL_Context *
159 GNUNET_CURL_init (GNUNET_CURL_RescheduleCallback cb,
160                   void *cb_cls)
161 {
162   struct GNUNET_CURL_Context *ctx;
163   CURLM *multi;
164   CURLSH *share;
165
166   if (curl_fail)
167   {
168     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
169                 "Curl was not initialised properly\n");
170     return NULL;
171   }
172   if (NULL == (multi = curl_multi_init ()))
173   {
174     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
175                 "Failed to create a Curl multi handle\n");
176     return NULL;
177   }
178   if (NULL == (share = curl_share_init ()))
179   {
180     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
181                 "Failed to create a Curl share handle\n");
182     return NULL;
183   }
184   ctx = GNUNET_new (struct GNUNET_CURL_Context);
185   ctx->cb = cb;
186   ctx->cb_cls = cb_cls;
187   ctx->multi = multi;
188   ctx->share = share;
189   GNUNET_assert (NULL != (ctx->json_header =
190                           curl_slist_append (NULL,
191                                              "Content-Type: application/json")));
192   return ctx;
193 }
194
195
196 /**
197  * Callback used when downloading the reply to an HTTP request.
198  * Just appends all of the data to the `buf` in the
199  * `struct DownloadBuffer` for further processing. The size of
200  * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
201  * the download exceeds this size, we abort with an error.
202  *
203  * @param bufptr data downloaded via HTTP
204  * @param size size of an item in @a bufptr
205  * @param nitems number of items in @a bufptr
206  * @param cls the `struct DownloadBuffer`
207  * @return number of bytes processed from @a bufptr
208  */
209 static size_t
210 download_cb (char *bufptr,
211              size_t size,
212              size_t nitems,
213              void *cls)
214 {
215   struct GNUNET_CURL_DownloadBuffer *db = cls;
216   size_t msize;
217   void *buf;
218
219   if (0 == size * nitems)
220   {
221     /* Nothing (left) to do */
222     return 0;
223   }
224   msize = size * nitems;
225   if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED)
226   {
227     db->eno = ENOMEM;
228     return 0; /* signals an error to curl */
229   }
230   db->buf = GNUNET_realloc (db->buf,
231                             db->buf_size + msize);
232   buf = db->buf + db->buf_size;
233   GNUNET_memcpy (buf, bufptr, msize);
234   db->buf_size += msize;
235   return msize;
236 }
237
238
239 /**
240  * Schedule a CURL request to be executed and call the given @a jcc
241  * upon its completion.  Note that the context will make use of the
242  * CURLOPT_PRIVATE facility of the CURL @a eh.
243  *
244  * This function modifies the CURL handle to add the
245  * "Content-Type: application/json" header if @a add_json is set.
246  *
247  * @param ctx context to execute the job in
248  * @param eh curl easy handle for the request, will
249  *           be executed AND cleaned up
250  * @param add_json add "application/json" content type header
251  * @param jcc callback to invoke upon completion
252  * @param jcc_cls closure for @a jcc
253  * @return NULL on error (in this case, @eh is still released!)
254  */
255 struct GNUNET_CURL_Job *
256 GNUNET_CURL_job_add (struct GNUNET_CURL_Context *ctx,
257                      CURL *eh,
258                      int add_json,
259                      GNUNET_CURL_JobCompletionCallback jcc,
260                      void *jcc_cls)
261 {
262   struct GNUNET_CURL_Job *job;
263
264   if (GNUNET_YES == add_json)
265     if (CURLE_OK !=
266         curl_easy_setopt (eh,
267                           CURLOPT_HTTPHEADER,
268                           ctx->json_header))
269     {
270       GNUNET_break (0);
271       curl_easy_cleanup (eh);
272       return NULL;
273     }
274
275   job = GNUNET_new (struct GNUNET_CURL_Job);
276   if ( (CURLE_OK !=
277         curl_easy_setopt (eh,
278                           CURLOPT_PRIVATE,
279                           job)) ||
280        (CURLE_OK !=
281         curl_easy_setopt (eh,
282                           CURLOPT_WRITEFUNCTION,
283                           &download_cb)) ||
284        (CURLE_OK !=
285         curl_easy_setopt (eh,
286                           CURLOPT_WRITEDATA,
287                           &job->db)) ||
288        (CURLE_OK !=
289         curl_easy_setopt (eh,
290                           CURLOPT_SHARE,
291                           ctx->share)) ||
292        (CURLM_OK !=
293         curl_multi_add_handle (ctx->multi,
294                                eh)) )
295   {
296     GNUNET_break (0);
297     GNUNET_free (job);
298     curl_easy_cleanup (eh);
299     return NULL;
300   }
301
302   job->easy_handle = eh;
303   job->ctx = ctx;
304   job->jcc = jcc;
305   job->jcc_cls = jcc_cls;
306   GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
307                                ctx->jobs_tail,
308                                job);
309   ctx->cb (ctx->cb_cls);
310   return job;
311 }
312
313
314 /**
315  * Cancel a job.  Must only be called before the job completion
316  * callback is called for the respective job.
317  *
318  * @param job job to cancel
319  */
320 void
321 GNUNET_CURL_job_cancel (struct GNUNET_CURL_Job *job)
322 {
323   struct GNUNET_CURL_Context *ctx = job->ctx;
324
325   GNUNET_CONTAINER_DLL_remove (ctx->jobs_head,
326                                ctx->jobs_tail,
327                                job);
328   GNUNET_break (CURLM_OK ==
329                 curl_multi_remove_handle (ctx->multi,
330                                           job->easy_handle));
331   curl_easy_cleanup (job->easy_handle);
332   GNUNET_free_non_null (job->db.buf);
333   GNUNET_free (job);
334 }
335
336
337 /**
338  * Obtain information about the final result about the
339  * HTTP download. If the download was successful, parses
340  * the JSON in the @a db and returns it. Also returns
341  * the HTTP @a response_code.  If the download failed,
342  * the return value is NULL.  The response code is set
343  * in any case, on download errors to zero.
344  *
345  * Calling this function also cleans up @a db.
346  *
347  * @param db download buffer
348  * @param eh CURL handle (to get the response code)
349  * @param[out] response_code set to the HTTP response code
350  *             (or zero if we aborted the download, i.e.
351  *              because the response was too big, or if
352  *              the JSON we received was malformed).
353  * @return NULL if downloading a JSON reply failed.
354  */
355 void *
356 download_get_result (struct GNUNET_CURL_DownloadBuffer *db,
357                      CURL *eh,
358                      long *response_code)
359 {
360   json_t *json;
361   json_error_t error;
362   char *ct;
363
364   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
365               "Downloaded body: %.*s\n",
366               (int) db->buf_size,
367               (char *) db->buf);
368
369   if ( (CURLE_OK !=
370         curl_easy_getinfo (eh,
371                            CURLINFO_CONTENT_TYPE,
372                            &ct)) ||
373        (NULL == ct) ||
374        (0 != strcasecmp (ct,
375                          "application/json")) )
376   {
377     /* No content type or explicitly not JSON, refuse to parse
378        (but keep response code) */
379     if (CURLE_OK !=
380         curl_easy_getinfo (eh,
381                            CURLINFO_RESPONSE_CODE,
382                            response_code))
383     {
384       /* unexpected error... */
385       GNUNET_break (0);
386       *response_code = 0;
387     }
388     if (0 != db->buf_size)
389       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
390                   "Did NOT detect response as JSON\n");
391     return NULL;
392   }
393   json = NULL;
394   if (0 == db->eno)
395   {
396     json = json_loadb (db->buf,
397                        db->buf_size,
398                        JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
399                        &error);
400     if (NULL == json)
401     {
402       JSON_WARN (error);
403       *response_code = 0;
404     }
405   }
406   GNUNET_free_non_null (db->buf);
407   db->buf = NULL;
408   db->buf_size = 0;
409   if (NULL != json)
410   {
411     if (CURLE_OK !=
412         curl_easy_getinfo (eh,
413                            CURLINFO_RESPONSE_CODE,
414                            response_code))
415     {
416       /* unexpected error... */
417       GNUNET_break (0);
418       *response_code = 0;
419     }
420   }
421   return json;
422 }
423
424
425 /**
426  * Add custom request header.
427  *
428  * @param ctx cURL context.
429  * @param header header string; will be given to the context AS IS.
430  * @return #GNUNET_OK if no errors occurred, #GNUNET_SYSERR otherwise.
431  */
432 int
433 GNUNET_CURL_append_header (struct GNUNET_CURL_Context *ctx,
434                            const char *header)
435 {
436   ctx->json_header = curl_slist_append (ctx->json_header,
437                                         header);
438   if (NULL == ctx->json_header)
439     return GNUNET_SYSERR;
440
441   return GNUNET_OK;
442 }
443
444
445 /**
446  * Run the main event loop for the Taler interaction.
447  *
448  * @param ctx the library context
449  * @param rp parses the raw response returned from
450  *        the Web server.
451  * @param rc cleans/frees the response
452  */
453 void
454 GNUNET_CURL_perform2 (struct GNUNET_CURL_Context *ctx,
455                       GNUNET_CURL_RawParser rp,
456                       GNUNET_CURL_ResponseCleaner rc)
457 {
458   CURLMsg *cmsg;
459   struct GNUNET_CURL_Job *job;
460   int n_running;
461   int n_completed;
462   long response_code;
463   void *response;
464
465   (void) curl_multi_perform (ctx->multi,
466                              &n_running);
467   while (NULL != (cmsg = curl_multi_info_read (ctx->multi,
468                                                &n_completed)))
469   {
470     /* Only documented return value is CURLMSG_DONE */
471     GNUNET_break (CURLMSG_DONE == cmsg->msg);
472     GNUNET_assert (CURLE_OK ==
473                    curl_easy_getinfo (cmsg->easy_handle,
474                                       CURLINFO_PRIVATE,
475                                       (char **) &job));
476     GNUNET_assert (job->ctx == ctx);
477     response_code = 0 ;
478     response = rp (&job->db,
479                    job->easy_handle,
480                    &response_code);
481 #if ENABLE_BENCHMARK
482   {
483     char *url = NULL;
484     double total_as_double = 0;
485     struct GNUNET_TIME_Relative total;
486     struct UrlRequestData *urd;
487     /* Some care required, as curl is using data types (long vs curl_off_t vs
488      * double) inconsistently to store byte count. */
489     curl_off_t size_curl = 0;
490     long size_long = 0;
491     uint64_t bytes_sent = 0;
492     uint64_t bytes_received = 0;
493
494     GNUNET_break (CURLE_OK ==
495                   curl_easy_getinfo (cmsg->easy_handle,
496                                      CURLINFO_TOTAL_TIME,
497                                      &total_as_double));
498     total.rel_value_us = total_as_double * 1000 * 1000;
499
500     GNUNET_break (CURLE_OK ==
501                   curl_easy_getinfo (cmsg->easy_handle,
502                                      CURLINFO_EFFECTIVE_URL,
503                                      &url));
504
505     /* HEADER_SIZE + SIZE_DOWNLOAD_T is hopefully the total
506        number of bytes received, not clear from curl docs. */
507
508     GNUNET_break (CURLE_OK ==
509                   curl_easy_getinfo (cmsg->easy_handle,
510                                      CURLINFO_HEADER_SIZE,
511                                      &size_long));
512     bytes_received += size_long;
513
514     GNUNET_break (CURLE_OK ==
515                   curl_easy_getinfo (cmsg->easy_handle,
516                                      CURLINFO_SIZE_DOWNLOAD_T,
517                                      &size_curl));
518     bytes_received += size_curl;
519
520     /* REQUEST_SIZE + SIZE_UPLOAD_T is hopefully the total number of bytes
521        sent, again docs are not completely clear. */
522
523     GNUNET_break (CURLE_OK ==
524                   curl_easy_getinfo (cmsg->easy_handle,
525                                      CURLINFO_REQUEST_SIZE,
526                                      &size_long));
527     bytes_sent += size_long;
528
529     GNUNET_break (CURLE_OK ==
530                   curl_easy_getinfo (cmsg->easy_handle,
531                                      CURLINFO_SIZE_UPLOAD_T,
532                                      &size_curl));
533     bytes_sent += size_curl;
534
535     urd = get_url_benchmark_data (url, (unsigned int) response_code);
536     urd->count++;
537     urd->time = GNUNET_TIME_relative_add (urd->time, total);
538     urd->time_max = GNUNET_TIME_relative_max (total, urd->time_max);
539     urd->bytes_sent += bytes_sent;
540     urd->bytes_received += bytes_received;
541   }
542 #endif
543     job->jcc (job->jcc_cls,
544               response_code,
545               response);
546     rc (response);
547     GNUNET_CURL_job_cancel (job);
548   }
549 }
550
551
552 /**
553  * Run the main event loop for the Taler interaction.
554  *
555  * @param ctx the library context
556  */
557 void
558 GNUNET_CURL_perform (struct GNUNET_CURL_Context *ctx)
559 {
560   
561   GNUNET_CURL_perform2 (ctx,
562                         download_get_result,
563                         (GNUNET_CURL_ResponseCleaner) &json_decref);
564 }
565
566
567 /**
568  * Obtain the information for a select() call to wait until
569  * #GNUNET_CURL_perform() is ready again.  Note that calling
570  * any other GNUNET_CURL-API may also imply that the library
571  * is again ready for #GNUNET_CURL_perform().
572  *
573  * Basically, a client should use this API to prepare for select(),
574  * then block on select(), then call #GNUNET_CURL_perform() and then
575  * start again until the work with the context is done.
576  *
577  * This function will NOT zero out the sets and assumes that @a max_fd
578  * and @a timeout are already set to minimal applicable values.  It is
579  * safe to give this API FD-sets and @a max_fd and @a timeout that are
580  * already initialized to some other descriptors that need to go into
581  * the select() call.
582  *
583  * @param ctx context to get the event loop information for
584  * @param read_fd_set will be set for any pending read operations
585  * @param write_fd_set will be set for any pending write operations
586  * @param except_fd_set is here because curl_multi_fdset() has this argument
587  * @param max_fd set to the highest FD included in any set;
588  *        if the existing sets have no FDs in it, the initial
589  *        value should be "-1". (Note that `max_fd + 1` will need
590  *        to be passed to select().)
591  * @param timeout set to the timeout in milliseconds (!); -1 means
592  *        no timeout (NULL, blocking forever is OK), 0 means to
593  *        proceed immediately with #GNUNET_CURL_perform().
594  */
595 void
596 GNUNET_CURL_get_select_info (struct GNUNET_CURL_Context *ctx,
597                              fd_set *read_fd_set,
598                              fd_set *write_fd_set,
599                              fd_set *except_fd_set,
600                              int *max_fd,
601                              long *timeout)
602 {
603   long to;
604   int m;
605
606   m = -1;
607   GNUNET_assert (CURLM_OK ==
608                  curl_multi_fdset (ctx->multi,
609                                    read_fd_set,
610                                    write_fd_set,
611                                    except_fd_set,
612                                    &m));
613   to = *timeout;
614   *max_fd = GNUNET_MAX (m, *max_fd);
615   GNUNET_assert (CURLM_OK ==
616                  curl_multi_timeout (ctx->multi,
617                                      &to));
618
619   /* Only if what we got back from curl is smaller than what we
620      already had (-1 == infinity!), then update timeout */
621   if ( (to < *timeout) &&
622        (-1 != to) )
623     *timeout = to;
624   if ( (-1 == (*timeout)) &&
625        (NULL != ctx->jobs_head) )
626     *timeout = to;
627 }
628
629
630 /**
631  * Cleanup library initialisation resources.  This function should be called
632  * after using this library to cleanup the resources occupied during library's
633  * initialisation.
634  *
635  * @param ctx the library context
636  */
637 void
638 GNUNET_CURL_fini (struct GNUNET_CURL_Context *ctx)
639 {
640   /* all jobs must have been cancelled at this time, assert this */
641   GNUNET_assert (NULL == ctx->jobs_head);
642   curl_share_cleanup (ctx->share);
643   curl_multi_cleanup (ctx->multi);
644   curl_slist_free_all (ctx->json_header);
645   GNUNET_free (ctx);
646 }
647
648
649 /**
650  * Initial global setup logic, specifically runs the Curl setup.
651  */
652 __attribute__ ((constructor))
653 void
654 GNUNET_CURL_constructor__ (void)
655 {
656   CURLcode ret;
657
658   if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
659   {
660     CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR,
661                    "curl_global_init",
662                    ret);
663     curl_fail = 1;
664   }
665 }
666
667
668 /**
669  * Cleans up after us, specifically runs the Curl cleanup.
670  */
671 __attribute__ ((destructor))
672 void
673 GNUNET_CURL_destructor__ (void)
674 {
675   if (curl_fail)
676     return;
677   curl_global_cleanup ();
678 }
679
680 /* end of curl.c */