5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2006 - 2007 Pekka Riikonen
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.
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.
20 #include "silcruntime.h"
21 #include "silchttpserver.h"
23 /************************** Types and definitions ***************************/
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"
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 */
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 */
54 unsigned int keepalive : 1; /* Keep alive */
57 /************************ Static utility functions **************************/
59 /* Close HTTP connection */
61 static void silc_http_server_close_connection(SilcHttpConnection conn)
64 silc_mime_free(conn->headers);
67 if (conn->curheaders) {
68 silc_mime_free(conn->curheaders);
69 conn->curheaders = NULL;
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;
80 SILC_LOG_DEBUG(("Closing HTTP connection %p", conn));
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);
87 /* Add to free list */
88 silc_list_add(conn->httpd->conns, conn);
93 static SilcBool silc_http_server_parse(SilcHttpServer httpd,
94 SilcHttpConnection conn)
98 unsigned char *data, *tmp;
99 const char *value, *cl;
100 SilcBufferStruct postdata;
103 SILC_LOG_DEBUG(("Parsing HTTP data"));
105 data = silc_buffer_data(conn->inbuf);
106 data_len = silc_buffer_len(conn->inbuf);
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')
118 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(conn->inbuf),
119 silc_buffer_len(conn->inbuf));
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)
131 if (strchr(data, ' '))
132 *strchr(data, ' ') = 0;
134 SILC_LOG_DEBUG(("Method: '%s'", conn->method));
137 tmp = memchr(data, '\0', data_len);
139 if (data_len < SILC_HTTP_SERVER_BUFLEN)
144 if (strchr(tmp, ' '))
145 *strchr(tmp, ' ') = 0;
147 SILC_LOG_DEBUG(("URI: '%s'", conn->uri));
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;
159 /* Get HTTP headers */
160 tmp = memchr(tmp, '\0', data_len - (tmp - data));
162 if (data_len < SILC_HTTP_SERVER_BUFLEN)
166 if (data_len - (tmp - data) < 2) {
167 if (data_len < SILC_HTTP_SERVER_BUFLEN)
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)
180 /* Check for persistent connection */
181 value = silc_mime_get_field(conn->curheaders, "Connection");
182 if (value && !strcasecmp(value, "close"))
183 conn->keepalive = FALSE;
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);
190 } else if (!strcasecmp(conn->method, "POST")) {
192 tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len);
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;
207 silc_buffer_set(&postdata, tmp, data_len);
208 SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len);
210 httpd->callback(httpd, conn, conn->uri, conn->method,
211 &postdata, httpd->context);
213 /* Send bad request */
214 silc_http_server_send_error(httpd, conn, "400 Bad Request",
215 "<body><h1>400 Bad Request</h1><body>");
222 /* Send HTTP data to connection */
224 static SilcBool silc_http_server_send_internal(SilcHttpServer httpd,
225 SilcHttpConnection conn,
231 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data),
232 silc_buffer_len(data));
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)
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);
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);
258 silc_buffer_pull(data, ret);
262 /* Data sent, close connection */
263 SILC_LOG_DEBUG(("Data sent %p", conn));
264 silc_http_server_close_connection(conn);
270 /* Allocate connection context */
272 static SilcHttpConnection silc_http_server_alloc_connection(void)
274 SilcHttpConnection conn;
276 conn = silc_calloc(1, sizeof(*conn));
280 conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
286 conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
288 silc_buffer_free(conn->inbuf);
293 silc_buffer_reset(conn->inbuf);
294 silc_buffer_reset(conn->outbuf);
299 /* Check if connection has timedout */
301 SILC_TASK_CALLBACK(silc_http_server_connection_timeout)
303 SilcHttpConnection conn = context;
304 SilcInt64 curtime = silc_time();
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);
313 silc_schedule_task_add_timeout(conn->httpd->schedule,
314 silc_http_server_connection_timeout, conn,
315 SILC_HTTP_SERVER_TIMEOUT, 0);
318 /* Data I/O callback */
320 static void silc_http_server_io(SilcStream stream, SilcStreamStatus status,
323 SilcHttpConnection conn = context;
324 SilcHttpServer httpd = conn->httpd;
328 case SILC_STREAM_CAN_READ:
329 SILC_LOG_DEBUG(("Read HTTP data %p", conn));
331 conn->touched = silc_time();
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);
342 /* Read data from stream */
343 ret = silc_stream_read(conn->stream, conn->inbuf->tail,
344 silc_buffer_taillen(conn->inbuf));
346 if (ret == 0 || ret == -2) {
347 conn->keepalive = FALSE;
348 silc_http_server_close_connection(conn);
353 /* Cannot read now, do it later. */
354 silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf));
358 SILC_LOG_DEBUG(("Read %d bytes data", ret));
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);
369 case SILC_STREAM_CAN_WRITE:
370 SILC_LOG_DEBUG(("Write HTTP data %p", conn));
372 conn->touched = silc_time();
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));
379 if (ret == 0 || ret == -2) {
380 conn->keepalive = FALSE;
381 silc_http_server_close_connection(conn);
386 /* Cannot write now, write later. */
390 silc_buffer_pull(conn->outbuf, ret);
393 /* Data sent, close connection */
394 SILC_LOG_DEBUG(("Data sent"));
395 silc_http_server_close_connection(conn);
399 conn->keepalive = FALSE;
400 silc_http_server_close_connection(conn);
405 /* Accepts new connection */
407 static void silc_http_server_new_connection(SilcResult status,
411 SilcHttpServer httpd = context;
412 SilcHttpConnection conn;
413 const char *hostname = NULL, *ip = NULL;
415 /* Get free connection */
416 silc_list_start(httpd->conns);
417 conn = silc_list_get(httpd->conns);
419 /* Add new connection */
420 conn = silc_http_server_alloc_connection();
422 silc_stream_destroy(stream);
425 silc_list_add(httpd->allconns, conn);
427 silc_list_del(httpd->conns, conn);
430 conn->stream = stream;
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));
436 /* Schedule the connection for data I/O */
437 silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn);
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);
446 /******************************* Public API *********************************/
448 /* Allocate HTTP server */
450 SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port,
451 SilcSchedule schedule,
452 SilcHttpServerCallback callback,
455 SilcHttpServer httpd;
456 SilcHttpConnection conn;
459 SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port));
462 schedule = silc_schedule_get_global();
464 if (!ip || !schedule || !callback)
467 httpd = silc_calloc(1, sizeof(*httpd));
471 /* Create server 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);
481 httpd->schedule = schedule;
482 httpd->callback = callback;
483 httpd->context = context;
485 silc_list_init(httpd->conns, struct SilcHttpConnectionStruct, next);
486 silc_list_init(httpd->allconns, struct SilcHttpConnectionStruct, next2);
488 /* Allocate connections list */
489 for (i = 0; i < SILC_HTTP_SERVER_CONNS; i++) {
490 conn = silc_http_server_alloc_connection();
493 silc_list_add(httpd->conns, conn);
494 silc_list_add(httpd->allconns, conn);
498 SILC_LOG_DEBUG(("HTTP Server started"));
503 /* Free HTTP server */
505 void silc_http_server_free(SilcHttpServer httpd)
507 SilcHttpConnection conn;
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);
520 silc_net_close_listener(httpd->listener);
525 /* Send HTTP data to connection */
527 SilcBool silc_http_server_send(SilcHttpServer httpd,
528 SilcHttpConnection conn,
532 unsigned char *headers, tmp[16];
533 SilcUInt32 headers_len;
536 SILC_LOG_DEBUG(("Sending HTTP data"));
538 conn->touched = silc_time();
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);
545 conn->keepalive = FALSE;
546 silc_http_server_close_connection(conn);
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);
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);
569 headers = silc_mime_encode(conn->headers, &headers_len);
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);
580 /* Write the page data */
581 return silc_http_server_send_internal(httpd, conn, data, FALSE);
584 /* Send error reply */
586 SilcBool silc_http_server_send_error(SilcHttpServer httpd,
587 SilcHttpConnection conn,
589 const char *error_message)
592 SilcBufferStruct data;
594 memset(&data, 0, sizeof(data));
595 silc_buffer_strformat(&data,
596 "HTTP/1.1 ", error, "\r\n\r\n", error_message,
599 /* Send the message */
600 ret = silc_http_server_send_internal(httpd, conn, &data, FALSE);
602 silc_buffer_purge(&data);
604 /* Close connection */
605 conn->keepalive = FALSE;
606 silc_http_server_close_connection(conn);
613 const char *silc_http_server_get_header(SilcHttpServer httpd,
614 SilcHttpConnection conn,
617 if (!conn->curheaders)
619 return silc_mime_get_field(conn->curheaders, field);
624 SilcBool silc_http_server_add_header(SilcHttpServer httpd,
625 SilcHttpConnection conn,
629 SILC_LOG_DEBUG(("Adding header %s:%s", field, value));
631 if (!conn->headers) {
632 conn->headers = silc_mime_alloc();
633 if (!conn->headers) {
634 silc_http_server_close_connection(conn);
639 silc_mime_add_field(conn->headers, field, value);