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 int keepalive : 1; /* Keep alive */
53 /************************ Static utility functions **************************/
55 /* Close HTTP connection */
57 static void silc_http_server_close_connection(SilcHttpConnection conn)
60 silc_mime_free(conn->headers);
63 if (conn->curheaders) {
64 silc_mime_free(conn->curheaders);
65 conn->curheaders = NULL;
67 silc_buffer_clear(conn->inbuf);
68 silc_buffer_clear(conn->outbuf);
69 silc_buffer_reset(conn->inbuf);
70 silc_buffer_reset(conn->outbuf);
75 SILC_LOG_DEBUG(("Closing HTTP connection"));
77 silc_schedule_task_del_by_context(conn->httpd->schedule, conn);
78 silc_stream_destroy(conn->stream);
80 /* Add to free list */
81 silc_list_add(conn->httpd->conns, conn);
86 static SilcBool silc_http_server_parse(SilcHttpServer httpd,
87 SilcHttpConnection conn)
90 unsigned char *data, *tmp;
93 SilcBufferStruct postdata;
95 SILC_LOG_DEBUG(("Parsing HTTP data"));
96 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(conn->inbuf),
97 silc_buffer_len(conn->inbuf));
99 data = silc_buffer_data(conn->inbuf);
100 data_len = silc_buffer_len(conn->inbuf);
105 tmp = memchr(data, '\n', data_len);
106 if (!tmp || tmp[-1] != '\r') {
107 if (data_len < SILC_HTTP_SERVER_BUFLEN)
114 if (strchr(data, ' '))
115 *strchr(data, ' ') = 0;
117 SILC_LOG_DEBUG(("Method: '%s'", method));
120 tmp = memchr(data, '\0', data_len);
122 if (data_len < SILC_HTTP_SERVER_BUFLEN)
127 if (strchr(tmp, ' '))
128 *strchr(tmp, ' ') = 0;
130 SILC_LOG_DEBUG(("URI: '%s'", uri));
132 /* Get HTTP headers */
134 tmp = memchr(tmp, '\n', data_len - (tmp - data)) + 1;
136 if (data_len < SILC_HTTP_SERVER_BUFLEN)
140 conn->curheaders = silc_mime_decode(NULL, tmp, data_len - (tmp - data));
141 if (!conn->curheaders) {
142 if (data_len < SILC_HTTP_SERVER_BUFLEN)
147 /* Check for persistent connection */
148 value = silc_mime_get_field(conn->curheaders, "Keep-alive");
150 conn->keepalive = TRUE;
151 value = silc_mime_get_field(conn->curheaders, "Connection");
152 if (value && !strcasecmp(value, "keep-alive"))
153 conn->keepalive = TRUE;
154 if (value && !strcasecmp(value, "close"))
155 conn->keepalive = FALSE;
157 /* Deliver request to caller */
158 if (!strcasecmp(method, "GET") || !strcasecmp(method, "HEAD")) {
159 /* Send request to caller */
160 httpd->callback(httpd, conn, uri, method, NULL, httpd->context);
162 } else if (!strcasecmp(method, "POST")) {
164 tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len);
166 silc_mime_free(conn->curheaders);
167 conn->curheaders = NULL;
168 if (data_len < SILC_HTTP_SERVER_BUFLEN)
172 silc_buffer_set(&postdata, tmp, data_len);
173 SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len);
175 /* Send request to caller */
176 httpd->callback(httpd, conn, uri, method, &postdata, httpd->context);
179 /* XXX Send bad request error */
185 /* Send HTTP data to connection */
187 static SilcBool silc_http_server_send_internal(SilcHttpServer httpd,
188 SilcHttpConnection conn,
194 SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data),
195 silc_buffer_len(data));
197 /* Write the packet to the stream */
198 while (silc_buffer_len(data) > 0) {
199 ret = silc_stream_write(conn->stream, silc_buffer_data(data),
200 silc_buffer_len(data));
201 if (ret == 0 || ret == - 2)
205 /* Cannot write now, write later. */
206 if (silc_buffer_len(data) - ret >= silc_buffer_taillen(conn->outbuf))
207 if (!silc_buffer_realloc(conn->outbuf,
208 silc_buffer_truelen(conn->outbuf) +
209 silc_buffer_len(data) - ret)) {
210 conn->keepalive = FALSE;
211 silc_http_server_close_connection(conn);
214 silc_buffer_pull_tail(conn->outbuf, silc_buffer_len(data) - ret);
215 silc_buffer_put(conn->outbuf, silc_buffer_data(data) + ret,
216 silc_buffer_len(data) - ret);
221 silc_buffer_pull(data, ret);
225 /* Data sent, close connection */
226 SILC_LOG_DEBUG(("Data sent"));
227 silc_http_server_close_connection(conn);
233 /* Allocate connection context */
235 static SilcHttpConnection silc_http_server_alloc_connection(void)
237 SilcHttpConnection conn;
239 conn = silc_calloc(1, sizeof(*conn));
243 conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
249 conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN);
251 silc_buffer_free(conn->inbuf);
256 silc_buffer_reset(conn->inbuf);
257 silc_buffer_reset(conn->outbuf);
262 /* Check if connection has timedout */
264 SILC_TASK_CALLBACK(silc_http_server_connection_timeout)
266 SilcHttpConnection conn = context;
267 SilcInt64 curtime = silc_time();
269 if (curtime - conn->touched > SILC_HTTP_SERVER_TIMEOUT) {
270 SILC_LOG_DEBUG(("Connection timeout"));
271 conn->keepalive = FALSE;
272 silc_http_server_close_connection(conn);
276 silc_schedule_task_add_timeout(conn->httpd->schedule,
277 silc_http_server_connection_timeout, conn,
278 SILC_HTTP_SERVER_TIMEOUT, 0);
281 /* Data I/O callback */
283 static void silc_http_server_io(SilcStream stream, SilcStreamStatus status,
286 SilcHttpConnection conn = context;
287 SilcHttpServer httpd = conn->httpd;
291 case SILC_STREAM_CAN_READ:
292 SILC_LOG_DEBUG(("Read HTTP data"));
294 conn->touched = silc_time();
296 /* Make sure we have fair amount of free space in inbuf */
297 if (silc_buffer_taillen(conn->inbuf) < SILC_HTTP_SERVER_BUFLEN)
298 if (!silc_buffer_realloc(conn->inbuf, silc_buffer_truelen(conn->inbuf) +
299 SILC_HTTP_SERVER_BUFLEN * 2)) {
300 conn->keepalive = FALSE;
301 silc_http_server_close_connection(conn);
305 /* Read data from stream */
306 ret = silc_stream_read(conn->stream, conn->inbuf->tail,
307 silc_buffer_taillen(conn->inbuf));
309 if (ret == 0 || ret == -2) {
310 silc_http_server_close_connection(conn);
315 /* Cannot read now, do it later. */
316 silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf));
320 SILC_LOG_DEBUG(("Read %d bytes data", ret));
323 silc_buffer_pull_tail(conn->inbuf, ret);
324 if (!silc_http_server_parse(httpd, conn))
325 silc_buffer_reset(conn->outbuf);
329 case SILC_STREAM_CAN_WRITE:
330 SILC_LOG_DEBUG(("Write HTTP data"));
332 conn->touched = silc_time();
334 /* Write pending data to stream */
335 while (silc_buffer_len(conn->outbuf) > 0) {
336 ret = silc_stream_write(conn->stream, silc_buffer_data(conn->outbuf),
337 silc_buffer_len(conn->outbuf));
339 if (ret == 0 || ret == -2) {
340 conn->keepalive = FALSE;
341 silc_http_server_close_connection(conn);
346 /* Cannot write now, write later. */
350 silc_buffer_pull(conn->outbuf, ret);
353 /* Data sent, close connection */
354 SILC_LOG_DEBUG(("Data sent"));
355 silc_http_server_close_connection(conn);
359 conn->keepalive = FALSE;
360 silc_http_server_close_connection(conn);
365 /* Accepts new connection */
367 static void silc_http_server_new_connection(SilcNetStatus status,
371 SilcHttpServer httpd = context;
372 SilcHttpConnection conn;
373 const char *hostname = NULL, *ip = NULL;
375 /* Get free connection */
376 silc_list_start(httpd->conns);
377 conn = silc_list_get(httpd->conns);
379 conn = silc_http_server_alloc_connection();
381 silc_stream_destroy(stream);
387 conn->stream = stream;
389 silc_socket_stream_get_info(stream, NULL, &hostname, &ip, NULL);
390 SILC_LOG_INFO(("HTTPD: New connection %s (%s)", hostname, ip));
392 /* Schedule the connection for data I/O */
393 silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn);
395 /* Add connection timeout check */
396 silc_schedule_task_add_timeout(httpd->schedule,
397 silc_http_server_connection_timeout, conn,
398 SILC_HTTP_SERVER_TIMEOUT, 0);
402 /******************************* Public API *********************************/
404 /* Allocate HTTP server */
406 SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port,
407 SilcUInt32 max_connections,
408 SilcSchedule schedule,
409 SilcHttpServerCallback callback,
412 SilcHttpServer httpd;
413 SilcHttpConnection conn;
416 SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port));
418 if (!ip || !schedule || !callback)
421 httpd = silc_calloc(1, sizeof(*httpd));
425 /* Create server listener */
427 silc_net_tcp_create_listener(&ip, 1, port, TRUE, FALSE, schedule,
428 silc_http_server_new_connection, httpd);
429 if (!httpd->listener) {
430 SILC_LOG_ERROR(("Could not bind HTTP server at %s:%d", ip, port));
431 silc_http_server_free(httpd);
435 httpd->schedule = schedule;
436 httpd->callback = callback;
437 httpd->context = context;
439 /* Allocate connections list */
440 if (!max_connections)
441 max_connections = SILC_HTTP_SERVER_CONNS;
442 for (i = 0; i < max_connections; i++) {
443 conn = silc_http_server_alloc_connection();
446 silc_list_add(httpd->conns, conn);
447 silc_list_add(httpd->allconns, conn);
450 SILC_LOG_DEBUG(("HTTP Server started"));
455 /* Free HTTP server */
457 void silc_http_server_free(SilcHttpServer httpd)
459 SilcHttpConnection conn;
461 silc_list_start(httpd->allconns);
462 while ((conn = silc_list_get(httpd->allconns))) {
463 conn->keepalive = FALSE;
464 silc_http_server_close_connection(conn);
465 silc_buffer_free(conn->inbuf);
466 silc_buffer_free(conn->outbuf);
471 silc_net_close_listener(httpd->listener);
476 /* Send HTTP data to connection */
478 SilcBool silc_http_server_send(SilcHttpServer httpd,
479 SilcHttpConnection conn,
483 unsigned char *headers, tmp[16];
484 SilcUInt32 headers_len;
487 SILC_LOG_DEBUG(("Sending HTTP data"));
489 conn->touched = silc_time();
492 silc_buffer_set(&h, SILC_HTTP_SERVER_HEADER,
493 strlen(SILC_HTTP_SERVER_HEADER));
494 ret = silc_http_server_send_internal(httpd, conn, &h, TRUE);
496 conn->keepalive = FALSE;
497 silc_http_server_close_connection(conn);
501 if (!conn->headers) {
502 conn->headers = silc_mime_alloc();
503 if (!conn->headers) {
504 conn->keepalive = FALSE;
505 silc_http_server_close_connection(conn);
510 snprintf(tmp, sizeof(tmp), "%d", (int)silc_buffer_len(data));
511 silc_mime_add_field(conn->headers, "Content-Length", tmp);
512 silc_mime_add_field(conn->headers, "Connection", "keep-alive");
513 snprintf(tmp, sizeof(tmp), "%d", (int)SILC_HTTP_SERVER_TIMEOUT);
514 silc_mime_add_field(conn->headers, "Keep-alive", tmp);
515 silc_mime_add_field(conn->headers, "Last-Modified",
516 silc_time_string(conn->touched));
518 headers = silc_mime_encode(conn->headers, &headers_len);
520 silc_buffer_set(&h, headers, headers_len);
521 if (!silc_http_server_send_internal(httpd, conn, &h, TRUE)) {
522 conn->keepalive = FALSE;
523 silc_http_server_close_connection(conn);
529 /* Write the page data */
530 return silc_http_server_send_internal(httpd, conn, data, FALSE);
533 /* Send error reply */
535 SilcBool silc_http_server_send_error(SilcHttpServer httpd,
536 SilcHttpConnection conn,
538 const char *error_message)
541 SilcBufferStruct data;
543 memset(&data, 0, sizeof(data));
544 silc_buffer_strformat(&data,
545 "HTTP/1.1 ", error, "\r\n\r\n", error_message,
548 /* Send the message */
549 ret = silc_http_server_send_internal(httpd, conn, &data, FALSE);
551 silc_buffer_purge(&data);
553 /* Close connection */
554 conn->keepalive = FALSE;
555 silc_http_server_close_connection(conn);
562 const char *silc_http_server_get_header(SilcHttpServer httpd,
563 SilcHttpConnection conn,
566 if (!conn->curheaders)
568 return silc_mime_get_field(conn->curheaders, field);
573 SilcBool silc_http_server_add_header(SilcHttpServer httpd,
574 SilcHttpConnection conn,
578 SILC_LOG_DEBUG(("Adding header %s:%s", field, value));
580 if (!conn->headers) {
581 conn->headers = silc_mime_alloc();
582 if (!conn->headers) {
583 silc_http_server_close_connection(conn);
588 silc_mime_add_field(conn->headers, field, value);