Improve http_prepare_response()'s error semantics - quark - quark web server | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit c0909c70e4767fa47b44b0964cf03e7962e430c3 | |
parent 123f168a3b5d1e378aa2827d6306a0270b553f90 | |
Author: Laslo Hunhold <[email protected]> | |
Date: Fri, 28 Aug 2020 22:48:32 +0200 | |
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 <[email protected]> | |
Diffstat: | |
M http.c | 96 +++++++++++++++++++----------… | |
M http.h | 4 ++-- | |
M main.c | 5 +++-- | |
3 files changed, 65 insertions(+), 40 deletions(-) | |
--- | |
diff --git a/http.c b/http.c | |
@@ -523,11 +523,11 @@ parse_range(const char *str, size_t size, size_t *lower, … | |
#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 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 r… | |
} | |
} | |
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 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 r… | |
/* 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 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 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 r… | |
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 r… | |
* 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 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 r… | |
if (esnprintf(res->field[RES_CONTENT_TYPE], | |
sizeof(res->field[RES_CONTENT_TY… | |
"%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 == EACCE… | |
- 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 r… | |
/* 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 r… | |
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 | |
@@ -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 | |
@@ -39,9 +39,10 @@ serve(int infd, const struct sockaddr_storage *in_sa, const … | |
/* 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); |