summaryrefslogtreecommitdiffstats
path: root/cpukit/shttpd/shttpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'cpukit/shttpd/shttpd.c')
-rw-r--r--cpukit/shttpd/shttpd.c1130
1 files changed, 1130 insertions, 0 deletions
diff --git a/cpukit/shttpd/shttpd.c b/cpukit/shttpd/shttpd.c
new file mode 100644
index 0000000000..c9bd0c69d3
--- /dev/null
+++ b/cpukit/shttpd/shttpd.c
@@ -0,0 +1,1130 @@
+/*
+ * 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$
+ */
+
+#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}
+};
+
+/*
+ * 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[]);
+
+void
+decode_url_encoded_string(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 */
+}
+
+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';
+
+ decode_url_encoded_string(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");
+#ifndef NO_CGI
+ } else if (is_cgi(c->ctx, path)) {
+ 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 */
+ } 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;
+
+ 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)
+{
+ struct listener *l;
+ int sock;
+
+ if ((sock = open_listening_port(port)) == -1) {
+ } else if ((l = calloc(1, sizeof(*l))) == NULL) {
+ (void) closesocket(sock);
+ } else {
+ 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 < 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);
+ }
+
+#if 0
+ struct opt *opt;
+ /* Free strdup-ed temporary vars */
+ for (opt = options; opt->sw != 0; opt++)
+ if (opt->tmp) {
+ free(opt->tmp);
+ opt->tmp = NULL;
+ }
+#endif
+
+ /* 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 (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);
+}