From 6e8488ced827350e5c0e95f7a9feffdd80281eae Mon Sep 17 00:00:00 2001 From: Pekka Riikonen Date: Tue, 10 Jun 2008 08:38:14 +0300 Subject: [PATCH] Added SILC HTTP Server API Provides simple HTTP server and HTTP PHP translator with simple API. --- TODO | 2 + configure.ad | 3 + lib/Makefile.ad | 3 +- lib/silchttp/Makefile.ad | 36 ++ lib/silchttp/silchttpphp.c | 121 +++++ lib/silchttp/silchttpphp.h | 64 +++ lib/silchttp/silchttpserver.c | 641 +++++++++++++++++++++++ lib/silchttp/silchttpserver.h | 232 ++++++++ lib/silchttp/tests/Makefile.am | 27 + lib/silchttp/tests/test_silchttpserver.c | 145 +++++ lib/silcutil/silcruntime.h.in | 2 + 11 files changed, 1275 insertions(+), 1 deletion(-) create mode 100644 lib/silchttp/Makefile.ad create mode 100644 lib/silchttp/silchttpphp.c create mode 100644 lib/silchttp/silchttpphp.h create mode 100644 lib/silchttp/silchttpserver.c create mode 100644 lib/silchttp/silchttpserver.h create mode 100644 lib/silchttp/tests/Makefile.am create mode 100644 lib/silchttp/tests/test_silchttpserver.c diff --git a/TODO b/TODO index 1aafd2f2..7ebae882 100644 --- a/TODO +++ b/TODO @@ -44,6 +44,8 @@ Runtime library, lib/silcutil/ SILC currently supports SOCKS4 and SOCKS5 but it needs to be compiled in separately. + o Bring silchttp HTTP server library to SRT. (***DONE) + o Simple SILC Rand API for pseudo-random numbers. (***DONE) o Add directory opening/traversing functions (***DONE, TODO Windows & Symbian) diff --git a/configure.ad b/configure.ad index 7d76b9a4..cf337358 100644 --- a/configure.ad +++ b/configure.ad @@ -856,6 +856,7 @@ LDFLAGS="-L\$(silc_top_srcdir)/lib $LDFLAGS" SILC_LIB_INCLUDES="$SILC_LIB_INCLUDES -I$SILC_TOP_SRCDIR/lib/contrib" SILC_LIB_INCLUDES="$SILC_LIB_INCLUDES -I$SILC_TOP_SRCDIR/lib/silcutil" +SILC_LIB_INCLUDES="$SILC_LIB_INCLUDES -I$SILC_TOP_SRCDIR/lib/silchttp" # Check for iconv support @@ -1272,6 +1273,8 @@ lib/silcutil/tests/Makefile lib/silcutil/unix/Makefile lib/silcutil/win32/Makefile lib/silcutil/symbian/Makefile +lib/silchttp/Makefile +lib/silchttp/tests/Makefile lib/srt.pc ) diff --git a/lib/Makefile.ad b/lib/Makefile.ad index 218694a5..3ad5741e 100644 --- a/lib/Makefile.ad +++ b/lib/Makefile.ad @@ -25,7 +25,8 @@ RUNTIME_AGE=@RUNTIME_AGE@ # SILC Library dirs RUNTIME_DIRS = \ contrib \ - silcutil + silcutil \ + silchttp if SILC_ENABLE_SHARED if SILC_WIN32 diff --git a/lib/silchttp/Makefile.ad b/lib/silchttp/Makefile.ad new file mode 100644 index 00000000..030e4782 --- /dev/null +++ b/lib/silchttp/Makefile.ad @@ -0,0 +1,36 @@ +# +# Makefile.ad +# +# Author: Pekka Riikonen +# +# Copyright (C) 2006 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. +# + +AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign + +noinst_LTLIBRARIES = libsilchttp.la + +libsilchttp_la_SOURCES = \ + silchttpserver.c \ + silchttpphp.c + +#ifdef SILC_DIST_TOOLKIT +include_HEADERS = \ + silchttpserver.h \ + silchttpphp.h + +SILC_EXTRA_DIST = tests +#endif SILC_DIST_TOOLKIT + +EXTRA_DIST = *.h $(SILC_EXTRA_DIST) + +include $(top_srcdir)/Makefile.defines.in diff --git a/lib/silchttp/silchttpphp.c b/lib/silchttp/silchttpphp.c new file mode 100644 index 00000000..2a5a28d6 --- /dev/null +++ b/lib/silchttp/silchttpphp.c @@ -0,0 +1,121 @@ +/* + + silchttpphp.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 "silcruntime.h" +#include "silchttpphp.h" + +/* Executes PHP code and returns result */ + +SilcBuffer silc_http_php(char *php_data) +{ + SilcBuffer ret; + char *name, tmp[32]; + + /* Write the PHP data to temporary file */ +#ifdef SILC_WIN32 + name = _mktemp("silchttpphpXXXXXX"); + if (!name) + return NULL; +#else + memset(tmp, 0, sizeof(tmp)); + silc_snprintf(tmp, sizeof(tmp) - 1, "/tmp/silchttpphpXXXXXX"); + if (mkstemp(tmp) == -1) + return NULL; + name = tmp; +#endif /* SILC_WIN32 */ + + silc_file_writefile_mode(name, php_data, strlen(php_data), 0600); + + /* Execute PHP */ + ret = silc_http_php_file(name); + +#ifdef SILC_WIN32 + _unlink(name); +#else + unlink(name); +#endif /* SILC_WIN32 */ + + return ret; +} + +/* Loads PHP file and executes the PHP code and returns the result */ + +SilcBuffer silc_http_php_file(const char *filename) +{ + SilcBuffer ret = NULL; + unsigned char tmp[8192]; + FILE *fd; + int len; + + SILC_LOG_DEBUG(("Executing PHP")); + + memset(tmp, 0, sizeof(tmp)); + silc_snprintf(tmp, sizeof(tmp) - 1, "php -f %s", filename); + +#ifdef SILC_WIN32 + fd = _popen(tmp, "r"); +#else + fd = popen(tmp, "r"); +#endif /* SILC_WIN32 */ + if (!fd) + return NULL; + + /* Read the result */ + do { + len = fread(tmp, 1, sizeof(tmp), fd); + if (len < 0) { + silc_buffer_free(ret); +#ifdef SILC_WIN32 + _pclose(fd); +#else + pclose(fd); +#endif /* SILC_WIN32 */ + return NULL; + } + + if (len) { + if (!ret) { + ret = silc_buffer_alloc(0); + if (!ret) { +#ifdef SILC_WIN32 + _pclose(fd); +#else + pclose(fd); +#endif /* SILC_WIN32 */ + return NULL; + } + } + + silc_buffer_format(ret, + SILC_STR_ADVANCE, + SILC_STR_DATA(tmp, len), + SILC_STR_END); + } + } while (len); + + if (ret) { + silc_buffer_format(ret, + SILC_STR_ADVANCE, + SILC_STR_DATA('\0', 1), + SILC_STR_END); + silc_buffer_push(ret, silc_buffer_truelen(ret)); + } + + return ret; +} diff --git a/lib/silchttp/silchttpphp.h b/lib/silchttp/silchttpphp.h new file mode 100644 index 00000000..f9502aed --- /dev/null +++ b/lib/silchttp/silchttpphp.h @@ -0,0 +1,64 @@ +/* + + silchttpphp.h + + Author: Pekka Riikonen + + Copyright (C) 2006 - 2008 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. + +*/ + +/****h* silchttp/HTTP PHP Translator + * + * DESCRIPTION + * + * PHP translator for SILC HTTP Server, enabling PHP support for the pages + * served through the SILC HTTP Server interface (silchttpserver.h). + * The PHP must be installed in the system and must be in the execution + * path for the interface to work. + * + ***/ + +#ifndef SILCHTTPPHP_H +#define SILCHTTPPHP_H + +/****f* silchttp/silc_http_php + * + * SYNOPSIS + * + * SilcBuffer silc_http_php(char *php_data); + * + * DESCRIPTION + * + * Executes the PHP code contained in the buffer `php_data' and returns + * the result in the allocated SilcBuffer or NULL on error. The caller + * must free the returned buffer. + * + ***/ +SilcBuffer silc_http_php(char *php_data); + +/****f* silchttp/silc_http_php_file + * + * SYNOPSIS + * + * SilcBuffer silc_http_php_file(const char *filepath); + * + * DESCRIPTION + * + * Reads the PHP contents from the file indicated by the `filepath' and + * executes the PHP code and returns the result in the allocated + * SilcBuffer or NULL on error. The caller must free the returned buffer. + * + ***/ +SilcBuffer silc_http_php_file(const char *filename); + +#endif /* SILCHTTPPHP_H */ diff --git a/lib/silchttp/silchttpserver.c b/lib/silchttp/silchttpserver.c new file mode 100644 index 00000000..0cc3ccdc --- /dev/null +++ b/lib/silchttp/silchttpserver.c @@ -0,0 +1,641 @@ +/* + + 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 "silcruntime.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; + unsigned long 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", &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(SilcResult 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 (!schedule) + schedule = silc_schedule_get_global(); + + 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; +} diff --git a/lib/silchttp/silchttpserver.h b/lib/silchttp/silchttpserver.h new file mode 100644 index 00000000..a966794f --- /dev/null +++ b/lib/silchttp/silchttpserver.h @@ -0,0 +1,232 @@ +/* + + silchttpserver.h + + Author: Pekka Riikonen + + Copyright (C) 2006 - 2008 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. + +*/ + +/****h* silchttp/HTTP Server Interface + * + * DESCRIPTION + * + * Very simple HTTP server interface. This HTTP server supports basic HTTP + * features. All pages on the server are dynamically created by the caller + * of this interface. The server does not support plugins, modules, cgi-bin, + * server-side includes or any other special features. Naturally, the caller + * of this interface may itself implement such features. + * + ***/ + +#ifndef SILCHTTPSERVER_H +#define SILCHTTPSERVER_H + +/****s* silchttp/SilcHttpServer + * + * NAME + * + * typedef struct SilcHttpServerStruct *SilcHttpServer; + * + * DESCRIPTION + * + * The actual HTTP server allocated with silc_http_server_alloc and + * freed with silc_http_server_free. + * + ***/ +typedef struct SilcHttpServerStruct *SilcHttpServer; + +/****s* silchttp/SilcHttpConnection + * + * NAME + * + * typedef struct SilcHttpConnectionStruct *SilcHttpConnection; + * + * DESCRIPTION + * + * HTTP connection context. This is allocated by the library and + * delivered to application in SilcHttpServerCallback callbcak function. + * It is given as argument to many silc_http_server_* functions. + * It is freed automatically by the library. + * + ***/ +typedef struct SilcHttpConnectionStruct *SilcHttpConnection; + +/****f* silchttp/SilcHttpServerCallback + * + * SYNOPSIS + * + * typedef void (*SilcHttpServerCallback)(SilcHttpServer httpd, + * SilcHttpConnection conn, + * const char *uri, + * const char *method, + * SilcBuffer data, + * void *context); + * + * DESCRIPTION + * + * The HTTP request callback that is called everytime a new HTTP request + * comes from a HTTP client. The `uri' is the requested URI (web page), + * and the `method' is the HTTP request method (GET, POST, etc.). The + * `data' is non-NULL only if the `method' is POST, and it includes the + * the POST data. + * + * The requested web page must be returned to the HTTP client from this + * callback by calling silc_http_server_send or error is returned by + * calling silc_http_server_send_error. + * + * The silc_http_server_get_header may be called to find a specific + * HTTP header from this request. New headers may be added to the + * reply by calling silc_http_server_add_header. + * + ***/ +typedef void (*SilcHttpServerCallback)(SilcHttpServer httpd, + SilcHttpConnection conn, + const char *uri, + const char *method, + SilcBuffer data, + void *context); + +/****f* silchttp/silc_http_server_alloc + * + * SYNOPSIS + * + * SilcHttpServer + * silc_http_server_alloc(const char *ip, SilcUInt16 port, + * SilcSchedule schedule, + * SilcHttpServerCallback callback, void *context); + * + * DESCRIPTION + * + * Allocates HTTP server and binds it to the IP address `ip' on the + * `port'. The `callback' with `context' will be called everytime a new + * HTTP request comes to the server from a HTTP client. In that callback + * the caller must then reply with the requested Web page or with error. + * If the `schedule' is NULL this will call silc_schedule_get_global to + * try to get global scheduler. + * + ***/ +SilcHttpServer silc_http_server_alloc(const char *ip, SilcUInt16 port, + SilcSchedule schedule, + SilcHttpServerCallback callback, + void *context); + +/****f* silchttp/silc_http_server_free + * + * SYNOPSIS + * + * void silc_http_server_free(SilcHttpServer httpd); + * + * DESCRIPTION + * + * Close HTTP server and free all resources. + * + ***/ +void silc_http_server_free(SilcHttpServer httpd); + +/****f* silchttp/silc_http_server_send + * + * SYNOPSIS + * + * SilcBool silc_http_server_send(SilcHttpServer httpd, + * SilcHttpConnection conn, + * SilcBuffer data); + * + * DESCRIPTION + * + * Send the HTTP data indicated by `data' buffer into the connection + * indicated by `conn'. Returns TRUE after the data is sent, and FALSE + * if error occurred. Usually the `data' would be the requested web page. + * + ***/ +SilcBool silc_http_server_send(SilcHttpServer httpd, + SilcHttpConnection conn, + SilcBuffer data); + +/****f* silchttp/silc_http_server_send_error + * + * SYNOPSIS + * + * SilcBool silc_http_server_send_error(SilcHttpServer httpd, + * SilcHttpConnection conn, + * const char *error, + * const char *error_message); + * + * DESCRIPTION + * + * Send HTTP error back to the connection indicated by `conn'. The + * `error' is one of the 4xx or 5xx errors defined by the HTTP protocol. + * The `error_message' is the optional error message sent to the + * connection. Returns FALSE if the error could not be sent. + * + * Typical errors are: 400 Bad Request + * 403 Forbidden + * 404 Not Found + * + * EXAMPLE + * + * silc_http_server_send_error(httpd, conn, "400 Bad Request", + * "

400 Bad Request!!

"); + * + ***/ +SilcBool silc_http_server_send_error(SilcHttpServer httpd, + SilcHttpConnection conn, + const char *error, + const char *error_message); + +/****f* silchttp/silc_http_server_get_header + * + * SYNOPSIS + * + * const char *silc_http_server_get_header(SilcHttpServer httpd, + * SilcHttpConnection conn, + * const char *field); + * + * DESCRIPTION + * + * Finds a header field indicated by `field' from the current HTTP + * request sent by the HTTP client. Returns the field value or NULL + * if such header field does not exist. + * + ***/ +const char *silc_http_server_get_header(SilcHttpServer httpd, + SilcHttpConnection conn, + const char *field); + +/****f* silchttp/silc_http_server_add_header + * + * SYNOPSIS + * + * SilcBool silc_http_server_add_header(SilcHttpServer httpd, + * SilcHttpConnection conn, + * const char *field, + * const char *value); + * + * DESCRIPTION + * + * Adds a new header to the HTTP headers to be sent back to the + * HTTP client. This may be called to add needed headers to the + * HTTP reply. + * + * EXAMPLE + * + * silc_http_server_add_header(httpd, conn, "Content-Type", "image/jpeg"); + * silc_http_server_send(httpd, conn, image_data); + * + ***/ +SilcBool silc_http_server_add_header(SilcHttpServer httpd, + SilcHttpConnection conn, + const char *field, + const char *value); + +#endif /* SILCHTTPSERVER_H */ diff --git a/lib/silchttp/tests/Makefile.am b/lib/silchttp/tests/Makefile.am new file mode 100644 index 00000000..763cfadd --- /dev/null +++ b/lib/silchttp/tests/Makefile.am @@ -0,0 +1,27 @@ +# +# Makefile.am +# +# Author: Pekka Riikonen +# +# Copyright (C) 2006 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. +# + +AUTOMAKE_OPTIONS = 1.0 no-dependencies foreign + +bin_PROGRAMS = test_silchttpserver + +test_silchttpserver_SOURCES = test_silchttpserver.c + +LIBS = $(SILC_COMMON_LIBS) +LDADD = -L.. -L../.. -lsrt -lsilchttp + +include $(top_srcdir)/Makefile.defines.in diff --git a/lib/silchttp/tests/test_silchttpserver.c b/lib/silchttp/tests/test_silchttpserver.c new file mode 100644 index 00000000..c2ce19d4 --- /dev/null +++ b/lib/silchttp/tests/test_silchttpserver.c @@ -0,0 +1,145 @@ +/* SilcHttpServer tests */ +/* Actually this is almost a full-fledged HTTP server. It can serve HTML + and PHP pages pretty well. In PHP the variables passed in URI with '?' + work in PHP script, with this HTTPD of ours, only if $_REQUEST variable + is used to fetch them (limitation in PHP command line version). In other + ways '?' in URI is not supported. */ +/* Usage: ./test_silchttpserver [-d] [] */ + +#include "silcruntime.h" +#include "../silchttpserver.h" +#include "../silchttpphp.h" + +char *htdocs = "."; + +/* Add proper content type to reply per URI */ + +static void http_content_type(SilcHttpServer httpd, SilcHttpConnection conn, + const char *uri) +{ + const char *type; + + type = silc_http_server_get_header(httpd, conn, "Content-Type"); + if (type) + silc_http_server_add_header(httpd, conn, "Content-Type", type); + else if (strstr(uri, ".jpg")) + silc_http_server_add_header(httpd, conn, "Content-Type", "image/jpeg"); + else if (strstr(uri, ".gif")) + silc_http_server_add_header(httpd, conn, "Content-Type", "image/gif"); + else if (strstr(uri, ".png")) + silc_http_server_add_header(httpd, conn, "Content-Type", "image/png"); + else if (strstr(uri, ".css")) + silc_http_server_add_header(httpd, conn, "Content-Type", "text/css"); + else if (strstr(uri, ".htm")) + silc_http_server_add_header(httpd, conn, "Content-Type", "text/html"); + else if (strstr(uri, ".php")) + silc_http_server_add_header(httpd, conn, "Content-Type", "text/html"); +} + +/* Serve pages */ + +static void http_callback_file(SilcHttpServer httpd, SilcHttpConnection conn, + const char *uri, const char *method, + SilcBuffer data, void *context) +{ + SilcBufferStruct page; + SilcBuffer php; + char *filedata, filename[256]; + SilcUInt32 data_len; + SilcBool usephp = FALSE; + + if (!strcasecmp(method, "GET")) { + if (strstr(uri, ".php")) + usephp = TRUE; + + if (!strcmp(uri, "/")) + snprintf(filename, sizeof(filename), "%s/index.html", htdocs); + else + snprintf(filename, sizeof(filename), "%s%s", htdocs, uri); + + if (strchr(filename, '?')) + *strchr(filename, '?') = ' '; + while (strchr(filename, '&')) + *strchr(filename, '&') = ' '; + + SILC_LOG_DEBUG(("Filename: '%s'", filename)); + + if (!usephp) { + filedata = silc_file_readfile(filename, &data_len, NULL); + if (!filedata) { + silc_http_server_send_error(httpd, conn, "404 Not Found", + "

404 Not Found

The page you are looking for cannot be located"); + return; + } + + http_content_type(httpd, conn, uri); + + /* Send page */ + silc_buffer_set(&page, filedata, data_len); + silc_http_server_send(httpd, conn, &page); + silc_buffer_purge(&page); + } else { + php = silc_http_php_file(filename); + if (!php) { + silc_http_server_send_error(httpd, conn, "404 Not Found", + "

404 Not Found

The page you are looking for cannot be located"); + return; + } + + http_content_type(httpd, conn, uri); + + /* Send page */ + silc_http_server_send(httpd, conn, php); + silc_buffer_free(php); + } + + return; + } + + silc_http_server_send_error(httpd, conn, "404 Not Found", + "

404 Not Found

The page you are looking for cannot be located"); +} + +int main(int argc, char **argv) +{ + SilcBool success = FALSE; + SilcSchedule schedule; + SilcHttpServer httpd; + + if (argc > 1) { + if (!strcmp(argv[1], "-d")) { + silc_log_debug(TRUE); + silc_log_debug_hexdump(TRUE); + silc_log_set_debug_string("*http*,*mime*"); + if (argc > 2) + htdocs = argv[2]; + } else { + htdocs = argv[1]; + } + } + + signal(SIGPIPE, SIG_IGN); + + SILC_LOG_DEBUG(("Allocating scheduler")); + schedule = silc_schedule_init(0, NULL, NULL, NULL); + if (!schedule) + goto err; + + SILC_LOG_DEBUG(("Allocating HTTP server at 127.0.0.1:5000")); + httpd = silc_http_server_alloc("127.0.0.1", 5000, schedule, + http_callback_file, NULL); + if (!httpd) + goto err; + + silc_schedule(schedule); + + silc_schedule_uninit(schedule); + + success = TRUE; + + err: + SILC_LOG_DEBUG(("Testing was %s", success ? "SUCCESS" : "FAILURE")); + fprintf(stderr, "Testing was %s\n", success ? "SUCCESS" : "FAILURE"); + + return success; +} diff --git a/lib/silcutil/silcruntime.h.in b/lib/silcutil/silcruntime.h.in index d0bc898c..a458af67 100644 --- a/lib/silcutil/silcruntime.h.in +++ b/lib/silcutil/silcruntime.h.in @@ -239,6 +239,8 @@ extern "C" { #include #include #include +#include +#include /* Runtime Toolkit API */ -- 2.24.0