http.c - quark - quark web server
 (HTM) git clone git://git.suckless.org/quark
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
       http.c (23865B)
       ---
            1 /* See LICENSE file for copyright and license details. */
            2 #include <arpa/inet.h>
            3 #include <ctype.h>
            4 #include <errno.h>
            5 #include <limits.h>
            6 #include <netinet/in.h>
            7 #include <regex.h>
            8 #include <stddef.h>
            9 #include <stdint.h>
           10 #include <stdio.h>
           11 #include <string.h>
           12 #include <strings.h>
           13 #include <sys/socket.h>
           14 #include <sys/stat.h>
           15 #include <sys/types.h>
           16 #include <time.h>
           17 #include <unistd.h>
           18 
           19 #include "config.h"
           20 #include "http.h"
           21 #include "util.h"
           22 
           23 const char *req_field_str[] = {
           24         [REQ_HOST]              = "Host",
           25         [REQ_RANGE]             = "Range",
           26         [REQ_IF_MODIFIED_SINCE] = "If-Modified-Since",
           27 };
           28 
           29 const char *req_method_str[] = {
           30         [M_GET]  = "GET",
           31         [M_HEAD] = "HEAD",
           32 };
           33 
           34 const char *status_str[] = {
           35         [S_OK]                    = "OK",
           36         [S_PARTIAL_CONTENT]       = "Partial Content",
           37         [S_MOVED_PERMANENTLY]     = "Moved Permanently",
           38         [S_NOT_MODIFIED]          = "Not Modified",
           39         [S_BAD_REQUEST]           = "Bad Request",
           40         [S_FORBIDDEN]             = "Forbidden",
           41         [S_NOT_FOUND]             = "Not Found",
           42         [S_METHOD_NOT_ALLOWED]    = "Method Not Allowed",
           43         [S_REQUEST_TIMEOUT]       = "Request Time-out",
           44         [S_RANGE_NOT_SATISFIABLE] = "Range Not Satisfiable",
           45         [S_REQUEST_TOO_LARGE]     = "Request Header Fields Too Large",
           46         [S_INTERNAL_SERVER_ERROR] = "Internal Server Error",
           47         [S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
           48 };
           49 
           50 const char *res_field_str[] = {
           51         [RES_ACCEPT_RANGES]  = "Accept-Ranges",
           52         [RES_ALLOW]          = "Allow",
           53         [RES_LOCATION]       = "Location",
           54         [RES_LAST_MODIFIED]  = "Last-Modified",
           55         [RES_CONTENT_LENGTH] = "Content-Length",
           56         [RES_CONTENT_RANGE]  = "Content-Range",
           57         [RES_CONTENT_TYPE]   = "Content-Type",
           58 };
           59 
           60 enum status
           61 http_prepare_header_buf(const struct response *res, struct buffer *buf)
           62 {
           63         char tstmp[FIELD_MAX];
           64         size_t i;
           65 
           66         /* reset buffer */
           67         memset(buf, 0, sizeof(*buf));
           68 
           69         /* generate timestamp */
           70         if (timestamp(tstmp, sizeof(tstmp), time(NULL))) {
           71                 goto err;
           72         }
           73 
           74         /* write data */
           75         if (buffer_appendf(buf,
           76                            "HTTP/1.1 %d %s\r\n"
           77                            "Date: %s\r\n"
           78                            "Connection: close\r\n",
           79                            res->status, status_str[res->status], tstmp)) {
           80                 goto err;
           81         }
           82 
           83         for (i = 0; i < NUM_RES_FIELDS; i++) {
           84                 if (res->field[i][0] != '\0' &&
           85                     buffer_appendf(buf, "%s: %s\r\n", res_field_str[i],
           86                                    res->field[i])) {
           87                         goto err;
           88                 }
           89         }
           90 
           91         if (buffer_appendf(buf, "\r\n")) {
           92                 goto err;
           93         }
           94 
           95         return 0;
           96 err:
           97         memset(buf, 0, sizeof(*buf));
           98         return S_INTERNAL_SERVER_ERROR;
           99 }
          100 
          101 enum status
          102 http_send_buf(int fd, struct buffer *buf)
          103 {
          104         ssize_t r;
          105 
          106         if (buf == NULL) {
          107                 return S_INTERNAL_SERVER_ERROR;
          108         }
          109 
          110         while (buf->len > 0) {
          111                 if ((r = write(fd, buf->data, buf->len)) <= 0) {
          112                         if (errno == EAGAIN || errno == EWOULDBLOCK) {
          113                                 /*
          114                                  * socket is blocking, return normally.
          115                                  * given the buffer still contains data,
          116                                  * this indicates to the caller that we
          117                                  * have been interrupted.
          118                                  */
          119                                 return 0;
          120                         } else {
          121                                 return S_REQUEST_TIMEOUT;
          122                         }
          123                 }
          124                 memmove(buf->data, buf->data + r, buf->len - r);
          125                 buf->len -= r;
          126         }
          127 
          128         return 0;
          129 }
          130 
          131 static void
          132 decode(const char src[PATH_MAX], char dest[PATH_MAX])
          133 {
          134         size_t i;
          135         uint8_t n;
          136         const char *s;
          137 
          138         for (s = src, i = 0; *s; i++) {
          139                 if (*s == '%' && isxdigit((unsigned char)s[1]) &&
          140                     isxdigit((unsigned char)s[2])) {
          141                         sscanf(s + 1, "%2hhx", &n);
          142                         dest[i] = n;
          143                         s += 3;
          144                 } else {
          145                         dest[i] = *s++;
          146                 }
          147         }
          148         dest[i] = '\0';
          149 }
          150 
          151 enum status
          152 http_recv_header(int fd, struct buffer *buf, int *done)
          153 {
          154         enum status s;
          155         ssize_t r;
          156 
          157         while (1) {
          158                 if ((r = read(fd, buf->data + buf->len,
          159                               sizeof(buf->data) - buf->len)) < 0) {
          160                         if (errno == EAGAIN || errno == EWOULDBLOCK) {
          161                                 /*
          162                                  * socket is drained, return normally,
          163                                  * but set done to zero
          164                                  */
          165                                 *done = 0;
          166                                 return 0;
          167                         } else {
          168                                 s = S_REQUEST_TIMEOUT;
          169                                 goto err;
          170                         }
          171                 } else if (r == 0) {
          172                         /*
          173                          * unexpected EOF because the client probably
          174                          * hung up. This is technically a bad request,
          175                          * because it's incomplete
          176                          */
          177                         s = S_BAD_REQUEST;
          178                         goto err;
          179                 }
          180                 buf->len += r;
          181 
          182                 /* check if we are done (header terminated) */
          183                 if (buf->len >= 4 && !memcmp(buf->data + buf->len - 4,
          184                                              "\r\n\r\n", 4)) {
          185                         break;
          186                 }
          187 
          188                 /* buffer is full or read over, but header is not terminated */
          189                 if (r == 0 || buf->len == sizeof(buf->data)) {
          190                         s = S_REQUEST_TOO_LARGE;
          191                         goto err;
          192                 }
          193         }
          194 
          195         /* header is complete, remove last \r\n and set done */
          196         buf->len -= 2;
          197         *done = 1;
          198 
          199         return 0;
          200 err:
          201         memset(buf, 0, sizeof(*buf));
          202         return s;
          203 }
          204 
          205 enum status
          206 http_parse_header(const char *h, struct request *req)
          207 {
          208         struct in6_addr addr;
          209         size_t i, mlen;
          210         const char *p, *q, *r, *s, *t;
          211         char *m, *n;
          212 
          213         /* empty the request struct */
          214         memset(req, 0, sizeof(*req));
          215 
          216         /*
          217          * parse request line
          218          */
          219 
          220         /* METHOD */
          221         for (i = 0; i < NUM_REQ_METHODS; i++) {
          222                 mlen = strlen(req_method_str[i]);
          223                 if (!strncmp(req_method_str[i], h, mlen)) {
          224                         req->method = i;
          225                         break;
          226                 }
          227         }
          228         if (i == NUM_REQ_METHODS) {
          229                 return S_METHOD_NOT_ALLOWED;
          230         }
          231 
          232         /* a single space must follow the method */
          233         if (h[mlen] != ' ') {
          234                 return S_BAD_REQUEST;
          235         }
          236 
          237         /* basis for next step */
          238         p = h + mlen + 1;
          239 
          240         /* RESOURCE */
          241 
          242         /*
          243          * path?query#fragment
          244          * ^   ^     ^        ^
          245          * |   |     |        |
          246          * p   r     s        q
          247          *
          248          */
          249         if (!(q = strchr(p, ' '))) {
          250                 return S_BAD_REQUEST;
          251         }
          252 
          253         /* search for first '?' */
          254         for (r = p; r < q; r++) {
          255                 if (!isprint(*r)) {
          256                         return S_BAD_REQUEST;
          257                 }
          258                 if (*r == '?') {
          259                         break;
          260                 }
          261         }
          262         if (r == q) {
          263                 /* not found */
          264                 r = NULL;
          265         }
          266 
          267         /* search for first '#' */
          268         for (s = p; s < q; s++) {
          269                 if (!isprint(*s)) {
          270                         return S_BAD_REQUEST;
          271                 }
          272                 if (*s == '#') {
          273                         break;
          274                 }
          275         }
          276         if (s == q) {
          277                 /* not found */
          278                 s = NULL;
          279         }
          280 
          281         if (r != NULL && s != NULL && s < r) {
          282                 /*
          283                  * '#' comes before '?' and thus the '?' is literal,
          284                  * because the query must come before the fragment
          285                  */
          286                 r = NULL;
          287         }
          288 
          289         /* write path using temporary endpointer t */
          290         if (r != NULL) {
          291                 /* resource contains a query, path ends at r */
          292                 t = r;
          293         } else if (s != NULL) {
          294                 /* resource contains only a fragment, path ends at s */
          295                 t = s;
          296         } else {
          297                 /* resource contains no queries, path ends at q */
          298                 t = q;
          299         }
          300         if ((size_t)(t - p + 1) > LEN(req->path)) {
          301                 return S_REQUEST_TOO_LARGE;
          302         }
          303         memcpy(req->path, p, t - p);
          304         req->path[t - p] = '\0';
          305         decode(req->path, req->path);
          306 
          307         /* write query if present */
          308         if (r != NULL) {
          309                 /* query ends either at s (if fragment present) or q */
          310                 t = (s != NULL) ? s : q;
          311 
          312                 if ((size_t)(t - (r + 1) + 1) > LEN(req->query)) {
          313                         return S_REQUEST_TOO_LARGE;
          314                 }
          315                 memcpy(req->query, r + 1, t - (r + 1));
          316                 req->query[t - (r + 1)] = '\0';
          317         }
          318 
          319         /* write fragment if present */
          320         if (s != NULL) {
          321                 /* the fragment always starts at s + 1 and ends at q */
          322                 if ((size_t)(q - (s + 1) + 1) > LEN(req->fragment)) {
          323                         return S_REQUEST_TOO_LARGE;
          324                 }
          325                 memcpy(req->fragment, s + 1, q - (s + 1));
          326                 req->fragment[q - (s + 1)] = '\0';
          327         }
          328 
          329         /* basis for next step */
          330         p = q + 1;
          331 
          332         /* HTTP-VERSION */
          333         if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
          334                 return S_BAD_REQUEST;
          335         }
          336         p += sizeof("HTTP/") - 1;
          337         if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
          338             strncmp(p, "1.1", sizeof("1.1") - 1)) {
          339                 return S_VERSION_NOT_SUPPORTED;
          340         }
          341         p += sizeof("1.*") - 1;
          342 
          343         /* check terminator */
          344         if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
          345                 return S_BAD_REQUEST;
          346         }
          347 
          348         /* basis for next step */
          349         p += sizeof("\r\n") - 1;
          350 
          351         /*
          352          * parse request-fields
          353          */
          354 
          355         /* match field type */
          356         for (; *p != '\0';) {
          357                 for (i = 0; i < NUM_REQ_FIELDS; i++) {
          358                         if (!strncasecmp(p, req_field_str[i],
          359                                          strlen(req_field_str[i]))) {
          360                                 break;
          361                         }
          362                 }
          363                 if (i == NUM_REQ_FIELDS) {
          364                         /* unmatched field, skip this line */
          365                         if (!(q = strstr(p, "\r\n"))) {
          366                                 return S_BAD_REQUEST;
          367                         }
          368                         p = q + (sizeof("\r\n") - 1);
          369                         continue;
          370                 }
          371 
          372                 p += strlen(req_field_str[i]);
          373 
          374                 /* a single colon must follow the field name */
          375                 if (*p != ':') {
          376                         return S_BAD_REQUEST;
          377                 }
          378 
          379                 /* skip whitespace */
          380                 for (++p; *p == ' ' || *p == '\t'; p++)
          381                         ;
          382 
          383                 /* extract field content */
          384                 if (!(q = strstr(p, "\r\n"))) {
          385                         return S_BAD_REQUEST;
          386                 }
          387                 if ((size_t)(q - p + 1) > LEN(req->field[i])) {
          388                         return S_REQUEST_TOO_LARGE;
          389                 }
          390                 memcpy(req->field[i], p, q - p);
          391                 req->field[i][q - p] = '\0';
          392 
          393                 /* go to next line */
          394                 p = q + (sizeof("\r\n") - 1);
          395         }
          396 
          397         /*
          398          * clean up host
          399          */
          400 
          401         m = strrchr(req->field[REQ_HOST], ':');
          402         n = strrchr(req->field[REQ_HOST], ']');
          403 
          404         /* strip port suffix but don't interfere with IPv6 bracket notation
          405          * as per RFC 2732 */
          406         if (m && (!n || m > n)) {
          407                 /* port suffix must not be empty */
          408                 if (*(m + 1) == '\0') {
          409                         return S_BAD_REQUEST;
          410                 }
          411                 *m = '\0';
          412         }
          413 
          414         /* strip the brackets from the IPv6 notation and validate the address */
          415         if (n) {
          416                 /* brackets must be on the outside */
          417                 if (req->field[REQ_HOST][0] != '[' || *(n + 1) != '\0') {
          418                         return S_BAD_REQUEST;
          419                 }
          420 
          421                 /* remove the right bracket */
          422                 *n = '\0';
          423                 m = req->field[REQ_HOST] + 1;
          424 
          425                 /* validate the contained IPv6 address */
          426                 if (inet_pton(AF_INET6, m, &addr) != 1) {
          427                         return S_BAD_REQUEST;
          428                 }
          429 
          430                 /* copy it into the host field */
          431                 memmove(req->field[REQ_HOST], m, n - m + 1);
          432         }
          433 
          434         return 0;
          435 }
          436 
          437 static void
          438 encode(const char src[PATH_MAX], char dest[PATH_MAX])
          439 {
          440         size_t i;
          441         const char *s;
          442 
          443         for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) {
          444                 if (iscntrl(*s) || (unsigned char)*s > 127) {
          445                         i += snprintf(dest + i, PATH_MAX - i, "%%%02X",
          446                                       (unsigned char)*s);
          447                 } else {
          448                         dest[i] = *s;
          449                         i++;
          450                 }
          451         }
          452         dest[i] = '\0';
          453 }
          454 
          455 static enum status
          456 path_normalize(char *uri, int *redirect)
          457 {
          458         size_t len;
          459         int last = 0;
          460         char *p, *q;
          461 
          462         /* require and skip first slash */
          463         if (uri[0] != '/') {
          464                 return S_BAD_REQUEST;
          465         }
          466         p = uri + 1;
          467 
          468         /* get length of URI */
          469         len = strlen(p);
          470 
          471         for (; !last; ) {
          472                 /* bound uri component within (p,q) */
          473                 if (!(q = strchr(p, '/'))) {
          474                         q = strchr(p, '\0');
          475                         last = 1;
          476                 }
          477 
          478                 if (*p == '\0') {
          479                         break;
          480                 } else if (p == q || (q - p == 1 && p[0] == '.')) {
          481                         /* "/" or "./" */
          482                         goto squash;
          483                 } else if (q - p == 2 && p[0] == '.' && p[1] == '.') {
          484                         /* "../" */
          485                         if (p != uri + 1) {
          486                                 /* place p right after the previous / */
          487                                 for (p -= 2; p > uri && *p != '/'; p--);
          488                                 p++;
          489                         }
          490                         goto squash;
          491                 } else {
          492                         /* move on */
          493                         p = q + 1;
          494                         continue;
          495                 }
          496 squash:
          497                 /* squash (p,q) into void */
          498                 if (last) {
          499                         *p = '\0';
          500                         len = p - uri;
          501                 } else {
          502                         memmove(p, q + 1, len - ((q + 1) - uri) + 2);
          503                         len -= (q + 1) - p;
          504                 }
          505                 if (redirect != NULL) {
          506                         *redirect = 1;
          507                 }
          508         }
          509 
          510         return 0;
          511 }
          512 
          513 static enum status
          514 path_add_vhost_prefix(char uri[PATH_MAX], int *redirect,
          515                      const struct server *srv, const struct response *res)
          516 {
          517         if (srv->vhost && res->vhost && res->vhost->prefix) {
          518                 if (prepend(uri, PATH_MAX, res->vhost->prefix)) {
          519                         return S_REQUEST_TOO_LARGE;
          520                 }
          521                 if (redirect != NULL) {
          522                         *redirect = 1;
          523                 }
          524         }
          525 
          526         return 0;
          527 }
          528 
          529 static enum status
          530 path_apply_prefix_mapping(char uri[PATH_MAX], int *redirect,
          531                          const struct server *srv, const struct response *res)
          532 {
          533         size_t i, len;
          534 
          535         for (i = 0; i < srv->map_len; i++) {
          536                 len = strlen(srv->map[i].from);
          537                 if (!strncmp(uri, srv->map[i].from, len)) {
          538                         /*
          539                          * if vhosts are enabled only apply mappings
          540                          * defined for the current canonical host
          541                          */
          542                         if (srv->vhost && res->vhost && srv->map[i].chost &&
          543                             strcmp(srv->map[i].chost, res->vhost->chost)) {
          544                                 continue;
          545                         }
          546 
          547                         /* swap out URI prefix */
          548                         memmove(uri, uri + len, strlen(uri) + 1);
          549                         if (prepend(uri, PATH_MAX, srv->map[i].to)) {
          550                                 return S_REQUEST_TOO_LARGE;
          551                         }
          552 
          553                         if (redirect != NULL) {
          554                                 *redirect = 1;
          555                         }
          556 
          557                         /* break so we don't possibly hit an infinite loop */
          558                         break;
          559                 }
          560         }
          561 
          562         return 0;
          563 }
          564 
          565 static enum status
          566 path_ensure_dirslash(char uri[PATH_MAX], int *redirect)
          567 {
          568         size_t len;
          569 
          570         /* append '/' to URI if not present */
          571         len = strlen(uri);
          572         if (len + 1 + 1 > PATH_MAX) {
          573                 return S_REQUEST_TOO_LARGE;
          574         }
          575         if (len > 0 && uri[len - 1] != '/') {
          576                 uri[len] = '/';
          577                 uri[len + 1] = '\0';
          578                 if (redirect != NULL) {
          579                         *redirect = 1;
          580                 }
          581         }
          582 
          583         return 0;
          584 }
          585 
          586 static enum status
          587 parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
          588 {
          589         char first[FIELD_MAX], last[FIELD_MAX];
          590         const char *p, *q, *r, *err;
          591 
          592         /* default to the complete range */
          593         *lower = 0;
          594         *upper = size - 1;
          595 
          596         /* done if no range-string is given */
          597         if (str == NULL || *str == '\0') {
          598                 return 0;
          599         }
          600 
          601         /* skip opening statement */
          602         if (strncmp(str, "bytes=", sizeof("bytes=") - 1)) {
          603                 return S_BAD_REQUEST;
          604         }
          605         p = str + (sizeof("bytes=") - 1);
          606 
          607         /* check string (should only contain numbers and a hyphen) */
          608         for (r = p, q = NULL; *r != '\0'; r++) {
          609                 if (*r < '0' || *r > '9') {
          610                         if (*r == '-') {
          611                                 if (q != NULL) {
          612                                         /* we have already seen a hyphen */
          613                                         return S_BAD_REQUEST;
          614                                 } else {
          615                                         /* place q after the hyphen */
          616                                         q = r + 1;
          617                                 }
          618                         } else if (*r == ',' && r > p) {
          619                                 /*
          620                                  * we refuse to accept range-lists out
          621                                  * of spite towards this horrible part
          622                                  * of the spec
          623                                  */
          624                                 return S_RANGE_NOT_SATISFIABLE;
          625                         } else {
          626                                 return S_BAD_REQUEST;
          627                         }
          628                 }
          629         }
          630         if (q == NULL) {
          631                 /* the input string must contain a hyphen */
          632                 return S_BAD_REQUEST;
          633         }
          634         r = q + strlen(q);
          635 
          636         /*
          637          *  byte-range=first-last\0
          638          *             ^     ^   ^
          639          *             |     |   |
          640          *             p     q   r
          641          */
          642 
          643         /* copy 'first' and 'last' to their respective arrays */
          644         if ((size_t)((q - 1) - p + 1) > sizeof(first) ||
          645             (size_t)(r - q + 1) > sizeof(last)) {
          646                 return S_REQUEST_TOO_LARGE;
          647         }
          648         memcpy(first, p, (q - 1) - p);
          649         first[(q - 1) - p] = '\0';
          650         memcpy(last, q, r - q);
          651         last[r - q] = '\0';
          652 
          653         if (first[0] != '\0') {
          654                 /*
          655                  * range has format "first-last" or "first-",
          656                  * i.e. return bytes 'first' to 'last' (or the
          657                  * last byte if 'last' is not given),
          658                  * inclusively, and byte-numbering beginning at 0
          659                  */
          660                 *lower = strtonum(first, 0, MIN(SIZE_MAX, LLONG_MAX),
          661                                   &err);
          662                 if (!err) {
          663                         if (last[0] != '\0') {
          664                                 *upper = strtonum(last, 0,
          665                                                   MIN(SIZE_MAX, LLONG_MAX),
          666                                                   &err);
          667                         } else {
          668                                 *upper = size - 1;
          669                         }
          670                 }
          671                 if (err) {
          672                         /* one of the strtonum()'s failed */
          673                         return S_BAD_REQUEST;
          674                 }
          675 
          676                 /* check ranges */
          677                 if (*lower > *upper || *lower >= size) {
          678                         return S_RANGE_NOT_SATISFIABLE;
          679                 }
          680 
          681                 /* adjust upper limit to be at most the last byte */
          682                 *upper = MIN(*upper, size - 1);
          683         } else {
          684                 /* last must not also be empty */
          685                 if (last[0] == '\0') {
          686                         return S_BAD_REQUEST;
          687                 }
          688 
          689                 /*
          690                  * Range has format "-num", i.e. return the 'num'
          691                  * last bytes
          692                  */
          693 
          694                 /*
          695                  * use upper as a temporary storage for 'num',
          696                  * as we know 'upper' is size - 1
          697                  */
          698                 *upper = strtonum(last, 0, MIN(SIZE_MAX, LLONG_MAX), &err);
          699                 if (err) {
          700                         return S_BAD_REQUEST;
          701                 }
          702 
          703                 /* determine lower */
          704                 if (*upper > size) {
          705                         /* more bytes requested than we have */
          706                         *lower = 0;
          707                 } else {
          708                         *lower = size - *upper;
          709                 }
          710 
          711                 /* set upper to the correct value */
          712                 *upper = size - 1;
          713         }
          714 
          715         return 0;
          716 }
          717 
          718 void
          719 http_prepare_response(const struct request *req, struct response *res,
          720                       const struct server *srv)
          721 {
          722         enum status s, tmps;
          723         struct in6_addr addr;
          724         struct stat st;
          725         struct tm tm = { 0 };
          726         size_t i;
          727         int redirect, hasport, ipv6host;
          728         static char tmppath[PATH_MAX];
          729         char *p, *mime;
          730 
          731         /* empty all response fields */
          732         memset(res, 0, sizeof(*res));
          733 
          734         /* determine virtual host */
          735         if (srv->vhost) {
          736                 for (i = 0; i < srv->vhost_len; i++) {
          737                         if (!regexec(&(srv->vhost[i].re),
          738                                      req->field[REQ_HOST], 0, NULL, 0)) {
          739                                 /* we have a matching vhost */
          740                                 res->vhost = &(srv->vhost[i]);
          741                                 break;
          742                         }
          743                 }
          744                 if (i == srv->vhost_len) {
          745                         s = S_NOT_FOUND;
          746                         goto err;
          747                 }
          748         }
          749 
          750         /* copy request-path to response-path and clean it up */
          751         redirect = 0;
          752         memcpy(res->path, req->path, MIN(sizeof(res->path), sizeof(req->path)));
          753         if ((tmps = path_normalize(res->path, &redirect)) ||
          754             (tmps = path_add_vhost_prefix(res->path, &redirect, srv, res)) ||
          755             (tmps = path_apply_prefix_mapping(res->path, &redirect, srv, res)) ||
          756             (tmps = path_normalize(res->path, &redirect))) {
          757                 s = tmps;
          758                 goto err;
          759         }
          760 
          761         /* redirect all non-canonical hosts to their canonical forms */
          762         if (srv->vhost && res->vhost &&
          763             strcmp(req->field[REQ_HOST], res->vhost->chost)) {
          764                 redirect = 1;
          765         }
          766 
          767         /* reject all non-well-known hidden targets (see RFC 8615) */
          768         if (strstr(res->path, "/.") && strncmp(res->path, "/.well-known/",
          769                                           sizeof("/.well-known/") - 1)) {
          770                 s = S_FORBIDDEN;
          771                 goto err;
          772         }
          773 
          774         /*
          775          * generate and stat internal path based on the cleaned up request
          776          * path and the virtual host while ignoring query and fragment
          777          * (valid according to RFC 3986)
          778          */
          779         if (esnprintf(res->internal_path, sizeof(res->internal_path), "/%s/%s",
          780                       (srv->vhost && res->vhost) ? res->vhost->dir : "",
          781                       res->path)) {
          782                 s = S_REQUEST_TOO_LARGE;
          783                 goto err;
          784         }
          785         if ((tmps = path_normalize(res->internal_path, NULL))) {
          786                 s = tmps;
          787                 goto err;
          788         }
          789         if (stat(res->internal_path, &st) < 0) {
          790                 s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
          791                 goto err;
          792         }
          793 
          794         /*
          795          * if the path points at a directory, make sure both the path
          796          * and internal path have a trailing slash
          797          */
          798         if (S_ISDIR(st.st_mode)) {
          799                 if ((tmps = path_ensure_dirslash(res->path, &redirect)) ||
          800                     (tmps = path_ensure_dirslash(res->internal_path, NULL))) {
          801                         s = tmps;
          802                         goto err;
          803                 }
          804         }
          805 
          806         /* redirect if the path-cleanup necessitated it earlier */
          807         if (redirect) {
          808                 res->status = S_MOVED_PERMANENTLY;
          809 
          810                 /* encode path */
          811                 encode(res->path, tmppath);
          812 
          813                 /* determine target location */
          814                 if (srv->vhost && res->vhost) {
          815                         /* absolute redirection URL */
          816 
          817                         /* do we need to add a port to the Location? */
          818                         hasport = srv->port && strcmp(srv->port, "80");
          819 
          820                         /* RFC 2732 specifies to use brackets for IPv6-addresses
          821                          * in URLs, so we need to check if our host is one and
          822                          * honor that later when we fill the "Location"-field */
          823                         if ((ipv6host = inet_pton(AF_INET6, res->vhost->chost,
          824                                                   &addr)) < 0) {
          825                                 s = S_INTERNAL_SERVER_ERROR;
          826                                 goto err;
          827                         }
          828 
          829                         /*
          830                          * write location to response struct (re-including
          831                          * the query and fragment, if present)
          832                          */
          833                         if (esnprintf(res->field[RES_LOCATION],
          834                                       sizeof(res->field[RES_LOCATION]),
          835                                       "//%s%s%s%s%s%s%s%s%s%s",
          836                                       ipv6host ? "[" : "",
          837                                       res->vhost->chost,
          838                                       ipv6host ? "]" : "",
          839                                       hasport ? ":" : "",
          840                                       hasport ? srv->port : "",
          841                                       tmppath,
          842                                       req->query[0] ? "?" : "",
          843                                       req->query,
          844                                       req->fragment[0] ? "#" : "",
          845                                       req->fragment)) {
          846                                 s = S_REQUEST_TOO_LARGE;
          847                                 goto err;
          848                         }
          849                 } else {
          850                         /*
          851                          * write relative redirection URI to response struct
          852                          * (re-including the query and fragment, if present)
          853                          */
          854                         if (esnprintf(res->field[RES_LOCATION],
          855                                       sizeof(res->field[RES_LOCATION]),
          856                                       "%s%s%s%s%s",
          857                                       tmppath,
          858                                       req->query[0] ? "?" : "",
          859                                       req->query,
          860                                       req->fragment[0] ? "#" : "",
          861                                       req->fragment)) {
          862                                 s = S_REQUEST_TOO_LARGE;
          863                                 goto err;
          864                         }
          865                 }
          866 
          867                 return;
          868         }
          869 
          870         if (S_ISDIR(st.st_mode)) {
          871                 /*
          872                  * when we serve a directory, we first check if there
          873                  * exists a directory index. If not, we either make
          874                  * a directory listing (if enabled) or send an error
          875                  */
          876 
          877                 /*
          878                  * append docindex to internal_path temporarily
          879                  * (internal_path is guaranteed to end with '/')
          880                  */
          881                 if (esnprintf(tmppath, sizeof(tmppath), "%s%s",
          882                               res->internal_path, srv->docindex)) {
          883                         s = S_REQUEST_TOO_LARGE;
          884                         goto err;
          885                 }
          886 
          887                 /* stat the temporary path, which must be a regular file */
          888                 if (stat(tmppath, &st) < 0 || !S_ISREG(st.st_mode)) {
          889                         if (srv->listdirs) {
          890                                 /* serve directory listing */
          891 
          892                                 /* check if directory is accessible */
          893                                 if (access(res->internal_path, R_OK) != 0) {
          894                                         s = S_FORBIDDEN;
          895                                         goto err;
          896                                 } else {
          897                                         res->status = S_OK;
          898                                 }
          899                                 res->type = RESTYPE_DIRLISTING;
          900 
          901                                 if (esnprintf(res->field[RES_CONTENT_TYPE],
          902                                               sizeof(res->field[RES_CONTENT_TYPE]),
          903                                               "%s", "text/html; charset=utf-8")) {
          904                                         s = S_INTERNAL_SERVER_ERROR;
          905                                         goto err;
          906                                 }
          907 
          908                                 return;
          909                         } else {
          910                                 /* reject */
          911                                 s = (!S_ISREG(st.st_mode) || errno == EACCES) ?
          912                                     S_FORBIDDEN : S_NOT_FOUND;
          913                                 goto err;
          914                         }
          915                 } else {
          916                         /* the docindex exists; copy tmppath to internal path */
          917                         if (esnprintf(res->internal_path,
          918                                       sizeof(res->internal_path), "%s",
          919                                       tmppath)) {
          920                                 s = S_REQUEST_TOO_LARGE;
          921                                 goto err;
          922                         }
          923                 }
          924         }
          925 
          926         /* modified since */
          927         if (req->field[REQ_IF_MODIFIED_SINCE][0]) {
          928                 /* parse field */
          929                 if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
          930                               "%a, %d %b %Y %T GMT", &tm)) {
          931                         s = S_BAD_REQUEST;
          932                         goto err;
          933                 }
          934 
          935                 /* compare with last modification date of the file */
          936                 if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
          937                         res->status = S_NOT_MODIFIED;
          938                         return;
          939                 }
          940         }
          941 
          942         /* range */
          943         if ((s = parse_range(req->field[REQ_RANGE], st.st_size,
          944                              &(res->file.lower), &(res->file.upper)))) {
          945                 if (s == S_RANGE_NOT_SATISFIABLE) {
          946                         res->status = S_RANGE_NOT_SATISFIABLE;
          947 
          948                         if (esnprintf(res->field[RES_CONTENT_RANGE],
          949                                       sizeof(res->field[RES_CONTENT_RANGE]),
          950                                       "bytes */%zu", st.st_size)) {
          951                                 s = S_INTERNAL_SERVER_ERROR;
          952                                 goto err;
          953                         }
          954 
          955                         return;
          956                 } else {
          957                         goto err;
          958                 }
          959         }
          960 
          961         /* mime */
          962         mime = "application/octet-stream";
          963         if ((p = strrchr(res->internal_path, '.'))) {
          964                 for (i = 0; i < LEN(mimes); i++) {
          965                         if (!strcmp(mimes[i].ext, p + 1)) {
          966                                 mime = mimes[i].type;
          967                                 break;
          968                         }
          969                 }
          970         }
          971 
          972         /* fill response struct */
          973         res->type = RESTYPE_FILE;
          974 
          975         /* check if file is readable */
          976         res->status = (access(res->internal_path, R_OK)) ? S_FORBIDDEN :
          977                       (req->field[REQ_RANGE][0] != '\0') ?
          978                       S_PARTIAL_CONTENT : S_OK;
          979 
          980         if (esnprintf(res->field[RES_ACCEPT_RANGES],
          981                       sizeof(res->field[RES_ACCEPT_RANGES]),
          982                       "%s", "bytes")) {
          983                 s = S_INTERNAL_SERVER_ERROR;
          984                 goto err;
          985         }
          986 
          987         if (esnprintf(res->field[RES_CONTENT_LENGTH],
          988                       sizeof(res->field[RES_CONTENT_LENGTH]),
          989                       "%zu", res->file.upper - res->file.lower + 1)) {
          990                 s = S_INTERNAL_SERVER_ERROR;
          991                 goto err;
          992         }
          993         if (req->field[REQ_RANGE][0] != '\0') {
          994                 if (esnprintf(res->field[RES_CONTENT_RANGE],
          995                               sizeof(res->field[RES_CONTENT_RANGE]),
          996                               "bytes %zd-%zd/%zu", res->file.lower,
          997                               res->file.upper, st.st_size)) {
          998                         s = S_INTERNAL_SERVER_ERROR;
          999                         goto err;
         1000                 }
         1001         }
         1002         if (esnprintf(res->field[RES_CONTENT_TYPE],
         1003                       sizeof(res->field[RES_CONTENT_TYPE]),
         1004                       "%s", mime)) {
         1005                 s = S_INTERNAL_SERVER_ERROR;
         1006                 goto err;
         1007         }
         1008         if (timestamp(res->field[RES_LAST_MODIFIED],
         1009                       sizeof(res->field[RES_LAST_MODIFIED]),
         1010                       st.st_mtim.tv_sec)) {
         1011                 s = S_INTERNAL_SERVER_ERROR;
         1012                 goto err;
         1013         }
         1014 
         1015         return;
         1016 err:
         1017         http_prepare_error_response(req, res, s);
         1018 }
         1019 
         1020 void
         1021 http_prepare_error_response(const struct request *req,
         1022                             struct response *res, enum status s)
         1023 {
         1024         /* used later */
         1025         (void)req;
         1026 
         1027         /* empty all response fields */
         1028         memset(res, 0, sizeof(*res));
         1029 
         1030         res->type = RESTYPE_ERROR;
         1031         res->status = s;
         1032 
         1033         if (esnprintf(res->field[RES_CONTENT_TYPE],
         1034                       sizeof(res->field[RES_CONTENT_TYPE]),
         1035                       "text/html; charset=utf-8")) {
         1036                 res->status = S_INTERNAL_SERVER_ERROR;
         1037         }
         1038 
         1039         if (res->status == S_METHOD_NOT_ALLOWED) {
         1040                 if (esnprintf(res->field[RES_ALLOW],
         1041                               sizeof(res->field[RES_ALLOW]),
         1042                               "Allow: GET, HEAD")) {
         1043                         res->status = S_INTERNAL_SERVER_ERROR;
         1044                 }
         1045         }
         1046 }