Added socket stream and socket into SilcClientConnection context.
[silc.git] / lib / silchttp / silchttpserver.c
1 /*
2
3   silchttpserver.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2006 - 2007 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silc.h"
21 #include "silchttpserver.h"
22
23 /************************** Types and definitions ***************************/
24
25 #define SILC_HTTP_SERVER_TIMEOUT  120   /* Connection timeout */
26 #define SILC_HTTP_SERVER_CONNS    2     /* Default number of connections */
27 #define SILC_HTTP_SERVER_BUFLEN   1024  /* Default data buffer length */
28 #define SILC_HTTP_SERVER_HEADER   "HTTP/1.1 200 OK\r\nServer: SILCHTTP/1.0\r\n"
29
30 /* HTTP server context */
31 struct SilcHttpServerStruct {
32   SilcNetListener listener;         /* Server listener */
33   SilcSchedule schedule;            /* Scheduler */
34   SilcList allconns;                /* All connections */
35   SilcList conns;                   /* Connection free list */
36   SilcHttpServerCallback callback;  /* Requset callback */
37   void *context;                    /* Request callback context */
38 };
39
40 /* HTTP connection context */
41 struct SilcHttpConnectionStruct {
42   struct SilcHttpConnectionStruct *next;
43   struct SilcHttpConnectionStruct *next2;
44   SilcHttpServer httpd;             /* Server */
45   SilcStream stream;                /* Connection stream */
46   SilcBuffer inbuf;                 /* Read data buffer */
47   SilcBuffer outbuf;                /* Write data buffer */
48   SilcInt64 touched;                /* Time last connection was touched */
49   SilcMime curheaders;              /* HTTP request headers */
50   SilcMime headers;                 /* HTTP reply headers */
51   unsigned char *hptr;              /* Pointer to start of headers */
52   char *method;                     /* Method */
53   char *uri;                        /* URI */
54   unsigned int keepalive    : 1;    /* Keep alive */
55 };
56
57 /************************ Static utility functions **************************/
58
59 /* Close HTTP connection */
60
61 static void silc_http_server_close_connection(SilcHttpConnection conn)
62 {
63   if (conn->headers) {
64     silc_mime_free(conn->headers);
65     conn->headers = NULL;
66   }
67   if (conn->curheaders) {
68     silc_mime_free(conn->curheaders);
69     conn->curheaders = NULL;
70   }
71   silc_buffer_clear(conn->inbuf);
72   silc_buffer_clear(conn->outbuf);
73   silc_buffer_reset(conn->inbuf);
74   silc_buffer_reset(conn->outbuf);
75   conn->hptr = conn->method = conn->uri = NULL;
76
77   if (conn->keepalive)
78     return;
79
80   SILC_LOG_DEBUG(("Closing HTTP connection %p", conn));
81
82   silc_schedule_task_del_by_context(conn->httpd->schedule, conn);
83   silc_stream_set_notifier(conn->stream, conn->httpd->schedule, NULL, NULL);
84   silc_stream_destroy(conn->stream);
85   conn->stream = NULL;
86
87   /* Add to free list */
88   silc_list_add(conn->httpd->conns, conn);
89 }
90
91 /* Parse HTTP data */
92
93 static SilcBool silc_http_server_parse(SilcHttpServer httpd,
94                                        SilcHttpConnection conn)
95 {
96   SilcUInt32 data_len, cll;
97   unsigned char *data, *tmp;
98   const char *value, *cl;
99   SilcBufferStruct postdata;
100   int i;
101
102   SILC_LOG_DEBUG(("Parsing HTTP data"));
103
104   data = silc_buffer_data(conn->inbuf);
105   data_len = silc_buffer_len(conn->inbuf);
106
107   /* Check for end of headers */
108   for (i = 0; i < data_len ; i++) {
109     if (data_len - i >= 4 &&
110         data[i    ] == '\r' && data[i + 1] == '\n' &&
111         data[i + 2] == '\r' && data[i + 3] == '\n')
112       break;
113   }
114   if (i == data_len)
115     return TRUE;
116
117   SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(conn->inbuf),
118                    silc_buffer_len(conn->inbuf));
119
120   if (!conn->method && !conn->uri) {
121     tmp = memchr(data, '\n', data_len);
122     if (!tmp || tmp[-1] != '\r') {
123       if (data_len < SILC_HTTP_SERVER_BUFLEN)
124         return TRUE;
125       return FALSE;
126     }
127     *tmp = 0;
128
129     /* Get method */
130     if (strchr(data, ' '))
131       *strchr(data, ' ') = 0;
132     conn->method = data;
133     SILC_LOG_DEBUG(("Method: '%s'", conn->method));
134
135     /* Get URI */
136     tmp = memchr(data, '\0', data_len);
137     if (!tmp) {
138       if (data_len < SILC_HTTP_SERVER_BUFLEN)
139         return TRUE;
140       return FALSE;
141     }
142     tmp++;
143     if (strchr(tmp, ' '))
144       *strchr(tmp, ' ') = 0;
145     conn->uri = tmp;
146     SILC_LOG_DEBUG(("URI: '%s'", conn->uri));
147
148     /* Protocol version compatibility */
149     tmp = ((unsigned char *)memchr(tmp, '\0', data_len - (tmp - data))) + 1;
150     SILC_LOG_DEBUG(("Protocol: %s", tmp));
151     if (strstr(tmp, "HTTP/1.0"))
152       conn->keepalive = FALSE;
153     if (strstr(tmp, "HTTP/1.1"))
154       conn->keepalive = TRUE;
155     if (strstr(tmp, "HTTP/1.2"))
156       conn->keepalive = TRUE;
157
158     /* Get HTTP headers */
159     tmp = memchr(tmp, '\0', data_len - (tmp - data));
160     if (!tmp) {
161       if (data_len < SILC_HTTP_SERVER_BUFLEN)
162         return TRUE;
163       return FALSE;
164     }
165     if (data_len - (tmp - data) < 2) {
166       if (data_len < SILC_HTTP_SERVER_BUFLEN)
167         return TRUE;
168       return FALSE;
169     }
170     conn->hptr = ++tmp;
171   }
172
173   /* Parse headers and data area */
174   conn->curheaders = silc_mime_decode(NULL, conn->hptr,
175                                       data_len - (conn->hptr - data));
176   if (!conn->curheaders)
177     return FALSE;
178
179   /* Check for persistent connection */
180   value = silc_mime_get_field(conn->curheaders, "Connection");
181   if (value && !strcasecmp(value, "close"))
182     conn->keepalive = FALSE;
183
184   /* Deliver request to caller */
185   if (!strcasecmp(conn->method, "GET") || !strcasecmp(conn->method, "HEAD")) {
186     httpd->callback(httpd, conn, conn->uri, conn->method,
187                     NULL, httpd->context);
188
189   } else if (!strcasecmp(conn->method, "POST")) {
190     /* Get POST data */
191     tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len);
192     if (!tmp)
193       return FALSE;
194
195     /* Check we have received all data */
196     cl = silc_mime_get_field(conn->curheaders, "Content-Length");
197     if (cl && sscanf(cl, "%lu", &cll) == 1) {
198       if (data_len < cll) {
199         /* More data to come */
200         silc_mime_free(conn->curheaders);
201         conn->curheaders = NULL;
202         return TRUE;
203       }
204     }
205
206     silc_buffer_set(&postdata, tmp, data_len);
207     SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len);
208
209     httpd->callback(httpd, conn, conn->uri, conn->method,
210                     &postdata, httpd->context);
211   } else {
212     /* Send bad request */
213     silc_http_server_send_error(httpd, conn, "400 Bad Request",
214                                 "<body><h1>400 Bad Request</h1><body>");
215     return TRUE;
216   }
217
218   return TRUE;
219 }
220
221 /* Send HTTP data to connection */
222
223 static SilcBool silc_http_server_send_internal(SilcHttpServer httpd,
224                                                SilcHttpConnection conn,
225                                                SilcBuffer data,
226                                                SilcBool headers)
227 {
228   int ret;
229
230   SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data),
231                    silc_buffer_len(data));
232
233   /* Write the packet to the stream */
234   while (silc_buffer_len(data) > 0) {
235     ret = silc_stream_write(conn->stream, silc_buffer_data(data),
236                             silc_buffer_len(data));
237     if (ret == 0 || ret == - 2)
238       return FALSE;
239
240     if (ret == -1) {
241       /* Cannot write now, write later. */
242       if (silc_buffer_len(data) - ret >= silc_buffer_taillen(conn->outbuf))
243         if (!silc_buffer_realloc(conn->outbuf,
244                                  silc_buffer_truelen(conn->outbuf) +
245                                  silc_buffer_len(data) - ret)) {
246           conn->keepalive = FALSE;
247           silc_http_server_close_connection(conn);
248           return FALSE;
249         }
250       silc_buffer_pull_tail(conn->outbuf, silc_buffer_len(data) - ret);
251       silc_buffer_put(conn->outbuf, silc_buffer_data(data) + ret,
252                       silc_buffer_len(data) - ret);
253       return TRUE;
254     }
255
256     /* Wrote data */
257     silc_buffer_pull(data, ret);
258   }
259
260   if (!headers) {
261     /* Data sent, close connection */
262     SILC_LOG_DEBUG(("Data sent %p", conn));
263     silc_http_server_close_connection(conn);
264   }
265
266   return TRUE;
267 }
268
269 /* Allocate connection context */
270
271 static SilcHttpConnection silc_http_server_alloc_connection(void)
272 {
273   SilcHttpConnection conn;
274
275   conn = silc_calloc(1, sizeof(*conn));
276   if (!conn)
277     return NULL;
278
279   conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
280   if (!conn->inbuf) {
281     silc_free(conn);
282     return NULL;
283   }
284
285   conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
286   if (!conn->outbuf) {
287     silc_buffer_free(conn->inbuf);
288     silc_free(conn);
289     return NULL;
290   }
291
292   silc_buffer_reset(conn->inbuf);
293   silc_buffer_reset(conn->outbuf);
294
295   return conn;
296 }
297
298 /* Check if connection has timedout */
299
300 SILC_TASK_CALLBACK(silc_http_server_connection_timeout)
301 {
302   SilcHttpConnection conn = context;
303   SilcInt64 curtime = silc_time();
304
305   if (curtime - conn->touched > SILC_HTTP_SERVER_TIMEOUT) {
306     SILC_LOG_DEBUG(("Connection timeout %p", conn));
307     conn->keepalive = FALSE;
308     silc_http_server_close_connection(conn);
309     return;
310   }
311
312   silc_schedule_task_add_timeout(conn->httpd->schedule,
313                                  silc_http_server_connection_timeout, conn,
314                                  SILC_HTTP_SERVER_TIMEOUT, 0);
315 }
316
317 /* Data I/O callback */
318
319 static void silc_http_server_io(SilcStream stream, SilcStreamStatus status,
320                                 void *context)
321 {
322   SilcHttpConnection conn = context;
323   SilcHttpServer httpd = conn->httpd;
324   int ret;
325
326   switch (status) {
327   case SILC_STREAM_CAN_READ:
328     SILC_LOG_DEBUG(("Read HTTP data %p", conn));
329
330     conn->touched = silc_time();
331
332     /* Make sure we have fair amount of free space in inbuf */
333     if (silc_buffer_taillen(conn->inbuf) < SILC_HTTP_SERVER_BUFLEN)
334       if (!silc_buffer_realloc(conn->inbuf, silc_buffer_truelen(conn->inbuf) +
335                                SILC_HTTP_SERVER_BUFLEN * 2)) {
336         conn->keepalive = FALSE;
337         silc_http_server_close_connection(conn);
338         return;
339       }
340
341     /* Read data from stream */
342     ret = silc_stream_read(conn->stream, conn->inbuf->tail,
343                            silc_buffer_taillen(conn->inbuf));
344
345     if (ret == 0 || ret == -2) {
346       conn->keepalive = FALSE;
347       silc_http_server_close_connection(conn);
348       return;
349     }
350
351     if (ret == -1) {
352       /* Cannot read now, do it later. */
353       silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf));
354       return;
355     }
356
357     SILC_LOG_DEBUG(("Read %d bytes data", ret));
358
359     /* Parse the data */
360     silc_buffer_pull_tail(conn->inbuf, ret);
361     if (!silc_http_server_parse(httpd, conn)) {
362       conn->keepalive = FALSE;
363       silc_http_server_close_connection(conn);
364     }
365
366     break;
367
368   case SILC_STREAM_CAN_WRITE:
369     SILC_LOG_DEBUG(("Write HTTP data %p", conn));
370
371     conn->touched = silc_time();
372
373     /* Write pending data to stream */
374     while (silc_buffer_len(conn->outbuf) > 0) {
375       ret = silc_stream_write(conn->stream, silc_buffer_data(conn->outbuf),
376                               silc_buffer_len(conn->outbuf));
377
378       if (ret == 0 || ret == -2) {
379         conn->keepalive = FALSE;
380         silc_http_server_close_connection(conn);
381         return;
382       }
383
384       if (ret == -1)
385         /* Cannot write now, write later. */
386         return;
387
388       /* Wrote data */
389       silc_buffer_pull(conn->outbuf, ret);
390     }
391
392     /* Data sent, close connection */
393     SILC_LOG_DEBUG(("Data sent"));
394     silc_http_server_close_connection(conn);
395     break;
396
397   default:
398     conn->keepalive = FALSE;
399     silc_http_server_close_connection(conn);
400     break;
401   }
402 }
403
404 /* Accepts new connection */
405
406 static void silc_http_server_new_connection(SilcResult status,
407                                             SilcStream stream,
408                                             void *context)
409 {
410   SilcHttpServer httpd = context;
411   SilcHttpConnection conn;
412   const char *hostname = NULL, *ip = NULL;
413
414   /* Get free connection */
415   silc_list_start(httpd->conns);
416   conn = silc_list_get(httpd->conns);
417   if (!conn) {
418     /* Add new connection */
419     conn = silc_http_server_alloc_connection();
420     if (!conn) {
421       silc_stream_destroy(stream);
422       return;
423     }
424     silc_list_add(httpd->allconns, conn);
425   }
426   silc_list_del(httpd->conns, conn);
427
428   conn->httpd = httpd;
429   conn->stream = stream;
430
431   silc_socket_stream_get_info(stream, NULL, &hostname, &ip, NULL);
432   SILC_LOG_INFO(("HTTPD: New connection %s (%s)", hostname, ip));
433   SILC_LOG_DEBUG(("New connection %p", conn));
434
435   /* Schedule the connection for data I/O */
436   silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn);
437
438   /* Add connection timeout check */
439   silc_schedule_task_add_timeout(httpd->schedule,
440                                  silc_http_server_connection_timeout, conn,
441                                  SILC_HTTP_SERVER_TIMEOUT, 0);
442 }
443
444
445 /******************************* Public API *********************************/
446
447 /* Allocate HTTP server */
448
449 SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port,
450                                       SilcSchedule schedule,
451                                       SilcHttpServerCallback callback,
452                                       void *context)
453 {
454   SilcHttpServer httpd;
455   SilcHttpConnection conn;
456   int i;
457
458   SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port));
459
460   if (!schedule)
461     schedule = silc_schedule_get_global();
462
463   if (!ip || !schedule || !callback)
464     return FALSE;
465
466   httpd = silc_calloc(1, sizeof(*httpd));
467   if (!httpd)
468     return NULL;
469
470   /* Create server listener */
471   httpd->listener =
472     silc_net_tcp_create_listener(&ip, 1, port, TRUE, FALSE, schedule,
473                                  silc_http_server_new_connection, httpd);
474   if (!httpd->listener) {
475     SILC_LOG_ERROR(("Could not bind HTTP server at %s:%d", ip, port));
476     silc_http_server_free(httpd);
477     return NULL;
478   }
479
480   httpd->schedule = schedule;
481   httpd->callback = callback;
482   httpd->context = context;
483
484   silc_list_init(httpd->conns, struct SilcHttpConnectionStruct, next);
485   silc_list_init(httpd->allconns, struct SilcHttpConnectionStruct, next2);
486
487   /* Allocate connections list */
488   for (i = 0; i < SILC_HTTP_SERVER_CONNS; i++) {
489     conn = silc_http_server_alloc_connection();
490     if (!conn)
491       break;
492     silc_list_add(httpd->conns, conn);
493     silc_list_add(httpd->allconns, conn);
494     conn->httpd = httpd;
495   }
496
497   SILC_LOG_DEBUG(("HTTP Server started"));
498
499   return httpd;
500 }
501
502 /* Free HTTP server */
503
504 void silc_http_server_free(SilcHttpServer httpd)
505 {
506   SilcHttpConnection conn;
507
508   silc_list_start(httpd->allconns);
509   while ((conn = silc_list_get(httpd->allconns))) {
510     conn->keepalive = FALSE;
511     if (conn->httpd && conn->stream)
512       silc_http_server_close_connection(conn);
513     silc_buffer_free(conn->inbuf);
514     silc_buffer_free(conn->outbuf);
515     silc_free(conn);
516   }
517
518   if (httpd->listener)
519     silc_net_close_listener(httpd->listener);
520
521   silc_free(httpd);
522 }
523
524 /* Send HTTP data to connection */
525
526 SilcBool silc_http_server_send(SilcHttpServer httpd,
527                                SilcHttpConnection conn,
528                                SilcBuffer data)
529 {
530   SilcBufferStruct h;
531   unsigned char *headers, tmp[16];
532   SilcUInt32 headers_len;
533   SilcBool ret;
534
535   SILC_LOG_DEBUG(("Sending HTTP data"));
536
537   conn->touched = silc_time();
538
539   /* Write headers */
540   silc_buffer_set(&h, SILC_HTTP_SERVER_HEADER,
541                   strlen(SILC_HTTP_SERVER_HEADER));
542   ret = silc_http_server_send_internal(httpd, conn, &h, TRUE);
543   if (!ret) {
544     conn->keepalive = FALSE;
545     silc_http_server_close_connection(conn);
546     return FALSE;
547   }
548
549   if (!conn->headers) {
550     conn->headers = silc_mime_alloc();
551     if (!conn->headers) {
552       conn->keepalive = FALSE;
553       silc_http_server_close_connection(conn);
554       return FALSE;
555     }
556   }
557
558   silc_mime_add_field(conn->headers, "Last-Modified",
559                       silc_time_string(conn->touched));
560   silc_snprintf(tmp, sizeof(tmp), "%d", (int)silc_buffer_len(data));
561   silc_mime_add_field(conn->headers, "Content-Length", tmp);
562   if (conn->keepalive) {
563     silc_mime_add_field(conn->headers, "Connection", "keep-alive");
564     silc_snprintf(tmp, sizeof(tmp), "%d", (int)SILC_HTTP_SERVER_TIMEOUT);
565     silc_mime_add_field(conn->headers, "Keep-alive", tmp);
566   }
567
568   headers = silc_mime_encode(conn->headers, &headers_len);
569   if (headers) {
570     silc_buffer_set(&h, headers, headers_len);
571     if (!silc_http_server_send_internal(httpd, conn, &h, TRUE)) {
572       conn->keepalive = FALSE;
573       silc_http_server_close_connection(conn);
574       return FALSE;
575     }
576     silc_free(headers);
577   }
578
579   /* Write the page data */
580   return silc_http_server_send_internal(httpd, conn, data, FALSE);
581 }
582
583 /* Send error reply */
584
585 SilcBool silc_http_server_send_error(SilcHttpServer httpd,
586                                      SilcHttpConnection conn,
587                                      const char *error,
588                                      const char *error_message)
589 {
590   SilcBool ret;
591   SilcBufferStruct data;
592
593   memset(&data, 0, sizeof(data));
594   silc_buffer_strformat(&data,
595                         "HTTP/1.1 ", error, "\r\n\r\n", error_message,
596                         SILC_STRFMT_END);
597
598   /* Send the message */
599   ret = silc_http_server_send_internal(httpd, conn, &data, FALSE);
600
601   silc_buffer_purge(&data);
602
603   /* Close connection */
604   conn->keepalive = FALSE;
605   silc_http_server_close_connection(conn);
606
607   return ret;
608 }
609
610 /* Get field */
611
612 const char *silc_http_server_get_header(SilcHttpServer httpd,
613                                         SilcHttpConnection conn,
614                                         const char *field)
615 {
616   if (!conn->curheaders)
617     return NULL;
618   return silc_mime_get_field(conn->curheaders, field);
619 }
620
621 /* Add field */
622
623 SilcBool silc_http_server_add_header(SilcHttpServer httpd,
624                                      SilcHttpConnection conn,
625                                      const char *field,
626                                      const char *value)
627 {
628   SILC_LOG_DEBUG(("Adding header %s:%s", field, value));
629
630   if (!conn->headers) {
631     conn->headers = silc_mime_alloc();
632     if (!conn->headers) {
633       silc_http_server_close_connection(conn);
634       return FALSE;
635     }
636   }
637
638   silc_mime_add_field(conn->headers, field, value);
639   return TRUE;
640 }