. 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:
* . 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 <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/queue.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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),
"<html><head><title>%d %s</title></head><body>\n"
"<h1>%s</h1>\n" /* errname */
@ -405,6 +420,7 @@ static void default_reply(struct connection *conn,
"Generated by %s on %s\n"
"</body></html>\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.<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);
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;
}
}