From 123f168a3b5d1e378aa2827d6306a0270b553f90 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 22:32:47 +0200 Subject: [PATCH 1/8] Replace http_send_status() with http_prepare_error_response() This approach fits better in line of first initializing the response struct and then sending the header with http_send_header() later. Signed-off-by: Laslo Hunhold --- http.c | 70 +++++++++++++++++++++++++++++++--------------------------- http.h | 2 ++ main.c | 20 ++++++++--------- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/http.c b/http.c index b6b1ab7..5b0390d 100644 --- a/http.c +++ b/http.c @@ -99,42 +99,18 @@ http_send_header(int fd, const struct response *res) esc) < 0) { 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; + } else if (res->type == RESTYPE_ERROR) { + 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; } } - 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 res->status; } static void @@ -827,3 +803,31 @@ http_prepare_response(const struct request *req, struct response *res, return 0; } + +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; + } + } +} diff --git a/http.h b/http.h index a9cf871..caac765 100644 --- a/http.h +++ b/http.h @@ -105,5 +105,7 @@ 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_error_response(const struct request *, + struct response *, enum status); #endif /* HTTP_H */ diff --git a/main.c b/main.c index 110af1a..fa0fa0f 100644 --- a/main.c +++ b/main.c @@ -41,16 +41,16 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) 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); - } else { - status = http_send_header(c.fd, &c.res); + http_prepare_error_response(&c.req, &c.res, status); + } - /* 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); - } + 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); } /* write output to log */ @@ -63,7 +63,7 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) 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, + printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, c.res.status, c.req.field[REQ_HOST], c.req.uri); cleanup: /* clean up and finish */ From c0909c70e4767fa47b44b0964cf03e7962e430c3 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 22:48:32 +0200 Subject: [PATCH 2/8] Improve http_prepare_response()'s error semantics I don't like the juggling with status-values in serve. It makes sense for http_recv_header() and http_parse_header(), because we don't have a response-struct yet that we can "fill". We could pass it to them, but that would make the usage a bit messy. However, in http_prepare_response(), we are already entrusted with a pointer to a response-struct, and just failing here (by returning an error value) leaves the response-struct in an invalid state. Instead, we make it a void function and reflect the status using the status field in the passed response struct. This way, there is no case where the response struct is in an invalid state after calling a http_prepare_*()-method. Signed-off-by: Laslo Hunhold --- http.c | 96 ++++++++++++++++++++++++++++++++++++---------------------- http.h | 4 +-- main.c | 5 +-- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/http.c b/http.c index 5b0390d..633c2f9 100644 --- a/http.c +++ b/http.c @@ -523,11 +523,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 }; @@ -544,7 +544,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 */ @@ -559,13 +560,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; } } @@ -583,7 +586,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; } @@ -591,19 +595,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] = '/'; @@ -617,7 +624,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; } /* @@ -646,7 +654,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 */ @@ -657,18 +666,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 @@ -677,11 +688,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; } } @@ -692,7 +705,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 */ @@ -706,14 +720,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; } } } @@ -723,32 +739,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; } } @@ -774,34 +791,41 @@ 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 0; + return; +err: + http_prepare_error_response(req, res, s); } void diff --git a/http.h b/http.h index caac765..d75d8d0 100644 --- a/http.h +++ b/http.h @@ -103,8 +103,8 @@ 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); diff --git a/main.c b/main.c index fa0fa0f..590e558 100644 --- a/main.c +++ b/main.c @@ -39,9 +39,10 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) /* 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_parse_header(c.header, &c.req))) { http_prepare_error_response(&c.req, &c.res, status); + } else { + http_prepare_response(&c.req, &c.res, srv); } status = http_send_header(c.fd, &c.res); From 68e4ff3021d558e1ff3db1767d1b692cbda70c7c Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 23:16:47 +0200 Subject: [PATCH 3/8] Return proper error-status when http_send_header() fails Explicitly show that we set the status of the response struct to the returned error status. This makes it clear that we are beyond the point where the "form" of the response struct matters and it's now only about the log-output. Signed-off-by: Laslo Hunhold --- http.c | 2 +- main.c | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/http.c b/http.c index 633c2f9..ee075e4 100644 --- a/http.c +++ b/http.c @@ -110,7 +110,7 @@ http_send_header(int fd, const struct response *res) } } - return res->status; + return 0; } static void diff --git a/main.c b/main.c index 590e558..deb273b 100644 --- a/main.c +++ b/main.c @@ -45,13 +45,15 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) http_prepare_response(&c.req, &c.res, srv); } - 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); + if ((status = http_send_header(c.fd, &c.res))) { + c.res.status = status; + } else { + /* 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); + } } /* write output to log */ From 9a95d9183c0d4c656d9aca33c2fca2327dc5f3a6 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 23:19:29 +0200 Subject: [PATCH 4/8] Rename status to s in serve() This is more consistent with the codebase. Signed-off-by: Laslo Hunhold --- main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.c b/main.c index deb273b..91d70b7 100644 --- a/main.c +++ b/main.c @@ -28,7 +28,7 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) { struct connection c = { .fd = infd }; time_t t; - enum status status; + enum status s; char inaddr[INET6_ADDRSTRLEN /* > INET_ADDRSTRLEN */]; char tstmp[21]; @@ -38,15 +38,15 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) } /* handle request */ - if ((status = http_recv_header(c.fd, c.header, LEN(c.header), &c.off)) || - (status = http_parse_header(c.header, &c.req))) { - http_prepare_error_response(&c.req, &c.res, 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 { http_prepare_response(&c.req, &c.res, srv); } - if ((status = http_send_header(c.fd, &c.res))) { - c.res.status = status; + if ((s = http_send_header(c.fd, &c.res))) { + c.res.status = s; } else { /* send data */ if (c.res.type == RESTYPE_FILE) { From a94b15814cf5729a377dd23c7961f183c6884b59 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 23:29:54 +0200 Subject: [PATCH 5/8] Rename resp.{c,h} to data.{c,h} The methods in data.h only deal with the actual response data, not the request handling itself, which has been formalized a bit more in http.h. To avoid confusion, we rename it to data.h. Signed-off-by: Laslo Hunhold --- Makefile | 10 +++++----- resp.c => data.c | 2 +- resp.h => data.h | 0 http.c | 1 - main.c | 2 +- 5 files changed, 7 insertions(+), 8 deletions(-) rename resp.c => data.c (99%) rename resp.h => data.h (100%) diff --git a/Makefile b/Makefile index cf57f13..0c0ebed 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,15 @@ include config.mk -COMPONENTS = util sock http resp +COMPONENTS = data http sock util 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 config.mk +data.o: data.c data.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 99% rename from resp.c rename to data.c index b7441dc..3794416 100644 --- a/resp.c +++ b/data.c @@ -8,7 +8,7 @@ #include #include "http.h" -#include "resp.h" +#include "data.h" #include "util.h" static int diff --git a/resp.h b/data.h similarity index 100% rename from resp.h rename to data.h diff --git a/http.c b/http.c index ee075e4..6e32fba 100644 --- a/http.c +++ b/http.c @@ -18,7 +18,6 @@ #include "config.h" #include "http.h" -#include "resp.h" #include "util.h" const char *req_field_str[] = { diff --git a/main.c b/main.c index 91d70b7..81cabde 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" From db127723c67534d5693fc033f19c855a403d1447 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Fri, 28 Aug 2020 23:46:12 +0200 Subject: [PATCH 6/8] Rename functions in data.h and adapt ifdef Signed-off-by: Laslo Hunhold --- data.c | 4 ++-- data.h | 13 +++++-------- main.c | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/data.c b/data.c index 3794416..46486aa 100644 --- a/data.c +++ b/data.c @@ -39,7 +39,7 @@ suffix(int t) } enum status -resp_dir(int fd, const struct response *res) +data_send_dirlisting(int fd, const struct response *res) { enum status ret; struct dirent **e; @@ -87,7 +87,7 @@ cleanup: } enum status -resp_file(int fd, const struct response *res) +data_send_file(int fd, const struct response *res) { FILE *fp; enum status ret = 0; diff --git a/data.h b/data.h index 92e5ad6..12aacc8 100644 --- a/data.h +++ b/data.h @@ -1,13 +1,10 @@ /* See LICENSE file for copyright and license details. */ -#ifndef RESP_H -#define RESP_H - -#include -#include +#ifndef DATA_H +#define DATA_H #include "http.h" -enum status resp_dir(int, const struct response *); -enum status resp_file(int, const struct response *); +enum status data_send_dirlisting(int, const struct response *); +enum status data_send_file(int, const struct response *); -#endif /* RESP_H */ +#endif /* DATA_H */ diff --git a/main.c b/main.c index 81cabde..2946a0b 100644 --- a/main.c +++ b/main.c @@ -50,9 +50,9 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) } else { /* send data */ if (c.res.type == RESTYPE_FILE) { - resp_file(c.fd, &c.res); + data_send_file(c.fd, &c.res); } else if (c.res.type == RESTYPE_DIRLISTING) { - resp_dir(c.fd, &c.res); + data_send_dirlisting(c.fd, &c.res); } } From a36b901d404f4d4268384a379fd040898f78b1b3 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Sat, 29 Aug 2020 00:42:54 +0200 Subject: [PATCH 7/8] Add http_send_body() and data_send_error() and refactor This turns the data-functions into the only functions "allowed" to send body-data (called with http_send_body()). The previous (hacky) approach of doing this in http_send_header() is not only out of place, it's an easy source of bugs given, for instance, the sending of body data is not expected with HEAD-requests. Given html_escape() is now only used in data.c, we move it there from util.c and make it a static method again. Signed-off-by: Laslo Hunhold --- data.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- data.h | 1 + http.c | 43 +++++++++++++++++----------------- http.h | 6 ++++- main.c | 10 ++------ util.c | 45 ------------------------------------ util.h | 1 - 7 files changed, 101 insertions(+), 78 deletions(-) diff --git a/data.c b/data.c index 46486aa..3b6b2e5 100644 --- a/data.c +++ b/data.c @@ -38,10 +38,55 @@ suffix(int t) 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; + enum status ret = 0; struct dirent **e; size_t i; int dirlen; @@ -52,6 +97,17 @@ data_send_dirlisting(int fd, const struct response *res) return S_FORBIDDEN; } + /* listing header (we use esc because sizeof(esc) >= PATH_MAX) */ + html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc))); + if (dprintf(fd, + "\n\n\t" + "Index of %s\n" + "\t\n\t\t..", + esc) < 0) { + ret = S_REQUEST_TIMEOUT; + goto cleanup; + } + /* listing */ for (i = 0; i < (size_t)dirlen; i++) { /* skip hidden files, "." and ".." */ @@ -86,6 +142,21 @@ cleanup: return ret; } +enum status +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) { diff --git a/data.h b/data.h index 12aacc8..91aedf5 100644 --- a/data.h +++ b/data.h @@ -5,6 +5,7 @@ #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 6e32fba..96e673a 100644 --- a/http.c +++ b/http.c @@ -17,6 +17,7 @@ #include #include "config.h" +#include "data.h" #include "http.h" #include "util.h" @@ -57,10 +58,16 @@ 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) { - char t[FIELD_MAX], esc[PATH_MAX]; + char t[FIELD_MAX]; size_t i; if (timestamp(t, sizeof(t), time(NULL))) { @@ -88,27 +95,6 @@ http_send_header(int fd, const struct response *res) return S_REQUEST_TIMEOUT; } - /* listing header */ - if (res->type == RESTYPE_DIRLISTING) { - html_escape(res->uri, esc, sizeof(esc)); - if (dprintf(fd, - "\n\n\t" - "Index of %s\n" - "\t\n\t\t..", - esc) < 0) { - return S_REQUEST_TIMEOUT; - } - } else if (res->type == RESTYPE_ERROR) { - 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; } @@ -854,3 +840,16 @@ http_prepare_error_response(const struct request *req, } } } + +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 d75d8d0..3d6d4d2 100644 --- a/http.h +++ b/http.h @@ -82,11 +82,13 @@ 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, }; @@ -107,5 +109,7 @@ 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 2946a0b..739e2fa 100644 --- a/main.c +++ b/main.c @@ -45,15 +45,9 @@ serve(int infd, const struct sockaddr_storage *in_sa, const struct server *srv) http_prepare_response(&c.req, &c.res, srv); } - if ((s = http_send_header(c.fd, &c.res))) { + if ((s = http_send_header(c.fd, &c.res)) || + (s = http_send_body(c.fd, &c.res, &c.req))) { c.res.status = s; - } else { - /* send data */ - if (c.res.type == RESTYPE_FILE) { - data_send_file(c.fd, &c.res); - } else if (c.res.type == RESTYPE_DIRLISTING) { - data_send_dirlisting(c.fd, &c.res); - } } /* write output to log */ diff --git a/util.c b/util.c index 2b54df1..b281613 100644 --- a/util.c +++ b/util.c @@ -123,51 +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'; -} - #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 diff --git a/util.h b/util.h index 6b6d17d..bc7f8ec 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 *reallocarray(void *, size_t, size_t); long long strtonum(const char *, long long, long long, const char **); From 0823ba4c3e480fb5e2c246b8ac6c4783d866ab87 Mon Sep 17 00:00:00 2001 From: Laslo Hunhold Date: Sat, 29 Aug 2020 13:02:51 +0200 Subject: [PATCH 8/8] Add logmsg() and refactor connection handling Also use compound literals for immediate pointers we don't use later (same as with setsockopt() in 32223c96bdee8f94980d3a1877a643a4d59f897f). Signed-off-by: Laslo Hunhold --- http.h | 2 ++ main.c | 79 ++++++++++++++++++++++++++++++++-------------------------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/http.h b/http.h index 3d6d4d2..078423b 100644 --- a/http.h +++ b/http.h @@ -3,6 +3,7 @@ #define HTTP_H #include +#include #include "util.h" @@ -95,6 +96,7 @@ enum conn_state { 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; diff --git a/main.c b/main.c index 739e2fa..49c8656 100644 --- a/main.c +++ b/main.c @@ -24,49 +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 s; - 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 ((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); + 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 { - http_prepare_response(&c.req, &c.res, srv); + http_prepare_response(&c->req, &c->res, srv); } - if ((s = http_send_header(c.fd, &c.res)) || - (s = http_send_body(c.fd, &c.res, &c.req))) { - c.res.status = s; + if ((s = http_send_header(c->fd, &c->res)) || + (s = http_send_body(c->fd, &c->res, &c->req))) { + c->res.status = s; } - /* 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 (sock_get_inaddr_str(in_sa, inaddr, LEN(inaddr))) { - goto cleanup; - } - printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inaddr, c.res.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 @@ -189,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]; @@ -367,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; } @@ -377,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: @@ -385,7 +392,7 @@ main(int argc, char *argv[]) /* fallthrough */ default: /* close the connection in the parent */ - close(infd); + close(c.fd); } } exit(0);