Static analyzer bug fixes
[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   if (!conn->method)
185     return FALSE;
186
187   /* Deliver request to caller */
188   if (!strcasecmp(conn->method, "GET") || !strcasecmp(conn->method, "HEAD")) {
189     httpd->callback(httpd, conn, conn->uri, conn->method,
190                     NULL, httpd->context);
191
192   } else if (!strcasecmp(conn->method, "POST")) {
193     /* Get POST data */
194     tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len);
195     if (!tmp)
196       return FALSE;
197
198     /* Check we have received all data */
199     cl = silc_mime_get_field(conn->curheaders, "Content-Length");
200     if (cl && sscanf(cl, "%u", &cll) == 1) {
201       if (data_len < cll) {
202         /* More data to come */
203         silc_mime_free(conn->curheaders);
204         conn->curheaders = NULL;
205         return TRUE;
206       }
207     }
208
209     silc_buffer_set(&postdata, tmp, data_len);
210     SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len);
211
212     httpd->callback(httpd, conn, conn->uri, conn->method,
213                     &postdata, httpd->context);
214   } else {
215     /* Send bad request */
216     silc_http_server_send_error(httpd, conn, "400 Bad Request",
217                                 "<body><h1>400 Bad Request</h1><body>");
218     return TRUE;
219   }
220
221   return TRUE;
222 }
223
224 /* Send HTTP data to connection */
225
226 static SilcBool silc_http_server_send_internal(SilcHttpServer httpd,
227                                                SilcHttpConnection conn,
228                                                SilcBuffer data,
229                                                SilcBool headers)
230 {
231   int ret;
232
233   SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data),
234                    silc_buffer_len(data));
235
236   /* Write the packet to the stream */
237   while (silc_buffer_len(data) > 0) {
238     ret = silc_stream_write(conn->stream, silc_buffer_data(data),
239                             silc_buffer_len(data));
240     if (ret == 0 || ret == - 2)
241       return FALSE;
242
243     if (ret == -1) {
244       /* Cannot write now, write later. */
245       if (silc_buffer_len(data) - ret >= silc_buffer_taillen(conn->outbuf))
246         if (!silc_buffer_realloc(conn->outbuf,
247                                  silc_buffer_truelen(conn->outbuf) +
248                                  silc_buffer_len(data) - ret)) {
249           conn->keepalive = FALSE;
250           silc_http_server_close_connection(conn);
251           return FALSE;
252         }
253       silc_buffer_pull_tail(conn->outbuf, silc_buffer_len(data) - ret);
254       silc_buffer_put(conn->outbuf, silc_buffer_data(data) + ret,
255                       silc_buffer_len(data) - ret);
256       return TRUE;
257     }
258
259     /* Wrote data */
260     silc_buffer_pull(data, ret);
261   }
262
263   if (!headers) {
264     /* Data sent, close connection */
265     SILC_LOG_DEBUG(("Data sent %p", conn));
266     silc_http_server_close_connection(conn);
267   }
268
269   return TRUE;
270 }
271
272 /* Allocate connection context */
273
274 static SilcHttpConnection silc_http_server_alloc_connection(void)
275 {
276   SilcHttpConnection conn;
277
278   conn = silc_calloc(1, sizeof(*conn));
279   if (!conn)
280     return NULL;
281
282   conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
283   if (!conn->inbuf) {
284     silc_free(conn);
285     return NULL;
286   }
287
288   conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
289   if (!conn->outbuf) {
290     silc_buffer_free(conn->inbuf);
291     silc_free(conn);
292     return NULL;
293   }
294
295   silc_buffer_reset(conn->inbuf);
296   silc_buffer_reset(conn->outbuf);
297
298   return conn;
299 }
300
301 /* Check if connection has timedout */
302
303 SILC_TASK_CALLBACK(silc_http_server_connection_timeout)
304 {
305   SilcHttpConnection conn = context;
306   SilcInt64 curtime = silc_time();
307
308   if (curtime - conn->touched > SILC_HTTP_SERVER_TIMEOUT) {
309     SILC_LOG_DEBUG(("Connection timeout %p", conn));
310     conn->keepalive = FALSE;
311     silc_http_server_close_connection(conn);
312     return;
313   }
314
315   silc_schedule_task_add_timeout(conn->httpd->schedule,
316                                  silc_http_server_connection_timeout, conn,
317                                  SILC_HTTP_SERVER_TIMEOUT, 0);
318 }
319
320 /* Data I/O callback */
321
322 static void silc_http_server_io(SilcStream stream, SilcStreamStatus status,
323                                 void *context)
324 {
325   SilcHttpConnection conn = context;
326   SilcHttpServer httpd = conn->httpd;
327   int ret;
328
329   switch (status) {
330   case SILC_STREAM_CAN_READ:
331     SILC_LOG_DEBUG(("Read HTTP data %p", conn));
332
333     conn->touched = silc_time();
334
335     /* Make sure we have fair amount of free space in inbuf */
336     if (silc_buffer_taillen(conn->inbuf) < SILC_HTTP_SERVER_BUFLEN)
337       if (!silc_buffer_realloc(conn->inbuf, silc_buffer_truelen(conn->inbuf) +
338                                SILC_HTTP_SERVER_BUFLEN * 2)) {
339         conn->keepalive = FALSE;
340         silc_http_server_close_connection(conn);
341         return;
342       }
343
344     /* Read data from stream */
345     ret = silc_stream_read(conn->stream, conn->inbuf->tail,
346                            silc_buffer_taillen(conn->inbuf));
347
348     if (ret == 0 || ret == -2) {
349       conn->keepalive = FALSE;
350       silc_http_server_close_connection(conn);
351       return;
352     }
353
354     if (ret == -1) {
355       /* Cannot read now, do it later. */
356       silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf));
357       return;
358     }
359
360     SILC_LOG_DEBUG(("Read %d bytes data", ret));
361
362     /* Parse the data */
363     silc_buffer_pull_tail(conn->inbuf, ret);
364     if (!silc_http_server_parse(httpd, conn)) {
365       conn->keepalive = FALSE;
366       silc_http_server_close_connection(conn);
367     }
368
369     break;
370
371   case SILC_STREAM_CAN_WRITE:
372     SILC_LOG_DEBUG(("Write HTTP data %p", conn));
373
374     conn->touched = silc_time();
375
376     /* Write pending data to stream */
377     while (silc_buffer_len(conn->outbuf) > 0) {
378       ret = silc_stream_write(conn->stream, silc_buffer_data(conn->outbuf),
379                               silc_buffer_len(conn->outbuf));
380
381       if (ret == 0 || ret == -2) {
382         conn->keepalive = FALSE;
383         silc_http_server_close_connection(conn);
384         return;
385       }
386
387       if (ret == -1)
388         /* Cannot write now, write later. */
389         return;
390
391       /* Wrote data */
392       silc_buffer_pull(conn->outbuf, ret);
393     }
394
395     /* Data sent, close connection */
396     SILC_LOG_DEBUG(("Data sent"));
397     silc_http_server_close_connection(conn);
398     break;
399
400   default:
401     conn->keepalive = FALSE;
402     silc_http_server_close_connection(conn);
403     break;
404   }
405 }
406
407 /* Accepts new connection */
408
409 static void silc_http_server_new_connection(SilcNetStatus status,
410                                             SilcStream stream,
411                                             void *context)
412 {
413   SilcHttpServer httpd = context;
414   SilcHttpConnection conn;
415   const char *hostname = NULL, *ip = NULL;
416
417   /* Get free connection */
418   silc_list_start(httpd->conns);
419   conn = silc_list_get(httpd->conns);
420   if (!conn) {
421     /* Add new connection */
422     conn = silc_http_server_alloc_connection();
423     if (!conn) {
424       silc_stream_destroy(stream);
425       return;
426     }
427     silc_list_add(httpd->allconns, conn);
428   }
429   silc_list_del(httpd->conns, conn);
430
431   conn->httpd = httpd;
432   conn->stream = stream;
433
434   silc_socket_stream_get_info(stream, NULL, &hostname, &ip, NULL);
435   SILC_LOG_INFO(("HTTPD: New connection %s (%s)", hostname, ip));
436   SILC_LOG_DEBUG(("New connection %p", conn));
437
438   /* Schedule the connection for data I/O */
439   silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn);
440
441   /* Add connection timeout check */
442   silc_schedule_task_add_timeout(httpd->schedule,
443                                  silc_http_server_connection_timeout, conn,
444                                  SILC_HTTP_SERVER_TIMEOUT, 0);
445 }
446
447
448 /******************************* Public API *********************************/
449
450 /* Allocate HTTP server */
451
452 SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port,
453                                       SilcSchedule schedule,
454                                       SilcHttpServerCallback callback,
455                                       void *context)
456 {
457   SilcHttpServer httpd;
458   SilcHttpConnection conn;
459   int i;
460
461   SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port));
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 }