/* 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.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include <unistd.h>
#include <zlib.h>
+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";
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 */
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;
* 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,
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;
}
/* ---------------------------------------------------------------------------
* 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, ...)
{
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;
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;
}
*/
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) {
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);
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) {
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() */
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,
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.
/* 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;
/* 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));
free(bindaddr);
}
- if (insocks == 0)
+ if (insocks == NULL)
errx(1, "was not able to bind any ports for http interface");
/* ignore SIGPIPE */
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) {
void http_stop(void) {
struct connection *conn;
+ struct connection *next;
unsigned int i;
+ free(http_base_url);
+
/* Close listening sockets. */
for (i=0; i<insock_num; i++)
close(insocks[i]);
insocks = NULL;
/* Close in-flight connections. */
- LIST_FOREACH(conn, &connlist, entries) {
+ LIST_FOREACH_SAFE(conn, &connlist, entries, next) {
LIST_REMOVE(conn, entries);
free_connection(conn);
free(conn);