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