commit 58d0f44e0395fe37b3575da35992b3d3e7f262d7
Author:     Laslo Hunhold <d...@frign.de>
AuthorDate: Sat Aug 22 23:20:00 2020 +0200
Commit:     Laslo Hunhold <d...@frign.de>
CommitDate: Sat Aug 22 23:20:00 2020 +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 <d...@frign.de>

diff --git a/http.c b/http.c
index 6c0b519..b8ae1aa 100644
--- 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, 
size_t *upper)
 #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], 0,
-                                    NULL, 0)) {
-                               if (chdir(s->vhost[i].dir) < 0) {
-                                       return http_send_status(fd, (errno == 
EACCES) ?
-                                                               S_FORBIDDEN : 
S_NOT_FOUND);
-                               }
-                               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_LARGE);
-                       }
-                       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_LARGE);
+                       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, 
const struct server *s)
         * 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->host ?
+                       targethost = req->field[REQ_HOST][0] ? vhost->chost ?
+                                    vhost->chost : req->field[REQ_HOST] : 
s->host ?
                                     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, 
const struct server *s)
                         * 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_ERROR);
+                               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_LARGE);
+                                     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_LARGE);
+                       /* 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_TYPE]),
+                                             "%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_FORBIDDEN);
-                               } else {
-                                       return http_send_status(fd, 
S_NOT_FOUND);
-                               }
+                               return (!S_ISREG(st.st_mode) || errno == 
EACCES) ?
+                                      S_FORBIDDEN : S_NOT_FOUND;
                        }
                }
        }
@@ -714,39 +747,39 @@ http_send_response(int fd, const struct request *req, 
const struct server *s)
                /* 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_ERROR);
+                               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, 
const struct server *s)
                }
        }
 
-       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
index 0b1f6be..5226dca 100644
--- 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
index a3b8fa3..bb7233c 100644
--- 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 
struct server *s)
        }
 
        /* 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
index 2f639e5..b7441dc 100644
--- 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[] = {
-               { '&',  "&amp;"  },
-               { '<',  "&lt;"   },
-               { '>',  "&gt;"   },
-               { '"',  "&quot;" },
-               { '\'', "&#x27;" },
-       };
-       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
index 32d4c5d..92e5ad6 100644
--- 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
index 518a3eb..2b54df1 100644
--- 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[] = {
+               { '&',  "&amp;"  },
+               { '<',  "&lt;"   },
+               { '>',  "&gt;"   },
+               { '"',  "&quot;" },
+               { '\'', "&#x27;" },
+       };
+       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 21c18f8..6b6d17d 100644
--- 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 **);

Reply via email to