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