aboutsummaryrefslogtreecommitdiff
path: root/src/curl/curl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/curl/curl.c')
-rw-r--r--src/curl/curl.c1100
1 files changed, 0 insertions, 1100 deletions
diff --git a/src/curl/curl.c b/src/curl/curl.c
deleted file mode 100644
index 71672c780..000000000
--- a/src/curl/curl.c
+++ /dev/null
@@ -1,1100 +0,0 @@
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 <microhttpd.h>
29#include "gnunet_curl_lib.h"
30
31#if ENABLE_BENCHMARK
32#include "../util/benchmark.h"
33#endif
34
35
36/**
37 * Log error related to CURL operations.
38 *
39 * @param type log level
40 * @param function which function failed to run
41 * @param code what was the curl error code
42 */
43#define CURL_STRERROR(type, function, code) \
44 GNUNET_log (type, \
45 "Curl function `%s' has failed at `%s:%d' with error: %s\n", \
46 function, \
47 __FILE__, \
48 __LINE__, \
49 curl_easy_strerror (code));
50
51/**
52 * Print JSON parsing related error information
53 */
54#define JSON_WARN(error) \
55 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
56 "JSON parsing failed at %s:%u: %s (%s)\n", \
57 __FILE__, \
58 __LINE__, \
59 error.text, \
60 error.source)
61
62
63/**
64 * Failsafe flag. Raised if our constructor fails to initialize
65 * the Curl library.
66 */
67static int curl_fail;
68
69/**
70 * Jobs are CURL requests running within a `struct GNUNET_CURL_Context`.
71 */
72struct GNUNET_CURL_Job
73{
74 /**
75 * We keep jobs in a DLL.
76 */
77 struct GNUNET_CURL_Job *next;
78
79 /**
80 * We keep jobs in a DLL.
81 */
82 struct GNUNET_CURL_Job *prev;
83
84 /**
85 * Easy handle of the job.
86 */
87 CURL *easy_handle;
88
89 /**
90 * Context this job runs in.
91 */
92 struct GNUNET_CURL_Context *ctx;
93
94 /**
95 * Function to call upon completion.
96 */
97 GNUNET_CURL_JobCompletionCallback jcc;
98
99 /**
100 * Closure for @e jcc.
101 */
102 void *jcc_cls;
103
104 /**
105 * Function to call upon completion.
106 */
107 GNUNET_CURL_RawJobCompletionCallback jcc_raw;
108
109 /**
110 * Closure for @e jcc_raw.
111 */
112 void *jcc_raw_cls;
113
114 /**
115 * Buffer for response received from CURL.
116 */
117 struct GNUNET_CURL_DownloadBuffer db;
118
119 /**
120 * Headers used for this job, the list needs to be freed
121 * after the job has finished.
122 */
123 struct curl_slist *job_headers;
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 * Headers common for all requests in the context.
154 */
155 struct curl_slist *common_headers;
156
157 /**
158 * If non-NULL, the async scope ID is sent in a request
159 * header of this name.
160 */
161 const char *async_scope_id_header;
162
163 /**
164 * Function we need to call whenever the event loop's
165 * socket set changed.
166 */
167 GNUNET_CURL_RescheduleCallback cb;
168
169 /**
170 * Closure for @e cb.
171 */
172 void *cb_cls;
173
174 /**
175 * USERNAME:PASSWORD to use for client-authentication
176 * with all requests of this context, or NULL.
177 */
178 char *userpass;
179
180 /**
181 * Type of the TLS client certificate used, or NULL.
182 */
183 char *certtype;
184
185 /**
186 * File with the TLS client certificate, or NULL.
187 */
188 char *certfile;
189
190 /**
191 * File with the private key to authenticate the
192 * TLS client, or NULL.
193 */
194 char *keyfile;
195
196 /**
197 * Passphrase to decrypt @e keyfile, or NULL.
198 */
199 char *keypass;
200
201};
202
203
204/**
205 * Force use of the provided username and password
206 * for client authentication for all operations performed
207 * with @a ctx.
208 *
209 * @param ctx context to set authentication data for
210 * @param userpass string with "$USERNAME:$PASSWORD"
211 */
212void
213GNUNET_CURL_set_userpass (struct GNUNET_CURL_Context *ctx,
214 const char *userpass)
215{
216 GNUNET_free (ctx->userpass);
217 if (NULL != userpass)
218 ctx->userpass = GNUNET_strdup (userpass);
219}
220
221
222/**
223 * Force use of the provided TLS client certificate
224 * for client authentication for all operations performed
225 * with @a ctx.
226 *
227 * Note that if the provided information is incorrect,
228 * the earliest operation that could fail is
229 * #GNUNET_CURL_job_add() or #GNUNET_CURL_job_add2()!
230 *
231 * @param ctx context to set authentication data for
232 * @param certtype type of the certificate
233 * @param certfile file with the certificate
234 * @param keyfile file with the private key
235 * @param keypass passphrase to decrypt @a keyfile (or NULL)
236 */
237void
238GNUNET_CURL_set_tlscert (struct GNUNET_CURL_Context *ctx,
239 const char *certtype,
240 const char *certfile,
241 const char *keyfile,
242 const char *keypass)
243{
244 GNUNET_free (ctx->certtype);
245 GNUNET_free (ctx->certfile);
246 GNUNET_free (ctx->keyfile);
247 GNUNET_free (ctx->keypass);
248 if (NULL != certtype)
249 ctx->certtype = GNUNET_strdup (certtype);
250 if (NULL != certfile)
251 ctx->certfile = GNUNET_strdup (certfile);
252 if (NULL != keyfile)
253 ctx->certtype = GNUNET_strdup (keyfile);
254 if (NULL != keypass)
255 ctx->certtype = GNUNET_strdup (keypass);
256}
257
258
259/**
260 * Initialise this library. This function should be called before using any of
261 * the following functions.
262 *
263 * @param cb function to call when rescheduling is required
264 * @param cb_cls closure for @a cb
265 * @return library context
266 */
267struct GNUNET_CURL_Context *
268GNUNET_CURL_init (GNUNET_CURL_RescheduleCallback cb,
269 void *cb_cls)
270{
271 struct GNUNET_CURL_Context *ctx;
272 CURLM *multi;
273 CURLSH *share;
274
275 if (curl_fail)
276 {
277 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
278 "Curl was not initialised properly\n");
279 return NULL;
280 }
281 if (NULL == (multi = curl_multi_init ()))
282 {
283 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
284 "Failed to create a Curl multi handle\n");
285 return NULL;
286 }
287 if (NULL == (share = curl_share_init ()))
288 {
289 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
290 "Failed to create a Curl share handle\n");
291 return NULL;
292 }
293 ctx = GNUNET_new (struct GNUNET_CURL_Context);
294 ctx->cb = cb;
295 ctx->cb_cls = cb_cls;
296 ctx->multi = multi;
297 ctx->share = share;
298 return ctx;
299}
300
301
302/**
303 * Enable sending the async scope ID as a header.
304 *
305 * @param ctx the context to enable this for
306 * @param header_name name of the header to send.
307 */
308void
309GNUNET_CURL_enable_async_scope_header (struct GNUNET_CURL_Context *ctx,
310 const char *header_name)
311{
312 ctx->async_scope_id_header = header_name;
313}
314
315
316/**
317 * Return #GNUNET_YES if given a valid scope ID and
318 * #GNUNET_NO otherwise. See #setup_job_headers,
319 * logic related to
320 * #GNUNET_CURL_enable_async_scope_header() for the
321 * code that generates such a @a scope_id.
322 *
323 * @returns #GNUNET_YES iff given a valid scope ID
324 */
325int
326GNUNET_CURL_is_valid_scope_id (const char *scope_id)
327{
328 if (strlen (scope_id) >= 64)
329 return GNUNET_NO;
330 for (size_t i = 0; i < strlen (scope_id); i++)
331 if (! (isalnum (scope_id[i]) || (scope_id[i] == '-')))
332 return GNUNET_NO;
333 return GNUNET_YES;
334}
335
336
337/**
338 * Callback used when downloading the reply to an HTTP request.
339 * Just appends all of the data to the `buf` in the
340 * `struct DownloadBuffer` for further processing. The size of
341 * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if
342 * the download exceeds this size, we abort with an error.
343 *
344 * @param bufptr data downloaded via HTTP
345 * @param size size of an item in @a bufptr
346 * @param nitems number of items in @a bufptr
347 * @param cls the `struct DownloadBuffer`
348 * @return number of bytes processed from @a bufptr
349 */
350static size_t
351download_cb (char *bufptr,
352 size_t size,
353 size_t nitems,
354 void *cls)
355{
356 struct GNUNET_CURL_DownloadBuffer *db = cls;
357 size_t msize;
358 void *buf;
359
360 if (0 == size * nitems)
361 {
362 /* Nothing (left) to do */
363 return 0;
364 }
365 msize = size * nitems;
366 if ((msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED)
367 {
368 db->eno = ENOMEM;
369 return 0; /* signals an error to curl */
370 }
371 db->buf = GNUNET_realloc (db->buf, db->buf_size + msize);
372 buf = db->buf + db->buf_size;
373 GNUNET_memcpy (buf, bufptr, msize);
374 db->buf_size += msize;
375 return msize;
376}
377
378
379/**
380 * Create the HTTP headers for the request
381 *
382 * @param ctx context we run in
383 * @param job_headers job-specific headers
384 * @return all headers to use
385 */
386static struct curl_slist *
387setup_job_headers (struct GNUNET_CURL_Context *ctx,
388 const struct curl_slist *job_headers)
389{
390 struct curl_slist *all_headers = NULL;
391
392 for (const struct curl_slist *curr = job_headers;
393 NULL != curr;
394 curr = curr->next)
395 {
396 GNUNET_assert (NULL !=
397 (all_headers = curl_slist_append (all_headers,
398 curr->data)));
399 }
400
401 for (const struct curl_slist *curr = ctx->common_headers;
402 NULL != curr;
403 curr = curr->next)
404 {
405 GNUNET_assert (NULL !=
406 (all_headers = curl_slist_append (all_headers,
407 curr->data)));
408 }
409
410 if (NULL != ctx->async_scope_id_header)
411 {
412 struct GNUNET_AsyncScopeSave scope;
413
414 GNUNET_async_scope_get (&scope);
415 if (GNUNET_YES == scope.have_scope)
416 {
417 char *aid_header;
418
419 aid_header =
420 GNUNET_STRINGS_data_to_string_alloc (
421 &scope.scope_id,
422 sizeof(struct GNUNET_AsyncScopeId));
423 GNUNET_assert (NULL != aid_header);
424 GNUNET_assert (NULL != curl_slist_append (all_headers, aid_header));
425 GNUNET_free (aid_header);
426 }
427 }
428 return all_headers;
429}
430
431
432/**
433 * Create a job.
434 *
435 * @param eh easy handle to use
436 * @param ctx context to run the job in
437 * @param all_headers HTTP client headers to use (free'd)
438 * @return NULL on error
439 */
440static struct GNUNET_CURL_Job *
441setup_job (CURL *eh,
442 struct GNUNET_CURL_Context *ctx,
443 struct curl_slist *all_headers)
444{
445 struct GNUNET_CURL_Job *job;
446
447 if (CURLE_OK !=
448 curl_easy_setopt (eh, CURLOPT_HTTPHEADER, all_headers))
449 {
450 GNUNET_break (0);
451 curl_slist_free_all (all_headers);
452 curl_easy_cleanup (eh);
453 return NULL;
454 }
455 job = GNUNET_new (struct GNUNET_CURL_Job);
456 job->job_headers = all_headers;
457
458 if ((CURLE_OK != curl_easy_setopt (eh, CURLOPT_PRIVATE, job)) ||
459 (CURLE_OK !=
460 curl_easy_setopt (eh, CURLOPT_WRITEFUNCTION, &download_cb)) ||
461 (CURLE_OK != curl_easy_setopt (eh, CURLOPT_WRITEDATA, &job->db)) ||
462 (CURLE_OK != curl_easy_setopt (eh, CURLOPT_SHARE, ctx->share)) ||
463 (CURLM_OK != curl_multi_add_handle (ctx->multi, eh)))
464 {
465 GNUNET_break (0);
466 GNUNET_free (job);
467 curl_easy_cleanup (eh);
468 return NULL;
469 }
470 job->easy_handle = eh;
471 job->ctx = ctx;
472 GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
473 ctx->jobs_tail,
474 job);
475 return job;
476}
477
478
479/**
480 * Add @a extra_headers to the HTTP headers for @a job.
481 *
482 * @param[in,out] job the job to modify
483 * @param extra_headers headers to append
484 */
485void
486GNUNET_CURL_extend_headers (struct GNUNET_CURL_Job *job,
487 const struct curl_slist *extra_headers)
488{
489 struct curl_slist *all_headers = job->job_headers;
490
491 for (const struct curl_slist *curr = extra_headers;
492 NULL != curr;
493 curr = curr->next)
494 {
495 GNUNET_assert (NULL !=
496 (all_headers = curl_slist_append (all_headers,
497 curr->data)));
498 }
499 job->job_headers = all_headers;
500}
501
502
503/**
504 * Schedule a CURL request to be executed and call the given @a jcc
505 * upon its completion. Note that the context will make use of the
506 * CURLOPT_PRIVATE facility of the CURL @a eh. Used to download
507 * resources that are NOT in JSON. The raw body will be returned.
508 *
509 * @param ctx context to execute the job in
510 * @param eh curl easy handle for the request, will
511 * be executed AND cleaned up
512 * @param job_headers extra headers to add for this request
513 * @param max_reply_size largest acceptable response body
514 * @param jcc callback to invoke upon completion
515 * @param jcc_cls closure for @a jcc
516 * @return NULL on error (in this case, @eh is still released!)
517 */
518struct GNUNET_CURL_Job *
519GNUNET_CURL_job_add_raw (struct GNUNET_CURL_Context *ctx,
520 CURL *eh,
521 const struct curl_slist *job_headers,
522 GNUNET_CURL_RawJobCompletionCallback jcc,
523 void *jcc_cls)
524{
525 struct GNUNET_CURL_Job *job;
526 struct curl_slist *all_headers;
527
528 GNUNET_assert (NULL != jcc);
529 all_headers = setup_job_headers (ctx,
530 job_headers);
531 if (NULL == (job = setup_job (eh,
532 ctx,
533 all_headers)))
534 return NULL;
535 job->jcc_raw = jcc;
536 job->jcc_raw_cls = jcc_cls;
537 ctx->cb (ctx->cb_cls);
538 return job;
539}
540
541
542/**
543 * Schedule a CURL request to be executed and call the given @a jcc
544 * upon its completion. Note that the context will make use of the
545 * CURLOPT_PRIVATE facility of the CURL @a eh.
546 *
547 * This function modifies the CURL handle to add the
548 * "Content-Type: application/json" header if @a add_json is set.
549 *
550 * @param ctx context to execute the job in
551 * @param eh curl easy handle for the request, will be executed AND
552 * cleaned up. NOTE: the handle should _never_ have gotten
553 * any headers list, as that would then be overridden by
554 * @a jcc. Therefore, always pass custom headers as the
555 * @a job_headers parameter.
556 * @param job_headers extra headers to add for this request
557 * @param jcc callback to invoke upon completion
558 * @param jcc_cls closure for @a jcc
559 * @return NULL on error (in this case, @eh is still released!)
560 */
561struct GNUNET_CURL_Job *
562GNUNET_CURL_job_add2 (struct GNUNET_CURL_Context *ctx,
563 CURL *eh,
564 const struct curl_slist *job_headers,
565 GNUNET_CURL_JobCompletionCallback jcc,
566 void *jcc_cls)
567{
568 struct GNUNET_CURL_Job *job;
569 struct curl_slist *all_headers;
570
571 GNUNET_assert (NULL != jcc);
572 if ( (NULL != ctx->userpass) &&
573 (0 != curl_easy_setopt (eh,
574 CURLOPT_USERPWD,
575 ctx->userpass)) )
576 return NULL;
577 if ( (NULL != ctx->certfile) &&
578 (0 != curl_easy_setopt (eh,
579 CURLOPT_SSLCERT,
580 ctx->certfile)) )
581 return NULL;
582 if ( (NULL != ctx->certtype) &&
583 (0 != curl_easy_setopt (eh,
584 CURLOPT_SSLCERTTYPE,
585 ctx->certtype)) )
586 return NULL;
587 if ( (NULL != ctx->keyfile) &&
588 (0 != curl_easy_setopt (eh,
589 CURLOPT_SSLKEY,
590 ctx->keyfile)) )
591 return NULL;
592 if ( (NULL != ctx->keypass) &&
593 (0 != curl_easy_setopt (eh,
594 CURLOPT_KEYPASSWD,
595 ctx->keypass)) )
596 return NULL;
597
598 all_headers = setup_job_headers (ctx,
599 job_headers);
600 if (NULL == (job = setup_job (eh,
601 ctx,
602 all_headers)))
603 return NULL;
604
605 job->jcc = jcc;
606 job->jcc_cls = jcc_cls;
607 ctx->cb (ctx->cb_cls);
608 return job;
609}
610
611
612/**
613 * Schedule a CURL request to be executed and call the given @a jcc
614 * upon its completion. Note that the context will make use of the
615 * CURLOPT_PRIVATE facility of the CURL @a eh.
616 *
617 * This function modifies the CURL handle to add the
618 * "Content-Type: application/json" header.
619 *
620 * @param ctx context to execute the job in
621 * @param eh curl easy handle for the request, will
622 * be executed AND cleaned up
623 * @param jcc callback to invoke upon completion
624 * @param jcc_cls closure for @a jcc
625 * @return NULL on error (in this case, @eh is still released!)
626 */
627struct GNUNET_CURL_Job *
628GNUNET_CURL_job_add_with_ct_json (struct GNUNET_CURL_Context *ctx,
629 CURL *eh,
630 GNUNET_CURL_JobCompletionCallback jcc,
631 void *jcc_cls)
632{
633 struct GNUNET_CURL_Job *job;
634 struct curl_slist *job_headers = NULL;
635
636 GNUNET_assert (NULL != (job_headers =
637 curl_slist_append (NULL,
638 "Content-Type: application/json")));
639 job = GNUNET_CURL_job_add2 (ctx,
640 eh,
641 job_headers,
642 jcc,
643 jcc_cls);
644 curl_slist_free_all (job_headers);
645 return job;
646}
647
648
649/**
650 * Schedule a CURL request to be executed and call the given @a jcc
651 * upon its completion. Note that the context will make use of the
652 * CURLOPT_PRIVATE facility of the CURL @a eh.
653 *
654 * @param ctx context to execute the job in
655 * @param eh curl easy handle for the request, will
656 * be executed AND cleaned up
657 * @param jcc callback to invoke upon completion
658 * @param jcc_cls closure for @a jcc
659 * @return NULL on error (in this case, @eh is still released!)
660 */
661struct GNUNET_CURL_Job *
662GNUNET_CURL_job_add (struct GNUNET_CURL_Context *ctx,
663 CURL *eh,
664 GNUNET_CURL_JobCompletionCallback jcc,
665 void *jcc_cls)
666{
667 return GNUNET_CURL_job_add2 (ctx,
668 eh,
669 NULL,
670 jcc,
671 jcc_cls);
672}
673
674
675/**
676 * Cancel a job. Must only be called before the job completion
677 * callback is called for the respective job.
678 *
679 * @param job job to cancel
680 */
681void
682GNUNET_CURL_job_cancel (struct GNUNET_CURL_Job *job)
683{
684 struct GNUNET_CURL_Context *ctx = job->ctx;
685
686 GNUNET_CONTAINER_DLL_remove (ctx->jobs_head, ctx->jobs_tail, job);
687 GNUNET_break (CURLM_OK ==
688 curl_multi_remove_handle (ctx->multi, job->easy_handle));
689 curl_easy_cleanup (job->easy_handle);
690 GNUNET_free (job->db.buf);
691 curl_slist_free_all (job->job_headers);
692 ctx->cb (ctx->cb_cls);
693 GNUNET_free (job);
694}
695
696
697/**
698 * Test if the given content type @a ct is JSON
699 *
700 * @param ct a content type, e.g. "application/json; charset=UTF-8"
701 * @return true if @a ct denotes JSON
702 */
703static bool
704is_json (const char *ct)
705{
706 const char *semi;
707
708 /* check for "application/json" exact match */
709 if (0 == strcasecmp (ct,
710 "application/json"))
711 return true;
712 /* check for "application/json;[ANYTHING]" */
713 semi = strchr (ct,
714 ';');
715 /* also allow "application/json [ANYTHING]" (note the space!) */
716 if (NULL == semi)
717 semi = strchr (ct,
718 ' ');
719 if (NULL == semi)
720 return false; /* no delimiter we accept, forget it */
721 if (semi - ct != strlen ("application/json"))
722 return false; /* delimiter past desired length, forget it */
723 if (0 == strncasecmp (ct,
724 "application/json",
725 strlen ("application/json")))
726 return true; /* OK */
727 return false;
728}
729
730
731/**
732 * Obtain information about the final result about the
733 * HTTP download. If the download was successful, parses
734 * the JSON in the @a db and returns it. Also returns
735 * the HTTP @a response_code. If the download failed,
736 * the return value is NULL. The response code is set
737 * in any case, on download errors to zero.
738 *
739 * Calling this function also cleans up @a db.
740 *
741 * @param db download buffer
742 * @param eh CURL handle (to get the response code)
743 * @param[out] response_code set to the HTTP response code
744 * (or zero if we aborted the download, for example
745 * because the response was too big, or if
746 * the JSON we received was malformed).
747 * @return NULL if downloading a JSON reply failed.
748 */
749void *
750GNUNET_CURL_download_get_result_ (struct GNUNET_CURL_DownloadBuffer *db,
751 CURL *eh,
752 long *response_code)
753{
754 json_t *json;
755 json_error_t error;
756 char *ct;
757
758 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
759 "Downloaded body: %.*s\n",
760 (int) db->buf_size,
761 (char *) db->buf);
762 if (CURLE_OK !=
763 curl_easy_getinfo (eh,
764 CURLINFO_RESPONSE_CODE,
765 response_code))
766 {
767 /* unexpected error... */
768 GNUNET_break (0);
769 *response_code = 0;
770 }
771 if ((CURLE_OK !=
772 curl_easy_getinfo (eh,
773 CURLINFO_CONTENT_TYPE,
774 &ct)) ||
775 (NULL == ct) ||
776 (! is_json (ct)))
777 {
778 /* No content type or explicitly not JSON, refuse to parse
779 (but keep response code) */
780 if (0 != db->buf_size)
781 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
782 "Did NOT detect response `%.*s' as JSON\n",
783 (int) db->buf_size,
784 (const char *) db->buf);
785 return NULL;
786 }
787 if (MHD_HTTP_NO_CONTENT == *response_code)
788 return NULL;
789 if (0 == *response_code)
790 {
791 char *url;
792
793 if (CURLE_OK !=
794 curl_easy_getinfo (eh,
795 CURLINFO_EFFECTIVE_URL,
796 &url))
797 url = "<unknown URL>";
798 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
799 "Failed to download response from `%s': \n",
800 url);
801 return NULL;
802 }
803 json = NULL;
804 if (0 == db->eno)
805 {
806 json = json_loadb (db->buf,
807 db->buf_size,
808 JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
809 &error);
810 if (NULL == json)
811 {
812 JSON_WARN (error);
813 *response_code = 0;
814 }
815 }
816 GNUNET_free (db->buf);
817 db->buf = NULL;
818 db->buf_size = 0;
819 return json;
820}
821
822
823/**
824 * Add custom request header.
825 *
826 * @param ctx cURL context.
827 * @param header header string; will be given to the context AS IS.
828 * @return #GNUNET_OK if no errors occurred, #GNUNET_SYSERR otherwise.
829 */
830enum GNUNET_GenericReturnValue
831GNUNET_CURL_append_header (struct GNUNET_CURL_Context *ctx,
832 const char *header)
833{
834 ctx->common_headers = curl_slist_append (ctx->common_headers, header);
835 if (NULL == ctx->common_headers)
836 return GNUNET_SYSERR;
837
838 return GNUNET_OK;
839}
840
841
842#if ENABLE_BENCHMARK
843static void
844do_benchmark (CURLMsg *cmsg)
845{
846 char *url = NULL;
847 double total_as_double = 0;
848 struct GNUNET_TIME_Relative total;
849 struct UrlRequestData *urd;
850 /* Some care required, as curl is using data types (long vs curl_off_t vs
851 * double) inconsistently to store byte count. */
852 curl_off_t size_curl = 0;
853 long size_long = 0;
854 uint64_t bytes_sent = 0;
855 uint64_t bytes_received = 0;
856
857 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
858 CURLINFO_TOTAL_TIME,
859 &total_as_double));
860 total.rel_value_us = total_as_double * 1000 * 1000;
861
862 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
863 CURLINFO_EFFECTIVE_URL,
864 &url));
865
866 /* HEADER_SIZE + SIZE_DOWNLOAD_T is hopefully the total
867 number of bytes received, not clear from curl docs. */
868
869 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
870 CURLINFO_HEADER_SIZE,
871 &size_long));
872 bytes_received += size_long;
873
874 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
875 CURLINFO_SIZE_DOWNLOAD_T,
876 &size_curl));
877 bytes_received += size_curl;
878
879 /* REQUEST_SIZE + SIZE_UPLOAD_T is hopefully the total number of bytes
880 sent, again docs are not completely clear. */
881
882 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
883 CURLINFO_REQUEST_SIZE,
884 &size_long));
885 bytes_sent += size_long;
886
887 /* We obtain this value to check an invariant, but never use it otherwise. */
888 GNUNET_break (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
889 CURLINFO_SIZE_UPLOAD_T,
890 &size_curl));
891
892 /* CURLINFO_SIZE_UPLOAD_T <= CURLINFO_REQUEST_SIZE should
893 be an invariant.
894 As verified with
895 curl -w "foo%{size_request} -XPOST --data "ABC" $URL
896 the CURLINFO_REQUEST_SIZE should be the whole size of the request
897 including headers and body.
898 */
899 GNUNET_break (size_curl <= size_long);
900
901 urd = get_url_benchmark_data (url, (unsigned int) response_code);
902 urd->count++;
903 urd->time = GNUNET_TIME_relative_add (urd->time, total);
904 urd->time_max = GNUNET_TIME_relative_max (total, urd->time_max);
905 urd->bytes_sent += bytes_sent;
906 urd->bytes_received += bytes_received;
907}
908
909
910#endif
911
912
913/**
914 * Run the main event loop for the HTTP interaction.
915 *
916 * @param ctx the library context
917 * @param rp parses the raw response returned from
918 * the Web server.
919 * @param rc cleans/frees the response
920 */
921void
922GNUNET_CURL_perform2 (struct GNUNET_CURL_Context *ctx,
923 GNUNET_CURL_RawParser rp,
924 GNUNET_CURL_ResponseCleaner rc)
925{
926 CURLMsg *cmsg;
927 int n_running;
928 int n_completed;
929
930 (void) curl_multi_perform (ctx->multi,
931 &n_running);
932 while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed)))
933 {
934 struct GNUNET_CURL_Job *job;
935 long response_code;
936 void *response;
937
938 /* Only documented return value is CURLMSG_DONE */
939 GNUNET_break (CURLMSG_DONE == cmsg->msg);
940 GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
941 CURLINFO_PRIVATE,
942 (char **) &job));
943 GNUNET_assert (job->ctx == ctx);
944 response_code = 0;
945 if (NULL != job->jcc_raw)
946 {
947 /* RAW mode, no parsing */
948 GNUNET_break (CURLE_OK ==
949 curl_easy_getinfo (job->easy_handle,
950 CURLINFO_RESPONSE_CODE,
951 &response_code));
952 job->jcc_raw (job->jcc_raw_cls,
953 response_code,
954 job->db.buf,
955 job->db.buf_size);
956 }
957 else
958 {
959 /* to be parsed via 'rp' */
960 response = rp (&job->db,
961 job->easy_handle,
962 &response_code);
963 job->jcc (job->jcc_cls,
964 response_code,
965 response);
966 rc (response);
967 }
968#if ENABLE_BENCHMARK
969 do_benchmark (cmsg);
970#endif
971 GNUNET_CURL_job_cancel (job);
972 }
973}
974
975
976/**
977 * Run the main event loop for the HTTP interaction.
978 *
979 * @param ctx the library context
980 */
981void
982GNUNET_CURL_perform (struct GNUNET_CURL_Context *ctx)
983{
984 GNUNET_CURL_perform2 (ctx,
985 &GNUNET_CURL_download_get_result_,
986 (GNUNET_CURL_ResponseCleaner) & json_decref);
987}
988
989
990/**
991 * Obtain the information for a select() call to wait until
992 * #GNUNET_CURL_perform() is ready again. Note that calling
993 * any other GNUNET_CURL-API may also imply that the library
994 * is again ready for #GNUNET_CURL_perform().
995 *
996 * Basically, a client should use this API to prepare for select(),
997 * then block on select(), then call #GNUNET_CURL_perform() and then
998 * start again until the work with the context is done.
999 *
1000 * This function will NOT zero out the sets and assumes that @a max_fd
1001 * and @a timeout are already set to minimal applicable values. It is
1002 * safe to give this API FD-sets and @a max_fd and @a timeout that are
1003 * already initialized to some other descriptors that need to go into
1004 * the select() call.
1005 *
1006 * @param ctx context to get the event loop information for
1007 * @param read_fd_set will be set for any pending read operations
1008 * @param write_fd_set will be set for any pending write operations
1009 * @param except_fd_set is here because curl_multi_fdset() has this argument
1010 * @param max_fd set to the highest FD included in any set;
1011 * if the existing sets have no FDs in it, the initial
1012 * value should be "-1". (Note that `max_fd + 1` will need
1013 * to be passed to select().)
1014 * @param timeout set to the timeout in milliseconds (!); -1 means
1015 * no timeout (NULL, blocking forever is OK), 0 means to
1016 * proceed immediately with #GNUNET_CURL_perform().
1017 */
1018void
1019GNUNET_CURL_get_select_info (struct GNUNET_CURL_Context *ctx,
1020 fd_set *read_fd_set,
1021 fd_set *write_fd_set,
1022 fd_set *except_fd_set,
1023 int *max_fd,
1024 long *timeout)
1025{
1026 long to;
1027 int m;
1028
1029 m = -1;
1030 GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi,
1031 read_fd_set,
1032 write_fd_set,
1033 except_fd_set,
1034 &m));
1035 to = *timeout;
1036 *max_fd = GNUNET_MAX (m, *max_fd);
1037 GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &to));
1038
1039 /* Only if what we got back from curl is smaller than what we
1040 already had (-1 == infinity!), then update timeout */
1041 if ((to < *timeout) && (-1 != to))
1042 *timeout = to;
1043 if ((-1 == (*timeout)) && (NULL != ctx->jobs_head))
1044 *timeout = to;
1045}
1046
1047
1048/**
1049 * Cleanup library initialisation resources. This function should be called
1050 * after using this library to cleanup the resources occupied during library's
1051 * initialisation.
1052 *
1053 * @param ctx the library context
1054 */
1055void
1056GNUNET_CURL_fini (struct GNUNET_CURL_Context *ctx)
1057{
1058 /* all jobs must have been cancelled at this time, assert this */
1059 GNUNET_assert (NULL == ctx->jobs_head);
1060 curl_share_cleanup (ctx->share);
1061 curl_multi_cleanup (ctx->multi);
1062 curl_slist_free_all (ctx->common_headers);
1063 GNUNET_free (ctx->userpass);
1064 GNUNET_free (ctx->certtype);
1065 GNUNET_free (ctx->certfile);
1066 GNUNET_free (ctx->keyfile);
1067 GNUNET_free (ctx->keypass);
1068 GNUNET_free (ctx);
1069}
1070
1071
1072/**
1073 * Initial global setup logic, specifically runs the Curl setup.
1074 */
1075__attribute__ ((constructor)) void
1076GNUNET_CURL_constructor__ (void)
1077{
1078 CURLcode ret;
1079
1080 if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
1081 {
1082 CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret);
1083 curl_fail = 1;
1084 }
1085}
1086
1087
1088/**
1089 * Cleans up after us, specifically runs the Curl cleanup.
1090 */
1091__attribute__ ((destructor)) void
1092GNUNET_CURL_destructor__ (void)
1093{
1094 if (curl_fail)
1095 return;
1096 curl_global_cleanup ();
1097}
1098
1099
1100/* end of curl.c */