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.c1134
1 files changed, 0 insertions, 1134 deletions
diff --git a/src/curl/curl.c b/src/curl/curl.c
deleted file mode 100644
index 30c2f8c01..000000000
--- a/src/curl/curl.c
+++ /dev/null
@@ -1,1134 +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,
372 db->buf_size + msize);
373 buf = db->buf + db->buf_size;
374 GNUNET_memcpy (buf, bufptr, msize);
375 db->buf_size += msize;
376 return msize;
377}
378
379
380/**
381 * Create the HTTP headers for the request
382 *
383 * @param ctx context we run in
384 * @param job_headers job-specific headers
385 * @return all headers to use
386 */
387static struct curl_slist *
388setup_job_headers (struct GNUNET_CURL_Context *ctx,
389 const struct curl_slist *job_headers)
390{
391 struct curl_slist *all_headers = NULL;
392
393 for (const struct curl_slist *curr = job_headers;
394 NULL != curr;
395 curr = curr->next)
396 {
397 GNUNET_assert (NULL !=
398 (all_headers = curl_slist_append (all_headers,
399 curr->data)));
400 }
401
402 for (const struct curl_slist *curr = ctx->common_headers;
403 NULL != curr;
404 curr = curr->next)
405 {
406 GNUNET_assert (NULL !=
407 (all_headers = curl_slist_append (all_headers,
408 curr->data)));
409 }
410
411 if (NULL != ctx->async_scope_id_header)
412 {
413 struct GNUNET_AsyncScopeSave scope;
414
415 GNUNET_async_scope_get (&scope);
416 if (GNUNET_YES == scope.have_scope)
417 {
418 char *aid_header;
419
420 aid_header =
421 GNUNET_STRINGS_data_to_string_alloc (
422 &scope.scope_id,
423 sizeof(struct GNUNET_AsyncScopeId));
424 GNUNET_assert (NULL != aid_header);
425 GNUNET_assert (NULL != curl_slist_append (all_headers,
426 aid_header));
427 GNUNET_free (aid_header);
428 }
429 }
430 return all_headers;
431}
432
433
434/**
435 * Create a job.
436 *
437 * @param eh easy handle to use
438 * @param ctx context to run the job in
439 * @param all_headers HTTP client headers to use (free'd)
440 * @return NULL on error
441 */
442static struct GNUNET_CURL_Job *
443setup_job (CURL *eh,
444 struct GNUNET_CURL_Context *ctx,
445 struct curl_slist *all_headers)
446{
447 struct GNUNET_CURL_Job *job;
448
449 if (CURLE_OK !=
450 curl_easy_setopt (eh, CURLOPT_HTTPHEADER, all_headers))
451 {
452 GNUNET_break (0);
453 curl_slist_free_all (all_headers);
454 curl_easy_cleanup (eh);
455 return NULL;
456 }
457 job = GNUNET_new (struct GNUNET_CURL_Job);
458 job->job_headers = all_headers;
459
460 if ( (CURLE_OK !=
461 curl_easy_setopt (eh,
462 CURLOPT_PRIVATE,
463 job)) ||
464 (CURLE_OK !=
465 curl_easy_setopt (eh,
466 CURLOPT_WRITEFUNCTION,
467 &download_cb)) ||
468 (CURLE_OK !=
469 curl_easy_setopt (eh,
470 CURLOPT_WRITEDATA,
471 &job->db)) ||
472 (CURLE_OK !=
473 curl_easy_setopt (eh,
474 CURLOPT_SHARE,
475 ctx->share)) ||
476 (CURLM_OK !=
477 curl_multi_add_handle (ctx->multi,
478 eh)) )
479 {
480 GNUNET_break (0);
481 GNUNET_free (job);
482 curl_easy_cleanup (eh);
483 return NULL;
484 }
485 job->easy_handle = eh;
486 job->ctx = ctx;
487 GNUNET_CONTAINER_DLL_insert (ctx->jobs_head,
488 ctx->jobs_tail,
489 job);
490 return job;
491}
492
493
494/**
495 * Add @a extra_headers to the HTTP headers for @a job.
496 *
497 * @param[in,out] job the job to modify
498 * @param extra_headers headers to append
499 */
500void
501GNUNET_CURL_extend_headers (struct GNUNET_CURL_Job *job,
502 const struct curl_slist *extra_headers)
503{
504 struct curl_slist *all_headers = job->job_headers;
505
506 for (const struct curl_slist *curr = extra_headers;
507 NULL != curr;
508 curr = curr->next)
509 {
510 GNUNET_assert (NULL !=
511 (all_headers = curl_slist_append (all_headers,
512 curr->data)));
513 }
514 job->job_headers = all_headers;
515}
516
517
518/**
519 * Schedule a CURL request to be executed and call the given @a jcc
520 * upon its completion. Note that the context will make use of the
521 * CURLOPT_PRIVATE facility of the CURL @a eh. Used to download
522 * resources that are NOT in JSON. The raw body will be returned.
523 *
524 * @param ctx context to execute the job in
525 * @param eh curl easy handle for the request, will
526 * be executed AND cleaned up
527 * @param job_headers extra headers to add for this request
528 * @param max_reply_size largest acceptable response body
529 * @param jcc callback to invoke upon completion
530 * @param jcc_cls closure for @a jcc
531 * @return NULL on error (in this case, @eh is still released!)
532 */
533struct GNUNET_CURL_Job *
534GNUNET_CURL_job_add_raw (struct GNUNET_CURL_Context *ctx,
535 CURL *eh,
536 const struct curl_slist *job_headers,
537 GNUNET_CURL_RawJobCompletionCallback jcc,
538 void *jcc_cls)
539{
540 struct GNUNET_CURL_Job *job;
541 struct curl_slist *all_headers;
542
543 GNUNET_assert (NULL != jcc);
544 all_headers = setup_job_headers (ctx,
545 job_headers);
546 if (NULL == (job = setup_job (eh,
547 ctx,
548 all_headers)))
549 return NULL;
550 job->jcc_raw = jcc;
551 job->jcc_raw_cls = jcc_cls;
552 ctx->cb (ctx->cb_cls);
553 return job;
554}
555
556
557/**
558 * Schedule a CURL request to be executed and call the given @a jcc
559 * upon its completion. Note that the context will make use of the
560 * CURLOPT_PRIVATE facility of the CURL @a eh.
561 *
562 * This function modifies the CURL handle to add the
563 * "Content-Type: application/json" header if @a add_json is set.
564 *
565 * @param ctx context to execute the job in
566 * @param eh curl easy handle for the request, will be executed AND
567 * cleaned up. NOTE: the handle should _never_ have gotten
568 * any headers list, as that would then be overridden by
569 * @a jcc. Therefore, always pass custom headers as the
570 * @a job_headers parameter.
571 * @param job_headers extra headers to add for this request
572 * @param jcc callback to invoke upon completion
573 * @param jcc_cls closure for @a jcc
574 * @return NULL on error (in this case, @eh is still released!)
575 */
576struct GNUNET_CURL_Job *
577GNUNET_CURL_job_add2 (struct GNUNET_CURL_Context *ctx,
578 CURL *eh,
579 const struct curl_slist *job_headers,
580 GNUNET_CURL_JobCompletionCallback jcc,
581 void *jcc_cls)
582{
583 struct GNUNET_CURL_Job *job;
584 struct curl_slist *all_headers;
585
586 GNUNET_assert (NULL != jcc);
587 if ( (NULL != ctx->userpass) &&
588 (0 != curl_easy_setopt (eh,
589 CURLOPT_USERPWD,
590 ctx->userpass)) )
591 return NULL;
592 if ( (NULL != ctx->certfile) &&
593 (0 != curl_easy_setopt (eh,
594 CURLOPT_SSLCERT,
595 ctx->certfile)) )
596 return NULL;
597 if ( (NULL != ctx->certtype) &&
598 (0 != curl_easy_setopt (eh,
599 CURLOPT_SSLCERTTYPE,
600 ctx->certtype)) )
601 return NULL;
602 if ( (NULL != ctx->keyfile) &&
603 (0 != curl_easy_setopt (eh,
604 CURLOPT_SSLKEY,
605 ctx->keyfile)) )
606 return NULL;
607 if ( (NULL != ctx->keypass) &&
608 (0 != curl_easy_setopt (eh,
609 CURLOPT_KEYPASSWD,
610 ctx->keypass)) )
611 return NULL;
612
613 all_headers = setup_job_headers (ctx,
614 job_headers);
615 if (NULL == (job = setup_job (eh,
616 ctx,
617 all_headers)))
618 return NULL;
619
620 job->jcc = jcc;
621 job->jcc_cls = jcc_cls;
622 ctx->cb (ctx->cb_cls);
623 return job;
624}
625
626
627/**
628 * Schedule a CURL request to be executed and call the given @a jcc
629 * upon its completion. Note that the context will make use of the
630 * CURLOPT_PRIVATE facility of the CURL @a eh.
631 *
632 * This function modifies the CURL handle to add the
633 * "Content-Type: application/json" header.
634 *
635 * @param ctx context to execute the job in
636 * @param eh curl easy handle for the request, will
637 * be executed AND cleaned up
638 * @param jcc callback to invoke upon completion
639 * @param jcc_cls closure for @a jcc
640 * @return NULL on error (in this case, @eh is still released!)
641 */
642struct GNUNET_CURL_Job *
643GNUNET_CURL_job_add_with_ct_json (struct GNUNET_CURL_Context *ctx,
644 CURL *eh,
645 GNUNET_CURL_JobCompletionCallback jcc,
646 void *jcc_cls)
647{
648 struct GNUNET_CURL_Job *job;
649 struct curl_slist *job_headers = NULL;
650
651 GNUNET_assert (NULL != (job_headers =
652 curl_slist_append (NULL,
653 "Content-Type: application/json")));
654 job = GNUNET_CURL_job_add2 (ctx,
655 eh,
656 job_headers,
657 jcc,
658 jcc_cls);
659 curl_slist_free_all (job_headers);
660 return job;
661}
662
663
664/**
665 * Schedule a CURL request to be executed and call the given @a jcc
666 * upon its completion. Note that the context will make use of the
667 * CURLOPT_PRIVATE facility of the CURL @a eh.
668 *
669 * @param ctx context to execute the job in
670 * @param eh curl easy handle for the request, will
671 * be executed AND cleaned up
672 * @param jcc callback to invoke upon completion
673 * @param jcc_cls closure for @a jcc
674 * @return NULL on error (in this case, @eh is still released!)
675 */
676struct GNUNET_CURL_Job *
677GNUNET_CURL_job_add (struct GNUNET_CURL_Context *ctx,
678 CURL *eh,
679 GNUNET_CURL_JobCompletionCallback jcc,
680 void *jcc_cls)
681{
682 return GNUNET_CURL_job_add2 (ctx,
683 eh,
684 NULL,
685 jcc,
686 jcc_cls);
687}
688
689
690/**
691 * Cancel a job. Must only be called before the job completion
692 * callback is called for the respective job.
693 *
694 * @param job job to cancel
695 */
696void
697GNUNET_CURL_job_cancel (struct GNUNET_CURL_Job *job)
698{
699 struct GNUNET_CURL_Context *ctx = job->ctx;
700
701 GNUNET_CONTAINER_DLL_remove (ctx->jobs_head,
702 ctx->jobs_tail,
703 job);
704 GNUNET_break (CURLM_OK ==
705 curl_multi_remove_handle (ctx->multi,
706 job->easy_handle));
707 curl_easy_cleanup (job->easy_handle);
708 GNUNET_free (job->db.buf);
709 curl_slist_free_all (job->job_headers);
710 ctx->cb (ctx->cb_cls);
711 GNUNET_free (job);
712}
713
714
715/**
716 * Test if the given content type @a ct is JSON
717 *
718 * @param ct a content type, e.g. "application/json; charset=UTF-8"
719 * @return true if @a ct denotes JSON
720 */
721static bool
722is_json (const char *ct)
723{
724 const char *semi;
725
726 /* check for "application/json" exact match */
727 if (0 == strcasecmp (ct,
728 "application/json"))
729 return true;
730 /* check for "application/json;[ANYTHING]" */
731 semi = strchr (ct,
732 ';');
733 /* also allow "application/json [ANYTHING]" (note the space!) */
734 if (NULL == semi)
735 semi = strchr (ct,
736 ' ');
737 if (NULL == semi)
738 return false; /* no delimiter we accept, forget it */
739 if (semi - ct != strlen ("application/json"))
740 return false; /* delimiter past desired length, forget it */
741 if (0 == strncasecmp (ct,
742 "application/json",
743 strlen ("application/json")))
744 return true; /* OK */
745 return false;
746}
747
748
749/**
750 * Obtain information about the final result about the
751 * HTTP download. If the download was successful, parses
752 * the JSON in the @a db and returns it. Also returns
753 * the HTTP @a response_code. If the download failed,
754 * the return value is NULL. The response code is set
755 * in any case, on download errors to zero.
756 *
757 * Calling this function also cleans up @a db.
758 *
759 * @param db download buffer
760 * @param eh CURL handle (to get the response code)
761 * @param[out] response_code set to the HTTP response code
762 * (or zero if we aborted the download, for example
763 * because the response was too big, or if
764 * the JSON we received was malformed).
765 * @return NULL if downloading a JSON reply failed.
766 */
767void *
768GNUNET_CURL_download_get_result_ (struct GNUNET_CURL_DownloadBuffer *db,
769 CURL *eh,
770 long *response_code)
771{
772 json_t *json;
773 json_error_t error;
774 char *ct;
775
776 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
777 "Downloaded body: %.*s\n",
778 (int) db->buf_size,
779 (char *) db->buf);
780 if (CURLE_OK !=
781 curl_easy_getinfo (eh,
782 CURLINFO_RESPONSE_CODE,
783 response_code))
784 {
785 /* unexpected error... */
786 GNUNET_break (0);
787 *response_code = 0;
788 }
789 if ((CURLE_OK !=
790 curl_easy_getinfo (eh,
791 CURLINFO_CONTENT_TYPE,
792 &ct)) ||
793 (NULL == ct) ||
794 (! is_json (ct)))
795 {
796 /* No content type or explicitly not JSON, refuse to parse
797 (but keep response code) */
798 if (0 != db->buf_size)
799 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
800 "Did NOT detect response `%.*s' as JSON\n",
801 (int) db->buf_size,
802 (const char *) db->buf);
803 return NULL;
804 }
805 if (MHD_HTTP_NO_CONTENT == *response_code)
806 return NULL;
807 if (0 == *response_code)
808 {
809 char *url;
810
811 if (CURLE_OK !=
812 curl_easy_getinfo (eh,
813 CURLINFO_EFFECTIVE_URL,
814 &url))
815 url = "<unknown URL>";
816 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
817 "Failed to download response from `%s': \n",
818 url);
819 return NULL;
820 }
821 json = NULL;
822 if (0 == db->eno)
823 {
824 json = json_loadb (db->buf,
825 db->buf_size,
826 JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
827 &error);
828 if (NULL == json)
829 {
830 JSON_WARN (error);
831 *response_code = 0;
832 }
833 }
834 GNUNET_free (db->buf);
835 db->buf = NULL;
836 db->buf_size = 0;
837 return json;
838}
839
840
841/**
842 * Add custom request header.
843 *
844 * @param ctx cURL context.
845 * @param header header string; will be given to the context AS IS.
846 * @return #GNUNET_OK if no errors occurred, #GNUNET_SYSERR otherwise.
847 */
848enum GNUNET_GenericReturnValue
849GNUNET_CURL_append_header (struct GNUNET_CURL_Context *ctx,
850 const char *header)
851{
852 ctx->common_headers = curl_slist_append (ctx->common_headers,
853 header);
854 if (NULL == ctx->common_headers)
855 return GNUNET_SYSERR;
856
857 return GNUNET_OK;
858}
859
860
861#if ENABLE_BENCHMARK
862static void
863do_benchmark (CURLMsg *cmsg)
864{
865 char *url = NULL;
866 double total_as_double = 0;
867 struct GNUNET_TIME_Relative total;
868 struct UrlRequestData *urd;
869 /* Some care required, as curl is using data types (long vs curl_off_t vs
870 * double) inconsistently to store byte count. */
871 curl_off_t size_curl = 0;
872 long size_long = 0;
873 uint64_t bytes_sent = 0;
874 uint64_t bytes_received = 0;
875
876 GNUNET_break (CURLE_OK ==
877 curl_easy_getinfo (cmsg->easy_handle,
878 CURLINFO_TOTAL_TIME,
879 &total_as_double));
880 total.rel_value_us = total_as_double * 1000 * 1000;
881
882 GNUNET_break (CURLE_OK ==
883 curl_easy_getinfo (cmsg->easy_handle,
884 CURLINFO_EFFECTIVE_URL,
885 &url));
886
887 /* HEADER_SIZE + SIZE_DOWNLOAD_T is hopefully the total
888 number of bytes received, not clear from curl docs. */
889
890 GNUNET_break (CURLE_OK ==
891 curl_easy_getinfo (cmsg->easy_handle,
892 CURLINFO_HEADER_SIZE,
893 &size_long));
894 bytes_received += size_long;
895
896 GNUNET_break (CURLE_OK ==
897 curl_easy_getinfo (cmsg->easy_handle,
898 CURLINFO_SIZE_DOWNLOAD_T,
899 &size_curl));
900 bytes_received += size_curl;
901
902 /* REQUEST_SIZE + SIZE_UPLOAD_T is hopefully the total number of bytes
903 sent, again docs are not completely clear. */
904
905 GNUNET_break (CURLE_OK ==
906 curl_easy_getinfo (cmsg->easy_handle,
907 CURLINFO_REQUEST_SIZE,
908 &size_long));
909 bytes_sent += size_long;
910
911 /* We obtain this value to check an invariant, but never use it otherwise. */
912 GNUNET_break (CURLE_OK ==
913 curl_easy_getinfo (cmsg->easy_handle,
914 CURLINFO_SIZE_UPLOAD_T,
915 &size_curl));
916
917 /* CURLINFO_SIZE_UPLOAD_T <= CURLINFO_REQUEST_SIZE should
918 be an invariant.
919 As verified with
920 curl -w "foo%{size_request} -XPOST --data "ABC" $URL
921 the CURLINFO_REQUEST_SIZE should be the whole size of the request
922 including headers and body.
923 */
924 GNUNET_break (size_curl <= size_long);
925
926 urd = get_url_benchmark_data (url, (unsigned int) response_code);
927 urd->count++;
928 urd->time = GNUNET_TIME_relative_add (urd->time,
929 total);
930 urd->time_max = GNUNET_TIME_relative_max (total,
931 urd->time_max);
932 urd->bytes_sent += bytes_sent;
933 urd->bytes_received += bytes_received;
934}
935
936
937#endif
938
939
940/**
941 * Run the main event loop for the HTTP interaction.
942 *
943 * @param ctx the library context
944 * @param rp parses the raw response returned from
945 * the Web server.
946 * @param rc cleans/frees the response
947 */
948void
949GNUNET_CURL_perform2 (struct GNUNET_CURL_Context *ctx,
950 GNUNET_CURL_RawParser rp,
951 GNUNET_CURL_ResponseCleaner rc)
952{
953 CURLMsg *cmsg;
954 int n_running;
955 int n_completed;
956
957 (void) curl_multi_perform (ctx->multi,
958 &n_running);
959 while (NULL != (cmsg = curl_multi_info_read (ctx->multi,
960 &n_completed)))
961 {
962 struct GNUNET_CURL_Job *job;
963 long response_code;
964 void *response;
965
966 /* Only documented return value is CURLMSG_DONE */
967 GNUNET_break (CURLMSG_DONE == cmsg->msg);
968 GNUNET_assert (CURLE_OK ==
969 curl_easy_getinfo (cmsg->easy_handle,
970 CURLINFO_PRIVATE,
971 (char **) &job));
972 GNUNET_assert (job->ctx == ctx);
973 response_code = 0;
974 if (NULL != job->jcc_raw)
975 {
976 /* RAW mode, no parsing */
977 GNUNET_break (CURLE_OK ==
978 curl_easy_getinfo (job->easy_handle,
979 CURLINFO_RESPONSE_CODE,
980 &response_code));
981 job->jcc_raw (job->jcc_raw_cls,
982 response_code,
983 job->db.buf,
984 job->db.buf_size);
985 }
986 else
987 {
988 /* to be parsed via 'rp' */
989 response = rp (&job->db,
990 job->easy_handle,
991 &response_code);
992 job->jcc (job->jcc_cls,
993 response_code,
994 response);
995 rc (response);
996 }
997#if ENABLE_BENCHMARK
998 do_benchmark (cmsg);
999#endif
1000 GNUNET_CURL_job_cancel (job);
1001 }
1002}
1003
1004
1005/**
1006 * Run the main event loop for the HTTP interaction.
1007 *
1008 * @param ctx the library context
1009 */
1010void
1011GNUNET_CURL_perform (struct GNUNET_CURL_Context *ctx)
1012{
1013 GNUNET_CURL_perform2 (ctx,
1014 &GNUNET_CURL_download_get_result_,
1015 (GNUNET_CURL_ResponseCleaner) & json_decref);
1016}
1017
1018
1019/**
1020 * Obtain the information for a select() call to wait until
1021 * #GNUNET_CURL_perform() is ready again. Note that calling
1022 * any other GNUNET_CURL-API may also imply that the library
1023 * is again ready for #GNUNET_CURL_perform().
1024 *
1025 * Basically, a client should use this API to prepare for select(),
1026 * then block on select(), then call #GNUNET_CURL_perform() and then
1027 * start again until the work with the context is done.
1028 *
1029 * This function will NOT zero out the sets and assumes that @a max_fd
1030 * and @a timeout are already set to minimal applicable values. It is
1031 * safe to give this API FD-sets and @a max_fd and @a timeout that are
1032 * already initialized to some other descriptors that need to go into
1033 * the select() call.
1034 *
1035 * @param ctx context to get the event loop information for
1036 * @param read_fd_set will be set for any pending read operations
1037 * @param write_fd_set will be set for any pending write operations
1038 * @param except_fd_set is here because curl_multi_fdset() has this argument
1039 * @param max_fd set to the highest FD included in any set;
1040 * if the existing sets have no FDs in it, the initial
1041 * value should be "-1". (Note that `max_fd + 1` will need
1042 * to be passed to select().)
1043 * @param timeout set to the timeout in milliseconds (!); -1 means
1044 * no timeout (NULL, blocking forever is OK), 0 means to
1045 * proceed immediately with #GNUNET_CURL_perform().
1046 */
1047void
1048GNUNET_CURL_get_select_info (struct GNUNET_CURL_Context *ctx,
1049 fd_set *read_fd_set,
1050 fd_set *write_fd_set,
1051 fd_set *except_fd_set,
1052 int *max_fd,
1053 long *timeout)
1054{
1055 long to;
1056 int m;
1057
1058 m = -1;
1059 GNUNET_assert (CURLM_OK ==
1060 curl_multi_fdset (ctx->multi,
1061 read_fd_set,
1062 write_fd_set,
1063 except_fd_set,
1064 &m));
1065 to = *timeout;
1066 *max_fd = GNUNET_MAX (m, *max_fd);
1067 GNUNET_assert (CURLM_OK ==
1068 curl_multi_timeout (ctx->multi,
1069 &to));
1070
1071 /* Only if what we got back from curl is smaller than what we
1072 already had (-1 == infinity!), then update timeout */
1073 if ((to < *timeout) && (-1 != to))
1074 *timeout = to;
1075 if ((-1 == (*timeout)) && (NULL != ctx->jobs_head))
1076 *timeout = to;
1077}
1078
1079
1080/**
1081 * Cleanup library initialisation resources. This function should be called
1082 * after using this library to cleanup the resources occupied during library's
1083 * initialisation.
1084 *
1085 * @param ctx the library context
1086 */
1087void
1088GNUNET_CURL_fini (struct GNUNET_CURL_Context *ctx)
1089{
1090 /* all jobs must have been cancelled at this time, assert this */
1091 GNUNET_assert (NULL == ctx->jobs_head);
1092 curl_share_cleanup (ctx->share);
1093 curl_multi_cleanup (ctx->multi);
1094 curl_slist_free_all (ctx->common_headers);
1095 GNUNET_free (ctx->userpass);
1096 GNUNET_free (ctx->certtype);
1097 GNUNET_free (ctx->certfile);
1098 GNUNET_free (ctx->keyfile);
1099 GNUNET_free (ctx->keypass);
1100 GNUNET_free (ctx);
1101}
1102
1103
1104/**
1105 * Initial global setup logic, specifically runs the Curl setup.
1106 */
1107__attribute__ ((constructor)) void
1108GNUNET_CURL_constructor__ (void)
1109{
1110 CURLcode ret;
1111
1112 if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
1113 {
1114 CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR,
1115 "curl_global_init",
1116 ret);
1117 curl_fail = 1;
1118 }
1119}
1120
1121
1122/**
1123 * Cleans up after us, specifically runs the Curl cleanup.
1124 */
1125__attribute__ ((destructor)) void
1126GNUNET_CURL_destructor__ (void)
1127{
1128 if (curl_fail)
1129 return;
1130 curl_global_cleanup ();
1131}
1132
1133
1134/* end of curl.c */