version 0.1.6
[fms.git] / libs / shttpd / shttpd.c
diff --git a/libs/shttpd/shttpd.c b/libs/shttpd/shttpd.c
new file mode 100644 (file)
index 0000000..36372d2
--- /dev/null
@@ -0,0 +1,1165 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
+ * All rights reserved
+ *
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Sergey Lyubka wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.
+ */
+
+/*
+ * Small and portable HTTP server, http://shttpd.sourceforge.net
+ * $Id: shttpd.c,v 1.10 2007/06/01 17:59:32 drozd Exp $
+ */
+
+#include "defs.h"
+
+time_t         current_time;   /* Current UTC time             */
+int            tz_offset;      /* Time zone offset from UTC    */
+
+static LL_HEAD(listeners);     /* List of listening sockets    */
+
+const struct vec known_http_methods[] = {
+       {"GET",         3},
+       {"POST",        4},
+       {"PUT",         3},
+       {"DELETE",      6},
+       {"HEAD",        4},
+       {NULL,          0}
+};
+
+struct listener {
+       struct llhead   link;
+       struct shttpd_ctx *ctx;         /* Context that socket belongs  */
+       int             sock;           /* Listening socket             */
+       int             is_ssl;         /* Should be SSL-ed             */
+};
+
+/*
+ * This structure tells how HTTP headers must be parsed.
+ * Used by parse_headers() function.
+ */
+#define        OFFSET(x)       offsetof(struct headers, x)
+static const struct http_header http_headers[] = {
+       {16, HDR_INT,    OFFSET(cl),            "Content-Length: "      },
+       {14, HDR_STRING, OFFSET(ct),            "Content-Type: "        },
+       {12, HDR_STRING, OFFSET(useragent),     "User-Agent: "          },
+       {19, HDR_DATE,   OFFSET(ims),           "If-Modified-Since: "   },
+       {15, HDR_STRING, OFFSET(auth),          "Authorization: "       },
+       {9,  HDR_STRING, OFFSET(referer),       "Referer: "             },
+       {8,  HDR_STRING, OFFSET(cookie),        "Cookie: "              },
+       {10, HDR_STRING, OFFSET(location),      "Location: "            },
+       {8,  HDR_INT,    OFFSET(status),        "Status: "              },
+       {7,  HDR_STRING, OFFSET(range),         "Range: "               },
+       {12, HDR_STRING, OFFSET(connection),    "Connection: "          },
+       {19, HDR_STRING, OFFSET(transenc),      "Transfer-Encoding: "   },
+       {0,  HDR_INT,    0,                     NULL                    }
+};
+
+struct shttpd_ctx *init_ctx(const char *config_file, int argc, char *argv[]);
+
+int
+url_decode(const char *src, int src_len, char *dst, int dst_len)
+{
+       int     i, j, a, b;
+#define        HEXTOI(x)  (isdigit(x) ? x - '0' : x - 'W')
+
+       for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++)
+               switch (src[i]) {
+               case '%':
+                       if (isxdigit(((unsigned char *) src)[i + 1]) &&
+                           isxdigit(((unsigned char *) src)[i + 2])) {
+                               a = tolower(((unsigned char *)src)[i + 1]);
+                               b = tolower(((unsigned char *)src)[i + 2]);
+                               dst[j] = (HEXTOI(a) << 4) | HEXTOI(b);
+                               i += 2;
+                       } else {
+                               dst[j] = '%';
+                       }
+                       break;
+               case '+':
+                       dst[j] = ' ';
+                       break;
+               default:
+                       dst[j] = src[i];
+                       break;
+               }
+
+       dst[j] = '\0';  /* Null-terminate the destination */
+
+       return (j);
+}
+
+void
+shttpd_add_mime_type(struct shttpd_ctx *ctx, const char *ext, const char *mime)
+{
+       struct mime_type_link   *e;
+       const char              *error_msg = "shttpd_add_mime_type: no memory";
+
+       if ((e = malloc(sizeof(*e))) == NULL) {
+               elog(E_FATAL, 0, error_msg);
+       } else if ((e->ext= my_strdup(ext)) == NULL) {
+               elog(E_FATAL, 0, error_msg);
+       } else if ((e->mime = my_strdup(mime)) == NULL) {
+               elog(E_FATAL, 0, error_msg);
+       } else {
+               e->ext_len = strlen(ext);
+               LL_TAIL(&ctx->mime_types, &e->link);
+       }
+}
+
+
+static const char *
+is_alias(struct shttpd_ctx *ctx, const char *uri,
+               struct vec *a_uri, struct vec *a_path)
+{
+       const char      *p, *s = ctx->aliases;
+       size_t          len;
+
+       DBG(("is_alias: aliases [%s]", s == NULL ? "" : s));
+
+       FOR_EACH_WORD_IN_LIST(s, len) {
+               if ((p = memchr(s, '=', len)) != NULL &&
+                   memcmp(uri, s, p - s) == 0) {
+                       a_uri->ptr = s;
+                       a_uri->len = p - s;
+                       a_path->ptr = ++p;
+                       a_path->len = (s + len) - p;
+                       return (s);
+               }
+       }
+
+       return (NULL);
+}
+
+void
+stop_stream(struct stream *stream)
+{
+       if (stream->io_class != NULL && stream->io_class->close != NULL)
+               stream->io_class->close(stream);
+
+       stream->io_class= NULL;
+       stream->flags |= FLAG_CLOSED;
+       stream->flags &= ~(FLAG_R | FLAG_W | FLAG_ALWAYS_READY);
+
+       DBG(("%d %s stopped. %lu of content data, %d now in a buffer",
+           stream->conn->rem.chan.sock, 
+           stream->io_class ? stream->io_class->name : "(null)",
+           (unsigned long) stream->io.total, io_data_len(&stream->io)));
+}
+
+/*
+ * Setup listening socket on given port, return socket
+ */
+static int
+open_listening_port(int port)
+{
+       int             sock, on = 1;
+       struct usa      sa;
+
+#ifdef _WIN32
+       {WSADATA data;  WSAStartup(MAKEWORD(2,2), &data);}
+#endif /* _WIN32 */
+
+       sa.len                          = sizeof(sa.u.sin);
+       sa.u.sin.sin_family             = AF_INET;
+       sa.u.sin.sin_port               = htons((uint16_t) port);
+       sa.u.sin.sin_addr.s_addr        = htonl(INADDR_ANY);
+
+       if ((sock = socket(PF_INET, SOCK_STREAM, 6)) == -1)
+               goto fail;
+       if (set_non_blocking_mode(sock) != 0)
+               goto fail;
+       if (setsockopt(sock, SOL_SOCKET,
+           SO_REUSEADDR,(char *) &on, sizeof(on)) != 0)
+               goto fail;
+       if (bind(sock, &sa.u.sa, sa.len) < 0)
+               goto fail;
+       if (listen(sock, 128) != 0)
+               goto fail;
+
+#ifndef _WIN32
+       (void) fcntl(sock, F_SETFD, FD_CLOEXEC);
+#endif /* !_WIN32 */
+
+       return (sock);
+fail:
+       if (sock != -1)
+               (void) closesocket(sock);
+       elog(E_LOG, NULL, "open_listening_port(%d): %s", port, strerror(errno));
+       return (-1);
+}
+
+/*
+ * Check whether full request is buffered Return headers length, or 0
+ */
+int
+get_headers_len(const char *buf, size_t buflen)
+{
+       const char      *s, *e;
+       int             len = 0;
+
+       for (s = buf, e = s + buflen - 1; len == 0 && s < e; s++)
+               /* Control characters are not allowed but >=128 is. */
+               if (!isprint(*(unsigned char *)s) && *s != '\r' && *s != '\n' && *(unsigned char *)s < 128)
+                       len = -1;
+               else if (s[0] == '\n' && s[1] == '\n')
+                       len = s - buf + 2;
+               else if (s[0] == '\n' && &s[1] < e &&
+                   s[1] == '\r' && s[2] == '\n')
+                       len = s - buf + 3;
+
+       return (len);
+}
+
+/*
+ * Send error message back to a client.
+ */
+void
+send_server_error(struct conn *c, int status, const char *reason)
+{
+#ifdef EMBEDDED
+       struct llhead           *lp;
+       struct error_handler    *e;
+
+       LL_FOREACH(&c->ctx->error_handlers, lp) {
+               e = LL_ENTRY(lp, struct error_handler, link);
+
+               if (e->code == status) {
+                       if (c->loc.io_class != NULL &&
+                           c->loc.io_class->close != NULL)
+                               c->loc.io_class->close(&c->loc);
+                       io_clear(&c->loc.io);
+                       setup_embedded_stream(c, e->callback, NULL);
+                       return;
+               }
+       }
+#endif /* EMBEDDED */
+
+       io_clear(&c->loc.io);
+       c->loc.headers_len = c->loc.io.head = my_snprintf(c->loc.io.buf,
+           c->loc.io.size, "HTTP/1.1 %d %s\r\n\r\n%d %s",
+           status, reason, status, reason);
+       c->status = status;
+       stop_stream(&c->loc);
+}
+
+/*
+ * Convert month to the month number. Return -1 on error, or month number
+ */
+static int
+montoi(const char *s)
+{
+       static const char *ar[] = {
+               "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+       };
+       size_t  i;
+
+       for (i = 0; i < sizeof(ar) / sizeof(ar[0]); i++)
+               if (!strcmp(s, ar[i]))
+                       return (i);
+
+       return (-1);
+}
+
+/*
+ * Parse date-time string, and return the corresponding time_t value
+ */
+static time_t
+date_to_epoch(const char *s)
+{
+       struct tm       tm, *tmp;
+       char            mon[32];
+       int             sec, min, hour, mday, month, year;
+
+       (void) memset(&tm, 0, sizeof(tm));
+       sec = min = hour = mday = month = year = 0;
+
+       if (((sscanf(s, "%d/%3s/%d %d:%d:%d",
+           &mday, mon, &year, &hour, &min, &sec) == 6) ||
+           (sscanf(s, "%d %3s %d %d:%d:%d",
+           &mday, mon, &year, &hour, &min, &sec) == 6) ||
+           (sscanf(s, "%*3s, %d %3s %d %d:%d:%d",
+           &mday, mon, &year, &hour, &min, &sec) == 6) ||
+           (sscanf(s, "%d-%3s-%d %d:%d:%d",
+           &mday, mon, &year, &hour, &min, &sec) == 6)) &&
+           (month = montoi(mon)) != -1) {
+               tm.tm_mday      = mday;
+               tm.tm_mon       = month;
+               tm.tm_year      = year;
+               tm.tm_hour      = hour;
+               tm.tm_min       = min;
+               tm.tm_sec       = sec;
+       }
+
+       if (tm.tm_year > 1900)
+               tm.tm_year -= 1900;
+       else if (tm.tm_year < 70)
+               tm.tm_year += 100;
+
+       /* Set Daylight Saving Time field */
+       tmp = localtime(&current_time);
+       tm.tm_isdst = tmp->tm_isdst;
+
+       return (mktime(&tm));
+}
+
+static void
+remove_double_dots(char *s)
+{
+       char    *p = s;
+
+       while (*s != '\0') {
+               *p++ = *s++;
+               if (s[-1] == '/')
+                       while (*s == '.' || *s == '/')
+                               s++;
+       }
+       *p = '\0';
+}
+
+void
+parse_headers(const char *s, int len, struct headers *parsed)
+{
+       const struct http_header        *h;
+       union variant                   *v;
+       const char                      *p, *e = s + len;
+
+       DBG(("parsing headers (len %d): [%.*s]", len, len, s));
+
+       /* Loop through all headers in the request */
+       while (s < e) {
+
+               /* Find where this header ends */
+               for (p = s; p < e && *p != '\n'; ) p++;
+
+               /* Is this header known to us ? */
+               for (h = http_headers; h->len != 0; h++)
+                       if (e - s > h->len &&
+                           !my_strncasecmp(s, h->name, h->len))
+                               break;
+
+               /* If the header is known to us, store its value */
+               if (h->len != 0) {
+
+                       /* Shift to where value starts */
+                       s += h->len;
+
+                       /* Find place to store the value */
+                       v = (union variant *) ((char *) parsed + h->offset);
+
+                       /* Fetch header value into the connection structure */
+                       if (h->type == HDR_STRING) {
+                               v->v_vec.ptr = s;
+                               v->v_vec.len = p - s;
+                               if (p[-1] == '\r' && v->v_vec.len > 0)
+                                       v->v_vec.len--;
+                       } else if (h->type == HDR_INT) {
+                               v->v_big_int = strtoul(s, NULL, 10);
+                       } else if (h->type == HDR_DATE) {
+                               v->v_time = date_to_epoch(s);
+                       }
+               }
+
+               s = p + 1;      /* Shift to the next header */
+       }
+}
+
+/*
+ * For given directory path, substitute it to valid index file.
+ * Return 0 if index file has been found, -1 if not found
+ */
+static int
+find_index_file(struct conn *c, char *path, size_t maxpath, struct stat *stp)
+{
+       char            buf[FILENAME_MAX];
+       const char      *s = c->ctx->index_files;
+       size_t          len;
+
+       FOR_EACH_WORD_IN_LIST(s, len) {
+               my_snprintf(buf, sizeof(buf), "%s%c%.*s",path, DIRSEP, len, s);
+               if (my_stat(buf, stp) == 0) {
+                       my_strlcpy(path, buf, maxpath);
+                       c->mime_type = get_mime_type(c->ctx, s, len);
+                       return (0);
+               }
+       }
+
+       return (-1);
+}
+
+/*
+ * Try to open requested file, return 0 if OK, -1 if error.
+ * If the file is given arguments using PATH_INFO mechanism,
+ * initialize pathinfo pointer.
+ */
+static int
+get_path_info(struct conn *c, char *path, struct stat *stp)
+{
+       char    *p, *e;
+
+       if (my_stat(path, stp) == 0)
+               return (0);
+
+       p = path + strlen(path);
+       e = path + strlen(c->ctx->document_root) + 2;
+       
+       /* Strip directory parts of the path one by one */
+       for (; p > e; p--)
+               if (*p == '/') {
+                       *p = '\0';
+                       if (!my_stat(path, stp) && !S_ISDIR(stp->st_mode)) {
+                               c->path_info = p + 1;
+                               return (0);
+                       } else {
+                               *p = '/';
+                       }
+               }
+
+       return (-1);
+}
+
+
+static void
+decide_what_to_do(struct conn *c)
+{
+       char            path[URI_MAX], buf[1024];
+       struct vec      alias_uri, alias_path;
+       struct stat     st;
+       int             rc;
+#ifdef EMBEDDED
+       struct registered_uri   *ruri;
+#endif /* EMBEDDED */
+
+       DBG(("decide_what_to_do: [%s]", c->uri));
+
+       if ((c->query = strchr(c->uri, '?')) != NULL)
+               *c->query++ = '\0';
+
+       url_decode(c->uri, strlen(c->uri), c->uri, strlen(c->uri) + 1);
+       remove_double_dots(c->uri);
+       
+       if (strlen(c->uri) + strlen(c->ctx->document_root) >= sizeof(path)) {
+               send_server_error(c, 400, "URI is too long");
+               return;
+       }
+
+       (void) my_snprintf(path, sizeof(path), "%s%s",
+           c->ctx->document_root, c->uri);
+
+       /* User may use the aliases - check URI for mount point */
+       if (is_alias(c->ctx, c->uri, &alias_uri, &alias_path) != NULL) {
+               (void) my_snprintf(path, sizeof(path), "%.*s%s",
+                   alias_path.len, alias_path.ptr, c->uri + alias_uri.len);
+               DBG(("using alias %.*s -> %.*s", alias_uri.len, alias_uri.ptr,
+                   alias_path.len, alias_path.ptr));
+       }
+
+#if !defined(NO_AUTH)
+       if (check_authorization(c, path) != 1) {
+               send_authorization_request(c);
+       } else
+#endif /* NO_AUTH */
+#ifdef EMBEDDED
+       if ((ruri = is_registered_uri(c->ctx, c->uri)) != NULL) {
+               setup_embedded_stream(c, ruri->callback, ruri->callback_data);
+       } else
+#endif /* EMBEDDED */
+       if (strstr(path, HTPASSWD)) {
+               /* Do not allow to view passwords files */
+               send_server_error(c, 403, "Forbidden");
+       } else
+#if !defined(NO_AUTH)
+       if ((c->method == METHOD_PUT || c->method == METHOD_DELETE) &&
+           (c->ctx->put_auth_file == NULL || !is_authorized_for_put(c))) {
+               send_authorization_request(c);
+       } else
+#endif /* NO_AUTH */
+       if (c->method == METHOD_PUT) {
+               c->status = my_stat(path, &st) == 0 ? 200 : 201;
+
+               if (c->ch.range.v_vec.len > 0) {
+                       send_server_error(c, 501, "PUT Range Not Implemented");
+               } else if ((rc = put_dir(path)) == 0) {
+                       send_server_error(c, 200, "OK");
+               } else if (rc == -1) {
+                       send_server_error(c, 500, "PUT Directory Error");
+               } else if (c->rem.content_len == 0) {
+                       send_server_error(c, 411, "Length Required");
+               } else if ((c->loc.chan.fd = my_open(path, O_WRONLY | O_BINARY |
+                   O_CREAT | O_NONBLOCK | O_TRUNC, 0644)) == -1) {
+                       send_server_error(c, 500, "PUT Error");
+               } else {
+                       DBG(("PUT file [%s]", c->uri));
+                       c->loc.io_class = &io_file;
+                       c->loc.flags |= FLAG_W | FLAG_ALWAYS_READY ;
+               }
+       } else if (c->method == METHOD_DELETE) {
+               DBG(("DELETE [%s]", c->uri));
+               if (my_remove(path) == 0)
+                       send_server_error(c, 200, "OK");
+               else
+                       send_server_error(c, 500, "DELETE Error");
+       } else if (get_path_info(c, path, &st) != 0) {
+               send_server_error(c, 404, "Not Found");
+       } else if (S_ISDIR(st.st_mode) && path[strlen(path) - 1] != '/') {
+               (void) my_snprintf(buf, sizeof(buf),
+                       "Moved Permanently\r\nLocation: %s/", c->uri);
+               send_server_error(c, 301, buf);
+       } else if (S_ISDIR(st.st_mode) &&
+           find_index_file(c, path, sizeof(path) - 1, &st) == -1 &&
+           c->ctx->dirlist == 0) {
+               send_server_error(c, 403, "Directory Listing Denied");
+       } else if (S_ISDIR(st.st_mode) && c->ctx->dirlist) {
+               if ((c->loc.chan.dir.path = my_strdup(path)) != NULL)
+                       get_dir(c);
+               else
+                       send_server_error(c, 500, "GET Directory Error");
+       } else if (S_ISDIR(st.st_mode) && c->ctx->dirlist == 0) {
+               send_server_error(c, 403, "Directory listing denied");
+#if !defined(NO_CGI)
+       } else if (match_extension(path, c->ctx->cgi_extensions)) {
+               if (c->method != METHOD_POST && c->method != METHOD_GET) {
+                       send_server_error(c, 501, "Bad method ");
+               } else if ((run_cgi(c, path)) == -1) {
+                       send_server_error(c, 500, "Cannot exec CGI");
+               } else {
+                       do_cgi(c);
+               }
+#endif /* NO_CGI */
+#if !defined(NO_SSI)
+       } else if (match_extension(path, c->ctx->ssi_extensions)) {
+               if ((c->loc.chan.fd = my_open(path,
+                   O_RDONLY | O_BINARY, 0644)) == -1) {
+                       send_server_error(c, 500, "SSI open error");
+               } else {
+                       do_ssi(c);
+               }
+#endif /* NO_CGI */
+       } else if (c->ch.ims.v_time && st.st_mtime <= c->ch.ims.v_time) {
+               send_server_error(c, 304, "Not Modified");
+       } else if ((c->loc.chan.fd = my_open(path,
+           O_RDONLY | O_BINARY, 0644)) != -1) {
+               get_file(c, &st);
+       } else {
+               send_server_error(c, 500, "Internal Error");
+       }
+}
+
+static int
+set_request_method(struct conn *c)
+{
+       const struct vec        *v;
+
+       assert(c->rem.io.head >= MIN_REQ_LEN);
+
+       /* Set the request method */
+       for (v = known_http_methods; v->ptr != NULL; v++)
+               if (!memcmp(c->rem.io.buf, v->ptr, v->len)) {
+                       c->method = v - known_http_methods;
+                       break;
+               }
+
+       return (v->ptr == NULL);
+}
+
+static void
+parse_http_request(struct conn *c)
+{
+       char    *s, *e, *p, *start;
+       char    *end_number;
+       int     uri_len, req_len;
+
+       s = c->rem.io.buf;
+       req_len = c->rem.headers_len = get_headers_len(s, c->rem.io.head);
+
+       if (req_len == 0 && io_space_len(&c->rem.io) == 0)
+               send_server_error(c, 400, "Request is too big");
+
+       if (req_len == 0)
+               return;
+       else if (req_len < MIN_REQ_LEN)
+               send_server_error(c, 400, "Bad request");
+       else if (set_request_method(c))
+               send_server_error(c, 501, "Method Not Implemented");
+       else if ((c->request = my_strndup(s, req_len)) == NULL)
+               send_server_error(c, 500, "Cannot allocate request");
+
+       if (c->loc.flags & FLAG_CLOSED)
+               return;
+
+       DBG(("Conn %d: parsing request: [%.*s]", c->rem.chan.sock, req_len, s));
+       c->rem.flags |= FLAG_HEADERS_PARSED;
+
+       /* Set headers pointer. Headers follow the request line */
+       c->headers = memchr(c->request, '\n', req_len);
+       assert(c->headers != NULL);
+       assert(c->headers < c->request + req_len);
+       if (c->headers > c->request && c->headers[-1] == '\r')
+               c->headers[-1] = '\0';
+       *c->headers++ = '\0';
+
+       /*
+        * Now make a copy of the URI, because it will be URL-decoded,
+        * and we need a copy of unmodified URI for the access log.
+        * First, we skip the REQUEST_METHOD and shift to the URI.
+        */
+       for (p = c->request, e = p + req_len; *p != ' ' && p < e; p++);
+       while (p < e && *p == ' ') p++;
+
+       /* Now remember where URI starts, and shift to the end of URI */
+       for (start = p; p < e && !isspace((unsigned char)*p); ) p++;
+       uri_len = p - start;
+       /* Skip space following the URI */
+       while (p < e && *p == ' ') p++;
+
+       /* Now comes the HTTP-Version in the form HTTP/<major>.<minor> */
+       if (strncmp(p, "HTTP/", 5) != 0) {
+               send_server_error(c, 400, "Bad HTTP version");
+               return;
+       }
+       p += 5;
+       /* Parse the HTTP major version number */
+       c->major_version = strtoul(p, &end_number, 10);
+       if (end_number == p || *end_number != '.') {
+               send_server_error(c, 400, "Bad HTTP major version");
+               return;
+       }
+       p = end_number + 1;
+       /* Parse the minor version number */
+       c->minor_version = strtoul(p, &end_number, 10);
+       if (end_number == p || *end_number != '\0') {
+               send_server_error(c, 400, "Bad HTTP minor version");
+               return;
+       }
+       /* Version must be <=1.1 */
+       if (c->major_version > 1 ||
+           (c->major_version == 1 && c->minor_version > 1)) {
+               send_server_error(c, 505, "HTTP version not supported");
+               return;
+       }
+
+       if (uri_len <= 0) {
+               send_server_error(c, 400, "Bad URI");
+       } else if ((c->uri = malloc(uri_len + 1)) == NULL) {
+               send_server_error(c, 500, "Cannot allocate URI");
+       } else {
+               my_strlcpy(c->uri, (char *) start, uri_len + 1);
+               parse_headers(c->headers,
+                   (c->request + req_len) - c->headers, &c->ch);
+
+               /* Remove the length of request from total, count only data */
+               assert(c->rem.io.total >= (big_int_t) req_len);
+               c->rem.io.total -= req_len;
+
+               c->rem.content_len = c->ch.cl.v_big_int;
+               io_inc_tail(&c->rem.io, req_len);
+
+               decide_what_to_do(c);
+       }
+}
+
+void
+shttpd_add_socket(struct shttpd_ctx *ctx, int sock)
+{
+       struct conn     *c;
+       struct usa      sa;
+       int             l = ctx->inetd_mode ? E_FATAL : E_LOG;
+#if !defined(NO_SSL)
+       SSL             *ssl = NULL;
+#endif /* NO_SSL */
+
+       sa.len = sizeof(sa.u.sin);
+       (void) set_non_blocking_mode(sock);
+
+       if (getpeername(sock, &sa.u.sa, &sa.len)) {
+               elog(l, NULL, "add_socket: %s", strerror(errno));
+#if !defined(NO_SSL)
+       } else if (ctx->ssl_ctx && (ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
+               elog(l, NULL, "add_socket: SSL_new: %s", strerror(ERRNO));
+               (void) closesocket(sock);
+       } else if (ctx->ssl_ctx && SSL_set_fd(ssl, sock) == 0) {
+               elog(l, NULL, "add_socket: SSL_set_fd: %s", strerror(ERRNO));
+               (void) closesocket(sock);
+               SSL_free(ssl);
+#endif /* NO_SSL */
+       } else if ((c = calloc(1, sizeof(*c) + 2 * ctx->io_buf_size)) == NULL) {
+
+#if !defined(NO_SSL)
+               if (ssl)
+                       SSL_free(ssl);
+#endif /* NO_SSL */
+               (void) closesocket(sock);
+               elog(l, NULL, "add_socket: calloc: %s", strerror(ERRNO));
+       } else {
+               ctx->nrequests++;
+               c->rem.conn = c->loc.conn = c;
+               c->ctx          = ctx;
+               c->sa           = sa;
+               c->birth_time   = current_time;
+               c->expire_time  = current_time + EXPIRE_TIME;
+
+               (void) getsockname(sock, &sa.u.sa, &sa.len);
+               c->loc_port = sa.u.sin.sin_port;
+
+               set_close_on_exec(sock);
+
+               c->loc.io_class = NULL;
+       
+               c->rem.io_class = &io_socket;
+               c->rem.chan.sock = sock;
+
+               /* Set IO buffers */
+               c->loc.io.buf   = (char *) (c + 1);
+               c->rem.io.buf   = c->loc.io.buf + ctx->io_buf_size;
+               c->loc.io.size  = c->rem.io.size = ctx->io_buf_size;
+
+#if !defined(NO_SSL)
+               if (ssl) {
+                       c->rem.io_class = &io_ssl;
+                       c->rem.chan.ssl.sock = sock;
+                       c->rem.chan.ssl.ssl = ssl;
+                       ssl_handshake(&c->rem);
+               }
+#endif /* NO_SSL */
+
+               EnterCriticalSection(&ctx->mutex);
+               LL_TAIL(&ctx->connections, &c->link);
+               ctx->nactive++;
+               LeaveCriticalSection(&ctx->mutex);
+               
+               DBG(("%s:%hu connected (socket %d)",
+                   inet_ntoa(* (struct in_addr *) &sa.u.sin.sin_addr.s_addr),
+                   ntohs(sa.u.sin.sin_port), sock));
+       }
+}
+
+int
+shttpd_active(struct shttpd_ctx *ctx)
+{
+       return (ctx->nactive);
+}
+
+/*
+ * Setup a listening socket on given port. Return opened socket or -1
+ */
+int
+shttpd_listen(struct shttpd_ctx *ctx, int port, int is_ssl)
+{
+       struct listener *l;
+       int             sock;
+
+       if ((sock = open_listening_port(port)) == -1) {
+               elog(E_FATAL, NULL, "cannot open port %d", port);
+       } else if ((l = calloc(1, sizeof(*l))) == NULL) {
+               (void) closesocket(sock);
+               elog(E_FATAL, NULL, "cannot allocate listener");
+       } else if (is_ssl && ctx->ssl_ctx == NULL) {
+               (void) closesocket(sock);
+               elog(E_FATAL, NULL, "cannot add SSL socket, "
+                   "please specify certificate file");
+       } else {
+               l->is_ssl = is_ssl;
+               l->sock = sock;
+               l->ctx  = ctx;
+               LL_TAIL(&listeners, &l->link);
+               DBG(("shttpd_listen: added socket %d", sock));
+       }
+
+       return (sock);
+}
+
+int
+shttpd_accept(int lsn_sock, int milliseconds)
+{
+       struct timeval  tv;
+       struct usa      sa;
+       fd_set          read_set;
+       int             sock = -1;
+       
+       tv.tv_sec       = milliseconds / 1000;
+       tv.tv_usec      = milliseconds % 1000;
+       sa.len          = sizeof(sa.u.sin);
+       FD_ZERO(&read_set);
+       FD_SET(lsn_sock, &read_set);
+       
+       if (select(lsn_sock + 1, &read_set, NULL, NULL, &tv) == 1)
+               sock = accept(lsn_sock, &sa.u.sa, &sa.len);
+
+       return (sock);
+}
+
+static void
+read_stream(struct stream *stream)
+{
+       int     n, len;
+
+       len = io_space_len(&stream->io);
+       assert(len > 0);
+
+       /* Do not read more that needed */
+       if (stream->content_len > 0 &&
+           stream->io.total + len > stream->content_len)
+               len = stream->content_len - stream->io.total;
+
+       /* Read from underlying channel */
+       n = stream->nread_last = stream->io_class->read(stream,
+           io_space(&stream->io), len);
+
+       if (n > 0)
+               io_inc_head(&stream->io, n);
+       else if (n == -1 && (ERRNO == EINTR || ERRNO == EWOULDBLOCK))
+               n = n;  /* Ignore EINTR and EAGAIN */
+       else if (!(stream->flags & FLAG_DONT_CLOSE))
+               stop_stream(stream);
+
+       DBG(("read_stream (%d %s): read %d/%d/%lu bytes (errno %d)",
+           stream->conn->rem.chan.sock,
+           stream->io_class ? stream->io_class->name : "(null)",
+           n, len, (unsigned long) stream->io.total, ERRNO));
+
+       /*
+        * Close the local stream if everything was read
+        * XXX We do not close the remote stream though! It may be
+        * a POST data completed transfer, we do not want the socket
+        * to be closed.
+        */
+       if (stream->content_len > 0 && stream == &stream->conn->loc) {
+               assert(stream->io.total <= stream->content_len);
+               if (stream->io.total == stream->content_len)
+                       stop_stream(stream);
+       }
+
+       stream->conn->expire_time = current_time + EXPIRE_TIME;
+}
+
+static void
+write_stream(struct stream *from, struct stream *to)
+{
+       int     n, len;
+
+       len = io_data_len(&from->io);
+       assert(len > 0);
+
+       /* TODO: should be assert on CAN_WRITE flag */
+       n = to->io_class->write(to, io_data(&from->io), len);
+       to->conn->expire_time = current_time + EXPIRE_TIME;
+       DBG(("write_stream (%d %s): written %d/%d bytes (errno %d)",
+           to->conn->rem.chan.sock,
+           to->io_class ? to->io_class->name : "(null)", n, len, ERRNO));
+
+       if (n > 0)
+               io_inc_tail(&from->io, n);
+       else if (n == -1 && (ERRNO == EINTR || ERRNO == EWOULDBLOCK))
+               n = n;  /* Ignore EINTR and EAGAIN */
+       else if (!(to->flags & FLAG_DONT_CLOSE))
+               stop_stream(to);
+}
+
+
+static void
+disconnect(struct conn *c)
+{
+       static const struct vec ka = {"keep-alive", 10};
+       int                     dont_close;
+
+       DBG(("Disconnecting %d (%.*s)", c->rem.chan.sock,
+           c->ch.connection.v_vec.len, c->ch.connection.v_vec.ptr));
+
+#if !defined(_WIN32) || defined(NO_GUI)
+       if (c->ctx->access_log != NULL)
+#endif /* _WIN32 */
+                       log_access(c->ctx->access_log, c);
+
+       /* In inetd mode, exit if request is finished. */
+       if (c->ctx->inetd_mode)
+               exit(0);
+
+       if (c->loc.io_class != NULL && c->loc.io_class->close != NULL)
+               c->loc.io_class->close(&c->loc);
+
+       /*
+        * Check the "Connection: " header before we free c->request
+        * If it its 'keep-alive', then do not close the connection
+        */
+       dont_close =  c->ch.connection.v_vec.len >= ka.len &&
+           !my_strncasecmp(ka.ptr, c->ch.connection.v_vec.ptr, ka.len);
+
+       if (c->request)
+               free(c->request);
+       if (c->uri)
+               free(c->uri);
+
+       /* Handle Keep-Alive */
+       dont_close = 0;
+       if (dont_close) {
+               c->loc.io_class = NULL;
+               c->loc.flags = c->rem.flags = 0;
+               c->query = c->request = c->uri = c->path_info = NULL;
+               c->mime_type = NULL;
+               (void) memset(&c->ch, 0, sizeof(c->ch));
+               io_clear(&c->rem.io);
+               io_clear(&c->loc.io);
+               c->rem.io.total = c->loc.io.total = 0;
+       } else {
+               if (c->rem.io_class != NULL)
+                       c->rem.io_class->close(&c->rem);
+
+               EnterCriticalSection(&c->ctx->mutex);
+               LL_DEL(&c->link);
+               c->ctx->nactive--;
+               assert(c->ctx->nactive >= 0);
+               LeaveCriticalSection(&c->ctx->mutex);
+
+               free(c);
+       }
+}
+
+static void
+add_to_set(int fd, fd_set *set, int *max_fd)
+{
+       FD_SET(fd, set);
+       if (fd > *max_fd)
+               *max_fd = fd;
+}
+
+/*
+ * One iteration of server loop. This is the core of the data exchange.
+ */
+void
+shttpd_poll(struct shttpd_ctx *ctx, int milliseconds)
+{
+       struct llhead   *lp, *tmp;
+       struct listener *l;
+       struct conn     *c;
+       struct timeval  tv;                     /* Timeout for select() */
+       fd_set          read_set, write_set;
+       int             sock, max_fd = -1, msec = milliseconds;
+       struct usa      sa;
+
+       current_time = time(0);
+       FD_ZERO(&read_set);
+       FD_ZERO(&write_set);
+
+       /* Add listening sockets to the read set */
+       LL_FOREACH(&listeners, lp) {
+               l = LL_ENTRY(lp, struct listener, link);
+               FD_SET(l->sock, &read_set);
+               if (l->sock > max_fd)
+                       max_fd = l->sock;
+               DBG(("FD_SET(%d) (listening)", l->sock));
+       }
+
+       /* Multiplex streams */
+       LL_FOREACH(&ctx->connections, lp) {
+               c = LL_ENTRY(lp, struct conn, link);
+               
+               /* If there is a space in remote IO, check remote socket */
+               if (io_space_len(&c->rem.io))
+                       add_to_set(c->rem.chan.fd, &read_set, &max_fd);
+
+#if !defined(NO_CGI)
+               /*
+                * If there is a space in local IO, and local endpoint is
+                * CGI, check local socket for read availability
+                */
+               if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) &&
+                   c->loc.io_class == &io_cgi)
+                       add_to_set(c->loc.chan.fd, &read_set, &max_fd);
+
+               /*
+                * If there is some data read from remote socket, and
+                * local endpoint is CGI, check local for write availability
+                */
+               if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) &&
+                   c->loc.io_class == &io_cgi)
+                       add_to_set(c->loc.chan.fd, &write_set, &max_fd);
+#endif /* NO_CGI */
+
+               /*
+                * If there is some data read from local endpoint, check the
+                * remote socket for write availability
+                */
+               if (io_data_len(&c->loc.io))
+                       add_to_set(c->rem.chan.fd, &write_set, &max_fd);
+
+               if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) &&
+                   (c->loc.flags & FLAG_ALWAYS_READY))
+                       msec = 0;
+               
+               if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) &&
+                   (c->loc.flags & FLAG_ALWAYS_READY))
+                       msec = 0;
+       }
+
+       tv.tv_sec = msec / 1000;
+       tv.tv_usec = msec % 1000;
+
+       /* Check IO readiness */
+       if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) < 0) {
+#ifdef _WIN32
+               /*
+                * On windows, if read_set and write_set are empty,
+                * select() returns "Invalid parameter" error
+                * (at least on my Windows XP Pro). So in this case,
+                * we sleep here.
+                */
+               Sleep(milliseconds);
+#endif /* _WIN32 */
+               DBG(("select: %d", ERRNO));
+               return;
+       }
+
+       /* Check for incoming connections on listener sockets */
+       LL_FOREACH(&listeners, lp) {
+               l = LL_ENTRY(lp, struct listener, link);
+               if (!FD_ISSET(l->sock, &read_set))
+                       continue;
+               do {
+                       sa.len = sizeof(sa.u.sin);
+                       if ((sock = accept(l->sock, &sa.u.sa, &sa.len)) != -1) {
+#if defined(_WIN32)
+                               shttpd_add_socket(ctx, sock);
+#else
+                               if (sock < (int) FD_SETSIZE) {
+                                       shttpd_add_socket(ctx, sock);
+                               } else {
+                                       elog(E_LOG, NULL,
+                                          "shttpd_poll: ctx %p: disarding "
+                                          "socket %d, too busy", ctx, sock);
+                                       (void) closesocket(sock);
+                               }
+#endif /* _WIN32 */
+                       }
+               } while (sock != -1);
+       }
+
+       /* Process all connections */
+       LL_FOREACH_SAFE(&ctx->connections, lp, tmp) {
+               c = LL_ENTRY(lp, struct conn, link);
+
+               /* Read from remote end if it is ready */
+               if (FD_ISSET(c->rem.chan.fd, &read_set) &&
+                   io_space_len(&c->rem.io))
+                       read_stream(&c->rem);
+
+               /* If the request is not parsed yet, do so */
+               if (!(c->rem.flags & FLAG_HEADERS_PARSED))
+                       parse_http_request(c);
+
+               DBG(("loc: %u [%.*s]", io_data_len(&c->loc.io),
+                   io_data_len(&c->loc.io), io_data(&c->loc.io)));
+               DBG(("rem: %u [%.*s]", io_data_len(&c->rem.io),
+                   io_data_len(&c->rem.io), io_data(&c->rem.io)));
+
+               /* Read from the local end if it is ready */
+               if (io_space_len(&c->loc.io) &&
+                   ((c->loc.flags & FLAG_ALWAYS_READY)
+                   
+#if !defined(NO_CGI)
+                   ||(c->loc.io_class == &io_cgi &&
+                    FD_ISSET(c->loc.chan.fd, &read_set))
+#endif /* NO_CGI */
+                   ))
+                       read_stream(&c->loc);
+
+               if (io_data_len(&c->rem.io) > 0 && (c->loc.flags & FLAG_W) &&
+                   c->loc.io_class != NULL && c->loc.io_class->write != NULL)
+                       write_stream(&c->rem, &c->loc);
+
+               if (io_data_len(&c->loc.io) > 0 && c->rem.io_class != NULL)
+                       write_stream(&c->loc, &c->rem); 
+
+               if (c->rem.nread_last > 0)
+                       c->ctx->in += c->rem.nread_last;
+               if (c->loc.nread_last > 0)
+                       c->ctx->out += c->loc.nread_last;
+
+               /* Check whether we should close this connection */
+               if ((current_time > c->expire_time) ||
+                   (c->rem.flags & FLAG_CLOSED) ||
+                   ((c->loc.flags & FLAG_CLOSED) && !io_data_len(&c->loc.io)))
+                       disconnect(c);
+       }
+}
+
+/*
+ * Deallocate shttpd object, free up the resources
+ */
+void
+shttpd_fini(struct shttpd_ctx *ctx)
+{
+       struct llhead           *lp, *tmp;
+       struct mime_type_link   *mtl;
+       struct conn             *c;
+       struct listener         *l;
+       struct registered_uri   *ruri;
+
+       /* Free configured mime types */
+       LL_FOREACH_SAFE(&ctx->mime_types, lp, tmp) {
+               mtl = LL_ENTRY(lp, struct mime_type_link, link);
+               free(mtl->mime);
+               free(mtl->ext);
+               free(mtl);
+       }
+
+       /* Free all connections */
+       LL_FOREACH_SAFE(&ctx->connections, lp, tmp) {
+               c = LL_ENTRY(lp, struct conn, link);
+               disconnect(c);
+       }
+
+       /* Free registered URIs (must be done after disconnect()) */
+       LL_FOREACH_SAFE(&ctx->registered_uris, lp, tmp) {
+               ruri = LL_ENTRY(lp, struct registered_uri, link);
+               free((void *)ruri->uri);
+               free(ruri);
+       }
+
+       /* Free listener sockets for this context */
+       LL_FOREACH_SAFE(&listeners, lp, tmp) {
+               l = LL_ENTRY(lp, struct listener, link);
+               (void) closesocket(l->sock);
+               LL_DEL(&l->link);
+               free(l);
+       }
+
+#if !defined(NO_SSI)
+       free_ssi_funcs(ctx);
+#endif /* NO_SSI */
+
+       if (ctx->access_log)            (void) fclose(ctx->access_log);
+       if (ctx->error_log)             (void) fclose(ctx->error_log);
+       if (ctx->put_auth_file)         free(ctx->put_auth_file);
+       if (ctx->document_root)         free(ctx->document_root);
+       if (ctx->index_files)           free(ctx->index_files);
+       if (ctx->aliases)               free(ctx->aliases);
+#if !defined(NO_CGI)
+       if (ctx->cgi_vars)              free(ctx->cgi_vars);
+       if (ctx->cgi_extensions)        free(ctx->cgi_extensions);
+       if (ctx->cgi_interpreter)       free(ctx->cgi_interpreter);
+#endif /* NO_CGI */
+       if (ctx->auth_realm)            free(ctx->auth_realm);
+       if (ctx->global_passwd_file)    free(ctx->global_passwd_file);
+       if (ctx->uid)                   free(ctx->uid);
+
+       /* TODO: free SSL context */
+
+       free(ctx);
+}
+
+void
+open_listening_ports(struct shttpd_ctx *ctx)
+{
+       const char      *p = ctx->ports;
+       int             len, is_ssl;
+       
+       FOR_EACH_WORD_IN_LIST(p, len) {
+               is_ssl = p[len - 1] == 's' ? 1 : 0;
+               if (shttpd_listen(ctx, atoi(p), is_ssl) == -1)
+                       elog(E_FATAL, NULL,
+                           "Cannot open socket on port %d", atoi(p));
+       }
+}