. 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:
parent
32b3855aeb
commit
02964cc5a6
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue