From b81639075a4cd85e418ec5cb5d48e8b430d056c7 Mon Sep 17 00:00:00 2001 From: Duncan Mac-Vicar P Date: Thu, 16 Oct 2008 17:23:14 +0000 Subject: [PATCH] add embeded http library so we can test remote scenarios --- CMakeLists.txt | 3 +- vendor/CMakeLists.txt | 2 + vendor/shttpd/CMakeLists.txt | 29 + vendor/shttpd/auth.c | 419 ++++++++++ vendor/shttpd/cgi.c | 229 +++++ vendor/shttpd/compat_rtems.c | 198 +++++ vendor/shttpd/compat_rtems.h | 60 ++ vendor/shttpd/compat_unix.c | 128 +++ vendor/shttpd/compat_unix.h | 35 + vendor/shttpd/compat_win32.c | 687 +++++++++++++++ vendor/shttpd/compat_win32.h | 83 ++ vendor/shttpd/compat_wince.c | 1593 +++++++++++++++++++++++++++++++++++ vendor/shttpd/compat_wince.h | 145 ++++ vendor/shttpd/config.h | 30 + vendor/shttpd/defs.h | 395 +++++++++ vendor/shttpd/io.h | 97 +++ vendor/shttpd/io_cgi.c | 127 +++ vendor/shttpd/io_dir.c | 153 ++++ vendor/shttpd/io_emb.c | 291 +++++++ vendor/shttpd/io_file.c | 157 ++++ vendor/shttpd/io_socket.c | 39 + vendor/shttpd/io_ssi.c | 488 +++++++++++ vendor/shttpd/io_ssl.c | 85 ++ vendor/shttpd/llist.h | 59 ++ vendor/shttpd/log.c | 93 +++ vendor/shttpd/md5.c | 249 ++++++ vendor/shttpd/md5.h | 24 + vendor/shttpd/shttpd.c | 1903 ++++++++++++++++++++++++++++++++++++++++++ vendor/shttpd/shttpd.h | 111 +++ vendor/shttpd/ssl.h | 52 ++ vendor/shttpd/standalone.c | 72 ++ vendor/shttpd/std_includes.h | 40 + vendor/shttpd/string.c | 95 +++ 33 files changed, 8170 insertions(+), 1 deletion(-) create mode 100644 vendor/CMakeLists.txt create mode 100644 vendor/shttpd/CMakeLists.txt create mode 100644 vendor/shttpd/auth.c create mode 100644 vendor/shttpd/cgi.c create mode 100644 vendor/shttpd/compat_rtems.c create mode 100644 vendor/shttpd/compat_rtems.h create mode 100644 vendor/shttpd/compat_unix.c create mode 100644 vendor/shttpd/compat_unix.h create mode 100644 vendor/shttpd/compat_win32.c create mode 100644 vendor/shttpd/compat_win32.h create mode 100644 vendor/shttpd/compat_wince.c create mode 100644 vendor/shttpd/compat_wince.h create mode 100644 vendor/shttpd/config.h create mode 100644 vendor/shttpd/defs.h create mode 100644 vendor/shttpd/io.h create mode 100644 vendor/shttpd/io_cgi.c create mode 100644 vendor/shttpd/io_dir.c create mode 100644 vendor/shttpd/io_emb.c create mode 100644 vendor/shttpd/io_file.c create mode 100644 vendor/shttpd/io_socket.c create mode 100644 vendor/shttpd/io_ssi.c create mode 100644 vendor/shttpd/io_ssl.c create mode 100644 vendor/shttpd/llist.h create mode 100644 vendor/shttpd/log.c create mode 100644 vendor/shttpd/md5.c create mode 100644 vendor/shttpd/md5.h create mode 100644 vendor/shttpd/shttpd.c create mode 100644 vendor/shttpd/shttpd.h create mode 100644 vendor/shttpd/ssl.h create mode 100644 vendor/shttpd/standalone.c create mode 100644 vendor/shttpd/std_includes.h create mode 100644 vendor/shttpd/string.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 588bbf0..f56e1da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ MACRO(ADD_TESTS) FOREACH( loop_var ${ARGV} ) SET_SOURCE_FILES_PROPERTIES( ${loop_var}_test.cc COMPILE_FLAGS "-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN -DBOOST_AUTO_TEST_MAIN=\"\" " ) ADD_EXECUTABLE( ${loop_var}_test ${loop_var}_test.cc ) - TARGET_LINK_LIBRARIES( ${loop_var}_test zypp boost_unit_test_framework) + TARGET_LINK_LIBRARIES( ${loop_var}_test zypp boost_unit_test_framework zypp_test_utils) ADD_TEST( ${loop_var}_test ${CMAKE_CURRENT_BINARY_DIR}/${loop_var}_test) ENDFOREACH( loop_var ) ENDMACRO(ADD_TESTS) @@ -161,6 +161,7 @@ ADD_SUBDIRECTORY( tools ) #ADD_SUBDIRECTORY( examples ) ADD_SUBDIRECTORY( po EXCLUDE_FROM_ALL ) ADD_SUBDIRECTORY( doc ) +ADD_SUBDIRECTORY( vendor ) ADD_SUBDIRECTORY( tests EXCLUDE_FROM_ALL ) INCLUDE(CTest) diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt new file mode 100644 index 0000000..46dd22c --- /dev/null +++ b/vendor/CMakeLists.txt @@ -0,0 +1,2 @@ +ADD_SUBDIRECTORY(shttpd) + diff --git a/vendor/shttpd/CMakeLists.txt b/vendor/shttpd/CMakeLists.txt new file mode 100644 index 0000000..6c6c152 --- /dev/null +++ b/vendor/shttpd/CMakeLists.txt @@ -0,0 +1,29 @@ +SET(shttp_SOURCES + auth.c + cgi.c + compat_unix.c + config.h + defs.h + io_cgi.c + io_dir.c + io_emb.c + io_file.c + io.h + io_socket.c + io_ssi.c + io_ssl.c + llist.h + log.c + md5.c + md5.h + shttpd.c + shttpd.h + ssl.h + standalone.c + std_includes.h + string.c +) + +ADD_LIBRARY(shttp ${shttp_SOURCES}) +TARGET_LINK_LIBRARIES(shttp dl pthread) + diff --git a/vendor/shttpd/auth.c b/vendor/shttpd/auth.c new file mode 100644 index 0000000..85d1b4d --- /dev/null +++ b/vendor/shttpd/auth.c @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +#if !defined(NO_AUTH) +/* + * Stringify binary data. Output buffer must be twice as big as input, + * because each byte takes 2 bytes in string representation + */ +static void +bin2str(char *to, const unsigned char *p, size_t len) +{ + const char *hex = "0123456789abcdef"; + + for (;len--; p++) { + *to++ = hex[p[0] >> 4]; + *to++ = hex[p[0] & 0x0f]; + } +} + +/* + * Return stringified MD5 hash for list of vectors. + * buf must point to at least 32-bytes long buffer + */ +static void +md5(char *buf, ...) +{ + unsigned char hash[16]; + const struct vec *v; + va_list ap; + MD5_CTX ctx; + int i; + + MD5Init(&ctx); + + va_start(ap, buf); + for (i = 0; (v = va_arg(ap, const struct vec *)) != NULL; i++) { + assert(v->len >= 0); + if (v->len == 0) + continue; + if (i > 0) + MD5Update(&ctx, (unsigned char *) ":", 1); + MD5Update(&ctx,(unsigned char *)v->ptr,(unsigned int)v->len); + } + va_end(ap); + + MD5Final(hash, &ctx); + bin2str(buf, hash, sizeof(hash)); +} + +/* + * Compare to vectors. Return 1 if they are equal + */ +static int +vcmp(const struct vec *v1, const struct vec *v2) +{ + return (v1->len == v2->len && !memcmp(v1->ptr, v2->ptr, v1->len)); +} + +struct digest { + struct vec user; + struct vec uri; + struct vec nonce; + struct vec cnonce; + struct vec resp; + struct vec qop; + struct vec nc; +}; + +static const struct auth_keyword { + size_t offset; + struct vec vec; +} known_auth_keywords[] = { + {offsetof(struct digest, user), {"username=", 9}}, + {offsetof(struct digest, cnonce), {"cnonce=", 7}}, + {offsetof(struct digest, resp), {"response=", 9}}, + {offsetof(struct digest, uri), {"uri=", 4}}, + {offsetof(struct digest, qop), {"qop=", 4}}, + {offsetof(struct digest, nc), {"nc=", 3}}, + {offsetof(struct digest, nonce), {"nonce=", 6}}, + {0, {NULL, 0}} +}; + +static void +parse_authorization_header(const struct vec *h, struct digest *dig) +{ + const unsigned char *p, *e, *s; + struct vec *v, vec; + const struct auth_keyword *kw; + + (void) memset(dig, 0, sizeof(*dig)); + p = (unsigned char *) h->ptr + 7; + e = (unsigned char *) h->ptr + h->len; + + while (p < e) { + + /* Skip spaces */ + while (p < e && (*p == ' ' || *p == ',')) + p++; + + /* Skip to "=" */ + for (s = p; s < e && *s != '='; ) + s++; + s++; + + /* Is it known keyword ? */ + for (kw = known_auth_keywords; kw->vec.len > 0; kw++) + if (kw->vec.len <= s - p && + !memcmp(p, kw->vec.ptr, kw->vec.len)) + break; + + if (kw->vec.len == 0) + v = &vec; /* Dummy placeholder */ + else + v = (struct vec *) ((char *) dig + kw->offset); + + if (*s == '"') { + p = ++s; + while (p < e && *p != '"') + p++; + } else { + p = s; + while (p < e && *p != ' ' && *p != ',') + p++; + } + + v->ptr = (char *) s; + v->len = p - s; + + if (*p == '"') + p++; + + DBG(("auth field [%.*s]", v->len, v->ptr)); + } +} + +/* + * Check the user's password, return 1 if OK + */ +static int +check_password(int method, const struct vec *ha1, const struct digest *digest) +{ + char a2[32], resp[32]; + struct vec vec_a2; + + /* XXX Due to a bug in MSIE, we do not compare the URI */ + /* Also, we do not check for authentication timeout */ + if (/*strcmp(dig->uri, c->ouri) != 0 || */ + digest->resp.len != 32 /*|| + now - strtoul(dig->nonce, NULL, 10) > 3600 */) + return (0); + + md5(a2, &_shttpd_known_http_methods[method], &digest->uri, NULL); + vec_a2.ptr = a2; + vec_a2.len = sizeof(a2); + md5(resp, ha1, &digest->nonce, &digest->nc, + &digest->cnonce, &digest->qop, &vec_a2, NULL); + DBG(("%s: uri [%.*s] expected_resp [%.*s] resp [%.*s]", + "check_password", digest->uri.len, digest->uri.ptr, + 32, resp, digest->resp.len, digest->resp.ptr)); + + return (!memcmp(resp, digest->resp.ptr, 32)); +} + +static FILE * +open_auth_file(struct shttpd_ctx *ctx, const char *path) +{ + char name[FILENAME_MAX]; + const char *p, *e; + FILE *fp = NULL; + int fd; + + if (ctx->options[OPT_AUTH_GPASSWD] != NULL) { + /* Use global passwords file */ + _shttpd_snprintf(name, sizeof(name), "%s", + ctx->options[OPT_AUTH_GPASSWD]); + } else { + /* + * Try to find .htpasswd in requested directory. + * Given the path, create the path to .htpasswd file + * in the same directory. Find the right-most + * directory separator character first. That would be the + * directory name. If directory separator character is not + * found, 'e' will point to 'p'. + */ + for (p = path, e = p + strlen(p) - 1; e > p; e--) + if (IS_DIRSEP_CHAR(*e)) + break; + + /* + * Make up the path by concatenating directory name and + * .htpasswd file name. + */ + (void) _shttpd_snprintf(name, sizeof(name), "%.*s/%s", + (int) (e - p), p, HTPASSWD); + } + + if ((fd = _shttpd_open(name, O_RDONLY, 0)) == -1) { + DBG(("open_auth_file: open(%s)", name)); + } else if ((fp = fdopen(fd, "r")) == NULL) { + DBG(("open_auth_file: fdopen(%s)", name)); + (void) close(fd); + } + + return (fp); +} + +/* + * Parse the line from htpasswd file. Line should be in form of + * "user:domain:ha1". Fill in the vector values. Return 1 if successful. + */ +static int +parse_htpasswd_line(const char *s, struct vec *user, + struct vec *domain, struct vec *ha1) +{ + user->len = domain->len = ha1->len = 0; + + for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++); + if (*s++ != ':') + return (0); + + for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++); + if (*s++ != ':') + return (0); + + for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s); + s++, ha1->len++); + + DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr, + domain->len, domain->ptr, ha1->len, ha1->ptr)); + + return (user->len > 0 && domain->len > 0 && ha1->len > 0); +} + +/* + * Authorize against the opened passwords file. Return 1 if authorized. + */ +static int +authorize(struct conn *c, FILE *fp) +{ + struct vec *auth_vec = &c->ch.auth.v_vec; + struct vec *user_vec = &c->ch.user.v_vec; + struct vec user, domain, ha1; + struct digest digest; + int ok = 0; + char line[256]; + + if (auth_vec->len > 20 && + !_shttpd_strncasecmp(auth_vec->ptr, "Digest ", 7)) { + + parse_authorization_header(auth_vec, &digest); + *user_vec = digest.user; + + while (fgets(line, sizeof(line), fp) != NULL) { + + if (!parse_htpasswd_line(line, &user, &domain, &ha1)) + continue; + + DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr, + domain.len, domain.ptr, ha1.len, ha1.ptr)); + + if (vcmp(user_vec, &user) && + !memcmp(c->ctx->options[OPT_AUTH_REALM], + domain.ptr, domain.len)) { + ok = check_password(c->method, &ha1, &digest); + break; + } + } + } + + return (ok); +} + +int +_shttpd_check_authorization(struct conn *c, const char *path) +{ + FILE *fp = NULL; + int len, n, authorized = 1; + const char *p, *s = c->ctx->options[OPT_PROTECT]; + char protected_path[FILENAME_MAX]; + + FOR_EACH_WORD_IN_LIST(s, len) { + + if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s) + continue; + + if (!memcmp(c->uri, s, p - s)) { + + n = s + len - p; + if (n > (int) sizeof(protected_path) - 1) + n = sizeof(protected_path) - 1; + + _shttpd_strlcpy(protected_path, p + 1, n); + + if ((fp = fopen(protected_path, "r")) == NULL) + _shttpd_elog(E_LOG, c, + "check_auth: cannot open %s: %s", + protected_path, strerror(errno)); + break; + } + } + + if (fp == NULL) + fp = open_auth_file(c->ctx, path); + + if (fp != NULL) { + authorized = authorize(c, fp); + (void) fclose(fp); + } + + return (authorized); +} + +int +_shttpd_is_authorized_for_put(struct conn *c) +{ + FILE *fp; + int ret = 0; + + if ((fp = fopen(c->ctx->options[OPT_AUTH_PUT], "r")) != NULL) { + ret = authorize(c, fp); + (void) fclose(fp); + } + + return (ret); +} + +void +_shttpd_send_authorization_request(struct conn *c) +{ + char buf[512]; + + (void) _shttpd_snprintf(buf, sizeof(buf), "Unauthorized\r\n" + "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", " + "nonce=\"%lu\"", c->ctx->options[OPT_AUTH_REALM], + (unsigned long) _shttpd_current_time); + + _shttpd_send_server_error(c, 401, buf); +} + +/* + * Edit the passwords file. + */ +int +_shttpd_edit_passwords(const char *fname, const char *domain, + const char *user, const char *pass) +{ + int ret = EXIT_SUCCESS, found = 0; + struct vec u, d, p; + char line[512], tmp[FILENAME_MAX], ha1[32]; + FILE *fp = NULL, *fp2 = NULL; + + (void) _shttpd_snprintf(tmp, sizeof(tmp), "%s.tmp", fname); + + /* Create the file if does not exist */ + if ((fp = fopen(fname, "a+"))) + (void) fclose(fp); + + /* Open the given file and temporary file */ + if ((fp = fopen(fname, "r")) == NULL) + _shttpd_elog(E_FATAL, NULL, + "Cannot open %s: %s", fname, strerror(errno)); + else if ((fp2 = fopen(tmp, "w+")) == NULL) + _shttpd_elog(E_FATAL, NULL, + "Cannot open %s: %s", tmp, strerror(errno)); + + p.ptr = pass; + p.len = strlen(pass); + + /* Copy the stuff to temporary file */ + while (fgets(line, sizeof(line), fp) != NULL) { + u.ptr = line; + if ((d.ptr = strchr(line, ':')) == NULL) + continue; + u.len = d.ptr - u.ptr; + d.ptr++; + if (strchr(d.ptr, ':') == NULL) + continue; + d.len = strchr(d.ptr, ':') - d.ptr; + + if ((int) strlen(user) == u.len && + !memcmp(user, u.ptr, u.len) && + (int) strlen(domain) == d.len && + !memcmp(domain, d.ptr, d.len)) { + found++; + md5(ha1, &u, &d, &p, NULL); + (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1); + } else { + (void) fprintf(fp2, "%s", line); + } + } + + /* If new user, just add it */ + if (found == 0) { + u.ptr = user; u.len = strlen(user); + d.ptr = domain; d.len = strlen(domain); + md5(ha1, &u, &d, &p, NULL); + (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1); + } + + /* Close files */ + (void) fclose(fp); + (void) fclose(fp2); + + /* Put the temp file in place of real file */ + (void) _shttpd_remove(fname); + (void) _shttpd_rename(tmp, fname); + + return (ret); +} +#endif /* NO_AUTH */ diff --git a/vendor/shttpd/cgi.c b/vendor/shttpd/cgi.c new file mode 100644 index 0000000..1bdfbad --- /dev/null +++ b/vendor/shttpd/cgi.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +#if !defined(NO_CGI) +struct env_block { + char buf[ENV_MAX]; /* Environment buffer */ + int len; /* Space taken */ + char *vars[CGI_ENV_VARS]; /* Point into the buffer */ + int nvars; /* Number of variables */ +}; + +static void +addenv(struct env_block *block, const char *fmt, ...) +{ + int n, space; + va_list ap; + + space = sizeof(block->buf) - block->len - 2; + assert(space >= 0); + + va_start(ap, fmt); + n = vsnprintf(block->buf + block->len, space, fmt, ap); + va_end(ap); + + if (n > 0 && n < space && block->nvars < CGI_ENV_VARS - 2) { + block->vars[block->nvars++] = block->buf + block->len; + block->len += n + 1; /* Include \0 terminator */ + } +} + +static void +add_http_headers_to_env(struct env_block *b, const char *s, int len) +{ + const char *p, *v, *e = s + len; + int space, n, i, ch; + + /* Loop through all headers in the request */ + while (s < e) { + + /* Find where this header ends. Remember where value starts */ + for (p = s, v = NULL; p < e && *p != '\n'; p++) + if (v == NULL && *p == ':') + v = p; + + /* 2 null terminators and "HTTP_" */ + space = (sizeof(b->buf) - b->len) - (2 + 5); + assert(space >= 0); + + /* Copy header if enough space in the environment block */ + if (v > s && p > v + 2 && space > p - s) { + + /* Store var */ + if (b->nvars < (int) NELEMS(b->vars) - 1) + b->vars[b->nvars++] = b->buf + b->len; + + (void) memcpy(b->buf + b->len, "HTTP_", 5); + b->len += 5; + + /* Copy header name. Substitute '-' to '_' */ + n = v - s; + for (i = 0; i < n; i++) { + ch = s[i] == '-' ? '_' : s[i]; + b->buf[b->len++] = toupper(ch); + } + + b->buf[b->len++] = '='; + + /* Copy header value */ + v += 2; + n = p[-1] == '\r' ? (p - v) - 1 : p - v; + for (i = 0; i < n; i++) + b->buf[b->len++] = v[i]; + + /* Null-terminate */ + b->buf[b->len++] = '\0'; + } + + s = p + 1; /* Shift to the next header */ + } +} + +static void +prepare_environment(const struct conn *c, const char *prog, + struct env_block *blk) +{ + const struct headers *h = &c->ch; + const char *s, *fname, *root = c->ctx->options[OPT_ROOT]; + size_t len; + + blk->len = blk->nvars = 0; + + /* SCRIPT_FILENAME */ + fname = prog; + if ((s = strrchr(prog, '/'))) + fname = s + 1; + + /* Prepare the environment block */ + addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + addenv(blk, "%s", "REDIRECT_STATUS=200"); /* PHP */ + addenv(blk, "SERVER_PORT=%d", c->loc_port); + addenv(blk, "SERVER_NAME=%s", c->ctx->options[OPT_AUTH_REALM]); + addenv(blk, "SERVER_ROOT=%s", root); + addenv(blk, "DOCUMENT_ROOT=%s", root); + addenv(blk, "REQUEST_METHOD=%s", + _shttpd_known_http_methods[c->method].ptr); + addenv(blk, "REMOTE_ADDR=%s", inet_ntoa(c->sa.u.sin.sin_addr)); + addenv(blk, "REMOTE_PORT=%hu", ntohs(c->sa.u.sin.sin_port)); + addenv(blk, "REQUEST_URI=%s", c->uri); + addenv(blk, "SCRIPT_NAME=%s", prog + strlen(root)); + addenv(blk, "SCRIPT_FILENAME=%s", fname); /* PHP */ + addenv(blk, "PATH_TRANSLATED=%s", prog); + + if (h->ct.v_vec.len > 0) + addenv(blk, "CONTENT_TYPE=%.*s", + h->ct.v_vec.len, h->ct.v_vec.ptr); + + if (c->query != NULL) + addenv(blk, "QUERY_STRING=%s", c->query); + + if (c->path_info != NULL) + addenv(blk, "PATH_INFO=/%s", c->path_info); + + if (h->cl.v_big_int > 0) + addenv(blk, "CONTENT_LENGTH=%lu", h->cl.v_big_int); + + if ((s = getenv("PATH")) != NULL) + addenv(blk, "PATH=%s", s); + +#ifdef _WIN32 + if ((s = getenv("COMSPEC")) != NULL) + addenv(blk, "COMSPEC=%s", s); + if ((s = getenv("SYSTEMROOT")) != NULL) + addenv(blk, "SYSTEMROOT=%s", s); +#else + if ((s = getenv("LD_LIBRARY_PATH")) != NULL) + addenv(blk, "LD_LIBRARY_PATH=%s", s); +#endif /* _WIN32 */ + + if ((s = getenv("PERLLIB")) != NULL) + addenv(blk, "PERLLIB=%s", s); + + if (h->user.v_vec.len > 0) { + addenv(blk, "REMOTE_USER=%.*s", + h->user.v_vec.len, h->user.v_vec.ptr); + addenv(blk, "%s", "AUTH_TYPE=Digest"); + } + + /* Add user-specified variables */ + s = c->ctx->options[OPT_CGI_ENVIRONMENT]; + FOR_EACH_WORD_IN_LIST(s, len) + addenv(blk, "%.*s", len, s); + + /* Add all headers as HTTP_* variables */ + add_http_headers_to_env(blk, c->headers, + c->rem.headers_len - (c->headers - c->request)); + + blk->vars[blk->nvars++] = NULL; + blk->buf[blk->len++] = '\0'; + + assert(blk->nvars < CGI_ENV_VARS); + assert(blk->len > 0); + assert(blk->len < (int) sizeof(blk->buf)); + + /* Debug stuff to view passed environment */ + DBG(("%s: %d vars, %d env size", prog, blk->nvars, blk->len)); + { + int i; + for (i = 0 ; i < blk->nvars; i++) + DBG(("[%s]", blk->vars[i] ? blk->vars[i] : "null")); + } +} + +int +_shttpd_run_cgi(struct conn *c, const char *prog) +{ + struct env_block blk; + char dir[FILENAME_MAX], *p; + int ret, pair[2]; + + prepare_environment(c, prog, &blk); + pair[0] = pair[1] = -1; + + /* CGI must be executed in its own directory */ + (void) _shttpd_snprintf(dir, sizeof(dir), "%s", prog); + for (p = dir + strlen(dir) - 1; p > dir; p--) + if (*p == '/') { + *p++ = '\0'; + break; + } + + if (shttpd_socketpair(pair) != 0) { + ret = -1; + } else if (_shttpd_spawn_process(c, + prog, blk.buf, blk.vars, pair[1], dir)) { + ret = -1; + (void) closesocket(pair[0]); + (void) closesocket(pair[1]); + } else { + ret = 0; + c->loc.chan.sock = pair[0]; + } + + return (ret); +} + +void +_shttpd_do_cgi(struct conn *c) +{ + DBG(("running CGI: [%s]", c->uri)); + assert(c->loc.io.size > CGI_REPLY_LEN); + memcpy(c->loc.io.buf, CGI_REPLY, CGI_REPLY_LEN); + c->loc.io.head = c->loc.io.tail = c->loc.io.total = CGI_REPLY_LEN; + c->loc.io_class = &_shttpd_io_cgi; + c->loc.flags = FLAG_R; + if (c->method == METHOD_POST) + c->loc.flags |= FLAG_W; +} + +#endif /* !NO_CGI */ diff --git a/vendor/shttpd/compat_rtems.c b/vendor/shttpd/compat_rtems.c new file mode 100644 index 0000000..2a01a07 --- /dev/null +++ b/vendor/shttpd/compat_rtems.c @@ -0,0 +1,198 @@ +/********************************************************************** + * + * rtems shttpd management + * + * FILE NAME : rtems_shttpd.c + * + * AUTHOR : Steven Johnson + * + * DESCRIPTION : Defines the interface functions to the shttp daemon + * + * REVISION : $Id: compat_rtems.c,v 1.2 2006/11/12 03:29:17 infidel Exp $ + * + * COMMENTS : + * + **********************************************************************/ + + /********************************************************************** + * INCLUDED MODULES + **********************************************************************/ +#include +#include "defs.h" + +#define MAX_WEB_BASE_PATH_LENGTH 256 +#define MIN_SHTTPD_STACK (8*1024) + +typedef struct RTEMS_HTTPD_ARGS { + rtems_shttpd_init init_callback; + rtems_shttpd_addpages addpages_callback; + char webroot[MAX_WEB_BASE_PATH_LENGTH]; +} RTEMS_HTTPD_ARGS; + +static int rtems_webserver_running = FALSE; //not running. + +static rtems_task rtems_httpd_daemon(rtems_task_argument args ) +{ + RTEMS_HTTPD_ARGS *httpd_args = (RTEMS_HTTPD_ARGS*)args; + + struct shttpd_ctx *ctx; + + if (httpd_args != NULL) + if (httpd_args->init_callback != NULL) + httpd_args->init_callback(); + +/************************************** + * Initialize the web server + */ + /* + * Initialize SHTTPD context. + * Set WWW root to current WEB_ROOT_PATH. + */ + ctx = shttpd_init(NULL, "document_root", httpd_args->webroot, NULL); + + if (httpd_args != NULL) + if (httpd_args->addpages_callback != NULL) + httpd_args->addpages_callback(ctx); + + /* Finished with args, so free them */ + if (httpd_args != NULL) + free(httpd_args); + + /* Open listening socket */ + shttpd_listen(ctx, 9000); + + rtems_webserver_running = TRUE; + + /* Serve connections infinitely until someone kills us */ + while (rtems_webserver_running) + shttpd_poll(ctx, 1000); + + /* Unreached, because we will be killed by a signal */ + shttpd_fini(ctx); + + rtems_task_delete( RTEMS_SELF ); +} + +rtems_status_code rtems_initialize_webserver(rtems_task_priority initial_priority, + rtems_unsigned32 stack_size, + rtems_mode initial_modes, + rtems_attribute attribute_set, + rtems_shttpd_init init_callback, + rtems_shttpd_addpages addpages_callback, + char *webroot + ) +{ + rtems_status_code sc; + rtems_id tid; + RTEMS_HTTPD_ARGS *args; + + if (stack_size < MIN_SHTTPD_STACK) + stack_size = MIN_SHTTPD_STACK; + + args = malloc(sizeof(RTEMS_HTTPD_ARGS)); + + if (args != NULL) + { + args->init_callback = init_callback; + args->addpages_callback = addpages_callback; + strncpy(args->webroot,webroot,MAX_WEB_BASE_PATH_LENGTH); + + sc = rtems_task_create(rtems_build_name('H', 'T', 'P', 'D'), + initial_priority, + stack_size, + initial_modes, + attribute_set, + &tid); + + if (sc == RTEMS_SUCCESSFUL) + { + sc = rtems_task_start(tid, rtems_httpd_daemon, (rtems_task_argument)args); + } + } + else + { + sc = RTEMS_NO_MEMORY; + } + + return sc; +} + +void rtems_terminate_webserver(void) +{ + rtems_webserver_running = FALSE; //not running, so terminate +} + +int rtems_webserver_ok(void) +{ + return rtems_webserver_running; +} + +void +set_close_on_exec(int fd) +{ + // RTEMS Does not have a functional "execve" + // so technically this call does not do anything, + // but it doesnt hurt either. + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +int +my_stat(const char *path, struct stat *stp) +{ + return (stat(path, stp)); +} + +int +my_open(const char *path, int flags, int mode) +{ + return (open(path, flags, mode)); +} + +int +my_remove(const char *path) +{ + return (remove(path)); +} + +int +my_rename(const char *path1, const char *path2) +{ + return (rename(path1, path2)); +} + +int +my_mkdir(const char *path, int mode) +{ + return (mkdir(path, mode)); +} + +char * +my_getcwd(char *buffer, int maxlen) +{ + return (getcwd(buffer, maxlen)); +} + +int +set_non_blocking_mode(int fd) +{ + int ret = -1; + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { + DBG(("nonblock: fcntl(F_GETFL): %d", ERRNO)); + } else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) { + DBG(("nonblock: fcntl(F_SETFL): %d", ERRNO)); + } else { + ret = 0; /* Success */ + } + + return (ret); +} + +#if !defined(NO_CGI) +int +spawn_process(struct conn *c, const char *prog, char *envblk, char **envp) +{ + return (-1); // RTEMS does not have subprocess support as standard. +} +#endif diff --git a/vendor/shttpd/compat_rtems.h b/vendor/shttpd/compat_rtems.h new file mode 100644 index 0000000..8d31e46 --- /dev/null +++ b/vendor/shttpd/compat_rtems.h @@ -0,0 +1,60 @@ +/** + * @file rtems/rtems-shttpd.h + */ + +#ifndef _rtems_rtems_webserver_h +#define _rtems_rtems_webserver_h + +#include "shttpd.h" + +#include +#include +#include +#include + +/* RTEMS is an Real Time Embedded operating system, for operation in hardware. + It does not have SSL or CGI support, as it does not have dynamic library + loading or sub-processes. */ +#define EMBEDDED +#define NO_SSL +#define NO_CGI + +#define DIRSEP '/' +#define O_BINARY 0 +#define ERRNO errno + +/* RTEMS version is Thread Safe */ +#define InitializeCriticalSection(x) rtems_semaphore_create( \ + rtems_build_name('H','T','P','X'), \ + 1, /* Not Held Yet.*/ \ + RTEMS_FIFO | \ + RTEMS_BINARY_SEMAPHORE, \ + 0, \ + x); +#define EnterCriticalSection(x) rtems_semaphore_obtain(*(x),RTEMS_WAIT,RTEMS_NO_TIMEOUT) +#define LeaveCriticalSection(x) rtems_semaphore_release(*(x)) + + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*rtems_shttpd_addpages)(struct shttpd_ctx *ctx); +typedef void (*rtems_shttpd_init)(void); + +rtems_status_code rtems_initialize_webserver(rtems_task_priority initial_priority, + rtems_unsigned32 stack_size, + rtems_mode initial_modes, + rtems_attribute attribute_set, + rtems_shttpd_init init_callback, + rtems_shttpd_addpages addpages_callback, + char *webroot + ); +void rtems_terminate_webserver(void); +int rtems_webserver_ok(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/vendor/shttpd/compat_unix.c b/vendor/shttpd/compat_unix.c new file mode 100644 index 0000000..e8fe929 --- /dev/null +++ b/vendor/shttpd/compat_unix.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +void +_shttpd_set_close_on_exec(int fd) +{ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +} + +int +_shttpd_stat(const char *path, struct stat *stp) +{ + return (stat(path, stp)); +} + +int +_shttpd_open(const char *path, int flags, int mode) +{ + return (open(path, flags, mode)); +} + +int +_shttpd_remove(const char *path) +{ + return (remove(path)); +} + +int +_shttpd_rename(const char *path1, const char *path2) +{ + return (rename(path1, path2)); +} + +int +_shttpd_mkdir(const char *path, int mode) +{ + return (mkdir(path, mode)); +} + +char * +_shttpd_getcwd(char *buffer, int maxlen) +{ + return (getcwd(buffer, maxlen)); +} + +int +_shttpd_set_non_blocking_mode(int fd) +{ + int ret = -1; + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { + DBG(("nonblock: fcntl(F_GETFL): %d", ERRNO)); + } else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) { + DBG(("nonblock: fcntl(F_SETFL): %d", ERRNO)); + } else { + ret = 0; /* Success */ + } + + return (ret); +} + +#ifndef NO_CGI +int +_shttpd_spawn_process(struct conn *c, const char *prog, char *envblk, + char *envp[], int sock, const char *dir) +{ + int ret; + pid_t pid; + const char *p, *interp = c->ctx->options[OPT_CGI_INTERPRETER]; + + envblk = NULL; /* unused */ + + if ((pid = vfork()) == -1) { + + ret = -1; + _shttpd_elog(E_LOG, c, "redirect: fork: %s", strerror(errno)); + + } else if (pid == 0) { + + /* Child */ + + (void) chdir(dir); + (void) dup2(sock, 0); + (void) dup2(sock, 1); + (void) closesocket(sock); + + /* If error file is specified, send errors there */ + if (c->ctx->error_log) + (void) dup2(fileno(c->ctx->error_log), 2); + + if ((p = strrchr(prog, '/')) != NULL) + p++; + else + p = prog; + + /* Execute CGI program */ + if (interp == NULL) { + (void) execle(p, p, NULL, envp); + _shttpd_elog(E_FATAL, c, "redirect: exec(%s)", prog); + } else { + (void) execle(interp, interp, p, NULL, envp); + _shttpd_elog(E_FATAL, c, "redirect: exec(%s %s)", + interp, prog); + } + + /* UNREACHED */ + exit(EXIT_FAILURE); + + } else { + + /* Parent */ + ret = 0; + (void) closesocket(sock); + } + + return (ret); +} +#endif /* !NO_CGI */ diff --git a/vendor/shttpd/compat_unix.h b/vendor/shttpd/compat_unix.h new file mode 100644 index 0000000..53c7f03 --- /dev/null +++ b/vendor/shttpd/compat_unix.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2004-2007 Sergey Lyubka + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if !defined(NO_THREADS) +#include "pthread.h" +#define _beginthread(a, b, c) do { pthread_t tid; \ + pthread_create(&tid, NULL, (void *(*)(void *))a, c); } while (0) +#endif /* !NO_THREADS */ + +#define SSL_LIB "libssl.so" +#define DIRSEP '/' +#define IS_DIRSEP_CHAR(c) ((c) == '/') +#define O_BINARY 0 +#define closesocket(a) close(a) +#define ERRNO errno diff --git a/vendor/shttpd/compat_win32.c b/vendor/shttpd/compat_win32.c new file mode 100644 index 0000000..d3b740a --- /dev/null +++ b/vendor/shttpd/compat_win32.c @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +static SERVICE_STATUS ss; +static SERVICE_STATUS_HANDLE hStatus; +static SERVICE_DESCRIPTION service_descr = {"Web server"}; + +static void +fix_directory_separators(char *path) +{ + for (; *path != '\0'; path++) { + if (*path == '/') + *path = '\\'; + if (*path == '\\') + while (path[1] == '\\' || path[1] == '/') + (void) memmove(path + 1, + path + 2, strlen(path + 2) + 1); + } +} + +static int +protect_against_code_disclosure(const wchar_t *path) +{ + WIN32_FIND_DATAW data; + HANDLE handle; + const wchar_t *p; + + /* + * Protect against CGI code disclosure under Windows. + * This is very nasty hole. Windows happily opens files with + * some garbage in the end of file name. So fopen("a.cgi ", "r") + * actually opens "a.cgi", and does not return an error! And since + * "a.cgi " does not have valid CGI extension, this leads to + * the CGI code disclosure. + * To protect, here we delete all fishy characters from the + * end of file name. + */ + + if ((handle = FindFirstFileW(path, &data)) == INVALID_HANDLE_VALUE) + return (FALSE); + + FindClose(handle); + + for (p = path + wcslen(path); p > path && p[-1] != L'\\';) + p--; + + if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + wcscmp(data.cFileName, p) != 0) + return (FALSE); + + return (TRUE); +} + +int +_shttpd_open(const char *path, int flags, int mode) +{ + char buf[FILENAME_MAX]; + wchar_t wbuf[FILENAME_MAX]; + + _shttpd_strlcpy(buf, path, sizeof(buf)); + fix_directory_separators(buf); + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); + + if (protect_against_code_disclosure(wbuf) == FALSE) + return (-1); + + return (_wopen(wbuf, flags)); +} + +int +_shttpd_stat(const char *path, struct stat *stp) +{ + char buf[FILENAME_MAX], *p; + wchar_t wbuf[FILENAME_MAX]; + + _shttpd_strlcpy(buf, path, sizeof(buf)); + fix_directory_separators(buf); + + p = buf + strlen(buf) - 1; + while (p > buf && *p == '\\' && p[-1] != ':') + *p-- = '\0'; + + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); + + return (_wstat(wbuf, (struct _stat *) stp)); +} + +int +_shttpd_remove(const char *path) +{ + char buf[FILENAME_MAX]; + wchar_t wbuf[FILENAME_MAX]; + + _shttpd_strlcpy(buf, path, sizeof(buf)); + fix_directory_separators(buf); + + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); + + return (_wremove(wbuf)); +} + +int +_shttpd_rename(const char *path1, const char *path2) +{ + char buf1[FILENAME_MAX]; + char buf2[FILENAME_MAX]; + wchar_t wbuf1[FILENAME_MAX]; + wchar_t wbuf2[FILENAME_MAX]; + + _shttpd_strlcpy(buf1, path1, sizeof(buf1)); + _shttpd_strlcpy(buf2, path2, sizeof(buf2)); + fix_directory_separators(buf1); + fix_directory_separators(buf2); + + MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1)); + MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2)); + + return (_wrename(wbuf1, wbuf2)); +} + +int +_shttpd_mkdir(const char *path, int mode) +{ + char buf[FILENAME_MAX]; + wchar_t wbuf[FILENAME_MAX]; + + _shttpd_strlcpy(buf, path, sizeof(buf)); + fix_directory_separators(buf); + + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); + + return (_wmkdir(wbuf)); +} + +static char * +wide_to_utf8(const wchar_t *str) +{ + char *buf = NULL; + if (str) { + int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + if (nchar > 0) { + buf = malloc(nchar); + if (!buf) + errno = ENOMEM; + else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) { + free(buf); + buf = NULL; + errno = EINVAL; + } + } else + errno = EINVAL; + } else + errno = EINVAL; + return buf; +} + +char * +_shttpd_getcwd(char *buffer, int maxlen) +{ + char *result = NULL; + wchar_t *wbuffer, *wresult; + + if (buffer) { + /* User-supplied buffer */ + wbuffer = malloc(maxlen * sizeof(wchar_t)); + if (wbuffer == NULL) + return NULL; + } else + /* Dynamically allocated buffer */ + wbuffer = NULL; + wresult = _wgetcwd(wbuffer, maxlen); + if (wresult) { + int err = errno; + if (buffer) { + /* User-supplied buffer */ + int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL); + if (n == 0) + err = ERANGE; + free(wbuffer); + result = buffer; + } else { + /* Buffer allocated by _wgetcwd() */ + result = wide_to_utf8(wresult); + err = errno; + free(wresult); + } + errno = err; + } + return result; +} + +DIR * +opendir(const char *name) +{ + DIR *dir = NULL; + char path[FILENAME_MAX]; + wchar_t wpath[FILENAME_MAX]; + + if (name == NULL || name[0] == '\0') { + errno = EINVAL; + } else if ((dir = malloc(sizeof(*dir))) == NULL) { + errno = ENOMEM; + } else { + _shttpd_snprintf(path, sizeof(path), "%s/*", name); + fix_directory_separators(path); + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath)); + dir->handle = FindFirstFileW(wpath, &dir->info); + + if (dir->handle != INVALID_HANDLE_VALUE) { + dir->result.d_name[0] = '\0'; + } else { + free(dir); + dir = NULL; + } + } + + return (dir); +} + +int +closedir(DIR *dir) +{ + int result = -1; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + + free(dir); + } + + if (result == -1) + errno = EBADF; + + return (result); +} + +struct dirent * +readdir(DIR *dir) +{ + struct dirent *result = 0; + + if (dir && dir->handle != INVALID_HANDLE_VALUE) { + if(!dir->result.d_name || + FindNextFileW(dir->handle, &dir->info)) { + result = &dir->result; + + WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, + -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + } + } else { + errno = EBADF; + } + + return (result); +} + +int +_shttpd_set_non_blocking_mode(int fd) +{ + unsigned long on = 1; + + return (ioctlsocket(fd, FIONBIO, &on)); +} + +void +_shttpd_set_close_on_exec(int fd) +{ + fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */ +} + +#if !defined(NO_CGI) + +struct threadparam { + SOCKET s; + HANDLE hPipe; + big_int_t content_len; +}; + + +enum ready_mode_t {IS_READY_FOR_READ, IS_READY_FOR_WRITE}; + +/* + * Wait until given socket is in ready state. Always return TRUE. + */ +static int +is_socket_ready(int sock, enum ready_mode_t mode) +{ + fd_set read_set, write_set; + + FD_ZERO(&read_set); + FD_ZERO(&write_set); + + if (mode == IS_READY_FOR_READ) + FD_SET(sock, &read_set); + else + FD_SET(sock, &write_set); + + select(sock + 1, &read_set, &write_set, NULL, NULL); + + return (TRUE); +} + +/* + * Thread function that reads POST data from the socket pair + * and writes it to the CGI process. + */ +static void//DWORD WINAPI +stdoutput(void *arg) +{ + struct threadparam *tp = arg; + int n, sent, stop = 0; + big_int_t total = 0; + DWORD k; + char buf[BUFSIZ]; + size_t max_recv; + + max_recv = min(sizeof(buf), tp->content_len - total); + while (!stop && + max_recv > 0 && + is_socket_ready(tp->s, IS_READY_FOR_READ) && + (n = recv(tp->s, buf, max_recv, 0)) > 0) { + if (n == -1 && ERRNO == EWOULDBLOCK) + continue; + for (sent = 0; !stop && sent < n; sent += k) + if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) + stop++; + total += n; + max_recv = min(sizeof(buf), tp->content_len - total); + } + + CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */ + free(tp); +} + +/* + * Thread function that reads CGI output and pushes it to the socket pair. + */ +static void +stdinput(void *arg) +{ + struct threadparam *tp = arg; + static int ntotal; + int k, stop = 0; + DWORD n, sent; + char buf[BUFSIZ]; + + while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) { + ntotal += n; + for (sent = 0; !stop && sent < n; sent += k) { + if (is_socket_ready(tp->s, IS_READY_FOR_WRITE) && + (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) { + if (k == -1 && ERRNO == EWOULDBLOCK) { + k = 0; + continue; + } + stop++; + } + } + } + CloseHandle(tp->hPipe); + + /* + * Windows is a piece of crap. When this thread closes its end + * of the socket pair, the other end (get_cgi() function) may loose + * some data. I presume, this happens if get_cgi() is not fast enough, + * and the data written by this end does not "push-ed" to the other + * end socket buffer. So after closesocket() the remaining data is + * gone. If I put shutdown() before closesocket(), that seems to + * fix the problem, but I am not sure this is the right fix. + * XXX (submitted by James Marshall) we do not do shutdown() on UNIX. + * If fork() is called from user callback, shutdown() messes up things. + */ + shutdown(tp->s, 2); + + closesocket(tp->s); + free(tp); + + _endthread(); +} + +static void +spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *), + big_int_t content_len) +{ + struct threadparam *tp; + DWORD tid; + + tp = malloc(sizeof(*tp)); + assert(tp != NULL); + + tp->s = sock; + tp->hPipe = hPipe; + tp->content_len = content_len; + _beginthread(func, 0, tp); +} + +int +_shttpd_spawn_process(struct conn *c, const char *prog, char *envblk, + char *envp[], int sock, const char *dir) +{ + HANDLE a[2], b[2], h[2], me; + DWORD flags; + char *p, *interp, cmdline[FILENAME_MAX], line[FILENAME_MAX]; + FILE *fp; + STARTUPINFOA si; + PROCESS_INFORMATION pi; + + me = GetCurrentProcess(); + flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; + + /* FIXME add error checking code here */ + CreatePipe(&a[0], &a[1], NULL, 0); + CreatePipe(&b[0], &b[1], NULL, 0); + DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags); + DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags); + + (void) memset(&si, 0, sizeof(si)); + (void) memset(&pi, 0, sizeof(pi)); + + /* XXX redirect CGI errors to the error log file */ + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdOutput = h[1]; + si.hStdInput = h[0]; + + /* If CGI file is a script, try to read the interpreter line */ + interp = c->ctx->options[OPT_CGI_INTERPRETER]; + if (interp == NULL) { + if ((fp = fopen(prog, "r")) != NULL) { + (void) fgets(line, sizeof(line), fp); + if (memcmp(line, "#!", 2) != 0) + line[2] = '\0'; + /* Trim whitespaces from interpreter name */ + for (p = &line[strlen(line) - 1]; p > line && + isspace(*p); p--) + *p = '\0'; + (void) fclose(fp); + } + interp = line + 2; + (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s%s%s", + line + 2, line[2] == '\0' ? "" : " ", prog); + } + + if ((p = strrchr(prog, '/')) != NULL) + prog = p + 1; + + (void) _shttpd_snprintf(cmdline, sizeof(cmdline), "%s %s", interp, prog); + + (void) _shttpd_snprintf(line, sizeof(line), "%s", dir); + fix_directory_separators(line); + fix_directory_separators(cmdline); + + /* + * Spawn reader & writer threads before we create CGI process. + * Otherwise CGI process may die too quickly, loosing the data + */ + spawn_stdio_thread(sock, b[0], stdinput, 0); + spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len); + + if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, + CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) { + _shttpd_elog(E_LOG, c, + "redirect: CreateProcess(%s): %d", cmdline, ERRNO); + return (-1); + } else { + CloseHandle(h[0]); + CloseHandle(h[1]); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + + return (0); +} + +#endif /* !NO_CGI */ + +#define ID_TRAYICON 100 +#define ID_QUIT 101 +static NOTIFYICONDATA ni; + +static LRESULT CALLBACK +WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + POINT pt; + HMENU hMenu; + + switch (msg) { + case WM_COMMAND: + switch (LOWORD(wParam)) { + case ID_QUIT: + exit(EXIT_SUCCESS); + break; + } + break; + case WM_USER: + switch (lParam) { + case WM_RBUTTONUP: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD"); + GetCursorPos(&pt); + TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL); + DestroyMenu(hMenu); + break; + } + break; + } + + return (DefWindowProc(hWnd, msg, wParam, lParam)); +} + +static void +systray(void *arg) +{ + WNDCLASS cls; + HWND hWnd; + MSG msg; + + (void) memset(&cls, 0, sizeof(cls)); + + cls.lpfnWndProc = (WNDPROC) WindowProc; + cls.hIcon = LoadIcon(NULL, IDI_APPLICATION); + cls.lpszClassName = "shttpd v." VERSION; + + if (!RegisterClass(&cls)) + _shttpd_elog(E_FATAL, NULL, "RegisterClass: %d", ERRNO); + else if ((hWnd = CreateWindow(cls.lpszClassName, "", + WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, NULL, NULL, NULL, arg)) == NULL) + _shttpd_elog(E_FATAL, NULL, "CreateWindow: %d", ERRNO); + ShowWindow(hWnd, SW_HIDE); + + ni.cbSize = sizeof(ni); + ni.uID = ID_TRAYICON; + ni.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + ni.hIcon = LoadIcon(NULL, IDI_APPLICATION); + ni.hWnd = hWnd; + _shttpd_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server"); + ni.uCallbackMessage = WM_USER; + Shell_NotifyIcon(NIM_ADD, &ni); + + while (GetMessage(&msg, hWnd, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +int +_shttpd_set_systray(struct shttpd_ctx *ctx, const char *opt) +{ + HWND hWnd; + char title[512]; + static WNDPROC oldproc; + + if (!_shttpd_is_true(opt)) + return (TRUE); + + FreeConsole(); + GetConsoleTitle(title, sizeof(title)); + hWnd = FindWindow(NULL, title); + ShowWindow(hWnd, SW_HIDE); + _beginthread(systray, 0, hWnd); + + return (TRUE); +} + +int +_shttpd_set_nt_service(struct shttpd_ctx *ctx, const char *action) +{ + SC_HANDLE hSCM, hService; + char path[FILENAME_MAX], key[128]; + HKEY hKey; + DWORD dwData; + + + if (!strcmp(action, "install")) { + if ((hSCM = OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS)) == NULL) + _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO); + + GetModuleFileName(NULL, path, sizeof(path)); + + hService = CreateService(hSCM, SERVICE_NAME, SERVICE_NAME, + SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path, + NULL, NULL, NULL, NULL, NULL); + + if (!hService) + _shttpd_elog(E_FATAL, NULL, + "Error installing service (%d)", ERRNO); + + ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, + &service_descr); + _shttpd_elog(E_FATAL, NULL, "Service successfully installed"); + + + } else if (!strcmp(action, "uninstall")) { + + if ((hSCM = OpenSCManager(NULL, NULL, + SC_MANAGER_ALL_ACCESS)) == NULL) { + _shttpd_elog(E_FATAL, NULL, "Error opening SCM (%d)", ERRNO); + } else if ((hService = OpenService(hSCM, + SERVICE_NAME, DELETE)) == NULL) { + _shttpd_elog(E_FATAL, NULL, + "Error opening service (%d)", ERRNO); + } else if (!DeleteService(hService)) { + _shttpd_elog(E_FATAL, NULL, + "Error deleting service (%d)", ERRNO); + } else { + _shttpd_elog(E_FATAL, NULL, "Service deleted"); + } + + } else { + _shttpd_elog(E_FATAL, NULL, "Use -service "); + } + + /* NOTREACHED */ + return (TRUE); +} + +static void WINAPI +ControlHandler(DWORD code) +{ + if (code == SERVICE_CONTROL_STOP || code == SERVICE_CONTROL_SHUTDOWN) { + ss.dwWin32ExitCode = 0; + ss.dwCurrentState = SERVICE_STOPPED; + } + + SetServiceStatus(hStatus, &ss); +} + +static void WINAPI +ServiceMain(int argc, char *argv[]) +{ + char path[MAX_PATH], *p, *av[] = {"shttpd_service", path, NULL}; + struct shttpd_ctx *ctx; + + ss.dwServiceType = SERVICE_WIN32; + ss.dwCurrentState = SERVICE_RUNNING; + ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + + hStatus = RegisterServiceCtrlHandler(SERVICE_NAME, ControlHandler); + SetServiceStatus(hStatus, &ss); + + GetModuleFileName(NULL, path, sizeof(path)); + + if ((p = strrchr(path, DIRSEP)) != NULL) + *++p = '\0'; + + strcat(path, CONFIG_FILE); /* woo ! */ + + ctx = shttpd_init(NELEMS(av) - 1, av); + if ((ctx = shttpd_init(NELEMS(av) - 1, av)) == NULL) + _shttpd_elog(E_FATAL, NULL, "Cannot initialize SHTTP context"); + + while (ss.dwCurrentState == SERVICE_RUNNING) + shttpd_poll(ctx, INT_MAX); + shttpd_fini(ctx); + + ss.dwCurrentState = SERVICE_STOPPED; + ss.dwWin32ExitCode = -1; + SetServiceStatus(hStatus, &ss); +} + +void +try_to_run_as_nt_service(void) +{ + static SERVICE_TABLE_ENTRY service_table[] = { + {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, + {NULL, NULL} + }; + + if (StartServiceCtrlDispatcher(service_table)) + exit(EXIT_SUCCESS); +} diff --git a/vendor/shttpd/compat_win32.h b/vendor/shttpd/compat_win32.h new file mode 100644 index 0000000..99ba99d --- /dev/null +++ b/vendor/shttpd/compat_win32.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2004-2007 Sergey Lyubka + * 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. + */ + +/* Tip from Justin Maximilian, suppress errors from winsock2.h */ +#define _WINSOCKAPI_ + +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32_WCE + +#include +#include +#include + +#else /* _WIN32_WCE */ + +/* Windows CE-specific definitions */ +#define NO_CGI /* WinCE has no pipes */ +#define NO_GUI /* temporarily until it is fixed */ +#pragma comment(lib,"ws2") +/* WinCE has both Unicode and ANSI versions of GetProcAddress */ +#undef GetProcAddress +#define GetProcAddress GetProcAddressA +#include "compat_wince.h" + +#endif /* _WIN32_WCE */ + +#define ERRNO GetLastError() +#define NO_SOCKLEN_T +#define SSL_LIB L"ssleay32.dll" +#define DIRSEP '\\' +#define IS_DIRSEP_CHAR(c) ((c) == '/' || (c) == '\\') +#define O_NONBLOCK 0 +#define EWOULDBLOCK WSAEWOULDBLOCK +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define mkdir(x,y) _mkdir(x) +#define popen(x,y) _popen(x, y) +#define pclose(x) _pclose(x) +#define dlopen(x,y) LoadLibraryW(x) +#define dlsym(x,y) (void *) GetProcAddress(x,y) +#define _POSIX_ + +#ifdef __LCC__ +#include +#endif /* __LCC__ */ + +#ifdef _MSC_VER /* MinGW already has these */ +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef __int64 uint64_t; +#define S_ISDIR(x) ((x) & _S_IFDIR) +#endif /* _MSC_VER */ + +/* + * POSIX dirent interface + */ +struct dirent { + char d_name[FILENAME_MAX]; +}; + +typedef struct DIR { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; + char *name; +} DIR; + +extern DIR *opendir(const char *name); +extern int closedir(DIR *dir); +extern struct dirent *readdir(DIR *dir); diff --git a/vendor/shttpd/compat_wince.c b/vendor/shttpd/compat_wince.c new file mode 100644 index 0000000..36702f6 --- /dev/null +++ b/vendor/shttpd/compat_wince.c @@ -0,0 +1,1593 @@ +/* + vi:ts=8:sw=8:noet + * Copyright (c) 2006 Luke Dunstan + * Partly based on code by David Kashtan, Validus Medical Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * This file provides various functions that are available on desktop Windows + * but not on Windows CE + */ + +#ifdef _MSC_VER +/* Level 4 warnings caused by windows.h */ +#pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int +#pragma warning(disable : 4115) // named type definition in parentheses +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#pragma warning(disable : 4244) // conversion from 'int ' to 'unsigned short ', possible loss of data +#pragma warning(disable : 4100) // unreferenced formal parameter +#endif + +#include +#include + +#include "compat_wince.h" + + +static WCHAR *to_wide_string(LPCSTR pStr) +{ + int nwide; + WCHAR *buf; + + if(pStr == NULL) + return NULL; + nwide = MultiByteToWideChar(CP_ACP, 0, pStr, -1, NULL, 0); + if(nwide == 0) + return NULL; + buf = malloc(nwide * sizeof(WCHAR)); + if(buf == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + if(MultiByteToWideChar(CP_ACP, 0, pStr, -1, buf, nwide) == 0) { + free(buf); + return NULL; + } + return buf; +} + +FILE *fdopen(int handle, const char *mode) +{ + WCHAR *wmode = to_wide_string(mode); + FILE *result; + + if(wmode != NULL) + result = _wfdopen((void *)handle, wmode); + else + result = NULL; + free(wmode); + return result; +} + +/* + * Time conversion constants + */ +#define FT_EPOCH (116444736000000000i64) +#define FT_TICKS (10000000i64) + + /* + * Convert a FILETIME to a time_t + */ +static time_t convert_FILETIME_to_time_t(FILETIME *File_Time) +{ + __int64 Temp; + + /* + * Convert the FILETIME structure to 100nSecs since 1601 (as a 64-bit value) + */ + Temp = (((__int64)File_Time->dwHighDateTime) << 32) + (__int64)File_Time->dwLowDateTime; + /* + * Convert to seconds from 1970 + */ + return((time_t)((Temp - FT_EPOCH) / FT_TICKS)); +} + +/* + * Convert a FILETIME to a tm structure + */ +static struct tm *Convert_FILETIME_To_tm(FILETIME *File_Time) +{ + SYSTEMTIME System_Time; + static struct tm tm = {0}; + static const short Day_Of_Year_By_Month[12] = {(short)(0), + (short)(31), + (short)(31 + 28), + (short)(31 + 28 + 31), + (short)(31 + 28 + 31 + 30), + (short)(31 + 28 + 31 + 30 + 31), + (short)(31 + 28 + 31 + 30 + 31 + 30), + (short)(31 + 28 + 31 + 30 + 31 + 30 + 31), + (short)(31 + 28 + 31 + 30 + 31 + 30 + 31 + 31), + (short)(31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30), + (short)(31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31), + (short)(31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30)}; + + + /* + * Turn the FILETIME into a SYSTEMTIME + */ + FileTimeToSystemTime(File_Time, &System_Time); + /* + * Use SYSTEMTIME to fill in the tm structure + */ + tm.tm_sec = System_Time.wSecond; + tm.tm_min = System_Time.wMinute; + tm.tm_hour = System_Time.wHour; + tm.tm_mday = System_Time.wDay; + tm.tm_mon = System_Time.wMonth - 1; + tm.tm_year = System_Time.wYear - 1900; + tm.tm_wday = System_Time.wDayOfWeek; + tm.tm_yday = Day_Of_Year_By_Month[tm.tm_mon] + tm.tm_mday - 1; + if (tm.tm_mon >= 2) { + /* + * Check for leap year (every 4 years but not every 100 years but every 400 years) + */ + if ((System_Time.wYear % 4) == 0) { + /* + * It Is a 4th year + */ + if ((System_Time.wYear % 100) == 0) { + /* + * It is a 100th year + */ + if ((System_Time.wYear % 400) == 0) { + /* + * It is a 400th year: It is a leap year + */ + tm.tm_yday++; + } + } else { + /* + * It is not a 100th year: It is a leap year + */ + tm.tm_yday++; + } + } + } + return(&tm); +} + +/* + * Convert a time_t to a FILETIME + */ +static void Convert_time_t_To_FILETIME(time_t Time, FILETIME *File_Time) +{ + __int64 Temp; + + /* + * Use 64-bit calculation to convert seconds since 1970 to + * 100nSecs since 1601 + */ + Temp = ((__int64)Time * FT_TICKS) + FT_EPOCH; + /* + * Put it into the FILETIME structure + */ + File_Time->dwLowDateTime = (DWORD)Temp; + File_Time->dwHighDateTime = (DWORD)(Temp >> 32); +} + +/* + * Convert a tm structure to a FILETIME + */ +static FILETIME *Convert_tm_To_FILETIME(struct tm *tm) +{ + SYSTEMTIME System_Time; + static FILETIME File_Time = {0}; + + /* + * Use the tm structure to fill in a SYSTEM + */ + System_Time.wYear = tm->tm_year + 1900; + System_Time.wMonth = tm->tm_mon + 1; + System_Time.wDayOfWeek = tm->tm_wday; + System_Time.wDay = tm->tm_mday; + System_Time.wHour = tm->tm_hour; + System_Time.wMinute = tm->tm_min; + System_Time.wSecond = tm->tm_sec; + System_Time.wMilliseconds = 0; + /* + * Convert it to a FILETIME and return it + */ + SystemTimeToFileTime(&System_Time, &File_Time); + return(&File_Time); +} + + +/************************************************************************/ +/* */ +/* Errno emulation: There is no errno on Windows/CE and we need */ +/* to make it per-thread. So we have a function */ +/* that returns a pointer to the errno for the */ +/* current thread. */ +/* */ +/* If there is ONLY the main thread then we can */ +/* quickly return some static storage. */ +/* */ +/* If we have multiple threads running, we use */ +/* Thread-Local Storage to hold the pointer */ +/* */ +/************************************************************************/ + +/* + * Function pointer for returning errno pointer + */ +static int *Initialize_Errno(void); +int *(*__WinCE_Errno_Pointer_Function)(void) = Initialize_Errno; + +/* + * Static errno storage for the main thread + */ +static int Errno_Storage = 0; + +/* + * Thread-Local storage slot for errno + */ +static int TLS_Errno_Slot = 0xffffffff; + +/* + * Number of threads we have running and critical section protection + * for manipulating it + */ +static int Number_Of_Threads = 0; +static CRITICAL_SECTION Number_Of_Threads_Critical_Section; + +/* + * For the main thread only -- return the errno pointer + */ +static int *Get_Main_Thread_Errno(void) +{ + return &Errno_Storage; +} + +/* + * When there is more than one thread -- return the errno pointer + */ +static int *Get_Thread_Errno(void) +{ + return (int *)TlsGetValue(TLS_Errno_Slot); +} + +/* + * Initialize a thread's errno + */ +static void Initialize_Thread_Errno(int *Errno_Pointer) +{ + /* + * Make sure we have a slot + */ + if (TLS_Errno_Slot == 0xffffffff) { + /* + * No: Get one + */ + TLS_Errno_Slot = (int)TlsAlloc(); + if (TLS_Errno_Slot == 0xffffffff) ExitProcess(3); + } + /* + * We can safely check for 0 threads, because + * only the main thread will be initializing + * at this point. Make sure the critical + * section that protects the number of threads + * is initialized + */ + if (Number_Of_Threads == 0) + InitializeCriticalSection(&Number_Of_Threads_Critical_Section); + /* + * Store the errno pointer + */ + if (TlsSetValue(TLS_Errno_Slot, (LPVOID)Errno_Pointer) == 0) ExitProcess(3); + /* + * Bump the number of threads + */ + EnterCriticalSection(&Number_Of_Threads_Critical_Section); + Number_Of_Threads++; + if (Number_Of_Threads > 1) { + /* + * We have threads other than the main thread: + * Use thread-local storage + */ + __WinCE_Errno_Pointer_Function = Get_Thread_Errno; + } + LeaveCriticalSection(&Number_Of_Threads_Critical_Section); +} + +/* + * Initialize errno emulation on Windows/CE (Main thread) + */ +static int *Initialize_Errno(void) +{ + /* + * Initialize the main thread's errno in thread-local storage + */ + Initialize_Thread_Errno(&Errno_Storage); + /* + * Set the errno function to be the one that returns the + * main thread's errno + */ + __WinCE_Errno_Pointer_Function = Get_Main_Thread_Errno; + /* + * Return the main thread's errno + */ + return &Errno_Storage; +} + +/* + * Initialize errno emulation on Windows/CE (New thread) + */ +void __WinCE_Errno_New_Thread(int *Errno_Pointer) +{ + Initialize_Thread_Errno(Errno_Pointer); +} + +/* + * Note that a thread has exited + */ +void __WinCE_Errno_Thread_Exit(void) +{ + /* + * Decrease the number of threads + */ + EnterCriticalSection(&Number_Of_Threads_Critical_Section); + Number_Of_Threads--; + if (Number_Of_Threads <= 1) { + /* + * We only have the main thread + */ + __WinCE_Errno_Pointer_Function = Get_Main_Thread_Errno; + } + LeaveCriticalSection(&Number_Of_Threads_Critical_Section); +} + + +char * +strerror(int errnum) +{ + return "(strerror not implemented)"; +} + +#define FT_EPOCH (116444736000000000i64) +#define FT_TICKS (10000000i64) + +int +_wstat(const WCHAR *path, struct _stat *buffer) +{ + WIN32_FIND_DATA data; + HANDLE handle; + WCHAR *p; + + /* Fail if wildcard characters are specified */ + if (wcscspn(path, L"?*") != wcslen(path)) + return -1; + + handle = FindFirstFile(path, &data); + if (handle == INVALID_HANDLE_VALUE) { + errno = GetLastError(); + return -1; + } + FindClose(handle); + + /* Found: Convert the file times */ + buffer->st_mtime = convert_FILETIME_to_time_t(&data.ftLastWriteTime); + if (data.ftLastAccessTime.dwLowDateTime || data.ftLastAccessTime.dwHighDateTime) + buffer->st_atime = convert_FILETIME_to_time_t(&data.ftLastAccessTime); + else + buffer->st_atime = buffer->st_mtime; + if (data.ftCreationTime.dwLowDateTime || data.ftCreationTime.dwHighDateTime) + buffer->st_ctime = convert_FILETIME_to_time_t(&data.ftCreationTime); + else + buffer->st_ctime = buffer->st_mtime; + + /* Convert the file modes */ + buffer->st_mode = (unsigned short)((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? (S_IFDIR | S_IEXEC) : S_IFREG); + buffer->st_mode |= (data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : (S_IREAD | S_IWRITE); + if((p = wcsrchr(path, L'.')) != NULL) { + p++; + if (_wcsicmp(p, L".exe") == 0) + buffer->st_mode |= S_IEXEC; + } + buffer->st_mode |= (buffer->st_mode & 0700) >> 3; + buffer->st_mode |= (buffer->st_mode & 0700) >> 6; + /* Set the other information */ + buffer->st_nlink = 1; + buffer->st_size = (unsigned long int)data.nFileSizeLow; + buffer->st_uid = 0; + buffer->st_gid = 0; + buffer->st_ino = 0 /*data.dwOID ?*/; + buffer->st_dev = 0; + + return 0; /* success */ +} + +/* + * Helper function for cemodule -- do an fstat() operation on a Win32 File Handle + */ +int +_fstat(int handle, struct _stat *st) +{ + BY_HANDLE_FILE_INFORMATION Data; + + /* + * Get the file information + */ + if (!GetFileInformationByHandle((HANDLE)handle, &Data)) { + /* + * Return error + */ + errno = GetLastError(); + return(-1); + } + /* + * Found: Convert the file times + */ + st->st_mtime=(time_t)((*(__int64*)&Data.ftLastWriteTime-FT_EPOCH)/FT_TICKS); + if(Data.ftLastAccessTime.dwLowDateTime || Data.ftLastAccessTime.dwHighDateTime) + st->st_atime=(time_t)((*(__int64*)&Data.ftLastAccessTime-FT_EPOCH)/FT_TICKS); + else + st->st_atime=st->st_mtime ; + if(Data.ftCreationTime.dwLowDateTime || Data.ftCreationTime.dwHighDateTime ) + st->st_ctime=(time_t)((*(__int64*)&Data.ftCreationTime-FT_EPOCH)/FT_TICKS); + else + st->st_ctime=st->st_mtime ; + /* + * Convert the file modes + */ + st->st_mode = (unsigned short)((Data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? (S_IFDIR | S_IEXEC) : S_IFREG); + st->st_mode |= (Data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : (S_IREAD | S_IWRITE); + st->st_mode |= (st->st_mode & 0700) >> 3; + st->st_mode |= (st->st_mode & 0700) >> 6; + /* + * Set the other information + */ + st->st_nlink=1; + st->st_size=(unsigned long int)Data.nFileSizeLow; + st->st_uid=0; + st->st_gid=0; + st->st_ino=0; + st->st_dev=0; + /* + * Return success + */ + return(0); +} + +int _wopen(const wchar_t *filename, int oflag, ...) +{ + DWORD Access, ShareMode, CreationDisposition; + HANDLE Handle; + static int Modes[4] = {0, (GENERIC_READ | GENERIC_WRITE), GENERIC_READ, GENERIC_WRITE}; + + /* + * Calculate the CreateFile arguments + */ + Access = Modes[oflag & O_MODE_MASK]; + ShareMode = (oflag & O_EXCL) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE); + if (oflag & O_TRUNC) + CreationDisposition = (oflag & O_CREAT) ? CREATE_ALWAYS : TRUNCATE_EXISTING; + else + CreationDisposition = (oflag & O_CREAT) ? CREATE_NEW : OPEN_EXISTING; + + Handle = CreateFileW(filename, Access, ShareMode, NULL, CreationDisposition, FILE_ATTRIBUTE_NORMAL, NULL); + + /* + * Deal with errors + */ + if (Handle == INVALID_HANDLE_VALUE) { + errno = GetLastError(); + if ((errno == ERROR_ALREADY_EXISTS) || (errno == ERROR_FILE_EXISTS)) + errno = ERROR_ALREADY_EXISTS; + return -1; + } + /* + * Return the handle + */ + return (int)Handle; +} + +int +_close(int handle) +{ + if(CloseHandle((HANDLE)handle)) + return 0; + errno = GetLastError(); + return -1; +} + +int +_write(int handle, const void *buffer, unsigned int count) +{ + DWORD numwritten = 0; + if(!WriteFile((HANDLE)handle, buffer, count, &numwritten, NULL)) { + errno = GetLastError(); + return -1; + } + return numwritten; +} + +int +_read(int handle, void *buffer, unsigned int count) +{ + DWORD numread = 0; + if(!ReadFile((HANDLE)handle, buffer, count, &numread, NULL)) { + errno = GetLastError(); + return -1; + } + return numread; +} + +long +_lseek(int handle, long offset, int origin) +{ + DWORD dwMoveMethod; + DWORD result; + + switch(origin) { + default: + errno = EINVAL; + return -1L; + case SEEK_SET: + dwMoveMethod = FILE_BEGIN; + break; + case SEEK_CUR: + dwMoveMethod = FILE_CURRENT; + break; + case SEEK_END: + dwMoveMethod = FILE_END; + break; + } + result = SetFilePointer((HANDLE)handle, offset, NULL, dwMoveMethod); + if(result == 0xFFFFFFFF) { + errno = GetLastError(); + return -1; + } + return (long)result; +} + +int +_wmkdir(const wchar_t *dirname) +{ + if(!CreateDirectoryW(dirname, NULL)) { + errno = GetLastError(); + return -1; + } + return 0; +} + +int +_wremove(const wchar_t *filename) +{ + if(!DeleteFileW(filename)) { + errno = GetLastError(); + return -1; + } + return 0; +} + +int +_wrename(const wchar_t *oldname, const wchar_t *newname) +{ + if(!MoveFileW(oldname, newname)) { + errno = GetLastError(); + return -1; + } + return 0; +} + +wchar_t * +_wgetcwd(wchar_t *buffer, int maxlen) +{ + wchar_t *result; + WCHAR wszPath[MAX_PATH + 1]; + WCHAR *p; + + if(!GetModuleFileNameW(NULL, wszPath, MAX_PATH + 1)) { + errno = GetLastError(); + return NULL; + } + /* Remove the filename part of the path to leave the directory */ + p = wcsrchr(wszPath, L'\\'); + if(p) + *p = L'\0'; + + if(buffer == NULL) + result = _wcsdup(wszPath); + else if(wcslen(wszPath) + 1 > (size_t)maxlen) { + result = NULL; + errno = ERROR_INSUFFICIENT_BUFFER; + } else { + wcsncpy(buffer, wszPath, maxlen); + buffer[maxlen - 1] = L'\0'; + result = buffer; + } + return result; +} + +/* + * The missing "C" runtime gmtime() function + */ +struct tm *gmtime(const time_t *TimeP) +{ + FILETIME File_Time; + + /* + * Deal with null time pointer + */ + if (!TimeP) return(NULL); + /* + * time_t -> FILETIME -> tm + */ + Convert_time_t_To_FILETIME(*TimeP, &File_Time); + return(Convert_FILETIME_To_tm(&File_Time)); +} + +/* + * The missing "C" runtime localtime() function + */ +struct tm *localtime(const time_t *TimeP) +{ + FILETIME File_Time, Local_File_Time; + + /* + * Deal with null time pointer + */ + if (!TimeP) return(NULL); + /* + * time_t -> FILETIME -> Local FILETIME -> tm + */ + Convert_time_t_To_FILETIME(*TimeP, &File_Time); + FileTimeToLocalFileTime(&File_Time, &Local_File_Time); + return(Convert_FILETIME_To_tm(&Local_File_Time)); +} + +/* + * The missing "C" runtime mktime() function + */ +time_t mktime(struct tm *tm) +{ + FILETIME *Local_File_Time; + FILETIME File_Time; + + /* + * tm -> Local FILETIME -> FILETIME -> time_t + */ + Local_File_Time = Convert_tm_To_FILETIME(tm); + LocalFileTimeToFileTime(Local_File_Time, &File_Time); + return(convert_FILETIME_to_time_t(&File_Time)); +} + +/* + * Missing "C" runtime time() function + */ +time_t time(time_t *TimeP) +{ + SYSTEMTIME System_Time; + FILETIME File_Time; + time_t Result; + + /* + * Get the current system time + */ + GetSystemTime(&System_Time); + /* + * SYSTEMTIME -> FILETIME -> time_t + */ + SystemTimeToFileTime(&System_Time, &File_Time); + Result = convert_FILETIME_to_time_t(&File_Time); + /* + * Return the time_t + */ + if (TimeP) *TimeP = Result; + return(Result); +} + +static char Standard_Name[32] = "GMT"; +static char Daylight_Name[32] = "GMT"; +char *tzname[2] = {Standard_Name, Daylight_Name}; +long timezone = 0; +int daylight = 0; + +void tzset(void) +{ + TIME_ZONE_INFORMATION Info; + int Result; + + /* + * Get our current timezone information + */ + Result = GetTimeZoneInformation(&Info); + switch(Result) { + /* + * We are on standard time + */ + case TIME_ZONE_ID_STANDARD: + daylight = 0; + break; + /* + * We are on daylight savings time + */ + case TIME_ZONE_ID_DAYLIGHT: + daylight = 1; + break; + /* + * We don't know the timezone information (leave it GMT) + */ + default: return; + } + /* + * Extract the timezone information + */ + timezone = Info.Bias * 60; + if (Info.StandardName[0]) + WideCharToMultiByte(CP_ACP, 0, Info.StandardName, -1, Standard_Name, sizeof(Standard_Name) - 1, NULL, NULL); + if (Info.DaylightName[0]) + WideCharToMultiByte(CP_ACP, 0, Info.DaylightName, -1, Daylight_Name, sizeof(Daylight_Name) - 1, NULL, NULL); +} + +/*** strftime() from newlib libc/time/strftime.c ***/ + +/* + * Sane snprintf(). Acts like snprintf(), but never return -1 or the + * value bigger than supplied buffer. + */ +static int +Snprintf(char *buf, size_t buflen, const char *fmt, ...) +{ + va_list ap; + int n; + + if (buflen == 0) + return (0); + + va_start(ap, fmt); + n = _vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + if (n < 0 || n > (int) buflen - 1) { + n = buflen - 1; + } + buf[n] = '\0'; + + return (n); +} + +#define snprintf Snprintf + +/* from libc/include/_ansi.h */ +#define _CONST const +#define _DEFUN(name, arglist, args) name(args) +#define _AND , +/* from libc/time/local.h */ +#define TZ_LOCK +#define TZ_UNLOCK +#define _tzname tzname +#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) +#define YEAR_BASE 1900 +#define SECSPERMIN 60L +#define MINSPERHOUR 60L +#define HOURSPERDAY 24L +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) + +/* + * strftime.c + * Original Author: G. Haley + * Additions from: Eric Blake + * + * Places characters into the array pointed to by s as controlled by the string + * pointed to by format. If the total number of resulting characters including + * the terminating null character is not more than maxsize, returns the number + * of characters placed into the array pointed to by s (not including the + * terminating null character); otherwise zero is returned and the contents of + * the array indeterminate. + */ + +/* +FUNCTION +<>---flexible calendar time formatter + +INDEX + strftime + +ANSI_SYNOPSIS + #include + size_t strftime(char *<[s]>, size_t <[maxsize]>, + const char *<[format]>, const struct tm *<[timp]>); + +TRAD_SYNOPSIS + #include + size_t strftime(<[s]>, <[maxsize]>, <[format]>, <[timp]>) + char *<[s]>; + size_t <[maxsize]>; + char *<[format]>; + struct tm *<[timp]>; + +DESCRIPTION +<> converts a <> representation of the time (at +<[timp]>) into a null-terminated string, starting at <[s]> and occupying +no more than <[maxsize]> characters. + +You control the format of the output using the string at <[format]>. +<<*<[format]>>> can contain two kinds of specifications: text to be +copied literally into the formatted string, and time conversion +specifications. Time conversion specifications are two- and +three-character sequences beginning with `<<%>>' (use `<<%%>>' to +include a percent sign in the output). Each defined conversion +specification selects only the specified field(s) of calendar time +data from <<*<[timp]>>>, and converts it to a string in one of the +following ways: + +o+ +o %a +A three-letter abbreviation for the day of the week. [tm_wday] + +o %A +The full name for the day of the week, one of `<>', +`<>', `<>', `<>', `<>', +`<>', or `<>'. [tm_wday] + +o %b +A three-letter abbreviation for the month name. [tm_mon] + +o %B +The full name of the month, one of `<>', `<>', +`<>', `<>', `<>', `<>', `<>', +`<>', `<>', `<>', `<>', +`<>'. [tm_mon] + +o %c +A string representing the complete date and time, in the form +`<<"%a %b %e %H:%M:%S %Y">>' (example "Mon Apr 01 13:13:13 +1992"). [tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday] + +o %C +The century, that is, the year divided by 100 then truncated. For +4-digit years, the result is zero-padded and exactly two characters; +but for other years, there may a negative sign or more digits. In +this way, `<<%C%y>>' is equivalent to `<<%Y>>'. [tm_year] + +o %d +The day of the month, formatted with two digits (from `<<01>>' to +`<<31>>'). [tm_mday] + +o %D +A string representing the date, in the form `<<"%m/%d/%y">>'. +[tm_mday, tm_mon, tm_year] + +o %e +The day of the month, formatted with leading space if single digit +(from `<<1>>' to `<<31>>'). [tm_mday] + +o %E<> +In some locales, the E modifier selects alternative representations of +certain modifiers <>. But in the "C" locale supported by newlib, +it is ignored, and treated as %<>. + +o %F +A string representing the ISO 8601:2000 date format, in the form +`<<"%Y-%m-%d">>'. [tm_mday, tm_mon, tm_year] + +o %g +The last two digits of the week-based year, see specifier %G (from +`<<00>>' to `<<99>>'). [tm_year, tm_wday, tm_yday] + +o %G +The week-based year. In the ISO 8601:2000 calendar, week 1 of the year +includes January 4th, and begin on Mondays. Therefore, if January 1st, +2nd, or 3rd falls on a Sunday, that day and earlier belong to the last +week of the previous year; and if December 29th, 30th, or 31st falls +on Monday, that day and later belong to week 1 of the next year. For +consistency with %Y, it always has at least four characters. +Example: "%G" for Saturday 2nd January 1999 gives "1998", and for +Tuesday 30th December 1997 gives "1998". [tm_year, tm_wday, tm_yday] + +o %h +A three-letter abbreviation for the month name (synonym for +"%b"). [tm_mon] + +o %H +The hour (on a 24-hour clock), formatted with two digits (from +`<<00>>' to `<<23>>'). [tm_hour] + +o %I +The hour (on a 12-hour clock), formatted with two digits (from +`<<01>>' to `<<12>>'). [tm_hour] + +o %j +The count of days in the year, formatted with three digits +(from `<<001>>' to `<<366>>'). [tm_yday] + +o %k +The hour (on a 24-hour clock), formatted with leading space if single +digit (from `<<0>>' to `<<23>>'). Non-POSIX extension. [tm_hour] + +o %l +The hour (on a 12-hour clock), formatted with leading space if single +digit (from `<<1>>' to `<<12>>'). Non-POSIX extension. [tm_hour] + +o %m +The month number, formatted with two digits (from `<<01>>' to `<<12>>'). +[tm_mon] + +o %M +The minute, formatted with two digits (from `<<00>>' to `<<59>>'). [tm_min] + +o %n +A newline character (`<<\n>>'). + +o %O<> +In some locales, the O modifier selects alternative digit characters +for certain modifiers <>. But in the "C" locale supported by newlib, it +is ignored, and treated as %<>. + +o %p +Either `<>' or `<>' as appropriate. [tm_hour] + +o %r +The 12-hour time, to the second. Equivalent to "%I:%M:%S %p". [tm_sec, +tm_min, tm_hour] + +o %R +The 24-hour time, to the minute. Equivalent to "%H:%M". [tm_min, tm_hour] + +o %S +The second, formatted with two digits (from `<<00>>' to `<<60>>'). The +value 60 accounts for the occasional leap second. [tm_sec] + +o %t +A tab character (`<<\t>>'). + +o %T +The 24-hour time, to the second. Equivalent to "%H:%M:%S". [tm_sec, +tm_min, tm_hour] + +o %u +The weekday as a number, 1-based from Monday (from `<<1>>' to +`<<7>>'). [tm_wday] + +o %U +The week number, where weeks start on Sunday, week 1 contains the first +Sunday in a year, and earlier days are in week 0. Formatted with two +digits (from `<<00>>' to `<<53>>'). See also <<%W>>. [tm_wday, tm_yday] + +o %V +The week number, where weeks start on Monday, week 1 contains January 4th, +and earlier days are in the previous year. Formatted with two digits +(from `<<01>>' to `<<53>>'). See also <<%G>>. [tm_year, tm_wday, tm_yday] + +o %w +The weekday as a number, 0-based from Sunday (from `<<0>>' to `<<6>>'). +[tm_wday] + +o %W +The week number, where weeks start on Monday, week 1 contains the first +Monday in a year, and earlier days are in week 0. Formatted with two +digits (from `<<00>>' to `<<53>>'). [tm_wday, tm_yday] + +o %x +A string representing the complete date, equivalent to "%m/%d/%y". +[tm_mon, tm_mday, tm_year] + +o %X +A string representing the full time of day (hours, minutes, and +seconds), equivalent to "%H:%M:%S". [tm_sec, tm_min, tm_hour] + +o %y +The last two digits of the year (from `<<00>>' to `<<99>>'). [tm_year] + +o %Y +The full year, equivalent to <<%C%y>>. It will always have at least four +characters, but may have more. The year is accurate even when tm_year +added to the offset of 1900 overflows an int. [tm_year] + +o %z +The offset from UTC. The format consists of a sign (negative is west of +Greewich), two characters for hour, then two characters for minutes +(-hhmm or +hhmm). If tm_isdst is negative, the offset is unknown and no +output is generated; if it is zero, the offset is the standard offset for +the current time zone; and if it is positive, the offset is the daylight +savings offset for the current timezone. The offset is determined from +the TZ environment variable, as if by calling tzset(). [tm_isdst] + +o %Z +The time zone name. If tm_isdst is negative, no output is generated. +Otherwise, the time zone name is based on the TZ environment variable, +as if by calling tzset(). [tm_isdst] + +o %% +A single character, `<<%>>'. +o- + +RETURNS +When the formatted time takes up no more than <[maxsize]> characters, +the result is the length of the formatted string. Otherwise, if the +formatting operation was abandoned due to lack of room, the result is +<<0>>, and the string starting at <[s]> corresponds to just those +parts of <<*<[format]>>> that could be completely filled in within the +<[maxsize]> limit. + +PORTABILITY +ANSI C requires <>, but does not specify the contents of +<<*<[s]>>> when the formatted string would require more than +<[maxsize]> characters. Unrecognized specifiers and fields of +<> that are out of range cause undefined results. Since some +formats expand to 0 bytes, it is wise to set <<*<[s]>>> to a nonzero +value beforehand to distinguish between failure and an empty string. +This implementation does not support <> being NULL, nor overlapping +<> and <>. + +<> requires no supporting OS subroutines. +*/ + +static _CONST int dname_len[7] = +{6, 6, 7, 9, 8, 6, 8}; + +static _CONST char *_CONST dname[7] = +{"Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + +static _CONST int mname_len[12] = +{7, 8, 5, 5, 3, 4, 4, 6, 9, 7, 8, 8}; + +static _CONST char *_CONST mname[12] = +{"January", "February", "March", "April", + "May", "June", "July", "August", "September", "October", "November", + "December"}; + +/* Using the tm_year, tm_wday, and tm_yday components of TIM_P, return + -1, 0, or 1 as the adjustment to add to the year for the ISO week + numbering used in "%g%G%V", avoiding overflow. */ +static int +_DEFUN (iso_year_adjust, (tim_p), + _CONST struct tm *tim_p) +{ + /* Account for fact that tm_year==0 is year 1900. */ + int leap = isleap (tim_p->tm_year + (YEAR_BASE + - (tim_p->tm_year < 0 ? 0 : 2000))); + + /* Pack the yday, wday, and leap year into a single int since there are so + many disparate cases. */ +#define PACK(yd, wd, lp) (((yd) << 4) + (wd << 1) + (lp)) + switch (PACK (tim_p->tm_yday, tim_p->tm_wday, leap)) + { + case PACK (0, 5, 0): /* Jan 1 is Fri, not leap. */ + case PACK (0, 6, 0): /* Jan 1 is Sat, not leap. */ + case PACK (0, 0, 0): /* Jan 1 is Sun, not leap. */ + case PACK (0, 5, 1): /* Jan 1 is Fri, leap year. */ + case PACK (0, 6, 1): /* Jan 1 is Sat, leap year. */ + case PACK (0, 0, 1): /* Jan 1 is Sun, leap year. */ + case PACK (1, 6, 0): /* Jan 2 is Sat, not leap. */ + case PACK (1, 0, 0): /* Jan 2 is Sun, not leap. */ + case PACK (1, 6, 1): /* Jan 2 is Sat, leap year. */ + case PACK (1, 0, 1): /* Jan 2 is Sun, leap year. */ + case PACK (2, 0, 0): /* Jan 3 is Sun, not leap. */ + case PACK (2, 0, 1): /* Jan 3 is Sun, leap year. */ + return -1; /* Belongs to last week of previous year. */ + case PACK (362, 1, 0): /* Dec 29 is Mon, not leap. */ + case PACK (363, 1, 1): /* Dec 29 is Mon, leap year. */ + case PACK (363, 1, 0): /* Dec 30 is Mon, not leap. */ + case PACK (363, 2, 0): /* Dec 30 is Tue, not leap. */ + case PACK (364, 1, 1): /* Dec 30 is Mon, leap year. */ + case PACK (364, 2, 1): /* Dec 30 is Tue, leap year. */ + case PACK (364, 1, 0): /* Dec 31 is Mon, not leap. */ + case PACK (364, 2, 0): /* Dec 31 is Tue, not leap. */ + case PACK (364, 3, 0): /* Dec 31 is Wed, not leap. */ + case PACK (365, 1, 1): /* Dec 31 is Mon, leap year. */ + case PACK (365, 2, 1): /* Dec 31 is Tue, leap year. */ + case PACK (365, 3, 1): /* Dec 31 is Wed, leap year. */ + return 1; /* Belongs to first week of next year. */ + } + return 0; /* Belongs to specified year. */ +#undef PACK +} + +size_t +_DEFUN (strftime, (s, maxsize, format, tim_p), + char *s _AND + size_t maxsize _AND + _CONST char *format _AND + _CONST struct tm *tim_p) +{ + size_t count = 0; + int i; + + for (;;) + { + while (*format && *format != '%') + { + if (count < maxsize - 1) + s[count++] = *format++; + else + return 0; + } + + if (*format == '\0') + break; + + format++; + if (*format == 'E' || *format == 'O') + format++; + + switch (*format) + { + case 'a': + for (i = 0; i < 3; i++) + { + if (count < maxsize - 1) + s[count++] = + dname[tim_p->tm_wday][i]; + else + return 0; + } + break; + case 'A': + for (i = 0; i < dname_len[tim_p->tm_wday]; i++) + { + if (count < maxsize - 1) + s[count++] = + dname[tim_p->tm_wday][i]; + else + return 0; + } + break; + case 'b': + case 'h': + for (i = 0; i < 3; i++) + { + if (count < maxsize - 1) + s[count++] = + mname[tim_p->tm_mon][i]; + else + return 0; + } + break; + case 'B': + for (i = 0; i < mname_len[tim_p->tm_mon]; i++) + { + if (count < maxsize - 1) + s[count++] = + mname[tim_p->tm_mon][i]; + else + return 0; + } + break; + case 'c': + { + /* Length is not known because of %C%y, so recurse. */ + size_t adjust = strftime (&s[count], maxsize - count, + "%a %b %e %H:%M:%S %C%y", tim_p); + if (adjust > 0) + count += adjust; + else + return 0; + } + break; + case 'C': + { + /* Examples of (tm_year + YEAR_BASE) that show how %Y == %C%y + with 32-bit int. + %Y %C %y + 2147485547 21474855 47 + 10000 100 00 + 9999 99 99 + 0999 09 99 + 0099 00 99 + 0001 00 01 + 0000 00 00 + -001 -0 01 + -099 -0 99 + -999 -9 99 + -1000 -10 00 + -10000 -100 00 + -2147481748 -21474817 48 + + Be careful of both overflow and sign adjustment due to the + asymmetric range of years. + */ + int neg = tim_p->tm_year < -YEAR_BASE; + int century = tim_p->tm_year >= 0 + ? tim_p->tm_year / 100 + YEAR_BASE / 100 + : abs (tim_p->tm_year + YEAR_BASE) / 100; + count += snprintf (&s[count], maxsize - count, "%s%.*d", + neg ? "-" : "", 2 - neg, century); + if (count >= maxsize) + return 0; + } + break; + case 'd': + case 'e': + if (count < maxsize - 2) + { + sprintf (&s[count], *format == 'd' ? "%.2d" : "%2d", + tim_p->tm_mday); + count += 2; + } + else + return 0; + break; + case 'D': + case 'x': + /* %m/%d/%y */ + if (count < maxsize - 8) + { + sprintf (&s[count], "%.2d/%.2d/%.2d", + tim_p->tm_mon + 1, tim_p->tm_mday, + tim_p->tm_year >= 0 ? tim_p->tm_year % 100 + : abs (tim_p->tm_year + YEAR_BASE) % 100); + count += 8; + } + else + return 0; + break; + case 'F': + { + /* Length is not known because of %C%y, so recurse. */ + size_t adjust = strftime (&s[count], maxsize - count, + "%C%y-%m-%d", tim_p); + if (adjust > 0) + count += adjust; + else + return 0; + } + break; + case 'g': + if (count < maxsize - 2) + { + /* Be careful of both overflow and negative years, thanks to + the asymmetric range of years. */ + int adjust = iso_year_adjust (tim_p); + int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 + : abs (tim_p->tm_year + YEAR_BASE) % 100; + if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE) + adjust = 1; + else if (adjust > 0 && tim_p->tm_year < -YEAR_BASE) + adjust = -1; + sprintf (&s[count], "%.2d", + ((year + adjust) % 100 + 100) % 100); + count += 2; + } + else + return 0; + break; + case 'G': + { + /* See the comments for 'C' and 'Y'; this is a variable length + field. Although there is no requirement for a minimum number + of digits, we use 4 for consistency with 'Y'. */ + int neg = tim_p->tm_year < -YEAR_BASE; + int adjust = iso_year_adjust (tim_p); + int century = tim_p->tm_year >= 0 + ? tim_p->tm_year / 100 + YEAR_BASE / 100 + : abs (tim_p->tm_year + YEAR_BASE) / 100; + int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 + : abs (tim_p->tm_year + YEAR_BASE) % 100; + if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE) + neg = adjust = 1; + else if (adjust > 0 && neg) + adjust = -1; + year += adjust; + if (year == -1) + { + year = 99; + --century; + } + else if (year == 100) + { + year = 0; + ++century; + } + count += snprintf (&s[count], maxsize - count, "%s%.*d%.2d", + neg ? "-" : "", 2 - neg, century, year); + if (count >= maxsize) + return 0; + } + break; + case 'H': + case 'k': + if (count < maxsize - 2) + { + sprintf (&s[count], *format == 'k' ? "%2d" : "%.2d", + tim_p->tm_hour); + count += 2; + } + else + return 0; + break; + case 'I': + case 'l': + if (count < maxsize - 2) + { + if (tim_p->tm_hour == 0 || + tim_p->tm_hour == 12) + { + s[count++] = '1'; + s[count++] = '2'; + } + else + { + sprintf (&s[count], *format == 'I' ? "%.2d" : "%2d", + tim_p->tm_hour % 12); + count += 2; + } + } + else + return 0; + break; + case 'j': + if (count < maxsize - 3) + { + sprintf (&s[count], "%.3d", + tim_p->tm_yday + 1); + count += 3; + } + else + return 0; + break; + case 'm': + if (count < maxsize - 2) + { + sprintf (&s[count], "%.2d", + tim_p->tm_mon + 1); + count += 2; + } + else + return 0; + break; + case 'M': + if (count < maxsize - 2) + { + sprintf (&s[count], "%.2d", + tim_p->tm_min); + count += 2; + } + else + return 0; + break; + case 'n': + if (count < maxsize - 1) + s[count++] = '\n'; + else + return 0; + break; + case 'p': + if (count < maxsize - 2) + { + if (tim_p->tm_hour < 12) + s[count++] = 'A'; + else + s[count++] = 'P'; + + s[count++] = 'M'; + } + else + return 0; + break; + case 'r': + if (count < maxsize - 11) + { + if (tim_p->tm_hour == 0 || + tim_p->tm_hour == 12) + { + s[count++] = '1'; + s[count++] = '2'; + } + else + { + sprintf (&s[count], "%.2d", tim_p->tm_hour % 12); + count += 2; + } + s[count++] = ':'; + sprintf (&s[count], "%.2d", + tim_p->tm_min); + count += 2; + s[count++] = ':'; + sprintf (&s[count], "%.2d", + tim_p->tm_sec); + count += 2; + s[count++] = ' '; + if (tim_p->tm_hour < 12) + s[count++] = 'A'; + else + s[count++] = 'P'; + + s[count++] = 'M'; + } + else + return 0; + break; + case 'R': + if (count < maxsize - 5) + { + sprintf (&s[count], "%.2d:%.2d", tim_p->tm_hour, tim_p->tm_min); + count += 5; + } + else + return 0; + break; + case 'S': + if (count < maxsize - 2) + { + sprintf (&s[count], "%.2d", + tim_p->tm_sec); + count += 2; + } + else + return 0; + break; + case 't': + if (count < maxsize - 1) + s[count++] = '\t'; + else + return 0; + break; + case 'T': + case 'X': + if (count < maxsize - 8) + { + sprintf (&s[count], "%.2d:%.2d:%.2d", tim_p->tm_hour, + tim_p->tm_min, tim_p->tm_sec); + count += 8; + } + else + return 0; + break; + case 'u': + if (count < maxsize - 1) + { + if (tim_p->tm_wday == 0) + s[count++] = '7'; + else + s[count++] = '0' + tim_p->tm_wday; + } + else + return 0; + break; + case 'U': + if (count < maxsize - 2) + { + sprintf (&s[count], "%.2d", + (tim_p->tm_yday + 7 - + tim_p->tm_wday) / 7); + count += 2; + } + else + return 0; + break; + case 'V': + if (count < maxsize - 2) + { + int adjust = iso_year_adjust (tim_p); + int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6; + int week = (tim_p->tm_yday + 10 - wday) / 7; + if (adjust > 0) + week = 1; + else if (adjust < 0) + /* Previous year has 53 weeks if current year starts on + Fri, and also if current year starts on Sat and + previous year was leap year. */ + week = 52 + (4 >= (wday - tim_p->tm_yday + - isleap (tim_p->tm_year + + (YEAR_BASE - 1 + - (tim_p->tm_year < 0 + ? 0 : 2000))))); + sprintf (&s[count], "%.2d", week); + count += 2; + } + else + return 0; + break; + case 'w': + if (count < maxsize - 1) + s[count++] = '0' + tim_p->tm_wday; + else + return 0; + break; + case 'W': + if (count < maxsize - 2) + { + int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6; + sprintf (&s[count], "%.2d", + (tim_p->tm_yday + 7 - wday) / 7); + count += 2; + } + else + return 0; + break; + case 'y': + if (count < maxsize - 2) + { + /* Be careful of both overflow and negative years, thanks to + the asymmetric range of years. */ + int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100 + : abs (tim_p->tm_year + YEAR_BASE) % 100; + sprintf (&s[count], "%.2d", year); + count += 2; + } + else + return 0; + break; + case 'Y': + { + /* Length is not known because of %C%y, so recurse. */ + size_t adjust = strftime (&s[count], maxsize - count, + "%C%y", tim_p); + if (adjust > 0) + count += adjust; + else + return 0; + } + break; + case 'z': +#ifndef _WIN32_WCE + if (tim_p->tm_isdst >= 0) + { + if (count < maxsize - 5) + { + long offset; + __tzinfo_type *tz = __gettzinfo (); + TZ_LOCK; + /* The sign of this is exactly opposite the envvar TZ. We + could directly use the global _timezone for tm_isdst==0, + but have to use __tzrule for daylight savings. */ + offset = -tz->__tzrule[tim_p->tm_isdst > 0].offset; + TZ_UNLOCK; + sprintf (&s[count], "%+03ld%.2ld", offset / SECSPERHOUR, + labs (offset / SECSPERMIN) % 60L); + count += 5; + } + else + return 0; + } + break; +#endif + case 'Z': + if (tim_p->tm_isdst >= 0) + { + int size; + TZ_LOCK; + size = strlen(_tzname[tim_p->tm_isdst > 0]); + for (i = 0; i < size; i++) + { + if (count < maxsize - 1) + s[count++] = _tzname[tim_p->tm_isdst > 0][i]; + else + { + TZ_UNLOCK; + return 0; + } + } + TZ_UNLOCK; + } + break; + case '%': + if (count < maxsize - 1) + s[count++] = '%'; + else + return 0; + break; + } + if (*format) + format++; + else + break; + } + if (maxsize) + s[count] = '\0'; + + return count; +} diff --git a/vendor/shttpd/compat_wince.h b/vendor/shttpd/compat_wince.h new file mode 100644 index 0000000..651ec50 --- /dev/null +++ b/vendor/shttpd/compat_wince.h @@ -0,0 +1,145 @@ + +#ifndef INCLUDE_WINCE_COMPAT_H +#define INCLUDE_WINCE_COMPAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*** ANSI C library ***/ + +/* Missing ANSI C definitions */ + +#define BUFSIZ 4096 + +#define ENOMEM ERROR_NOT_ENOUGH_MEMORY +#define EBADF ERROR_INVALID_HANDLE +#define EINVAL ERROR_INVALID_PARAMETER +#define ENOENT ERROR_FILE_NOT_FOUND +#define ERANGE ERROR_INSUFFICIENT_BUFFER +#define EINTR WSAEINTR + +/* + * Because we need a per-thread errno, we define a function + * pointer that we can call to return a pointer to the errno + * for the current thread. Then we define a macro for errno + * that dereferences this function's result. + * + * This makes it syntactically just like the "real" errno. + * + * Using a function pointer allows us to use a very fast + * function when there are no threads running and a slower + * function when there are multiple threads running. + */ +void __WinCE_Errno_New_Thread(int *Errno_Pointer); +void __WinCE_Errno_Thread_Exit(void); +extern int *(*__WinCE_Errno_Pointer_Function)(void); + +#define errno (*(*__WinCE_Errno_Pointer_Function)()) + +char *strerror(int errnum); + +struct tm { + int tm_sec; /* seconds after the minute - [0,59] */ + int tm_min; /* minutes after the hour - [0,59] */ + int tm_hour; /* hours since midnight - [0,23] */ + int tm_mday; /* day of the month - [1,31] */ + int tm_mon; /* months since January - [0,11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday - [0,6] */ + int tm_yday; /* days since January 1 - [0,365] */ + int tm_isdst; /* daylight savings time flag */ +}; + +struct tm *gmtime(const time_t *TimeP); /* for future use */ +struct tm *localtime(const time_t *TimeP); +time_t mktime(struct tm *tm); +time_t time(time_t *TimeP); + +size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *tim_p); + +int _wrename(const wchar_t *oldname, const wchar_t *newname); +int _wremove(const wchar_t *filename); + +/* Environment variables are not supported */ +#define getenv(x) (NULL) + +/* Redefine fileno so that it returns an integer */ +#undef fileno +#define fileno(f) (int)_fileno(f) + +/* Signals are not supported */ +#define signal(num, handler) (0) +#define SIGTERM 0 +#define SIGINT 0 + + +/*** POSIX API ***/ + +/* Missing POSIX definitions */ + +#define FILENAME_MAX MAX_PATH + +struct _stat { + unsigned long st_size; + unsigned long st_ino; + int st_mode; + unsigned long st_atime; + unsigned long st_mtime; + unsigned long st_ctime; + unsigned short st_dev; + unsigned short st_nlink; + unsigned short st_uid; + unsigned short st_gid; +}; + +#define S_IFMT 0170000 +#define S_IFDIR 0040000 +#define S_IFREG 0100000 +#define S_IEXEC 0000100 +#define S_IWRITE 0000200 +#define S_IREAD 0000400 + +#define _S_IFDIR S_IFDIR /* MSVCRT compatibilit */ + +int _fstat(int handle, struct _stat *buffer); +int _wstat(const wchar_t *path, struct _stat *buffer); + +#define stat _stat /* NOTE: applies to _stat() and also struct _stat */ +#define fstat _fstat + +#define O_RDWR (1<<0) +#define O_RDONLY (2<<0) +#define O_WRONLY (3<<0) +#define O_MODE_MASK (3<<0) +#define O_TRUNC (1<<2) +#define O_EXCL (1<<3) +#define O_CREAT (1<<4) +#define O_BINARY 0 + +int _wopen(const wchar_t *filename, int oflag, ...); +int _close(int handle); +int _write(int handle, const void *buffer, unsigned int count); +int _read(int handle, void *buffer, unsigned int count); +long _lseek(int handle, long offset, int origin); + +#define close _close +#define write _write +#define read _read +#define lseek _lseek + +/* WinCE has only a Unicode version of this function */ +FILE *fdopen(int handle, const char *mode); + +int _wmkdir(const wchar_t *dirname); + +/* WinCE has no concept of current directory so we return a constant path */ +wchar_t *_wgetcwd(wchar_t *buffer, int maxlen); + +#define freopen(path, mode, stream) assert(0) + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDE_WINCE_COMPAT_H */ diff --git a/vendor/shttpd/config.h b/vendor/shttpd/config.h new file mode 100644 index 0000000..875bbf3 --- /dev/null +++ b/vendor/shttpd/config.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#ifndef CONFIG_HEADER_DEFINED +#define CONFIG_HEADER_DEFINED + +#undef VERSION +#define VERSION "1.42" /* Version */ +#define CONFIG_FILE "shttpd.conf" /* Configuration file */ +#define HTPASSWD ".htpasswd" /* Passwords file name */ +#define URI_MAX 16384 /* Default max request size */ +#define LISTENING_PORTS "80" /* Default listening ports */ +#define INDEX_FILES "index.html,index.htm,index.php,index.cgi" +#define CGI_EXT "cgi,pl,php" /* Default CGI extensions */ +#define SSI_EXT "shtml,shtm" /* Default SSI extensions */ +#define REALM "mydomain.com" /* Default authentication realm */ +#define DELIM_CHARS "," /* Separators for lists */ +#define EXPIRE_TIME 3600 /* Expiration time, seconds */ +#define ENV_MAX 4096 /* Size of environment block */ +#define CGI_ENV_VARS 64 /* Maximum vars passed to CGI */ +#define SERVICE_NAME "SHTTPD " VERSION /* NT service name */ + +#endif /* CONFIG_HEADER_DEFINED */ diff --git a/vendor/shttpd/defs.h b/vendor/shttpd/defs.h new file mode 100644 index 0000000..120de35 --- /dev/null +++ b/vendor/shttpd/defs.h @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#ifndef DEFS_HEADER_DEFINED +#define DEFS_HEADER_DEFINED + +#include "std_includes.h" +#include "llist.h" +#include "io.h" +#include "md5.h" +#include "config.h" +#include "shttpd.h" + +#define NELEMS(ar) (sizeof(ar) / sizeof(ar[0])) + +#ifdef _DEBUG +#define DBG(x) do { printf x ; putchar('\n'); fflush(stdout); } while (0) +#else +#define DBG(x) +#endif /* DEBUG */ + +/* + * Darwin prior to 7.0 and Win32 do not have socklen_t + */ +#ifdef NO_SOCKLEN_T +typedef int socklen_t; +#endif /* NO_SOCKLEN_T */ + +/* + * For parsing. This guy represents a substring. + */ +struct vec { + const char *ptr; + int len; +}; + +#if !defined(FALSE) +enum {FALSE, TRUE}; +#endif /* !FALSE */ + +enum {METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_DELETE, METHOD_HEAD}; +enum {HDR_DATE, HDR_INT, HDR_STRING}; /* HTTP header types */ +enum {E_FATAL = 1, E_LOG = 2}; /* Flags for elog() function */ +typedef unsigned long big_int_t; /* Type for Content-Length */ + +/* + * Unified socket address + */ +struct usa { + socklen_t len; + union { + struct sockaddr sa; + struct sockaddr_in sin; + } u; +}; + +/* + * This thing is aimed to hold values of any type. + * Used to store parsed headers' values. + */ +union variant { + char *v_str; + int v_int; + big_int_t v_big_int; + time_t v_time; + void (*v_func)(void); + void *v_void; + struct vec v_vec; +}; + +/* + * This is used only in embedded configuration. This structure holds a + * registered URI, associated callback function with callback data. + * For non-embedded compilation shttpd_callback_t is not defined, so + * we use union variant to keep the compiler silent. + */ +struct registered_uri { + struct llhead link; + const char *uri; + union variant callback; + void *callback_data; +}; + +/* + * User may want to handle certain errors. This structure holds the + * handlers for corresponding error codes. + */ +struct error_handler { + struct llhead link; + int code; + union variant callback; + void *callback_data; +}; + +struct http_header { + int len; /* Header name length */ + int type; /* Header type */ + size_t offset; /* Value placeholder */ + const char *name; /* Header name */ +}; + +/* + * This guy holds parsed HTTP headers + */ +struct headers { + union variant cl; /* Content-Length: */ + union variant ct; /* Content-Type: */ + union variant connection; /* Connection: */ + union variant ims; /* If-Modified-Since: */ + union variant user; /* Remote user name */ + union variant auth; /* Authorization */ + union variant useragent; /* User-Agent: */ + union variant referer; /* Referer: */ + union variant cookie; /* Cookie: */ + union variant location; /* Location: */ + union variant range; /* Range: */ + union variant status; /* Status: */ + union variant transenc; /* Transfer-Encoding: */ +}; + +/* Must go after union variant definition */ +#include "ssl.h" + +/* + * The communication channel + */ +union channel { + int fd; /* Regular static file */ + int sock; /* Connected socket */ + struct { + int sock; /* XXX important. must be first */ + SSL *ssl; /* shttpd_poll() assumes that */ + } ssl; /* SSL-ed socket */ + struct { + DIR *dirp; + char *path; + } dir; /* Opened directory */ + struct { + void *state; /* For keeping state */ + union variant func; /* User callback function */ + void *data; /* User defined parameters */ + } emb; /* Embedded, user callback */ +}; + +struct stream; + +/* + * IO class descriptor (file, directory, socket, SSL, CGI, etc) + * These classes are defined in io_*.c files. + */ +struct io_class { + const char *name; + int (*read)(struct stream *, void *buf, size_t len); + int (*write)(struct stream *, const void *buf, size_t len); + void (*close)(struct stream *); +}; + +/* + * Data exchange stream. It is backed by some communication channel: + * opened file, socket, etc. The 'read' and 'write' methods are + * determined by a communication channel. + */ +struct stream { + struct conn *conn; + union channel chan; /* Descriptor */ + struct io io; /* IO buffer */ + const struct io_class *io_class; /* IO class */ + int headers_len; + big_int_t content_len; + unsigned int flags; +#define FLAG_HEADERS_PARSED 1 +#define FLAG_SSL_ACCEPTED 2 +#define FLAG_R 4 /* Can read in general */ +#define FLAG_W 8 /* Can write in general */ +#define FLAG_CLOSED 16 +#define FLAG_DONT_CLOSE 32 +#define FLAG_ALWAYS_READY 64 /* File, dir, user_func */ +#define FLAG_SUSPEND 128 +}; + +struct worker { + struct llhead link; + int num_conns; /* Num of active connections */ + int exit_flag; /* Ditto - exit flag */ + int ctl[2]; /* Control socket pair */ + struct shttpd_ctx *ctx; /* Context reference */ + struct llhead connections; /* List of connections */ +}; + +struct conn { + struct llhead link; /* Connections chain */ + struct worker *worker; /* Worker this conn belongs to */ + struct shttpd_ctx *ctx; /* Context this conn belongs to */ + struct usa sa; /* Remote socket address */ + time_t birth_time; /* Creation time */ + time_t expire_time; /* Expiration time */ + + int loc_port; /* Local port */ + int status; /* Reply status code */ + int method; /* Request method */ + char *uri; /* Decoded URI */ + unsigned long major_version; /* Major HTTP version number */ + unsigned long minor_version; /* Minor HTTP version number */ + char *request; /* Request line */ + char *headers; /* Request headers */ + char *query; /* QUERY_STRING part of the URI */ + char *path_info; /* PATH_INFO thing */ + struct vec mime_type; /* Mime type */ + + struct headers ch; /* Parsed client headers */ + + struct stream loc; /* Local stream */ + struct stream rem; /* Remote stream */ + +#if !defined(NO_SSI) + void *ssi; /* SSI descriptor */ +#endif /* NO_SSI */ +}; + +enum { + OPT_ROOT, OPT_INDEX_FILES, OPT_PORTS, OPT_DIR_LIST, + OPT_CGI_EXTENSIONS, OPT_CGI_INTERPRETER, OPT_CGI_ENVIRONMENT, + OPT_SSI_EXTENSIONS, OPT_AUTH_REALM, OPT_AUTH_GPASSWD, + OPT_AUTH_PUT, OPT_ACCESS_LOG, OPT_ERROR_LOG, OPT_MIME_TYPES, + OPT_SSL_CERTIFICATE, OPT_ALIASES, OPT_ACL, OPT_INETD, OPT_UID, + OPT_CFG_URI, OPT_PROTECT, OPT_SERVICE, OPT_HIDE, OPT_THREADS, + NUM_OPTIONS +}; + +/* + * SHTTPD context + */ +struct shttpd_ctx { + SSL_CTX *ssl_ctx; /* SSL context */ + + struct llhead registered_uris;/* User urls */ + struct llhead error_handlers; /* Embedded error handlers */ + struct llhead acl; /* Access control list */ + struct llhead ssi_funcs; /* SSI callback functions */ + struct llhead listeners; /* Listening sockets */ + struct llhead workers; /* Worker workers */ + + FILE *access_log; /* Access log stream */ + FILE *error_log; /* Error log stream */ + + char *options[NUM_OPTIONS]; /* Configurable options */ +#if defined(__rtems__) + rtems_id mutex; +#endif /* _WIN32 */ +}; + +struct listener { + struct llhead link; + struct shttpd_ctx *ctx; /* Context that socket belongs */ + int sock; /* Listening socket */ + int is_ssl; /* Should be SSL-ed */ +}; + +/* Types of messages that could be sent over the control socket */ +enum {CTL_PASS_SOCKET, CTL_WAKEUP}; + +/* + * In SHTTPD, list of values are represented as comma or space separated + * string. For example, list of CGI extensions can be represented as + * ".cgi,.php,.pl", or ".cgi .php .pl". The macro that follows allows to + * loop through the individual values in that list. + * + * A "const char *" pointer and size_t variable must be passed to the macro. + * Spaces or commas can be used as delimiters (macro DELIM_CHARS). + * + * In every iteration of the loop, "s" points to the current value, and + * "len" specifies its length. The code inside loop must not change + * "s" and "len" parameters. + */ +#define FOR_EACH_WORD_IN_LIST(s,len) \ + for (; s != NULL && (len = strcspn(s, DELIM_CHARS)) != 0; \ + s += len, s+= strspn(s, DELIM_CHARS)) + +/* + * IPv4 ACL entry. Specifies subnet with deny/allow flag + */ +struct acl { + struct llhead link; + uint32_t ip; /* IP, in network byte order */ + uint32_t mask; /* Also in network byte order */ + int flag; /* Either '+' or '-' */ +}; + +/* + * shttpd.c + */ +extern time_t _shttpd_current_time; /* Current UTC time */ +extern int _shttpd_tz_offset; /* Offset from GMT time zone */ +extern const struct vec _shttpd_known_http_methods[]; + +extern void _shttpd_stop_stream(struct stream *stream); +extern int _shttpd_url_decode(const char *, int, char *dst, int); +extern void _shttpd_send_server_error(struct conn *, int, const char *); +extern int _shttpd_get_headers_len(const char *buf, size_t buflen); +extern void _shttpd_parse_headers(const char *s, int, struct headers *); +extern int _shttpd_is_true(const char *str); +extern int _shttpd_socketpair(int pair[2]); +extern void _shttpd_get_mime_type(struct shttpd_ctx *, + const char *, int, struct vec *); + +#define IS_TRUE(ctx, opt) _shttpd_is_true((ctx)->options[opt]) + +/* + * config.c + */ +extern void _shttpd_usage(const char *prog); + +/* + * log.c + */ +extern void _shttpd_elog(int flags, struct conn *c, const char *fmt, ...); +extern void _shttpd_log_access(FILE *fp, const struct conn *c); + +/* + * string.c + */ +extern void _shttpd_strlcpy(register char *, register const char *, size_t); +extern int _shttpd_strncasecmp(register const char *, + register const char *, size_t); +extern char *_shttpd_strndup(const char *ptr, size_t len); +extern char *_shttpd_strdup(const char *str); +extern int _shttpd_snprintf(char *buf, size_t len, const char *fmt, ...); +extern int _shttpd_match_extension(const char *path, const char *ext_list); + +/* + * compat_*.c + */ +extern void _shttpd_set_close_on_exec(int fd); +extern int _shttpd_set_non_blocking_mode(int fd); +extern int _shttpd_stat(const char *, struct stat *stp); +extern int _shttpd_open(const char *, int flags, int mode); +extern int _shttpd_remove(const char *); +extern int _shttpd_rename(const char *, const char *); +extern int _shttpd_mkdir(const char *, int); +extern char * _shttpd_getcwd(char *, int); +extern int _shttpd_spawn_process(struct conn *c, const char *prog, + char *envblk, char *envp[], int sock, const char *dir); + +extern int _shttpd_set_nt_service(struct shttpd_ctx *, const char *); +extern int _shttpd_set_systray(struct shttpd_ctx *, const char *); +extern void _shttpd_try_to_run_as_nt_service(void); + +/* + * io_*.c + */ +extern const struct io_class _shttpd_io_file; +extern const struct io_class _shttpd_io_socket; +extern const struct io_class _shttpd_io_ssl; +extern const struct io_class _shttpd_io_cgi; +extern const struct io_class _shttpd_io_dir; +extern const struct io_class _shttpd_io_embedded; +extern const struct io_class _shttpd_io_ssi; + +extern int _shttpd_put_dir(const char *path); +extern void _shttpd_get_dir(struct conn *c); +extern void _shttpd_get_file(struct conn *c, struct stat *stp); +extern void _shttpd_ssl_handshake(struct stream *stream); +extern void _shttpd_setup_embedded_stream(struct conn *, + union variant, void *); +extern struct registered_uri *_shttpd_is_registered_uri(struct shttpd_ctx *, + const char *uri); +extern void _shttpd_do_ssi(struct conn *); +extern void _shttpd_ssi_func_destructor(struct llhead *lp); + +/* + * auth.c + */ +extern int _shttpd_check_authorization(struct conn *c, const char *path); +extern int _shttpd_is_authorized_for_put(struct conn *c); +extern void _shttpd_send_authorization_request(struct conn *c); +extern int _shttpd_edit_passwords(const char *fname, const char *domain, + const char *user, const char *pass); + +/* + * cgi.c + */ +extern int _shttpd_run_cgi(struct conn *c, const char *prog); +extern void _shttpd_do_cgi(struct conn *c); + +#define CGI_REPLY "HTTP/1.1 OK\r\n" +#define CGI_REPLY_LEN (sizeof(CGI_REPLY) - 1) + +#endif /* DEFS_HEADER_DEFINED */ diff --git a/vendor/shttpd/io.h b/vendor/shttpd/io.h new file mode 100644 index 0000000..d774cc2 --- /dev/null +++ b/vendor/shttpd/io.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#ifndef IO_HEADER_INCLUDED +#define IO_HEADER_INCLUDED + +#include +#include + +/* + * I/O buffer descriptor + */ +struct io { + char *buf; /* IO Buffer */ + size_t size; /* IO buffer size */ + size_t head; /* Bytes read */ + size_t tail; /* Bytes written */ + size_t total; /* Total bytes read */ +}; + +static __inline void +io_clear(struct io *io) +{ + assert(io->buf != NULL); + assert(io->size > 0); + io->total = io->tail = io->head = 0; +} + +static __inline char * +io_space(struct io *io) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->head <= io->size); + return (io->buf + io->head); +} + +static __inline char * +io_data(struct io *io) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->tail <= io->size); + return (io->buf + io->tail); +} + +static __inline size_t +io_space_len(const struct io *io) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->head <= io->size); + return (io->size - io->head); +} + +static __inline size_t +io_data_len(const struct io *io) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->head <= io->size); + assert(io->tail <= io->head); + return (io->head - io->tail); +} + +static __inline void +io_inc_tail(struct io *io, size_t n) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->tail <= io->head); + assert(io->head <= io->size); + io->tail += n; + assert(io->tail <= io->head); + if (io->tail == io->head) + io->head = io->tail = 0; +} + +static __inline void +io_inc_head(struct io *io, size_t n) +{ + assert(io->buf != NULL); + assert(io->size > 0); + assert(io->tail <= io->head); + io->head += n; + io->total += n; + assert(io->head <= io->size); +} + +#endif /* IO_HEADER_INCLUDED */ diff --git a/vendor/shttpd/io_cgi.c b/vendor/shttpd/io_cgi.c new file mode 100644 index 0000000..b41c600 --- /dev/null +++ b/vendor/shttpd/io_cgi.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +static int +write_cgi(struct stream *stream, const void *buf, size_t len) +{ + assert(stream->chan.sock != -1); + assert(stream->flags & FLAG_W); + + return (send(stream->chan.sock, buf, len, 0)); +} + +static int +read_cgi(struct stream *stream, void *buf, size_t len) +{ + struct headers parsed; + char status[4]; + int n; + + assert(stream->chan.sock != -1); + assert(stream->flags & FLAG_R); + + stream->flags &= ~FLAG_DONT_CLOSE; + + n = recv(stream->chan.sock, buf, len, 0); + + if (stream->flags & FLAG_HEADERS_PARSED) + return (n); + + if (n <= 0 && ERRNO != EWOULDBLOCK) { + _shttpd_send_server_error(stream->conn, 500, + "Error running CGI"); + return (n); + } + + /* + * CGI script may output Status: and Location: headers, which + * may alter the status code. Buffer in headers, parse + * them, send correct status code and then forward all data + * from CGI script back to the remote end. + * Reply line was alredy appended to the IO buffer in + * decide_what_to_do(), with blank status code. + */ + + stream->flags |= FLAG_DONT_CLOSE; + io_inc_head(&stream->io, n); + + stream->headers_len = _shttpd_get_headers_len(stream->io.buf, + stream->io.head); + if (stream->headers_len < 0) { + stream->flags &= ~FLAG_DONT_CLOSE; + _shttpd_send_server_error(stream->conn, 500, + "Bad headers sent"); + _shttpd_elog(E_LOG, stream->conn, + "CGI script sent invalid headers: " + "[%.*s]", stream->io.head - CGI_REPLY_LEN, + stream->io.buf + CGI_REPLY_LEN); + return (0); + } + + /* + * If we did not received full headers yet, we must not send any + * data read from the CGI back to the client. Suspend sending by + * setting tail = head, which tells that there is no data in IO buffer + */ + + if (stream->headers_len == 0) { + stream->io.tail = stream->io.head; + return (0); + } + + /* Received all headers. Set status code for the connection. */ + (void) memset(&parsed, 0, sizeof(parsed)); + _shttpd_parse_headers(stream->io.buf, stream->headers_len, &parsed); + stream->content_len = parsed.cl.v_big_int; + stream->conn->status = (int) parsed.status.v_big_int; + + /* If script outputs 'Location:' header, set status code to 302 */ + if (parsed.location.v_vec.len > 0) + stream->conn->status = 302; + + /* + * If script did not output neither 'Location:' nor 'Status' headers, + * set the default status code 200, which means 'success'. + */ + if (stream->conn->status == 0) + stream->conn->status = 200; + + /* Append the status line to the beginning of the output */ + (void) _shttpd_snprintf(status, + sizeof(status), "%3d", stream->conn->status); + (void) memcpy(stream->io.buf + 9, status, 3); + DBG(("read_cgi: content len %lu status %s", + stream->content_len, status)); + + /* Next time, pass output directly back to the client */ + assert((big_int_t) stream->headers_len <= stream->io.total); + stream->io.total -= stream->headers_len; + stream->io.tail = 0; + stream->flags |= FLAG_HEADERS_PARSED; + + /* Return 0 because we've already shifted the head */ + return (0); +} + +static void +close_cgi(struct stream *stream) +{ + assert(stream->chan.sock != -1); + (void) closesocket(stream->chan.sock); +} + +const struct io_class _shttpd_io_cgi = { + "cgi", + read_cgi, + write_cgi, + close_cgi +}; diff --git a/vendor/shttpd/io_dir.c b/vendor/shttpd/io_dir.c new file mode 100644 index 0000000..7366398 --- /dev/null +++ b/vendor/shttpd/io_dir.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +/* + * For a given PUT path, create all intermediate subdirectories + * for given path. Return 0 if the path itself is a directory, + * or -1 on error, 1 if OK. + */ +int +_shttpd_put_dir(const char *path) +{ + char buf[FILENAME_MAX]; + const char *s, *p; + struct stat st; + size_t len; + + for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { + len = p - path; + assert(len < sizeof(buf)); + (void) memcpy(buf, path, len); + buf[len] = '\0'; + + /* Try to create intermediate directory */ + if (_shttpd_stat(buf, &st) == -1 && + _shttpd_mkdir(buf, 0755) != 0) + return (-1); + + /* Is path itself a directory ? */ + if (p[1] == '\0') + return (0); + } + + return (1); +} + +static int +read_dir(struct stream *stream, void *buf, size_t len) +{ + static const char footer[] = "\n"; + + struct dirent *dp = NULL; + char file[FILENAME_MAX], line[FILENAME_MAX + 512], + size[64], mod[64]; + struct stat st; + struct conn *c = stream->conn; + int n, nwritten = 0; + const char *slash = ""; + + assert(stream->chan.dir.dirp != NULL); + assert(stream->conn->uri[0] != '\0'); + + do { + if (len < sizeof(line)) + break; + + if ((dp = readdir(stream->chan.dir.dirp)) == NULL) + break; + DBG(("read_dir: %s", dp->d_name)); + + /* Do not show current dir and passwords file */ + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, HTPASSWD) == 0) + continue; + + (void) _shttpd_snprintf(file, sizeof(file), + "%s%s%s", stream->chan.dir.path, slash, dp->d_name); + (void) _shttpd_stat(file, &st); + if (S_ISDIR(st.st_mode)) { + _shttpd_snprintf(size,sizeof(size),"%s","<DIR>"); + } else { + if (st.st_size < 1024) + (void) _shttpd_snprintf(size, sizeof(size), + "%lu", (unsigned long) st.st_size); + else if (st.st_size < 1024 * 1024) + (void) _shttpd_snprintf(size, + sizeof(size), "%luk", + (unsigned long) (st.st_size >> 10) + 1); + else + (void) _shttpd_snprintf(size, sizeof(size), + "%.1fM", (float) st.st_size / 1048576); + } + (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", + localtime(&st.st_mtime)); + + n = _shttpd_snprintf(line, sizeof(line), + "%s%s" + " %s  %s\n", + c->uri, slash, dp->d_name, dp->d_name, + S_ISDIR(st.st_mode) ? "/" : "", mod, size); + (void) memcpy(buf, line, n); + buf = (char *) buf + n; + nwritten += n; + len -= n; + } while (dp != NULL); + + /* Append proper HTML footer for the page */ + if (dp == NULL && len >= sizeof(footer)) { + (void) memcpy(buf, footer, sizeof(footer)); + nwritten += sizeof(footer); + stream->flags |= FLAG_CLOSED; + } + + return (nwritten); +} + +static void +close_dir(struct stream *stream) +{ + assert(stream->chan.dir.dirp != NULL); + assert(stream->chan.dir.path != NULL); + (void) closedir(stream->chan.dir.dirp); + free(stream->chan.dir.path); +} + +void +_shttpd_get_dir(struct conn *c) +{ + if ((c->loc.chan.dir.dirp = opendir(c->loc.chan.dir.path)) == NULL) { + (void) free(c->loc.chan.dir.path); + _shttpd_send_server_error(c, 500, "Cannot open directory"); + } else { + c->loc.io.head = _shttpd_snprintf(c->loc.io.buf, c->loc.io.size, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: text/html; charset=utf-8\r\n\r\n" + "Index of %s" + "" + "

Index of %s

"
+		    ""
+		    "",
+		    c->uri, c->uri);
+		io_clear(&c->rem.io);
+		c->status = 200;
+		c->loc.io_class = &_shttpd_io_dir;
+		c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
+	}
+}
+
+const struct io_class	_shttpd_io_dir =  {
+	"dir",
+	read_dir,
+	NULL,
+	close_dir
+};
diff --git a/vendor/shttpd/io_emb.c b/vendor/shttpd/io_emb.c
new file mode 100644
index 0000000..468180b
--- /dev/null
+++ b/vendor/shttpd/io_emb.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+const char *
+shttpd_version(void)
+{
+	return (VERSION);
+}
+
+static void
+call_user(struct conn *c, struct shttpd_arg *arg, shttpd_callback_t func)
+{
+	arg->priv		= c;
+	arg->state		= c->loc.chan.emb.state;
+	arg->out.buf		= io_space(&c->loc.io);
+	arg->out.len		= io_space_len(&c->loc.io);
+	arg->out.num_bytes	= 0;
+	arg->in.buf		= io_data(&c->rem.io);;
+	arg->in.len		= io_data_len(&c->rem.io);
+	arg->in.num_bytes	= 0;
+
+	if (io_data_len(&c->rem.io) >= c->rem.io.size)
+		arg->flags |= SHTTPD_POST_BUFFER_FULL;
+
+	if (c->rem.content_len > 0 && c->rem.io.total < c->rem.content_len)
+		arg->flags |= SHTTPD_MORE_POST_DATA;
+
+	func(arg);
+
+	io_inc_head(&c->loc.io, arg->out.num_bytes);
+	io_inc_tail(&c->rem.io, arg->in.num_bytes);
+	c->loc.chan.emb.state = arg->state;		/* Save state */
+
+	/*
+	 * If callback finished output, that means it did all cleanup.
+	 * If the connection is terminated unexpectedly, we canna call
+	 * the callback via the stream close() method from disconnect.
+	 * However, if cleanup is already done, we set close() method to
+	 * NULL, to prevent the call from disconnect().
+	 */
+
+	if (arg->flags & SHTTPD_END_OF_OUTPUT)
+		c->loc.flags &= ~FLAG_DONT_CLOSE;
+	else
+		c->loc.flags |= FLAG_DONT_CLOSE;
+
+	if (arg->flags & SHTTPD_SUSPEND)
+		c->loc.flags |= FLAG_SUSPEND;
+}
+
+static int
+do_embedded(struct stream *stream, void *buf, size_t len)
+{
+	struct shttpd_arg	arg;
+	buf = NULL; len = 0;		/* Squash warnings */
+
+	arg.user_data	= stream->conn->loc.chan.emb.data;
+	arg.flags	= 0;
+
+	call_user(stream->conn, &arg, (shttpd_callback_t)
+			stream->conn->loc.chan.emb.func.v_func);
+
+	return (0);
+}
+
+static void
+close_embedded(struct stream *stream)
+{
+	struct shttpd_arg	arg;
+	struct conn		*c = stream->conn;
+
+	arg.flags	= SHTTPD_CONNECTION_ERROR;
+	arg.user_data	= c->loc.chan.emb.data;
+
+	/*
+	 * Do not call the user function if SHTTPD_END_OF_OUTPUT was set,
+	 * i.e. the callback already terminated correctly
+	 */
+	if (stream->flags & FLAG_DONT_CLOSE)
+		call_user(stream->conn, &arg, (shttpd_callback_t)
+		    c->loc.chan.emb.func.v_func);
+}
+
+size_t
+shttpd_printf(struct shttpd_arg *arg, const char *fmt, ...)
+{
+	char		*buf = arg->out.buf + arg->out.num_bytes;
+	int		buflen = arg->out.len - arg->out.num_bytes, len = 0;
+	va_list		ap;
+
+	if (buflen > 0) {
+		va_start(ap, fmt);
+		len = vsnprintf(buf, buflen, fmt, ap);
+		va_end(ap);
+
+		if (len < 0 || len > buflen)
+			len = buflen;
+		arg->out.num_bytes += len;
+	}
+
+	return (len);
+}
+
+const char *
+shttpd_get_header(struct shttpd_arg *arg, const char *header_name)
+{
+	struct conn	*c = arg->priv;
+	char		*p, *s, *e;
+	size_t		len;
+
+	p = c->headers;
+	e = c->request + c->rem.headers_len;
+	len = strlen(header_name);
+
+	while (p < e) {
+		if ((s = strchr(p, '\n')) != NULL)
+			s[s[-1] == '\r' ? -1 : 0] = '\0';
+		if (_shttpd_strncasecmp(header_name, p, len) == 0)
+			return (p + len + 2);
+
+		p += strlen(p) + 1;
+	}
+
+	return (NULL);
+}
+
+const char *
+shttpd_get_env(struct shttpd_arg *arg, const char *env_name)
+{
+	struct conn	*c = arg->priv;
+	struct vec	*vec;
+
+	if (strcmp(env_name, "REQUEST_METHOD") == 0) {
+		return (_shttpd_known_http_methods[c->method].ptr);
+	} else if (strcmp(env_name, "REQUEST_URI") == 0) {
+		return (c->uri);
+	} else if (strcmp(env_name, "QUERY_STRING") == 0) {
+		return (c->query);
+	} else if (strcmp(env_name, "REMOTE_USER") == 0) {
+		vec = &c->ch.user.v_vec;
+		if (vec->len > 0) {
+			((char *) vec->ptr)[vec->len] = '\0';
+			return (vec->ptr);
+		}
+	} else if (strcmp(env_name, "REMOTE_ADDR") == 0) {
+		return (inet_ntoa(c->sa.u.sin.sin_addr));/* FIXME NOT MT safe */
+	}
+
+	return (NULL);
+}
+
+void
+shttpd_get_http_version(struct shttpd_arg *arg,
+		unsigned long *major, unsigned long *minor)
+{
+	struct conn *c = arg->priv;
+	
+	*major = c->major_version;
+	*minor = c->minor_version;
+}
+
+void
+shttpd_register_uri(struct shttpd_ctx *ctx,
+		const char *uri, shttpd_callback_t callback, void *data)
+{
+	struct registered_uri	*e;
+
+	if ((e = malloc(sizeof(*e))) != NULL) {
+		e->uri			= _shttpd_strdup(uri);
+		e->callback.v_func	= (void (*)(void)) callback;
+		e->callback_data	= data;
+		LL_TAIL(&ctx->registered_uris, &e->link);
+	}
+}
+
+int
+shttpd_get_var(const char *var, const char *buf, int buf_len,
+		char *value, int value_len)
+{
+	const char	*p, *e, *s;
+	size_t		var_len;
+
+	var_len = strlen(var);
+	e = buf + buf_len;		/* End of QUERY_STRING buffer	*/
+
+	/* buf is "var1=val1&var2=val2...". Find variable first */
+	for (p = buf; p + var_len < e; p++)
+		if ((p == buf || p[-1] == '&') &&
+		    p[var_len] == '=' &&
+		    !_shttpd_strncasecmp(var, p, var_len)) {
+
+			/* Point 'p' to var value, 's' to the end of value */
+			p += var_len + 1;	
+			if ((s = memchr(p, '&', e - p)) == NULL)
+				s = e;
+
+			/* URL-decode value. Return result length */
+			return (_shttpd_url_decode(p, s - p, value, value_len));
+		}
+
+	return (-1);
+}
+
+static int
+match_regexp(const char *regexp, const char *text)
+{
+	if (*regexp == '\0')
+		return (*text == '\0');
+
+	if (*regexp == '*')
+		do {
+			if (match_regexp(regexp + 1, text))
+				return (1);
+		} while (*text++ != '\0');
+
+	if (*text != '\0' && *regexp == *text)
+		return (match_regexp(regexp + 1, text + 1));
+
+	return (0);
+}
+
+struct registered_uri *
+_shttpd_is_registered_uri(struct shttpd_ctx *ctx, const char *uri)
+{
+	struct llhead		*lp;
+	struct registered_uri	*reg_uri;
+
+	LL_FOREACH(&ctx->registered_uris, lp) {
+		reg_uri = LL_ENTRY(lp, struct registered_uri, link);
+		if (match_regexp(reg_uri->uri, uri))
+			return (reg_uri);
+	}
+
+	return (NULL);
+}
+
+void
+_shttpd_setup_embedded_stream(struct conn *c, union variant func, void *data)
+{
+	c->loc.chan.emb.state = NULL;
+	c->loc.chan.emb.func = func;
+	c->loc.chan.emb.data = data;
+	c->loc.io_class = &_shttpd_io_embedded;
+	c->loc.flags |= FLAG_R | FLAG_W |FLAG_ALWAYS_READY;
+}
+
+void
+shttpd_handle_error(struct shttpd_ctx *ctx, int code,
+		shttpd_callback_t func, void *data)
+{
+	struct error_handler	*e;
+
+	if ((e = malloc(sizeof(*e))) != NULL) {
+		e->code = code;
+		e->callback.v_func = (void (*)(void)) func;
+		e->callback_data = data;
+		LL_TAIL(&ctx->error_handlers, &e->link);
+	}
+}
+
+void
+shttpd_wakeup(const void *priv)
+{
+	const struct conn	*conn = priv;
+	char			buf[sizeof(int) + sizeof(void *)];
+	int			cmd = CTL_WAKEUP;
+
+#if 0
+	conn->flags &= ~SHTTPD_SUSPEND;
+#endif
+	(void) memcpy(buf, &cmd, sizeof(cmd));
+	(void) memcpy(buf + sizeof(cmd), conn, sizeof(conn));
+
+	(void) send(conn->worker->ctl[1], buf, sizeof(buf), 0);
+}
+
+const struct io_class	_shttpd_io_embedded =  {
+	"embedded",
+	do_embedded,
+	(int (*)(struct stream *, const void *, size_t)) do_embedded,
+	close_embedded
+};
diff --git a/vendor/shttpd/io_file.c b/vendor/shttpd/io_file.c
new file mode 100644
index 0000000..674e455
--- /dev/null
+++ b/vendor/shttpd/io_file.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+static int
+write_file(struct stream *stream, const void *buf, size_t len)
+{
+	struct stat	st;
+	struct stream	*rem = &stream->conn->rem;
+	int		n, fd = stream->chan.fd;
+
+	assert(fd != -1);
+	n = write(fd, buf, len);
+
+	DBG(("put_file(%p, %d): %d bytes", (void *) stream, (int) len, n));
+
+	if (n <= 0 || (rem->io.total >= (big_int_t) rem->content_len)) {
+		(void) fstat(fd, &st);
+		stream->io.head = stream->headers_len =
+		    _shttpd_snprintf(stream->io.buf,
+		    stream->io.size, "HTTP/1.1 %d OK\r\n"
+		    "Content-Length: %lu\r\nConnection: close\r\n\r\n",
+		    stream->conn->status, st.st_size);
+		_shttpd_stop_stream(stream);
+	}
+
+	return (n);
+}
+
+static int
+read_file(struct stream *stream, void *buf, size_t len)
+{
+#ifdef USE_SENDFILE
+	struct	iovec	vec;
+	struct	sf_hdtr	hd = {&vec, 1, NULL, 0}, *hdp = &hd;
+	int		sock, fd, n;
+	size_t		nbytes;
+	off_t		sent;
+
+	sock = stream->conn->rem.chan.sock;
+	fd = stream->chan.fd;
+
+	/* If this is the first call for this file, send the headers */
+	vec.iov_base = stream->io.buf;
+	vec.iov_len = stream->headers_len;
+	if (stream->io.total > 0)
+		hdp = NULL;
+
+	nbytes = stream->content_len - stream->io.total;
+	n = sendfile(fd, sock, lseek(fd, 0, SEEK_CUR), nbytes, hdp, &sent, 0);
+
+	if (n == -1 && ERRNO != EAGAIN) {
+		stream->flags &= ~FLAG_DONT_CLOSE;
+		return (n);
+	}
+
+	stream->conn->ctx->out += sent;
+
+	/* If we have sent the HTTP headers in this turn, clear them off */
+	if (stream->io.total == 0) {
+		assert(sent >= stream->headers_len);
+		sent -= stream->headers_len;
+		io_clear(&stream->io);
+	}
+
+	(void) lseek(fd, sent, SEEK_CUR);
+	stream->io.total += sent;
+	stream->flags |= FLAG_DONT_CLOSE;
+
+	return (0);
+#endif /* USE_SENDFILE */
+
+	assert(stream->chan.fd != -1);
+	return (read(stream->chan.fd, buf, len));
+}
+
+static void
+close_file(struct stream *stream)
+{
+	assert(stream->chan.fd != -1);
+	(void) close(stream->chan.fd);
+}
+
+void
+_shttpd_get_file(struct conn *c, struct stat *stp)
+{
+	char		date[64], lm[64], etag[64], range[64] = "";
+	size_t		n, status = 200;
+	unsigned long	r1, r2;
+	const char	*fmt = "%a, %d %b %Y %H:%M:%S GMT", *msg = "OK";
+	big_int_t	cl; /* Content-Length */
+
+	if (c->mime_type.len == 0)
+		_shttpd_get_mime_type(c->ctx, c->uri,
+		    strlen(c->uri), &c->mime_type); 
+	cl = (big_int_t) stp->st_size;
+
+	/* If Range: header specified, act accordingly */
+	if (c->ch.range.v_vec.len > 0 &&
+	    (n = sscanf(c->ch.range.v_vec.ptr,"bytes=%lu-%lu",&r1, &r2)) > 0) {
+		status = 206;
+		(void) lseek(c->loc.chan.fd, r1, SEEK_SET);
+		cl = n == 2 ? r2 - r1 + 1: cl - r1;
+		(void) _shttpd_snprintf(range, sizeof(range),
+		    "Content-Range: bytes %lu-%lu/%lu\r\n",
+		    r1, r1 + cl - 1, (unsigned long) stp->st_size);
+		msg = "Partial Content";
+	}
+
+	/* Prepare Etag, Date, Last-Modified headers */
+	(void) strftime(date, sizeof(date),
+	    fmt, localtime(&_shttpd_current_time));
+	(void) strftime(lm, sizeof(lm), fmt, localtime(&stp->st_mtime));
+	(void) _shttpd_snprintf(etag, sizeof(etag), "%lx.%lx",
+	    (unsigned long) stp->st_mtime, (unsigned long) stp->st_size);
+
+	/*
+	 * We do not do io_inc_head here, because it will increase 'total'
+	 * member in io. We want 'total' to be equal to the content size,
+	 * and exclude the headers length from it.
+	 */
+	c->loc.io.head = c->loc.headers_len = _shttpd_snprintf(c->loc.io.buf,
+	    c->loc.io.size,
+	    "HTTP/1.1 %d %s\r\n"
+	    "Date: %s\r\n"
+	    "Last-Modified: %s\r\n"
+	    "Etag: \"%s\"\r\n"
+	    "Content-Type: %.*s\r\n"
+	    "Content-Length: %lu\r\n"
+	    "Accept-Ranges: bytes\r\n"
+	    "%s\r\n",
+	    status, msg, date, lm, etag,
+	    c->mime_type.len, c->mime_type.ptr, cl, range);
+
+	c->status = status;
+	c->loc.content_len = cl;
+	c->loc.io_class = &_shttpd_io_file;
+	c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
+
+	if (c->method == METHOD_HEAD)
+		_shttpd_stop_stream(&c->loc);
+}
+
+const struct io_class	_shttpd_io_file =  {
+	"file",
+	read_file,
+	write_file,
+	close_file
+};
diff --git a/vendor/shttpd/io_socket.c b/vendor/shttpd/io_socket.c
new file mode 100644
index 0000000..20bf207
--- /dev/null
+++ b/vendor/shttpd/io_socket.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+static int
+read_socket(struct stream *stream, void *buf, size_t len)
+{
+	assert(stream->chan.sock != -1);
+	return (recv(stream->chan.sock, buf, len, 0));
+}
+
+static int
+write_socket(struct stream *stream, const void *buf, size_t len)
+{
+	assert(stream->chan.sock != -1);
+	return (send(stream->chan.sock, buf, len, 0));
+}
+
+static void
+close_socket(struct stream *stream)
+{
+	assert(stream->chan.sock != -1);
+	(void) closesocket(stream->chan.sock);
+}
+
+const struct io_class	_shttpd_io_socket =  {
+	"socket",
+	read_socket,
+	write_socket,
+	close_socket
+};
diff --git a/vendor/shttpd/io_ssi.c b/vendor/shttpd/io_ssi.c
new file mode 100644
index 0000000..1c15fc6
--- /dev/null
+++ b/vendor/shttpd/io_ssi.c
@@ -0,0 +1,488 @@
+/*
+ * Copyright (c) 2006,2007 Steven Johnson 
+ * Copyright (c) 2007 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+#if !defined(NO_SSI)
+
+#define	CMDBUFSIZ	512		/* SSI command buffer size	*/
+#define	NEST_MAX	6		/* Maximum nesting level	*/
+
+struct ssi_func {
+	struct llhead	link;
+	void		*user_data;
+	char		*name;
+	shttpd_callback_t func;
+};
+
+struct ssi_inc {
+	int		state;		/* Buffering state		*/
+	int		cond;		/* Conditional state		*/
+	FILE		*fp;		/* Icluded file stream		*/
+	char		buf[CMDBUFSIZ];	/* SSI command buffer		*/
+	size_t		nbuf;		/* Bytes in a command buffer	*/
+	FILE		*pipe;		/* #exec stream			*/
+	struct ssi_func	func;		/* #call function		*/
+};
+
+struct ssi {
+	struct conn	*conn;		/* Connection we belong to	*/
+	int		nest;		/* Current nesting level	*/
+	struct ssi_inc	incs[NEST_MAX];	/* Nested includes		*/
+};
+
+enum { SSI_PASS, SSI_BUF, SSI_EXEC, SSI_CALL };
+enum { SSI_GO, SSI_STOP };		/* Conditional states		*/
+
+static const struct vec	st = {"".
+		 * That means that when do_command() is called, we can rely
+		 * on that full command with arguments is buffered in and
+		 * there is no need for streaming.
+		 * Restrictions:
+		 *  1. The command must fit in CMDBUFSIZ
+		 *  2. HTML comments inside the command ? Not sure about this.
+		 */
+		case SSI_BUF:
+			if (inc->nbuf >= sizeof(inc->buf) - 1) {
+				pass(inc, buf + n, &n);
+			} else if (ch == '>' &&
+			    !memcmp(inc->buf + inc->nbuf - 2, "--", 2)) {
+				do_command(ssi, buf + n, len - n, &n);
+				inc = ssi->incs + ssi->nest;
+			} else {
+				inc->buf[inc->nbuf++] = ch;
+
+				/* If not SSI tag, pass it */
+				if (inc->nbuf <= (size_t) st.len &&
+				    memcmp(inc->buf, st.ptr, inc->nbuf) != 0)
+					pass(inc, buf + n, &n);
+			}
+			break;
+
+		case SSI_EXEC:
+		case SSI_CALL:
+			break;
+
+		default:
+			/* Never happens */
+			abort();
+			break;
+		}
+
+	if (ssi->nest > 0 && n + inc->nbuf < len && ch == EOF) {
+		(void) fclose(inc->fp);
+		inc->fp = NULL;
+		ssi->nest--;
+		inc--;
+		goto again;
+	}
+	
+	return (n);
+}
+
+static void
+close_ssi(struct stream *stream)
+{
+	struct ssi	*ssi = stream->conn->ssi;
+	size_t		i;
+
+	for (i = 0; i < NELEMS(ssi->incs); i++) {
+		if (ssi->incs[i].fp != NULL)
+			(void) fclose(ssi->incs[i].fp);
+		if (ssi->incs[i].pipe != NULL)
+			(void) pclose(ssi->incs[i].pipe);
+	}
+
+	free(ssi);
+}
+
+void
+_shttpd_do_ssi(struct conn *c)
+{
+	char		date[64];
+	struct ssi	*ssi;
+
+	(void) strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT",
+	    localtime(&_shttpd_current_time));
+
+	c->loc.io.head = c->loc.headers_len = _shttpd_snprintf(c->loc.io.buf,
+	    c->loc.io.size,
+	    "HTTP/1.1 200 OK\r\n"
+	    "Date: %s\r\n"
+	    "Content-Type: text/html\r\n"
+	    "Connection: close\r\n\r\n",
+	    date);
+
+	c->status = 200;
+	c->loc.io_class = &_shttpd_io_ssi;
+	c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
+
+	if (c->method == METHOD_HEAD) {
+		_shttpd_stop_stream(&c->loc);
+	} else if ((ssi = calloc(1, sizeof(struct ssi))) == NULL) {
+		_shttpd_send_server_error(c, 500,
+		    "Cannot allocate SSI descriptor");
+	} else {
+		ssi->incs[0].fp = fdopen(c->loc.chan.fd, "r");
+		ssi->conn = c;
+		c->ssi = ssi;
+	}
+}
+
+const struct io_class	_shttpd_io_ssi =  {
+	"ssi",
+	read_ssi,
+	NULL,
+	close_ssi
+};
+
+#endif /* !NO_SSI */
diff --git a/vendor/shttpd/io_ssl.c b/vendor/shttpd/io_ssl.c
new file mode 100644
index 0000000..6de0db2
--- /dev/null
+++ b/vendor/shttpd/io_ssl.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+#if !defined(NO_SSL)
+struct ssl_func	ssl_sw[] = {
+	{"SSL_free",			{0}},
+	{"SSL_accept",			{0}},
+	{"SSL_connect",			{0}},
+	{"SSL_read",			{0}},
+	{"SSL_write",			{0}},
+	{"SSL_get_error",		{0}},
+	{"SSL_set_fd",			{0}},
+	{"SSL_new",			{0}},
+	{"SSL_CTX_new",			{0}},
+	{"SSLv23_server_method",	{0}},
+	{"SSL_library_init",		{0}},
+	{"SSL_CTX_use_PrivateKey_file",	{0}},
+	{"SSL_CTX_use_certificate_file",{0}},
+	{NULL,				{0}}
+};
+
+void
+_shttpd_ssl_handshake(struct stream *stream)
+{
+	int	n;
+
+	if ((n = SSL_accept(stream->chan.ssl.ssl)) == 1) {
+		DBG(("handshake: SSL accepted"));
+		stream->flags |= FLAG_SSL_ACCEPTED;
+	} else {
+		n = SSL_get_error(stream->chan.ssl.ssl, n);
+		if (n != SSL_ERROR_WANT_READ && n != SSL_ERROR_WANT_WRITE)
+			stream->flags |= FLAG_CLOSED;
+		DBG(("SSL_accept error %d", n));
+	}
+}
+
+static int
+read_ssl(struct stream *stream, void *buf, size_t len)
+{
+	int	nread = -1;
+
+	assert(stream->chan.ssl.ssl != NULL);
+
+	if (!(stream->flags & FLAG_SSL_ACCEPTED))
+		_shttpd_ssl_handshake(stream);
+
+	if (stream->flags & FLAG_SSL_ACCEPTED)
+		nread = SSL_read(stream->chan.ssl.ssl, buf, len);
+
+	return (nread);
+}
+
+static int
+write_ssl(struct stream *stream, const void *buf, size_t len)
+{
+	assert(stream->chan.ssl.ssl != NULL);
+	return (SSL_write(stream->chan.ssl.ssl, buf, len));
+}
+
+static void
+close_ssl(struct stream *stream)
+{
+	assert(stream->chan.ssl.sock != -1);
+	assert(stream->chan.ssl.ssl != NULL);
+	(void) closesocket(stream->chan.ssl.sock);
+	SSL_free(stream->chan.ssl.ssl);
+}
+
+const struct io_class	_shttpd_io_ssl =  {
+	"ssl",
+	read_ssl,
+	write_ssl,
+	close_ssl
+};
+#endif /* !NO_SSL */
diff --git a/vendor/shttpd/llist.h b/vendor/shttpd/llist.h
new file mode 100644
index 0000000..04e79bb
--- /dev/null
+++ b/vendor/shttpd/llist.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#ifndef LLIST_HEADER_INCLUDED
+#define	LLIST_HEADER_INCLUDED
+
+/*
+ * Linked list macros.
+ */
+struct llhead {
+	struct llhead	*prev;
+	struct llhead	*next;
+};
+
+#define	LL_INIT(N)	((N)->next = (N)->prev = (N))
+
+#define LL_HEAD(H)	struct llhead H = { &H, &H }
+
+#define LL_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))
+
+#define	LL_ADD(H, N)							\
+	do {								\
+		((H)->next)->prev = (N);				\
+		(N)->next = ((H)->next);				\
+		(N)->prev = (H);					\
+		(H)->next = (N);					\
+	} while (0)
+
+#define	LL_TAIL(H, N)							\
+	do {								\
+		((H)->prev)->next = (N);				\
+		(N)->prev = ((H)->prev);				\
+		(N)->next = (H);					\
+		(H)->prev = (N);					\
+	} while (0)
+
+#define	LL_DEL(N)							\
+	do {								\
+		((N)->next)->prev = ((N)->prev);			\
+		((N)->prev)->next = ((N)->next);			\
+		LL_INIT(N);						\
+	} while (0)
+
+#define	LL_EMPTY(N)	((N)->next == (N))
+
+#define	LL_FOREACH(H,N)	for (N = (H)->next; N != (H); N = (N)->next)
+
+#define LL_FOREACH_SAFE(H,N,T)						\
+	for (N = (H)->next, T = (N)->next; N != (H);			\
+			N = (T), T = (N)->next)
+
+#endif /* LLIST_HEADER_INCLUDED */
diff --git a/vendor/shttpd/log.c b/vendor/shttpd/log.c
new file mode 100644
index 0000000..8c506f4
--- /dev/null
+++ b/vendor/shttpd/log.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#include "defs.h"
+
+/*
+ * Log function
+ */
+void
+_shttpd_elog(int flags, struct conn *c, const char *fmt, ...)
+{
+	char	date[64], buf[URI_MAX];
+	int	len;
+	FILE	*fp = c == NULL ? NULL : c->ctx->error_log;
+	va_list	ap;
+
+	/* Print to stderr */
+	if (c == NULL || !IS_TRUE(c->ctx, OPT_INETD)) {
+		va_start(ap, fmt);
+		(void) vfprintf(stderr, fmt, ap);
+		(void) fputc('\n', stderr);
+		va_end(ap);
+	}
+
+	strftime(date, sizeof(date), "%a %b %d %H:%M:%S %Y",
+	    localtime(&_shttpd_current_time));
+
+	len = _shttpd_snprintf(buf, sizeof(buf),
+	    "[%s] [error] [client %s] \"%s\" ",
+	    date, c ? inet_ntoa(c->sa.u.sin.sin_addr) : "-",
+	    c && c->request ? c->request : "-");
+
+	va_start(ap, fmt);
+	(void) vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
+	va_end(ap);
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	if (fp != NULL && (flags & (E_FATAL | E_LOG))) {
+		(void) fprintf(fp, "%s\n", buf);
+		(void) fflush(fp);
+	}
+
+	if (flags & E_FATAL)
+		exit(EXIT_FAILURE);
+}
+
+void
+_shttpd_log_access(FILE *fp, const struct conn *c)
+{
+	static const struct vec	dash = {"-", 1};
+
+	const struct vec	*user = &c->ch.user.v_vec;
+	const struct vec	*referer = &c->ch.referer.v_vec;
+	const struct vec	*user_agent = &c->ch.useragent.v_vec;
+	char			date[64], buf[URI_MAX], *q1 = "\"", *q2 = "\"";
+
+	if (user->len == 0)
+		user = ‐
+
+	if (referer->len == 0) {
+		referer = ‐
+		q1 = "";
+	}
+
+	if (user_agent->len == 0) {
+		user_agent = ‐
+		q2 = "";
+	}
+
+	(void) strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S",
+			localtime(&c->birth_time));
+
+	(void) _shttpd_snprintf(buf, sizeof(buf),
+	    "%s - %.*s [%s %+05d] \"%s\" %d %lu %s%.*s%s %s%.*s%s",
+	    inet_ntoa(c->sa.u.sin.sin_addr), user->len, user->ptr,
+	    date, _shttpd_tz_offset, c->request ? c->request : "-",
+	    c->status, (unsigned long) c->loc.io.total,
+	    q1, referer->len, referer->ptr, q1,
+	    q2, user_agent->len, user_agent->ptr, q2);
+
+	if (fp != NULL) {
+		(void) fprintf(fp, "%s\n", buf);
+		(void) fflush(fp);
+	}
+}
diff --git a/vendor/shttpd/md5.c b/vendor/shttpd/md5.c
new file mode 100644
index 0000000..ddd90e1
--- /dev/null
+++ b/vendor/shttpd/md5.c
@@ -0,0 +1,249 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#include "defs.h"
+
+#ifndef HAVE_MD5
+#if __BYTE_ORDER == 1234
+#define byteReverse(buf, len)	/* Nothing */
+#else
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse(unsigned char *buf, unsigned longs)
+{
+	uint32_t t;
+	do {
+		t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+			((unsigned) buf[1] << 8 | buf[0]);
+		*(uint32_t *) buf = t;
+		buf += 4;
+	} while (--longs);
+}
+#endif /* __BYTE_ORDER */
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+( w += f(x, y, z) + data,  w = w<>(32-s),  w += x )
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(MD5_CTX *ctx)
+{
+	ctx->buf[0] = 0x67452301;
+	ctx->buf[1] = 0xefcdab89;
+	ctx->buf[2] = 0x98badcfe;
+	ctx->buf[3] = 0x10325476;
+
+	ctx->bits[0] = 0;
+	ctx->bits[1] = 0;
+}
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+	register uint32_t a, b, c, d;
+
+	a = buf[0];
+	b = buf[1];
+	c = buf[2];
+	d = buf[3];
+
+	MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+	MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+	MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+	MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+	MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+	MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+	MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+	MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+	MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+	MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+	MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+	MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+	MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+	MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+	MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+	MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+	MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+	MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+	MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+	MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+	MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+	MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+	MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+	MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+	MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+	MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+	MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+	MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+	MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+	MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+	MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+	MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+	MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+	MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+	MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+	MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+	MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+	MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+	MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+	MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+	MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+	MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+	MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+	MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+	MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+	MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+	MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+	MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+	MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+	MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+	MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+	MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+	MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+	MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+	MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+	MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+	MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+	MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+	MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+	MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+	MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+	MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+	MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+	MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+	buf[0] += a;
+	buf[1] += b;
+	buf[2] += c;
+	buf[3] += d;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len)
+{
+	uint32_t t;
+
+	/* Update bitcount */
+
+	t = ctx->bits[0];
+	if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+		ctx->bits[1]++;		/* Carry from low to high */
+	ctx->bits[1] += len >> 29;
+
+	t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */
+
+	/* Handle any leading odd-sized chunks */
+
+	if (t) {
+		unsigned char *p = (unsigned char *) ctx->in + t;
+
+		t = 64 - t;
+		if (len < t) {
+			memcpy(p, buf, len);
+			return;
+		}
+		memcpy(p, buf, t);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+		buf += t;
+		len -= t;
+	}
+	/* Process data in 64-byte chunks */
+
+	while (len >= 64) {
+		memcpy(ctx->in, buf, 64);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+		buf += 64;
+		len -= 64;
+	}
+
+	/* Handle any remaining bytes of data. */
+
+	memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern 
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[16], MD5_CTX *ctx)
+{
+	unsigned count;
+	unsigned char *p;
+
+	/* Compute number of bytes mod 64 */
+	count = (ctx->bits[0] >> 3) & 0x3F;
+
+	/* Set the first char of padding to 0x80.  This is safe since there is
+	   always at least one byte free */
+	p = ctx->in + count;
+	*p++ = 0x80;
+
+	/* Bytes of padding needed to make 64 bytes */
+	count = 64 - 1 - count;
+
+	/* Pad out to 56 mod 64 */
+	if (count < 8) {
+		/* Two lots of padding:  Pad the first block to 64 bytes */
+		memset(p, 0, count);
+		byteReverse(ctx->in, 16);
+		MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+
+		/* Now fill the next block with 56 bytes */
+		memset(ctx->in, 0, 56);
+	} else {
+		/* Pad block to 56 bytes */
+		memset(p, 0, count - 8);
+	}
+	byteReverse(ctx->in, 14);
+
+	/* Append length in bits and transform */
+	((uint32_t *) ctx->in)[14] = ctx->bits[0];
+	((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+	MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+	byteReverse((unsigned char *) ctx->buf, 4);
+	memcpy(digest, ctx->buf, 16);
+	memset((char *) ctx, 0, sizeof(ctx));	/* In case it's sensitive */
+}
+
+#endif /* !HAVE_MD5 */
diff --git a/vendor/shttpd/md5.h b/vendor/shttpd/md5.h
new file mode 100644
index 0000000..fcca00e
--- /dev/null
+++ b/vendor/shttpd/md5.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.
+ */
+
+#ifndef MD5_HEADER_INCLUDED
+#define	MD5_HEADER_INCLUDED
+
+typedef struct MD5Context {
+	uint32_t	buf[4];
+	uint32_t	bits[2];
+	unsigned char	in[64];
+} MD5_CTX;
+
+extern void MD5Init(MD5_CTX *ctx);
+extern void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len);
+extern void MD5Final(unsigned char digest[16], MD5_CTX *ctx);
+
+#endif /*MD5_HEADER_INCLUDED */
diff --git a/vendor/shttpd/shttpd.c b/vendor/shttpd/shttpd.c
new file mode 100644
index 0000000..8b66243
--- /dev/null
+++ b/vendor/shttpd/shttpd.c
@@ -0,0 +1,1903 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka 
+ * 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.57 2008/08/23 21:00:38 drozd Exp $
+ */
+
+#include "defs.h"
+
+time_t	_shttpd_current_time;	/* Current UTC time		*/
+int	_shttpd_tz_offset;	/* Time zone offset from UTC	*/
+int	_shttpd_exit_flag;	/* Program exit flag		*/
+
+const struct vec _shttpd_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[]);
+static void process_connection(struct conn *, int, int);
+
+int
+_shttpd_is_true(const char *str)
+{
+	static const char	*trues[] = {"1", "yes", "true", "jawohl", NULL};
+	const char		**p;
+
+	for (p = trues; *p != NULL; p++)
+		if (str && !strcmp(str, *p))
+			return (TRUE);
+
+	return (FALSE);
+}
+
+static void
+free_list(struct llhead *head, void (*dtor)(struct llhead *))
+{
+	struct llhead	*lp, *tmp;
+
+	LL_FOREACH_SAFE(head, lp, tmp) {
+		LL_DEL(lp);
+		dtor(lp);
+	}
+}
+
+static void
+listener_destructor(struct llhead *lp)
+{
+	struct listener	*listener = LL_ENTRY(lp, struct listener, link);
+
+	(void) closesocket(listener->sock);
+	free(listener);
+}
+
+static void
+registered_uri_destructor(struct llhead *lp)
+{
+	struct registered_uri *ruri = LL_ENTRY(lp, struct registered_uri, link);
+
+	free((void *) ruri->uri);
+	free(ruri);
+}
+
+static void
+acl_destructor(struct llhead *lp)
+{
+	struct acl	*acl = LL_ENTRY(lp, struct acl, link);
+	free(acl);
+}
+
+int
+_shttpd_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;
+		default:
+			dst[j] = src[i];
+			break;
+		}
+
+	dst[j] = '\0';	/* Null-terminate the destination */
+
+	return (j);
+}
+
+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->options[OPT_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 || p >= s + len || p == s)
+			continue;
+
+		if (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
+_shttpd_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, (int) io_data_len(&stream->io)));
+}
+
+/*
+ * Setup listening socket on given port, return socket
+ */
+static int
+shttpd_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 (_shttpd_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);
+	_shttpd_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
+_shttpd_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
+_shttpd_send_server_error(struct conn *c, int status, const char *reason)
+{
+	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);
+			_shttpd_setup_embedded_stream(c,
+			    e->callback, e->callback_data);
+			return;
+		}
+	}
+
+	io_clear(&c->loc.io);
+	c->loc.io.head = _shttpd_snprintf(c->loc.io.buf, c->loc.io.size,
+	    "HTTP/1.1 %d %s\r\n"
+	    "Content-Type: text/plain\r\n"
+	    "Content-Length: 12\r\n"
+	    "\r\n"
+	    "Error: %03d\r\n",
+	    status, reason, status);
+	c->loc.content_len = 10;
+	c->status = status;
+	_shttpd_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(&_shttpd_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] == '/' || s[-1] == '\\')
+			while (*s == '.' || *s == '/' || *s == '\\')
+				s++;
+	}
+	*p = '\0';
+}
+
+void
+_shttpd_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 &&
+			    !_shttpd_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 */
+	}
+}
+
+static const struct {
+	const char	*extension;
+	int		ext_len;
+	const char	*mime_type;
+} builtin_mime_types[] = {
+	{"html",	4,	"text/html"			},
+	{"htm",		3,	"text/html"			},
+	{"txt",		3,	"text/plain"			},
+	{"css",		3,	"text/css"			},
+	{"ico",		3,	"image/x-icon"			},
+	{"gif",		3,	"image/gif"			},
+	{"jpg",		3,	"image/jpeg"			},
+	{"jpeg",	4,	"image/jpeg"			},
+	{"png",		3,	"image/png"			},
+	{"svg",		3,	"image/svg+xml"			},
+	{"torrent",	7,	"application/x-bittorrent"	},
+	{"wav",		3,	"audio/x-wav"			},
+	{"mp3",		3,	"audio/x-mp3"			},
+	{"mid",		3,	"audio/mid"			},
+	{"m3u",		3,	"audio/x-mpegurl"		},
+	{"ram",		3,	"audio/x-pn-realaudio"		},
+	{"ra",		2,	"audio/x-pn-realaudio"		},
+	{"doc",		3,	"application/msword",		},
+	{"exe",		3,	"application/octet-stream"	},
+	{"zip",		3,	"application/x-zip-compressed"	},
+	{"xls",		3,	"application/excel"		},
+	{"tgz",		3,	"application/x-tar-gz"		},
+	{"tar.gz",	6,	"application/x-tar-gz"		},
+	{"tar",		3,	"application/x-tar"		},
+	{"gz",		2,	"application/x-gunzip"		},
+	{"arj",		3,	"application/x-arj-compressed"	},
+	{"rar",		3,	"application/x-arj-compressed"	},
+	{"rtf",		3,	"application/rtf"		},
+	{"pdf",		3,	"application/pdf"		},
+	{"swf",		3,	"application/x-shockwave-flash"	},
+	{"mpg",		3,	"video/mpeg"			},
+	{"mpeg",	4,	"video/mpeg"			},
+	{"asf",		3,	"video/x-ms-asf"		},
+	{"avi",		3,	"video/x-msvideo"		},
+	{"bmp",		3,	"image/bmp"			},
+	{NULL,		0,	NULL				}
+};
+
+void
+_shttpd_get_mime_type(struct shttpd_ctx *ctx,
+		const char *uri, int len, struct vec *vec)
+{
+	const char	*eq, *p = ctx->options[OPT_MIME_TYPES];
+	int		i, n, ext_len;
+
+	/* Firt, loop through the custom mime types if any */
+	FOR_EACH_WORD_IN_LIST(p, n) {
+		if ((eq = memchr(p, '=', n)) == NULL || eq >= p + n || eq == p)
+			continue;
+		ext_len = eq - p;
+		if (len > ext_len && uri[len - ext_len - 1] == '.' &&
+		    !_shttpd_strncasecmp(p, &uri[len - ext_len], ext_len)) {
+			vec->ptr = eq + 1;
+			vec->len = p + n - vec->ptr;
+			return;
+		}
+	}
+
+	/* If no luck, try built-in mime types */
+	for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
+		ext_len = builtin_mime_types[i].ext_len;
+		if (len > ext_len && uri[len - ext_len - 1] == '.' &&
+		    !_shttpd_strncasecmp(builtin_mime_types[i].extension,
+			    &uri[len - ext_len], ext_len)) {
+			vec->ptr = builtin_mime_types[i].mime_type;
+			vec->len = strlen(vec->ptr);
+			return;
+		}
+	}
+
+	/* Oops. This extension is unknown to us. Fallback to text/plain */
+	vec->ptr = "text/plain";
+	vec->len = strlen(vec->ptr);
+}
+
+/*
+ * 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->options[OPT_INDEX_FILES];
+	size_t		len;
+
+	FOR_EACH_WORD_IN_LIST(s, len) {
+		/* path must end with '/' character */
+		_shttpd_snprintf(buf, sizeof(buf), "%s%.*s", path, len, s);
+		if (_shttpd_stat(buf, stp) == 0) {
+			_shttpd_strlcpy(path, buf, maxpath);
+			_shttpd_get_mime_type(c->ctx, s, len, &c->mime_type);
+			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 (_shttpd_stat(path, stp) == 0)
+		return (0);
+
+	p = path + strlen(path);
+	e = path + strlen(c->ctx->options[OPT_ROOT]) + 2;
+	
+	/* Strip directory parts of the path one by one */
+	for (; p > e; p--)
+		if (*p == '/') {
+			*p = '\0';
+			if (!_shttpd_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], *root;
+	struct vec	alias_uri, alias_path;
+	struct stat	st;
+	int		rc;
+	struct registered_uri	*ruri;
+
+	DBG(("decide_what_to_do: [%s]", c->uri));
+
+	if ((c->query = strchr(c->uri, '?')) != NULL)
+		*c->query++ = '\0';
+
+	_shttpd_url_decode(c->uri, strlen(c->uri), c->uri, strlen(c->uri) + 1);
+	remove_double_dots(c->uri);
+	
+	root = c->ctx->options[OPT_ROOT];
+	if (strlen(c->uri) + strlen(root) >= sizeof(path)) {
+		_shttpd_send_server_error(c, 400, "URI is too long");
+		return;
+	}
+
+	(void) _shttpd_snprintf(path, sizeof(path), "%s%s", 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) _shttpd_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 (_shttpd_check_authorization(c, path) != 1) {
+		_shttpd_send_authorization_request(c);
+	} else
+#endif /* NO_AUTH */
+	if ((ruri = _shttpd_is_registered_uri(c->ctx, c->uri)) != NULL) {
+		_shttpd_setup_embedded_stream(c,
+		    ruri->callback, ruri->callback_data);
+	} else
+	if (strstr(path, HTPASSWD)) {
+		/* Do not allow to view passwords files */
+		_shttpd_send_server_error(c, 403, "Forbidden");
+	} else
+#if !defined(NO_AUTH)
+	if ((c->method == METHOD_PUT || c->method == METHOD_DELETE) &&
+	    (c->ctx->options[OPT_AUTH_PUT] == NULL ||
+	     !_shttpd_is_authorized_for_put(c))) {
+		_shttpd_send_authorization_request(c);
+	} else
+#endif /* NO_AUTH */
+	if (c->method == METHOD_PUT) {
+		c->status = _shttpd_stat(path, &st) == 0 ? 200 : 201;
+
+		if (c->ch.range.v_vec.len > 0) {
+			_shttpd_send_server_error(c, 501,
+			    "PUT Range Not Implemented");
+		} else if ((rc = _shttpd_put_dir(path)) == 0) {
+			_shttpd_send_server_error(c, 200, "OK");
+		} else if (rc == -1) {
+			_shttpd_send_server_error(c, 500, "PUT Directory Error");
+		} else if (c->rem.content_len == 0) {
+			_shttpd_send_server_error(c, 411, "Length Required");
+		} else if ((c->loc.chan.fd = _shttpd_open(path, O_WRONLY | O_BINARY |
+		    O_CREAT | O_NONBLOCK | O_TRUNC, 0644)) == -1) {
+			_shttpd_send_server_error(c, 500, "PUT Error");
+		} else {
+			DBG(("PUT file [%s]", c->uri));
+			c->loc.io_class = &_shttpd_io_file;
+			c->loc.flags |= FLAG_W | FLAG_ALWAYS_READY ;
+		}
+	} else if (c->method == METHOD_DELETE) {
+		DBG(("DELETE [%s]", c->uri));
+		if (_shttpd_remove(path) == 0)
+			_shttpd_send_server_error(c, 200, "OK");
+		else
+			_shttpd_send_server_error(c, 500, "DELETE Error");
+	} else if (get_path_info(c, path, &st) != 0) {
+		_shttpd_send_server_error(c, 404, "Not Found");
+	} else if (S_ISDIR(st.st_mode) && path[strlen(path) - 1] != '/') {
+		(void) _shttpd_snprintf(buf, sizeof(buf),
+			"Moved Permanently\r\nLocation: %s/", c->uri);
+		_shttpd_send_server_error(c, 301, buf);
+	} else if (S_ISDIR(st.st_mode) &&
+	    find_index_file(c, path, sizeof(path) - 1, &st) == -1 &&
+	    !IS_TRUE(c->ctx, OPT_DIR_LIST)) {
+		_shttpd_send_server_error(c, 403, "Directory Listing Denied");
+	} else if (S_ISDIR(st.st_mode) && IS_TRUE(c->ctx, OPT_DIR_LIST)) {
+		if ((c->loc.chan.dir.path = _shttpd_strdup(path)) != NULL)
+			_shttpd_get_dir(c);
+		else
+			_shttpd_send_server_error(c, 500, "GET Directory Error");
+	} else if (S_ISDIR(st.st_mode) && !IS_TRUE(c->ctx, OPT_DIR_LIST)) {
+		_shttpd_send_server_error(c, 403, "Directory listing denied");
+#if !defined(NO_CGI)
+	} else if (_shttpd_match_extension(path,
+	    c->ctx->options[OPT_CGI_EXTENSIONS])) {
+		if (c->method != METHOD_POST && c->method != METHOD_GET) {
+			_shttpd_send_server_error(c, 501, "Bad method ");
+		} else if ((_shttpd_run_cgi(c, path)) == -1) {
+			_shttpd_send_server_error(c, 500, "Cannot exec CGI");
+		} else {
+			_shttpd_do_cgi(c);
+		}
+#endif /* NO_CGI */
+#if !defined(NO_SSI)
+	} else if (_shttpd_match_extension(path,
+	    c->ctx->options[OPT_SSI_EXTENSIONS])) {
+		if ((c->loc.chan.fd = _shttpd_open(path,
+		    O_RDONLY | O_BINARY, 0644)) == -1) {
+			_shttpd_send_server_error(c, 500, "SSI open error");
+		} else {
+			_shttpd_do_ssi(c);
+		}
+#endif /* NO_CGI */
+	} else if (c->ch.ims.v_time && st.st_mtime <= c->ch.ims.v_time) {
+		_shttpd_send_server_error(c, 304, "Not Modified");
+	} else if ((c->loc.chan.fd = _shttpd_open(path,
+	    O_RDONLY | O_BINARY, 0644)) != -1) {
+		_shttpd_get_file(c, &st);
+	} else {
+		_shttpd_send_server_error(c, 500, "Internal Error");
+	}
+}
+
+static int
+set_request_method(struct conn *c)
+{
+	const struct vec	*v;
+
+	/* Set the request method */
+	for (v = _shttpd_known_http_methods; v->ptr != NULL; v++)
+		if (!memcmp(c->rem.io.buf, v->ptr, v->len)) {
+			c->method = v - _shttpd_known_http_methods;
+			break;
+		}
+
+	return (v->ptr == NULL);
+}
+
+static void
+parse_http_request(struct conn *c)
+{
+	char	*s, *e, *p, *start;
+	int	uri_len, req_len, n;
+
+	s = io_data(&c->rem.io);;
+	req_len = c->rem.headers_len =
+	    _shttpd_get_headers_len(s, io_data_len(&c->rem.io));
+
+	if (req_len == 0 && io_space_len(&c->rem.io) == 0) {
+		io_clear(&c->rem.io);
+		_shttpd_send_server_error(c, 400, "Request is too big");
+	}
+
+	if (req_len == 0) {
+		return;
+	} else if (req_len < 16) {	/* Minimal: "GET / HTTP/1.0\n\n" */
+		_shttpd_send_server_error(c, 400, "Bad request");
+	} else if (set_request_method(c)) {
+		_shttpd_send_server_error(c, 501, "Method Not Implemented");
+	} else if ((c->request = _shttpd_strndup(s, req_len)) == NULL) {
+		_shttpd_send_server_error(c, 500, "Cannot allocate request");
+	}
+
+	if (c->loc.flags & FLAG_CLOSED)
+		return;
+
+	io_inc_tail(&c->rem.io, req_len);
+
+	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/. */
+	if (sscanf(p, "HTTP/%lu.%lu%n",
+	    &c->major_version, &c->minor_version, &n) != 2 || p[n] != '\0') {
+		_shttpd_send_server_error(c, 400, "Bad HTTP version");
+	} else if (c->major_version > 1 ||
+	    (c->major_version == 1 && c->minor_version > 1)) {
+		_shttpd_send_server_error(c, 505, "HTTP version not supported");
+	} else if (uri_len <= 0) {
+		_shttpd_send_server_error(c, 400, "Bad URI");
+	} else if ((c->uri = malloc(uri_len + 1)) == NULL) {
+		_shttpd_send_server_error(c, 500, "Cannot allocate URI");
+	} else {
+		_shttpd_strlcpy(c->uri, (char *) start, uri_len + 1);
+		_shttpd_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;
+		decide_what_to_do(c);
+	}
+}
+
+static void
+add_socket(struct worker *worker, int sock, int is_ssl)
+{
+	struct shttpd_ctx	*ctx = worker->ctx;
+	struct conn		*c;
+	struct usa		sa;
+	int			l = IS_TRUE(ctx, OPT_INETD) ? E_FATAL : E_LOG;
+#if !defined(NO_SSL)
+	SSL		*ssl = NULL;
+#else
+	is_ssl = is_ssl;	/* supress warnings */
+#endif /* NO_SSL */
+
+	sa.len = sizeof(sa.u.sin);
+	(void) _shttpd_set_non_blocking_mode(sock);
+
+	if (getpeername(sock, &sa.u.sa, &sa.len)) {
+		_shttpd_elog(l, NULL, "add_socket: %s", strerror(errno));
+#if !defined(NO_SSL)
+	} else if (is_ssl && (ssl = SSL_new(ctx->ssl_ctx)) == NULL) {
+		_shttpd_elog(l, NULL, "add_socket: SSL_new: %s", strerror(ERRNO));
+		(void) closesocket(sock);
+	} else if (is_ssl && SSL_set_fd(ssl, sock) == 0) {
+		_shttpd_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 * URI_MAX)) == NULL) {
+#if !defined(NO_SSL)
+		if (ssl)
+			SSL_free(ssl);
+#endif /* NO_SSL */
+		(void) closesocket(sock);
+		_shttpd_elog(l, NULL, "add_socket: calloc: %s", strerror(ERRNO));
+	} else {
+		c->rem.conn	= c->loc.conn = c;
+		c->ctx		= ctx;
+		c->worker	= worker;
+		c->sa		= sa;
+		c->birth_time	= _shttpd_current_time;
+		c->expire_time	= _shttpd_current_time + EXPIRE_TIME;
+
+		(void) getsockname(sock, &sa.u.sa, &sa.len);
+		c->loc_port = sa.u.sin.sin_port;
+
+		_shttpd_set_close_on_exec(sock);
+
+		c->loc.io_class	= NULL;
+	
+		c->rem.io_class	= &_shttpd_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 + URI_MAX;
+		c->loc.io.size	= c->rem.io.size = URI_MAX;
+
+#if !defined(NO_SSL)
+		if (is_ssl) {
+			c->rem.io_class	= &_shttpd_io_ssl;
+			c->rem.chan.ssl.sock = sock;
+			c->rem.chan.ssl.ssl = ssl;
+			_shttpd_ssl_handshake(&c->rem);
+		}
+#endif /* NO_SSL */
+
+		LL_TAIL(&worker->connections, &c->link);
+		worker->num_conns++;
+		
+		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));
+	}
+}
+
+static struct worker *
+first_worker(struct shttpd_ctx *ctx)
+{
+	return (LL_ENTRY(ctx->workers.next, struct worker, link));
+}
+
+static void
+pass_socket(struct shttpd_ctx *ctx, int sock, int is_ssl)
+{
+	struct llhead	*lp;
+	struct worker	*worker, *lazy;
+	int		buf[3];
+
+	lazy = first_worker(ctx);
+
+	/* Find least busy worker */
+	LL_FOREACH(&ctx->workers, lp) {
+		worker = LL_ENTRY(lp, struct worker, link);
+		if (worker->num_conns < lazy->num_conns)
+			lazy = worker;
+	}
+
+	buf[0] = CTL_PASS_SOCKET;
+	buf[1] = sock;
+	buf[2] = is_ssl;
+
+	(void) send(lazy->ctl[1], (void *) buf, sizeof(buf), 0);
+}
+
+static int
+set_ports(struct shttpd_ctx *ctx, const char *p)
+{
+	int		sock, len, is_ssl, port;
+	struct listener	*l;
+
+
+	free_list(&ctx->listeners, &listener_destructor);
+
+	FOR_EACH_WORD_IN_LIST(p, len) {
+
+		is_ssl	= p[len - 1] == 's' ? 1 : 0;
+		port	= atoi(p);
+
+		if ((sock = shttpd_open_listening_port(port)) == -1) {
+			_shttpd_elog(E_LOG, NULL, "cannot open port %d", port);
+			goto fail;
+		} else if (is_ssl && ctx->ssl_ctx == NULL) {
+			(void) closesocket(sock);
+			_shttpd_elog(E_LOG, NULL, "cannot add SSL socket, "
+			    "please specify certificate file");
+			goto fail;
+		} else if ((l = calloc(1, sizeof(*l))) == NULL) {
+			(void) closesocket(sock);
+			_shttpd_elog(E_LOG, NULL, "cannot allocate listener");
+			goto fail;
+		} else {
+			l->is_ssl = is_ssl;
+			l->sock	= sock;
+			l->ctx	= ctx;
+			LL_TAIL(&ctx->listeners, &l->link);
+			DBG(("shttpd_listen: added socket %d", sock));
+		}
+	}
+
+	return (TRUE);
+fail:
+	free_list(&ctx->listeners, &listener_destructor);
+	return (FALSE);
+}
+
+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 */
+	assert(stream->io_class != NULL);
+	n = 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))
+		_shttpd_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)
+			_shttpd_stop_stream(stream);
+	}
+
+	stream->conn->expire_time = _shttpd_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 = _shttpd_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))
+		_shttpd_stop_stream(to);
+}
+
+
+static void
+connection_desctructor(struct llhead *lp)
+{
+	struct conn		*c = LL_ENTRY(lp, struct conn, link);
+	static const struct vec	vec = {"close", 5};
+	int			do_close;
+
+	DBG(("Disconnecting %d (%.*s)", c->rem.chan.sock,
+	    c->ch.connection.v_vec.len, c->ch.connection.v_vec.ptr));
+
+	if (c->request != NULL && c->ctx->access_log != NULL)
+		_shttpd_log_access(c->ctx->access_log, c);
+
+	/* In inetd mode, exit if request is finished. */
+	if (IS_TRUE(c->ctx, OPT_INETD))
+		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
+	 */
+	do_close = (c->ch.connection.v_vec.len >= vec.len &&
+	    !_shttpd_strncasecmp(vec.ptr,c->ch.connection.v_vec.ptr,vec.len)) ||
+	    (c->major_version < 1 ||
+	    (c->major_version >= 1 && c->minor_version < 1));
+
+	if (c->request)
+		free(c->request);
+	if (c->uri)
+		free(c->uri);
+
+	/* Keep the connection open only if we have Content-Length set */
+	if (!do_close && c->loc.content_len > 0) {
+		c->loc.io_class = NULL;
+		c->loc.flags = 0;
+		c->loc.content_len = 0;
+		c->rem.flags = FLAG_W | FLAG_R | FLAG_SSL_ACCEPTED;
+		c->query = c->request = c->uri = c->path_info = NULL;
+		c->mime_type.len = 0;
+		(void) memset(&c->ch, 0, sizeof(c->ch));
+		io_clear(&c->loc.io);
+		c->birth_time = _shttpd_current_time;
+		if (io_data_len(&c->rem.io) > 0)
+			process_connection(c, 0, 0);
+	} else {
+		if (c->rem.io_class != NULL)
+			c->rem.io_class->close(&c->rem);
+
+		LL_DEL(&c->link);
+		c->worker->num_conns--;
+		assert(c->worker->num_conns >= 0);
+
+		free(c);
+	}
+}
+
+static void
+worker_destructor(struct llhead *lp)
+{
+	struct worker	*worker = LL_ENTRY(lp, struct worker, link);
+
+	free_list(&worker->connections, connection_desctructor);
+	free(worker);
+}
+
+static int
+is_allowed(const struct shttpd_ctx *ctx, const struct usa *usa)
+{
+	const struct acl	*acl;
+	const struct llhead	*lp;
+	int			allowed = '+';
+	uint32_t		ip;
+
+	LL_FOREACH(&ctx->acl, lp) {
+		acl = LL_ENTRY(lp, struct acl, link);
+		(void) memcpy(&ip, &usa->u.sin.sin_addr, sizeof(ip));
+		if (acl->ip == (ntohl(ip) & acl->mask))
+			allowed = acl->flag;
+	}
+
+	return (allowed == '+');
+}
+
+static void
+add_to_set(int fd, fd_set *set, int *max_fd)
+{
+	FD_SET(fd, set);
+	if (fd > *max_fd)
+		*max_fd = fd;
+}
+
+static void
+process_connection(struct conn *c, int remote_ready, int local_ready)
+{
+	/* Read from remote end if it is ready */
+	if (remote_ready && 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: %d [%.*s]", (int) io_data_len(&c->loc.io),
+	    (int) io_data_len(&c->loc.io), io_data(&c->loc.io)));
+	DBG(("rem: %d [%.*s]", (int) io_data_len(&c->rem.io),
+	    (int) io_data_len(&c->rem.io), io_data(&c->rem.io)));
+
+	/* Read from the local end if it is ready */
+	if (local_ready && io_space_len(&c->loc.io))
+		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); 
+
+	/* Check whether we should close this connection */
+	if ((_shttpd_current_time > c->expire_time) ||
+	    (c->rem.flags & FLAG_CLOSED) ||
+	    ((c->loc.flags & FLAG_CLOSED) && !io_data_len(&c->loc.io)))
+		connection_desctructor(&c->link);
+}
+
+static int
+num_workers(const struct shttpd_ctx *ctx)
+{
+	char	*p = ctx->options[OPT_THREADS];
+	return (p ? atoi(p) : 1);
+}
+
+static void
+handle_connected_socket(struct shttpd_ctx *ctx,
+		struct usa *sap, int sock, int is_ssl)
+{
+#if !defined(_WIN32)
+	if (sock >= (int) FD_SETSIZE) {
+		_shttpd_elog(E_LOG, NULL, "ctx %p: discarding "
+		    "socket %d, too busy", ctx, sock);
+		(void) closesocket(sock);
+	} else
+#endif /* !_WIN32 */
+		if (!is_allowed(ctx, sap)) {
+		_shttpd_elog(E_LOG, NULL, "%s is not allowed to connect",
+		    inet_ntoa(sap->u.sin.sin_addr));
+		(void) closesocket(sock);
+	} else if (num_workers(ctx) > 1) {
+		pass_socket(ctx, sock, is_ssl);
+	} else {
+		add_socket(first_worker(ctx), sock, is_ssl);
+	}
+}
+
+static int
+do_select(int max_fd, fd_set *read_set, fd_set *write_set, int milliseconds)
+{
+	struct timeval	tv;
+	int		n;
+
+	tv.tv_sec = milliseconds / 1000;
+	tv.tv_usec = (milliseconds % 1000) * 1000;
+
+	/* Check IO readiness */
+	if ((n = 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 (n);
+}
+
+static int
+multiplex_worker_sockets(const struct worker *worker, int *max_fd,
+		fd_set *read_set, fd_set *write_set)
+{
+	struct llhead	*lp;
+	struct conn	*c;
+	int		nowait = FALSE;
+
+	/* Add control socket */
+	add_to_set(worker->ctl[0], read_set, max_fd);
+
+	/* Multiplex streams */
+	LL_FOREACH(&worker->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 == &_shttpd_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 == &_shttpd_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) && !(c->loc.flags & FLAG_SUSPEND))
+			add_to_set(c->rem.chan.fd, write_set, max_fd);
+
+		/*
+		 * Set select wait interval to zero if FLAG_ALWAYS_READY set
+		 */
+		if (io_space_len(&c->loc.io) && (c->loc.flags & FLAG_R) &&
+		    (c->loc.flags & FLAG_ALWAYS_READY))
+			nowait = TRUE;
+		
+		if (io_data_len(&c->rem.io) && (c->loc.flags & FLAG_W) &&
+		    (c->loc.flags & FLAG_ALWAYS_READY))
+			nowait = TRUE;
+	}
+
+	return (nowait);
+}
+
+int
+shttpd_join(struct shttpd_ctx *ctx,
+		fd_set *read_set, fd_set *write_set, int *max_fd)
+{
+	struct llhead	*lp;
+	struct listener	*l;
+	int		nowait = FALSE;
+
+	/* Add listening sockets to the read set */
+	LL_FOREACH(&ctx->listeners, lp) {
+		l = LL_ENTRY(lp, struct listener, link);
+		add_to_set(l->sock, read_set, max_fd);
+		DBG(("FD_SET(%d) (listening)", l->sock));
+	}
+
+	if (num_workers(ctx) == 1)
+		nowait = multiplex_worker_sockets(first_worker(ctx), max_fd,
+		    read_set, write_set);
+
+	return (nowait);
+}
+
+
+static void
+process_worker_sockets(struct worker *worker, fd_set *read_set)
+{
+	struct llhead	*lp, *tmp;
+	int		cmd, skt[2], sock = worker->ctl[0];
+	struct conn	*c;
+
+	/* Check if new socket is passed to us over the control socket */
+	if (FD_ISSET(worker->ctl[0], read_set))
+		while (recv(sock, (void *) &cmd, sizeof(cmd), 0) == sizeof(cmd))
+			switch (cmd) {
+			case CTL_PASS_SOCKET:
+				(void)recv(sock, (void *) &skt, sizeof(skt), 0);
+				add_socket(worker, skt[0], skt[1]);
+				break;
+			case CTL_WAKEUP:
+				(void)recv(sock, (void *) &c, sizeof(c), 0);
+				c->loc.flags &= FLAG_SUSPEND;
+				break;
+			default:
+				_shttpd_elog(E_FATAL, NULL, "ctx %p: ctl cmd %d",
+				    worker->ctx, cmd);
+				break;
+			}
+
+	/* Process all connections */
+	LL_FOREACH_SAFE(&worker->connections, lp, tmp) {
+		c = LL_ENTRY(lp, struct conn, link);
+		process_connection(c, FD_ISSET(c->rem.chan.sock, read_set),
+		    c->loc.io_class != NULL &&
+		    ((c->loc.flags & FLAG_ALWAYS_READY)
+#if !defined(NO_CGI)
+		    || (c->loc.io_class == &_shttpd_io_cgi &&
+		     FD_ISSET(c->loc.chan.fd, read_set))
+#endif /* NO_CGI */
+		    ));
+	}
+}
+
+/*
+ * 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;
+	struct listener	*l;
+	fd_set		read_set, write_set;
+	int		sock, max_fd = -1;
+	struct usa	sa;
+
+	_shttpd_current_time = time(0);
+	FD_ZERO(&read_set);
+	FD_ZERO(&write_set);
+
+	if (shttpd_join(ctx, &read_set, &write_set, &max_fd))
+		milliseconds = 0;
+
+	if (do_select(max_fd, &read_set, &write_set, milliseconds) < 0)
+		return;;
+
+	/* Check for incoming connections on listener sockets */
+	LL_FOREACH(&ctx->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)
+				handle_connected_socket(ctx,&sa,sock,l->is_ssl);
+		} while (sock != -1);
+	}
+
+	if (num_workers(ctx) == 1)
+		process_worker_sockets(first_worker(ctx), &read_set);
+}
+
+/*
+ * Deallocate shttpd object, free up the resources
+ */
+void
+shttpd_fini(struct shttpd_ctx *ctx)
+{
+	size_t	i;
+
+	free_list(&ctx->workers, worker_destructor);
+	free_list(&ctx->registered_uris, registered_uri_destructor);
+	free_list(&ctx->acl, acl_destructor);
+	free_list(&ctx->listeners, listener_destructor);
+#if !defined(NO_SSI)
+	free_list(&ctx->ssi_funcs, _shttpd_ssi_func_destructor);
+#endif /* !NO_SSI */
+
+	for (i = 0; i < NELEMS(ctx->options); i++)
+		if (ctx->options[i] != NULL)
+			free(ctx->options[i]);
+
+	if (ctx->access_log)		(void) fclose(ctx->access_log);
+	if (ctx->error_log)		(void) fclose(ctx->error_log);
+
+	/* TODO: free SSL context */
+
+	free(ctx);
+}
+
+/*
+ * UNIX socketpair() implementation. Why? Because Windows does not have it.
+ * Return 0 on success, -1 on error.
+ */
+int
+shttpd_socketpair(int sp[2])
+{
+	struct sockaddr_in	sa;
+	int			sock, ret = -1;
+	socklen_t		len = sizeof(sa);
+
+	sp[0] = sp[1] = -1;
+
+	(void) memset(&sa, 0, sizeof(sa));
+	sa.sin_family 		= AF_INET;
+	sa.sin_port		= htons(0);
+	sa.sin_addr.s_addr	= htonl(INADDR_LOOPBACK);
+
+	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) != -1 &&
+	    !bind(sock, (struct sockaddr *) &sa, len) &&
+	    !listen(sock, 1) &&
+	    !getsockname(sock, (struct sockaddr *) &sa, &len) &&
+	    (sp[0] = socket(AF_INET, SOCK_STREAM, 6)) != -1 &&
+	    !connect(sp[0], (struct sockaddr *) &sa, len) &&
+	    (sp[1] = accept(sock,(struct sockaddr *) &sa, &len)) != -1) {
+
+		/* Success */
+		ret = 0;
+	} else {
+
+		/* Failure, close descriptors */
+		if (sp[0] != -1)
+			(void) closesocket(sp[0]);
+		if (sp[1] != -1)
+			(void) closesocket(sp[1]);
+	}
+
+	(void) closesocket(sock);
+	(void) _shttpd_set_non_blocking_mode(sp[0]);
+	(void) _shttpd_set_non_blocking_mode(sp[1]);
+
+#ifndef _WIN32
+	(void) fcntl(sp[0], F_SETFD, FD_CLOEXEC);
+	(void) fcntl(sp[1], F_SETFD, FD_CLOEXEC);
+#endif /* _WIN32*/
+
+	return (ret);
+}
+
+static int isbyte(int n) { return (n >= 0 && n <= 255); }
+
+static int
+set_inetd(struct shttpd_ctx *ctx, const char *flag)
+{
+	ctx = NULL; /* Unused */
+
+	if (_shttpd_is_true(flag)) {
+		shttpd_set_option(ctx, "ports", NULL);
+		(void) freopen("/dev/null", "a", stderr);
+		add_socket(first_worker(ctx), 0, 0);
+	}
+
+	return (TRUE);
+}
+
+static int
+set_uid(struct shttpd_ctx *ctx, const char *uid)
+{
+	struct passwd	*pw;
+
+	ctx = NULL; /* Unused */
+
+#if !defined(_WIN32)
+	if ((pw = getpwnam(uid)) == NULL)
+		_shttpd_elog(E_FATAL, 0, "%s: unknown user [%s]", __func__, uid);
+	else if (setgid(pw->pw_gid) == -1)
+		_shttpd_elog(E_FATAL, NULL, "%s: setgid(%s): %s",
+		    __func__, uid, strerror(errno));
+	else if (setuid(pw->pw_uid) == -1)
+		_shttpd_elog(E_FATAL, NULL, "%s: setuid(%s): %s",
+		    __func__, uid, strerror(errno));
+#endif /* !_WIN32 */
+	return (TRUE);
+}
+
+static int
+set_acl(struct shttpd_ctx *ctx, const char *s)
+{
+	struct acl	*acl = NULL;
+	char		flag;
+	int		len, a, b, c, d, n, mask;
+
+	/* Delete the old ACLs if any */
+	free_list(&ctx->acl, acl_destructor);
+
+	FOR_EACH_WORD_IN_LIST(s, len) {
+
+		mask = 32;
+
+		if (sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5) {
+			_shttpd_elog(E_FATAL, NULL, "[%s]: subnet must be "
+			    "[+|-]x.x.x.x[/x]", s);
+		} else if (flag != '+' && flag != '-') {
+			_shttpd_elog(E_FATAL, NULL, "flag must be + or -: [%s]", s);
+		} else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
+			_shttpd_elog(E_FATAL, NULL, "bad ip address: [%s]", s);
+		} else	if ((acl = malloc(sizeof(*acl))) == NULL) {
+			_shttpd_elog(E_FATAL, NULL, "%s", "cannot malloc subnet");
+		} else if (sscanf(s + n, "/%d", &mask) == 0) { 
+			/* Do nothing, no mask specified */
+		} else if (mask < 0 || mask > 32) {
+			_shttpd_elog(E_FATAL, NULL, "bad subnet mask: %d [%s]", n, s);
+		}
+
+		acl->ip = (a << 24) | (b << 16) | (c << 8) | d;
+		acl->mask = mask ? 0xffffffffU << (32 - mask) : 0;
+		acl->flag = flag;
+		LL_TAIL(&ctx->acl, &acl->link);
+	}
+
+	return (TRUE);
+}
+
+#ifndef NO_SSL
+/*
+ * Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
+ */
+static int
+set_ssl(struct shttpd_ctx *ctx, const char *pem)
+{
+	SSL_CTX		*CTX;
+	void		*lib;
+	struct ssl_func	*fp;
+	int		retval = FALSE;
+
+	/* Load SSL library dynamically */
+	if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL) {
+		_shttpd_elog(E_LOG, NULL, "set_ssl: cannot load %s", SSL_LIB);
+		return (FALSE);
+	}
+
+	for (fp = ssl_sw; fp->name != NULL; fp++)
+		if ((fp->ptr.v_void = dlsym(lib, fp->name)) == NULL) {
+			_shttpd_elog(E_LOG, NULL,"set_ssl: cannot find %s", fp->name);
+			return (FALSE);
+		}
+
+	/* Initialize SSL crap */
+	SSL_library_init();
+
+	if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL)
+		_shttpd_elog(E_LOG, NULL, "SSL_CTX_new error");
+	else if (SSL_CTX_use_certificate_file(CTX, pem, SSL_FILETYPE_PEM) == 0)
+		_shttpd_elog(E_LOG, NULL, "cannot open %s", pem);
+	else if (SSL_CTX_use_PrivateKey_file(CTX, pem, SSL_FILETYPE_PEM) == 0)
+		_shttpd_elog(E_LOG, NULL, "cannot open %s", pem);
+	else
+		retval = TRUE;
+
+	ctx->ssl_ctx = CTX;
+
+	return (retval);
+}
+#endif /* NO_SSL */
+
+static int
+open_log_file(FILE **fpp, const char *path)
+{
+	int	retval = TRUE;
+
+	if (*fpp != NULL)
+		(void) fclose(*fpp);
+
+	if (path == NULL) {
+		*fpp = NULL;
+	} else if ((*fpp = fopen(path, "a")) == NULL) {
+		_shttpd_elog(E_LOG, NULL, "cannot open log file %s: %s",
+		    path, strerror(errno));
+		retval = FALSE;
+	}
+
+	return (retval);
+}
+
+static int set_alog(struct shttpd_ctx *ctx, const char *path) {
+	return (open_log_file(&ctx->access_log, path));
+}
+
+static int set_elog(struct shttpd_ctx *ctx, const char *path) {
+	return (open_log_file(&ctx->error_log, path));
+}
+
+static void show_cfg_page(struct shttpd_arg *arg);
+
+static int
+set_cfg_uri(struct shttpd_ctx *ctx, const char *uri)
+{
+	free_list(&ctx->registered_uris, ®istered_uri_destructor);
+
+	if (uri != NULL)
+		shttpd_register_uri(ctx, uri, &show_cfg_page, ctx);
+
+	return (TRUE);
+}
+
+static struct worker *
+add_worker(struct shttpd_ctx *ctx)
+{
+	struct worker	*worker;
+
+	if ((worker = calloc(1, sizeof(*worker))) == NULL)
+		_shttpd_elog(E_FATAL, NULL, "Cannot allocate worker");
+	LL_INIT(&worker->connections);
+	worker->ctx = ctx;
+	(void) shttpd_socketpair(worker->ctl);
+	LL_TAIL(&ctx->workers, &worker->link);
+
+	return (worker);
+}
+
+#if !defined(NO_THREADS)
+static void
+poll_worker(struct worker *worker, int milliseconds)
+{
+	fd_set		read_set, write_set;
+	int		max_fd = -1;
+
+	FD_ZERO(&read_set);
+	FD_ZERO(&write_set);
+
+	if (multiplex_worker_sockets(worker, &max_fd, &read_set, &write_set))
+		milliseconds = 0;
+
+	if (do_select(max_fd, &read_set, &write_set, milliseconds) < 0)
+		return;;
+
+	process_worker_sockets(worker, &read_set);
+}
+
+static void
+worker_function(void *param)
+{
+	struct worker *worker = param;
+
+	while (worker->exit_flag == 0)
+		poll_worker(worker, 1000 * 10);
+
+	free_list(&worker->connections, connection_desctructor);
+	free(worker);
+}
+
+static int
+set_workers(struct shttpd_ctx *ctx, const char *value)
+{
+	int		new_num, old_num;
+	struct llhead	*lp, *tmp;
+	struct worker	*worker;
+
+       	new_num = atoi(value);
+	old_num = 0;
+	LL_FOREACH(&ctx->workers, lp)
+		old_num++;
+
+	if (new_num == 1) {
+		if (old_num > 1)
+			/* Stop old threads */
+			LL_FOREACH_SAFE(&ctx->workers, lp, tmp) {
+				worker = LL_ENTRY(lp, struct worker, link);
+				LL_DEL(&worker->link);
+				worker = LL_ENTRY(lp, struct worker, link);
+				worker->exit_flag = 1;
+			}
+		(void) add_worker(ctx);
+	} else {
+		/* FIXME: we cannot here reduce the number of threads */
+		while (new_num > 1 && new_num > old_num) {
+			worker = add_worker(ctx);
+			_beginthread(worker_function, 0, worker);
+			old_num++;
+		}
+	}
+
+	return (TRUE);
+}
+#endif /* NO_THREADS */
+
+static const struct opt {
+	int		index;		/* Index in shttpd_ctx		*/
+	const char	*name;		/* Option name in config file	*/
+	const char	*description;	/* Description			*/
+	const char	*default_value;	/* Default option value		*/
+	int (*setter)(struct shttpd_ctx *, const char *);
+} known_options[] = {
+	{OPT_ROOT, "root", "\tWeb root directory", ".", NULL},
+	{OPT_INDEX_FILES, "index_files", "Index files", INDEX_FILES, NULL},
+#ifndef NO_SSL
+	{OPT_SSL_CERTIFICATE, "ssl_cert", "SSL certificate file", NULL,set_ssl},
+#endif /* NO_SSL */
+	{OPT_PORTS, "ports", "Listening ports", LISTENING_PORTS, set_ports},
+	{OPT_DIR_LIST, "dir_list", "Directory listing", "yes", NULL},
+	{OPT_CFG_URI, "cfg_uri", "Config uri", NULL, set_cfg_uri},
+	{OPT_PROTECT, "protect", "URI to htpasswd mapping", NULL, NULL},
+#ifndef NO_CGI
+	{OPT_CGI_EXTENSIONS, "cgi_ext", "CGI extensions", CGI_EXT, NULL},
+	{OPT_CGI_INTERPRETER, "cgi_interp", "CGI interpreter", NULL, NULL},
+	{OPT_CGI_ENVIRONMENT, "cgi_env", "Additional CGI env vars", NULL, NULL},
+#endif /* NO_CGI */
+	{OPT_SSI_EXTENSIONS, "ssi_ext",	"SSI extensions", SSI_EXT, NULL},
+#ifndef NO_AUTH
+	{OPT_AUTH_REALM, "auth_realm", "Authentication domain name",REALM,NULL},
+	{OPT_AUTH_GPASSWD, "auth_gpass", "Global passwords file", NULL, NULL},
+	{OPT_AUTH_PUT, "auth_PUT", "PUT,DELETE auth file", NULL, NULL},
+#endif /* !NO_AUTH */
+#ifdef _WIN32
+	{OPT_SERVICE, "service", "Manage WinNNT service (install"
+	    "|uninstall)", NULL, _shttpd_set_nt_service},
+	{OPT_HIDE, "systray", "Hide console, show icon on systray",
+		"no", _shttpd_set_systray},
+#else
+	{OPT_INETD, "inetd", "Inetd mode", "no", set_inetd},
+	{OPT_UID, "uid", "\tRun as user", NULL, set_uid},
+#endif /* _WIN32 */
+	{OPT_ACCESS_LOG, "access_log", "Access log file", NULL, set_alog},
+	{OPT_ERROR_LOG, "error_log", "Error log file", NULL, set_elog},
+	{OPT_MIME_TYPES, "mime_types", "Additional mime types list", NULL,NULL},
+	{OPT_ALIASES, "aliases", "Path=URI mappings", NULL, NULL},
+	{OPT_ACL, "acl", "\tAllow/deny IP addresses/subnets", NULL, set_acl},
+#if !defined(NO_THREADS)
+	{OPT_THREADS, "threads", "Number of worker threads", "1", set_workers},
+#endif /* !NO_THREADS */
+	{-1, NULL, NULL, NULL, NULL}
+};
+
+static const struct opt *
+find_opt(const char *opt_name)
+{
+	int	i;
+
+	for (i = 0; known_options[i].name != NULL; i++)
+		if (!strcmp(opt_name, known_options[i].name))
+			return (known_options + i);
+
+	_shttpd_elog(E_FATAL, NULL, "no such option: [%s]", opt_name);
+
+	/* UNREACHABLE */
+	return (NULL);
+}
+
+int
+shttpd_set_option(struct shttpd_ctx *ctx, const char *opt, const char *val)
+{
+	const struct opt	*o = find_opt(opt);
+	int			retval = TRUE;
+
+	/* Call option setter first, so it can use both new and old values */
+	if (o->setter != NULL)
+		retval = o->setter(ctx, val);
+
+	/* Free old value if any */
+	if (ctx->options[o->index] != NULL)
+		free(ctx->options[o->index]);
+	
+	/* Set new option value */
+	ctx->options[o->index] = val ? _shttpd_strdup(val) : NULL;
+
+	return (retval);
+}
+
+static void
+show_cfg_page(struct shttpd_arg *arg)
+{
+	struct shttpd_ctx	*ctx = arg->user_data;
+	char			opt_name[20], value[BUFSIZ];
+	const struct opt	*o;
+
+	opt_name[0] = value[0] = '\0';
+
+	if (!strcmp(shttpd_get_env(arg, "REQUEST_METHOD"), "POST")) {
+		if (arg->flags & SHTTPD_MORE_POST_DATA)
+			return;
+		(void) shttpd_get_var("o", arg->in.buf, arg->in.len,
+		    opt_name, sizeof(opt_name));
+		(void) shttpd_get_var("v", arg->in.buf, arg->in.len,
+		    value, sizeof(value));
+		shttpd_set_option(ctx, opt_name, value[0] ? value : NULL);
+	}
+
+	shttpd_printf(arg, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
+	    "

SHTTPD v. %s

", shttpd_version()); + + shttpd_printf(arg, "%s", "
NameModifiedSize

" + ""); + + if (opt_name[0] != '\0' && value[0] != '\0') + shttpd_printf(arg, "

Saved: %s=%s

", + opt_name, value[0] ? value : "NULL"); + + + for (o = known_options; o->name != NULL; o++) { + shttpd_printf(arg, + "
" + "" + "" + "", + o->name, o->description, o->name, + ctx->options[o->index] ? ctx->options[o->index] : ""); + } + + shttpd_printf(arg, "%s", "
OptionDescriptionValue
%s%s
"); + arg->flags |= SHTTPD_END_OF_OUTPUT; +} + +/* + * Show usage string and exit. + */ +void +_shttpd_usage(const char *prog) +{ + const struct opt *o; + + (void) fprintf(stderr, + "SHTTPD version %s (c) Sergey Lyubka\n" + "usage: %s [options] [config_file]\n", VERSION, prog); + +#if !defined(NO_AUTH) + fprintf(stderr, " -A \n"); +#endif /* NO_AUTH */ + + for (o = known_options; o->name != NULL; o++) { + (void) fprintf(stderr, " -%s\t%s", o->name, o->description); + if (o->default_value != NULL) + fprintf(stderr, " (default: %s)", o->default_value); + fputc('\n', stderr); + } + + exit(EXIT_FAILURE); +} + +static void +set_opt(struct shttpd_ctx *ctx, const char *opt, const char *value) +{ + const struct opt *o; + + o = find_opt(opt); + if (ctx->options[o->index] != NULL) + free(ctx->options[o->index]); + ctx->options[o->index] = _shttpd_strdup(value); +} + +static void +process_command_line_arguments(struct shttpd_ctx *ctx, char *argv[]) +{ + const char *config_file = CONFIG_FILE; + char line[BUFSIZ], opt[BUFSIZ], + val[BUFSIZ], path[FILENAME_MAX], *p; + FILE *fp; + size_t i, line_no = 0; + + /* First find out, which config file to open */ + for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) + if (argv[i + 1] == NULL) + _shttpd_usage(argv[0]); + + if (argv[i] != NULL && argv[i + 1] != NULL) { + /* More than one non-option arguments are given w*/ + _shttpd_usage(argv[0]); + } else if (argv[i] != NULL) { + /* Just one non-option argument is given, this is config file */ + config_file = argv[i]; + } else { + /* No config file specified. Look for one where shttpd lives */ + if ((p = strrchr(argv[0], DIRSEP)) != 0) { + _shttpd_snprintf(path, sizeof(path), "%.*s%s", + p - argv[0] + 1, argv[0], config_file); + config_file = path; + } + } + + fp = fopen(config_file, "r"); + + /* If config file was set in command line and open failed, exit */ + if (fp == NULL && argv[i] != NULL) + _shttpd_elog(E_FATAL, NULL, "cannot open config file %s: %s", + config_file, strerror(errno)); + + if (fp != NULL) { + + _shttpd_elog(E_LOG, NULL, "Loading config file %s", config_file); + + /* Loop over the lines in config file */ + while (fgets(line, sizeof(line), fp) != NULL) { + + line_no++; + + /* Ignore empty lines and comments */ + if (line[0] == '#' || line[0] == '\n') + continue; + + if (sscanf(line, "%s %[^\n#]", opt, val) != 2) + _shttpd_elog(E_FATAL, NULL, "line %d in %s is invalid", + line_no, config_file); + + set_opt(ctx, opt, val); + } + + (void) fclose(fp); + } + + /* Now pass through the command line options */ + for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) + set_opt(ctx, &argv[i][1], argv[i + 1]); +} + +struct shttpd_ctx * +shttpd_init(int argc, char *argv[]) +{ + struct shttpd_ctx *ctx; + struct tm *tm; + const struct opt *o; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + _shttpd_elog(E_FATAL, NULL, "cannot allocate shttpd context"); + + LL_INIT(&ctx->registered_uris); + LL_INIT(&ctx->error_handlers); + LL_INIT(&ctx->acl); + LL_INIT(&ctx->ssi_funcs); + LL_INIT(&ctx->listeners); + LL_INIT(&ctx->workers); + + /* Initialize options. First pass: set default option values */ + for (o = known_options; o->name != NULL; o++) + ctx->options[o->index] = o->default_value ? + _shttpd_strdup(o->default_value) : NULL; + + /* Second and third passes: config file and argv */ + if (argc > 0 && argv != NULL) + process_command_line_arguments(ctx, argv); + + /* Call setter functions */ + for (o = known_options; o->name != NULL; o++) + if (o->setter && ctx->options[o->index] != NULL) + if (o->setter(ctx, ctx->options[o->index]) == FALSE) { + shttpd_fini(ctx); + return (NULL); + } + + _shttpd_current_time = time(NULL); + tm = localtime(&_shttpd_current_time); + _shttpd_tz_offset = 0; + + if (num_workers(ctx) == 1) + (void) add_worker(ctx); +#if 0 + tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0); +#endif + +#ifdef _WIN32 + {WSADATA data; WSAStartup(MAKEWORD(2,2), &data);} +#endif /* _WIN32 */ + + return (ctx); +} diff --git a/vendor/shttpd/shttpd.h b/vendor/shttpd/shttpd.h new file mode 100644 index 0000000..d475b06 --- /dev/null +++ b/vendor/shttpd/shttpd.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2004-2008 Sergey Lyubka + * 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. + * + * $Id: shttpd.h,v 1.18 2008/08/23 08:34:50 drozd Exp $ + */ + +#ifndef SHTTPD_HEADER_INCLUDED +#define SHTTPD_HEADER_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct ubuf { + char *buf; /* Buffer pointer */ + int len; /* Size of a buffer */ + int num_bytes; /* Bytes processed by callback */ +}; + +/* + * This structure is passed to the user callback function + */ +struct shttpd_arg { + void *priv; /* Private! Do not touch! */ + void *state; /* User state */ + void *user_data; /* Data from register_uri() */ + struct ubuf in; /* Input is here, POST data */ + struct ubuf out; /* Output goes here */ + + unsigned int flags; +#define SHTTPD_END_OF_OUTPUT 1 /* No more data do send */ +#define SHTTPD_CONNECTION_ERROR 2 /* Server closed the connection */ +#define SHTTPD_MORE_POST_DATA 4 /* arg->in has incomplete data */ +#define SHTTPD_POST_BUFFER_FULL 8 /* arg->in has max data */ +#define SHTTPD_SSI_EVAL_TRUE 16 /* SSI eval callback must set it*/ +#define SHTTPD_SUSPEND 32 /* User wants to suspend output */ +}; + +/* + * User callback function. Called when certain registered URLs have been + * requested. These are the requirements to the callback function: + * + * 1. It must copy data into 'out.buf' buffer, not more than 'out.len' bytes, + * and record how many bytes are copied, into 'out.num_bytes' + * 2. It must not call any blocking functions + * 3. It must set SHTTPD_END_OF_OUTPUT flag when there is no more data to send + * 4. For POST requests, it must process the incoming data (in.buf) of length + * 'in.len', and set 'in.num_bytes', which is how many bytes of POST + * data was processed and can be discarded by SHTTPD. + * 5. If callback allocates arg->state, to keep state, it must deallocate it + * at the end of coonection SHTTPD_CONNECTION_ERROR or SHTTPD_END_OF_OUTPUT + * 6. If callback function wants to suspend until some event, it must store + * arg->priv pointer elsewhere, set SHTTPD_SUSPEND flag and return. When + * the event happens, user code should call shttpd_wakeup(priv). + * It is safe to call shttpd_wakeup() from any thread. User code must + * not call shttpd_wakeup once the connection is closed. + */ +typedef void (*shttpd_callback_t)(struct shttpd_arg *); + +/* + * shttpd_init Initialize shttpd context + * shttpd_fini Dealocate the context, close all connections + * shttpd_set_option Set new value for option + * shttpd_register_uri Setup the callback function for specified URL + * shttpd_poll Do connections processing + * shttpd_version return string with SHTTPD version + * shttpd_get_var Fetch POST/GET variable value by name. Return value len + * shttpd_get_header return value of the specified HTTP header + * shttpd_get_env return values for the following pseudo-variables: + "REQUEST_METHOD", "REQUEST_URI", + * "REMOTE_USER" and "REMOTE_ADDR" + * shttpd_printf helper function to output data + * shttpd_handle_error register custom HTTP error handler + * shttpd_wakeup clear SHTTPD_SUSPEND state for the connection + */ + +struct shttpd_ctx; + +struct shttpd_ctx *shttpd_init(int argc, char *argv[]); +int shttpd_set_option(struct shttpd_ctx *, const char *opt, const char *val); +void shttpd_fini(struct shttpd_ctx *); +void shttpd_register_uri(struct shttpd_ctx *ctx, const char *uri, + shttpd_callback_t callback, void *const user_data); +void shttpd_poll(struct shttpd_ctx *, int milliseconds); +const char *shttpd_version(void); +int shttpd_get_var(const char *var, const char *buf, int buf_len, + char *value, int value_len); +const char *shttpd_get_header(struct shttpd_arg *, const char *header_name); +const char *shttpd_get_env(struct shttpd_arg *, const char *name); +void shttpd_get_http_version(struct shttpd_arg *, + unsigned long *major, unsigned long *minor); +size_t shttpd_printf(struct shttpd_arg *, const char *fmt, ...); +void shttpd_handle_error(struct shttpd_ctx *ctx, int status, + shttpd_callback_t func, void *const data); +void shttpd_register_ssi_func(struct shttpd_ctx *ctx, const char *name, + shttpd_callback_t func, void *const user_data); +void shttpd_wakeup(const void *priv); +int shttpd_join(struct shttpd_ctx *, fd_set *, fd_set *, int *max_fd); +int shttpd_socketpair(int sp[2]); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SHTTPD_HEADER_INCLUDED */ diff --git a/vendor/shttpd/ssl.h b/vendor/shttpd/ssl.h new file mode 100644 index 0000000..5aeae4a --- /dev/null +++ b/vendor/shttpd/ssl.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +/* + * Snatched from OpenSSL includes. I put the prototypes here to be independent + * from the OpenSSL source installation. Having this, shttpd + SSL can be + * built on any system with binary SSL libraries installed. + */ + +typedef struct ssl_st SSL; +typedef struct ssl_method_st SSL_METHOD; +typedef struct ssl_ctx_st SSL_CTX; + +#define SSL_ERROR_WANT_READ 2 +#define SSL_ERROR_WANT_WRITE 3 +#define SSL_FILETYPE_PEM 1 + +/* + * Dynamically loaded SSL functionality + */ +struct ssl_func { + const char *name; /* SSL function name */ + union variant ptr; /* Function pointer */ +}; + +extern struct ssl_func ssl_sw[]; + +#define FUNC(x) ssl_sw[x].ptr.v_func + +#define SSL_free(x) (* (void (*)(SSL *)) FUNC(0))(x) +#define SSL_accept(x) (* (int (*)(SSL *)) FUNC(1))(x) +#define SSL_connect(x) (* (int (*)(SSL *)) FUNC(2))(x) +#define SSL_read(x,y,z) (* (int (*)(SSL *, void *, int)) FUNC(3))((x),(y),(z)) +#define SSL_write(x,y,z) \ + (* (int (*)(SSL *, const void *,int)) FUNC(4))((x), (y), (z)) +#define SSL_get_error(x,y)(* (int (*)(SSL *, int)) FUNC(5))((x), (y)) +#define SSL_set_fd(x,y) (* (int (*)(SSL *, int)) FUNC(6))((x), (y)) +#define SSL_new(x) (* (SSL * (*)(SSL_CTX *)) FUNC(7))(x) +#define SSL_CTX_new(x) (* (SSL_CTX * (*)(SSL_METHOD *)) FUNC(8))(x) +#define SSLv23_server_method() (* (SSL_METHOD * (*)(void)) FUNC(9))() +#define SSL_library_init() (* (int (*)(void)) FUNC(10))() +#define SSL_CTX_use_PrivateKey_file(x,y,z) (* (int (*)(SSL_CTX *, \ + const char *, int)) FUNC(11))((x), (y), (z)) +#define SSL_CTX_use_certificate_file(x,y,z) (* (int (*)(SSL_CTX *, \ + const char *, int)) FUNC(12))((x), (y), (z)) diff --git a/vendor/shttpd/standalone.c b/vendor/shttpd/standalone.c new file mode 100644 index 0000000..89ba661 --- /dev/null +++ b/vendor/shttpd/standalone.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +static int exit_flag; /* Program termination flag */ + +static void +signal_handler(int sig_num) +{ + switch (sig_num) { +#ifndef _WIN32 + case SIGCHLD: + while (waitpid(-1, &sig_num, WNOHANG) > 0) ; + break; +#endif /* !_WIN32 */ + default: + exit_flag = sig_num; + break; + } +} + +int +main(int argc, char *argv[]) +{ + struct shttpd_ctx *ctx; + +#if !defined(NO_AUTH) + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'A') { + if (argc != 6) + _shttpd_usage(argv[0]); + exit(_shttpd_edit_passwords(argv[2],argv[3],argv[4],argv[5])); + } +#endif /* NO_AUTH */ + + if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) + _shttpd_usage(argv[0]); + +#if defined(_WIN32) + try_to_run_as_nt_service(); +#endif /* _WIN32 */ + +#ifndef _WIN32 + (void) signal(SIGCHLD, signal_handler); + (void) signal(SIGPIPE, SIG_IGN); +#endif /* _WIN32 */ + + (void) signal(SIGTERM, signal_handler); + (void) signal(SIGINT, signal_handler); + + if ((ctx = shttpd_init(argc, argv)) == NULL) + _shttpd_elog(E_FATAL, NULL, "%s", + "Cannot initialize SHTTPD context"); + + _shttpd_elog(E_LOG, NULL, "shttpd %s started on port(s) %s, serving %s", + VERSION, ctx->options[OPT_PORTS], ctx->options[OPT_ROOT]); + + while (exit_flag == 0) + shttpd_poll(ctx, 10 * 1000); + + _shttpd_elog(E_LOG, NULL, "Exit on signal %d", exit_flag); + shttpd_fini(ctx); + + return (EXIT_SUCCESS); +} diff --git a/vendor/shttpd/std_includes.h b/vendor/shttpd/std_includes.h new file mode 100644 index 0000000..4bf1ea7 --- /dev/null +++ b/vendor/shttpd/std_includes.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#ifndef STD_HEADERS_INCLUDED +#define STD_HEADERS_INCLUDED + +#ifndef _WIN32_WCE /* Some ANSI #includes are not available on Windows CE */ +#include +#include +#include +#include +#include +#include +#endif /* _WIN32_WCE */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) /* Windows specific */ +#include "compat_win32.h" +#elif defined(__rtems__) /* RTEMS specific */ +#include "compat_rtems.h" +#else /* UNIX specific */ +#include "compat_unix.h" +#endif /* _WIN32 */ + +#endif /* STD_HEADERS_INCLUDED */ diff --git a/vendor/shttpd/string.c b/vendor/shttpd/string.c new file mode 100644 index 0000000..a383cf8 --- /dev/null +++ b/vendor/shttpd/string.c @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2004-2005 Sergey Lyubka + * 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. + */ + +#include "defs.h" + +void +_shttpd_strlcpy(register char *dst, register const char *src, size_t n) +{ + for (; *src != '\0' && n > 1; n--) + *dst++ = *src++; + *dst = '\0'; +} + +int +_shttpd_strncasecmp(const char *str1, const char *str2, size_t len) +{ + register const unsigned char *s1 = (unsigned char *) str1, + *s2 = (unsigned char *) str2, *e; + int ret; + + for (e = s1 + len - 1; s1 < e && *s1 != '\0' && *s2 != '\0' && + tolower(*s1) == tolower(*s2); s1++, s2++) ; + ret = tolower(*s1) - tolower(*s2); + + return (ret); +} + +char * +_shttpd_strndup(const char *ptr, size_t len) +{ + char *p; + + if ((p = malloc(len + 1)) != NULL) + _shttpd_strlcpy(p, ptr, len + 1); + + return (p); + +} + +char * +_shttpd_strdup(const char *str) +{ + return (_shttpd_strndup(str, strlen(str))); +} + +/* + * Sane snprintf(). Acts like snprintf(), but never return -1 or the + * value bigger than supplied buffer. + * Thanks Adam Zeldis to pointing snprintf()-caused vulnerability + * in his audit report. + */ +int +_shttpd_snprintf(char *buf, size_t buflen, const char *fmt, ...) +{ + va_list ap; + int n; + + if (buflen == 0) + return (0); + + va_start(ap, fmt); + n = vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + if (n < 0 || (size_t) n >= buflen) + n = buflen - 1; + buf[n] = '\0'; + + return (n); +} + +/* + * Verify that given file has certain extension + */ +int +_shttpd_match_extension(const char *path, const char *ext_list) +{ + size_t len, path_len; + + path_len = strlen(path); + + FOR_EACH_WORD_IN_LIST(ext_list, len) + if (len < path_len && path[path_len - len - 1] == '.' && + !_shttpd_strncasecmp(path + path_len - len, ext_list, len)) + return (TRUE); + + return (FALSE); +} -- 2.7.4