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