quark

quark web server
git clone git://git.suckless.org/quark
Log | Files | Refs | 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 }