. TODO: Added If-Modified-Since

. TODO: Marked off Actually serve files
. Added min(a,b) macro
. Moved header_only from process_get() to struct connection
. MAX_REQUEST_LENGTH from 20000 to 4000
. xrealloc() to strlen+1 in urldecode()
. Made default_reply() take variable arguments like printf
. Mostly implemented process_get()
    . Handling of file-not-found
    . Handling of other fopen() errors
    . Header generation (except Content-Type)
. Made default_replies more specific
. poll_send_header() advances state to DONE if header_only
. Completed poll_send_reply() (implemented REPLY_FROMFILE)
This commit is contained in:
Emil Mikulic 2003-03-01 09:07:56 +00:00
parent 32b3855aeb
commit 02964cc5a6
1 changed files with 118 additions and 28 deletions

View File

@ -10,10 +10,11 @@
/* /*
* TODO: * TODO:
* . Ignore SIGPIPE. * . Ignore SIGPIPE.
* . Actually serve files. * x Actually serve files.
* . Generate directory entries. * . Generate directory entries.
* . Log to file. * . Log to file.
* . Partial content. * . Partial content.
* . If-Modified-Since.
* . Keep-alive connections. * . Keep-alive connections.
* . Chroot, set{uid|gid}. * . Chroot, set{uid|gid}.
* . Port to Win32. * . Port to Win32.
@ -21,11 +22,14 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <sys/queue.h> #include <sys/queue.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ctype.h> #include <ctype.h>
#include <err.h> #include <err.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -35,6 +39,10 @@
/* for easy defusal */ /* for easy defusal */
#define debugf printf #define debugf printf
#ifndef min
#define min(a,b) ( ((a)<(b)) ? (a) : (b) )
#endif
LIST_HEAD(conn_list_head, connection) connlist = LIST_HEAD(conn_list_head, connection) connlist =
LIST_HEAD_INITIALIZER(conn_list_head); LIST_HEAD_INITIALIZER(conn_list_head);
@ -58,7 +66,7 @@ struct connection
char *header; char *header;
unsigned int header_sent, header_length; unsigned int header_sent, header_length;
int header_dont_free; int header_dont_free, header_only;
enum { REPLY_GENERATED, REPLY_FROMFILE } reply_type; enum { REPLY_GENERATED, REPLY_FROMFILE } reply_type;
char *reply; char *reply;
@ -77,7 +85,7 @@ struct connection
/* To prevent a malformed request from eating up too much memory, die once the /* To prevent a malformed request from eating up too much memory, die once the
* request exceeds this many bytes: * 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->request_length = 0;
conn->header = NULL; conn->header = NULL;
conn->header_sent = conn->header_length = 0; 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 = NULL;
conn->reply_dont_free = 0; /* you'll want to, later */ conn->reply_dont_free = 0;
conn->reply_file = NULL; conn->reply_file = NULL;
conn->reply_sent = conn->reply_length = 0; conn->reply_sent = conn->reply_length = 0;
@ -385,7 +393,7 @@ static char *urldecode(const char *url)
} }
out[pos] = 0; 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; return out;
} }
@ -395,8 +403,15 @@ static char *urldecode(const char *url)
* A default reply for any (erroneous) occasion. * A default reply for any (erroneous) occasion.
*/ */
static void default_reply(struct connection *conn, 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), conn->reply_length = asprintf(&(conn->reply),
"<html><head><title>%d %s</title></head><body>\n" "<html><head><title>%d %s</title></head><body>\n"
"<h1>%s</h1>\n" /* errname */ "<h1>%s</h1>\n" /* errname */
@ -405,6 +420,7 @@ static void default_reply(struct connection *conn,
"Generated by %s on %s\n" "Generated by %s on %s\n"
"</body></html>\n", "</body></html>\n",
errcode, errname, errname, reason, pkgname, rfc1123_date(time(NULL))); errcode, errname, errname, reason, pkgname, rfc1123_date(time(NULL)));
free(reason);
if (conn->reply == NULL) errx(1, "out of memory in asprintf()"); 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 * Process a GET/HEAD request
*/ */
static void process_get(struct connection *conn, static void process_get(struct connection *conn, const char *url)
const char *url, const int header_only)
{ {
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); decoded_url = urldecode(url);
/* FIXME: ensure this is a safe URL, i.e. no '../' */
if (decoded_url[strlen(decoded_url)-1] == '/') if (decoded_url[strlen(decoded_url)-1] == '/')
{ {
asprintf(&target, "%s%s%s", wwwroot, decoded_url, index_name); 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); asprintf(&target, "%s%s", wwwroot, decoded_url);
} }
free(decoded_url); free(decoded_url);
decoded_url = NULL;
debugf(">>>%s<<<\n", target); debugf(">>>%s<<<\n", target);
free(target);
/* FIXME */ conn->reply_file = fopen(target, "rb");
default_reply(conn, 200, "OK", "Nothing to see here. Move along."); 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.<br>\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); debugf("method=``%s'', url=``%s''\n", method, url);
if (strcmp(method, "GET") == 0) if (strcmp(method, "GET") == 0)
process_get(conn, url, 0); process_get(conn, url);
else if (strcmp(method, "HEAD") == 0) 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 || else if (strcmp(method, "OPTIONS") == 0 ||
strcmp(method, "POST") == 0 || strcmp(method, "POST") == 0 ||
strcmp(method, "PUT") == 0 || strcmp(method, "PUT") == 0 ||
@ -500,10 +561,10 @@ static void process_request(struct connection *conn)
strcmp(method, "TRACE") == 0 || strcmp(method, "TRACE") == 0 ||
strcmp(method, "CONNECT") == 0) strcmp(method, "CONNECT") == 0)
default_reply(conn, 501, "Not Implemented", default_reply(conn, 501, "Not Implemented",
"That method is not implemented."); "That method you specified (%s) is not implemented.", method);
else else
default_reply(conn, 400, "Bad Request", 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 */ /* advance state */
conn->state = SEND_HEADER; conn->state = SEND_HEADER;
@ -541,8 +602,7 @@ static void poll_recv_request(struct connection *conn)
#undef BUFSIZE #undef BUFSIZE
/* append to conn->request */ /* append to conn->request */
conn->request = xrealloc(conn->request, conn->request = xrealloc(conn->request, conn->request_length+recvd+1);
conn->request_length + recvd + 1);
memcpy(conn->request+conn->request_length, buf, recvd); memcpy(conn->request+conn->request_length, buf, recvd);
conn->request_length += recvd; conn->request_length += recvd;
conn->request[conn->request_length] = 0; 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) 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); if (!conn->header_dont_free) free(conn->header);
conn->header = NULL; 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) static void poll_send_reply(struct connection *conn)
{ {
ssize_t sent = send(conn->socket, conn->reply + conn->reply_sent, ssize_t sent;
conn->reply_length - conn->reply_sent, 0); 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 == -1) err(1, "send()");
if (sent == 0) if (sent == 0)
{ {
@ -607,8 +693,12 @@ static void poll_send_reply(struct connection *conn)
/* check if we're done sending */ /* check if we're done sending */
if (conn->reply_sent == conn->reply_length) if (conn->reply_sent == conn->reply_length)
{ {
if (!conn->reply_dont_free) free(conn->reply); if (!conn->reply_dont_free && conn->reply != NULL)
conn->reply = NULL; {
free(conn->reply);
conn->reply = NULL;
}
if (conn->reply_file != NULL) fclose(conn->reply_file);
conn->state = DONE; conn->state = DONE;
} }
} }