/* silchttpserver.c Author: Pekka Riikonen Copyright (C) 2006 - 2007 Pekka Riikonen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "silc.h" #include "silchttpserver.h" /************************** Types and definitions ***************************/ #define SILC_HTTP_SERVER_TIMEOUT 120 /* Connection timeout */ #define SILC_HTTP_SERVER_CONNS 2 /* Default number of connections */ #define SILC_HTTP_SERVER_BUFLEN 1024 /* Default data buffer length */ #define SILC_HTTP_SERVER_HEADER "HTTP/1.1 200 OK\r\nServer: SILCHTTP/1.0\r\n" /* HTTP server context */ struct SilcHttpServerStruct { SilcNetListener listener; /* Server listener */ SilcSchedule schedule; /* Scheduler */ SilcList allconns; /* All connections */ SilcList conns; /* Connection free list */ SilcHttpServerCallback callback; /* Requset callback */ void *context; /* Request callback context */ }; /* HTTP connection context */ struct SilcHttpConnectionStruct { struct SilcHttpConnectionStruct *next; struct SilcHttpConnectionStruct *next2; SilcHttpServer httpd; /* Server */ SilcStream stream; /* Connection stream */ SilcBuffer inbuf; /* Read data buffer */ SilcBuffer outbuf; /* Write data buffer */ SilcInt64 touched; /* Time last connection was touched */ SilcMime curheaders; /* HTTP request headers */ SilcMime headers; /* HTTP reply headers */ unsigned char *hptr; /* Pointer to start of headers */ char *method; /* Method */ char *uri; /* URI */ unsigned int keepalive : 1; /* Keep alive */ }; /************************ Static utility functions **************************/ /* Close HTTP connection */ static void silc_http_server_close_connection(SilcHttpConnection conn) { if (conn->headers) { silc_mime_free(conn->headers); conn->headers = NULL; } if (conn->curheaders) { silc_mime_free(conn->curheaders); conn->curheaders = NULL; } silc_buffer_clear(conn->inbuf); silc_buffer_clear(conn->outbuf); silc_buffer_reset(conn->inbuf); silc_buffer_reset(conn->outbuf); conn->hptr = conn->method = conn->uri = NULL; if (conn->keepalive) return; SILC_LOG_DEBUG(("Closing HTTP connection %p", conn)); silc_schedule_task_del_by_context(conn->httpd->schedule, conn); silc_stream_set_notifier(conn->stream, conn->httpd->schedule, NULL, NULL); silc_stream_destroy(conn->stream); conn->stream = NULL; /* Add to free list */ silc_list_add(conn->httpd->conns, conn); } /* Parse HTTP data */ static SilcBool silc_http_server_parse(SilcHttpServer httpd, SilcHttpConnection conn) { SilcUInt32 data_len, cll; unsigned char *data, *tmp; const char *value, *cl; SilcBufferStruct postdata; int i; SILC_LOG_DEBUG(("Parsing HTTP data")); data = silc_buffer_data(conn->inbuf); data_len = silc_buffer_len(conn->inbuf); /* Check for end of headers */ for (i = 0; i < data_len ; i++) { if (data_len - i >= 4 && data[i ] == '\r' && data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n') break; } if (i == data_len) return TRUE; SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(conn->inbuf), silc_buffer_len(conn->inbuf)); if (!conn->method && !conn->uri) { tmp = memchr(data, '\n', data_len); if (!tmp || tmp[-1] != '\r') { if (data_len < SILC_HTTP_SERVER_BUFLEN) return TRUE; return FALSE; } *tmp = 0; /* Get method */ if (strchr(data, ' ')) *strchr(data, ' ') = 0; conn->method = data; SILC_LOG_DEBUG(("Method: '%s'", conn->method)); /* Get URI */ tmp = memchr(data, '\0', data_len); if (!tmp) { if (data_len < SILC_HTTP_SERVER_BUFLEN) return TRUE; return FALSE; } tmp++; if (strchr(tmp, ' ')) *strchr(tmp, ' ') = 0; conn->uri = tmp; SILC_LOG_DEBUG(("URI: '%s'", conn->uri)); /* Protocol version compatibility */ tmp = ((unsigned char *)memchr(tmp, '\0', data_len - (tmp - data))) + 1; SILC_LOG_DEBUG(("Protocol: %s", tmp)); if (strstr(tmp, "HTTP/1.0")) conn->keepalive = FALSE; if (strstr(tmp, "HTTP/1.1")) conn->keepalive = TRUE; if (strstr(tmp, "HTTP/1.2")) conn->keepalive = TRUE; /* Get HTTP headers */ tmp = memchr(tmp, '\0', data_len - (tmp - data)); if (!tmp) { if (data_len < SILC_HTTP_SERVER_BUFLEN) return TRUE; return FALSE; } if (data_len - (tmp - data) < 2) { if (data_len < SILC_HTTP_SERVER_BUFLEN) return TRUE; return FALSE; } conn->hptr = ++tmp; } /* Parse headers and data area */ conn->curheaders = silc_mime_decode(NULL, conn->hptr, data_len - (conn->hptr - data)); if (!conn->curheaders) return FALSE; /* Check for persistent connection */ value = silc_mime_get_field(conn->curheaders, "Connection"); if (value && !strcasecmp(value, "close")) conn->keepalive = FALSE; /* Deliver request to caller */ if (!strcasecmp(conn->method, "GET") || !strcasecmp(conn->method, "HEAD")) { httpd->callback(httpd, conn, conn->uri, conn->method, NULL, httpd->context); } else if (!strcasecmp(conn->method, "POST")) { /* Get POST data */ tmp = (unsigned char *)silc_mime_get_data(conn->curheaders, &data_len); if (!tmp) return FALSE; /* Check we have received all data */ cl = silc_mime_get_field(conn->curheaders, "Content-Length"); if (cl && sscanf(cl, "%lu", (unsigned long *)&cll) == 1) { if (data_len < cll) { /* More data to come */ silc_mime_free(conn->curheaders); conn->curheaders = NULL; return TRUE; } } silc_buffer_set(&postdata, tmp, data_len); SILC_LOG_HEXDUMP(("HTTP POST data"), tmp, data_len); httpd->callback(httpd, conn, conn->uri, conn->method, &postdata, httpd->context); } else { /* Send bad request */ silc_http_server_send_error(httpd, conn, "400 Bad Request", "

400 Bad Request

"); return TRUE; } return TRUE; } /* Send HTTP data to connection */ static SilcBool silc_http_server_send_internal(SilcHttpServer httpd, SilcHttpConnection conn, SilcBuffer data, SilcBool headers) { int ret; SILC_LOG_HEXDUMP(("HTTP data"), silc_buffer_data(data), silc_buffer_len(data)); /* Write the packet to the stream */ while (silc_buffer_len(data) > 0) { ret = silc_stream_write(conn->stream, silc_buffer_data(data), silc_buffer_len(data)); if (ret == 0 || ret == - 2) return FALSE; if (ret == -1) { /* Cannot write now, write later. */ if (silc_buffer_len(data) - ret >= silc_buffer_taillen(conn->outbuf)) if (!silc_buffer_realloc(conn->outbuf, silc_buffer_truelen(conn->outbuf) + silc_buffer_len(data) - ret)) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return FALSE; } silc_buffer_pull_tail(conn->outbuf, silc_buffer_len(data) - ret); silc_buffer_put(conn->outbuf, silc_buffer_data(data) + ret, silc_buffer_len(data) - ret); return TRUE; } /* Wrote data */ silc_buffer_pull(data, ret); } if (!headers) { /* Data sent, close connection */ SILC_LOG_DEBUG(("Data sent %p", conn)); silc_http_server_close_connection(conn); } return TRUE; } /* Allocate connection context */ static SilcHttpConnection silc_http_server_alloc_connection(void) { SilcHttpConnection conn; conn = silc_calloc(1, sizeof(*conn)); if (!conn) return NULL; conn->inbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN); if (!conn->inbuf) { silc_free(conn); return NULL; } conn->outbuf = silc_buffer_alloc(SILC_HTTP_SERVER_BUFLEN); if (!conn->outbuf) { silc_buffer_free(conn->inbuf); silc_free(conn); return NULL; } silc_buffer_reset(conn->inbuf); silc_buffer_reset(conn->outbuf); return conn; } /* Check if connection has timedout */ SILC_TASK_CALLBACK(silc_http_server_connection_timeout) { SilcHttpConnection conn = context; SilcInt64 curtime = silc_time(); if (curtime - conn->touched > SILC_HTTP_SERVER_TIMEOUT) { SILC_LOG_DEBUG(("Connection timeout %p", conn)); conn->keepalive = FALSE; silc_http_server_close_connection(conn); return; } silc_schedule_task_add_timeout(conn->httpd->schedule, silc_http_server_connection_timeout, conn, SILC_HTTP_SERVER_TIMEOUT, 0); } /* Data I/O callback */ static void silc_http_server_io(SilcStream stream, SilcStreamStatus status, void *context) { SilcHttpConnection conn = context; SilcHttpServer httpd = conn->httpd; int ret; switch (status) { case SILC_STREAM_CAN_READ: SILC_LOG_DEBUG(("Read HTTP data %p", conn)); conn->touched = silc_time(); /* Make sure we have fair amount of free space in inbuf */ if (silc_buffer_taillen(conn->inbuf) < SILC_HTTP_SERVER_BUFLEN) if (!silc_buffer_realloc(conn->inbuf, silc_buffer_truelen(conn->inbuf) + SILC_HTTP_SERVER_BUFLEN * 2)) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return; } /* Read data from stream */ ret = silc_stream_read(conn->stream, conn->inbuf->tail, silc_buffer_taillen(conn->inbuf)); if (ret == 0 || ret == -2) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return; } if (ret == -1) { /* Cannot read now, do it later. */ silc_buffer_pull(conn->inbuf, silc_buffer_len(conn->inbuf)); return; } SILC_LOG_DEBUG(("Read %d bytes data", ret)); /* Parse the data */ silc_buffer_pull_tail(conn->inbuf, ret); if (!silc_http_server_parse(httpd, conn)) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); } break; case SILC_STREAM_CAN_WRITE: SILC_LOG_DEBUG(("Write HTTP data %p", conn)); conn->touched = silc_time(); /* Write pending data to stream */ while (silc_buffer_len(conn->outbuf) > 0) { ret = silc_stream_write(conn->stream, silc_buffer_data(conn->outbuf), silc_buffer_len(conn->outbuf)); if (ret == 0 || ret == -2) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return; } if (ret == -1) /* Cannot write now, write later. */ return; /* Wrote data */ silc_buffer_pull(conn->outbuf, ret); } /* Data sent, close connection */ SILC_LOG_DEBUG(("Data sent")); silc_http_server_close_connection(conn); break; default: conn->keepalive = FALSE; silc_http_server_close_connection(conn); break; } } /* Accepts new connection */ static void silc_http_server_new_connection(SilcNetStatus status, SilcStream stream, void *context) { SilcHttpServer httpd = context; SilcHttpConnection conn; const char *hostname = NULL, *ip = NULL; /* Get free connection */ silc_list_start(httpd->conns); conn = silc_list_get(httpd->conns); if (!conn) { /* Add new connection */ conn = silc_http_server_alloc_connection(); if (!conn) { silc_stream_destroy(stream); return; } silc_list_add(httpd->allconns, conn); } silc_list_del(httpd->conns, conn); conn->httpd = httpd; conn->stream = stream; silc_socket_stream_get_info(stream, NULL, &hostname, &ip, NULL); SILC_LOG_INFO(("HTTPD: New connection %s (%s)", hostname, ip)); SILC_LOG_DEBUG(("New connection %p", conn)); /* Schedule the connection for data I/O */ silc_stream_set_notifier(stream, httpd->schedule, silc_http_server_io, conn); /* Add connection timeout check */ silc_schedule_task_add_timeout(httpd->schedule, silc_http_server_connection_timeout, conn, SILC_HTTP_SERVER_TIMEOUT, 0); } /******************************* Public API *********************************/ /* Allocate HTTP server */ SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port, SilcSchedule schedule, SilcHttpServerCallback callback, void *context) { SilcHttpServer httpd; SilcHttpConnection conn; int i; SILC_LOG_DEBUG(("Start HTTP server at %s:%d", ip, port)); if (!ip || !schedule || !callback) return FALSE; httpd = silc_calloc(1, sizeof(*httpd)); if (!httpd) return NULL; /* Create server listener */ httpd->listener = silc_net_tcp_create_listener(&ip, 1, port, TRUE, FALSE, schedule, silc_http_server_new_connection, httpd); if (!httpd->listener) { SILC_LOG_ERROR(("Could not bind HTTP server at %s:%d", ip, port)); silc_http_server_free(httpd); return NULL; } httpd->schedule = schedule; httpd->callback = callback; httpd->context = context; silc_list_init(httpd->conns, struct SilcHttpConnectionStruct, next); silc_list_init(httpd->allconns, struct SilcHttpConnectionStruct, next2); /* Allocate connections list */ for (i = 0; i < SILC_HTTP_SERVER_CONNS; i++) { conn = silc_http_server_alloc_connection(); if (!conn) break; silc_list_add(httpd->conns, conn); silc_list_add(httpd->allconns, conn); conn->httpd = httpd; } SILC_LOG_DEBUG(("HTTP Server started")); return httpd; } /* Free HTTP server */ void silc_http_server_free(SilcHttpServer httpd) { SilcHttpConnection conn; silc_list_start(httpd->allconns); while ((conn = silc_list_get(httpd->allconns))) { conn->keepalive = FALSE; if (conn->httpd && conn->stream) silc_http_server_close_connection(conn); silc_buffer_free(conn->inbuf); silc_buffer_free(conn->outbuf); silc_free(conn); } if (httpd->listener) silc_net_close_listener(httpd->listener); silc_free(httpd); } /* Send HTTP data to connection */ SilcBool silc_http_server_send(SilcHttpServer httpd, SilcHttpConnection conn, SilcBuffer data) { SilcBufferStruct h; unsigned char *headers, tmp[16]; SilcUInt32 headers_len; SilcBool ret; SILC_LOG_DEBUG(("Sending HTTP data")); conn->touched = silc_time(); /* Write headers */ silc_buffer_set(&h, SILC_HTTP_SERVER_HEADER, strlen(SILC_HTTP_SERVER_HEADER)); ret = silc_http_server_send_internal(httpd, conn, &h, TRUE); if (!ret) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return FALSE; } if (!conn->headers) { conn->headers = silc_mime_alloc(); if (!conn->headers) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return FALSE; } } silc_mime_add_field(conn->headers, "Last-Modified", silc_time_string(conn->touched)); silc_snprintf(tmp, sizeof(tmp), "%d", (int)silc_buffer_len(data)); silc_mime_add_field(conn->headers, "Content-Length", tmp); if (conn->keepalive) { silc_mime_add_field(conn->headers, "Connection", "keep-alive"); silc_snprintf(tmp, sizeof(tmp), "%d", (int)SILC_HTTP_SERVER_TIMEOUT); silc_mime_add_field(conn->headers, "Keep-alive", tmp); } headers = silc_mime_encode(conn->headers, &headers_len); if (headers) { silc_buffer_set(&h, headers, headers_len); if (!silc_http_server_send_internal(httpd, conn, &h, TRUE)) { conn->keepalive = FALSE; silc_http_server_close_connection(conn); return FALSE; } silc_free(headers); } /* Write the page data */ return silc_http_server_send_internal(httpd, conn, data, FALSE); } /* Send error reply */ SilcBool silc_http_server_send_error(SilcHttpServer httpd, SilcHttpConnection conn, const char *error, const char *error_message) { SilcBool ret; SilcBufferStruct data; memset(&data, 0, sizeof(data)); silc_buffer_strformat(&data, "HTTP/1.1 ", error, "\r\n\r\n", error_message, SILC_STRFMT_END); /* Send the message */ ret = silc_http_server_send_internal(httpd, conn, &data, FALSE); silc_buffer_purge(&data); /* Close connection */ conn->keepalive = FALSE; silc_http_server_close_connection(conn); return ret; } /* Get field */ const char *silc_http_server_get_header(SilcHttpServer httpd, SilcHttpConnection conn, const char *field) { if (!conn->curheaders) return NULL; return silc_mime_get_field(conn->curheaders, field); } /* Add field */ SilcBool silc_http_server_add_header(SilcHttpServer httpd, SilcHttpConnection conn, const char *field, const char *value) { SILC_LOG_DEBUG(("Adding header %s:%s", field, value)); if (!conn->headers) { conn->headers = silc_mime_alloc(); if (!conn->headers) { silc_http_server_close_connection(conn); return FALSE; } } silc_mime_add_field(conn->headers, field, value); return TRUE; }