quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | LICENSE

http.c (23802B)


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