X-Git-Url: https://unix4lyfe.org/gitweb/darkstat-debian/blobdiff_plain/a1e8056c92203d02860d719abb1d562453896da8..HEAD:/http.c diff --git a/http.c b/http.c index 2835e8e..bd44ff7 100644 --- a/http.c +++ b/http.c @@ -1,5 +1,5 @@ /* darkstat 3 - * copyright (c) 2001-2011 Emil Mikulic. + * copyright (c) 2001-2014 Emil Mikulic. * * http.c: embedded webserver. * This borrows a lot of code from darkhttpd. @@ -33,9 +33,13 @@ #include #include #include +#include #include #include +static char *http_base_url = NULL; +static int http_base_len = 0; + static const char mime_type_xml[] = "text/xml"; static const char mime_type_html[] = "text/html; charset=us-ascii"; static const char mime_type_css[] = "text/css"; @@ -55,7 +59,7 @@ struct connection { int socket; struct sockaddr_storage client; - time_t last_active; + time_t last_active_mono; enum { RECV_REQUEST, /* receiving request */ SEND_HEADER_AND_REPLY, /* try to send header+reply together */ @@ -266,7 +270,7 @@ static struct connection *new_connection(void) conn->socket = -1; memset(&conn->client, 0, sizeof(conn->client)); - conn->last_active = now; + conn->last_active_mono = now_mono(); conn->request = NULL; conn->request_length = 0; conn->accept_gzip = 0; @@ -363,13 +367,11 @@ static void free_connection(struct connection *conn) * buffer is returned for convenience. */ #define DATE_LEN 30 /* strlen("Fri, 28 Feb 2003 00:02:08 GMT")+1 */ -static char *rfc1123_date(char *dest, const time_t when) -{ - time_t tmp = when; +static char *rfc1123_date(char *dest, time_t when) { if (strftime(dest, DATE_LEN, - "%a, %d %b %Y %H:%M:%S %Z", gmtime(&tmp) ) == 0) + "%a, %d %b %Y %H:%M:%S %Z", gmtime(&when) ) == 0) errx(1, "strftime() failed [%s]", dest); - return (dest); + return dest; } static void generate_header(struct connection *conn, @@ -382,23 +384,28 @@ static void generate_header(struct connection *conn, if (conn->encoding == NULL) conn->encoding = encoding_identity; - verbosef("http: %d %s (%s: %d bytes)", code, text, - conn->encoding, conn->reply_length); + verbosef("http: %d %s (%s: %zu bytes)", + code, + text, + conn->encoding, + conn->reply_length); conn->header_length = xasprintf(&(conn->header), "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" "Server: %s\r\n" "Vary: Accept-Encoding\r\n" "Content-Type: %s\r\n" - "Content-Length: %d\r\n" + "Content-Length: %qu\r\n" "Content-Encoding: %s\r\n" "X-Robots-Tag: noindex, noarchive\r\n" "%s" - "\r\n" - , + "\r\n", code, text, - rfc1123_date(date, now), server, - conn->mime_type, conn->reply_length, conn->encoding, + rfc1123_date(date, now_real()), + server, + conn->mime_type, + (qu)conn->reply_length, + conn->encoding, conn->header_extra); conn->http_code = code; } @@ -408,6 +415,9 @@ static void generate_header(struct connection *conn, /* --------------------------------------------------------------------------- * A default reply for any (erroneous) occasion. */ +static void default_reply(struct connection *conn, + const int errcode, const char *errname, const char *format, ...) + _printflike_(4, 5); static void default_reply(struct connection *conn, const int errcode, const char *errname, const char *format, ...) { @@ -572,11 +582,16 @@ process_gzip(struct connection *conn) zs.zfree = Z_NULL; zs.opaque = Z_NULL; - if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, - 15+16, /* 15 = biggest window, 16 = add gzip header+trailer */ - 8 /* default */, - Z_DEFAULT_STRATEGY) != Z_OK) - return; + if (deflateInit2(&zs, + Z_BEST_COMPRESSION, + Z_DEFLATED, + 15+16, /* 15 = biggest window, + 16 = add gzip header+trailer */ + 8 /* default */, + Z_DEFAULT_STRATEGY) != Z_OK) { + free(buf); + return; + } zs.avail_in = conn->reply_length; zs.next_in = (unsigned char *)conn->reply; @@ -587,7 +602,7 @@ process_gzip(struct connection *conn) if (deflate(&zs, Z_FINISH) != Z_STREAM_END) { deflateEnd(&zs); free(buf); - verbosef("failed to compress %u bytes", (unsigned int)len); + verbosef("failed to compress %zu bytes", len); return; } @@ -606,22 +621,32 @@ process_gzip(struct connection *conn) */ static void process_get(struct connection *conn) { - char *decoded_url, *safe_url; + char *safe_url; verbosef("http: %s \"%s\" %s", conn->method, conn->uri, (conn->query == NULL)?"":conn->query); - /* work out path of file being requested */ - decoded_url = urldecode(conn->uri); - - /* make sure it's safe */ - safe_url = make_safe_uri(decoded_url); - free(decoded_url); - if (safe_url == NULL) { - default_reply(conn, 400, "Bad Request", - "You requested an invalid URI: %s", conn->uri); - return; + /* Decode the URL being requested. */ + char *decoded_url; + char *decoded_url_offset; + + decoded_url = urldecode(conn->uri); + + /* Optionally strip the base. */ + decoded_url_offset = decoded_url; + if (str_starts_with(decoded_url, http_base_url)) { + decoded_url_offset += http_base_len - 1; + } + + /* Make sure it's safe. */ + safe_url = make_safe_uri(decoded_url_offset); + free(decoded_url); + if (safe_url == NULL) { + default_reply(conn, 400, "Bad Request", + "You requested an invalid URI: %s", conn->uri); + return; + } } if (strcmp(safe_url, "/") == 0) { @@ -719,7 +744,7 @@ static void poll_recv_request(struct connection *conn) conn->state = DONE; return; } - conn->last_active = now; + conn->last_active_mono = now_mono(); /* append to conn->request */ conn->request = xrealloc(conn->request, conn->request_length+recvd+1); @@ -772,7 +797,7 @@ static void poll_send_header_and_reply(struct connection *conn) iov[1].iov_len = conn->reply_length; sent = writev(conn->socket, iov, 2); - conn->last_active = now; + conn->last_active_mono = now_mono(); /* handle any errors (-1) or closure (0) in send() */ if (sent < 1) { @@ -814,7 +839,7 @@ static void poll_send_header(struct connection *conn) sent = send(conn->socket, conn->header + conn->header_sent, conn->header_length - conn->header_sent, 0); - conn->last_active = now; + conn->last_active_mono = now_mono(); dverbosef("poll_send_header(%d) sent %d bytes", conn->socket, (int)sent); /* handle any errors (-1) or closure (0) in send() */ @@ -850,7 +875,7 @@ static void poll_send_reply(struct connection *conn) sent = send(conn->socket, conn->reply + conn->reply_sent, conn->reply_length - conn->reply_sent, 0); - conn->last_active = now; + conn->last_active_mono = now_mono(); dverbosef("poll_send_reply(%d) sent %d: [%d-%d] of %d", conn->socket, (int)sent, (int)conn->reply_sent, @@ -874,6 +899,40 @@ static void poll_send_reply(struct connection *conn) if (conn->reply_sent == conn->reply_length) conn->state = DONE; } + + +/* -------------------------------------------------------------------------- + * Initialize the base url. + */ +void http_init_base(const char *url) { + char *slashed_url, *safe_url; + size_t urllen; + + if (url == NULL) { + http_base_url = strdup("/"); + } else { + /* Make sure that the url has leading and trailing slashes. */ + urllen = strlen(url); + slashed_url = xmalloc(urllen+3); + slashed_url[0] = '/'; + memcpy(slashed_url+1, url, urllen); /* don't copy NUL */ + slashed_url[urllen+1] = '/'; + slashed_url[urllen+2] = '\0'; + + /* Clean the url. */ + safe_url = make_safe_uri(slashed_url); + free(slashed_url); + if (safe_url == NULL) { + verbosef("invalid base \"%s\", ignored", url); + http_base_url = strdup("/"); /* set to default */ + } else { + http_base_url = safe_url; + } + } + http_base_len = strlen(http_base_url); + verbosef("set base url to \"%s\"", http_base_url); +} + /* Use getaddrinfo to figure out what type of socket to create and * what to bind it to. "bindaddr" can be NULL. Remember to freeaddrinfo() * the result. @@ -921,14 +980,18 @@ static void http_listen_one(struct addrinfo *ai, /* create incoming socket */ if ((sockin = socket(ai->ai_family, ai->ai_socktype, - ai->ai_protocol)) == -1) - err(1, "http_listen_one(%s, %u): socket(%d (%s), %d, %d) failed", + ai->ai_protocol)) == -1) { + warn("http_listen_one(%s, %u): socket(%d (%s), %d, %d) failed", ipaddr, (unsigned int)bindport, ai->ai_family, (ai->ai_family == AF_INET6) ? "AF_INET6" : (ai->ai_family == AF_INET) ? "AF_INET" : "?", ai->ai_socktype, ai->ai_protocol); + return; + } + + fd_set_nonblock(sockin); /* reuse address */ sockopt = 1; @@ -951,18 +1014,20 @@ static void http_listen_one(struct addrinfo *ai, /* bind socket */ if (bind(sockin, ai->ai_addr, ai->ai_addrlen) == -1) { warn("bind(\"%s\") failed", ipaddr); + close(sockin); return; } /* listen on socket */ - if (listen(sockin, -1) == -1) + if (listen(sockin, 128) == -1) err(1, "listen() failed"); - verbosef("listening on http://%s%s%s:%u/", + verbosef("listening on http://%s%s%s:%u%s", (ai->ai_family == AF_INET6) ? "[" : "", ipaddr, (ai->ai_family == AF_INET6) ? "]" : "", - bindport); + bindport, + http_base_url); /* add to insocks */ insocks = xrealloc(insocks, sizeof(*insocks) * (insock_num + 1)); @@ -993,7 +1058,7 @@ void http_listen(const unsigned short bindport) free(bindaddr); } - if (insocks == 0) + if (insocks == NULL) errx(1, "was not able to bind any ports for http interface"); /* ignore SIGPIPE */ @@ -1022,7 +1087,7 @@ http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd, LIST_FOREACH_SAFE(conn, &connlist, entries, next) { - int idlefor = now - conn->last_active; + int idlefor = now_mono() - conn->last_active_mono; /* Time out dead connections. */ if (idlefor >= idletime) { @@ -1116,8 +1181,11 @@ void http_poll(fd_set *recv_set, fd_set *send_set) void http_stop(void) { struct connection *conn; + struct connection *next; unsigned int i; + free(http_base_url); + /* Close listening sockets. */ for (i=0; i