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