Added SILC HTTP Server API
authorPekka Riikonen <priikone@silcnet.org>
Tue, 10 Jun 2008 05:38:14 +0000 (08:38 +0300)
committerPekka Riikonen <priikone@silcnet.org>
Tue, 10 Jun 2008 05:38:14 +0000 (08:38 +0300)
Provides simple HTTP server and HTTP PHP translator with simple API.

TODO
configure.ad
lib/Makefile.ad
lib/silchttp/Makefile.ad [new file with mode: 0644]
lib/silchttp/silchttpphp.c [new file with mode: 0644]
lib/silchttp/silchttpphp.h [new file with mode: 0644]
lib/silchttp/silchttpserver.c [new file with mode: 0644]
lib/silchttp/silchttpserver.h [new file with mode: 0644]
lib/silchttp/tests/Makefile.am [new file with mode: 0644]
lib/silchttp/tests/test_silchttpserver.c [new file with mode: 0644]
lib/silcutil/silcruntime.h.in

diff --git a/TODO b/TODO
index 1aafd2f2f37b1a642ffc9e72af99eea0e90db98b..7ebae882fdd9d13af89fedfae44d8a81d5ab7202 100644 (file)
--- 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)
index 7d76b9a4c89476c0206a409366828a3bd145cfa2..cf3373582f0a6df19d83bc82ae671c7bbfb5817c 100644 (file)
@@ -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
 )
 
index 218694a5458364e1e752734d767abc08d0d7cef7..3ad5741e2df9793fec9d48aa3ba8cd166a1103e5 100644 (file)
@@ -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 (file)
index 0000000..030e478
--- /dev/null
@@ -0,0 +1,36 @@
+#
+#  Makefile.ad
+#
+#  Author: Pekka Riikonen <priikone@silcnet.org>
+#
+#  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 (file)
index 0000000..2a5a28d
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+
+  silchttpphp.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  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 (file)
index 0000000..f9502ae
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+
+  silchttpphp.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  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 (file)
index 0000000..0cc3ccd
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+
+  silchttpserver.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  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",
+                               "<body><h1>400 Bad Request</h1><body>");
+    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 (file)
index 0000000..a966794
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+
+  silchttpserver.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  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",
+ *                                "<body><h1>400 Bad Request!!</h1></body>");
+ *
+ ***/
+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 (file)
index 0000000..763cfad
--- /dev/null
@@ -0,0 +1,27 @@
+#
+#  Makefile.am
+#
+#  Author: Pekka Riikonen <priikone@silcnet.org>
+#
+#  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 (file)
index 0000000..c2ce19d
--- /dev/null
@@ -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] [<htdocsdir>] */
+
+#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",
+                                   "<body><h1>404 Not Found</h1><p>The page you are looking for cannot be located</body>");
+       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",
+                                   "<body><h1>404 Not Found</h1><p>The page you are looking for cannot be located</body>");
+       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",
+                             "<body><h1>404 Not Found</h1><p>The page you are looking for cannot be located</body>");
+}
+
+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;
+}
index d0bc898c94f17eacfe76fc79cac36a9a8bb06a03..a458af67a64c32875e6eceb376a0bc5ac0fbbaf1 100644 (file)
@@ -239,6 +239,8 @@ extern "C" {
 #include <silcfdstream.h>
 #include <silcmime.h>
 #include <silcrand.h>
+#include <silchttpserver.h>
+#include <silchttpphp.h>
 
 /* Runtime Toolkit API */