Rewrite quark from the ground up again
I noticed that the data structures didn't allow a flexible handling of the code while trying to extend it to support if-modified-since-responses. To tackle this, I refactored the data structures and proceeded to rewrite the server from the ground up, implementing all present features plus fixing a lot of bugs and introducing the 304 header handling as requested by many people. Please report bugs if you find them. While at it, I refactored the build system as well and updated all surrounding files respectively.
This commit is contained in:
parent
29d53f65b7
commit
6347e2ec3e
5 changed files with 512 additions and 383 deletions
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
ISC-License
|
ISC-License
|
||||||
|
|
||||||
(c) 2016 Laslo Hunhold <dev@frign.de>
|
(c) 2016-2017 Laslo Hunhold <dev@frign.de>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
|
41
Makefile
41
Makefile
|
@ -1,39 +1,36 @@
|
||||||
|
# See LICENSE file for copyright and license details
|
||||||
# quark - simple web server
|
# quark - simple web server
|
||||||
|
.POSIX:
|
||||||
|
|
||||||
include config.mk
|
include config.mk
|
||||||
|
|
||||||
all: quark
|
all: quark
|
||||||
|
|
||||||
quark: quark.o config.h config.mk
|
quark: quark.c arg.h config.h config.mk
|
||||||
${CC} -o $@ quark.o ${LDFLAGS}
|
$(CC) -o $@ $(CPPFLAGS) $(CFLAGS) quark.c $(LDFLAGS)
|
||||||
|
|
||||||
quark.o: quark.c config.h config.mk
|
|
||||||
${CC} -c ${CFLAGS} quark.c
|
|
||||||
|
|
||||||
config.h:
|
config.h:
|
||||||
cp config.def.h $@
|
cp config.def.h $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f quark quark.o quark-${VERSION}.tar.gz
|
rm -f quark
|
||||||
|
|
||||||
dist: clean
|
dist:
|
||||||
mkdir -p quark-${VERSION}
|
rm -rf "quark-$(VERSION)"
|
||||||
|
mkdir -p "quark-$(VERSION)"
|
||||||
cp -R LICENSE Makefile arg.h config.def.h config.mk quark.1 \
|
cp -R LICENSE Makefile arg.h config.def.h config.mk quark.1 \
|
||||||
quark.c quark-${VERSION}
|
quark.c "quark-$(VERSION)"
|
||||||
tar -cf quark-${VERSION}.tar quark-${VERSION}
|
tar -cf - "quark-$(VERSION)" | gzip -c > "quark-$(VERSION).tar.gz"
|
||||||
gzip quark-${VERSION}.tar
|
rm -rf "quark-$(VERSION)"
|
||||||
rm -rf quark-${VERSION}
|
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
mkdir -p ${DESTDIR}${PREFIX}/bin
|
mkdir -p "$(DESTDIR)$(PREFIX)/bin"
|
||||||
cp -f quark ${DESTDIR}${PREFIX}/bin
|
cp -f quark "$(DESTDIR)$(PREFIX)/bin"
|
||||||
chmod 755 ${DESTDIR}${PREFIX}/bin/quark
|
chmod 755 "$(DESTDIR)$(PREFIX)/bin/quark"
|
||||||
mkdir -p ${DESTDIR}${MANPREFIX}/man1
|
mkdir -p "$(DESTDIR)$(MANPREFIX)/man1"
|
||||||
cp quark.1 ${DESTDIR}${MANPREFIX}/man1/quark.1
|
cp quark.1 "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
|
||||||
chmod 644 ${DESTDIR}${MANPREFIX}/man1/quark.1
|
chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f ${DESTDIR}${PREFIX}/bin/quark
|
rm -f "$(DESTDIR)$(PREFIX)/bin/quark"
|
||||||
rm -f ${DESTDIR}${MANPREFIX}/man1/quark.1
|
rm -f "$(DESTDIR)$(MANPREFIX)/man1/quark.1"
|
||||||
|
|
||||||
.PHONY: all options clean dist install uninstall
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
static const char *host = "127.0.0.1";
|
static const char *host = "localhost";
|
||||||
static const char *port = "80";
|
static const char *port = "80";
|
||||||
static const char *servedir = ".";
|
static const char *servedir = ".";
|
||||||
static const char *docindex = "index.html";
|
static const char *docindex = "index.html";
|
||||||
|
@ -7,7 +7,8 @@ static const char *user = "nobody";
|
||||||
static const char *group = "nogroup";
|
static const char *group = "nogroup";
|
||||||
static const int maxnprocs = 512;
|
static const int maxnprocs = 512;
|
||||||
|
|
||||||
#define MAXREQLEN 4096 /* >= 4 */
|
#define HEADER_MAX 4096
|
||||||
|
#define FIELD_MAX 200
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
char *ext;
|
char *ext;
|
||||||
|
|
12
config.mk
12
config.mk
|
@ -5,16 +5,12 @@ VERSION = 0
|
||||||
|
|
||||||
# paths
|
# paths
|
||||||
PREFIX = /usr/local
|
PREFIX = /usr/local
|
||||||
MANPREFIX = ${PREFIX}/share/man
|
MANPREFIX = $(PREFIX)/man
|
||||||
|
|
||||||
# includes and libs
|
|
||||||
INCS = -I. -I/usr/include
|
|
||||||
LIBS = -L/usr/lib -lc
|
|
||||||
|
|
||||||
# flags
|
# flags
|
||||||
CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE
|
CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE
|
||||||
CFLAGS = -g -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
|
CFLAGS = -std=c99 -pedantic -Wall -Os
|
||||||
LDFLAGS = ${LIBS}
|
LDFLAGS = -s
|
||||||
|
|
||||||
# compiler and linker
|
# compiler and linker
|
||||||
CC = cc
|
CC = cc
|
||||||
|
|
825
quark.c
825
quark.c
|
@ -30,10 +30,60 @@ char *argv0;
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
enum stati {
|
#undef MIN
|
||||||
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
||||||
|
#undef MAX
|
||||||
|
#define MAX(x,y) ((x) > (y) ? (x) : (y))
|
||||||
|
|
||||||
|
#define TIMESTAMP_LEN 30
|
||||||
|
|
||||||
|
enum req_field {
|
||||||
|
REQ_HOST,
|
||||||
|
REQ_RANGE,
|
||||||
|
REQ_MOD,
|
||||||
|
NUM_REQ_FIELDS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *req_field_str[] = {
|
||||||
|
[REQ_HOST] = "Host",
|
||||||
|
[REQ_RANGE] = "Range",
|
||||||
|
[REQ_MOD] = "If-Modified-Since",
|
||||||
|
};
|
||||||
|
|
||||||
|
enum req_method {
|
||||||
|
M_OPTIONS,
|
||||||
|
M_GET,
|
||||||
|
M_HEAD,
|
||||||
|
M_POST,
|
||||||
|
M_PUT,
|
||||||
|
M_DELETE,
|
||||||
|
M_TRACE,
|
||||||
|
M_CONNECT,
|
||||||
|
NUM_REQ_METHODS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *req_method_str[] = {
|
||||||
|
[M_OPTIONS] = "OPTIONS",
|
||||||
|
[M_GET] = "GET",
|
||||||
|
[M_HEAD] = "HEAD",
|
||||||
|
[M_POST] = "POST",
|
||||||
|
[M_PUT] = "PUT",
|
||||||
|
[M_DELETE] = "DELETE",
|
||||||
|
[M_TRACE] = "TRACE",
|
||||||
|
[M_CONNECT] = "CONNECT",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct request {
|
||||||
|
enum req_method method;
|
||||||
|
char target[PATH_MAX];
|
||||||
|
char field[NUM_REQ_FIELDS][FIELD_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum status {
|
||||||
S_OK = 200,
|
S_OK = 200,
|
||||||
S_PARTIAL_CONTENT = 206,
|
S_PARTIAL_CONTENT = 206,
|
||||||
S_MOVED_PERMANENTLY = 301,
|
S_MOVED_PERMANENTLY = 301,
|
||||||
|
S_NOT_MODIFIED = 304,
|
||||||
S_BAD_REQUEST = 400,
|
S_BAD_REQUEST = 400,
|
||||||
S_FORBIDDEN = 403,
|
S_FORBIDDEN = 403,
|
||||||
S_NOT_FOUND = 404,
|
S_NOT_FOUND = 404,
|
||||||
|
@ -44,10 +94,11 @@ enum stati {
|
||||||
S_VERSION_NOT_SUPPORTED = 505,
|
S_VERSION_NOT_SUPPORTED = 505,
|
||||||
};
|
};
|
||||||
|
|
||||||
static char *statistr[] = {
|
static char *status_str[] = {
|
||||||
[S_OK] = "OK",
|
[S_OK] = "OK",
|
||||||
[S_PARTIAL_CONTENT] = "Partial Content",
|
[S_PARTIAL_CONTENT] = "Partial Content",
|
||||||
[S_MOVED_PERMANENTLY] = "Moved Permanently",
|
[S_MOVED_PERMANENTLY] = "Moved Permanently",
|
||||||
|
[S_NOT_MODIFIED] = "Not Modified",
|
||||||
[S_BAD_REQUEST] = "Bad Request",
|
[S_BAD_REQUEST] = "Bad Request",
|
||||||
[S_FORBIDDEN] = "Forbidden",
|
[S_FORBIDDEN] = "Forbidden",
|
||||||
[S_NOT_FOUND] = "Not Found",
|
[S_NOT_FOUND] = "Not Found",
|
||||||
|
@ -58,107 +109,14 @@ static char *statistr[] = {
|
||||||
[S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
|
[S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
|
||||||
};
|
};
|
||||||
|
|
||||||
#undef MIN
|
|
||||||
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
timestamp(time_t t)
|
timestamp(time_t t, char buf[TIMESTAMP_LEN])
|
||||||
{
|
{
|
||||||
static char s[30];
|
|
||||||
|
|
||||||
if (!t)
|
if (!t)
|
||||||
t = time(NULL);
|
t = time(NULL);
|
||||||
strftime(s, sizeof(s), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
|
strftime(buf, TIMESTAMP_LEN, "%a, %d %b %Y %T GMT", gmtime(&t));
|
||||||
|
|
||||||
return s;
|
return buf;
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
sendstatus(enum stati code, int fd, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
char buf[4096];
|
|
||||||
size_t written, buflen;
|
|
||||||
ssize_t ret;
|
|
||||||
long lower, upper, size;
|
|
||||||
|
|
||||||
buflen = snprintf(buf, sizeof(buf), "HTTP/1.1 %d %s\r\n", code,
|
|
||||||
statistr[code]);
|
|
||||||
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Date: %s\r\n", timestamp(0));
|
|
||||||
va_start(ap, fd);
|
|
||||||
switch (code) {
|
|
||||||
case S_OK: /* arg-list: mime, size */
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Type: %s\r\n",
|
|
||||||
va_arg(ap, char *));
|
|
||||||
if ((size = va_arg(ap, long)) >= 0) {
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Length: %ld\r\n",
|
|
||||||
size);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case S_PARTIAL_CONTENT: /* arg-list: mime, lower, upper, size */
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Type: %s\r\n",
|
|
||||||
va_arg(ap, char *));
|
|
||||||
lower = va_arg(ap, long);
|
|
||||||
upper = va_arg(ap, long);
|
|
||||||
size = va_arg(ap, long);
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Range: bytes %ld-%ld/%ld\r\n",
|
|
||||||
lower, upper, size);
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Length: %ld\r\n",
|
|
||||||
(upper - lower) + 1);
|
|
||||||
break;
|
|
||||||
case S_MOVED_PERMANENTLY: /* arg-list: host, url */
|
|
||||||
if (!strcmp(port, "80")) {
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Location: http://%s%s\r\n",
|
|
||||||
va_arg(ap, char *),
|
|
||||||
va_arg(ap, char *));
|
|
||||||
} else {
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Location: http://%s:%s%s\r\n",
|
|
||||||
va_arg(ap, char *), port,
|
|
||||||
va_arg(ap, char *));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case S_METHOD_NOT_ALLOWED: /* arg-list: none */
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Allow: GET\r\n");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Connection: close\r\n");
|
|
||||||
|
|
||||||
if (code != S_OK && code != S_PARTIAL_CONTENT) {
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"Content-Type: text/html\r\n");
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen,
|
|
||||||
"\r\n<!DOCTYPE html>\r\n<html>\r\n"
|
|
||||||
"\t<head><title>%d %s</title></head>"
|
|
||||||
"\r\n\t<body><h1>%d %s</h1></body>\r\n"
|
|
||||||
"</html>\r\n", code, statistr[code],
|
|
||||||
code, statistr[code]);
|
|
||||||
} else {
|
|
||||||
buflen += snprintf(buf + buflen, sizeof(buf) - buflen, "\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (written = 0; buflen > 0; written += ret, buflen -= ret) {
|
|
||||||
if ((ret = write(fd, buf + written, buflen)) < 0) {
|
|
||||||
code = S_REQUEST_TIMEOUT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
|
@ -190,9 +148,9 @@ encode(char src[PATH_MAX], char dest[PATH_MAX])
|
||||||
char *s;
|
char *s;
|
||||||
|
|
||||||
for (s = src, i = 0; *s; s++) {
|
for (s = src, i = 0; *s; s++) {
|
||||||
if (isalnum(*s) || *s == '~' || *s == '-' || *s == '.' ||
|
if (iscntrl(*s) || (unsigned char)*s > 127) {
|
||||||
*s == '_' || *s > 127) {
|
i += snprintf(dest + i, PATH_MAX - i, "%%%02X",
|
||||||
i += snprintf(dest + i, PATH_MAX - i, "%%%02X", *s);
|
(unsigned char)*s);
|
||||||
} else {
|
} else {
|
||||||
dest[i] = *s;
|
dest[i] = *s;
|
||||||
i++;
|
i++;
|
||||||
|
@ -203,246 +161,450 @@ encode(char src[PATH_MAX], char dest[PATH_MAX])
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
listdir(char *dir, int fd)
|
sendbuffer(int fd, char *buf, size_t buflen) {
|
||||||
|
size_t written;
|
||||||
|
ssize_t off;
|
||||||
|
|
||||||
|
for (written = 0; buflen > 0; written += off, buflen -= off) {
|
||||||
|
if ((off = write(fd, buf + written, buflen)) < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum status
|
||||||
|
sendstatus(int fd, enum status s)
|
||||||
{
|
{
|
||||||
struct dirent **e = NULL;
|
static char res[4096], t[TIMESTAMP_LEN];
|
||||||
static char buf[BUFSIZ];
|
size_t len;
|
||||||
size_t buflen;
|
|
||||||
ssize_t bread, written;
|
|
||||||
int dirlen, ret, i;
|
|
||||||
|
|
||||||
if ((dirlen = scandir(dir, &e, NULL, alphasort)) < 0) {
|
/* assemble error response */
|
||||||
return sendstatus(S_FORBIDDEN, fd);
|
len = snprintf(res, sizeof(res),
|
||||||
|
"HTTP/1.1 %d %s\r\n"
|
||||||
|
"Date: %s\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"%s"
|
||||||
|
"Content-Type: text/html\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"<!DOCTYPE html>\n<html>\n\t<head>\n"
|
||||||
|
"\t\t<title>%d %s</title>\n\t</head>\n\t<body>\n"
|
||||||
|
"\t\t<h1>%d %s</h1>\n\t</body>\n</html>",
|
||||||
|
s, status_str[s], timestamp(0, t),
|
||||||
|
(s == S_METHOD_NOT_ALLOWED) ? "Allow: HEAD, GET\r\n" : "",
|
||||||
|
s, status_str[s], s, status_str[s]);
|
||||||
|
|
||||||
|
return sendbuffer(fd, res, len) ? S_REQUEST_TIMEOUT : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
getrequest(int fd, struct request *r)
|
||||||
|
{
|
||||||
|
size_t hlen, i, mlen;
|
||||||
|
ssize_t off;
|
||||||
|
char h[HEADER_MAX], *p, *q;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* receive header
|
||||||
|
*/
|
||||||
|
for (hlen = 0; ;) {
|
||||||
|
if ((off = read(fd, h + hlen, sizeof(h) - hlen)) < 0) {
|
||||||
|
return sendstatus(fd, S_REQUEST_TIMEOUT);
|
||||||
|
} else if (off == 0) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if ((ret = sendstatus(S_OK, fd, "text/html", (long)-1)) != S_OK) {
|
hlen += off;
|
||||||
return ret;
|
if (hlen >= 4 && !memcmp(h + hlen - 4, "\r\n\r\n", 4)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if ((buflen = snprintf(buf, sizeof(buf), "<!DOCTYPE html>\r\n"
|
if (hlen == sizeof(h)) {
|
||||||
"<html>\r\n<head><title>Index of %s"
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
||||||
"</title></head>\r\n<body>\r\n"
|
|
||||||
"<a href=\"..\">..</a><br />\r\n",
|
|
||||||
dir)) >= sizeof(buf)) {
|
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
|
||||||
}
|
}
|
||||||
written = 0;
|
|
||||||
while (buflen > 0) {
|
|
||||||
if ((bread = write(fd, buf + written, buflen)) < 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
written += bread;
|
|
||||||
buflen -= bread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < dirlen; i++) {
|
/* remove terminating empty line */
|
||||||
if (e[i]->d_name[0] == '.') { /* hidden files, ., .. */
|
if (hlen < 2) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
hlen -= 2;
|
||||||
|
|
||||||
|
/* null-terminate the header */
|
||||||
|
h[hlen] = '\0';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse request line
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* METHOD */
|
||||||
|
for (i = 0; i < NUM_REQ_METHODS; i++) {
|
||||||
|
mlen = strlen(req_method_str[i]);
|
||||||
|
if (!strncmp(req_method_str[i], h, mlen)) {
|
||||||
|
r->method = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == NUM_REQ_METHODS) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a single space must follow the method */
|
||||||
|
if (h[mlen] != ' ') {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* basis for next step */
|
||||||
|
p = h + mlen + 1;
|
||||||
|
|
||||||
|
/* TARGET */
|
||||||
|
if (!(q = strchr(p, ' '))) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
*q = '\0';
|
||||||
|
if (q - p + 1 > PATH_MAX) {
|
||||||
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
||||||
|
}
|
||||||
|
memcpy(r->target, p, q - p + 1);
|
||||||
|
decode(r->target, r->target);
|
||||||
|
|
||||||
|
/* basis for next step */
|
||||||
|
p = q + 1;
|
||||||
|
|
||||||
|
/* HTTP-VERSION */
|
||||||
|
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
p += sizeof("HTTP/") - 1;
|
||||||
|
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
||||||
|
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
||||||
|
return sendstatus(fd, S_VERSION_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
p += sizeof("1.*") - 1;
|
||||||
|
|
||||||
|
/* check terminator */
|
||||||
|
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* basis for next step */
|
||||||
|
p += sizeof("\r\n") - 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse request-fields
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* empty all fields */
|
||||||
|
for (i = 0; i < NUM_REQ_FIELDS; i++) {
|
||||||
|
r->field[i][0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* match field type */
|
||||||
|
for (; *p != '\0';) {
|
||||||
|
for (i = 0; i < NUM_REQ_FIELDS; i++) {
|
||||||
|
if (!strncmp(p, req_field_str[i],
|
||||||
|
strlen(req_field_str[i]))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == NUM_REQ_FIELDS) {
|
||||||
|
/* unmatched field, skip this line */
|
||||||
|
if (!(q = strstr(p, "\r\n"))) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
p = q + (sizeof("\r\n") - 1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((buflen = snprintf(buf, sizeof(buf), "<a href=\"%s"
|
|
||||||
"\">%s</a><br />\r\n", e[i]->d_name,
|
p += strlen(req_field_str[i]);
|
||||||
e[i]->d_name)) >= sizeof(buf)) {
|
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
/* a single colon must follow the field name */
|
||||||
}
|
if (*p != ':') {
|
||||||
written = 0;
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
while (buflen > 0) {
|
|
||||||
if ((bread = write(fd, buf + written, buflen)) < 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
written += bread;
|
|
||||||
buflen -= bread;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((buflen = snprintf(buf, sizeof(buf), "\r\n</body></html>\r\n"))
|
/* skip whitespace */
|
||||||
|
for (++p; *p == ' '; p++)
|
||||||
|
;
|
||||||
|
|
||||||
|
/* extract field content */
|
||||||
|
if (!(q = strstr(p, "\r\n"))) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
*q = '\0';
|
||||||
|
if (q - p + 1 > FIELD_MAX) {
|
||||||
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
||||||
|
}
|
||||||
|
memcpy(r->field[i], p, q - p + 1);
|
||||||
|
|
||||||
|
/* go to next line */
|
||||||
|
p = q + (sizeof("\r\n") - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum status
|
||||||
|
senddir(int fd, char *name, struct request *r)
|
||||||
|
{
|
||||||
|
struct dirent **e;
|
||||||
|
size_t len, i;
|
||||||
|
int dirlen;
|
||||||
|
static char resheader[HEADER_MAX], buf[BUFSIZ], t[TIMESTAMP_LEN];
|
||||||
|
|
||||||
|
/* read directory */
|
||||||
|
if ((dirlen = scandir(name, &e, NULL, alphasort)) < 0) {
|
||||||
|
return sendstatus(fd, S_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send header as late as possible */
|
||||||
|
len = snprintf(resheader, sizeof(resheader),
|
||||||
|
"HTTP/1.1 %d %s\r\n"
|
||||||
|
"Date: %s\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Content-Type: text/html\r\n"
|
||||||
|
"\r\n",
|
||||||
|
S_OK, status_str[S_OK], timestamp(0, t));
|
||||||
|
|
||||||
|
if (sendbuffer(fd, resheader, len)) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->method == M_GET) {
|
||||||
|
/* listing header */
|
||||||
|
if ((len = snprintf(buf, sizeof(buf),
|
||||||
|
"<!DOCTYPE html>\n<html>\n\t<head>"
|
||||||
|
"<title>Index of %s</title></head>\n"
|
||||||
|
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
||||||
|
name)) >= sizeof(buf)) {
|
||||||
|
return S_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
if (sendbuffer(fd, buf, len)) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listing */
|
||||||
|
for (i = 0; i < dirlen; i++) {
|
||||||
|
/* skip hidden files, "." and ".." */
|
||||||
|
if (e[i]->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* entry line */
|
||||||
|
if ((len = snprintf(buf, sizeof(buf),
|
||||||
|
"<br />\n\t\t<a href=\"%s\">%s</a>",
|
||||||
|
e[i]->d_name, e[i]->d_name))
|
||||||
>= sizeof(buf)) {
|
>= sizeof(buf)) {
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
return S_INTERNAL_SERVER_ERROR;
|
||||||
}
|
}
|
||||||
written = 0;
|
if (sendbuffer(fd, buf, len)) {
|
||||||
while (buflen > 0) {
|
return S_REQUEST_TIMEOUT;
|
||||||
if ((bread = write(fd, buf + written, buflen)) < 0) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listing footer */
|
||||||
|
if (sendbuffer(fd, "\n\t</body>\n</html>",
|
||||||
|
sizeof("\n\t</body>\n</html>") - 1)) {
|
||||||
return S_REQUEST_TIMEOUT;
|
return S_REQUEST_TIMEOUT;
|
||||||
}
|
}
|
||||||
written += bread;
|
|
||||||
buflen -= bread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static enum status
|
||||||
handle(int infd, char **url)
|
sendfile(int fd, char *name, struct request *r, struct stat *st, char *mime,
|
||||||
|
off_t lower, off_t upper)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
enum status s;
|
||||||
|
size_t len;
|
||||||
|
ssize_t bread, bwritten;
|
||||||
|
off_t remaining;
|
||||||
|
int range;
|
||||||
|
static char resheader[HEADER_MAX], buf[BUFSIZ], *p, t1[TIMESTAMP_LEN],
|
||||||
|
t2[TIMESTAMP_LEN];
|
||||||
|
|
||||||
|
/* open file */
|
||||||
|
if (!(fp = fopen(name, "r"))) {
|
||||||
|
return sendstatus(fd, S_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* seek to lower bound */
|
||||||
|
if (fseek(fp, lower, SEEK_SET)) {
|
||||||
|
return sendstatus(fd, S_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send header as late as possible */
|
||||||
|
range = r->field[REQ_RANGE][0];
|
||||||
|
s = range ? S_PARTIAL_CONTENT : S_OK;
|
||||||
|
|
||||||
|
len = snprintf(resheader, sizeof(resheader),
|
||||||
|
"HTTP/1.1 %d %s\r\n"
|
||||||
|
"Date: %s\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Last-Modified: %s\r\n"
|
||||||
|
"Content-Type: %s\r\n"
|
||||||
|
"Content-Length: %zu\r\n",
|
||||||
|
s, status_str[s], timestamp(0, t1),
|
||||||
|
timestamp(st->st_mtim.tv_sec, t2), mime, upper - lower);
|
||||||
|
if (range) {
|
||||||
|
len += snprintf(resheader + len, sizeof(resheader) - len,
|
||||||
|
"Content-Range: bytes %zu-%zu/%zu\r\n",
|
||||||
|
lower, upper - 1, st->st_size);
|
||||||
|
}
|
||||||
|
len += snprintf(resheader + len, sizeof(resheader) - len, "\r\n");
|
||||||
|
|
||||||
|
if (sendbuffer(fd, resheader, len)) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r->method == M_GET) {
|
||||||
|
/* write data until upper bound is hit */
|
||||||
|
remaining = upper - lower + 1;
|
||||||
|
|
||||||
|
while ((bread = fread(buf, 1, MIN(sizeof(buf), remaining), fp))) {
|
||||||
|
if (bread < 0) {
|
||||||
|
return S_INTERNAL_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
remaining -= bread;
|
||||||
|
p = buf;
|
||||||
|
while (bread > 0) {
|
||||||
|
bwritten = write(fd, p, bread);
|
||||||
|
if (bwritten <= 0) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
bread -= bwritten;
|
||||||
|
p += bwritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum status
|
||||||
|
sendresponse(int fd, struct request *r)
|
||||||
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
size_t reqlen, urllen, i;
|
struct tm tm;
|
||||||
ssize_t off, buflen, written;
|
size_t len, i;
|
||||||
long lower, upper, fsize, remaining;
|
off_t lower, upper;
|
||||||
int needredirect, ret;
|
static char realtarget[PATH_MAX], resheader[HEADER_MAX],
|
||||||
static char req[MAXREQLEN], buf[BUFSIZ],
|
tmptarget[PATH_MAX], t[TIMESTAMP_LEN];
|
||||||
urlenc[PATH_MAX], urldec[PATH_MAX],
|
|
||||||
urldecnorm[PATH_MAX], urldecnormind[PATH_MAX],
|
|
||||||
reqhost[256], range[128], modsince[30];
|
|
||||||
char *p, *q, *mime;
|
char *p, *q, *mime;
|
||||||
|
|
||||||
/* get request header */
|
/* check method */
|
||||||
for (reqlen = 0; ;) {
|
if (r->method != M_GET && r->method != M_HEAD) {
|
||||||
if ((off = read(infd, req + reqlen, MAXREQLEN - reqlen)) < 0) {
|
return sendstatus(fd, S_METHOD_NOT_ALLOWED);
|
||||||
return sendstatus(S_REQUEST_TIMEOUT, infd);
|
|
||||||
} else if (off == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
reqlen += off;
|
|
||||||
if (reqlen >= 4 && !memcmp(req + reqlen - 4, "\r\n\r\n", 4)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (reqlen == MAXREQLEN) {
|
|
||||||
return sendstatus(S_REQUEST_TOO_LARGE, infd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (reqlen < 2) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
reqlen -= 2; /* remove last \r\n */
|
|
||||||
req[reqlen] = '\0'; /* make it safe */
|
|
||||||
|
|
||||||
/* parse request line */
|
|
||||||
if (reqlen < 3) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
} else if (strncmp(req, "GET", sizeof("GET") - 1)) {
|
|
||||||
return sendstatus(S_METHOD_NOT_ALLOWED, infd);
|
|
||||||
} else if (req[3] != ' ') {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
for (p = req + sizeof("GET ") - 1; *p && *p != ' '; p++)
|
|
||||||
;
|
|
||||||
if (!*p) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
*p = '\0';
|
|
||||||
if (snprintf(urlenc, sizeof(urlenc), "%s",
|
|
||||||
req + sizeof("GET ") - 1) >= sizeof(urlenc)) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
*url = urldecnorm;
|
|
||||||
if (!strlen(urlenc)) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
p += sizeof(" ") - 1;
|
|
||||||
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
p += sizeof("HTTP/") - 1;
|
|
||||||
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
|
||||||
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
|
||||||
return sendstatus(S_VERSION_NOT_SUPPORTED, infd);
|
|
||||||
}
|
|
||||||
p += sizeof("1.*") - 1;
|
|
||||||
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
|
||||||
}
|
|
||||||
p += sizeof("\r\n") - 1;
|
|
||||||
|
|
||||||
/* parse header fields */
|
|
||||||
for (; (q = strstr(p, "\r\n")); p = q + sizeof("\r\n") - 1) {
|
|
||||||
*q = '\0';
|
|
||||||
if (!strncmp(p, "Host:", sizeof("Host:") - 1)) {
|
|
||||||
p += sizeof("Host:") - 1;
|
|
||||||
while (isspace(*p)) {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (snprintf(reqhost, sizeof(reqhost), "%s", p) >=
|
|
||||||
sizeof(reqhost)) {
|
|
||||||
return sendstatus(S_INTERNAL_SERVER_ERROR,
|
|
||||||
infd);
|
|
||||||
}
|
|
||||||
} else if (!strncmp(p, "Range:", sizeof("Range:") - 1)) {
|
|
||||||
p += sizeof("Range:") - 1;
|
|
||||||
while (isspace(*p)) {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (snprintf(range, sizeof(range), "%s", p) >=
|
|
||||||
sizeof(range)) {
|
|
||||||
return sendstatus(S_INTERNAL_SERVER_ERROR,
|
|
||||||
infd);
|
|
||||||
}
|
|
||||||
} else if (!strncmp(p, "If-Modified-Since:",
|
|
||||||
sizeof("If-Modified-Since:") - 1)) {
|
|
||||||
p+= sizeof("If-Modified-Since:") - 1;
|
|
||||||
while (isspace(*p)) {
|
|
||||||
p++;
|
|
||||||
}
|
|
||||||
if (snprintf(modsince, sizeof(modsince), "%s", p) >=
|
|
||||||
sizeof(modsince)) {
|
|
||||||
return sendstatus(S_INTERNAL_SERVER_ERROR,
|
|
||||||
infd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* normalization */
|
/* normalize target */
|
||||||
needredirect = 0;
|
if (!realpath(r->target, realtarget)) {
|
||||||
decode(urlenc, urldec);
|
return sendstatus(fd, (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND);
|
||||||
if (!realpath(urldec, urldecnorm)) {
|
|
||||||
/* todo: break up the cases */
|
|
||||||
return sendstatus((errno == EACCES) ? S_FORBIDDEN :
|
|
||||||
S_NOT_FOUND, infd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hidden path? */
|
/* reject hidden target */
|
||||||
if (urldecnorm[0] == '.' || strstr(urldecnorm, "/.")) {
|
if (realtarget[0] == '.' || strstr(realtarget, "/.")) {
|
||||||
return sendstatus(S_FORBIDDEN, infd);
|
return sendstatus(fd, S_FORBIDDEN);
|
||||||
}
|
}
|
||||||
/* check if file or directory */
|
|
||||||
if (stat(urldecnorm, &st) < 0) {
|
/* stat the target */
|
||||||
/* todo: break up the cases */
|
if (stat(realtarget, &st) < 0) {
|
||||||
return sendstatus(S_NOT_FOUND, infd);
|
return sendstatus(fd, (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
/* add / at the end, was removed by realpath */
|
/* add / to target if not present */
|
||||||
urllen = strlen(urldecnorm);
|
len = strlen(realtarget);
|
||||||
if (urldecnorm[urllen - 1] != '/') {
|
if (len == PATH_MAX - 2) {
|
||||||
urldecnorm[urllen + 1] = '\0';
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
||||||
urldecnorm[urllen] = '/';
|
}
|
||||||
|
if (len && realtarget[len - 1] != '/') {
|
||||||
|
realtarget[len] = '/';
|
||||||
|
realtarget[len + 1] = '\0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* is a / at the end on the raw string? */
|
/* redirect if targets differ */
|
||||||
urllen = strlen(urldec);
|
if (strcmp(r->target, realtarget)) {
|
||||||
if (urldec[urllen - 1] != '/') {
|
/* encode realtarget */
|
||||||
needredirect = 1;
|
encode(realtarget, tmptarget);
|
||||||
} else if (!needredirect) {
|
|
||||||
/* check index */
|
len = snprintf(resheader, sizeof(resheader),
|
||||||
if (snprintf(urldecnormind, sizeof(urldecnormind),
|
"HTTP/1.1 %d %s\r\n"
|
||||||
"%s/%s", urldecnorm, docindex) >=
|
"Date: %s\r\n"
|
||||||
sizeof(urldecnorm)) {
|
"Connection: close\r\n"
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
"Location: %s\r\n"
|
||||||
|
"\r\n",
|
||||||
|
S_MOVED_PERMANENTLY,
|
||||||
|
status_str[S_MOVED_PERMANENTLY], timestamp(0, t),
|
||||||
|
tmptarget);
|
||||||
|
return sendbuffer(fd, resheader, len) ? S_REQUEST_TIMEOUT :
|
||||||
|
S_MOVED_PERMANENTLY;
|
||||||
}
|
}
|
||||||
if (stat(urldecnormind, &st) < 0) {
|
|
||||||
/* no index, serve dir */
|
if (S_ISDIR(st.st_mode)) {
|
||||||
if (!listdirs) {
|
/* append docindex to target */
|
||||||
return sendstatus(S_FORBIDDEN, infd);
|
if (snprintf(realtarget, sizeof(realtarget), "%s%s",
|
||||||
|
r->target, docindex) >= sizeof(realtarget)) {
|
||||||
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
||||||
}
|
}
|
||||||
return listdir(urldecnorm, infd);
|
|
||||||
|
/* stat the docindex, which must be a regular file */
|
||||||
|
if (stat(realtarget, &st) < 0 || !S_ISREG(st.st_mode)) {
|
||||||
|
if (listdirs) {
|
||||||
|
/* remove index suffix and serve dir */
|
||||||
|
realtarget[strlen(realtarget) -
|
||||||
|
strlen(docindex)] = '\0';
|
||||||
|
return senddir(fd, realtarget, r);
|
||||||
|
} else {
|
||||||
|
/* reject */
|
||||||
|
if (!S_ISREG(st.st_mode) || errno == EACCES) {
|
||||||
|
return sendstatus(fd, S_FORBIDDEN);
|
||||||
|
} else {
|
||||||
|
return sendstatus(fd, S_NOT_FOUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (strcmp(urldec, urldecnorm)) {
|
|
||||||
needredirect = 1;
|
|
||||||
}
|
}
|
||||||
if (needredirect) {
|
|
||||||
encode(urldecnorm, urlenc);
|
/* modified since */
|
||||||
return sendstatus(S_MOVED_PERMANENTLY, infd, urlenc,
|
if (r->field[REQ_MOD][0]) {
|
||||||
reqhost[0] ? reqhost : host);
|
/* parse field */
|
||||||
|
if (!strptime(r->field[REQ_MOD], "%a, %d %b %Y %T GMT", &tm)) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compare with last modification date of the file */
|
||||||
|
if (difftime(st.st_mtim.tv_sec, mktime(&tm)) <= 0) {
|
||||||
|
len = snprintf(resheader, sizeof(resheader),
|
||||||
|
"HTTP/1.1 %d %s\r\n"
|
||||||
|
"Date: %s\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"\r\n",
|
||||||
|
S_NOT_MODIFIED, status_str[S_NOT_MODIFIED],
|
||||||
|
timestamp(0, t));
|
||||||
|
if (sendbuffer(fd, resheader, len)) {
|
||||||
|
return S_REQUEST_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* range */
|
/* range */
|
||||||
lower = 0;
|
lower = 0;
|
||||||
upper = LONG_MAX;
|
upper = st.st_size;
|
||||||
if (range[0]) {
|
|
||||||
if (strncmp(range, "bytes=", sizeof("bytes=") - 1)) {
|
if (r->field[REQ_RANGE][0]) {
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
/* parse field */
|
||||||
|
p = r->field[REQ_RANGE];
|
||||||
|
|
||||||
|
if (strncmp(p, "bytes=", sizeof("bytes=") - 1)) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
p = range + sizeof("bytes=") - 1;
|
p += sizeof("bytes=") - 1;
|
||||||
|
|
||||||
if (!(q = strchr(p, '-'))) {
|
if (!(q = strchr(p, '-'))) {
|
||||||
return sendstatus(S_BAD_REQUEST, infd);
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
*(q++) = '\0';
|
*(q++) = '\0';
|
||||||
if (p[0]) {
|
if (p[0]) {
|
||||||
|
@ -451,74 +613,40 @@ handle(int infd, char **url)
|
||||||
if (q[0]) {
|
if (q[0]) {
|
||||||
upper = atoi(q);
|
upper = atoi(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* sanitize range */
|
||||||
|
if (lower < 0 || upper < 0 || lower > upper) {
|
||||||
|
return sendstatus(fd, S_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
upper = MIN(st.st_size, upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* serve file */
|
/* mime */
|
||||||
if (!(fp = fopen(urldecnorm, "r"))) {
|
|
||||||
return sendstatus(S_FORBIDDEN, infd);
|
|
||||||
}
|
|
||||||
mime = "text/plain";
|
mime = "text/plain";
|
||||||
if ((p = strrchr(urldecnorm, '.'))) {
|
if ((p = strrchr(realtarget, '.'))) {
|
||||||
for (i = 0; i < sizeof(mimes)/sizeof(*mimes); i++) {
|
for (i = 0; i < sizeof(mimes) / sizeof(*mimes); i++) {
|
||||||
if (!strcmp(mimes[i].ext, p + 1)) {
|
if (!strcmp(mimes[i].ext, p + 1)) {
|
||||||
mime = mimes[i].type;
|
mime = mimes[i].type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fseek(fp, 0, SEEK_END) || (fsize = ftell(fp)) < 0) {
|
|
||||||
return sendstatus(S_INTERNAL_SERVER_ERROR, infd);
|
|
||||||
}
|
|
||||||
rewind(fp);
|
|
||||||
if (fsize && upper > fsize) {
|
|
||||||
upper = fsize - 1;
|
|
||||||
}
|
|
||||||
if (fseek(fp, lower, SEEK_SET)) {
|
|
||||||
return sendstatus(S_INTERNAL_SERVER_ERROR, infd);
|
|
||||||
}
|
|
||||||
if (!range[0]) {
|
|
||||||
if ((ret = sendstatus(S_OK, infd, mime, (long)fsize)) != S_OK) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((ret = sendstatus(S_PARTIAL_CONTENT, infd, mime, lower,
|
|
||||||
upper, fsize)) != S_PARTIAL_CONTENT) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remaining = (upper - lower) + 1;
|
|
||||||
while ((buflen = fread(buf, 1, MIN(sizeof(buf), remaining),
|
|
||||||
fp))) {
|
|
||||||
remaining -= buflen;
|
|
||||||
if (buflen < 0) {
|
|
||||||
return S_INTERNAL_SERVER_ERROR;
|
|
||||||
}
|
|
||||||
p = buf;
|
|
||||||
while (buflen > 0) {
|
|
||||||
written = write(infd, p, buflen);
|
|
||||||
if (written <= 0) {
|
|
||||||
return S_REQUEST_TIMEOUT;
|
|
||||||
}
|
|
||||||
buflen -= written;
|
|
||||||
p += written;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return S_OK;
|
return sendfile(fd, realtarget, r, &st, mime, lower, upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
serve(int insock)
|
serve(int insock)
|
||||||
{
|
{
|
||||||
|
struct request r;
|
||||||
struct sockaddr_storage in_sa;
|
struct sockaddr_storage in_sa;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
pid_t p;
|
pid_t p;
|
||||||
socklen_t in_sa_len;
|
socklen_t in_sa_len;
|
||||||
time_t t;
|
time_t t;
|
||||||
enum stati status;
|
enum status status;
|
||||||
int infd;
|
int infd;
|
||||||
char inip4[INET_ADDRSTRLEN], inip6[INET6_ADDRSTRLEN], *url = "",
|
char inip4[INET_ADDRSTRLEN], inip6[INET6_ADDRSTRLEN], tstmp[25];
|
||||||
tstmp[25];
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
/* accept incoming connections */
|
/* accept incoming connections */
|
||||||
|
@ -530,9 +658,10 @@ serve(int insock)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fork and handle */
|
||||||
switch ((p = fork())) {
|
switch ((p = fork())) {
|
||||||
case -1:
|
case -1:
|
||||||
fprintf(stderr, "%s: fork: %s", argv0,
|
fprintf(stderr, "%s: fork: %s\n", argv0,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -550,7 +679,10 @@ serve(int insock)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = handle(infd, &url);
|
/* handle request */
|
||||||
|
if (!(status = getrequest(infd, &r))) {
|
||||||
|
status = sendresponse(infd, &r);
|
||||||
|
}
|
||||||
|
|
||||||
/* write output to log */
|
/* write output to log */
|
||||||
t = time(NULL);
|
t = time(NULL);
|
||||||
|
@ -561,12 +693,14 @@ serve(int insock)
|
||||||
inet_ntop(AF_INET,
|
inet_ntop(AF_INET,
|
||||||
&(((struct sockaddr_in *)&in_sa)->sin_addr),
|
&(((struct sockaddr_in *)&in_sa)->sin_addr),
|
||||||
inip4, sizeof(inip4));
|
inip4, sizeof(inip4));
|
||||||
printf("%s\t%s\t%d\t%s\n", tstmp, inip4, status, url);
|
printf("%s\t%s\t%d\t%s\n", tstmp, inip4,
|
||||||
|
status, r.target);
|
||||||
} else {
|
} else {
|
||||||
inet_ntop(AF_INET6,
|
inet_ntop(AF_INET6,
|
||||||
&(((struct sockaddr_in6*)&in_sa)->sin6_addr),
|
&(((struct sockaddr_in6*)&in_sa)->sin6_addr),
|
||||||
inip6, sizeof(inip6));
|
inip6, sizeof(inip6));
|
||||||
printf("%s\t%s\t%d\t%s\n", tstmp, inip6, status, url);
|
printf("%s\t%s\t%d\t%s\n", tstmp, inip6,
|
||||||
|
status, r.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clean up and finish */
|
/* clean up and finish */
|
||||||
|
@ -575,6 +709,7 @@ serve(int insock)
|
||||||
close(infd);
|
close(infd);
|
||||||
_exit(0);
|
_exit(0);
|
||||||
default:
|
default:
|
||||||
|
/* close the connection in the parent */
|
||||||
close(infd);
|
close(infd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue