Refactor http_send_response() into http_prepare_response() - quark - quark web … | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit 58d0f44e0395fe37b3575da35992b3d3e7f262d7 | |
parent a5163d08135b81b271b1d7ba36b24e342b57961f | |
Author: Laslo Hunhold <[email protected]> | |
Date: Sat, 22 Aug 2020 23:20:00 +0200 | |
Refactor http_send_response() into http_prepare_response() | |
The function http_send_response() did too much. It not only took | |
the request fields and built them together into a response, it | |
delegated too little and many functions were "hacked" into it, for | |
instance shady directory-changes for vhosts and hand-construction | |
of response structs. | |
The preparations for a rework were already made in previous commits, | |
including a tighter focus on the response-struct itself. Instead of | |
doing everything locally in the http_send_response() function, the | |
new http_prepare_response() only really takes the request-struct and | |
builds a response-struct. The response-struct is expanded such that | |
it's possible to do the data-sending simply with the response-struct | |
itself and not any other magic parameters that just drop out of the | |
function. | |
Another matter are the http_send_status()-calls. Because the | |
aforementioned function is so central, this refactoring has included | |
many areas. Instead of calling http_send_status() in every error-case, | |
which makes little sense now given we first delegate everything through | |
a response struct, errors are just sent as a return value and caught | |
centrally (in serve() in main.c), which centralizes the error handling | |
a bit. | |
It might look a bit strange now and it might not be clear in which | |
direction this is going, but subsequent commits will hopefully give | |
clarity in this regard. | |
Signed-off-by: Laslo Hunhold <[email protected]> | |
Diffstat: | |
M http.c | 261 ++++++++++++++++++++---------… | |
M http.h | 11 +++++------ | |
M main.c | 16 ++++++++++++---- | |
M resp.c | 202 +++++++----------------------… | |
M resp.h | 5 ++--- | |
M util.c | 60 +++++++++++++++++++++++++++++… | |
M util.h | 2 ++ | |
7 files changed, 294 insertions(+), 263 deletions(-) | |
--- | |
diff --git a/http.c b/http.c | |
@@ -61,7 +61,7 @@ const char *res_field_str[] = { | |
enum status | |
http_send_header(int fd, const struct response *res) | |
{ | |
- char t[FIELD_MAX]; | |
+ char t[FIELD_MAX], esc[PATH_MAX]; | |
size_t i; | |
if (timestamp(t, sizeof(t), time(NULL))) { | |
@@ -89,6 +89,18 @@ 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, | |
+ "<!DOCTYPE html>\n<html>\n\t<head>" | |
+ "<title>Index of %s</title></head>\n" | |
+ "\t<body>\n\t\t<a href=\"..\">..</a>", | |
+ esc) < 0) { | |
+ return S_REQUEST_TIMEOUT; | |
+ } | |
+ } | |
+ | |
return res->status; | |
} | |
@@ -536,93 +548,90 @@ parse_range(const char *str, size_t size, size_t *lower, … | |
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1)) | |
enum status | |
-http_send_response(int fd, const struct request *req, const struct server *s) | |
+http_prepare_response(const struct request *req, struct response *res, | |
+ const struct server *s) | |
{ | |
enum status returnstatus; | |
struct in6_addr addr; | |
- struct response res = { 0 }; | |
struct stat st; | |
struct tm tm = { 0 }; | |
+ struct vhost *vhost; | |
size_t len, i; | |
- size_t lower, upper; | |
int hasport, ipv6host; | |
- static char realtarget[PATH_MAX], tmptarget[PATH_MAX]; | |
+ static char realuri[PATH_MAX], tmpuri[PATH_MAX]; | |
char *p, *mime; | |
- const char *vhostmatch, *targethost; | |
+ const char *targethost; | |
+ | |
+ /* empty all response fields */ | |
+ memset(res, 0, sizeof(*res)); | |
- /* make a working copy of the target */ | |
- memcpy(realtarget, req->target, sizeof(realtarget)); | |
+ /* make a working copy of the URI and normalize it */ | |
+ memcpy(realuri, req->target, sizeof(realuri)); | |
+ if (normabspath(realuri)) { | |
+ return S_BAD_REQUEST; | |
+ } | |
/* match vhost */ | |
- vhostmatch = NULL; | |
+ vhost = NULL; | |
if (s->vhost) { | |
for (i = 0; i < s->vhost_len; i++) { | |
- /* switch to vhost directory if there is a match */ | |
- if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], … | |
- NULL, 0)) { | |
- if (chdir(s->vhost[i].dir) < 0) { | |
- return http_send_status(fd, (errno == … | |
- S_FORBIDDEN : … | |
- } | |
- vhostmatch = s->vhost[i].chost; | |
+ if (!regexec(&(s->vhost[i].re), req->field[REQ_HOST], | |
+ 0, NULL, 0)) { | |
+ /* we have a matching vhost */ | |
+ vhost = &(s->vhost[i]); | |
break; | |
} | |
} | |
if (i == s->vhost_len) { | |
- return http_send_status(fd, S_NOT_FOUND); | |
+ return S_NOT_FOUND; | |
} | |
- /* if we have a vhost prefix, prepend it to the target */ | |
- if (s->vhost[i].prefix) { | |
- if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", | |
- s->vhost[i].prefix, realtarget)) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARG… | |
- } | |
- memcpy(realtarget, tmptarget, sizeof(realtarget)); | |
+ /* 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; | |
} | |
} | |
/* apply target prefix mapping */ | |
for (i = 0; i < s->map_len; i++) { | |
len = strlen(s->map[i].from); | |
- if (!strncmp(realtarget, s->map[i].from, len)) { | |
+ if (!strncmp(realuri, s->map[i].from, len)) { | |
/* match canonical host if vhosts are enabled and | |
* the mapping specifies a canonical host */ | |
if (s->vhost && s->map[i].chost && | |
- strcmp(s->map[i].chost, vhostmatch)) { | |
+ strcmp(s->map[i].chost, vhost->chost)) { | |
continue; | |
} | |
/* swap out target prefix */ | |
- if (esnprintf(tmptarget, sizeof(tmptarget), "%s%s", | |
- s->map[i].to, realtarget + len)) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARG… | |
+ memmove(realuri, realuri + len, strlen(realuri) + 1); | |
+ if (prepend(realuri, LEN(realuri), s->map[i].to)) { | |
+ return S_REQUEST_TOO_LARGE; | |
} | |
- memcpy(realtarget, tmptarget, sizeof(realtarget)); | |
break; | |
} | |
} | |
- /* normalize target */ | |
- if (normabspath(realtarget)) { | |
- return http_send_status(fd, S_BAD_REQUEST); | |
+ /* normalize URI again, in case we introduced dirt */ | |
+ if (normabspath(realuri)) { | |
+ return S_BAD_REQUEST; | |
} | |
- /* stat the target */ | |
- if (stat(RELPATH(realtarget), &st) < 0) { | |
- return http_send_status(fd, (errno == EACCES) ? | |
- S_FORBIDDEN : S_NOT_FOUND); | |
+ /* stat the relative path derived from the URI */ | |
+ if (stat(RELPATH(realuri), &st) < 0) { | |
+ return (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND; | |
} | |
if (S_ISDIR(st.st_mode)) { | |
- /* add / to target if not present */ | |
- len = strlen(realtarget); | |
- if (len >= PATH_MAX - 2) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
+ /* append '/' to URI if not present */ | |
+ len = strlen(realuri); | |
+ if (len + 1 + 1 > PATH_MAX) { | |
+ return S_REQUEST_TOO_LARGE; | |
} | |
- if (len && realtarget[len - 1] != '/') { | |
- realtarget[len] = '/'; | |
- realtarget[len + 1] = '\0'; | |
+ if (len > 0 && realuri[len - 1] != '/') { | |
+ realuri[len] = '/'; | |
+ realuri[len + 1] = '\0'; | |
} | |
} | |
@@ -630,24 +639,27 @@ http_send_response(int fd, const struct request *req, con… | |
* reject hidden target, except if it is a well-known URI | |
* according to RFC 8615 | |
*/ | |
- if (strstr(realtarget, "/.") && strncmp(realtarget, | |
+ if (strstr(realuri, "/.") && strncmp(realuri, | |
"/.well-known/", sizeof("/.well-known/") - 1)) { | |
- return http_send_status(fd, S_FORBIDDEN); | |
+ return S_FORBIDDEN; | |
} | |
- /* redirect if targets differ, host is non-canonical or we prefixed */ | |
- if (strcmp(req->target, realtarget) || (s->vhost && vhostmatch && | |
- strcmp(req->field[REQ_HOST], vhostmatch))) { | |
- res.status = S_MOVED_PERMANENTLY; | |
+ /* | |
+ * redirect if the original URI and the "real" URI differ or if | |
+ * the requested host is non-canonical | |
+ */ | |
+ if (strcmp(req->target, realuri) || (s->vhost && vhost && | |
+ strcmp(req->field[REQ_HOST], vhost->chost))) { | |
+ res->status = S_MOVED_PERMANENTLY; | |
- /* encode realtarget */ | |
- encode(realtarget, tmptarget); | |
+ /* encode realuri */ | |
+ encode(realuri, tmpuri); | |
/* determine target location */ | |
if (s->vhost) { | |
/* absolute redirection URL */ | |
- targethost = req->field[REQ_HOST][0] ? vhostmatch ? | |
- vhostmatch : req->field[REQ_HOST] : s->ho… | |
+ targethost = req->field[REQ_HOST][0] ? vhost->chost ? | |
+ vhost->chost : req->field[REQ_HOST] : s->… | |
s->host : "localhost"; | |
/* do we need to add a port to the Location? */ | |
@@ -658,53 +670,74 @@ http_send_response(int fd, const struct request *req, con… | |
* honor that later when we fill the "Location"-field … | |
if ((ipv6host = inet_pton(AF_INET6, targethost, | |
&addr)) < 0) { | |
- return http_send_status(fd, | |
- S_INTERNAL_SERVER_ERRO… | |
+ return S_INTERNAL_SERVER_ERROR; | |
} | |
/* write location to response struct */ | |
- if (esnprintf(res.field[RES_LOCATION], | |
- sizeof(res.field[RES_LOCATION]), | |
+ if (esnprintf(res->field[RES_LOCATION], | |
+ sizeof(res->field[RES_LOCATION]), | |
"//%s%s%s%s%s%s", | |
ipv6host ? "[" : "", | |
targethost, | |
ipv6host ? "]" : "", hasport ? ":" : "", | |
- hasport ? s->port : "", tmptarget)) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARG… | |
+ hasport ? s->port : "", tmpuri)) { | |
+ return S_REQUEST_TOO_LARGE; | |
} | |
} else { | |
- /* write relative redirection URL to response struct */ | |
- if (esnprintf(res.field[RES_LOCATION], | |
- sizeof(res.field[RES_LOCATION]), | |
- tmptarget)) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARG… | |
+ /* 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; | |
} | |
} | |
- return http_send_header(fd, &res); | |
+ return 0; | |
+ } else { | |
+ /* | |
+ * the URI is well-formed, we can now write the URI into | |
+ * the response-URI and corresponding relative path | |
+ * (optionally including the vhost servedir as a prefix) | |
+ * into the actual response-path | |
+ */ | |
+ if (esnprintf(res->uri, sizeof(res->uri), "%s", req->target)) { | |
+ return S_REQUEST_TOO_LARGE; | |
+ } | |
+ if (esnprintf(res->path, sizeof(res->path), "%s%s", | |
+ vhost ? vhost->dir : "", RELPATH(req->target))) { | |
+ return S_REQUEST_TOO_LARGE; | |
+ } | |
} | |
if (S_ISDIR(st.st_mode)) { | |
- /* append docindex to target */ | |
- if (esnprintf(realtarget, sizeof(realtarget), "%s%s", | |
+ /* | |
+ * check if the directory index exists by appending it to | |
+ * the URI | |
+ */ | |
+ if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s", | |
req->target, s->docindex)) { | |
- return http_send_status(fd, S_REQUEST_TOO_LARGE); | |
+ return S_REQUEST_TOO_LARGE; | |
} | |
/* stat the docindex, which must be a regular file */ | |
- if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)… | |
+ if (stat(RELPATH(tmpuri), &st) < 0 || !S_ISREG(st.st_mode)) { | |
if (s->listdirs) { | |
- /* remove index suffix and serve dir */ | |
- realtarget[strlen(realtarget) - | |
- strlen(s->docindex)] = '\0'; | |
- return resp_dir(fd, RELPATH(realtarget), req); | |
+ /* serve directory listing */ | |
+ res->type = RESTYPE_DIRLISTING; | |
+ res->status = (access(res->path, R_OK)) ? | |
+ S_FORBIDDEN : S_OK; | |
+ | |
+ if (esnprintf(res->field[RES_CONTENT_TYPE], | |
+ sizeof(res->field[RES_CONTENT_TY… | |
+ "%s", "text/html; charset=utf-8"… | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ | |
+ return 0; | |
} else { | |
/* reject */ | |
- if (!S_ISREG(st.st_mode) || errno == EACCES) { | |
- return http_send_status(fd, S_FORBIDDE… | |
- } else { | |
- return http_send_status(fd, S_NOT_FOUN… | |
- } | |
+ return (!S_ISREG(st.st_mode) || errno == EACCE… | |
+ S_FORBIDDEN : S_NOT_FOUND; | |
} | |
} | |
} | |
@@ -714,39 +747,39 @@ http_send_response(int fd, const struct request *req, con… | |
/* parse field */ | |
if (!strptime(req->field[REQ_IF_MODIFIED_SINCE], | |
"%a, %d %b %Y %T GMT", &tm)) { | |
- return http_send_status(fd, S_BAD_REQUEST); | |
+ return S_BAD_REQUEST; | |
} | |
/* compare with last modification date of the file */ | |
if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) { | |
- res.status = S_NOT_MODIFIED; | |
- return http_send_header(fd, &res); | |
+ res->status = S_NOT_MODIFIED; | |
+ return 0; | |
} | |
} | |
/* range */ | |
- if ((returnstatus = parse_range(req->field[REQ_RANGE], | |
- st.st_size, &lower, &upper))) { | |
+ if ((returnstatus = parse_range(req->field[REQ_RANGE], st.st_size, | |
+ &(res->file.lower), | |
+ &(res->file.upper)))) { | |
if (returnstatus == S_RANGE_NOT_SATISFIABLE) { | |
- res.status = S_RANGE_NOT_SATISFIABLE; | |
+ res->status = S_RANGE_NOT_SATISFIABLE; | |
- if (esnprintf(res.field[RES_CONTENT_RANGE], | |
- sizeof(res.field[RES_CONTENT_RANGE]), | |
+ if (esnprintf(res->field[RES_CONTENT_RANGE], | |
+ sizeof(res->field[RES_CONTENT_RANGE]), | |
"bytes */%zu", st.st_size)) { | |
- return http_send_status(fd, | |
- S_INTERNAL_SERVER_ERRO… | |
+ return S_INTERNAL_SERVER_ERROR; | |
} | |
- return http_send_header(fd, &res); | |
+ return 0; | |
} else { | |
- return http_send_status(fd, returnstatus); | |
+ return returnstatus; | |
} | |
} | |
/* mime */ | |
mime = "application/octet-stream"; | |
- if ((p = strrchr(realtarget, '.'))) { | |
- for (i = 0; i < sizeof(mimes) / sizeof(*mimes); i++) { | |
+ if ((p = strrchr(realuri, '.'))) { | |
+ for (i = 0; i < LEN(mimes); i++) { | |
if (!strcmp(mimes[i].ext, p + 1)) { | |
mime = mimes[i].type; | |
break; | |
@@ -754,5 +787,43 @@ http_send_response(int fd, const struct request *req, cons… | |
} | |
} | |
- return resp_file(fd, RELPATH(realtarget), req, &st, mime, lower, upper… | |
+ /* fill response struct */ | |
+ res->type = RESTYPE_FILE; | |
+ | |
+ /* check if file is readable */ | |
+ res->status = (access(res->path, R_OK)) ? S_FORBIDDEN : | |
+ (req->field[REQ_RANGE][0] != '\0') ? | |
+ S_PARTIAL_CONTENT : S_OK; | |
+ | |
+ if (esnprintf(res->field[RES_ACCEPT_RANGES], | |
+ sizeof(res->field[RES_ACCEPT_RANGES]), | |
+ "%s", "bytes")) { | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ | |
+ 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; | |
+ } | |
+ 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; | |
+ } | |
+ } | |
+ if (esnprintf(res->field[RES_CONTENT_TYPE], | |
+ sizeof(res->field[RES_CONTENT_TYPE]), | |
+ "%s", mime)) { | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ if (timestamp(res->field[RES_LAST_MODIFIED], | |
+ sizeof(res->field[RES_LAST_MODIFIED]), | |
+ st.st_mtim.tv_sec)) { | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ | |
+ return 0; | |
} | |
diff --git a/http.h b/http.h | |
@@ -3,7 +3,6 @@ | |
#define HTTP_H | |
#include <limits.h> | |
-#include <sys/stat.h> | |
#include "util.h" | |
@@ -67,8 +66,9 @@ enum res_field { | |
extern const char *res_field_str[]; | |
enum res_type { | |
+ RESTYPE_ERROR, | |
RESTYPE_FILE, | |
- RESTYPE_DIR, | |
+ RESTYPE_DIRLISTING, | |
NUM_RES_TYPES, | |
}; | |
@@ -76,10 +76,9 @@ struct response { | |
enum res_type type; | |
enum status status; | |
char field[NUM_RES_FIELDS][FIELD_MAX]; | |
+ char uri[PATH_MAX]; | |
char path[PATH_MAX]; | |
- struct stat st; | |
struct { | |
- char *mime; | |
size_t lower; | |
size_t upper; | |
} file; | |
@@ -106,7 +105,7 @@ 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_send_response(int fd, const struct request *, | |
- const struct server *); | |
+enum status http_prepare_response(const struct request *, struct response *, | |
+ const struct server *); | |
#endif /* HTTP_H */ | |
diff --git a/main.c b/main.c | |
@@ -16,6 +16,7 @@ | |
#include <time.h> | |
#include <unistd.h> | |
+#include "resp.h" | |
#include "http.h" | |
#include "sock.h" | |
#include "util.h" | |
@@ -37,12 +38,19 @@ 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_send_status(c.fd, status); | |
- } else if ((status = http_parse_header(c.header, &c.req))) { | |
+ 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, s))) { | |
status = http_send_status(c.fd, status); | |
} else { | |
- status = http_send_response(c.fd, &c.req, s); | |
+ 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 */ | |
diff --git a/resp.c b/resp.c | |
@@ -38,202 +38,94 @@ 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 | |
-resp_dir(int fd, const char *name, const struct request *req) | |
+resp_dir(int fd, const struct response *res) | |
{ | |
- enum status sendstatus; | |
+ enum status ret; | |
struct dirent **e; | |
- struct response res = { | |
- .status = S_OK, | |
- .field[RES_CONTENT_TYPE] = "text/html; charset=utf-8", | |
- }; | |
size_t i; | |
int dirlen; | |
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ | |
/* read directory */ | |
- if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) { | |
- return http_send_status(fd, S_FORBIDDEN); | |
+ if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) { | |
+ return S_FORBIDDEN; | |
} | |
- /* send header as late as possible */ | |
- if ((sendstatus = http_send_header(fd, &res)) != res.status) { | |
- res.status = sendstatus; | |
- goto cleanup; | |
- } | |
- | |
- if (req->method == M_GET) { | |
- /* listing header */ | |
- html_escape(name, esc, sizeof(esc)); | |
- if (dprintf(fd, | |
- "<!DOCTYPE html>\n<html>\n\t<head>" | |
- "<title>Index of %s</title></head>\n" | |
- "\t<body>\n\t\t<a href=\"..\">..</a>", | |
- esc) < 0) { | |
- res.status = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
- } | |
- | |
- /* listing */ | |
- for (i = 0; i < (size_t)dirlen; i++) { | |
- /* skip hidden files, "." and ".." */ | |
- if (e[i]->d_name[0] == '.') { | |
- continue; | |
- } | |
- | |
- /* entry line */ | |
- html_escape(e[i]->d_name, esc, sizeof(esc)); | |
- if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>… | |
- esc, | |
- (e[i]->d_type == DT_DIR) ? "/" : "", | |
- esc, | |
- suffix(e[i]->d_type)) < 0) { | |
- res.status = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
- } | |
+ /* listing */ | |
+ for (i = 0; i < (size_t)dirlen; i++) { | |
+ /* skip hidden files, "." and ".." */ | |
+ if (e[i]->d_name[0] == '.') { | |
+ continue; | |
} | |
- /* listing footer */ | |
- if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) { | |
- res.status = S_REQUEST_TIMEOUT; | |
+ /* entry line */ | |
+ html_escape(e[i]->d_name, esc, sizeof(esc)); | |
+ if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>", | |
+ esc, | |
+ (e[i]->d_type == DT_DIR) ? "/" : "", | |
+ esc, | |
+ suffix(e[i]->d_type)) < 0) { | |
+ ret = S_REQUEST_TIMEOUT; | |
goto cleanup; | |
} | |
} | |
+ /* listing footer */ | |
+ if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) { | |
+ ret = S_REQUEST_TIMEOUT; | |
+ goto cleanup; | |
+ } | |
+ | |
cleanup: | |
while (dirlen--) { | |
free(e[dirlen]); | |
} | |
free(e); | |
- return res.status; | |
+ return ret; | |
} | |
enum status | |
-resp_file(int fd, const char *name, const struct request *req, | |
- const struct stat *st, const char *mime, size_t lower, | |
- size_t upper) | |
+resp_file(int fd, const struct response *res) | |
{ | |
FILE *fp; | |
- enum status sendstatus; | |
- struct response res = { | |
- .status = (req->field[REQ_RANGE][0] != '\0') ? | |
- S_PARTIAL_CONTENT : S_OK, | |
- .field[RES_ACCEPT_RANGES] = "bytes", | |
- }; | |
+ enum status ret = 0; | |
ssize_t bread, bwritten; | |
size_t remaining; | |
static char buf[BUFSIZ], *p; | |
/* open file */ | |
- if (!(fp = fopen(name, "r"))) { | |
- res.status = http_send_status(fd, S_FORBIDDEN); | |
+ if (!(fp = fopen(res->path, "r"))) { | |
+ ret = S_FORBIDDEN; | |
goto cleanup; | |
} | |
/* seek to lower bound */ | |
- if (fseek(fp, lower, SEEK_SET)) { | |
- res.status = http_send_status(fd, S_INTERNAL_SERVER_ERROR); | |
+ if (fseek(fp, res->file.lower, SEEK_SET)) { | |
+ ret = S_INTERNAL_SERVER_ERROR; | |
goto cleanup; | |
} | |
- /* build header */ | |
- if (esnprintf(res.field[RES_CONTENT_LENGTH], | |
- sizeof(res.field[RES_CONTENT_LENGTH]), | |
- "%zu", upper - lower + 1)) { | |
- return http_send_status(fd, S_INTERNAL_SERVER_ERROR); | |
- } | |
- if (req->field[REQ_RANGE][0] != '\0') { | |
- if (esnprintf(res.field[RES_CONTENT_RANGE], | |
- sizeof(res.field[RES_CONTENT_RANGE]), | |
- "bytes %zd-%zd/%zu", lower, upper, | |
- st->st_size)) { | |
- return http_send_status(fd, S_INTERNAL_SERVER_ERROR); | |
- } | |
- } | |
- if (esnprintf(res.field[RES_CONTENT_TYPE], | |
- sizeof(res.field[RES_CONTENT_TYPE]), | |
- "%s", mime)) { | |
- return http_send_status(fd, S_INTERNAL_SERVER_ERROR); | |
- } | |
- if (timestamp(res.field[RES_LAST_MODIFIED], | |
- sizeof(res.field[RES_LAST_MODIFIED]), | |
- st->st_mtim.tv_sec)) { | |
- return http_send_status(fd, S_INTERNAL_SERVER_ERROR); | |
- } | |
- | |
- /* send header as late as possible */ | |
- if ((sendstatus = http_send_header(fd, &res)) != res.status) { | |
- res.status = sendstatus; | |
- goto cleanup; | |
- } | |
+ /* write data until upper bound is hit */ | |
+ remaining = res->file.upper - res->file.lower + 1; | |
- if (req->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) { | |
- res.status = S_INTERNAL_SERVER_ERROR; | |
+ while ((bread = fread(buf, 1, MIN(sizeof(buf), | |
+ remaining), fp))) { | |
+ if (bread < 0) { | |
+ ret = S_INTERNAL_SERVER_ERROR; | |
+ goto cleanup; | |
+ } | |
+ remaining -= bread; | |
+ p = buf; | |
+ while (bread > 0) { | |
+ bwritten = write(fd, p, bread); | |
+ if (bwritten <= 0) { | |
+ ret = S_REQUEST_TIMEOUT; | |
goto cleanup; | |
} | |
- remaining -= bread; | |
- p = buf; | |
- while (bread > 0) { | |
- bwritten = write(fd, p, bread); | |
- if (bwritten <= 0) { | |
- res.status = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
- } | |
- bread -= bwritten; | |
- p += bwritten; | |
- } | |
+ bread -= bwritten; | |
+ p += bwritten; | |
} | |
} | |
cleanup: | |
@@ -241,5 +133,5 @@ cleanup: | |
fclose(fp); | |
} | |
- return res.status; | |
+ return ret; | |
} | |
diff --git a/resp.h b/resp.h | |
@@ -7,8 +7,7 @@ | |
#include "http.h" | |
-enum status resp_dir(int, const char *, const struct request *); | |
-enum status resp_file(int, const char *, const struct request *, | |
- const struct stat *, const char *, size_t, size_t); | |
+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 | |
@@ -108,6 +108,66 @@ esnprintf(char *str, size_t size, const char *fmt, ...) | |
return (ret < 0 || (size_t)ret >= size); | |
} | |
+int | |
+prepend(char *str, size_t size, const char *prefix) | |
+{ | |
+ size_t len = strlen(str), prefixlen = strlen(prefix); | |
+ | |
+ if (len + prefixlen + 1 > size) { | |
+ return 1; | |
+ } | |
+ | |
+ memmove(str + prefixlen, str, len + 1); | |
+ memcpy(str, prefix, prefixlen); | |
+ | |
+ 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 | |
@@ -51,6 +51,8 @@ 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 **); |