no debug
[oweals/gnunet.git] / src / transport / plugin_transport_http.c
1 /*
2      This file is part of GNUnet
3      (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file transport/plugin_transport_http.c
23  * @brief http transport service plugin
24  * @author Matthias Wachs
25  */
26
27 #include "platform.h"
28 #include "gnunet_constants.h"
29 #include "gnunet_protocols.h"
30 #include "gnunet_connection_lib.h"
31 #include "gnunet_service_lib.h"
32 #include "gnunet_statistics_service.h"
33 #include "gnunet_transport_service.h"
34 #include "gnunet_resolver_service.h"
35 #include "gnunet_server_lib.h"
36 #include "gnunet_container_lib.h"
37 #include "plugin_transport.h"
38 #include "gnunet_os_lib.h"
39 #include "microhttpd.h"
40 #include <curl/curl.h>
41
42
43 #define DEBUG_CURL GNUNET_YES
44 #define DEBUG_HTTP GNUNET_NO
45
46 /**
47  * Text of the response sent back after the last bytes of a PUT
48  * request have been received (just to formally obey the HTTP
49  * protocol).
50  */
51 #define HTTP_PUT_RESPONSE "Thank you!"
52
53 /**
54  * After how long do we expire an address that we
55  * learned from another peer if it is not reconfirmed
56  * by anyone?
57  */
58 #define LEARNED_ADDRESS_EXPIRATION GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS, 6)
59
60 /**
61  * Page returned if request invalid
62  */
63 #define HTTP_ERROR_RESPONSE "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\"><HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>Not Found</H1>The requested URL was not found on this server.<P><HR><ADDRESS></ADDRESS></BODY></HTML>"
64
65 /**
66  * Timeout for a http connect
67  */
68 #define HTTP_CONNECT_TIMEOUT 30
69
70 /**
71  * Network format for IPv4 addresses.
72  */
73 struct IPv4HttpAddress
74 {
75   /**
76    * IPv4 address, in network byte order.
77    */
78   uint32_t ipv4_addr GNUNET_PACKED;
79
80   /**
81    * Port number, in network byte order.
82    */
83   uint16_t u_port GNUNET_PACKED;
84
85 };
86
87
88 /**
89  * Network format for IPv6 addresses.
90  */
91 struct IPv6HttpAddress
92 {
93   /**
94    * IPv6 address.
95    */
96   struct in6_addr ipv6_addr GNUNET_PACKED;
97
98   /**
99    * Port number, in network byte order.
100    */
101   uint16_t u6_port GNUNET_PACKED;
102
103 };
104
105
106 /**
107  *  Message to send using http
108  */
109 struct HTTP_Message
110 {
111   /**
112    * next pointer for double linked list
113    */
114   struct HTTP_Message * next;
115
116   /**
117    * previous pointer for double linked list
118    */
119   struct HTTP_Message * prev;
120
121   /**
122    * buffer containing data to send
123    */
124   char *buf;
125
126   /**
127    * amount of data already sent
128    */
129   size_t pos;
130
131   /**
132    * buffer length
133    */
134   size_t size;
135   
136   /**
137    * Continuation function to call once the transmission buffer
138    * has again space available.  NULL if there is no
139    * continuation to call.
140    */
141   GNUNET_TRANSPORT_TransmitContinuation transmit_cont;
142
143   /**
144    * Closure for transmit_cont.
145    */
146   void *transmit_cont_cls;
147 };
148
149
150 struct HTTP_Connection
151 {
152   struct HTTP_Connection * next;
153
154   struct HTTP_Connection * prev;
155
156   void * addr;
157   size_t addrlen;
158
159   struct HTTP_Message * pending_msgs_head;
160   struct HTTP_Message * pending_msgs_tail;
161
162   char * url;
163   unsigned int put_connected;
164   unsigned int put_send_paused;
165
166   unsigned int get_connected;
167
168   /**
169    * curl handle for sending data using HTTP/PUT
170    * outbound data
171    */
172   CURL *put_curl_handle;
173
174   /**
175    * curl handle for recieving data using HTTP/GET transmission
176    * inbound data
177    */
178   CURL *get_curl_handle;
179
180   struct Session * session;
181
182   struct GNUNET_SERVER_MessageStreamTokenizer * msgtok;
183 };
184
185 struct HTTP_Connection_in
186 {
187   struct HTTP_Connection_in * next;
188
189   struct HTTP_Connection_in * prev;
190
191   void * addr;
192   size_t addrlen;
193
194   unsigned int connected;
195   unsigned int send_paused;
196
197   struct GNUNET_SERVER_MessageStreamTokenizer * msgtok;
198
199   struct Session * session;
200
201   /**
202    * Is there a HTTP/PUT in progress?
203    */
204   int is_put_in_progress;
205
206   /**
207    * Is the http request invalid?
208    */
209   int is_bad_request;
210 };
211
212 /**
213  * Session handle for connections.
214  */
215 struct Session
216 {
217
218   /**
219    * API requirement.
220    */
221   struct SessionHeader header;
222
223   /**
224    * Stored in a linked list.
225    */
226   struct Session *next;
227
228   /**
229    * Pointer to the global plugin struct.
230    */
231   struct Plugin *plugin;
232
233   /**
234    * To whom are we talking to (set to our identity
235    * if we are still waiting for the welcome message)
236    */
237   struct GNUNET_PeerIdentity identity;
238
239   /**
240    * Did we initiate the connection (GNUNET_YES) or the other peer (GNUNET_NO)?
241    */
242   int is_client;
243
244   /**
245    * At what time did we reset last_received last?
246    */
247   struct GNUNET_TIME_Absolute last_quota_update;
248
249   /**
250    * How many bytes have we received since the "last_quota_update"
251    * timestamp?
252    */
253   uint64_t last_received;
254
255   /**
256    * Number of bytes per ms that this peer is allowed
257    * to send to us.
258    */
259   uint32_t quota;
260
261   /**
262    * Encoded hash
263    */
264   struct GNUNET_CRYPTO_HashAsciiEncoded hash;
265
266   struct HTTP_Connection *outbound_connections_head;
267   struct HTTP_Connection *outbound_connections_tail;
268
269   struct HTTP_Connection_in *inbound_connections_head;
270   struct HTTP_Connection_in *inbound_connections_tail;
271 };
272
273 /**
274  * Encapsulation of all of the state of the plugin.
275  */
276 struct Plugin
277 {
278   /**
279    * Our environment.
280    */
281   struct GNUNET_TRANSPORT_PluginEnvironment *env;
282
283   unsigned int port_inbound;
284
285   /**
286    * Hashmap for all existing sessions.
287    */
288   struct GNUNET_CONTAINER_MultiHashMap *sessions;
289
290   /**
291    * Daemon for listening for new IPv4 connections.
292    */
293   struct MHD_Daemon *http_server_daemon_v4;
294
295   /**
296    * Daemon for listening for new IPv6connections.
297    */
298   struct MHD_Daemon *http_server_daemon_v6;
299
300   /**
301    * Our primary task for http daemon handling IPv4 connections
302    */
303   GNUNET_SCHEDULER_TaskIdentifier http_server_task_v4;
304
305   /**
306    * Our primary task for http daemon handling IPv6 connections
307    */
308   GNUNET_SCHEDULER_TaskIdentifier http_server_task_v6;
309
310   /**
311    * The task sending data
312    */
313   GNUNET_SCHEDULER_TaskIdentifier http_server_task_send;
314
315   /**
316    * cURL Multihandle
317    */
318   CURLM * multi_handle;
319
320   /**
321    * Our ASCII encoded, hashed peer identity
322    * This string is used to distinguish between connections and is added to the urls
323    */
324   struct GNUNET_CRYPTO_HashAsciiEncoded my_ascii_hash_ident;
325 };
326
327
328 /**
329  * Function called for a quick conversion of the binary address to
330  * a numeric address.  Note that the caller must not free the
331  * address and that the next call to this function is allowed
332  * to override the address again.
333  *
334  * @param cls closure
335  * @param addr binary address
336  * @param addrlen length of the address
337  * @return string representing the same address
338  */
339 static const char*
340 http_plugin_address_to_string (void *cls,
341                                    const void *addr,
342                                    size_t addrlen);
343
344 /**
345  * Create a new session
346  * @param cls plugin as closure
347  * @param addr_in address the peer is using inbound
348  * @param addr_len_in address length
349  * @param addr_out address the peer is using outbound
350  * @param addr_len_out address length
351  * @param peer identity
352  * @return created session object
353  */
354 static struct Session * 
355 create_session (void * cls, 
356                 char * addr_in, 
357                 size_t addrlen_in,
358                 char * addr_out, 
359                 size_t addrlen_out, 
360                 const struct GNUNET_PeerIdentity *peer)
361 {
362   struct Plugin *plugin = cls;
363   struct Session * cs = GNUNET_malloc ( sizeof( struct Session) );
364
365   GNUNET_assert(cls !=NULL);
366   cs->plugin = plugin;
367   memcpy(&cs->identity, peer, sizeof (struct GNUNET_PeerIdentity));
368   GNUNET_CRYPTO_hash_to_enc(&cs->identity.hashPubKey,&(cs->hash));
369   cs->outbound_connections_head = NULL;
370   cs->outbound_connections_tail = NULL;
371   return cs;
372 }
373
374 /**
375  * Check if session for this peer is already existing, otherwise create it
376  * @param cls the plugin used
377  * @param p peer to get session for
378  * @return session found or created
379  */
380 static struct Session * session_get (void * cls, const struct GNUNET_PeerIdentity *p)
381 {
382   struct Plugin *plugin = cls;
383   struct Session *cs;
384   unsigned int res;
385
386   cs = GNUNET_CONTAINER_multihashmap_get (plugin->sessions, &p->hashPubKey);
387   if (cs == NULL)
388   {
389     cs = create_session(plugin, NULL, 0, NULL, 0, p);
390     res = GNUNET_CONTAINER_multihashmap_put ( plugin->sessions,
391                                         &cs->identity.hashPubKey,
392                                         cs,
393                                         GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
394     if (res == GNUNET_OK)
395       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
396                   "New Session `%s' inserted\n", GNUNET_i2s(p));
397   }
398   return cs;
399 }
400
401 static char * create_url(void * cls, const void * addr, size_t addrlen)
402 {
403   struct Plugin *plugin = cls;
404   char *url = NULL;
405
406   GNUNET_assert ((addr!=NULL) && (addrlen != 0));
407   GNUNET_asprintf(&url,
408                   "http://%s/%s",
409                   http_plugin_address_to_string(NULL, addr, addrlen),
410                   (char *) (&plugin->my_ascii_hash_ident));
411
412   return url;
413 }
414
415 /**
416  * Removes a message from the linked list of messages
417  * @param con connection to remove message from
418  * @param msg message to remove
419  * @return GNUNET_SYSERR if msg not found, GNUNET_OK on success
420  */
421
422 static int remove_http_message(struct HTTP_Connection * con, struct HTTP_Message * msg)
423 {
424   GNUNET_CONTAINER_DLL_remove(con->pending_msgs_head,con->pending_msgs_tail,msg);
425   GNUNET_free(msg);
426   return GNUNET_OK;
427 }
428
429 /**
430  * Check if session already knows this address for a outbound connection to this peer
431  * If address not in session, add it to the session
432  * @param cls the plugin used
433  * @param cs the session
434  * @param addr address
435  * @param addr_len address length
436  * @return the found or created address
437  */
438 static struct HTTP_Connection * session_check_outbound_address (void * cls, struct Session *cs, const void * addr, size_t addr_len)
439 {
440   struct Plugin *plugin = cls;
441   struct HTTP_Connection * cc = cs->outbound_connections_head;
442   struct HTTP_Connection * con = NULL;
443
444   GNUNET_assert((addr_len == sizeof (struct IPv4HttpAddress)) || (addr_len == sizeof (struct IPv6HttpAddress)));
445
446   while (cc!=NULL)
447   {
448     if (addr_len == cc->addrlen)
449     {
450       if (0 == memcmp(cc->addr, addr, addr_len))
451       {
452         con = cc;
453         break;
454       }
455     }
456     cc=cc->next;
457   }
458
459   if (con==NULL)
460   {
461     con = GNUNET_malloc(sizeof(struct HTTP_Connection) + addr_len);
462     con->addrlen = addr_len;
463     con->addr=&con[1];
464     con->url=create_url(plugin, addr, addr_len);
465     con->put_curl_handle = NULL;
466     con->put_connected = GNUNET_NO;
467     con->get_curl_handle = NULL;
468     con->get_connected = GNUNET_NO;
469     con->session = cs;
470     memcpy(con->addr, addr, addr_len);
471     GNUNET_CONTAINER_DLL_insert(cs->outbound_connections_head,cs->outbound_connections_tail,con);
472     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Created new connection %X to peer `%s'\n",con,GNUNET_i2s(&cs->identity));
473   }
474   return con;
475 }
476
477
478 /**
479  * Check if session already knows this address for a inbound connection to this peer
480  * If address not in session, add it to the session
481  * @param cls not needed, can be NULL
482  * @param cs the session
483  * @param addr address
484  * @param addr_len address length
485  * @return the found or created address
486  */
487 static struct HTTP_Connection_in * session_check_inbound_address (void * cls, struct Session *cs, const void * addr, size_t addr_len)
488 {
489   struct HTTP_Connection_in * cc = cs->inbound_connections_head;
490   struct HTTP_Connection_in * con = NULL;
491
492   GNUNET_assert((addr_len == sizeof (struct IPv4HttpAddress)) || (addr_len == sizeof (struct IPv6HttpAddress)));
493
494   while (cc!=NULL)
495   {
496     if (addr_len == cc->addrlen)
497     {
498       if (0 == memcmp(cc->addr, addr, addr_len))
499       {
500         con = cc;
501         break;
502       }
503     }
504     cc=cc->next;
505   }
506
507
508   if (con==NULL)
509   {
510     con = GNUNET_malloc(sizeof(struct HTTP_Connection_in) + addr_len);
511     con->addrlen = addr_len;
512     con->addr=&con[1];
513     con->connected = GNUNET_NO;
514     con->session = cs;
515     memcpy(con->addr, addr, addr_len);
516     GNUNET_CONTAINER_DLL_insert(cs->inbound_connections_head,cs->inbound_connections_tail,con);
517   }
518
519   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X for inbound address %s (%s) was found\n",
520               con,
521               GNUNET_i2s(&cs->identity),
522               http_plugin_address_to_string(NULL,con->addr,con->addrlen));
523   return con;
524 }
525
526
527 /**
528  * Callback called by MHD when a connection is terminated
529  */
530 static void requestCompletedCallback (void *cls, struct MHD_Connection * connection, void **httpSessionCache)
531 {
532   struct HTTP_Connection_in * con;
533
534   con = *httpSessionCache;
535   if (con == NULL)
536     return;
537   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection from peer `%s' was terminated\n",GNUNET_i2s(&con->session->identity));
538   /* session set to inactive */
539   con->is_put_in_progress = GNUNET_NO;
540   con->is_bad_request = GNUNET_NO;
541 }
542
543 static void messageTokenizerCallback (void *cls,
544                                       void *client,
545                                       const struct GNUNET_MessageHeader *message)
546 {
547   struct HTTP_Connection_in * con = cls;
548   GNUNET_assert(con != NULL);
549
550   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
551               "Forwarding message to transport service, type %u and size %u from `%s' (`%s')\n",
552               ntohs(message->type),
553               ntohs(message->size),
554               GNUNET_i2s(&(con->session->identity)),http_plugin_address_to_string(NULL,con->addr,con->addrlen));
555
556   con->session->plugin->env->receive (con->session->plugin->env->cls,
557                             &con->session->identity,
558                             message, 1, con->session,
559                             NULL,
560                             0);
561 }
562
563 /**
564  * Check if ip is allowed to connect.
565  */
566 static int
567 acceptPolicyCallback (void *cls,
568                       const struct sockaddr *addr, socklen_t addr_len)
569 {
570 #if 0
571   struct Plugin *plugin = cls;
572 #endif
573   /* Every connection is accepted, nothing more to do here */
574   return MHD_YES;
575 }
576
577 int server_read_callback (void *cls, uint64_t pos, char *buf, int max)
578 {
579   int bytes_read = 0;
580
581   struct HTTP_Connection * con = cls;
582   struct HTTP_Message * msg;
583   int res;res=5;
584
585   msg=con->pending_msgs_tail;
586
587
588   if (msg!=NULL)
589   {
590     /*
591     if ((msg->size-msg->pos) <= max)
592       {
593         memcpy(buf,&msg->buf[pos],(msg->size-msg->pos));
594         pos+=(msg->size-msg->pos);
595       }
596       else
597       {
598         memcpy(buf,&msg->buf[pos],max);
599         pos+=max;
600       }
601
602       if (msg->pos==msg->size)
603       {
604         if (NULL != con->pending_msgs_tail->transmit_cont)
605           msg->transmit_cont (msg->transmit_cont_cls,&con->session->identity,GNUNET_SYSERR);
606         res = remove_http_message(con,msg);
607       }
608       */
609   }
610
611   return bytes_read;
612 }
613
614 /**
615  * Process GET or PUT request received via MHD.  For
616  * GET, queue response that will send back our pending
617  * messages.  For PUT, process incoming data and send
618  * to GNUnet core.  In either case, check if a session
619  * already exists and create a new one if not.
620  */
621 static int
622 accessHandlerCallback (void *cls,
623                        struct MHD_Connection *mhd_connection,
624                        const char *url,
625                        const char *method,
626                        const char *version,
627                        const char *upload_data,
628                        size_t * upload_data_size, void **httpSessionCache)
629 {
630   struct Plugin *plugin = cls;
631   struct MHD_Response *response;
632   struct Session * cs;
633   struct HTTP_Connection_in * con;
634   const union MHD_ConnectionInfo * conn_info;
635   struct sockaddr_in  *addrin;
636   struct sockaddr_in6 *addrin6;
637   char address[INET6_ADDRSTRLEN+14];
638   struct GNUNET_PeerIdentity pi_in;
639   int res = GNUNET_NO;
640   int send_error_to_client;
641   struct IPv4HttpAddress ipv4addr;
642   struct IPv6HttpAddress ipv6addr;
643
644   GNUNET_assert(cls !=NULL);
645   send_error_to_client = GNUNET_NO;
646   if (NULL == *httpSessionCache)
647   {
648     /* check url for peer identity , if invalid send HTTP 404*/
649     res = GNUNET_CRYPTO_hash_from_string ( &url[1], &(pi_in.hashPubKey));
650     if ( GNUNET_SYSERR == res )
651     {
652       response = MHD_create_response_from_data (strlen (HTTP_ERROR_RESPONSE),HTTP_ERROR_RESPONSE, MHD_NO, MHD_NO);
653       res = MHD_queue_response (mhd_connection, MHD_HTTP_NOT_FOUND, response);
654       MHD_destroy_response (response);
655       if (res == MHD_YES)
656         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Peer has no valid ident, sent HTTP 1.1/404\n");
657       else
658         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Peer has no valid ident, could not send error\n");
659       return res;
660     }
661   }
662   else
663   {
664     con = *httpSessionCache;
665     cs = con->session;
666   }
667
668   if (NULL == *httpSessionCache)
669   {
670     /* get session for peer identity */
671     cs = session_get (plugin ,&pi_in);
672
673     conn_info = MHD_get_connection_info(mhd_connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS );
674     /* Incoming IPv4 connection */
675     if ( AF_INET == conn_info->client_addr->sin_family)
676     {
677       addrin = conn_info->client_addr;
678       inet_ntop(addrin->sin_family, &(addrin->sin_addr),address,INET_ADDRSTRLEN);
679       memcpy(&ipv4addr.ipv4_addr,&(addrin->sin_addr),sizeof(struct in_addr));
680       ipv4addr.u_port = addrin->sin_port;
681       con = session_check_inbound_address (plugin, cs, (const void *) &ipv4addr, sizeof (struct IPv4HttpAddress));
682     }
683     /* Incoming IPv6 connection */
684     if ( AF_INET6 == conn_info->client_addr->sin_family)
685     {
686       addrin6 = (struct sockaddr_in6 *) conn_info->client_addr;
687       inet_ntop(addrin6->sin6_family, &(addrin6->sin6_addr),address,INET6_ADDRSTRLEN);
688       memcpy(&ipv6addr.ipv6_addr,&(addrin6->sin6_addr),sizeof(struct in6_addr));
689       ipv6addr.u6_port = addrin6->sin6_port;
690       con = session_check_inbound_address (plugin, cs, &ipv6addr, sizeof (struct IPv6HttpAddress));
691     }
692     /* Set closure and update current session*/
693
694     *httpSessionCache = con;
695     if (con->msgtok==NULL)
696       con->msgtok = GNUNET_SERVER_mst_create (&messageTokenizerCallback, con);
697
698     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"HTTP Daemon has new an incoming `%s' request from peer `%s' (`%s')\n",
699                 method,
700                 GNUNET_i2s(&cs->identity),
701                 http_plugin_address_to_string(NULL, con->addr, con->addrlen));
702   }
703
704   /* Is it a PUT or a GET request */
705   if (0 == strcmp (MHD_HTTP_METHOD_PUT, method))
706   {
707     if ((*upload_data_size == 0) && (con->is_put_in_progress==GNUNET_NO))
708     {
709       con->is_put_in_progress = GNUNET_YES;
710       return MHD_YES;
711     }
712
713     /* Transmission of all data complete */
714     if ((*upload_data_size == 0) && (con->is_put_in_progress == GNUNET_YES))
715     {
716         response = MHD_create_response_from_data (strlen (HTTP_PUT_RESPONSE),HTTP_PUT_RESPONSE, MHD_NO, MHD_NO);
717         res = MHD_queue_response (mhd_connection, MHD_HTTP_OK, response);
718         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Sent HTTP/1.1: 200 OK as PUT Response\n",HTTP_PUT_RESPONSE, strlen (HTTP_PUT_RESPONSE), res );
719         MHD_destroy_response (response);
720         return MHD_YES;
721
722       con->is_put_in_progress = GNUNET_NO;
723       con->is_bad_request = GNUNET_NO;
724       return res;
725     }
726
727     /* Recieving data */
728     if ((*upload_data_size > 0) && (con->is_put_in_progress == GNUNET_YES))
729     {
730       res = GNUNET_SERVER_mst_receive(con->msgtok, con, upload_data,*upload_data_size, GNUNET_NO, GNUNET_NO);
731       (*upload_data_size) = 0;
732       return MHD_YES;
733     }
734     else
735       return MHD_NO;
736   }
737   if ( 0 == strcmp (MHD_HTTP_METHOD_GET, method) )
738   {
739     response = MHD_create_response_from_callback(-1,32 * 1024, &server_read_callback, con, NULL);
740     res = MHD_queue_response (mhd_connection, MHD_HTTP_OK, response);
741     MHD_destroy_response (response);
742
743     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"HTTP Daemon has new an incoming `%s' request from peer `%s' (`%s')\n",
744                 method,
745                 GNUNET_i2s(&cs->identity),
746                 http_plugin_address_to_string(NULL, con->addr, con->addrlen));
747
748
749     return res;
750
751   }
752   return MHD_NO;
753 }
754
755
756 /**
757  * Call MHD to process pending ipv4 requests and then go back
758  * and schedule the next run.
759  */
760 static void http_server_daemon_v4_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
761 /**
762  * Call MHD to process pending ipv6 requests and then go back
763  * and schedule the next run.
764  */
765 static void http_server_daemon_v6_run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
766
767 /**
768  * Function that queries MHD's select sets and
769  * starts the task waiting for them.
770  */
771 static GNUNET_SCHEDULER_TaskIdentifier
772 http_server_daemon_prepare (void * cls, struct MHD_Daemon *daemon_handle)
773 {
774   struct Plugin *plugin = cls;
775   GNUNET_SCHEDULER_TaskIdentifier ret;
776   fd_set rs;
777   fd_set ws;
778   fd_set es;
779   struct GNUNET_NETWORK_FDSet *wrs;
780   struct GNUNET_NETWORK_FDSet *wws;
781   struct GNUNET_NETWORK_FDSet *wes;
782   int max;
783   unsigned long long timeout;
784   int haveto;
785   struct GNUNET_TIME_Relative tv;
786
787   GNUNET_assert(cls !=NULL);
788   ret = GNUNET_SCHEDULER_NO_TASK;
789   FD_ZERO(&rs);
790   FD_ZERO(&ws);
791   FD_ZERO(&es);
792   wrs = GNUNET_NETWORK_fdset_create ();
793   wes = GNUNET_NETWORK_fdset_create ();
794   wws = GNUNET_NETWORK_fdset_create ();
795   max = -1;
796   GNUNET_assert (MHD_YES ==
797                  MHD_get_fdset (daemon_handle,
798                                 &rs,
799                                 &ws,
800                                 &es,
801                                 &max));
802   haveto = MHD_get_timeout (daemon_handle, &timeout);
803   if (haveto == MHD_YES)
804     tv.value = (uint64_t) timeout;
805   else
806     tv = GNUNET_TIME_UNIT_FOREVER_REL;
807   GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max);
808   GNUNET_NETWORK_fdset_copy_native (wws, &ws, max);
809   GNUNET_NETWORK_fdset_copy_native (wes, &es, max);
810   if (daemon_handle == plugin->http_server_daemon_v4)
811   {
812     ret = GNUNET_SCHEDULER_add_select (plugin->env->sched,
813                                        GNUNET_SCHEDULER_PRIORITY_DEFAULT,
814                                        GNUNET_SCHEDULER_NO_TASK,
815                                        tv,
816                                        wrs,
817                                        wws,
818                                        &http_server_daemon_v4_run,
819                                        plugin);
820   }
821   if (daemon_handle == plugin->http_server_daemon_v6)
822   {
823     ret = GNUNET_SCHEDULER_add_select (plugin->env->sched,
824                                        GNUNET_SCHEDULER_PRIORITY_DEFAULT,
825                                        GNUNET_SCHEDULER_NO_TASK,
826                                        tv,
827                                        wrs,
828                                        wws,
829                                        &http_server_daemon_v6_run,
830                                        plugin);
831   }
832   GNUNET_NETWORK_fdset_destroy (wrs);
833   GNUNET_NETWORK_fdset_destroy (wws);
834   GNUNET_NETWORK_fdset_destroy (wes);
835   return ret;
836 }
837
838 /**
839  * Call MHD to process pending requests and then go back
840  * and schedule the next run.
841  */
842 static void http_server_daemon_v4_run (void *cls,
843                              const struct GNUNET_SCHEDULER_TaskContext *tc)
844 {
845   struct Plugin *plugin = cls;
846
847   GNUNET_assert(cls !=NULL);
848   if (plugin->http_server_task_v4 != GNUNET_SCHEDULER_NO_TASK)
849     plugin->http_server_task_v4 = GNUNET_SCHEDULER_NO_TASK;
850
851   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
852     return;
853
854   GNUNET_assert (MHD_YES == MHD_run (plugin->http_server_daemon_v4));
855   plugin->http_server_task_v4 = http_server_daemon_prepare (plugin, plugin->http_server_daemon_v4);
856   return;
857 }
858
859
860 /**
861  * Call MHD to process pending requests and then go back
862  * and schedule the next run.
863  */
864 static void http_server_daemon_v6_run (void *cls,
865                              const struct GNUNET_SCHEDULER_TaskContext *tc)
866 {
867   struct Plugin *plugin = cls;
868
869   GNUNET_assert(cls !=NULL);
870   if (plugin->http_server_task_v6 != GNUNET_SCHEDULER_NO_TASK)
871     plugin->http_server_task_v6 = GNUNET_SCHEDULER_NO_TASK;
872
873   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
874     return;
875
876   GNUNET_assert (MHD_YES == MHD_run (plugin->http_server_daemon_v6));
877   plugin->http_server_task_v6 = http_server_daemon_prepare (plugin, plugin->http_server_daemon_v6);
878   return;
879 }
880
881
882 static size_t curl_header_function( void *ptr, size_t size, size_t nmemb, void *stream)
883 {
884   struct HTTP_Connection * con = stream;
885
886   char * tmp;
887   size_t len = size * nmemb;
888   long http_result = 0;
889   int res;
890
891   /* Getting last http result code */
892   if (con->get_connected==GNUNET_NO)
893   {
894     GNUNET_assert(NULL!=con);
895     res = curl_easy_getinfo(con->get_curl_handle, CURLINFO_RESPONSE_CODE, &http_result);
896     if (CURLE_OK == res)
897     {
898       if (http_result == 200)
899       {
900         con->get_connected = GNUNET_YES;
901         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: inbound connected\n",con);
902       }
903     }
904   }
905
906   tmp = NULL;
907   if ((size * nmemb) < SIZE_MAX)
908     tmp = GNUNET_malloc (len+1);
909
910   if ((tmp != NULL) && (len > 0))
911   {
912     memcpy(tmp,ptr,len);
913     if (len>=2)
914     {
915       if (tmp[len-2] == 13)
916         tmp[len-2]= '\0';
917     }
918 #if DEBUG_HTTP
919     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Header: `%s' %u \n",tmp, http_result);
920 #endif
921   }
922   if (NULL != tmp)
923     GNUNET_free (tmp);
924
925   return size * nmemb;
926 }
927
928 /**
929  * Callback method used with libcurl
930  * Method is called when libcurl needs to read data during sending
931  * @param stream pointer where to write data
932  * @param size size of an individual element
933  * @param nmemb count of elements that can be written to the buffer
934  * @param ptr source pointer, passed to the libcurl handle
935  * @return bytes written to stream
936  */
937 static size_t send_curl_read_callback(void *stream, size_t size, size_t nmemb, void *ptr)
938 {
939   struct HTTP_Connection * con = ptr;
940   struct HTTP_Message * msg = con->pending_msgs_tail;
941   size_t bytes_sent;
942   size_t len;
943
944   if (con->pending_msgs_tail == NULL)
945   {
946     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: No Message to send, pausing connection\n",con);
947     con->put_send_paused = GNUNET_YES;
948     return CURL_READFUNC_PAUSE;
949   }
950
951   msg = con->pending_msgs_tail;
952   /* data to send */
953   if (msg->pos < msg->size)
954   {
955     /* data fit in buffer */
956     if ((msg->size - msg->pos) <= (size * nmemb))
957     {
958       len = (msg->size - msg->pos);
959       memcpy(stream, &msg->buf[msg->pos], len);
960       msg->pos += len;
961       bytes_sent = len;
962     }
963     else
964     {
965       len = size*nmemb;
966       memcpy(stream, &msg->buf[msg->pos], len);
967       msg->pos += len;
968       bytes_sent = len;
969     }
970   }
971   /* no data to send */
972   else
973   {
974     bytes_sent = 0;
975   }
976
977   if ( msg->pos == msg->size)
978   {
979     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: Message with %u bytes sent, removing message from queue \n",con, msg->pos);
980     /* Calling transmit continuation  */
981     if (( NULL != con->pending_msgs_tail) && (NULL != con->pending_msgs_tail->transmit_cont))
982       msg->transmit_cont (con->pending_msgs_tail->transmit_cont_cls,&(con->session)->identity,GNUNET_OK);
983     remove_http_message(con, msg);
984   }
985   return bytes_sent;
986 }
987
988 /**
989 * Callback method used with libcurl
990 * Method is called when libcurl needs to write data during sending
991 * @param stream pointer where to write data
992 * @param size size of an individual element
993 * @param nmemb count of elements that can be written to the buffer
994 * @param ptr destination pointer, passed to the libcurl handle
995 * @return bytes read from stream
996 */
997 static size_t send_curl_write_callback( void *stream, size_t size, size_t nmemb, void *ptr)
998 {
999   struct HTTP_Connection * con = ptr;
1000   char * data = NULL;
1001
1002   data = GNUNET_malloc(size*nmemb +1);
1003   if (data != NULL)
1004   {
1005     memcpy( data, stream, size*nmemb);
1006     data[size*nmemb] = '\0';
1007     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: recieved %s\n",con,data);
1008     free (data);
1009   }
1010   return (size * nmemb);
1011
1012 }
1013
1014 /**
1015  * Function setting up file descriptors and scheduling task to run
1016  * @param cls closure
1017  * @param ses session to send data to
1018  * @return bytes sent to peer
1019  */
1020 static size_t send_schedule(void *cls, struct Session* ses );
1021
1022
1023
1024 /**
1025  * Function setting up curl handle and selecting message to send
1026  * @param cls plugin
1027  * @param ses session to send data to
1028  * @param con connection
1029  * @return bytes sent to peer
1030  */
1031 static ssize_t send_check_connections (void *cls, struct Session* ses , struct HTTP_Connection *con)
1032 {
1033   struct Plugin *plugin = cls;
1034   int bytes_sent = 0;
1035   CURLMcode mret;
1036   struct HTTP_Message * msg;
1037   struct GNUNET_TIME_Relative timeout = GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT;
1038
1039   GNUNET_assert(cls !=NULL);
1040
1041   if (con->get_connected == GNUNET_NO)
1042   {
1043       if (con->get_curl_handle == NULL)
1044       {
1045         con->get_curl_handle = curl_easy_init();
1046 #if DEBUG_CURL
1047       curl_easy_setopt(con->get_curl_handle, CURLOPT_VERBOSE, 1L);
1048 #endif
1049       curl_easy_setopt(con->get_curl_handle, CURLOPT_URL, con->url);
1050       //curl_easy_setopt(con->put_curl_handle, CURLOPT_PUT, 1L);
1051       curl_easy_setopt(con->get_curl_handle, CURLOPT_HEADERFUNCTION, &curl_header_function);
1052       curl_easy_setopt(con->get_curl_handle, CURLOPT_WRITEHEADER, con);
1053       curl_easy_setopt(con->get_curl_handle, CURLOPT_READFUNCTION, send_curl_read_callback);
1054       curl_easy_setopt(con->get_curl_handle, CURLOPT_READDATA, con);
1055       curl_easy_setopt(con->get_curl_handle, CURLOPT_WRITEFUNCTION, send_curl_write_callback);
1056       curl_easy_setopt(con->get_curl_handle, CURLOPT_WRITEDATA, con);
1057       curl_easy_setopt(con->get_curl_handle, CURLOPT_TIMEOUT, (long) timeout.value);
1058       curl_easy_setopt(con->get_curl_handle, CURLOPT_PRIVATE, con);
1059       curl_easy_setopt(con->get_curl_handle, CURLOPT_CONNECTTIMEOUT, HTTP_CONNECT_TIMEOUT);
1060       curl_easy_setopt(con->get_curl_handle, CURLOPT_BUFFERSIZE, GNUNET_SERVER_MAX_MESSAGE_SIZE);
1061
1062       mret = curl_multi_add_handle(plugin->multi_handle, con->get_curl_handle);
1063       if (mret != CURLM_OK)
1064       {
1065         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1066                     _("%s failed at %s:%d: `%s'\n"),
1067                     "curl_multi_add_handle", __FILE__, __LINE__,
1068                     curl_multi_strerror (mret));
1069         return -1;
1070       }
1071       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: inbound not connected, initiating connection\n",con);
1072     }
1073   }
1074
1075   /* PUT already connected, no need to initiate connection */
1076   if ((con->put_connected == GNUNET_YES) && (con->put_curl_handle != NULL))
1077   {
1078     if (con->put_send_paused == GNUNET_NO)
1079     {
1080       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: outbound active, enqueueing message\n",con);
1081       return bytes_sent;
1082     }
1083     if (con->put_send_paused == GNUNET_YES)
1084     {
1085       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: outbound paused, unpausing existing connection and enqueueing message\n",con);
1086       curl_easy_pause(con->put_curl_handle,CURLPAUSE_CONT);
1087       con->put_send_paused=GNUNET_NO;
1088       return bytes_sent;
1089     }
1090   }
1091
1092   /* not connected, initiate connection */
1093   if ( NULL == con->put_curl_handle)
1094     con->put_curl_handle = curl_easy_init();
1095   GNUNET_assert (con->put_curl_handle != NULL);
1096   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Connection %X: outbound not connected, initiating connection\n",con);
1097
1098   GNUNET_assert (NULL != con->pending_msgs_tail);
1099   msg = con->pending_msgs_tail;
1100
1101 #if DEBUG_CURL
1102   curl_easy_setopt(con->put_curl_handle, CURLOPT_VERBOSE, 1L);
1103 #endif
1104   curl_easy_setopt(con->put_curl_handle, CURLOPT_URL, con->url);
1105   curl_easy_setopt(con->put_curl_handle, CURLOPT_PUT, 1L);
1106   curl_easy_setopt(con->put_curl_handle, CURLOPT_READFUNCTION, send_curl_read_callback);
1107   curl_easy_setopt(con->put_curl_handle, CURLOPT_READDATA, con);
1108   curl_easy_setopt(con->put_curl_handle, CURLOPT_WRITEFUNCTION, send_curl_write_callback);
1109   curl_easy_setopt(con->put_curl_handle, CURLOPT_READDATA, con);
1110   curl_easy_setopt(con->put_curl_handle, CURLOPT_TIMEOUT, (long) timeout.value);
1111   curl_easy_setopt(con->put_curl_handle, CURLOPT_PRIVATE, con);
1112   curl_easy_setopt(con->put_curl_handle, CURLOPT_CONNECTTIMEOUT, HTTP_CONNECT_TIMEOUT);
1113   curl_easy_setopt(con->put_curl_handle, CURLOPT_BUFFERSIZE, GNUNET_SERVER_MAX_MESSAGE_SIZE);
1114
1115   mret = curl_multi_add_handle(plugin->multi_handle, con->put_curl_handle);
1116   if (mret != CURLM_OK)
1117   {
1118     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1119                 _("%s failed at %s:%d: `%s'\n"),
1120                 "curl_multi_add_handle", __FILE__, __LINE__,
1121                 curl_multi_strerror (mret));
1122     return -1;
1123   }
1124   con->put_connected = GNUNET_YES;
1125   bytes_sent = send_schedule (plugin, ses);
1126   return bytes_sent;
1127 }
1128
1129 static void send_execute (void *cls,
1130              const struct GNUNET_SCHEDULER_TaskContext *tc)
1131 {
1132   struct Plugin *plugin = cls;
1133   static unsigned int handles_last_run;
1134   int running;
1135   struct CURLMsg *msg;
1136   CURLMcode mret;
1137   struct HTTP_Connection * con = NULL;
1138   struct Session * cs = NULL;
1139   long http_result;
1140
1141   GNUNET_assert(cls !=NULL);
1142   plugin->http_server_task_send = GNUNET_SCHEDULER_NO_TASK;
1143   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
1144     return;
1145
1146   do
1147     {
1148       running = 0;
1149       mret = curl_multi_perform (plugin->multi_handle, &running);
1150       if (running < handles_last_run)
1151         {
1152           do
1153             {
1154
1155               msg = curl_multi_info_read (plugin->multi_handle, &running);
1156               if (msg == NULL)
1157                 break;
1158               /* get session for affected curl handle */
1159               GNUNET_assert ( msg->easy_handle != NULL );
1160               curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char *) &con);
1161               GNUNET_assert ( con != NULL );
1162               cs = con->session;
1163               GNUNET_assert ( cs != NULL );
1164               switch (msg->msg)
1165                 {
1166
1167                 case CURLMSG_DONE:
1168                   if ( (msg->data.result != CURLE_OK) &&
1169                        (msg->data.result != CURLE_GOT_NOTHING) )
1170                   {
1171                     /* sending msg failed*/
1172                     if (msg->easy_handle == con->put_curl_handle)
1173                     {
1174                       GNUNET_log(GNUNET_ERROR_TYPE_INFO,
1175                                  _("Connection %X: HTTP PUT to peer `%s' (`%s') failed: `%s' `%s'\n"),
1176                                  con,
1177                                  GNUNET_i2s(&cs->identity),
1178                                  http_plugin_address_to_string(NULL, con->addr, con->addrlen),
1179                                  "curl_multi_perform",
1180                                  curl_easy_strerror (msg->data.result));
1181
1182                       con->put_connected = GNUNET_NO;
1183                       curl_easy_cleanup(con->put_curl_handle);
1184                       con->put_curl_handle=NULL;
1185                       if (( NULL != con->pending_msgs_tail) && ( NULL != con->pending_msgs_tail->transmit_cont))
1186                         con->pending_msgs_tail->transmit_cont (con->pending_msgs_tail->transmit_cont_cls,&con->session->identity,GNUNET_SYSERR);
1187                     }
1188                     /* GET connection failed */
1189                     if (msg->easy_handle == con->get_curl_handle)
1190                     {
1191                       GNUNET_log(GNUNET_ERROR_TYPE_INFO,
1192                            _("Connection %X: HTTP GET to peer `%s' (`%s') failed: `%s' `%s'\n"),
1193                            con,
1194                            GNUNET_i2s(&cs->identity),
1195                            http_plugin_address_to_string(NULL, con->addr, con->addrlen),
1196                            "curl_multi_perform",
1197                            curl_easy_strerror (msg->data.result));
1198                       con->get_connected = GNUNET_NO;
1199                       curl_easy_cleanup(con->get_curl_handle);
1200                       con->get_curl_handle=NULL;
1201                     }
1202                   }
1203                   else
1204                   {
1205                     if (msg->easy_handle == con->put_curl_handle)
1206                     {
1207                       GNUNET_assert (CURLE_OK == curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &http_result));
1208                       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1209                                   "Connection %X: HTTP PUT connection to peer `%s' (`%s') was closed with HTTP code %u\n",
1210                                    con,
1211                                    GNUNET_i2s(&cs->identity),
1212                                    http_plugin_address_to_string(NULL, con->addr, con->addrlen),
1213                                    http_result);
1214
1215                       /* Calling transmit continuation  */
1216                       if (( NULL != con->pending_msgs_tail) && (NULL != con->pending_msgs_tail->transmit_cont))
1217                       {
1218                         /* HTTP 1xx : Last message before here was informational */
1219                         if ((http_result >=100) && (http_result < 200))
1220                           con->pending_msgs_tail->transmit_cont (con->pending_msgs_tail->transmit_cont_cls,&cs->identity,GNUNET_OK);
1221                         /* HTTP 2xx: successful operations */
1222                         if ((http_result >=200) && (http_result < 300))
1223                           con->pending_msgs_tail->transmit_cont (con->pending_msgs_tail->transmit_cont_cls,&cs->identity,GNUNET_OK);
1224                         /* HTTP 3xx..5xx: error */
1225                         if ((http_result >=300) && (http_result < 600))
1226                           con->pending_msgs_tail->transmit_cont (con->pending_msgs_tail->transmit_cont_cls,&cs->identity,GNUNET_SYSERR);
1227                       }
1228                       curl_easy_cleanup(con->put_curl_handle);
1229                       con->put_connected = GNUNET_NO;
1230                       con->put_curl_handle=NULL;
1231                     }
1232                     if (msg->easy_handle == con->get_curl_handle)
1233                     {
1234                       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1235                                   "Connection %X: HTTP GET connection to peer `%s' (`%s') was closed with HTTP code %u\n",
1236                                    con,
1237                                    GNUNET_i2s(&cs->identity),
1238                                    http_plugin_address_to_string(NULL, con->addr, con->addrlen),
1239                                    http_result);
1240
1241                       con->get_connected = GNUNET_NO;
1242                       curl_easy_cleanup(con->get_curl_handle);
1243                       con->get_curl_handle=NULL;
1244                     }
1245                   }
1246                   if (con->pending_msgs_tail != NULL)
1247                   {
1248                     if (con->pending_msgs_tail->pos>0)
1249                       remove_http_message(con, con->pending_msgs_tail);
1250                     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Message could not be removed from session `%s'\n", GNUNET_i2s(&cs->identity));
1251                   }
1252                   return;
1253                 default:
1254                   break;
1255                 }
1256
1257             }
1258           while ( (running > 0) );
1259         }
1260       handles_last_run = running;
1261     }
1262   while (mret == CURLM_CALL_MULTI_PERFORM);
1263   send_schedule(plugin, cls);
1264 }
1265
1266
1267 /**
1268  * Function setting up file descriptors and scheduling task to run
1269  * @param ses session to send data to
1270  * @return bytes sent to peer
1271  */
1272 static size_t send_schedule(void *cls, struct Session* ses )
1273 {
1274   struct Plugin *plugin = cls;
1275   fd_set rs;
1276   fd_set ws;
1277   fd_set es;
1278   int max;
1279   struct GNUNET_NETWORK_FDSet *grs;
1280   struct GNUNET_NETWORK_FDSet *gws;
1281   long to;
1282   CURLMcode mret;
1283
1284   GNUNET_assert(cls !=NULL);
1285   max = -1;
1286   FD_ZERO (&rs);
1287   FD_ZERO (&ws);
1288   FD_ZERO (&es);
1289   mret = curl_multi_fdset (plugin->multi_handle, &rs, &ws, &es, &max);
1290   if (mret != CURLM_OK)
1291     {
1292       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1293                   _("%s failed at %s:%d: `%s'\n"),
1294                   "curl_multi_fdset", __FILE__, __LINE__,
1295                   curl_multi_strerror (mret));
1296       return -1;
1297     }
1298   mret = curl_multi_timeout (plugin->multi_handle, &to);
1299   if (mret != CURLM_OK)
1300     {
1301       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1302                   _("%s failed at %s:%d: `%s'\n"),
1303                   "curl_multi_timeout", __FILE__, __LINE__,
1304                   curl_multi_strerror (mret));
1305       return -1;
1306     }
1307
1308   grs = GNUNET_NETWORK_fdset_create ();
1309   gws = GNUNET_NETWORK_fdset_create ();
1310   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
1311   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
1312   plugin->http_server_task_send = GNUNET_SCHEDULER_add_select (plugin->env->sched,
1313                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
1314                                    GNUNET_SCHEDULER_NO_TASK,
1315                                    GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 0),
1316                                    grs,
1317                                    gws,
1318                                    &send_execute,
1319                                    plugin);
1320   GNUNET_NETWORK_fdset_destroy (gws);
1321   GNUNET_NETWORK_fdset_destroy (grs);
1322
1323   /* FIXME: return bytes REALLY sent */
1324   return 0;
1325 }
1326
1327
1328 /**
1329  * Function that can be used by the transport service to transmit
1330  * a message using the plugin.   Note that in the case of a
1331  * peer disconnecting, the continuation MUST be called
1332  * prior to the disconnect notification itself.  This function
1333  * will be called with this peer's HELLO message to initiate
1334  * a fresh connection to another peer.
1335  *
1336  * @param cls closure
1337  * @param target who should receive this message
1338  * @param msgbuf the message to transmit
1339  * @param msgbuf_size number of bytes in 'msgbuf'
1340  * @param priority how important is the message (most plugins will
1341  *                 ignore message priority and just FIFO)
1342  * @param timeout how long to wait at most for the transmission (does not
1343  *                require plugins to discard the message after the timeout,
1344  *                just advisory for the desired delay; most plugins will ignore
1345  *                this as well)
1346  * @param session which session must be used (or NULL for "any")
1347  * @param addr the address to use (can be NULL if the plugin
1348  *                is "on its own" (i.e. re-use existing TCP connection))
1349  * @param addrlen length of the address in bytes
1350  * @param force_address GNUNET_YES if the plugin MUST use the given address,
1351  *                GNUNET_NO means the plugin may use any other address and
1352  *                GNUNET_SYSERR means that only reliable existing
1353  *                bi-directional connections should be used (regardless
1354  *                of address)
1355  * @param cont continuation to call once the message has
1356  *        been transmitted (or if the transport is ready
1357  *        for the next transmission call; or if the
1358  *        peer disconnected...); can be NULL
1359  * @param cont_cls closure for cont
1360  * @return number of bytes used (on the physical network, with overheads);
1361  *         -1 on hard errors (i.e. address invalid); 0 is a legal value
1362  *         and does NOT mean that the message was not transmitted (DV)
1363  */
1364 static ssize_t
1365 http_plugin_send (void *cls,
1366                   const struct GNUNET_PeerIdentity *target,
1367                   const char *msgbuf,
1368                   size_t msgbuf_size,
1369                   unsigned int priority,
1370                   struct GNUNET_TIME_Relative to,
1371                   struct Session *session,
1372                   const void *addr,
1373                   size_t addrlen,
1374                   int force_address,
1375                   GNUNET_TRANSPORT_TransmitContinuation cont,
1376                   void *cont_cls)
1377 {
1378   struct Plugin *plugin = cls;
1379   struct Session *cs;
1380   struct HTTP_Message *msg;
1381   struct HTTP_Connection *con;
1382   //unsigned int ret;
1383
1384   GNUNET_assert(cls !=NULL);
1385   GNUNET_assert ((addr!=NULL) && (addrlen != 0));
1386
1387   /* get session from hashmap */
1388   cs = session_get(plugin, target);
1389   con = session_check_outbound_address(plugin, cs, addr, addrlen);
1390
1391   char * force = GNUNET_malloc(30);
1392
1393   if (force_address == GNUNET_YES)
1394     strcpy(force,"forced addr.");
1395   if (force_address == GNUNET_NO)
1396     strcpy(force,"any addr.");
1397   if (force_address == GNUNET_SYSERR)
1398     strcpy(force,"reliable bi-direc. address addr.");
1399   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Transport tells me to send %u bytes to `%s' %s (%s), session: %X\n",
1400                                       msgbuf_size,
1401                                       GNUNET_i2s(&cs->identity),
1402                                       force,
1403                                       http_plugin_address_to_string(NULL, addr, addrlen),
1404                                       session);
1405
1406   //GNUNET_free(force);
1407   /* create msg */
1408   msg = GNUNET_malloc (sizeof (struct HTTP_Message) + msgbuf_size);
1409   msg->next = NULL;
1410   msg->size = msgbuf_size;
1411   msg->pos = 0;
1412   msg->buf = (char *) &msg[1];
1413   msg->transmit_cont = cont;
1414   msg->transmit_cont_cls = cont_cls;
1415   memcpy (msg->buf,msgbuf, msgbuf_size);
1416
1417   /* must use this address */
1418   if (force_address == GNUNET_YES)
1419   {
1420     /* enqueue in connection message queue */
1421     GNUNET_CONTAINER_DLL_insert(con->pending_msgs_head,con->pending_msgs_tail,msg);
1422   }
1423   /* can use existing connection to send */
1424   else
1425   {
1426     /* enqueue in connection message queue */
1427     GNUNET_CONTAINER_DLL_insert(con->pending_msgs_head,con->pending_msgs_tail,msg);
1428   }
1429   return send_check_connections (plugin, cs, con);
1430 }
1431
1432
1433
1434 /**
1435  * Function that can be used to force the plugin to disconnect
1436  * from the given peer and cancel all previous transmissions
1437  * (and their continuationc).
1438  *
1439  * @param cls closure
1440  * @param target peer from which to disconnect
1441  */
1442 static void
1443 http_plugin_disconnect (void *cls,
1444                             const struct GNUNET_PeerIdentity *target)
1445 {
1446   struct Plugin *plugin = cls;
1447   struct HTTP_Connection *con;
1448   struct Session *cs;
1449
1450   /* get session from hashmap */
1451   cs = session_get(plugin, target);
1452   con = cs->outbound_connections_head;
1453
1454   while (con!=NULL)
1455   {
1456     if (con->put_curl_handle!=NULL)
1457       curl_easy_cleanup(con->put_curl_handle);
1458     con->put_curl_handle=NULL;
1459     con->put_connected = GNUNET_NO;
1460     while (con->pending_msgs_head!=NULL)
1461     {
1462       remove_http_message(con, con->pending_msgs_head);
1463     }
1464     con=con->next;
1465   }
1466 }
1467
1468
1469 /**
1470  * Convert the transports address to a nice, human-readable
1471  * format.
1472  *
1473  * @param cls closure
1474  * @param type name of the transport that generated the address
1475  * @param addr one of the addresses of the host, NULL for the last address
1476  *        the specific address format depends on the transport
1477  * @param addrlen length of the address
1478  * @param numeric should (IP) addresses be displayed in numeric form?
1479  * @param timeout after how long should we give up?
1480  * @param asc function to call on each string
1481  * @param asc_cls closure for asc
1482  */
1483 static void
1484 http_plugin_address_pretty_printer (void *cls,
1485                                         const char *type,
1486                                         const void *addr,
1487                                         size_t addrlen,
1488                                         int numeric,
1489                                         struct GNUNET_TIME_Relative timeout,
1490                                         GNUNET_TRANSPORT_AddressStringCallback
1491                                         asc, void *asc_cls)
1492 {
1493   const struct IPv4HttpAddress *t4;
1494   const struct IPv6HttpAddress *t6;
1495   struct sockaddr_in a4;
1496   struct sockaddr_in6 a6;
1497   char * address;
1498   char * ret;
1499   unsigned int port;
1500   unsigned int res;
1501
1502   GNUNET_assert(cls !=NULL);
1503   if (addrlen == sizeof (struct IPv6HttpAddress))
1504   {
1505     address = GNUNET_malloc (INET6_ADDRSTRLEN);
1506     t6 = addr;
1507     a6.sin6_addr = t6->ipv6_addr;
1508     inet_ntop(AF_INET6, &(a6.sin6_addr),address,INET6_ADDRSTRLEN);
1509     port = ntohs(t6->u6_port);
1510   }
1511   else if (addrlen == sizeof (struct IPv4HttpAddress))
1512   {
1513     address = GNUNET_malloc (INET_ADDRSTRLEN);
1514     t4 = addr;
1515     a4.sin_addr.s_addr =  t4->ipv4_addr;
1516     inet_ntop(AF_INET, &(a4.sin_addr),address,INET_ADDRSTRLEN);
1517     port = ntohs(t4->u_port);
1518   }
1519   else
1520   {
1521     /* invalid address */
1522     GNUNET_break_op (0);
1523     asc (asc_cls, NULL);
1524     return;
1525   }
1526   res = GNUNET_asprintf(&ret,"http://%s:%u/",address,port);
1527   GNUNET_free (address);
1528   GNUNET_assert(res != 0);
1529
1530   asc (asc_cls, ret);
1531 }
1532
1533
1534
1535 /**
1536  * Another peer has suggested an address for this
1537  * peer and transport plugin.  Check that this could be a valid
1538  * address.  If so, consider adding it to the list
1539  * of addresses.
1540  *
1541  * @param cls closure
1542  * @param addr pointer to the address
1543  * @param addrlen length of addr
1544  * @return GNUNET_OK if this is a plausible address for this peer
1545  *         and transport
1546  */
1547 static int
1548 http_plugin_address_suggested (void *cls,
1549                                const void *addr, size_t addrlen)
1550 {
1551   struct Plugin *plugin = cls;
1552   struct IPv4HttpAddress *v4;
1553   struct IPv6HttpAddress *v6;
1554   unsigned int port;
1555
1556   GNUNET_assert(cls !=NULL);
1557   if ((addrlen != sizeof (struct IPv4HttpAddress)) &&
1558       (addrlen != sizeof (struct IPv6HttpAddress)))
1559     {
1560       return GNUNET_SYSERR;
1561     }
1562   if (addrlen == sizeof (struct IPv4HttpAddress))
1563     {
1564       v4 = (struct IPv4HttpAddress *) addr;
1565       if (INADDR_LOOPBACK == ntohl(v4->ipv4_addr))
1566       {
1567         return GNUNET_SYSERR;
1568       }
1569       port = ntohs (v4->u_port);
1570       if (port != plugin->port_inbound)
1571       {
1572         return GNUNET_SYSERR;
1573       }
1574     }
1575   else
1576     {
1577       v6 = (struct IPv6HttpAddress *) addr;
1578       if (IN6_IS_ADDR_LINKLOCAL (&v6->ipv6_addr))
1579         {
1580           return GNUNET_SYSERR;
1581         }
1582       port = ntohs (v6->u6_port);
1583       if (port != plugin->port_inbound)
1584       {
1585         return GNUNET_SYSERR;
1586       }
1587     }
1588   return GNUNET_OK;
1589 }
1590
1591
1592 /**
1593  * Function called for a quick conversion of the binary address to
1594  * a numeric address.  Note that the caller must not free the
1595  * address and that the next call to this function is allowed
1596  * to override the address again.
1597  *
1598  * @param cls closure
1599  * @param addr binary address
1600  * @param addrlen length of the address
1601  * @return string representing the same address
1602  */
1603 static const char*
1604 http_plugin_address_to_string (void *cls,
1605                                    const void *addr,
1606                                    size_t addrlen)
1607 {
1608   const struct IPv4HttpAddress *t4;
1609   const struct IPv6HttpAddress *t6;
1610   struct sockaddr_in a4;
1611   struct sockaddr_in6 a6;
1612   char * address;
1613   char * ret;
1614   uint16_t port;
1615   unsigned int res;
1616
1617   if (addrlen == sizeof (struct IPv6HttpAddress))
1618     {
1619       address = GNUNET_malloc (INET6_ADDRSTRLEN);
1620       t6 = addr;
1621       a6.sin6_addr = t6->ipv6_addr;
1622       inet_ntop(AF_INET6, &(a6.sin6_addr),address,INET6_ADDRSTRLEN);
1623       port = ntohs(t6->u6_port);
1624     }
1625   else if (addrlen == sizeof (struct IPv4HttpAddress))
1626     {
1627       address = GNUNET_malloc (INET_ADDRSTRLEN);
1628       t4 = addr;
1629       a4.sin_addr.s_addr =  t4->ipv4_addr;
1630       inet_ntop(AF_INET, &(a4.sin_addr),address,INET_ADDRSTRLEN);
1631       port = ntohs(t4->u_port);
1632     }
1633   else
1634     {
1635       /* invalid address */
1636       return NULL;
1637     }
1638   res = GNUNET_asprintf(&ret,"%s:%u",address,port);
1639   GNUNET_free (address);
1640   GNUNET_assert(res != 0);
1641   return ret;
1642 }
1643
1644 /**
1645  * Add the IP of our network interface to the list of
1646  * our external IP addresses.
1647  *
1648  * @param cls the 'struct Plugin*'
1649  * @param name name of the interface
1650  * @param isDefault do we think this may be our default interface
1651  * @param addr address of the interface
1652  * @param addrlen number of bytes in addr
1653  * @return GNUNET_OK to continue iterating
1654  */
1655 static int
1656 process_interfaces (void *cls,
1657                     const char *name,
1658                     int isDefault,
1659                     const struct sockaddr *addr, socklen_t addrlen)
1660 {
1661   struct Plugin *plugin = cls;
1662   struct IPv4HttpAddress * t4;
1663   struct IPv6HttpAddress * t6;
1664   int af;
1665
1666   GNUNET_assert(cls !=NULL);
1667   af = addr->sa_family;
1668   if (af == AF_INET)
1669     {
1670       t4 = GNUNET_malloc(sizeof(struct IPv4HttpAddress));
1671       if (INADDR_LOOPBACK == ntohl(((struct sockaddr_in *) addr)->sin_addr.s_addr))
1672       {
1673         /* skip loopback addresses */
1674         return GNUNET_OK;
1675       }
1676       t4->ipv4_addr = ((struct sockaddr_in *) addr)->sin_addr.s_addr;
1677       t4->u_port = htons (plugin->port_inbound);
1678       plugin->env->notify_address(plugin->env->cls,"http",t4, sizeof (struct IPv4HttpAddress), GNUNET_TIME_UNIT_FOREVER_REL);
1679
1680     }
1681   else if (af == AF_INET6)
1682     {
1683       t6 = GNUNET_malloc(sizeof(struct IPv6HttpAddress));
1684       if (IN6_IS_ADDR_LINKLOCAL (&((struct sockaddr_in6 *) addr)->sin6_addr))
1685         {
1686           /* skip link local addresses */
1687           return GNUNET_OK;
1688         }
1689       if (IN6_IS_ADDR_LOOPBACK (&((struct sockaddr_in6 *) addr)->sin6_addr))
1690         {
1691           /* skip loopback addresses */
1692           return GNUNET_OK;
1693         }
1694       memcpy (&t6->ipv6_addr,
1695               &((struct sockaddr_in6 *) addr)->sin6_addr,
1696               sizeof (struct in6_addr));
1697       t6->u6_port = htons (plugin->port_inbound);
1698       plugin->env->notify_address(plugin->env->cls,"http",t6,sizeof (struct IPv6HttpAddress) , GNUNET_TIME_UNIT_FOREVER_REL);
1699     }
1700   return GNUNET_OK;
1701 }
1702 int hashMapFreeIterator (void *cls, const GNUNET_HashCode *key, void *value)
1703 {
1704   struct Session * cs = value;
1705   struct HTTP_Connection * con = cs->outbound_connections_head;
1706   struct HTTP_Connection * tmp_con = cs->outbound_connections_head;
1707   struct HTTP_Message * msg = NULL;
1708   struct HTTP_Message * tmp_msg = NULL;
1709
1710   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Freeing session for peer `%s'\n",GNUNET_i2s(&cs->identity));
1711
1712   /* freeing connections */
1713   while (con!=NULL)
1714   {
1715     GNUNET_free(con->url);
1716     if (con->put_curl_handle!=NULL)
1717       curl_easy_cleanup(con->put_curl_handle);
1718     con->put_curl_handle = NULL;
1719     msg = con->pending_msgs_head;
1720     while (msg!=NULL)
1721     {
1722       tmp_msg=msg->next;
1723       GNUNET_free(msg);
1724       msg = tmp_msg;
1725     }
1726     tmp_con=con->next;
1727     GNUNET_free(con);
1728     con=tmp_con;
1729   }
1730   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"All sessions freed \n");
1731
1732   GNUNET_free (cs);
1733   return GNUNET_YES;
1734 }
1735
1736 /**
1737  * Exit point from the plugin.
1738  */
1739 void *
1740 libgnunet_plugin_transport_http_done (void *cls)
1741 {
1742   struct GNUNET_TRANSPORT_PluginFunctions *api = cls;
1743   struct Plugin *plugin = api->cls;
1744   CURLMcode mret;
1745
1746   GNUNET_assert(cls !=NULL);
1747
1748
1749   if ( plugin->http_server_task_v4 != GNUNET_SCHEDULER_NO_TASK)
1750   {
1751     GNUNET_SCHEDULER_cancel(plugin->env->sched, plugin->http_server_task_v4);
1752     plugin->http_server_task_v4 = GNUNET_SCHEDULER_NO_TASK;
1753   }
1754
1755   if ( plugin->http_server_task_v6 != GNUNET_SCHEDULER_NO_TASK)
1756   {
1757     GNUNET_SCHEDULER_cancel(plugin->env->sched, plugin->http_server_task_v6);
1758     plugin->http_server_task_v6 = GNUNET_SCHEDULER_NO_TASK;
1759   }
1760
1761   if ( plugin->http_server_task_send != GNUNET_SCHEDULER_NO_TASK)
1762   {
1763     GNUNET_SCHEDULER_cancel(plugin->env->sched, plugin->http_server_task_send);
1764     plugin->http_server_task_send = GNUNET_SCHEDULER_NO_TASK;
1765   }
1766
1767   if (plugin->http_server_daemon_v4 != NULL)
1768   {
1769     MHD_stop_daemon (plugin->http_server_daemon_v4);
1770     plugin->http_server_daemon_v4 = NULL;
1771   }
1772   if (plugin->http_server_daemon_v6 != NULL)
1773   {
1774     MHD_stop_daemon (plugin->http_server_daemon_v6);
1775     plugin->http_server_daemon_v6 = NULL;
1776   }
1777
1778   /* free all sessions */
1779   GNUNET_CONTAINER_multihashmap_iterate (plugin->sessions,
1780                                          &hashMapFreeIterator,
1781                                          NULL);
1782
1783   GNUNET_CONTAINER_multihashmap_destroy (plugin->sessions);
1784
1785   mret = curl_multi_cleanup(plugin->multi_handle);
1786   if ( CURLM_OK != mret)
1787     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"curl multihandle clean up failed");
1788
1789   GNUNET_free (plugin);
1790   GNUNET_free (api);
1791   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Unload http plugin complete...\n");
1792   return NULL;
1793 }
1794
1795
1796 /**
1797  * Entry point for the plugin.
1798  */
1799 void *
1800 libgnunet_plugin_transport_http_init (void *cls)
1801 {
1802   struct GNUNET_TRANSPORT_PluginEnvironment *env = cls;
1803   struct Plugin *plugin;
1804   struct GNUNET_TRANSPORT_PluginFunctions *api;
1805   struct GNUNET_TIME_Relative gn_timeout;
1806   long long unsigned int port;
1807
1808   GNUNET_assert(cls !=NULL);
1809   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Starting http plugin...\n");
1810
1811   plugin = GNUNET_malloc (sizeof (struct Plugin));
1812   plugin->env = env;
1813   plugin->sessions = NULL;
1814
1815   api = GNUNET_malloc (sizeof (struct GNUNET_TRANSPORT_PluginFunctions));
1816   api->cls = plugin;
1817   api->send = &http_plugin_send;
1818   api->disconnect = &http_plugin_disconnect;
1819   api->address_pretty_printer = &http_plugin_address_pretty_printer;
1820   api->check_address = &http_plugin_address_suggested;
1821   api->address_to_string = &http_plugin_address_to_string;
1822
1823   /* Hashing our identity to use it in URLs */
1824   GNUNET_CRYPTO_hash_to_enc ( &(plugin->env->my_identity->hashPubKey), &plugin->my_ascii_hash_ident);
1825
1826   /* Reading port number from config file */
1827   if ((GNUNET_OK !=
1828        GNUNET_CONFIGURATION_get_value_number (env->cfg,
1829                                               "transport-http",
1830                                               "PORT",
1831                                               &port)) ||
1832       (port > 65535) )
1833     {
1834       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
1835                        "http",
1836                        _
1837                        ("Require valid port number for transport plugin `%s' in configuration!\n"),
1838                        "transport-http");
1839       libgnunet_plugin_transport_http_done (api);
1840       return NULL;
1841     }
1842   GNUNET_assert ((port > 0) && (port <= 65535));
1843   plugin->port_inbound = port;
1844   gn_timeout = GNUNET_CONSTANTS_IDLE_CONNECTION_TIMEOUT;
1845   if ((plugin->http_server_daemon_v4 == NULL) && (plugin->http_server_daemon_v6 == NULL) && (port != 0))
1846     {
1847     plugin->http_server_daemon_v6 = MHD_start_daemon (MHD_USE_IPv6,
1848                                        port,
1849                                        &acceptPolicyCallback,
1850                                        plugin , &accessHandlerCallback, plugin,
1851                                        MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 16,
1852                                        MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
1853                                        MHD_OPTION_CONNECTION_TIMEOUT, (gn_timeout.value / 1000),
1854                                        MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (16 * 1024),
1855                                        MHD_OPTION_NOTIFY_COMPLETED, &requestCompletedCallback, NULL,
1856                                        MHD_OPTION_END);
1857     plugin->http_server_daemon_v4 = MHD_start_daemon (MHD_NO_FLAG,
1858                                        port,
1859                                        &acceptPolicyCallback,
1860                                        plugin , &accessHandlerCallback, plugin,
1861                                        MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 16,
1862                                        MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
1863                                        MHD_OPTION_CONNECTION_TIMEOUT, (gn_timeout.value / 1000),
1864                                        MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (16 * 1024),
1865                                        MHD_OPTION_NOTIFY_COMPLETED, &requestCompletedCallback, NULL,
1866                                        MHD_OPTION_END);
1867     }
1868   if (plugin->http_server_daemon_v4 != NULL)
1869     plugin->http_server_task_v4 = http_server_daemon_prepare (plugin, plugin->http_server_daemon_v4);
1870   if (plugin->http_server_daemon_v6 != NULL)
1871     plugin->http_server_task_v6 = http_server_daemon_prepare (plugin, plugin->http_server_daemon_v6);
1872
1873   if (plugin->http_server_task_v4 != GNUNET_SCHEDULER_NO_TASK)
1874     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Starting MHD with IPv4 on port %u\n",port);
1875   else if (plugin->http_server_task_v6 != GNUNET_SCHEDULER_NO_TASK)
1876     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"Starting MHD with IPv4 and IPv6 on port %u\n",port);
1877   else
1878   {
1879     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,"No MHD was started, transport plugin not functional!\n");
1880     libgnunet_plugin_transport_http_done (api);
1881     return NULL;
1882   }
1883
1884   /* Initializing cURL */
1885   curl_global_init(CURL_GLOBAL_ALL);
1886   plugin->multi_handle = curl_multi_init();
1887
1888   if ( NULL == plugin->multi_handle )
1889   {
1890     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
1891                      "http",
1892                      _("Could not initialize curl multi handle, failed to start http plugin!\n"),
1893                      "transport-http");
1894     libgnunet_plugin_transport_http_done (api);
1895     return NULL;
1896   }
1897
1898   plugin->sessions = GNUNET_CONTAINER_multihashmap_create (10);
1899   GNUNET_OS_network_interfaces_list (&process_interfaces, plugin);
1900
1901   return api;
1902 }
1903
1904 /* end of plugin_transport_http.c */