5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2006 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.
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 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 */
53 unsigned int keepalive : 1; /* Keep alive */
56 /************************ Static utility functions **************************/
58 /* Close HTTP connection */
60 static void silc_http_server_close_connection(SilcHttpConnection conn)
63 silc_mime_free(conn->headers);
66 if (conn->curheaders) {
67 silc_mime_free(conn->curheaders);
68 conn->curheaders = NULL;
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;
79 SILC_LOG_DEBUG(("Closing HTTP connection %p", conn));
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);
85 /* Add to free list */
86 silc_list_add(conn->httpd->conns, conn);
91 static SilcBool silc_http_server_parse(SilcHttpServer httpd,
92 SilcHttpConnection conn)
94 SilcUInt32 data_len, cll;
95 unsigned char *data, *tmp;
96 const char *value, *cl;
97 SilcBufferStruct postdata;
100 SILC_LOG_DEBUG(("Parsing HTTP data"));
102 data = silc_buffer_data(conn->inbuf);
103 data_len = silc_buffer_len(conn->inbuf);
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')
115 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(conn->inbuf),
116 silc_buffer_len(conn->inbuf));
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)
128 if (strchr(data, ' '))
129 *strchr(data, ' ') = 0;
131 SILC_LOG_DEBUG(("Method: '%s'", conn->method));
134 tmp = memchr(data, '\0', data_len);
136 if (data_len < SILC_HTTP_SERVER_BUFLEN)
141 if (strchr(tmp, ' '))
142 *strchr(tmp, ' ') = 0;
144 SILC_LOG_DEBUG(("URI: '%s'", conn->uri));
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;
156 /* Get HTTP headers */
157 tmp = memchr(tmp, '\0', data_len - (tmp - data));
159 if (data_len < SILC_HTTP_SERVER_BUFLEN)
163 if (data_len - (tmp - data) < 2) {
164 if (data_len < SILC_HTTP_SERVER_BUFLEN)
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)
177 /* Check for persistent connection */
178 value = silc_mime_get_field(conn->curheaders, "Connection");
179 if (value && !strcasecmp(value, "close"))
180 conn->keepalive = FALSE;
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);
187 } else if (!strcasecmp(conn->method, "POST")) {
189 tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len);
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;
204 silc_buffer_set(&postdata, tmp, data_len);
205 SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len);
207 httpd->callback(httpd, conn, conn->uri, conn->method,
208 &postdata, httpd->context);
210 /* Send bad request */
211 silc_http_server_send_error(httpd, conn, "400 Bad Request",
212 "<body><h1>400 Bad Request</h1><body>");
219 /* Send HTTP data to connection */
221 static SilcBool silc_http_server_send_internal(SilcHttpServer httpd,
222 SilcHttpConnection conn,
228 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data),
229 silc_buffer_len(data));
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)
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);
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);
255 silc_buffer_pull(data, ret);
259 /* Data sent, close connection */
260 SILC_LOG_DEBUG(("Data sent %p", conn));
261 silc_http_server_close_connection(conn);
267 /* Allocate connection context */
269 static SilcHttpConnection silc_http_server_alloc_connection(void)
271 SilcHttpConnection conn;
273 conn = silc_calloc(1, sizeof(*conn));
277 conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
283 conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
285 silc_buffer_free(conn->inbuf);
290 silc_buffer_reset(conn->inbuf);
291 silc_buffer_reset(conn->outbuf);
296 /* Check if connection has timedout */
298 SILC_TASK_CALLBACK(silc_http_server_connection_timeout)
300 SilcHttpConnection conn = context;
301 SilcInt64 curtime = silc_time();
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);
310 silc_schedule_task_add_timeout(conn->httpd->schedule,
311 silc_http_server_connection_timeout, conn,
312 SILC_HTTP_SERVER_TIMEOUT, 0);
315 /* Data I/O callback */
317 static void silc_http_server_io(SilcStream stream, SilcStreamStatus status,
320 SilcHttpConnection conn = context;
321 SilcHttpServer httpd = conn->httpd;
325 case SILC_STREAM_CAN_READ:
326 SILC_LOG_DEBUG(("Read HTTP data %p", conn));
328 conn->touched = silc_time();
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);
339 /* Read data from stream */
340 ret = silc_stream_read(conn->stream, conn->inbuf->tail,
341 silc_buffer_taillen(conn->inbuf));
343 if (ret == 0 || ret == -2) {
344 conn->keepalive = FALSE;
345 silc_http_server_close_connection(conn);
350 /* Cannot read now, do it later. */
351 silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf));
355 SILC_LOG_DEBUG(("Read %d bytes data", ret));
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);
366 case SILC_STREAM_CAN_WRITE:
367 SILC_LOG_DEBUG(("Write HTTP data %p", conn));
369 conn->touched = silc_time();
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));
376 if (ret == 0 || ret == -2) {
377 conn->keepalive = FALSE;
378 silc_http_server_close_connection(conn);
383 /* Cannot write now, write later. */
387 silc_buffer_pull(conn->outbuf, ret);
390 /* Data sent, close connection */
391 SILC_LOG_DEBUG(("Data sent"));
392 silc_http_server_close_connection(conn);
396 conn->keepalive = FALSE;
397 silc_http_server_close_connection(conn);
402 /* Accepts new connection */
404 static void silc_http_server_new_connection(SilcNetStatus status,
408 SilcHttpServer httpd = context;
409 SilcHttpConnection conn;
410 const char *hostname = NULL, *ip = NULL;
412 /* Get free connection */
413 silc_list_start(httpd->conns);
414 conn = silc_list_get(httpd->conns);
416 /* Add new connection */
417 conn = silc_http_server_alloc_connection();
419 silc_stream_destroy(stream);
422 silc_list_add(httpd->allconns, conn);
424 silc_list_del(httpd->conns, conn);
427 conn->stream = stream;
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));
433 /* Schedule the connection for data I/O */
434 silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn);
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);
443 /******************************* Public API *********************************/
445 /* Allocate HTTP server */
447 SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port,
448 SilcSchedule schedule,
449 SilcHttpServerCallback callback,
452 SilcHttpServer httpd;
453 SilcHttpConnection conn;
456 SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port));
458 if (!ip || !schedule || !callback)
461 httpd = silc_calloc(1, sizeof(*httpd));
465 /* Create server 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);
475 httpd->schedule = schedule;
476 httpd->callback = callback;
477 httpd->context = context;
479 /* Allocate connections list */
480 for (i = 0; i < SILC_HTTP_SERVER_CONNS; i++) {
481 conn = silc_http_server_alloc_connection();
484 silc_list_add(httpd->conns, conn);
485 silc_list_add(httpd->allconns, conn);
488 SILC_LOG_DEBUG(("HTTP Server started"));
493 /* Free HTTP server */
495 void silc_http_server_free(SilcHttpServer httpd)
497 SilcHttpConnection conn;
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);
509 silc_net_close_listener(httpd->listener);
514 /* Send HTTP data to connection */
516 SilcBool silc_http_server_send(SilcHttpServer httpd,
517 SilcHttpConnection conn,
521 unsigned char *headers, tmp[16];
522 SilcUInt32 headers_len;
525 SILC_LOG_DEBUG(("Sending HTTP data"));
527 conn->touched = silc_time();
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);
534 conn->keepalive = FALSE;
535 silc_http_server_close_connection(conn);
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);
548 silc_mime_add_field(conn->headers, "Last-Modified",
549 silc_time_string(conn->touched));
550 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 snprintf(tmp, sizeof(tmp), "%d", (int)SILC_HTTP_SERVER_TIMEOUT);
555 silc_mime_add_field(conn->headers, "Keep-alive", tmp);
558 headers = silc_mime_encode(conn->headers, &headers_len);
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);
569 /* Write the page data */
570 return silc_http_server_send_internal(httpd, conn, data, FALSE);
573 /* Send error reply */
575 SilcBool silc_http_server_send_error(SilcHttpServer httpd,
576 SilcHttpConnection conn,
578 const char *error_message)
581 SilcBufferStruct data;
583 memset(&data, 0, sizeof(data));
584 silc_buffer_strformat(&data,
585 "HTTP/1.1 ", error, "\r\n\r\n", error_message,
588 /* Send the message */
589 ret = silc_http_server_send_internal(httpd, conn, &data, FALSE);
591 silc_buffer_purge(&data);
593 /* Close connection */
594 conn->keepalive = FALSE;
595 silc_http_server_close_connection(conn);
602 const char *silc_http_server_get_header(SilcHttpServer httpd,
603 SilcHttpConnection conn,
606 if (!conn->curheaders)
608 return silc_mime_get_field(conn->curheaders, field);
613 SilcBool silc_http_server_add_header(SilcHttpServer httpd,
614 SilcHttpConnection conn,
618 SILC_LOG_DEBUG(("Adding header %s:%s", field, value));
620 if (!conn->headers) {
621 conn->headers = silc_mime_alloc();
622 if (!conn->headers) {
623 silc_http_server_close_connection(conn);
628 silc_mime_add_field(conn->headers, field, value);