-fix NPE
[oweals/gnunet.git] / src / transport / plugin_transport_http_client.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2002-2014 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file transport/plugin_transport_http_client.c
23  * @brief HTTP/S client transport plugin
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27
28 #if BUILD_HTTPS
29 #define PLUGIN_NAME "https_client"
30 #define HTTP_STAT_STR_CONNECTIONS "# HTTPS client connections"
31 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_https_client_init
32 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_https_client_done
33 #else
34 #define PLUGIN_NAME "http_client"
35 #define HTTP_STAT_STR_CONNECTIONS "# HTTP client connections"
36 #define LIBGNUNET_PLUGIN_TRANSPORT_INIT libgnunet_plugin_transport_http_client_init
37 #define LIBGNUNET_PLUGIN_TRANSPORT_DONE libgnunet_plugin_transport_http_client_done
38 #endif
39
40 #define VERBOSE_CURL GNUNET_NO
41
42 #define PUT_DISCONNECT_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1)
43
44 #define ENABLE_PUT GNUNET_YES
45 #define ENABLE_GET GNUNET_YES
46
47 #include "platform.h"
48 #include "gnunet_util_lib.h"
49 #include "gnunet_protocols.h"
50 #include "gnunet_transport_plugin.h"
51 #include "plugin_transport_http_common.h"
52 #if HAVE_CURL_CURL_H
53 #include <curl/curl.h>
54 #elif HAVE_GNURL_CURL_H
55 #include <gnurl/curl.h>
56 #endif
57
58
59 #define LOG(kind,...) GNUNET_log_from(kind, PLUGIN_NAME, __VA_ARGS__)
60
61 /**
62  * Encapsulation of all of the state of the plugin.
63  */
64 struct HTTP_Client_Plugin;
65
66 /**
67  * State of a HTTP PUT request
68  */
69 enum HTTP_PUT_REQUEST_STATE
70 {
71   /**
72    *  Just created, not yet connected
73    */
74   H_NOT_CONNECTED,
75
76   /**
77    *  Connected
78    */
79   H_CONNECTED,
80
81   /**
82    *  Paused, nothing to send
83    */
84   H_PAUSED,
85
86   /**
87    * Temporary disconnect in progress due to inactivity
88    */
89   H_TMP_DISCONNECTING,
90
91   /**
92    * Send request while temporary disconnect, reconnect
93    */
94   H_TMP_RECONNECT_REQUIRED,
95
96   /**
97    * Temporarily disconnected
98    */
99   H_TMP_DISCONNECTED,
100
101   /**
102    * Disconnected
103    */
104   H_DISCONNECTED
105 };
106
107 /**
108  *  Message to send using http
109  */
110 struct HTTP_Message
111 {
112   /**
113    * next pointer for double linked list
114    */
115   struct HTTP_Message *next;
116
117   /**
118    * previous pointer for double linked list
119    */
120   struct HTTP_Message *prev;
121
122   /**
123    * buffer containing data to send
124    */
125   char *buf;
126
127   /**
128    * Continuation function to call once the transmission buffer
129    * has again space available.  NULL if there is no
130    * continuation to call.
131    */
132   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
133
134   /**
135    * Closure for @e transmit_cont.
136    */
137   void *transmit_cont_cls;
138
139   /**
140    * amount of data already sent
141    */
142   size_t pos;
143
144   /**
145    * buffer length
146    */
147   size_t size;
148
149 };
150
151
152 /**
153  * Session handle for HTTP(S) connections.
154  */
155 struct Session;
156
157
158 /**
159  * A request handle
160  *
161  */
162 struct RequestHandle
163 {
164   /**
165    * Current state of this request
166    */
167   enum HTTP_PUT_REQUEST_STATE state;
168
169   /**
170    * The curl easy handle
171    */
172   CURL *easyhandle;
173
174   /**
175    * The related session
176    */
177   struct Session *s;
178 };
179
180
181 /**
182  * Session handle for connections.
183  */
184 struct Session
185 {
186   /**
187    * The URL to connect to
188    */
189   char *url;
190
191   /**
192    * Address
193    */
194   struct GNUNET_HELLO_Address *address;
195
196   /**
197    * Pointer to the global plugin struct.
198    */
199   struct HTTP_Client_Plugin *plugin;
200
201   /**
202    * Handle for the HTTP PUT request.
203    */
204   struct RequestHandle put;
205
206   /**
207    * Handle for the HTTP GET request.
208    */
209   struct RequestHandle get;
210
211   /**
212    * next pointer for double linked list
213    */
214   struct HTTP_Message *msg_head;
215
216   /**
217    * previous pointer for double linked list
218    */
219   struct HTTP_Message *msg_tail;
220
221   /**
222    * Message stream tokenizer for incoming data
223    */
224   struct GNUNET_SERVER_MessageStreamTokenizer *msg_tk;
225
226   /**
227    * Session timeout task
228    */
229   struct GNUNET_SCHEDULER_Task * put_disconnect_task;
230
231   /**
232    * Session timeout task
233    */
234   struct GNUNET_SCHEDULER_Task * timeout_task;
235
236   /**
237    * Task to wake up client receive handle when receiving is allowed again
238    */
239   struct GNUNET_SCHEDULER_Task * recv_wakeup_task;
240
241   /**
242    * Absolute time when to receive data again.
243    * Used for receive throttling.
244    */
245   struct GNUNET_TIME_Absolute next_receive;
246
247   /**
248    * When does this session time out.
249    */
250   struct GNUNET_TIME_Absolute timeout;
251
252   /**
253    * Number of bytes waiting for transmission to this peer.
254    */
255   unsigned long long bytes_in_queue;
256
257   /**
258    * Outbound overhead due to HTTP connection
259    * Add to next message of this session when calling callback
260    */
261   size_t overhead;
262
263   /**
264    * Number of messages waiting for transmission to this peer.
265    */
266   unsigned int msgs_in_queue;
267
268   /**
269    * ATS network type.
270    */
271   enum GNUNET_ATS_Network_Type scope;
272 };
273
274
275 /**
276  * Encapsulation of all of the state of the plugin.
277  */
278 struct HTTP_Client_Plugin
279 {
280   /**
281    * Our environment.
282    */
283   struct GNUNET_TRANSPORT_PluginEnvironment *env;
284
285   /**
286    * Open sessions.
287    */
288   struct GNUNET_CONTAINER_MultiPeerMap *sessions;
289
290   /**
291    * Function to call about session status changes.
292    */
293   GNUNET_TRANSPORT_SessionInfoCallback sic;
294
295   /**
296    * Closure for @e sic.
297    */
298   void *sic_cls;
299
300   /**
301    * Plugin name
302    */
303   char *name;
304
305   /**
306    * Protocol
307    */
308   char *protocol;
309
310   /**
311    * Proxy configuration: hostname or ip of the proxy server
312    */
313   char *proxy_hostname;
314
315   /**
316    * Username for the proxy server
317    */
318   char *proxy_username;
319
320   /**
321    * Password for the proxy server
322    */
323   char *proxy_password;
324
325   /**
326    * cURL Multihandle
327    */
328   CURLM *curl_multi_handle;
329
330   /**
331    * curl perform task
332    */
333   struct GNUNET_SCHEDULER_Task * client_perform_task;
334
335   /**
336    * Type of proxy server:
337    *
338    * Valid values as supported by curl:
339    * CURLPROXY_HTTP, CURLPROXY_HTTP_1_0 CURLPROXY_SOCKS4, CURLPROXY_SOCKS5,
340    * CURLPROXY_SOCKS4A, CURLPROXY_SOCKS5_HOSTNAME
341    */
342   curl_proxytype proxytype;
343
344   /**
345    * Use proxy tunneling:
346    * Tunnel all operations through a given HTTP instead of have the proxy
347    * evaluate the HTTP request
348    *
349    * Default: #GNUNET_NO, #GNUNET_YES experimental
350    */
351   int proxy_use_httpproxytunnel;
352
353   /**
354    * My options to be included in the address
355    */
356   uint32_t options;
357
358   /**
359    * Maximum number of sockets the plugin can use
360    * Each http connections are two requests
361    */
362   unsigned int max_requests;
363
364   /**
365    * Current number of sockets the plugin can use
366    * Each http connections are two requests
367    */
368   unsigned int cur_requests;
369
370   /**
371    * Last used unique HTTP connection tag
372    */
373   uint32_t last_tag;
374
375   /**
376    * use IPv6
377    */
378   uint16_t use_ipv6;
379
380   /**
381    * use IPv4
382    */
383   uint16_t use_ipv4;
384
385   /**
386    * Should we emulate an XHR client for testing?
387    */
388   int emulate_xhr;
389 };
390
391
392 /**
393  * Disconnect a session
394  *
395  * @param cls the `struct HTTP_Client_Plugin *`
396  * @param s session
397  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
398  */
399 static int
400 http_client_plugin_session_disconnect (void *cls, struct Session *s);
401
402
403 /**
404  * If a session monitor is attached, notify it about the new
405  * session state.
406  *
407  * @param plugin our plugin
408  * @param session session that changed state
409  * @param state new state of the session
410  */
411 static void
412 notify_session_monitor (struct HTTP_Client_Plugin *plugin,
413                         struct Session *session,
414                         enum GNUNET_TRANSPORT_SessionState state)
415 {
416   struct GNUNET_TRANSPORT_SessionInfo info;
417
418   if (NULL == plugin->sic)
419     return;
420   memset (&info, 0, sizeof (info));
421   info.state = state;
422   info.is_inbound = GNUNET_NO;
423   info.num_msg_pending = session->msgs_in_queue;
424   info.num_bytes_pending = session->bytes_in_queue;
425   info.receive_delay = session->next_receive;
426   info.session_timeout = session->timeout;
427   info.address = session->address;
428   plugin->sic (plugin->sic_cls,
429                session,
430                &info);
431 }
432
433
434 /**
435  * Delete session @a s.
436  *
437  * @param s the session to delete
438  */
439 static void
440 client_delete_session (struct Session *s)
441 {
442   struct HTTP_Client_Plugin *plugin = s->plugin;
443   struct HTTP_Message *pos;
444   struct HTTP_Message *next;
445   CURLMcode mret;
446
447   if (NULL != s->timeout_task)
448   {
449     GNUNET_SCHEDULER_cancel (s->timeout_task);
450     s->timeout_task = NULL;
451     s->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
452   }
453   if (NULL != s->put_disconnect_task)
454   {
455     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
456     s->put_disconnect_task = NULL;
457   }
458   if (NULL != s->recv_wakeup_task)
459   {
460     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
461     s->recv_wakeup_task = NULL;
462   }
463   GNUNET_assert (GNUNET_OK ==
464                  GNUNET_CONTAINER_multipeermap_remove (plugin->sessions,
465                                                        &s->address->peer,
466                                                        s));
467   if (NULL != s->put.easyhandle)
468   {
469     LOG (GNUNET_ERROR_TYPE_DEBUG,
470          "Session %p/request %p: disconnecting PUT request to peer `%s'\n",
471          s,
472          s->put.easyhandle,
473          GNUNET_i2s (&s->address->peer));
474
475     /* remove curl handle from multi handle */
476     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
477                                      s->put.easyhandle);
478     GNUNET_break (CURLM_OK == mret);
479     curl_easy_cleanup (s->put.easyhandle);
480     GNUNET_assert (plugin->cur_requests > 0);
481     plugin->cur_requests--;
482     s->put.easyhandle = NULL;
483   }
484   if (NULL != s->get.easyhandle)
485   {
486     LOG (GNUNET_ERROR_TYPE_DEBUG,
487          "Session %p/request %p: disconnecting GET request to peer `%s'\n",
488          s, s->get.easyhandle,
489          GNUNET_i2s (&s->address->peer));
490     /* remove curl handle from multi handle */
491     mret = curl_multi_remove_handle (plugin->curl_multi_handle,
492                                      s->get.easyhandle);
493     GNUNET_break (CURLM_OK == mret);
494     curl_easy_cleanup (s->get.easyhandle);
495     GNUNET_assert (plugin->cur_requests > 0);
496     plugin->cur_requests--;
497     s->get.easyhandle = NULL;
498   }
499
500   GNUNET_STATISTICS_set (plugin->env->stats,
501                          HTTP_STAT_STR_CONNECTIONS,
502                          plugin->cur_requests,
503                          GNUNET_NO);
504   next = s->msg_head;
505   while (NULL != (pos = next))
506   {
507     next = pos->next;
508     GNUNET_CONTAINER_DLL_remove (s->msg_head,
509                                  s->msg_tail,
510                                  pos);
511     GNUNET_assert (0 < s->msgs_in_queue);
512     s->msgs_in_queue--;
513     GNUNET_assert (pos->size <= s->bytes_in_queue);
514     s->bytes_in_queue -= pos->size;
515     if (NULL != pos->transmit_cont)
516       pos->transmit_cont (pos->transmit_cont_cls,
517                           &s->address->peer,
518                           GNUNET_SYSERR,
519                           pos->size,
520                           pos->pos + s->overhead);
521     s->overhead = 0;
522     GNUNET_free (pos);
523   }
524   GNUNET_assert (0 == s->msgs_in_queue);
525   GNUNET_assert (0 == s->bytes_in_queue);
526   notify_session_monitor (plugin,
527                           s,
528                           GNUNET_TRANSPORT_SS_DONE);
529   if (NULL != s->msg_tk)
530   {
531     GNUNET_SERVER_mst_destroy (s->msg_tk);
532     s->msg_tk = NULL;
533   }
534   GNUNET_HELLO_address_free (s->address);
535   GNUNET_free (s->url);
536   GNUNET_free (s);
537 }
538
539
540 /**
541  * Increment session timeout due to activity for session @a s.
542  *
543  * @param s the session
544  */
545 static void
546 client_reschedule_session_timeout (struct Session *s)
547 {
548   GNUNET_assert (NULL != s->timeout_task);
549   s->timeout = GNUNET_TIME_relative_to_absolute (GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT);
550 }
551
552
553 /**
554  * Task performing curl operations
555  *
556  * @param cls plugin as closure
557  * @param tc gnunet scheduler task context
558  */
559 static void
560 client_run (void *cls,
561             const struct GNUNET_SCHEDULER_TaskContext *tc);
562
563
564 /**
565  * Function setting up file descriptors and scheduling task to run
566  *
567  * @param plugin the plugin as closure
568  * @param now schedule task in 1ms, regardless of what curl may say
569  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
570  */
571 static int
572 client_schedule (struct HTTP_Client_Plugin *plugin,
573                  int now)
574 {
575   fd_set rs;
576   fd_set ws;
577   fd_set es;
578   int max;
579   struct GNUNET_NETWORK_FDSet *grs;
580   struct GNUNET_NETWORK_FDSet *gws;
581   long to;
582   CURLMcode mret;
583   struct GNUNET_TIME_Relative timeout;
584
585   /* Cancel previous scheduled task */
586   if (plugin->client_perform_task != NULL)
587   {
588     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
589     plugin->client_perform_task = NULL;
590   }
591   max = -1;
592   FD_ZERO (&rs);
593   FD_ZERO (&ws);
594   FD_ZERO (&es);
595   mret = curl_multi_fdset (plugin->curl_multi_handle, &rs, &ws, &es, &max);
596   if (mret != CURLM_OK)
597   {
598     LOG (GNUNET_ERROR_TYPE_ERROR,
599          _("%s failed at %s:%d: `%s'\n"),
600          "curl_multi_fdset",
601          __FILE__,
602          __LINE__,
603          curl_multi_strerror (mret));
604     return GNUNET_SYSERR;
605   }
606   mret = curl_multi_timeout (plugin->curl_multi_handle, &to);
607   if (-1 == to)
608     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 1);
609   else
610     timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to);
611   if (now == GNUNET_YES)
612     timeout = GNUNET_TIME_UNIT_MILLISECONDS;
613
614   if (CURLM_OK != mret)
615   {
616     LOG (GNUNET_ERROR_TYPE_ERROR,
617                 _("%s failed at %s:%d: `%s'\n"),
618                 "curl_multi_timeout", __FILE__, __LINE__,
619                 curl_multi_strerror (mret));
620     return GNUNET_SYSERR;
621   }
622
623   grs = GNUNET_NETWORK_fdset_create ();
624   gws = GNUNET_NETWORK_fdset_create ();
625   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
626   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
627
628   /* Schedule task to run when select is ready to read or write */
629   plugin->client_perform_task =
630       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
631                                    timeout, grs, gws,
632                                    &client_run, plugin);
633   GNUNET_NETWORK_fdset_destroy (gws);
634   GNUNET_NETWORK_fdset_destroy (grs);
635   return GNUNET_OK;
636 }
637
638
639 #if VERBOSE_CURL
640 /**
641  * Loggging function
642  *
643  * @param curl the curl easy handle
644  * @param type message type
645  * @param data data to log, NOT a 0-terminated string
646  * @param size data length
647  * @param cls the closure
648  * @return always 0
649  */
650 static int
651 client_log (CURL *curl,
652             curl_infotype type,
653             const char *data,
654             size_t size,
655             void *cls)
656 {
657   struct RequestHandle *ch = cls;
658   const char *ttype = "UNSPECIFIED";
659   char text[size + 2];
660
661   if (! ((CURLINFO_TEXT == type) ||
662          (CURLINFO_HEADER_IN == type) ||
663          (CURLINFO_HEADER_OUT == type)))
664     return 0;
665   switch (type)
666   {
667   case CURLINFO_TEXT:
668     ttype = "TEXT";
669     break;
670   case CURLINFO_HEADER_IN:
671     ttype = "HEADER_IN";
672     break;
673   case CURLINFO_HEADER_OUT:
674     ttype = "HEADER_OUT";
675     /* Overhead*/
676     GNUNET_assert (NULL != ch);
677     GNUNET_assert (NULL != ch->easyhandle);
678     GNUNET_assert (NULL != ch->s);
679     ch->s->overhead += size;
680     break;
681   default:
682     ttype = "UNSPECIFIED";
683     break;
684   }
685   memcpy (text, data, size);
686   if (text[size - 1] == '\n')
687   {
688     text[size] = '\0';
689   }
690   else
691   {
692     text[size] = '\n';
693     text[size + 1] = '\0';
694   }
695   LOG (GNUNET_ERROR_TYPE_DEBUG,
696        "Request %p %s: %s",
697        ch->easyhandle,
698        ttype,
699        text);
700   return 0;
701 }
702 #endif
703
704 /**
705  * Connect GET request
706  *
707  * @param s the session to connect
708  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
709  */
710 static int
711 client_connect_get (struct Session *s);
712
713
714 /**
715  * Connect a HTTP put request
716  *
717  * @param s the session to connect
718  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for success
719  */
720 static int
721 client_connect_put (struct Session *s);
722
723
724 /**
725  * Function that can be used by the transport service to transmit
726  * a message using the plugin.   Note that in the case of a
727  * peer disconnecting, the continuation MUST be called
728  * prior to the disconnect notification itself.  This function
729  * will be called with this peer's HELLO message to initiate
730  * a fresh connection to another peer.
731  *
732  * @param cls closure
733  * @param s which session must be used
734  * @param msgbuf the message to transmit
735  * @param msgbuf_size number of bytes in @a msgbuf
736  * @param priority how important is the message (most plugins will
737  *                 ignore message priority and just FIFO)
738  * @param to how long to wait at most for the transmission (does not
739  *                require plugins to discard the message after the timeout,
740  *                just advisory for the desired delay; most plugins will ignore
741  *                this as well)
742  * @param cont continuation to call once the message has
743  *        been transmitted (or if the transport is ready
744  *        for the next transmission call; or if the
745  *        peer disconnected...); can be NULL
746  * @param cont_cls closure for @a cont
747  * @return number of bytes used (on the physical network, with overheads);
748  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
749  *         and does NOT mean that the message was not transmitted (DV)
750  */
751 static ssize_t
752 http_client_plugin_send (void *cls,
753                          struct Session *s,
754                          const char *msgbuf,
755                          size_t msgbuf_size,
756                          unsigned int priority,
757                          struct GNUNET_TIME_Relative to,
758                          GNUNET_TRANSPORT_TransmitContinuation cont,
759                          void *cont_cls)
760 {
761   struct HTTP_Client_Plugin *plugin = cls;
762   struct HTTP_Message *msg;
763   char *stat_txt;
764
765   LOG (GNUNET_ERROR_TYPE_DEBUG,
766        "Session %p/request %p: Sending message with %u to peer `%s' \n",
767        s,
768        s->put.easyhandle,
769        msgbuf_size,
770        GNUNET_i2s (&s->address->peer));
771
772   /* create new message and schedule */
773   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
774   msg->size = msgbuf_size;
775   msg->buf = (char *) &msg[1];
776   msg->transmit_cont = cont;
777   msg->transmit_cont_cls = cont_cls;
778   memcpy (msg->buf,
779           msgbuf,
780           msgbuf_size);
781   GNUNET_CONTAINER_DLL_insert_tail (s->msg_head,
782                                     s->msg_tail,
783                                     msg);
784   s->msgs_in_queue++;
785   s->bytes_in_queue += msg->size;
786
787   GNUNET_asprintf (&stat_txt,
788                    "# bytes currently in %s_client buffers",
789                    plugin->protocol);
790   GNUNET_STATISTICS_update (plugin->env->stats,
791                             stat_txt, msgbuf_size, GNUNET_NO);
792   GNUNET_free (stat_txt);
793   notify_session_monitor (plugin,
794                           s,
795                           GNUNET_TRANSPORT_SS_UPDATE);
796   if (H_TMP_DISCONNECTING == s->put.state)
797   {
798     /* PUT request is currently getting disconnected */
799     s->put.state = H_TMP_RECONNECT_REQUIRED;
800     LOG (GNUNET_ERROR_TYPE_DEBUG,
801          "Session %p/request %p: currently disconnecting, reconnecting immediately\n",
802          s,
803          s->put.easyhandle);
804     return msgbuf_size;
805   }
806   if (H_PAUSED == s->put.state)
807   {
808     /* PUT request was paused, unpause */
809     GNUNET_assert (s->put_disconnect_task != NULL);
810     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
811     s->put_disconnect_task = NULL;
812     LOG (GNUNET_ERROR_TYPE_DEBUG,
813          "Session %p/request %p: unpausing request\n",
814          s, s->put.easyhandle);
815     s->put.state = H_CONNECTED;
816     if (NULL != s->put.easyhandle)
817       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
818   }
819   else if (H_TMP_DISCONNECTED == s->put.state)
820   {
821     /* PUT request was disconnected, reconnect */
822     LOG (GNUNET_ERROR_TYPE_DEBUG, "Session %p: Reconnecting PUT request\n", s);
823     GNUNET_break (NULL == s->put.easyhandle);
824     if (GNUNET_SYSERR == client_connect_put (s))
825     {
826       /* Could not reconnect */
827       http_client_plugin_session_disconnect (plugin, s);
828       return GNUNET_SYSERR;
829     }
830   }
831   client_schedule (s->plugin, GNUNET_YES);
832   return msgbuf_size;
833 }
834
835
836 /**
837  * Disconnect a session
838  *
839  * @param cls the `struct HTTP_Client_Plugin *`
840  * @param s session
841  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
842  */
843 static int
844 http_client_plugin_session_disconnect (void *cls,
845                                        struct Session *s)
846 {
847   struct HTTP_Client_Plugin *plugin = cls;
848
849   LOG (GNUNET_ERROR_TYPE_DEBUG,
850        "Session %p: notifying transport about ending session\n",
851        s);
852   plugin->env->session_end (plugin->env->cls,
853                             s->address,
854                             s);
855   client_delete_session (s);
856
857   /* Re-schedule since handles have changed */
858   if (NULL != plugin->client_perform_task)
859   {
860     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
861     plugin->client_perform_task = NULL;
862   }
863   client_schedule (plugin, GNUNET_YES);
864
865   return GNUNET_OK;
866 }
867
868
869 /**
870  * Function that is called to get the keepalive factor.
871  * #GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT is divided by this number to
872  * calculate the interval between keepalive packets.
873  *
874  * @param cls closure with the `struct Plugin`
875  * @return keepalive factor
876  */
877 static unsigned int
878 http_client_query_keepalive_factor (void *cls)
879 {
880   return 3;
881 }
882
883
884 /**
885  * Callback to destroys all sessions on exit.
886  *
887  * @param cls the `struct HTTP_Client_Plugin *`
888  * @param peer identity of the peer
889  * @param value the `struct Session *`
890  * @return #GNUNET_OK (continue iterating)
891  */
892 static int
893 destroy_session_cb (void *cls,
894                     const struct GNUNET_PeerIdentity *peer,
895                     void *value)
896 {
897   struct HTTP_Client_Plugin *plugin = cls;
898   struct Session *session = value;
899
900   http_client_plugin_session_disconnect (plugin, session);
901   return GNUNET_OK;
902 }
903
904
905 /**
906  * Function that can be used to force the plugin to disconnect
907  * from the given peer and cancel all previous transmissions
908  * (and their continuationc).
909  *
910  * @param cls closure
911  * @param target peer from which to disconnect
912  */
913 static void
914 http_client_plugin_peer_disconnect (void *cls,
915                                     const struct GNUNET_PeerIdentity *target)
916 {
917   struct HTTP_Client_Plugin *plugin = cls;
918
919   LOG (GNUNET_ERROR_TYPE_DEBUG,
920        "Transport tells me to disconnect `%s'\n",
921        GNUNET_i2s (target));
922   GNUNET_CONTAINER_multipeermap_get_multiple (plugin->sessions,
923                                               target,
924                                               &destroy_session_cb,
925                                               plugin);
926 }
927
928
929 /**
930  * Closure for #session_lookup_client_by_address().
931  */
932 struct SessionClientCtx
933 {
934   /**
935    * Address we are looking for.
936    */
937   const struct GNUNET_HELLO_Address *address;
938
939   /**
940    * Session that was found.
941    */
942   struct Session *ret;
943 };
944
945
946 /**
947  * Locate the seession object for a given address.
948  *
949  * @param cls the `struct SessionClientCtx *`
950  * @param key peer identity
951  * @param value the `struct Session` to check
952  * @return #GNUNET_NO if found, #GNUNET_OK if not
953  */
954 static int
955 session_lookup_client_by_address (void *cls,
956                                   const struct GNUNET_PeerIdentity *key,
957                                   void *value)
958 {
959   struct SessionClientCtx *sc_ctx = cls;
960   struct Session *s = value;
961
962   if (0 == GNUNET_HELLO_address_cmp (sc_ctx->address,
963                                      s->address))
964   {
965     sc_ctx->ret = s;
966     return GNUNET_NO;
967   }
968   return GNUNET_YES;
969 }
970
971
972 /**
973  * Check if a sessions exists for an specific address
974  *
975  * @param plugin the plugin
976  * @param address the address
977  * @return the session or NULL
978  */
979 static struct Session *
980 client_lookup_session (struct HTTP_Client_Plugin *plugin,
981                        const struct GNUNET_HELLO_Address *address)
982 {
983   struct SessionClientCtx sc_ctx;
984
985   sc_ctx.address = address;
986   sc_ctx.ret = NULL;
987   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
988                                          &session_lookup_client_by_address,
989                                          &sc_ctx);
990   return sc_ctx.ret;
991 }
992
993
994 /**
995  * When we have nothing to transmit, we pause the HTTP PUT
996  * after a while (so that gnurl stops asking).  This task
997  * is the delayed task that actually disconnects the PUT.
998  *
999  * @param cls the `struct Session *` with the put
1000  * @param tc scheduler context
1001  */
1002 static void
1003 client_put_disconnect (void *cls,
1004                        const struct GNUNET_SCHEDULER_TaskContext *tc)
1005 {
1006   struct Session *s = cls;
1007
1008   s->put_disconnect_task = NULL;
1009   LOG (GNUNET_ERROR_TYPE_DEBUG,
1010        "Session %p/request %p: will be disconnected due to no activity\n",
1011        s, s->put.easyhandle);
1012   s->put.state = H_TMP_DISCONNECTING;
1013   if (NULL != s->put.easyhandle)
1014     curl_easy_pause (s->put.easyhandle,
1015                      CURLPAUSE_CONT);
1016   client_schedule (s->plugin, GNUNET_YES);
1017 }
1018
1019
1020 /**
1021  * Callback method used with libcurl
1022  * Method is called when libcurl needs to read data during sending
1023  *
1024  * @param stream pointer where to write data
1025  * @param size size of an individual element
1026  * @param nmemb count of elements that can be written to the buffer
1027  * @param cls our `struct Session`
1028  * @return bytes written to stream, returning 0 will terminate request!
1029  */
1030 static size_t
1031 client_send_cb (void *stream,
1032                 size_t size,
1033                 size_t nmemb,
1034                 void *cls)
1035 {
1036   struct Session *s = cls;
1037   struct HTTP_Client_Plugin *plugin = s->plugin;
1038   struct HTTP_Message *msg = s->msg_head;
1039   size_t len;
1040   char *stat_txt;
1041
1042   if (H_TMP_DISCONNECTING == s->put.state)
1043   {
1044     LOG (GNUNET_ERROR_TYPE_DEBUG,
1045          "Session %p/request %p: disconnect due to inactivity\n",
1046          s, s->put.easyhandle);
1047     return 0;
1048   }
1049
1050   if (NULL == msg)
1051   {
1052     if (GNUNET_YES == plugin->emulate_xhr)
1053     {
1054       LOG (GNUNET_ERROR_TYPE_DEBUG,
1055            "Session %p/request %p: PUT request finished\n",
1056            s,
1057            s->put.easyhandle);
1058       s->put.state = H_TMP_DISCONNECTING;
1059       return 0;
1060     }
1061
1062     /* We have nothing to send, so pause PUT request */
1063     LOG (GNUNET_ERROR_TYPE_DEBUG,
1064          "Session %p/request %p: nothing to send, suspending\n",
1065          s,
1066          s->put.easyhandle);
1067     s->put_disconnect_task = GNUNET_SCHEDULER_add_delayed (PUT_DISCONNECT_TIMEOUT,
1068         &client_put_disconnect, s);
1069     s->put.state = H_PAUSED;
1070     return CURL_READFUNC_PAUSE;
1071   }
1072   /* data to send */
1073   GNUNET_assert (msg->pos < msg->size);
1074   /* calculate how much fits in buffer */
1075   len = GNUNET_MIN (msg->size - msg->pos,
1076                     size * nmemb);
1077   memcpy (stream, &msg->buf[msg->pos], len);
1078   msg->pos += len;
1079   if (msg->pos == msg->size)
1080   {
1081     LOG (GNUNET_ERROR_TYPE_DEBUG,
1082          "Session %p/request %p: sent message with %u bytes sent, removing message from queue\n",
1083          s,
1084          s->put.easyhandle,
1085          msg->size,
1086          msg->pos);
1087     /* Calling transmit continuation  */
1088     GNUNET_CONTAINER_DLL_remove (s->msg_head,
1089                                  s->msg_tail,
1090                                  msg);
1091     GNUNET_assert (0 < s->msgs_in_queue);
1092     s->msgs_in_queue--;
1093     GNUNET_assert (msg->size <= s->bytes_in_queue);
1094     s->bytes_in_queue -= msg->size;
1095     if (NULL != msg->transmit_cont)
1096       msg->transmit_cont (msg->transmit_cont_cls,
1097                           &s->address->peer,
1098                           GNUNET_OK,
1099                           msg->size,
1100                           msg->size + s->overhead);
1101     s->overhead = 0;
1102     GNUNET_free (msg);
1103   }
1104   notify_session_monitor (plugin,
1105                           s,
1106                           GNUNET_TRANSPORT_SS_UPDATE);
1107   GNUNET_asprintf (&stat_txt,
1108                    "# bytes currently in %s_client buffers",
1109                    plugin->protocol);
1110   GNUNET_STATISTICS_update (plugin->env->stats,
1111                             stat_txt,
1112                             - len,
1113                             GNUNET_NO);
1114   GNUNET_free (stat_txt);
1115   GNUNET_asprintf (&stat_txt,
1116                    "# bytes transmitted via %s_client",
1117                    plugin->protocol);
1118   GNUNET_STATISTICS_update (plugin->env->stats,
1119                             stat_txt,
1120                             len,
1121                             GNUNET_NO);
1122   GNUNET_free (stat_txt);
1123   return len;
1124 }
1125
1126
1127 /**
1128  * Wake up a curl handle which was suspended
1129  *
1130  * @param cls the session
1131  * @param tc task context
1132  */
1133 static void
1134 client_wake_up (void *cls,
1135                 const struct GNUNET_SCHEDULER_TaskContext *tc)
1136 {
1137   struct Session *s = cls;
1138
1139   s->recv_wakeup_task = NULL;
1140   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1141     return;
1142   LOG (GNUNET_ERROR_TYPE_DEBUG,
1143        "Session %p/request %p: Waking up GET handle\n",
1144        s, s->get.easyhandle);
1145   if (H_PAUSED == s->put.state)
1146   {
1147     /* PUT request was paused, unpause */
1148     GNUNET_assert (s->put_disconnect_task != NULL);
1149     GNUNET_SCHEDULER_cancel (s->put_disconnect_task);
1150     s->put_disconnect_task = NULL;
1151     s->put.state = H_CONNECTED;
1152     if (NULL != s->put.easyhandle)
1153       curl_easy_pause (s->put.easyhandle, CURLPAUSE_CONT);
1154   }
1155   if (NULL != s->get.easyhandle)
1156     curl_easy_pause (s->get.easyhandle, CURLPAUSE_CONT);
1157 }
1158
1159
1160 /**
1161  * Callback for message stream tokenizer
1162  *
1163  * @param cls the session
1164  * @param client not used
1165  * @param message the message received
1166  * @return always #GNUNET_OK
1167  */
1168 static int
1169 client_receive_mst_cb (void *cls,
1170                        void *client,
1171                        const struct GNUNET_MessageHeader *message)
1172 {
1173   struct Session *s = cls;
1174   struct HTTP_Client_Plugin *plugin;
1175   struct GNUNET_TIME_Relative delay;
1176   char *stat_txt;
1177
1178   plugin = s->plugin;
1179   delay = s->plugin->env->receive (plugin->env->cls,
1180                                    s->address,
1181                                    s,
1182                                    message);
1183   GNUNET_asprintf (&stat_txt,
1184                    "# bytes received via %s_client",
1185                    plugin->protocol);
1186   GNUNET_STATISTICS_update (plugin->env->stats,
1187                             stat_txt,
1188                             ntohs (message->size),
1189                             GNUNET_NO);
1190   GNUNET_free (stat_txt);
1191
1192   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
1193   if (GNUNET_TIME_absolute_get ().abs_value_us < s->next_receive.abs_value_us)
1194   {
1195     LOG (GNUNET_ERROR_TYPE_DEBUG,
1196          "Client: peer `%s' address `%s' next read delayed for %s\n",
1197          GNUNET_i2s (&s->address->peer),
1198          http_common_plugin_address_to_string (s->plugin->protocol,
1199                                                s->address->address,
1200                                                s->address->address_length),
1201          GNUNET_STRINGS_relative_time_to_string (delay,
1202                                                  GNUNET_YES));
1203   }
1204   client_reschedule_session_timeout (s);
1205   return GNUNET_OK;
1206 }
1207
1208
1209 /**
1210  * Callback method used with libcurl when data for a PUT request are
1211  * received.  We do not expect data here, so we just discard it.
1212  *
1213  * @param stream pointer where to write data
1214  * @param size size of an individual element
1215  * @param nmemb count of elements that can be written to the buffer
1216  * @param cls destination pointer, passed to the libcurl handle
1217  * @return bytes read from stream
1218  */
1219 static size_t
1220 client_receive_put (void *stream,
1221                     size_t size,
1222                     size_t nmemb,
1223                     void *cls)
1224 {
1225   return size * nmemb;
1226 }
1227
1228
1229 /**
1230  * Callback method used with libcurl when data for a GET request are
1231  * received. Forward to MST
1232  *
1233  * @param stream pointer where to write data
1234  * @param size size of an individual element
1235  * @param nmemb count of elements that can be written to the buffer
1236  * @param cls destination pointer, passed to the libcurl handle
1237  * @return bytes read from stream
1238  */
1239 static size_t
1240 client_receive (void *stream,
1241                 size_t size,
1242                 size_t nmemb,
1243                 void *cls)
1244 {
1245   struct Session *s = cls;
1246   struct GNUNET_TIME_Absolute now;
1247   size_t len = size * nmemb;
1248
1249   LOG (GNUNET_ERROR_TYPE_DEBUG,
1250        "Session %p / request %p: Received %u bytes from peer `%s'\n",
1251        s,
1252        s->get.easyhandle,
1253        len,
1254        GNUNET_i2s (&s->address->peer));
1255   now = GNUNET_TIME_absolute_get ();
1256   if (now.abs_value_us < s->next_receive.abs_value_us)
1257   {
1258     struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
1259     struct GNUNET_TIME_Relative delta
1260       = GNUNET_TIME_absolute_get_difference (now, s->next_receive);
1261
1262     LOG (GNUNET_ERROR_TYPE_DEBUG,
1263          "Session %p / request %p: No inbound bandwidth available! Next read was delayed for %s\n",
1264          s,
1265          s->get.easyhandle,
1266          GNUNET_STRINGS_relative_time_to_string (delta,
1267                                                  GNUNET_YES));
1268     if (s->recv_wakeup_task != NULL)
1269     {
1270       GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
1271       s->recv_wakeup_task = NULL;
1272     }
1273     s->recv_wakeup_task
1274       = GNUNET_SCHEDULER_add_delayed (delta,
1275                                       &client_wake_up,
1276                                       s);
1277     return CURL_WRITEFUNC_PAUSE;
1278   }
1279   if (NULL == s->msg_tk)
1280     s->msg_tk = GNUNET_SERVER_mst_create (&client_receive_mst_cb,
1281                                           s);
1282   GNUNET_SERVER_mst_receive (s->msg_tk,
1283                              s,
1284                              stream,
1285                              len,
1286                              GNUNET_NO,
1287                              GNUNET_NO);
1288   return len;
1289 }
1290
1291
1292 /**
1293  * Task performing curl operations
1294  *
1295  * @param cls plugin as closure
1296  * @param tc scheduler task context
1297  */
1298 static void
1299 client_run (void *cls,
1300             const struct GNUNET_SCHEDULER_TaskContext *tc)
1301 {
1302   struct HTTP_Client_Plugin *plugin = cls;
1303   int running;
1304   long http_statuscode;
1305   CURLMcode mret;
1306   CURLMsg *msg;
1307   int put_request; /* GNUNET_YES if easy handle is put, GNUNET_NO for get */
1308   int msgs_left;
1309
1310   plugin->client_perform_task = NULL;
1311   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1312     return;
1313
1314   /* While data are available or timeouts occured */
1315   do
1316   {
1317     running = 0;
1318     /* Perform operations for all handles */
1319     mret = curl_multi_perform (plugin->curl_multi_handle, &running);
1320
1321     /* Get additional information for all handles */
1322     while (NULL != (msg = curl_multi_info_read (plugin->curl_multi_handle, &msgs_left)))
1323     {
1324       CURL *easy_h = msg->easy_handle;
1325       struct Session *s = NULL;
1326       char *d = NULL; /* curl requires 'd' to be a 'char *' */
1327
1328       GNUNET_assert (NULL != easy_h);
1329
1330       /* Obtain session from easy handle */
1331       GNUNET_assert (CURLE_OK == curl_easy_getinfo (easy_h, CURLINFO_PRIVATE, &d));
1332       s = (struct Session *) d;
1333       GNUNET_assert (NULL != s);
1334
1335       if (msg->msg != CURLMSG_DONE)
1336         continue; /* This should not happen */
1337
1338       /* Get HTTP response code */
1339       GNUNET_break (CURLE_OK == curl_easy_getinfo (easy_h,
1340           CURLINFO_RESPONSE_CODE, &http_statuscode));
1341
1342       if (easy_h == s->put.easyhandle)
1343         put_request = GNUNET_YES;
1344       else
1345         put_request = GNUNET_NO;
1346
1347       /* Log status of terminated request */
1348       if  ((0 != msg->data.result) || (http_statuscode != 200))
1349         LOG (GNUNET_ERROR_TYPE_DEBUG,
1350              "Session %p/request %p: %s request to `%s' ended with status %i reason %i: `%s'\n",
1351              s, msg->easy_handle,
1352              (GNUNET_YES == put_request) ? "PUT" : "GET",
1353              GNUNET_i2s (&s->address->peer),
1354              http_statuscode,
1355              msg->data.result,
1356              curl_easy_strerror (msg->data.result));
1357       else
1358         LOG (GNUNET_ERROR_TYPE_DEBUG,
1359              "Session %p/request %p: %s request to `%s' ended normal\n",
1360              s, msg->easy_handle,
1361              (GNUNET_YES == put_request) ? "PUT" : "GET",
1362              GNUNET_i2s (&s->address->peer));
1363
1364       /* Remove easy handle from multi handle */
1365       curl_multi_remove_handle (plugin->curl_multi_handle, easy_h);
1366
1367       /* Clean up easy handle */
1368       curl_easy_cleanup (easy_h);
1369
1370       /* Remove information */
1371       GNUNET_assert (plugin->cur_requests > 0);
1372       plugin->cur_requests--;
1373       LOG  (GNUNET_ERROR_TYPE_INFO,
1374           "%s request to %s done, number of requests decreased to %u\n",
1375           (GNUNET_YES == put_request) ? "PUT" : "GET",
1376           s->url,
1377           plugin->cur_requests);
1378
1379       if (GNUNET_YES == put_request)
1380       {
1381         /* Clean up a PUT request */
1382         s->put.easyhandle = NULL;
1383         s->put.s = NULL;
1384
1385         switch (s->put.state) {
1386           case H_NOT_CONNECTED:
1387           case H_DISCONNECTED:
1388           case H_TMP_DISCONNECTED:
1389             /* This must not happen */
1390             GNUNET_break (0);
1391             break;
1392           case H_TMP_RECONNECT_REQUIRED:
1393             /* Transport called send while disconnect in progess, reconnect */
1394             if (GNUNET_SYSERR == client_connect_put (s))
1395             {
1396               /* Reconnect failed, disconnect session */
1397               http_client_plugin_session_disconnect (plugin, s);
1398             }
1399             break;
1400           case H_TMP_DISCONNECTING:
1401             /* PUT gets temporarily disconnected */
1402             s->put.state = H_TMP_DISCONNECTED;
1403             break;
1404           case H_PAUSED:
1405           case H_CONNECTED:
1406             /* PUT gets permanently disconnected */
1407             s->put.state = H_DISCONNECTED;
1408             http_client_plugin_session_disconnect (plugin, s);
1409             break;
1410           default:
1411             GNUNET_break (0);
1412             break;
1413         }
1414       }
1415       else if (GNUNET_NO == put_request)
1416       {
1417         /* Clean up a GET request */
1418         s->get.easyhandle = NULL;
1419         s->get.s = NULL;
1420
1421         /* If we are emulating an XHR client we need to make another GET
1422          * request.
1423          */
1424         if (GNUNET_YES == plugin->emulate_xhr)
1425         {
1426           if (GNUNET_SYSERR == client_connect_get (s))
1427             http_client_plugin_session_disconnect (plugin, s);
1428         }
1429         else
1430         {
1431           /* GET request was terminated, so disconnect session */
1432           http_client_plugin_session_disconnect (plugin, s);
1433         }
1434       }
1435       else
1436         GNUNET_break (0); /* Must not happen */
1437
1438       GNUNET_STATISTICS_set (plugin->env->stats,
1439                              HTTP_STAT_STR_CONNECTIONS,
1440                              plugin->cur_requests,
1441                              GNUNET_NO);
1442     }
1443   }
1444   while (mret == CURLM_CALL_MULTI_PERFORM);
1445   client_schedule (plugin, GNUNET_NO);
1446 }
1447
1448
1449 #ifdef TCP_STEALTH
1450 /**
1451  * Open TCP socket with TCP STEALTH enabled.
1452  *
1453  * @param clientp our `struct Session *`
1454  * @param purpose why does curl want to open a socket
1455  * @param address what kind of socket does curl want to have opened?
1456  * @return opened socket
1457  */
1458 static curl_socket_t
1459 open_tcp_stealth_socket_cb (void *clientp,
1460                             curlsocktype purpose,
1461                             struct curl_sockaddr *address)
1462 {
1463   struct Session *s = clientp;
1464   int ret;
1465
1466   switch (purpose)
1467   {
1468   case CURLSOCKTYPE_IPCXN:
1469     ret = socket (address->family,
1470                   address->socktype,
1471                   address->protocol);
1472     if (-1 == ret)
1473       return CURL_SOCKET_BAD;
1474     if ( ( (SOCK_STREAM != address->socktype) ||
1475            ( (0 != address->protocol) &&
1476              (IPPROTO_TCP != address->protocol))) )
1477       return (curl_socket_t) ret;
1478     if ( (0 != setsockopt (ret,
1479                            IPPROTO_TCP,
1480                            TCP_STEALTH,
1481                            &s->address->peer,
1482                            sizeof (struct GNUNET_PeerIdentity))) )
1483     {
1484       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1485                   _("TCP_STEALTH not supported on this platform.\n"));
1486       (void) close (ret);
1487       return CURL_SOCKET_BAD;
1488     }
1489     return (curl_socket_t) ret;
1490   case CURLSOCKTYPE_ACCEPT:
1491     GNUNET_break (0);
1492     return CURL_SOCKET_BAD;
1493     break;
1494   case CURLSOCKTYPE_LAST:
1495     GNUNET_break (0);
1496     return CURL_SOCKET_BAD;
1497   default:
1498     GNUNET_break (0);
1499     return CURL_SOCKET_BAD;
1500   }
1501 }
1502 #endif
1503
1504
1505 /**
1506  * Connect GET request for a session
1507  *
1508  * @param s the session to connect
1509  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1510  */
1511 static int
1512 client_connect_get (struct Session *s)
1513 {
1514   CURLMcode mret;
1515   struct HttpAddress *ha;
1516   uint32_t options;
1517
1518   ha = (struct HttpAddress *) s->address->address;
1519   options = ntohl (ha->options);
1520   /* create get request */
1521   s->get.easyhandle = curl_easy_init ();
1522   s->get.s = s;
1523   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1524   {
1525 #ifdef TCP_STEALTH
1526     curl_easy_setopt (s->get.easyhandle,
1527                       CURLOPT_OPENSOCKETFUNCTION,
1528                       &open_tcp_stealth_socket_cb);
1529     curl_easy_setopt (s->get.easyhandle,
1530                       CURLOPT_OPENSOCKETDATA,
1531                       s);
1532 #else
1533     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1534                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1535     curl_easy_cleanup (s->get.easyhandle);
1536     s->get.easyhandle = NULL;
1537     s->get.s = NULL;
1538     return GNUNET_SYSERR;
1539 #endif
1540   }
1541
1542 #if VERBOSE_CURL
1543   curl_easy_setopt (s->get.easyhandle,
1544                     CURLOPT_VERBOSE,
1545                     1L);
1546   curl_easy_setopt (s->get.easyhandle,
1547                     CURLOPT_DEBUGFUNCTION,
1548                     &client_log);
1549   curl_easy_setopt (s->get.easyhandle,
1550                     CURLOPT_DEBUGDATA,
1551                     &s->get);
1552 #endif
1553 #if BUILD_HTTPS
1554   curl_easy_setopt (s->get.easyhandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
1555   {
1556     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1557         (options & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1558     {
1559       curl_easy_setopt (s->get.easyhandle,
1560                         CURLOPT_SSL_VERIFYPEER, 1L);
1561       curl_easy_setopt (s->get.easyhandle,
1562                         CURLOPT_SSL_VERIFYHOST,
1563                         2L);
1564     }
1565     else
1566     {
1567       curl_easy_setopt (s->get.easyhandle,
1568                         CURLOPT_SSL_VERIFYPEER,
1569                         0L);
1570       curl_easy_setopt (s->get.easyhandle,
1571                         CURLOPT_SSL_VERIFYHOST,
1572                         0L);
1573     }
1574   }
1575   curl_easy_setopt (s->get.easyhandle,
1576                     CURLOPT_PROTOCOLS,
1577                     CURLPROTO_HTTPS);
1578   curl_easy_setopt (s->get.easyhandle,
1579                     CURLOPT_REDIR_PROTOCOLS,
1580                     CURLPROTO_HTTPS);
1581 #else
1582   curl_easy_setopt (s->get.easyhandle,
1583                     CURLOPT_PROTOCOLS,
1584                     CURLPROTO_HTTP);
1585   curl_easy_setopt (s->get.easyhandle,
1586                     CURLOPT_REDIR_PROTOCOLS,
1587                     CURLPROTO_HTTP);
1588 #endif
1589
1590   if (NULL != s->plugin->proxy_hostname)
1591   {
1592     curl_easy_setopt (s->get.easyhandle,
1593                       CURLOPT_PROXY,
1594                       s->plugin->proxy_hostname);
1595     curl_easy_setopt (s->get.easyhandle,
1596                       CURLOPT_PROXYTYPE,
1597                       s->plugin->proxytype);
1598     if (NULL != s->plugin->proxy_username)
1599       curl_easy_setopt (s->get.easyhandle,
1600                         CURLOPT_PROXYUSERNAME,
1601                         s->plugin->proxy_username);
1602     if (NULL != s->plugin->proxy_password)
1603       curl_easy_setopt (s->get.easyhandle,
1604                         CURLOPT_PROXYPASSWORD,
1605                         s->plugin->proxy_password);
1606     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1607       curl_easy_setopt (s->get.easyhandle,
1608                         CURLOPT_HTTPPROXYTUNNEL,
1609                         s->plugin->proxy_use_httpproxytunnel);
1610   }
1611
1612   if (GNUNET_YES == s->plugin->emulate_xhr)
1613   {
1614     char *url;
1615
1616     GNUNET_asprintf (&url,
1617                      "%s,1",
1618                      s->url);
1619     curl_easy_setopt (s->get.easyhandle,
1620                       CURLOPT_URL,
1621                       url);
1622     GNUNET_free(url);
1623   }
1624   else
1625   {
1626     curl_easy_setopt (s->get.easyhandle,
1627                       CURLOPT_URL,
1628                       s->url);
1629   }
1630   curl_easy_setopt (s->get.easyhandle,
1631                     CURLOPT_READFUNCTION,
1632                     &client_send_cb);
1633   curl_easy_setopt (s->get.easyhandle,
1634                     CURLOPT_READDATA,
1635                     s);
1636   curl_easy_setopt (s->get.easyhandle,
1637                     CURLOPT_WRITEFUNCTION,
1638                     &client_receive);
1639   curl_easy_setopt (s->get.easyhandle,
1640                     CURLOPT_WRITEDATA,
1641                     s);
1642   /* No timeout by default, timeout done with session timeout */
1643   curl_easy_setopt (s->get.easyhandle,
1644                     CURLOPT_TIMEOUT,
1645                     0L);
1646   curl_easy_setopt (s->get.easyhandle,
1647                     CURLOPT_PRIVATE, s);
1648   curl_easy_setopt (s->get.easyhandle,
1649                     CURLOPT_CONNECTTIMEOUT_MS,
1650                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1651   curl_easy_setopt (s->get.easyhandle, CURLOPT_BUFFERSIZE,
1652                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1653 #if CURL_TCP_NODELAY
1654   curl_easy_setopt (ps->recv_endpoint,
1655                     CURLOPT_TCP_NODELAY,
1656                     1L);
1657 #endif
1658   curl_easy_setopt (s->get.easyhandle,
1659                     CURLOPT_FOLLOWLOCATION,
1660                     0L);
1661
1662   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1663                                 s->get.easyhandle);
1664   if (CURLM_OK != mret)
1665   {
1666     LOG (GNUNET_ERROR_TYPE_ERROR,
1667          "Session %p : Failed to add GET handle to multihandle: `%s'\n",
1668          s,
1669          curl_multi_strerror (mret));
1670     curl_easy_cleanup (s->get.easyhandle);
1671     s->get.easyhandle = NULL;
1672     s->get.s = NULL;
1673     GNUNET_break (0);
1674     return GNUNET_SYSERR;
1675   }
1676   s->plugin->cur_requests++;
1677   LOG (GNUNET_ERROR_TYPE_INFO,
1678        "GET request `%s' established, number of requests increased to %u\n",
1679        s->url,
1680        s->plugin->cur_requests);
1681   return GNUNET_OK;
1682 }
1683
1684
1685 /**
1686  * Connect a HTTP put request
1687  *
1688  * @param s the session to connect
1689  * @return #GNUNET_SYSERR for hard failure, #GNUNET_OK for ok
1690  */
1691 static int
1692 client_connect_put (struct Session *s)
1693 {
1694   CURLMcode mret;
1695   struct HttpAddress *ha;
1696   uint32_t options;
1697
1698   ha = (struct HttpAddress *) s->address->address;
1699   options = ntohl (ha->options);
1700   /* create put request */
1701   LOG (GNUNET_ERROR_TYPE_DEBUG,
1702        "Session %p: Init PUT handle\n",
1703        s);
1704   s->put.easyhandle = curl_easy_init ();
1705   s->put.s = s;
1706 #if VERBOSE_CURL
1707   curl_easy_setopt (s->put.easyhandle,
1708                     CURLOPT_VERBOSE,
1709                     1L);
1710   curl_easy_setopt (s->put.easyhandle,
1711                     CURLOPT_DEBUGFUNCTION,
1712                     &client_log);
1713   curl_easy_setopt (s->put.easyhandle,
1714                     CURLOPT_DEBUGDATA,
1715                     &s->put);
1716 #endif
1717   if (0 != (options & HTTP_OPTIONS_TCP_STEALTH))
1718   {
1719 #ifdef TCP_STEALTH
1720     curl_easy_setopt (s->put.easyhandle,
1721                       CURLOPT_OPENSOCKETFUNCTION,
1722                       &open_tcp_stealth_socket_cb);
1723     curl_easy_setopt (s->put.easyhandle,
1724                       CURLOPT_OPENSOCKETDATA,
1725                       s);
1726 #else
1727     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1728                 "Cannot connect, TCP STEALTH needed and not supported by kernel.\n");
1729     curl_easy_cleanup (s->put.easyhandle);
1730     s->put.easyhandle = NULL;
1731     s->put.s = NULL;
1732     s->put.state = H_DISCONNECTED;
1733     return GNUNET_SYSERR;
1734 #endif
1735   }
1736 #if BUILD_HTTPS
1737   curl_easy_setopt (s->put.easyhandle,
1738                     CURLOPT_SSLVERSION,
1739                     CURL_SSLVERSION_TLSv1);
1740   {
1741     struct HttpAddress *ha;
1742     ha = (struct HttpAddress *) s->address->address;
1743
1744     if (HTTP_OPTIONS_VERIFY_CERTIFICATE ==
1745         (ntohl (ha->options) & HTTP_OPTIONS_VERIFY_CERTIFICATE))
1746     {
1747       curl_easy_setopt (s->put.easyhandle,
1748                         CURLOPT_SSL_VERIFYPEER,
1749                         1L);
1750       curl_easy_setopt (s->put.easyhandle,
1751                         CURLOPT_SSL_VERIFYHOST,
1752                         2L);
1753     }
1754     else
1755     {
1756       curl_easy_setopt (s->put.easyhandle,
1757                         CURLOPT_SSL_VERIFYPEER,
1758                         0L);
1759       curl_easy_setopt (s->put.easyhandle,
1760                         CURLOPT_SSL_VERIFYHOST,
1761                         0L);
1762     }
1763   }
1764   curl_easy_setopt (s->put.easyhandle,
1765                     CURLOPT_PROTOCOLS,
1766                     CURLPROTO_HTTPS);
1767   curl_easy_setopt (s->put.easyhandle,
1768                     CURLOPT_REDIR_PROTOCOLS,
1769                     CURLPROTO_HTTPS);
1770 #else
1771   curl_easy_setopt (s->put.easyhandle,
1772                     CURLOPT_PROTOCOLS,
1773                     CURLPROTO_HTTP);
1774   curl_easy_setopt (s->put.easyhandle,
1775                     CURLOPT_REDIR_PROTOCOLS,
1776                     CURLPROTO_HTTP);
1777 #endif
1778   if (NULL != s->plugin->proxy_hostname)
1779   {
1780     curl_easy_setopt (s->put.easyhandle,
1781                       CURLOPT_PROXY,
1782                       s->plugin->proxy_hostname);
1783     curl_easy_setopt (s->put.easyhandle,
1784                       CURLOPT_PROXYTYPE,
1785                       s->plugin->proxytype);
1786     if (NULL != s->plugin->proxy_username)
1787       curl_easy_setopt (s->put.easyhandle,
1788                         CURLOPT_PROXYUSERNAME,
1789                         s->plugin->proxy_username);
1790     if (NULL != s->plugin->proxy_password)
1791       curl_easy_setopt (s->put.easyhandle,
1792                         CURLOPT_PROXYPASSWORD,
1793                         s->plugin->proxy_password);
1794     if (GNUNET_YES == s->plugin->proxy_use_httpproxytunnel)
1795       curl_easy_setopt (s->put.easyhandle,
1796                         CURLOPT_HTTPPROXYTUNNEL,
1797                         s->plugin->proxy_use_httpproxytunnel);
1798   }
1799
1800   curl_easy_setopt (s->put.easyhandle,
1801                     CURLOPT_URL,
1802                     s->url);
1803   curl_easy_setopt (s->put.easyhandle,
1804                     CURLOPT_UPLOAD,
1805                     1L);
1806   curl_easy_setopt (s->put.easyhandle,
1807                     CURLOPT_READFUNCTION,
1808                     &client_send_cb);
1809   curl_easy_setopt (s->put.easyhandle,
1810                     CURLOPT_READDATA,
1811                     s);
1812   curl_easy_setopt (s->put.easyhandle,
1813                     CURLOPT_WRITEFUNCTION,
1814                     &client_receive_put);
1815   curl_easy_setopt (s->put.easyhandle,
1816                     CURLOPT_WRITEDATA,
1817                     s);
1818   /* No timeout by default, timeout done with session timeout */
1819   curl_easy_setopt (s->put.easyhandle,
1820                     CURLOPT_TIMEOUT,
1821                     0L);
1822   curl_easy_setopt (s->put.easyhandle,
1823                     CURLOPT_PRIVATE,
1824                     s);
1825   curl_easy_setopt (s->put.easyhandle,
1826                     CURLOPT_CONNECTTIMEOUT_MS,
1827                     (long) (HTTP_CLIENT_NOT_VALIDATED_TIMEOUT.rel_value_us / 1000LL));
1828   curl_easy_setopt (s->put.easyhandle, CURLOPT_BUFFERSIZE,
1829                     2 * GNUNET_SERVER_MAX_MESSAGE_SIZE);
1830 #if CURL_TCP_NODELAY
1831   curl_easy_setopt (s->put.easyhandle, CURLOPT_TCP_NODELAY, 1);
1832 #endif
1833   mret = curl_multi_add_handle (s->plugin->curl_multi_handle,
1834                                 s->put.easyhandle);
1835   if (CURLM_OK != mret)
1836   {
1837     LOG (GNUNET_ERROR_TYPE_ERROR,
1838          "Session %p : Failed to add PUT handle to multihandle: `%s'\n",
1839          s, curl_multi_strerror (mret));
1840     curl_easy_cleanup (s->put.easyhandle);
1841     s->put.easyhandle = NULL;
1842     s->put.s = NULL;
1843     s->put.state = H_DISCONNECTED;
1844     return GNUNET_SYSERR;
1845   }
1846   s->put.state = H_CONNECTED;
1847   s->plugin->cur_requests++;
1848
1849   LOG  (GNUNET_ERROR_TYPE_INFO,
1850       "PUT request `%s' established, number of requests increased to %u\n",
1851       s->url, s->plugin->cur_requests);
1852
1853   return GNUNET_OK;
1854 }
1855
1856
1857 /**
1858  * Connect both PUT and GET request for a session
1859  *
1860  * @param s the session to connect
1861  * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
1862  */
1863 static int
1864 client_connect (struct Session *s)
1865 {
1866   struct HTTP_Client_Plugin *plugin = s->plugin;
1867   int res = GNUNET_OK;
1868
1869   /* create url */
1870   if (NULL ==
1871       http_common_plugin_address_to_string(plugin->protocol,
1872                                            s->address->address,
1873                                            s->address->address_length))
1874   {
1875     LOG (GNUNET_ERROR_TYPE_DEBUG,
1876          "Invalid address peer `%s'\n",
1877          GNUNET_i2s(&s->address->peer));
1878     return GNUNET_SYSERR;
1879   }
1880
1881   GNUNET_asprintf (&s->url,
1882                    "%s/%s;%u",
1883                    http_common_plugin_address_to_url (NULL,
1884                                                       s->address->address,
1885                                                       s->address->address_length),
1886                    GNUNET_i2s_full (plugin->env->my_identity),
1887                    plugin->last_tag);
1888
1889   plugin->last_tag++;
1890   LOG (GNUNET_ERROR_TYPE_DEBUG,
1891        "Initiating outbound session peer `%s' using address `%s'\n",
1892        GNUNET_i2s (&s->address->peer), s->url);
1893
1894   if (GNUNET_SYSERR == client_connect_get (s))
1895     return GNUNET_SYSERR;
1896   /* If we are emulating an XHR client then delay sending a PUT request until
1897    * there is something to send.
1898    */
1899   if (GNUNET_YES == plugin->emulate_xhr)
1900   {
1901     s->put.state = H_TMP_DISCONNECTED;
1902   }
1903   else if (GNUNET_SYSERR == client_connect_put (s))
1904     return GNUNET_SYSERR;
1905
1906   LOG (GNUNET_ERROR_TYPE_DEBUG,
1907        "Session %p: connected with GET %p and PUT %p\n",
1908        s, s->get.easyhandle,
1909        s->put.easyhandle);
1910   /* Perform connect */
1911   GNUNET_STATISTICS_set (plugin->env->stats,
1912                          HTTP_STAT_STR_CONNECTIONS,
1913                          plugin->cur_requests,
1914                          GNUNET_NO);
1915   /* Re-schedule since handles have changed */
1916   if (NULL != plugin->client_perform_task)
1917   {
1918     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
1919     plugin->client_perform_task = NULL;
1920   }
1921
1922   /* Schedule task to run immediately */
1923   plugin->client_perform_task = GNUNET_SCHEDULER_add_now (client_run,
1924                                                           plugin);
1925   return res;
1926 }
1927
1928
1929 /**
1930  * Function obtain the network type for a session
1931  *
1932  * @param cls closure (`struct Plugin*`)
1933  * @param session the session
1934  * @return the network type
1935  */
1936 static enum GNUNET_ATS_Network_Type
1937 http_client_plugin_get_network (void *cls,
1938                                 struct Session *session)
1939 {
1940   return session->scope;
1941 }
1942
1943
1944 /**
1945  * Session was idle, so disconnect it
1946  *
1947  * @param cls the `struct Session` of the idle session
1948  * @param tc scheduler context
1949  */
1950 static void
1951 client_session_timeout (void *cls,
1952                         const struct GNUNET_SCHEDULER_TaskContext *tc)
1953 {
1954   struct Session *s = cls;
1955   struct GNUNET_TIME_Relative left;
1956
1957   s->timeout_task = NULL;
1958   left = GNUNET_TIME_absolute_get_remaining (s->timeout);
1959   if (0 != left.rel_value_us)
1960   {
1961     /* not actually our turn yet, but let's at least update
1962        the monitor, it may think we're about to die ... */
1963     notify_session_monitor (s->plugin,
1964                             s,
1965                             GNUNET_TRANSPORT_SS_UPDATE);
1966     s->timeout_task = GNUNET_SCHEDULER_add_delayed (left,
1967                                                     &client_session_timeout,
1968                                                     s);
1969     return;
1970   }
1971   LOG (TIMEOUT_LOG,
1972        "Session %p was idle for %s, disconnecting\n",
1973        s,
1974        GNUNET_STRINGS_relative_time_to_string (HTTP_CLIENT_SESSION_TIMEOUT,
1975                                                GNUNET_YES));
1976   GNUNET_assert (GNUNET_OK ==
1977                  http_client_plugin_session_disconnect (s->plugin,
1978                                                  s));
1979 }
1980
1981
1982 /**
1983  * Creates a new outbound session the transport service will use to
1984  * send data to the peer
1985  *
1986  * @param cls the plugin
1987  * @param address the address
1988  * @return the session or NULL of max connections exceeded
1989  */
1990 static struct Session *
1991 http_client_plugin_get_session (void *cls,
1992                                 const struct GNUNET_HELLO_Address *address)
1993 {
1994   struct HTTP_Client_Plugin *plugin = cls;
1995   struct Session *s;
1996   struct sockaddr *sa;
1997   enum GNUNET_ATS_Network_Type net_type;
1998   size_t salen = 0;
1999   int res;
2000
2001   GNUNET_assert (NULL != address->address);
2002
2003   /* find existing session */
2004   s = client_lookup_session (plugin, address);
2005   if (NULL != s)
2006     return s;
2007
2008   /* create a new session */
2009   if (plugin->max_requests <= plugin->cur_requests)
2010   {
2011     LOG (GNUNET_ERROR_TYPE_WARNING,
2012          "Maximum number of requests (%u) reached: "
2013          "cannot connect to peer `%s'\n",
2014          plugin->max_requests,
2015          GNUNET_i2s (&address->peer));
2016     return NULL;
2017   }
2018
2019   /* Determine network location */
2020   net_type = GNUNET_ATS_NET_UNSPECIFIED;
2021   sa = http_common_socket_from_address (address->address,
2022                                         address->address_length,
2023                                         &res);
2024   if (GNUNET_SYSERR == res)
2025     return NULL;
2026   if (GNUNET_YES == res)
2027   {
2028     GNUNET_assert (NULL != sa);
2029     if (AF_INET == sa->sa_family)
2030     {
2031       salen = sizeof (struct sockaddr_in);
2032     }
2033     else if (AF_INET6 == sa->sa_family)
2034     {
2035       salen = sizeof (struct sockaddr_in6);
2036     }
2037     net_type = plugin->env->get_address_type (plugin->env->cls, sa, salen);
2038     GNUNET_free (sa);
2039   }
2040   else if (GNUNET_NO == res)
2041   {
2042     /* Cannot convert to sockaddr -> is external hostname */
2043     net_type = GNUNET_ATS_NET_WAN;
2044   }
2045   if (GNUNET_ATS_NET_UNSPECIFIED == net_type)
2046   {
2047     GNUNET_break (0);
2048     return NULL;
2049   }
2050
2051   s = GNUNET_new (struct Session);
2052   s->plugin = plugin;
2053   s->address = GNUNET_HELLO_address_copy (address);
2054   s->scope = net_type;
2055
2056   s->put.state = H_NOT_CONNECTED;
2057   s->timeout = GNUNET_TIME_relative_to_absolute (HTTP_CLIENT_SESSION_TIMEOUT);
2058   s->timeout_task =  GNUNET_SCHEDULER_add_delayed (HTTP_CLIENT_SESSION_TIMEOUT,
2059                                                    &client_session_timeout,
2060                                                    s);
2061   LOG (GNUNET_ERROR_TYPE_DEBUG,
2062        "Created new session %p for `%s' address `%s''\n",
2063        s,
2064        http_common_plugin_address_to_string (plugin->protocol,
2065                                              s->address->address,
2066                                              s->address->address_length),
2067        GNUNET_i2s (&s->address->peer));
2068
2069   /* add new session */
2070   (void) GNUNET_CONTAINER_multipeermap_put (plugin->sessions,
2071                                             &s->address->peer,
2072                                             s,
2073                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
2074   /* initiate new connection */
2075   if (GNUNET_SYSERR == client_connect (s))
2076   {
2077     LOG (GNUNET_ERROR_TYPE_ERROR,
2078          "Cannot connect to peer `%s' address `%s''\n",
2079          http_common_plugin_address_to_string (plugin->protocol,
2080              s->address->address, s->address->address_length),
2081              GNUNET_i2s (&s->address->peer));
2082     client_delete_session (s);
2083     return NULL;
2084   }
2085   notify_session_monitor (plugin,
2086                           s,
2087                           GNUNET_TRANSPORT_SS_INIT);
2088   notify_session_monitor (plugin,
2089                           s,
2090                           GNUNET_TRANSPORT_SS_UP); /* or handshake? */
2091   return s;
2092 }
2093
2094
2095 /**
2096  * Setup http_client plugin
2097  *
2098  * @param plugin the plugin handle
2099  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
2100  */
2101 static int
2102 client_start (struct HTTP_Client_Plugin *plugin)
2103 {
2104   curl_global_init (CURL_GLOBAL_ALL);
2105   plugin->curl_multi_handle = curl_multi_init ();
2106
2107   if (NULL == plugin->curl_multi_handle)
2108   {
2109     LOG (GNUNET_ERROR_TYPE_ERROR,
2110          _("Could not initialize curl multi handle, failed to start %s plugin!\n"),
2111          plugin->name);
2112     return GNUNET_SYSERR;
2113   }
2114   return GNUNET_OK;
2115 }
2116
2117
2118 /**
2119  * Another peer has suggested an address for this
2120  * peer and transport plugin.  Check that this could be a valid
2121  * address.  If so, consider adding it to the list
2122  * of addresses.
2123  *
2124  * @param cls closure with the `struct Plugin`
2125  * @param addr pointer to the address
2126  * @param addrlen length of @a addr
2127  * @return #GNUNET_OK if this is a plausible address for this peer
2128  *         and transport; always returns #GNUNET_NO (this is the client!)
2129  */
2130 static int
2131 http_client_plugin_address_suggested (void *cls,
2132                                       const void *addr,
2133                                       size_t addrlen)
2134 {
2135   /* A HTTP/S client does not have any valid address so:*/
2136   return GNUNET_NO;
2137 }
2138
2139
2140 /**
2141  * Exit point from the plugin.
2142  *
2143  * @param cls api as closure
2144  * @return NULL
2145  */
2146 void *
2147 LIBGNUNET_PLUGIN_TRANSPORT_DONE (void *cls)
2148 {
2149   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
2150   struct HTTP_Client_Plugin *plugin = api->cls;
2151
2152   if (NULL == api->cls)
2153   {
2154     /* Stub shutdown */
2155     GNUNET_free (api);
2156     return NULL;
2157   }
2158   LOG (GNUNET_ERROR_TYPE_DEBUG,
2159        _("Shutting down plugin `%s'\n"),
2160        plugin->name);
2161   GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2162                                          &destroy_session_cb,
2163                                          plugin);
2164   if (NULL != plugin->client_perform_task)
2165   {
2166     GNUNET_SCHEDULER_cancel (plugin->client_perform_task);
2167     plugin->client_perform_task = NULL;
2168   }
2169   if (NULL != plugin->curl_multi_handle)
2170   {
2171     curl_multi_cleanup (plugin->curl_multi_handle);
2172     plugin->curl_multi_handle = NULL;
2173   }
2174   curl_global_cleanup ();
2175   LOG (GNUNET_ERROR_TYPE_DEBUG,
2176        _("Shutdown for plugin `%s' complete\n"),
2177        plugin->name);
2178   GNUNET_CONTAINER_multipeermap_destroy (plugin->sessions);
2179   GNUNET_free_non_null (plugin->proxy_hostname);
2180   GNUNET_free_non_null (plugin->proxy_username);
2181   GNUNET_free_non_null (plugin->proxy_password);
2182   GNUNET_free (plugin);
2183   GNUNET_free (api);
2184   return NULL;
2185 }
2186
2187
2188 /**
2189  * Configure plugin
2190  *
2191  * @param plugin the plugin handle
2192  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
2193  */
2194 static int
2195 client_configure_plugin (struct HTTP_Client_Plugin *plugin)
2196 {
2197   unsigned long long max_requests;
2198   char *proxy_type;
2199
2200   /* Optional parameters */
2201   if (GNUNET_OK !=
2202       GNUNET_CONFIGURATION_get_value_number (plugin->env->cfg,
2203                                              plugin->name,
2204                                              "MAX_CONNECTIONS",
2205                                              &max_requests))
2206     max_requests = 128;
2207   plugin->max_requests = max_requests;
2208
2209   LOG (GNUNET_ERROR_TYPE_DEBUG,
2210        _("Maximum number of requests is %u\n"),
2211        plugin->max_requests);
2212
2213   /* Read proxy configuration */
2214   if (GNUNET_OK ==
2215       GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2216                                              plugin->name,
2217                                              "PROXY",
2218                                              &plugin->proxy_hostname))
2219   {
2220     LOG (GNUNET_ERROR_TYPE_DEBUG,
2221          "Found proxy host: `%s'\n",
2222          plugin->proxy_hostname);
2223     /* proxy username */
2224     if (GNUNET_OK ==
2225         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2226                                                plugin->name,
2227                                                "PROXY_USERNAME",
2228                                                &plugin->proxy_username))
2229     {
2230       LOG (GNUNET_ERROR_TYPE_DEBUG,
2231            "Found proxy username name: `%s'\n",
2232            plugin->proxy_username);
2233     }
2234
2235     /* proxy password */
2236     if (GNUNET_OK ==
2237         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2238                                                plugin->name,
2239                                                "PROXY_PASSWORD",
2240                                                &plugin->proxy_password))
2241     {
2242       LOG (GNUNET_ERROR_TYPE_DEBUG,
2243            "Found proxy password name: `%s'\n",
2244            plugin->proxy_password);
2245     }
2246
2247     /* proxy type */
2248     if (GNUNET_OK ==
2249         GNUNET_CONFIGURATION_get_value_string (plugin->env->cfg,
2250                                                plugin->name,
2251                                                "PROXY_TYPE",
2252                                                &proxy_type))
2253     {
2254       GNUNET_STRINGS_utf8_toupper (proxy_type, proxy_type);
2255
2256       if (0 == strcmp(proxy_type, "HTTP"))
2257         plugin->proxytype = CURLPROXY_HTTP;
2258       else if (0 == strcmp(proxy_type, "SOCKS4"))
2259         plugin->proxytype = CURLPROXY_SOCKS4;
2260       else if (0 == strcmp(proxy_type, "SOCKS5"))
2261         plugin->proxytype = CURLPROXY_SOCKS5;
2262       else if (0 == strcmp(proxy_type, "SOCKS4A"))
2263         plugin->proxytype = CURLPROXY_SOCKS4A;
2264       else if (0 == strcmp(proxy_type, "SOCKS5_HOSTNAME "))
2265         plugin->proxytype = CURLPROXY_SOCKS5_HOSTNAME ;
2266       else
2267       {
2268         LOG (GNUNET_ERROR_TYPE_ERROR,
2269              _("Invalid proxy type: `%s', disabling proxy! Check configuration!\n"),
2270              proxy_type);
2271
2272         GNUNET_free (proxy_type);
2273         GNUNET_free (plugin->proxy_hostname);
2274         plugin->proxy_hostname = NULL;
2275         GNUNET_free_non_null (plugin->proxy_username);
2276         plugin->proxy_username = NULL;
2277         GNUNET_free_non_null (plugin->proxy_password);
2278         plugin->proxy_password = NULL;
2279
2280         return GNUNET_SYSERR;
2281       }
2282
2283       LOG (GNUNET_ERROR_TYPE_DEBUG,
2284            "Found proxy type: `%s'\n",
2285            proxy_type);
2286     }
2287
2288     /* proxy http tunneling */
2289     plugin->proxy_use_httpproxytunnel
2290       = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2291                                               plugin->name,
2292                                               "PROXY_HTTP_TUNNELING");
2293     if (GNUNET_SYSERR == plugin->proxy_use_httpproxytunnel)
2294       plugin->proxy_use_httpproxytunnel = GNUNET_NO;
2295
2296     GNUNET_free_non_null (proxy_type);
2297   }
2298
2299   /* Should we emulate an XHR client for testing? */
2300   plugin->emulate_xhr
2301     = GNUNET_CONFIGURATION_get_value_yesno (plugin->env->cfg,
2302                                             plugin->name,
2303                                             "EMULATE_XHR");
2304   return GNUNET_OK;
2305 }
2306
2307
2308 /**
2309  * Function to convert an address to a human-readable string.
2310  *
2311  * @param cls closure
2312  * @param addr address to convert
2313  * @param addrlen address length
2314  * @return res string if conversion was successful, NULL otherwise
2315  */
2316 static const char *
2317 http_client_plugin_address_to_string (void *cls,
2318                                       const void *addr,
2319                                       size_t addrlen)
2320 {
2321   return http_common_plugin_address_to_string (PLUGIN_NAME,
2322                                                addr,
2323                                                addrlen);
2324 }
2325
2326
2327 /**
2328  * Function that will be called whenever the transport service wants to
2329  * notify the plugin that a session is still active and in use and
2330  * therefore the session timeout for this session has to be updated
2331  *
2332  * @param cls closure
2333  * @param peer which peer was the session for
2334  * @param session which session is being updated
2335  */
2336 static void
2337 http_client_plugin_update_session_timeout (void *cls,
2338                                            const struct GNUNET_PeerIdentity *peer,
2339                                            struct Session *session)
2340 {
2341   client_reschedule_session_timeout (session);
2342 }
2343
2344
2345 /**
2346  * Function that will be called whenever the transport service wants to
2347  * notify the plugin that the inbound quota changed and that the plugin
2348  * should update it's delay for the next receive value
2349  *
2350  * @param cls closure
2351  * @param peer which peer was the session for
2352  * @param s which session is being updated
2353  * @param delay new delay to use for receiving
2354  */
2355 static void
2356 http_client_plugin_update_inbound_delay (void *cls,
2357                                          const struct GNUNET_PeerIdentity *peer,
2358                                          struct Session *s,
2359                                          struct GNUNET_TIME_Relative delay)
2360 {
2361   s->next_receive = GNUNET_TIME_relative_to_absolute (delay);
2362   LOG (GNUNET_ERROR_TYPE_DEBUG,
2363        "New inbound delay %s\n",
2364        GNUNET_STRINGS_relative_time_to_string (delay,
2365                                                GNUNET_NO));
2366   if (s->recv_wakeup_task != NULL)
2367   {
2368     GNUNET_SCHEDULER_cancel (s->recv_wakeup_task);
2369     s->recv_wakeup_task = GNUNET_SCHEDULER_add_delayed (delay,
2370         &client_wake_up, s);
2371   }
2372 }
2373
2374
2375 /**
2376  * Return information about the given session to the
2377  * monitor callback.
2378  *
2379  * @param cls the `struct Plugin` with the monitor callback (`sic`)
2380  * @param peer peer we send information about
2381  * @param value our `struct Session` to send information about
2382  * @return #GNUNET_OK (continue to iterate)
2383  */
2384 static int
2385 send_session_info_iter (void *cls,
2386                         const struct GNUNET_PeerIdentity *peer,
2387                         void *value)
2388 {
2389   struct HTTP_Client_Plugin *plugin = cls;
2390   struct Session *session = value;
2391
2392   notify_session_monitor (plugin,
2393                           session,
2394                           GNUNET_TRANSPORT_SS_INIT);
2395   notify_session_monitor (plugin,
2396                           session,
2397                           GNUNET_TRANSPORT_SS_UP); /* FIXME: or handshake? */
2398   return GNUNET_OK;
2399 }
2400
2401
2402 /**
2403  * Begin monitoring sessions of a plugin.  There can only
2404  * be one active monitor per plugin (i.e. if there are
2405  * multiple monitors, the transport service needs to
2406  * multiplex the generated events over all of them).
2407  *
2408  * @param cls closure of the plugin
2409  * @param sic callback to invoke, NULL to disable monitor;
2410  *            plugin will being by iterating over all active
2411  *            sessions immediately and then enter monitor mode
2412  * @param sic_cls closure for @a sic
2413  */
2414 static void
2415 http_client_plugin_setup_monitor (void *cls,
2416                                   GNUNET_TRANSPORT_SessionInfoCallback sic,
2417                                   void *sic_cls)
2418 {
2419   struct HTTP_Client_Plugin *plugin = cls;
2420
2421   plugin->sic = sic;
2422   plugin->sic_cls = sic_cls;
2423   if (NULL != sic)
2424   {
2425     GNUNET_CONTAINER_multipeermap_iterate (plugin->sessions,
2426                                            &send_session_info_iter,
2427                                            plugin);
2428     /* signal end of first iteration */
2429     sic (sic_cls, NULL, NULL);
2430   }
2431 }
2432
2433
2434 /**
2435  * Entry point for the plugin.
2436  */
2437 void *
2438 LIBGNUNET_PLUGIN_TRANSPORT_INIT (void *cls)
2439 {
2440   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
2441   struct GNUNET_TRANSPORT_PluginFunctions *api;
2442   struct HTTP_Client_Plugin *plugin;
2443
2444   if (NULL == env->receive)
2445   {
2446     /* run in 'stub' mode (i.e. as part of gnunet-peerinfo), don't fully
2447        initialze the plugin or the API */
2448     api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2449     api->cls = NULL;
2450     api->address_to_string = &http_client_plugin_address_to_string;
2451     api->string_to_address = &http_common_plugin_string_to_address;
2452     api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2453     return api;
2454   }
2455
2456   plugin = GNUNET_new (struct HTTP_Client_Plugin);
2457   plugin->env = env;
2458   plugin->sessions = GNUNET_CONTAINER_multipeermap_create (128,
2459                                                            GNUNET_YES);
2460   api = GNUNET_new (struct GNUNET_TRANSPORT_PluginFunctions);
2461   api->cls = plugin;
2462   api->send = &http_client_plugin_send;
2463   api->disconnect_session = &http_client_plugin_session_disconnect;
2464   api->query_keepalive_factor = &http_client_query_keepalive_factor;
2465   api->disconnect_peer = &http_client_plugin_peer_disconnect;
2466   api->check_address = &http_client_plugin_address_suggested;
2467   api->get_session = &http_client_plugin_get_session;
2468   api->address_to_string = &http_client_plugin_address_to_string;
2469   api->string_to_address = &http_common_plugin_string_to_address;
2470   api->address_pretty_printer = &http_common_plugin_address_pretty_printer;
2471   api->get_network = &http_client_plugin_get_network;
2472   api->update_session_timeout = &http_client_plugin_update_session_timeout;
2473   api->update_inbound_delay = &http_client_plugin_update_inbound_delay;
2474   api->setup_monitor = &http_client_plugin_setup_monitor;
2475 #if BUILD_HTTPS
2476   plugin->name = "transport-https_client";
2477   plugin->protocol = "https";
2478 #else
2479   plugin->name = "transport-http_client";
2480   plugin->protocol = "http";
2481 #endif
2482   plugin->last_tag = 1;
2483
2484   if (GNUNET_SYSERR == client_configure_plugin (plugin))
2485   {
2486     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2487     return NULL;
2488   }
2489
2490   /* Start client */
2491   if (GNUNET_SYSERR == client_start (plugin))
2492   {
2493     LIBGNUNET_PLUGIN_TRANSPORT_DONE (api);
2494     return NULL;
2495   }
2496   return api;
2497 }
2498
2499 /* end of plugin_transport_http_client.c */