diff --git a/Makefile b/Makefile index f5208ac..7573552 100644 --- a/Makefile +++ b/Makefile @@ -4,16 +4,16 @@ include config.mk -COMPONENTS = util sock http resp dirl +COMPONENTS = data http sock util dirl all: quark -util.o: util.c util.h config.mk -sock.o: sock.c sock.h util.h config.mk -http.o: http.c http.h util.h http.h resp.h config.h config.mk -resp.o: resp.c resp.h util.h http.h dirl.h config.mk +data.o: data.c data.h util.h http.h dirl.h config.mk dirl.o: dirl.c dirl.h util.h http.h config.mk +http.o: http.c http.h util.h http.h data.h config.h config.mk main.o: main.c util.h sock.h http.h arg.h config.h config.mk +sock.o: sock.c sock.h util.h config.mk +util.o: util.c util.h config.mk quark: $(COMPONENTS:=.o) $(COMPONENTS:=.h) main.o config.mk $(CC) -o $@ $(CPPFLAGS) $(CFLAGS) $(COMPONENTS:=.o) main.o $(LDFLAGS) diff --git a/resp.c b/data.c similarity index 58% rename from resp.c rename to data.c index ea9d388..da3aa24 100644 --- a/resp.c +++ b/data.c @@ -8,7 +8,7 @@ #include #include "http.h" -#include "resp.h" +#include "data.h" #include "util.h" #include "dirl.h" @@ -26,10 +26,68 @@ compareent(const struct dirent **d1, const struct dirent **d2) return strcmp((*d1)->d_name, (*d2)->d_name); } -enum status -resp_dir(int fd, const struct response *res) +static char * +suffix(int t) { - enum status ret; + switch (t) { + case DT_FIFO: return "|"; + case DT_DIR: return "/"; + case DT_LNK: return "@"; + case DT_SOCK: return "="; + } + + return ""; +} + +static void +html_escape(const char *src, char *dst, size_t dst_siz) +{ + const struct { + char c; + char *s; + } escape[] = { + { '&', "&" }, + { '<', "<" }, + { '>', ">" }, + { '"', """ }, + { '\'', "'" }, + }; + size_t i, j, k, esclen; + + for (i = 0, j = 0; src[i] != '\0'; i++) { + for (k = 0; k < LEN(escape); k++) { + if (src[i] == escape[k].c) { + break; + } + } + if (k == LEN(escape)) { + /* no escape char at src[i] */ + if (j == dst_siz - 1) { + /* silent truncation */ + break; + } else { + dst[j++] = src[i]; + } + } else { + /* escape char at src[i] */ + esclen = strlen(escape[k].s); + + if (j >= dst_siz - esclen) { + /* silent truncation */ + break; + } else { + memcpy(&dst[j], escape[k].s, esclen); + j += esclen; + } + } + } + dst[j] = '\0'; +} + +enum status +data_send_dirlisting(int fd, const struct response *res) +{ + enum status ret = 0; struct dirent **e; size_t i; int dirlen; @@ -77,7 +135,22 @@ cleanup: } enum status -resp_file(int fd, const struct response *res) +data_send_error(int fd, const struct response *res) +{ + if (dprintf(fd, + "\n\n\t\n" + "\t\t%d %s\n\t\n\t\n" + "\t\t

%d %s

\n\t\n\n", + res->status, status_str[res->status], + res->status, status_str[res->status]) < 0) { + return S_REQUEST_TIMEOUT; + } + + return 0; +} + +enum status +data_send_file(int fd, const struct response *res) { FILE *fp; enum status ret = 0; diff --git a/data.h b/data.h new file mode 100644 index 0000000..91aedf5 --- /dev/null +++ b/data.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef DATA_H +#define DATA_H + +#include "http.h" + +enum status data_send_dirlisting(int, const struct response *); +enum status data_send_error(int, const struct response *); +enum status data_send_file(int, const struct response *); + +#endif /* DATA_H */ diff --git a/http.c b/http.c index cfd439c..96e673a 100644 --- a/http.c +++ b/http.c @@ -17,8 +17,8 @@ #include #include "config.h" +#include "data.h" #include "http.h" -#include "resp.h" #include "util.h" const char *req_field_str[] = { @@ -58,6 +58,12 @@ const char *res_field_str[] = { [RES_CONTENT_TYPE] = "Content-Type", }; +enum status (* const body_fct[])(int, const struct response *) = { + [RESTYPE_ERROR] = data_send_error, + [RESTYPE_FILE] = data_send_file, + [RESTYPE_DIRLISTING] = data_send_dirlisting, +}; + enum status http_send_header(int fd, const struct response *res) { @@ -89,40 +95,7 @@ http_send_header(int fd, const struct response *res) return S_REQUEST_TIMEOUT; } - return res->status; -} - -enum status -http_send_status(int fd, enum status s) -{ - enum status sendstatus; - - struct response res = { - .status = s, - .field[RES_CONTENT_TYPE] = "text/html; charset=utf-8", - }; - - if (s == S_METHOD_NOT_ALLOWED) { - if (esnprintf(res.field[RES_ALLOW], - sizeof(res.field[RES_ALLOW]), "%s", - "Allow: GET, HEAD")) { - return S_INTERNAL_SERVER_ERROR; - } - } - - if ((sendstatus = http_send_header(fd, &res)) != s) { - return sendstatus; - } - - if (dprintf(fd, - "\n\n\t\n" - "\t\t%d %s\n\t\n\t\n" - "\t\t

%d %s

\n\t\n\n", - s, status_str[s], s, status_str[s]) < 0) { - return S_REQUEST_TIMEOUT; - } - - return s; + return 0; } static void @@ -535,11 +508,11 @@ parse_range(const char *str, size_t size, size_t *lower, size_t *upper) #undef RELPATH #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) -enum status +void http_prepare_response(const struct request *req, struct response *res, const struct server *srv) { - enum status returnstatus; + enum status s; struct in6_addr addr; struct stat st; struct tm tm = { 0 }; @@ -556,7 +529,8 @@ http_prepare_response(const struct request *req, struct response *res, /* make a working copy of the URI and normalize it */ memcpy(realuri, req->uri, sizeof(realuri)); if (normabspath(realuri)) { - return S_BAD_REQUEST; + s = S_BAD_REQUEST; + goto err; } /* match vhost */ @@ -571,13 +545,15 @@ http_prepare_response(const struct request *req, struct response *res, } } if (i == srv->vhost_len) { - return S_NOT_FOUND; + s = S_NOT_FOUND; + goto err; } /* if we have a vhost prefix, prepend it to the URI */ if (vhost->prefix && prepend(realuri, LEN(realuri), vhost->prefix)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } } @@ -595,7 +571,8 @@ http_prepare_response(const struct request *req, struct response *res, /* swap out URI prefix */ memmove(realuri, realuri + len, strlen(realuri) + 1); if (prepend(realuri, LEN(realuri), srv->map[i].to)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } break; } @@ -603,19 +580,22 @@ http_prepare_response(const struct request *req, struct response *res, /* normalize URI again, in case we introduced dirt */ if (normabspath(realuri)) { - return S_BAD_REQUEST; + s = S_BAD_REQUEST; + goto err; } /* stat the relative path derived from the URI */ if (stat(RELPATH(realuri), &st) < 0) { - return (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND; + s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND; + goto err; } if (S_ISDIR(st.st_mode)) { /* append '/' to URI if not present */ len = strlen(realuri); if (len + 1 + 1 > PATH_MAX) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } if (len > 0 && realuri[len - 1] != '/') { realuri[len] = '/'; @@ -629,7 +609,8 @@ http_prepare_response(const struct request *req, struct response *res, */ if (strstr(realuri, "/.") && strncmp(realuri, "/.well-known/", sizeof("/.well-known/") - 1)) { - return S_FORBIDDEN; + s = S_FORBIDDEN; + goto err; } /* @@ -658,7 +639,8 @@ http_prepare_response(const struct request *req, struct response *res, * honor that later when we fill the "Location"-field */ if ((ipv6host = inet_pton(AF_INET6, targethost, &addr)) < 0) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } /* write location to response struct */ @@ -669,18 +651,20 @@ http_prepare_response(const struct request *req, struct response *res, targethost, ipv6host ? "]" : "", hasport ? ":" : "", hasport ? srv->port : "", tmpuri)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } } else { /* write relative redirection URI to response struct */ if (esnprintf(res->field[RES_LOCATION], sizeof(res->field[RES_LOCATION]), "%s", tmpuri)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } } - return 0; + return; } else { /* * the URI is well-formed, we can now write the URI into @@ -689,11 +673,13 @@ http_prepare_response(const struct request *req, struct response *res, * into the actual response-path */ if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } if (esnprintf(res->path, sizeof(res->path), "%s%s", vhost ? vhost->dir : "", RELPATH(req->uri))) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } } @@ -704,7 +690,8 @@ http_prepare_response(const struct request *req, struct response *res, */ if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s", req->uri, srv->docindex)) { - return S_REQUEST_TOO_LARGE; + s = S_REQUEST_TOO_LARGE; + goto err; } /* stat the docindex, which must be a regular file */ @@ -718,14 +705,16 @@ http_prepare_response(const struct request *req, struct response *res, if (esnprintf(res->field[RES_CONTENT_TYPE], sizeof(res->field[RES_CONTENT_TYPE]), "%s", "text/html; charset=utf-8")) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } - return 0; + return; } else { /* reject */ - return (!S_ISREG(st.st_mode) || errno == EACCES) ? - S_FORBIDDEN : S_NOT_FOUND; + s = (!S_ISREG(st.st_mode) || errno == EACCES) ? + S_FORBIDDEN : S_NOT_FOUND; + goto err; } } } @@ -735,32 +724,33 @@ http_prepare_response(const struct request *req, struct response *res, /* parse field */ if (!strptime(req->field[REQ_IF_MODIFIED_SINCE], "%a, %d %b %Y %T GMT", &tm)) { - return S_BAD_REQUEST; + s = S_BAD_REQUEST; + goto err; } /* compare with last modification date of the file */ if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) { res->status = S_NOT_MODIFIED; - return 0; + return; } } /* range */ - if ((returnstatus = parse_range(req->field[REQ_RANGE], st.st_size, - &(res->file.lower), - &(res->file.upper)))) { - if (returnstatus == S_RANGE_NOT_SATISFIABLE) { + if ((s = parse_range(req->field[REQ_RANGE], st.st_size, + &(res->file.lower), &(res->file.upper)))) { + if (s == S_RANGE_NOT_SATISFIABLE) { res->status = S_RANGE_NOT_SATISFIABLE; if (esnprintf(res->field[RES_CONTENT_RANGE], sizeof(res->field[RES_CONTENT_RANGE]), "bytes */%zu", st.st_size)) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } - return 0; + return; } else { - return returnstatus; + goto err; } } @@ -786,31 +776,79 @@ http_prepare_response(const struct request *req, struct response *res, if (esnprintf(res->field[RES_ACCEPT_RANGES], sizeof(res->field[RES_ACCEPT_RANGES]), "%s", "bytes")) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } if (esnprintf(res->field[RES_CONTENT_LENGTH], sizeof(res->field[RES_CONTENT_LENGTH]), "%zu", res->file.upper - res->file.lower + 1)) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } if (req->field[REQ_RANGE][0] != '\0') { if (esnprintf(res->field[RES_CONTENT_RANGE], sizeof(res->field[RES_CONTENT_RANGE]), "bytes %zd-%zd/%zu", res->file.lower, res->file.upper, st.st_size)) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } } if (esnprintf(res->field[RES_CONTENT_TYPE], sizeof(res->field[RES_CONTENT_TYPE]), "%s", mime)) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; } if (timestamp(res->field[RES_LAST_MODIFIED], sizeof(res->field[RES_LAST_MODIFIED]), st.st_mtim.tv_sec)) { - return S_INTERNAL_SERVER_ERROR; + s = S_INTERNAL_SERVER_ERROR; + goto err; + } + + return; +err: + http_prepare_error_response(req, res, s); +} + +void +http_prepare_error_response(const struct request *req, + struct response *res, enum status s) +{ + /* used later */ + (void)req; + + /* empty all response fields */ + memset(res, 0, sizeof(*res)); + + res->type = RESTYPE_ERROR; + res->status = s; + + if (esnprintf(res->field[RES_CONTENT_TYPE], + sizeof(res->field[RES_CONTENT_TYPE]), + "text/html; charset=utf-8")) { + res->status = S_INTERNAL_SERVER_ERROR; + } + + if (res->status == S_METHOD_NOT_ALLOWED) { + if (esnprintf(res->field[RES_ALLOW], + sizeof(res->field[RES_ALLOW]), + "Allow: GET, HEAD")) { + res->status = S_INTERNAL_SERVER_ERROR; + } + } +} + +enum status +http_send_body(int fd, const struct response *res, + const struct request *req) +{ + enum status s; + + if (req->method == M_GET && (s = body_fct[res->type](fd, res))) { + return s; } return 0; diff --git a/http.h b/http.h index a9cf871..078423b 100644 --- a/http.h +++ b/http.h @@ -3,6 +3,7 @@ #define HTTP_H #include +#include #include "util.h" @@ -82,17 +83,20 @@ struct response { } file; }; +extern enum status (* const body_fct[])(int, const struct response *); + enum conn_state { C_VACANT, C_RECV_HEADER, C_SEND_HEADER, - C_SEND_DATA, + C_SEND_BODY, NUM_CONN_STATES, }; struct connection { enum conn_state state; int fd; + struct sockaddr_storage ia; char header[HEADER_MAX]; /* general req/res-header buffer */ size_t off; /* general offset (header/file/dir) */ struct request req; @@ -103,7 +107,11 @@ enum status http_send_header(int, const struct response *); enum status http_send_status(int, enum status); enum status http_recv_header(int, char *, size_t, size_t *); enum status http_parse_header(const char *, struct request *); -enum status http_prepare_response(const struct request *, struct response *, - const struct server *); +void http_prepare_response(const struct request *, struct response *, + const struct server *); +void http_prepare_error_response(const struct request *, + struct response *, enum status); +enum status http_send_body(int, const struct response *, + const struct request *); #endif /* HTTP_H */ diff --git a/main.c b/main.c index 110af1a..49c8656 100644 --- a/main.c +++ b/main.c @@ -16,7 +16,7 @@ #include #include -#include "resp.h" +#include "data.h" #include "http.h" #include "sock.h" #include "util.h" @@ -24,52 +24,57 @@ static char *udsname; static void -serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) +logmsg(const struct connection *c) { - struct connection c = { .fd = infd }; - time_t t; - enum status status; - char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; + char inaddr_str[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; char tstmp[21]; + /* create timestamp */ + if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", + gmtime(&(time_t){time(NULL)}))) { + warn("strftime: Exceeded buffer capacity"); + /* continue anyway (we accept the truncation) */ + } + + /* generate address-string */ + if (sock_get_inaddr_str(&c->ia, inaddr_str, LEN(inaddr_str))) { + warn("sock_get_inaddr_str: Couldn't generate adress-string"); + inaddr_str[0] = '\0'; + } + + printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr_str, c->res.status, + c->req.field[REQ_HOST], c->req.uri); +} + +static void +serve(struct connection *c, const struct server *srv) +{ + enum status s; + /* set connection timeout */ - if (sock_set_timeout(c.fd, 30)) { + if (sock_set_timeout(c->fd, 30)) { goto cleanup; } /* handle request */ - if ((status = http_recv_header(c.fd, c.header, LEN(c.header), &c.off)) || - (status = http_parse_header(c.header, &c.req)) || - (status = http_prepare_response(&c.req, &c.res, srv))) { - status = http_send_status(c.fd, status); + if ((s = http_recv_header(c->fd, c->header, LEN(c->header), &c->off)) || + (s = http_parse_header(c->header, &c->req))) { + http_prepare_error_response(&c->req, &c->res, s); } else { - status = http_send_header(c.fd, &c.res); - - /* send data */ - if (c.res.type == RESTYPE_FILE) { - resp_file(c.fd, &c.res); - } else if (c.res.type == RESTYPE_DIRLISTING) { - resp_dir(c.fd, &c.res); - } + http_prepare_response(&c->req, &c->res, srv); } - /* write output to log */ - t = time(NULL); - if (!strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%SZ", - gmtime(&t))) { - warn("strftime: Exceeded buffer capacity"); - goto cleanup; + if ((s = http_send_header(c->fd, &c->res)) || + (s = http_send_body(c->fd, &c->res, &c->req))) { + c->res.status = s; } - if (sock_get_inaddr_str(in_sa, inaddr, LEN(inaddr))) { - goto cleanup; - } - printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, status, - c.req.field[REQ_HOST], c.req.uri); + + logmsg(c); cleanup: /* clean up and finish */ - shutdown(c.fd, SHUT_RD); - shutdown(c.fd, SHUT_WR); - close(c.fd); + shutdown(c->fd, SHUT_RD); + shutdown(c->fd, SHUT_WR); + close(c->fd); } static void @@ -192,10 +197,8 @@ main(int argc, char *argv[]) struct server srv = { .docindex = "index.html", }; - struct sockaddr_storage in_sa; size_t i; - socklen_t in_sa_len; - int insock, status = 0, infd; + int insock, status = 0; const char *err; char *tok[4]; @@ -370,9 +373,10 @@ main(int argc, char *argv[]) /* accept incoming connections */ while (1) { - in_sa_len = sizeof(in_sa); - if ((infd = accept(insock, (struct sockaddr *)&in_sa, - &in_sa_len)) < 0) { + struct connection c = { 0 }; + + if ((c.fd = accept(insock, (struct sockaddr *)&c.ia, + &(socklen_t){sizeof(c.ia)})) < 0) { warn("accept:"); continue; } @@ -380,7 +384,7 @@ main(int argc, char *argv[]) /* fork and handle */ switch (fork()) { case 0: - serve(infd, &in_sa, &srv); + serve(&c, &srv); exit(0); break; case -1: @@ -388,7 +392,7 @@ main(int argc, char *argv[]) /* fallthrough */ default: /* close the connection in the parent */ - close(infd); + close(c.fd); } } exit(0); diff --git a/resp.h b/resp.h deleted file mode 100644 index 92e5ad6..0000000 --- a/resp.h +++ /dev/null @@ -1,13 +0,0 @@ -/* See LICENSE file for copyright and license details. */ -#ifndef RESP_H -#define RESP_H - -#include -#include - -#include "http.h" - -enum status resp_dir(int, const struct response *); -enum status resp_file(int, const struct response *); - -#endif /* RESP_H */ diff --git a/util.c b/util.c index c998d8c..5719b4b 100644 --- a/util.c +++ b/util.c @@ -123,137 +123,6 @@ prepend(char *str, size_t size, const char *prefix) return 0; } -void -html_escape(const char *src, char *dst, size_t dst_siz) -{ - const struct { - char c; - char *s; - } escape[] = { - { '&', "&" }, - { '<', "<" }, - { '>', ">" }, - { '"', """ }, - { '\'', "'" }, - }; - size_t i, j, k, esclen; - - for (i = 0, j = 0; src[i] != '\0'; i++) { - for (k = 0; k < LEN(escape); k++) { - if (src[i] == escape[k].c) { - break; - } - } - if (k == LEN(escape)) { - /* no escape char at src[i] */ - if (j == dst_siz - 1) { - /* silent truncation */ - break; - } else { - dst[j++] = src[i]; - } - } else { - /* escape char at src[i] */ - esclen = strlen(escape[k].s); - - if (j >= dst_siz - esclen) { - /* silent truncation */ - break; - } else { - memcpy(&dst[j], escape[k].s, esclen); - j += esclen; - } - } - } - dst[j] = '\0'; -} - -void -replace(char **src, const char *old, const char* new) { - int old_len = strlen(old); - int new_len = strlen(new); - int src_len = strlen(*src); - - /* Count needed replacements */ - const char* tmp = *src; - int replc=0; - while ((tmp=strstr(tmp, old))) { - replc++; - tmp += old_len; - } - - /* Allocate enough space for the new string */ - char *buf = calloc( sizeof(char), src_len + replc*(abs(new_len-old_len)) + 1); - - /* Now start replacing */ - const char *srcidx = *src; - const char *srcidx_old = *src; - char *bufidx = buf; - - while (replc--) { - srcidx_old = strstr(srcidx, old); - - long repl_len = labs(srcidx_old - srcidx); - bufidx = strncpy(bufidx, srcidx, repl_len) + repl_len; - bufidx = strcpy(bufidx, new) + new_len; - - srcidx = srcidx_old+old_len; - } - - strncpy(bufidx, srcidx, strlen(srcidx)); // copy tail - - free(*src); - *src = buf; -} - -char* -read_file(const char* path){ - FILE* tpl_fp; - - if (!(tpl_fp = fopen(path, "r"))) { - return NULL; - } - - /* Get size of template */ - if (fseek(tpl_fp, 0L, SEEK_END) < 0) { - fclose(tpl_fp); - return NULL; - } - - long tpl_size; - if ((tpl_size = ftell(tpl_fp)) < 0) { - fclose(tpl_fp); - return NULL; - } - - rewind(tpl_fp); - - /* Read template into tpl_buf */ - char* tpl_buf = (char*)calloc(sizeof(char), tpl_size); - - if (tpl_buf == NULL) { - fclose(tpl_fp); - free(tpl_buf); - return NULL; - } - - if (fread(tpl_buf, 1, tpl_size, tpl_fp) < tpl_size) { - if (feof(tpl_fp)) { - warn("Reached end of file %s prematurely", path); - } else if (ferror(tpl_fp)) { - warn("Error while reading file %s", path); - } - - fclose(tpl_fp); - free(tpl_buf); - clearerr(tpl_fp); - - return NULL; - } - - return tpl_buf; -} - #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 diff --git a/util.h b/util.h index c595ace..be152bd 100644 --- a/util.h +++ b/util.h @@ -52,7 +52,6 @@ void eunveil(const char *, const char *); int timestamp(char *, size_t, time_t); int esnprintf(char *, size_t, const char *, ...); int prepend(char *, size_t, const char *); -void html_escape(const char *, char *, size_t); void replace(char **, const char *, const char *); char *read_file(const char* path);