diff --git a/trunk/darkhttpd.c b/trunk/darkhttpd.c index 2e2b2eb..8b7a4a6 100644 --- a/trunk/darkhttpd.c +++ b/trunk/darkhttpd.c @@ -10,10 +10,11 @@ /* * TODO: * . Ignore SIGPIPE. - * . Actually serve files. + * x Actually serve files. * . Generate directory entries. * . Log to file. * . Partial content. + * . If-Modified-Since. * . Keep-alive connections. * . Chroot, set{uid|gid}. * . Port to Win32. @@ -21,11 +22,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -35,6 +39,10 @@ /* for easy defusal */ #define debugf printf +#ifndef min +#define min(a,b) ( ((a)<(b)) ? (a) : (b) ) +#endif + LIST_HEAD(conn_list_head, connection) connlist = LIST_HEAD_INITIALIZER(conn_list_head); @@ -58,7 +66,7 @@ struct connection char *header; unsigned int header_sent, header_length; - int header_dont_free; + int header_dont_free, header_only; enum { REPLY_GENERATED, REPLY_FROMFILE } reply_type; char *reply; @@ -77,7 +85,7 @@ struct connection /* To prevent a malformed request from eating up too much memory, die once the * request exceeds this many bytes: */ -#define MAX_REQUEST_LENGTH 20000 +#define MAX_REQUEST_LENGTH 4000 @@ -242,9 +250,9 @@ static struct connection *new_connection(void) conn->request_length = 0; conn->header = NULL; conn->header_sent = conn->header_length = 0; - conn->header_dont_free = 0; /* you'll want to, later */ + conn->header_dont_free = conn->header_only = 0; conn->reply = NULL; - conn->reply_dont_free = 0; /* you'll want to, later */ + conn->reply_dont_free = 0; conn->reply_file = NULL; conn->reply_sent = conn->reply_length = 0; @@ -385,7 +393,7 @@ static char *urldecode(const char *url) } out[pos] = 0; - out = xrealloc(out, strlen(out)); /* dealloc what we don't need */ + out = xrealloc(out, strlen(out)+1); /* dealloc what we don't need */ return out; } @@ -395,8 +403,15 @@ static char *urldecode(const char *url) * A default reply for any (erroneous) occasion. */ static void default_reply(struct connection *conn, - const int errcode, const char *errname, const char *reason) + const int errcode, const char *errname, const char *format, ...) { + char *reason; + va_list va; + + va_start(va, format); + vasprintf(&reason, format, va); + va_end(va); + conn->reply_length = asprintf(&(conn->reply), "%d %s\n" "

%s

\n" /* errname */ @@ -405,6 +420,7 @@ static void default_reply(struct connection *conn, "Generated by %s on %s\n" "\n", errcode, errname, errname, reason, pkgname, rfc1123_date(time(NULL))); + free(reason); if (conn->reply == NULL) errx(1, "out of memory in asprintf()"); @@ -453,13 +469,14 @@ static void parse_request(const char *req, const int length, /* --------------------------------------------------------------------------- * Process a GET/HEAD request */ -static void process_get(struct connection *conn, - const char *url, const int header_only) +static void process_get(struct connection *conn, const char *url) { - char *decoded_url, *target; + char *decoded_url, *target, *tmp; + struct stat filestat; - /* work out which file we're trying to get */ + /* work out path of file being requested */ decoded_url = urldecode(url); + /* FIXME: ensure this is a safe URL, i.e. no '../' */ if (decoded_url[strlen(decoded_url)-1] == '/') { asprintf(&target, "%s%s%s", wwwroot, decoded_url, index_name); @@ -469,13 +486,54 @@ static void process_get(struct connection *conn, asprintf(&target, "%s%s", wwwroot, decoded_url); } free(decoded_url); - decoded_url = NULL; debugf(">>>%s<<<\n", target); - free(target); - /* FIXME */ - default_reply(conn, 200, "OK", "Nothing to see here. Move along."); + conn->reply_file = fopen(target, "rb"); + free(target); + if (conn->reply_file == NULL) + { + /* fopen() failed */ + if (errno == ENOENT) + default_reply(conn, 404, "Not Found", + "The URI you requested (%s) was not found.", url); + else + default_reply(conn, 403, "Forbidden", + "The URI you requested (%s) cannot be returned.
\n" + "%s.", /* reason why */ + url, strerror(errno)); + + return; + } + + /* get information on the file */ + if (fstat(fileno(conn->reply_file), &filestat) == -1) + { + default_reply(conn, 500, "Internal Server Error", + "fstat() failed: %s.", strerror(errno)); + return; + } + + conn->reply_type = REPLY_FROMFILE; + conn->reply_length = filestat.st_size; + + asprintf(&tmp, + "HTTP/1.1 200 OK\r\n" + "Date: %s\r\n" + "Server: %s\r\n" + "Connection: close\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/plain\r\n" /* FIXME */ + , rfc1123_date(time(NULL)), pkgname, conn->reply_length + ); + + conn->header_length = asprintf(&(conn->header), + "%s" + "Last-Modified: %s\r\n" + "\r\n" + , tmp, rfc1123_date(filestat.st_mtime) + ); + free(tmp); } @@ -490,9 +548,12 @@ static void process_request(struct connection *conn) debugf("method=``%s'', url=``%s''\n", method, url); if (strcmp(method, "GET") == 0) - process_get(conn, url, 0); + process_get(conn, url); else if (strcmp(method, "HEAD") == 0) - process_get(conn, url, 1); + { + process_get(conn, url); + conn->header_only = 1; + } else if (strcmp(method, "OPTIONS") == 0 || strcmp(method, "POST") == 0 || strcmp(method, "PUT") == 0 || @@ -500,10 +561,10 @@ static void process_request(struct connection *conn) strcmp(method, "TRACE") == 0 || strcmp(method, "CONNECT") == 0) default_reply(conn, 501, "Not Implemented", - "That method is not implemented."); + "That method you specified (%s) is not implemented.", method); else default_reply(conn, 400, "Bad Request", - "That method is not a valid HTTP/1.1 method."); + "%s is not a valid HTTP/1.1 method.", method); /* advance state */ conn->state = SEND_HEADER; @@ -541,8 +602,7 @@ static void poll_recv_request(struct connection *conn) #undef BUFSIZE /* append to conn->request */ - conn->request = xrealloc(conn->request, - conn->request_length + recvd + 1); + conn->request = xrealloc(conn->request, conn->request_length+recvd+1); memcpy(conn->request+conn->request_length, buf, recvd); conn->request_length += recvd; conn->request[conn->request_length] = 0; @@ -564,7 +624,7 @@ static void poll_recv_request(struct connection *conn) /* --------------------------------------------------------------------------- - * Sending header. + * Sending header. Assumes conn->header is not NULL. */ static void poll_send_header(struct connection *conn) { @@ -583,19 +643,45 @@ static void poll_send_header(struct connection *conn) { if (!conn->header_dont_free) free(conn->header); conn->header = NULL; - conn->state = SEND_REPLY; + + if (conn->header_only) + conn->state = DONE; + else + conn->state = SEND_REPLY; } } /* --------------------------------------------------------------------------- - * Sending reply. (FIXME: FROM FILE) + * Sending reply. */ static void poll_send_reply(struct connection *conn) { - ssize_t sent = send(conn->socket, conn->reply + conn->reply_sent, - conn->reply_length - conn->reply_sent, 0); + ssize_t sent; + if (conn->reply_type == REPLY_GENERATED) + { + sent = send(conn->socket, conn->reply + conn->reply_sent, + conn->reply_length - conn->reply_sent, 0); + } + else + { + /* from file! */ + #define BUFSIZE 65000 + char buf[BUFSIZE]; + int amount = min(BUFSIZE, conn->reply_length - conn->reply_sent); + #undef BUFSIZE + + if (fseek(conn->reply_file, conn->reply_sent, SEEK_SET) == -1) + err(1, "fseek(%d)", conn->reply_sent); + + if (fread(buf, amount, 1, conn->reply_file) != 1) + err(1, "fread()"); + + sent = send(conn->socket, buf, amount, 0); + } + + /* handle any errors in send() */ if (sent == -1) err(1, "send()"); if (sent == 0) { @@ -607,8 +693,12 @@ static void poll_send_reply(struct connection *conn) /* check if we're done sending */ if (conn->reply_sent == conn->reply_length) { - if (!conn->reply_dont_free) free(conn->reply); - conn->reply = NULL; + if (!conn->reply_dont_free && conn->reply != NULL) + { + free(conn->reply); + conn->reply = NULL; + } + if (conn->reply_file != NULL) fclose(conn->reply_file); conn->state = DONE; } }