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