aboutsummaryrefslogtreecommitdiff
path: root/src/curl
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-04-17 15:09:23 +0000
committerChristian Grothoff <christian@grothoff.org>2016-04-17 15:09:23 +0000
commit16642c4cb25faae335591b39795c71dedbeb37f1 (patch)
tree99eced0e91cb1455f4dc5afd1fd3918259aa5dd3 /src/curl
parentda7b8273d9f2229fe163ef4fbdcf046fb6227b73 (diff)
downloadgnunet-16642c4cb25faae335591b39795c71dedbeb37f1.tar.gz
gnunet-16642c4cb25faae335591b39795c71dedbeb37f1.zip
adding libgnunetcurl
Diffstat (limited to 'src/curl')
-rw-r--r--src/curl/Makefile.am33
-rw-r--r--src/curl/curl.c574
2 files changed, 607 insertions, 0 deletions
diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am
new file mode 100644
index 000000000..4bdd03039
--- /dev/null
+++ b/src/curl/Makefile.am
@@ -0,0 +1,33 @@
1# This Makefile.am is in the public domain
2AM_CPPFLAGS = -I$(top_srcdir)/src/include
3
4if USE_COVERAGE
5 AM_CFLAGS = --coverage -O0
6 XLIB = -lgcov
7endif
8
9lib_LTLIBRARIES = \
10 libgnunetcurl.la
11
12libgnunetcurl_la_LDFLAGS = \
13 -version-info 0:0:0 \
14 -no-undefined
15libgnunetcurl_la_SOURCES = \
16 curl.c
17libgnunetcurl_la_LIBADD = \
18 $(top_builddir)/src/util/libgnunetutil.la \
19 -ljansson \
20 $(XLIB)
21
22#check_PROGRAMS = \
23# test_curl
24
25#TESTS = \
26# $(check_PROGRAMS)
27
28#test_curl_SOURCES = \
29# test_curl.c
30#test_curl_LDADD = \
31# libgnunetcurl.la \
32# $(top_builddir)/src/util/libgnunetutil.la \
33# -ljansson -lcurl
diff --git a/src/curl/curl.c b/src/curl/curl.c
new file mode 100644
index 000000000..10ba7f719
--- /dev/null
+++ b/src/curl/curl.c
@@ -0,0 +1,574 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2014, 2015, 2016 GNUnet e.V.
4
5 GNUnet is free software; you can redistribute it and/or modify it under the
6 terms of the GNU General Public License as published by the Free Software
7 Foundation; either version 3, or (at your option) any later version.
8
9 GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with
14 GNUnet; see the file COPYING. If not, If not, see
15 <http://www.gnu.org/licenses/>
16*/
17/**
18 * @file curl/curl.c
19 * @brief API for downloading JSON via CURL
20 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
21 * @author Christian Grothoff
22 */
23#include "platform.h"
24#include <curl/curl.h>
25#include <jansson.h>
26#include "gnunet_curl_lib.h"
27
28
29/**
30 * Log error related to CURL operations.
31 *
32 * @param type log level
33 * @param function which function failed to run
34 * @param code what was the curl error code
35 */
36#define CURL_STRERROR(type, function, code) \
37 GNUNET_log (type, \
38 "Curl function `%s' has failed at `%s:%d' with error: %s\n", \
39 function, __FILE__, __LINE__, curl_easy_strerror (code));
40
41/**
42 * Print JSON parsing related error information
43 */
44#define JSON_WARN(error) \
45 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
46 "JSON parsing failed at %s:%u: %s (%s)\n", \
47 __FILE__, __LINE__, error.text, error.source)
48
49
50/**
51 * Failsafe flag. Raised if our constructor fails to initialize
52 * the Curl library.
53 */
54static int curl_fail;
55
56
57/**
58 * @brief Buffer data structure we use to buffer the HTTP download
59 * before giving it to the JSON parser.
60 */
61struct DownloadBuffer
62{
63
64 /**
65 * Download buffer
66 */
67 void *buf;
68
69 /**
70 * The size of the download buffer
71 */
72 size_t buf_size;
73
74 /**
75 * Error code (based on libc errno) if we failed to download
76 * (i.e. response too large).
77 */
78 int eno;
79
80};
81
82
83/**
84 * Jobs are CURL requests running within a `struct GNUNET_CURL_Context`.
85 */
86struct GNUNET_CURL_Job
87{
88
89 /**
90 * We keep jobs in a DLL.
91 */
92 struct GNUNET_CURL_Job *next;
93
94 /**
95 * We keep jobs in a DLL.
96 */
97 struct GNUNET_CURL_Job *prev;
98
99 /**
100 * Easy handle of the job.
101 */
102 CURL *easy_handle;
103
104 /**
105 * Context this job runs in.
106 */
107 struct GNUNET_CURL_Context *ctx;
108
109 /**
110 * Function to call upon completion.
111 */
112 GNUNET_CURL_JobCompletionCallback jcc;
113
114 /**
115 * Closure for @e jcc.
116 */
117 void *jcc_cls;
118
119 /**
120 * Buffer for response received from CURL.
121 */
122 struct DownloadBuffer db;
123
124};
125
126
127/**
128 * Context
129 */
130struct GNUNET_CURL_Context
131{
132 /**
133 * Curl multi handle
134 */
135 CURLM *multi;
136
137 /**
138 * Curl share handle
139 */
140 CURLSH *share;
141
142 /**
143 * We keep jobs in a DLL.
144 */
145 struct GNUNET_CURL_Job *jobs_head;
146
147 /**
148 * We keep jobs in a DLL.
149 */
150 struct GNUNET_CURL_Job *jobs_tail;
151
152 /**
153 * HTTP header "application/json", created once and used
154 * for all requests that need it.
155 */
156 struct curl_slist *json_header;
157
158};
159
160
161/**
162 * Initialise this library. This function should be called before using any of
163 * the following functions.
164 *
165 * @return library context
166 */
167struct GNUNET_CURL_Context *
168GNUNET_CURL_init ()
169{
170 struct GNUNET_CURL_Context *ctx;
171 CURLM *multi;
172 CURLSH *share;
173
174 if (curl_fail)
175 {
176 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
177 "Curl was not initialised properly\n");
178 return NULL;
179 }
180 if (NULL == (multi = curl_multi_init ()))
181 {
182 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
183 "Failed to create a Curl multi handle\n");
184 return NULL;
185 }
186 if (NULL == (share = curl_share_init ()))
187 {
188 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
189 "Failed to create a Curl share handle\n");
190 return NULL;
191 }
192 ctx = GNUNET_new (struct GNUNET_CURL_Context);
193 ctx->multi = multi;
194 ctx->share = share;
195 GNUNET_assert (NULL != (ctx->json_header =
196 curl_slist_append (NULL,
197 "Content-Type: application/json")));
198 return ctx;
199}
200
201
202/**
203 * Callback used when downloading the reply to an HTTP request.
204 * Just appends all of the data to the `buf` in the
205 * `struct DownloadBuffer` for further processing. The size of
206 * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
207 * the download exceeds this size, we abort with an error.
208 *
209 * @param bufptr data downloaded via HTTP
210 * @param size size of an item in @a bufptr
211 * @param nitems number of items in @a bufptr
212 * @param cls the `struct DownloadBuffer`
213 * @return number of bytes processed from @a bufptr
214 */
215static size_t
216download_cb (char *bufptr,
217 size_t size,
218 size_t nitems,
219 void *cls)
220{
221 struct DownloadBuffer *db = cls;
222 size_t msize;
223 void *buf;
224
225 if (0 == size * nitems)
226 {
227 /* Nothing (left) to do */
228 return 0;
229 }
230 msize = size * nitems;
231 if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED)
232 {
233 db->eno = ENOMEM;
234 return 0; /* signals an error to curl */
235 }
236 db->buf = GNUNET_realloc (db->buf,
237 db->buf_size + msize);
238 buf = db->buf + db->buf_size;
239 memcpy (buf, bufptr, msize);
240 db->buf_size += msize;
241 return msize;
242}
243
244
245/**
246 * Schedule a CURL request to be executed and call the given @a jcc
247 * upon its completion. Note that the context will make use of the
248 * CURLOPT_PRIVATE facility of the CURL @a eh.
249 *
250 * This function modifies the CURL handle to add the
251 * "Content-Type: application/json" header if @a add_json is set.
252 *
253 * @param ctx context to execute the job in
254 * @param eh curl easy handle for the request, will
255 * be executed AND cleaned up
256 * @param add_json add "application/json" content type header
257 * @param jcc callback to invoke upon completion
258 * @param jcc_cls closure for @a jcc
259 * @return NULL on error (in this case, @eh is still released!)
260 */
261struct GNUNET_CURL_Job *
262GNUNET_CURL_job_add (struct GNUNET_CURL_Context *ctx,
263 CURL *eh,
264 int add_json,
265 GNUNET_CURL_JobCompletionCallback jcc,
266 void *jcc_cls)
267{
268 struct GNUNET_CURL_Job *job;
269
270 if (GNUNET_YES == add_json)
271 if (CURLE_OK !=
272 curl_easy_setopt (eh,
273 CURLOPT_HTTPHEADER,
274 ctx->json_header))
275 {
276 GNUNET_break (0);
277 curl_easy_cleanup (eh);
278 return NULL;
279 }
280
281 job = GNUNET_new (struct GNUNET_CURL_Job);
282 if ( (CURLE_OK !=
283 curl_easy_setopt (eh,
284 CURLOPT_PRIVATE,
285 job)) ||
286 (CURLE_OK !=
287 curl_easy_setopt (eh,
288 CURLOPT_WRITEFUNCTION,
289 &download_cb)) ||
290 (CURLE_OK !=
291 curl_easy_setopt (eh,
292 CURLOPT_WRITEDATA,
293 &job->db)) ||
294 (CURLE_OK !=
295 curl_easy_setopt (eh,
296 CURLOPT_SHARE,
297 ctx->share)) ||
298 (CURLM_OK !=
299 curl_multi_add_handle (ctx->multi,
300 eh)) )
301 {
302 GNUNET_break (0);
303 GNUNET_free (job);
304 curl_easy_cleanup (eh);
305 return NULL;
306 }
307
308 job->easy_handle = eh;
309 job->ctx = ctx;
310 job->jcc = jcc;
311 job->jcc_cls = jcc_cls;
312 GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
313 ctx->jobs_tail,
314 job);
315 return job;
316}
317
318
319/**
320 * Cancel a job. Must only be called before the job completion
321 * callback is called for the respective job.
322 *
323 * @param job job to cancel
324 */
325void
326GNUNET_CURL_job_cancel (struct GNUNET_CURL_Job *job)
327{
328 struct GNUNET_CURL_Context *ctx = job->ctx;
329
330 GNUNET_CONTAINER_DLL_remove (ctx->jobs_head,
331 ctx->jobs_tail,
332 job);
333 GNUNET_break (CURLM_OK ==
334 curl_multi_remove_handle (ctx->multi,
335 job->easy_handle));
336 curl_easy_cleanup (job->easy_handle);
337 GNUNET_free_non_null (job->db.buf);
338 GNUNET_free (job);
339}
340
341
342/**
343 * Obtain information about the final result about the
344 * HTTP download. If the download was successful, parses
345 * the JSON in the @a db and returns it. Also returns
346 * the HTTP @a response_code. If the download failed,
347 * the return value is NULL. The response code is set
348 * in any case, on download errors to zero.
349 *
350 * Calling this function also cleans up @a db.
351 *
352 * @param db download buffer
353 * @param eh CURL handle (to get the response code)
354 * @param[out] response_code set to the HTTP response code
355 * (or zero if we aborted the download, i.e.
356 * because the response was too big, or if
357 * the JSON we received was malformed).
358 * @return NULL if downloading a JSON reply failed
359 */
360static json_t *
361download_get_result (struct DownloadBuffer *db,
362 CURL *eh,
363 long *response_code)
364{
365 json_t *json;
366 json_error_t error;
367 char *ct;
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 return NULL;
389 }
390
391 json = NULL;
392 if (0 == db->eno)
393 {
394 json = json_loadb (db->buf,
395 db->buf_size,
396 JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
397 &error);
398 if (NULL == json)
399 {
400 JSON_WARN (error);
401 *response_code = 0;
402 }
403 }
404 GNUNET_free_non_null (db->buf);
405 db->buf = NULL;
406 db->buf_size = 0;
407 if (NULL != json)
408 {
409 if (CURLE_OK !=
410 curl_easy_getinfo (eh,
411 CURLINFO_RESPONSE_CODE,
412 response_code))
413 {
414 /* unexpected error... */
415 GNUNET_break (0);
416 *response_code = 0;
417 }
418 }
419 return json;
420}
421
422
423/**
424 * Run the main event loop for the Taler interaction.
425 *
426 * @param ctx the library context
427 */
428void
429GNUNET_CURL_perform (struct GNUNET_CURL_Context *ctx)
430{
431 CURLMsg *cmsg;
432 struct GNUNET_CURL_Job *job;
433 int n_running;
434 int n_completed;
435 long response_code;
436 json_t *j;
437
438 (void) curl_multi_perform (ctx->multi,
439 &n_running);
440 while (NULL != (cmsg = curl_multi_info_read (ctx->multi,
441 &n_completed)))
442 {
443 /* Only documented return value is CURLMSG_DONE */
444 GNUNET_break (CURLMSG_DONE == cmsg->msg);
445 GNUNET_assert (CURLE_OK ==
446 curl_easy_getinfo (cmsg->easy_handle,
447 CURLINFO_PRIVATE,
448 (char **) &job));
449 GNUNET_assert (job->ctx == ctx);
450 j = download_get_result (&job->db,
451 job->easy_handle,
452 &response_code);
453 job->jcc (job->jcc_cls,
454 response_code,
455 j);
456 GNUNET_CURL_job_cancel (job);
457 }
458}
459
460
461/**
462 * Obtain the information for a select() call to wait until
463 * #GNUNET_CURL_perform() is ready again. Note that calling
464 * any other GNUNET_CURL-API may also imply that the library
465 * is again ready for #GNUNET_CURL_perform().
466 *
467 * Basically, a client should use this API to prepare for select(),
468 * then block on select(), then call #GNUNET_CURL_perform() and then
469 * start again until the work with the context is done.
470 *
471 * This function will NOT zero out the sets and assumes that @a max_fd
472 * and @a timeout are already set to minimal applicable values. It is
473 * safe to give this API FD-sets and @a max_fd and @a timeout that are
474 * already initialized to some other descriptors that need to go into
475 * the select() call.
476 *
477 * @param ctx context to get the event loop information for
478 * @param read_fd_set will be set for any pending read operations
479 * @param write_fd_set will be set for any pending write operations
480 * @param except_fd_set is here because curl_multi_fdset() has this argument
481 * @param max_fd set to the highest FD included in any set;
482 * if the existing sets have no FDs in it, the initial
483 * value should be "-1". (Note that `max_fd + 1` will need
484 * to be passed to select().)
485 * @param timeout set to the timeout in milliseconds (!); -1 means
486 * no timeout (NULL, blocking forever is OK), 0 means to
487 * proceed immediately with #GNUNET_CURL_perform().
488 */
489void
490GNUNET_CURL_get_select_info (struct GNUNET_CURL_Context *ctx,
491 fd_set *read_fd_set,
492 fd_set *write_fd_set,
493 fd_set *except_fd_set,
494 int *max_fd,
495 long *timeout)
496{
497 long to;
498 int m;
499
500 m = -1;
501 GNUNET_assert (CURLM_OK ==
502 curl_multi_fdset (ctx->multi,
503 read_fd_set,
504 write_fd_set,
505 except_fd_set,
506 &m));
507 to = *timeout;
508 *max_fd = GNUNET_MAX (m, *max_fd);
509 GNUNET_assert (CURLM_OK ==
510 curl_multi_timeout (ctx->multi,
511 &to));
512
513 /* Only if what we got back from curl is smaller than what we
514 already had (-1 == infinity!), then update timeout */
515 if ( (to < *timeout) &&
516 (-1 != to) )
517 *timeout = to;
518 if ( (-1 == (*timeout)) &&
519 (NULL != ctx->jobs_head) )
520 *timeout = to;
521}
522
523
524/**
525 * Cleanup library initialisation resources. This function should be called
526 * after using this library to cleanup the resources occupied during library's
527 * initialisation.
528 *
529 * @param ctx the library context
530 */
531void
532GNUNET_CURL_fini (struct GNUNET_CURL_Context *ctx)
533{
534 /* all jobs must have been cancelled at this time, assert this */
535 GNUNET_assert (NULL == ctx->jobs_head);
536 curl_share_cleanup (ctx->share);
537 curl_multi_cleanup (ctx->multi);
538 curl_slist_free_all (ctx->json_header);
539 GNUNET_free (ctx);
540}
541
542
543/**
544 * Initial global setup logic, specifically runs the Curl setup.
545 */
546__attribute__ ((constructor))
547void
548GNUNET_CURL_constructor__ (void)
549{
550 CURLcode ret;
551
552 if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
553 {
554 CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR,
555 "curl_global_init",
556 ret);
557 curl_fail = 1;
558 }
559}
560
561
562/**
563 * Cleans up after us, specifically runs the Curl cleanup.
564 */
565__attribute__ ((destructor))
566void
567GNUNET_CURL_destructor__ (void)
568{
569 if (curl_fail)
570 return;
571 curl_global_cleanup ();
572}
573
574/* end of curl.c */