quark

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

http.c (19674B)


      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 			return S_REQUEST_TIMEOUT;
    113 		}
    114 		memmove(buf->data, buf->data + r, buf->len - r);
    115 		buf->len -= r;
    116 	}
    117 
    118 	return 0;
    119 }
    120 
    121 static void
    122 decode(const char src[PATH_MAX], char dest[PATH_MAX])
    123 {
    124 	size_t i;
    125 	uint8_t n;
    126 	const char *s;
    127 
    128 	for (s = src, i = 0; *s; s++, i++) {
    129 		if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) {
    130 			dest[i] = n;
    131 			s += 2;
    132 		} else {
    133 			dest[i] = *s;
    134 		}
    135 	}
    136 	dest[i] = '\0';
    137 }
    138 
    139 enum status
    140 http_recv_header(int fd, struct buffer *buf, int *done)
    141 {
    142 	enum status s;
    143 	ssize_t r;
    144 
    145 	while (1) {
    146 		if ((r = read(fd, buf->data + buf->len,
    147 		              sizeof(buf->data) - buf->len)) <= 0) {
    148 			s = S_REQUEST_TIMEOUT;
    149 			goto err;
    150 		}
    151 		buf->len += r;
    152 
    153 		/* check if we are done (header terminated) */
    154 		if (buf->len >= 4 && !memcmp(buf->data + buf->len - 4,
    155 		                             "\r\n\r\n", 4)) {
    156 			break;
    157 		}
    158 
    159 		/* buffer is full or read over, but header is not terminated */
    160 		if (r == 0 || buf->len == sizeof(buf->data)) {
    161 			s = S_REQUEST_TOO_LARGE;
    162 			goto err;
    163 		}
    164 	}
    165 
    166 	/* header is complete, remove last \r\n and set done */
    167 	buf->len -= 2;
    168 	*done = 1;
    169 
    170 	return 0;
    171 err:
    172 	memset(buf, 0, sizeof(*buf));
    173 	return s;
    174 }
    175 
    176 enum status
    177 http_parse_header(const char *h, struct request *req)
    178 {
    179 	struct in6_addr addr;
    180 	size_t i, mlen;
    181 	const char *p, *q;
    182 	char *m, *n;
    183 
    184 	/* empty all fields */
    185 	memset(req, 0, sizeof(*req));
    186 
    187 	/*
    188 	 * parse request line
    189 	 */
    190 
    191 	/* METHOD */
    192 	for (i = 0; i < NUM_REQ_METHODS; i++) {
    193 		mlen = strlen(req_method_str[i]);
    194 		if (!strncmp(req_method_str[i], h, mlen)) {
    195 			req->method = i;
    196 			break;
    197 		}
    198 	}
    199 	if (i == NUM_REQ_METHODS) {
    200 		return S_METHOD_NOT_ALLOWED;
    201 	}
    202 
    203 	/* a single space must follow the method */
    204 	if (h[mlen] != ' ') {
    205 		return S_BAD_REQUEST;
    206 	}
    207 
    208 	/* basis for next step */
    209 	p = h + mlen + 1;
    210 
    211 	/* TARGET */
    212 	if (!(q = strchr(p, ' '))) {
    213 		return S_BAD_REQUEST;
    214 	}
    215 	if (q - p + 1 > PATH_MAX) {
    216 		return S_REQUEST_TOO_LARGE;
    217 	}
    218 	memcpy(req->uri, p, q - p);
    219 	req->uri[q - p] = '\0';
    220 	decode(req->uri, req->uri);
    221 
    222 	/* basis for next step */
    223 	p = q + 1;
    224 
    225 	/* HTTP-VERSION */
    226 	if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
    227 		return S_BAD_REQUEST;
    228 	}
    229 	p += sizeof("HTTP/") - 1;
    230 	if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
    231 	    strncmp(p, "1.1", sizeof("1.1") - 1)) {
    232 		return S_VERSION_NOT_SUPPORTED;
    233 	}
    234 	p += sizeof("1.*") - 1;
    235 
    236 	/* check terminator */
    237 	if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
    238 		return S_BAD_REQUEST;
    239 	}
    240 
    241 	/* basis for next step */
    242 	p += sizeof("\r\n") - 1;
    243 
    244 	/*
    245 	 * parse request-fields
    246 	 */
    247 
    248 	/* match field type */
    249 	for (; *p != '\0';) {
    250 		for (i = 0; i < NUM_REQ_FIELDS; i++) {
    251 			if (!strncasecmp(p, req_field_str[i],
    252 			                 strlen(req_field_str[i]))) {
    253 				break;
    254 			}
    255 		}
    256 		if (i == NUM_REQ_FIELDS) {
    257 			/* unmatched field, skip this line */
    258 			if (!(q = strstr(p, "\r\n"))) {
    259 				return S_BAD_REQUEST;
    260 			}
    261 			p = q + (sizeof("\r\n") - 1);
    262 			continue;
    263 		}
    264 
    265 		p += strlen(req_field_str[i]);
    266 
    267 		/* a single colon must follow the field name */
    268 		if (*p != ':') {
    269 			return S_BAD_REQUEST;
    270 		}
    271 
    272 		/* skip whitespace */
    273 		for (++p; *p == ' ' || *p == '\t'; p++)
    274 			;
    275 
    276 		/* extract field content */
    277 		if (!(q = strstr(p, "\r\n"))) {
    278 			return S_BAD_REQUEST;
    279 		}
    280 		if (q - p + 1 > FIELD_MAX) {
    281 			return S_REQUEST_TOO_LARGE;
    282 		}
    283 		memcpy(req->field[i], p, q - p);
    284 		req->field[i][q - p] = '\0';
    285 
    286 		/* go to next line */
    287 		p = q + (sizeof("\r\n") - 1);
    288 	}
    289 
    290 	/*
    291 	 * clean up host
    292 	 */
    293 
    294 	m = strrchr(req->field[REQ_HOST], ':');
    295 	n = strrchr(req->field[REQ_HOST], ']');
    296 
    297 	/* strip port suffix but don't interfere with IPv6 bracket notation
    298 	 * as per RFC 2732 */
    299 	if (m && (!n || m > n)) {
    300 		/* port suffix must not be empty */
    301 		if (*(m + 1) == '\0') {
    302 			return S_BAD_REQUEST;
    303 		}
    304 		*m = '\0';
    305 	}
    306 
    307 	/* strip the brackets from the IPv6 notation and validate the address */
    308 	if (n) {
    309 		/* brackets must be on the outside */
    310 		if (req->field[REQ_HOST][0] != '[' || *(n + 1) != '\0') {
    311 			return S_BAD_REQUEST;
    312 		}
    313 
    314 		/* remove the right bracket */
    315 		*n = '\0';
    316 		m = req->field[REQ_HOST] + 1;
    317 
    318 		/* validate the contained IPv6 address */
    319 		if (inet_pton(AF_INET6, m, &addr) != 1) {
    320 			return S_BAD_REQUEST;
    321 		}
    322 
    323 		/* copy it into the host field */
    324 		memmove(req->field[REQ_HOST], m, n - m + 1);
    325 	}
    326 
    327 	return 0;
    328 }
    329 
    330 static void
    331 encode(const char src[PATH_MAX], char dest[PATH_MAX])
    332 {
    333 	size_t i;
    334 	const char *s;
    335 
    336 	for (s = src, i = 0; *s && i < (PATH_MAX - 4); s++) {
    337 		if (iscntrl(*s) || (unsigned char)*s > 127) {
    338 			i += snprintf(dest + i, PATH_MAX - i, "%%%02X",
    339 			              (unsigned char)*s);
    340 		} else {
    341 			dest[i] = *s;
    342 			i++;
    343 		}
    344 	}
    345 	dest[i] = '\0';
    346 }
    347 
    348 static int
    349 normabspath(char *path)
    350 {
    351 	size_t len;
    352 	int last = 0;
    353 	char *p, *q;
    354 
    355 	/* require and skip first slash */
    356 	if (path[0] != '/') {
    357 		return 1;
    358 	}
    359 	p = path + 1;
    360 
    361 	/* get length of path */
    362 	len = strlen(p);
    363 
    364 	for (; !last; ) {
    365 		/* bound path component within (p,q) */
    366 		if (!(q = strchr(p, '/'))) {
    367 			q = strchr(p, '\0');
    368 			last = 1;
    369 		}
    370 
    371 		if (p == q || (q - p == 1 && p[0] == '.')) {
    372 			/* "/" or "./" */
    373 			goto squash;
    374 		} else if (q - p == 2 && p[0] == '.' && p[1] == '.') {
    375 			/* "../" */
    376 			if (p != path + 1) {
    377 				/* place p right after the previous / */
    378 				for (p -= 2; p > path && *p != '/'; p--);
    379 				p++;
    380 			}
    381 			goto squash;
    382 		} else {
    383 			/* move on */
    384 			p = q + 1;
    385 			continue;
    386 		}
    387 squash:
    388 		/* squash (p,q) into void */
    389 		if (last) {
    390 			*p = '\0';
    391 			len = p - path;
    392 		} else {
    393 			memmove(p, q + 1, len - ((q + 1) - path) + 2);
    394 			len -= (q + 1) - p;
    395 		}
    396 	}
    397 
    398 	return 0;
    399 }
    400 
    401 static enum status
    402 parse_range(const char *str, size_t size, size_t *lower, size_t *upper)
    403 {
    404 	char first[FIELD_MAX], last[FIELD_MAX];
    405 	const char *p, *q, *r, *err;
    406 
    407 	/* default to the complete range */
    408 	*lower = 0;
    409 	*upper = size - 1;
    410 
    411 	/* done if no range-string is given */
    412 	if (str == NULL || *str == '\0') {
    413 		return 0;
    414 	}
    415 
    416 	/* skip opening statement */
    417 	if (strncmp(str, "bytes=", sizeof("bytes=") - 1)) {
    418 		return S_BAD_REQUEST;
    419 	}
    420 	p = str + (sizeof("bytes=") - 1);
    421 
    422 	/* check string (should only contain numbers and a hyphen) */
    423 	for (r = p, q = NULL; *r != '\0'; r++) {
    424 		if (*r < '0' || *r > '9') {
    425 			if (*r == '-') {
    426 				if (q != NULL) {
    427 					/* we have already seen a hyphen */
    428 					return S_BAD_REQUEST;
    429 				} else {
    430 					/* place q after the hyphen */
    431 					q = r + 1;
    432 				}
    433 			} else if (*r == ',' && r > p) {
    434 				/*
    435 				 * we refuse to accept range-lists out
    436 				 * of spite towards this horrible part
    437 				 * of the spec
    438 				 */
    439 				return S_RANGE_NOT_SATISFIABLE;
    440 			} else {
    441 				return S_BAD_REQUEST;
    442 			}
    443 		}
    444 	}
    445 	if (q == NULL) {
    446 		/* the input string must contain a hyphen */
    447 		return S_BAD_REQUEST;
    448 	}
    449 	r = q + strlen(q);
    450 
    451 	/*
    452 	 *  byte-range=first-last\0
    453 	 *             ^     ^   ^
    454 	 *             |     |   |
    455 	 *             p     q   r
    456 	 */
    457 
    458 	/* copy 'first' and 'last' to their respective arrays */
    459 	if ((size_t)((q - 1) - p + 1) > sizeof(first) ||
    460 	    (size_t)(r - q + 1) > sizeof(last)) {
    461 		return S_REQUEST_TOO_LARGE;
    462 	}
    463 	memcpy(first, p, (q - 1) - p);
    464 	first[(q - 1) - p] = '\0';
    465 	memcpy(last, q, r - q);
    466 	last[r - q] = '\0';
    467 
    468 	if (first[0] != '\0') {
    469 		/*
    470 		 * range has format "first-last" or "first-",
    471 		 * i.e. return bytes 'first' to 'last' (or the
    472 		 * last byte if 'last' is not given),
    473 		 * inclusively, and byte-numbering beginning at 0
    474 		 */
    475 		*lower = strtonum(first, 0, SIZE_MAX, &err);
    476 		if (!err) {
    477 			if (last[0] != '\0') {
    478 				*upper = strtonum(last, 0, SIZE_MAX, &err);
    479 			} else {
    480 				*upper = size - 1;
    481 			}
    482 		}
    483 		if (err) {
    484 			/* one of the strtonum()'s failed */
    485 			return S_BAD_REQUEST;
    486 		}
    487 
    488 		/* check ranges */
    489 		if (*lower > *upper || *lower >= size) {
    490 			return S_RANGE_NOT_SATISFIABLE;
    491 		}
    492 
    493 		/* adjust upper limit to be at most the last byte */
    494 		*upper = MIN(*upper, size - 1);
    495 	} else {
    496 		/* last must not also be empty */
    497 		if (last[0] == '\0') {
    498 			return S_BAD_REQUEST;
    499 		}
    500 
    501 		/*
    502 		 * Range has format "-num", i.e. return the 'num'
    503 		 * last bytes
    504 		 */
    505 
    506 		/*
    507 		 * use upper as a temporary storage for 'num',
    508 		 * as we know 'upper' is size - 1
    509 		 */
    510 		*upper = strtonum(last, 0, SIZE_MAX, &err);
    511 		if (err) {
    512 			return S_BAD_REQUEST;
    513 		}
    514 
    515 		/* determine lower */
    516 		if (*upper > size) {
    517 			/* more bytes requested than we have */
    518 			*lower = 0;
    519 		} else {
    520 			*lower = size - *upper;
    521 		}
    522 
    523 		/* set upper to the correct value */
    524 		*upper = size - 1;
    525 	}
    526 
    527 	return 0;
    528 }
    529 
    530 #undef RELPATH
    531 #define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
    532 
    533 void
    534 http_prepare_response(const struct request *req, struct response *res,
    535                       const struct server *srv)
    536 {
    537 	enum status s;
    538 	struct in6_addr addr;
    539 	struct stat st;
    540 	struct tm tm = { 0 };
    541 	struct vhost *vhost;
    542 	size_t len, i;
    543 	int hasport, ipv6host;
    544 	static char realuri[PATH_MAX], tmpuri[PATH_MAX];
    545 	char *p, *mime;
    546 	const char *targethost;
    547 
    548 	/* empty all response fields */
    549 	memset(res, 0, sizeof(*res));
    550 
    551 	/* make a working copy of the URI and normalize it */
    552 	memcpy(realuri, req->uri, sizeof(realuri));
    553 	if (normabspath(realuri)) {
    554 		s = S_BAD_REQUEST;
    555 		goto err;
    556 	}
    557 
    558 	/* match vhost */
    559 	vhost = NULL;
    560 	if (srv->vhost) {
    561 		for (i = 0; i < srv->vhost_len; i++) {
    562 			if (!regexec(&(srv->vhost[i].re), req->field[REQ_HOST],
    563 			             0, NULL, 0)) {
    564 				/* we have a matching vhost */
    565 				vhost = &(srv->vhost[i]);
    566 				break;
    567 			}
    568 		}
    569 		if (i == srv->vhost_len) {
    570 			s = S_NOT_FOUND;
    571 			goto err;
    572 		}
    573 
    574 		/* if we have a vhost prefix, prepend it to the URI */
    575 		if (vhost->prefix &&
    576 		    prepend(realuri, LEN(realuri), vhost->prefix)) {
    577 			s = S_REQUEST_TOO_LARGE;
    578 			goto err;
    579 		}
    580 	}
    581 
    582 	/* apply URI prefix mapping */
    583 	for (i = 0; i < srv->map_len; i++) {
    584 		len = strlen(srv->map[i].from);
    585 		if (!strncmp(realuri, srv->map[i].from, len)) {
    586 			/* match canonical host if vhosts are enabled and
    587 			 * the mapping specifies a canonical host */
    588 			if (srv->vhost && srv->map[i].chost &&
    589 			    strcmp(srv->map[i].chost, vhost->chost)) {
    590 				continue;
    591 			}
    592 
    593 			/* swap out URI prefix */
    594 			memmove(realuri, realuri + len, strlen(realuri) + 1);
    595 			if (prepend(realuri, LEN(realuri), srv->map[i].to)) {
    596 				s = S_REQUEST_TOO_LARGE;
    597 				goto err;
    598 			}
    599 			break;
    600 		}
    601 	}
    602 
    603 	/* normalize URI again, in case we introduced dirt */
    604 	if (normabspath(realuri)) {
    605 		s = S_BAD_REQUEST;
    606 		goto err;
    607 	}
    608 
    609 	/* stat the relative path derived from the URI */
    610 	if (stat(RELPATH(realuri), &st) < 0) {
    611 		s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND;
    612 		goto err;
    613 	}
    614 
    615 	if (S_ISDIR(st.st_mode)) {
    616 		/* append '/' to URI if not present */
    617 		len = strlen(realuri);
    618 		if (len + 1 + 1 > PATH_MAX) {
    619 			s = S_REQUEST_TOO_LARGE;
    620 			goto err;
    621 		}
    622 		if (len > 0 && realuri[len - 1] != '/') {
    623 			realuri[len] = '/';
    624 			realuri[len + 1] = '\0';
    625 		}
    626 	}
    627 
    628 	/*
    629 	 * reject hidden targets, except if it is a well-known URI
    630 	 * according to RFC 8615
    631 	 */
    632 	if (strstr(realuri, "/.") && strncmp(realuri,
    633 	    "/.well-known/", sizeof("/.well-known/") - 1)) {
    634 		s = S_FORBIDDEN;
    635 		goto err;
    636 	}
    637 
    638 	/*
    639 	 * redirect if the original URI and the "real" URI differ or if
    640 	 * the requested host is non-canonical
    641 	 */
    642 	if (strcmp(req->uri, realuri) || (srv->vhost && vhost &&
    643 	    strcmp(req->field[REQ_HOST], vhost->chost))) {
    644 		res->status = S_MOVED_PERMANENTLY;
    645 
    646 		/* encode realuri */
    647 		encode(realuri, tmpuri);
    648 
    649 		/* determine target location */
    650 		if (srv->vhost) {
    651 			/* absolute redirection URL */
    652 			targethost = req->field[REQ_HOST][0] ? vhost->chost ?
    653 			             vhost->chost : req->field[REQ_HOST] :
    654 				     srv->host ? srv->host : "localhost";
    655 
    656 			/* do we need to add a port to the Location? */
    657 			hasport = srv->port && strcmp(srv->port, "80");
    658 
    659 			/* RFC 2732 specifies to use brackets for IPv6-addresses
    660 			 * in URLs, so we need to check if our host is one and
    661 			 * honor that later when we fill the "Location"-field */
    662 			if ((ipv6host = inet_pton(AF_INET6, targethost,
    663 			                          &addr)) < 0) {
    664 				s = S_INTERNAL_SERVER_ERROR;
    665 				goto err;
    666 			}
    667 
    668 			/* write location to response struct */
    669 			if (esnprintf(res->field[RES_LOCATION],
    670 			              sizeof(res->field[RES_LOCATION]),
    671 			              "//%s%s%s%s%s%s",
    672 			              ipv6host ? "[" : "",
    673 			              targethost,
    674 			              ipv6host ? "]" : "", hasport ? ":" : "",
    675 			              hasport ? srv->port : "", tmpuri)) {
    676 				s = S_REQUEST_TOO_LARGE;
    677 				goto err;
    678 			}
    679 		} else {
    680 			/* write relative redirection URI to response struct */
    681 			if (esnprintf(res->field[RES_LOCATION],
    682 			              sizeof(res->field[RES_LOCATION]),
    683 			              "%s", tmpuri)) {
    684 				s = S_REQUEST_TOO_LARGE;
    685 				goto err;
    686 			}
    687 		}
    688 
    689 		return;
    690 	} else {
    691 		/*
    692 		 * the URI is well-formed, we can now write the URI into
    693 		 * the response-URI and corresponding relative path
    694 		 * (optionally including the vhost servedir as a prefix)
    695 		 * into the actual response-path
    696 		 */
    697 		if (esnprintf(res->uri, sizeof(res->uri), "%s", req->uri)) {
    698 			s = S_REQUEST_TOO_LARGE;
    699 			goto err;
    700 		}
    701 		if (esnprintf(res->path, sizeof(res->path), "%s%s",
    702 		    vhost ? vhost->dir : "", RELPATH(req->uri))) {
    703 			s = S_REQUEST_TOO_LARGE;
    704 			goto err;
    705 		}
    706 	}
    707 
    708 	if (S_ISDIR(st.st_mode)) {
    709 		/*
    710 		 * check if the directory index exists by appending it to
    711 		 * the URI
    712 		 */
    713 		if (esnprintf(tmpuri, sizeof(tmpuri), "%s%s",
    714 		              req->uri, srv->docindex)) {
    715 			s = S_REQUEST_TOO_LARGE;
    716 			goto err;
    717 		}
    718 
    719 		/* stat the docindex, which must be a regular file */
    720 		if (stat(RELPATH(tmpuri), &st) < 0 || !S_ISREG(st.st_mode)) {
    721 			if (srv->listdirs) {
    722 				/* serve directory listing */
    723 				res->type = RESTYPE_DIRLISTING;
    724 				res->status = (access(res->path, R_OK)) ?
    725 				              S_FORBIDDEN : S_OK;
    726 
    727 				if (esnprintf(res->field[RES_CONTENT_TYPE],
    728 				              sizeof(res->field[RES_CONTENT_TYPE]),
    729 					      "%s", "text/html; charset=utf-8")) {
    730 					s = S_INTERNAL_SERVER_ERROR;
    731 					goto err;
    732 				}
    733 
    734 				return;
    735 			} else {
    736 				/* reject */
    737 				s = (!S_ISREG(st.st_mode) || errno == EACCES) ?
    738 				    S_FORBIDDEN : S_NOT_FOUND;
    739 				goto err;
    740 			}
    741 		}
    742 	}
    743 
    744 	/* modified since */
    745 	if (req->field[REQ_IF_MODIFIED_SINCE][0]) {
    746 		/* parse field */
    747 		if (!strptime(req->field[REQ_IF_MODIFIED_SINCE],
    748 		              "%a, %d %b %Y %T GMT", &tm)) {
    749 			s = S_BAD_REQUEST;
    750 			goto err;
    751 		}
    752 
    753 		/* compare with last modification date of the file */
    754 		if (difftime(st.st_mtim.tv_sec, timegm(&tm)) <= 0) {
    755 			res->status = S_NOT_MODIFIED;
    756 			return;
    757 		}
    758 	}
    759 
    760 	/* range */
    761 	if ((s = parse_range(req->field[REQ_RANGE], st.st_size,
    762 	                     &(res->file.lower), &(res->file.upper)))) {
    763 		if (s == S_RANGE_NOT_SATISFIABLE) {
    764 			res->status = S_RANGE_NOT_SATISFIABLE;
    765 
    766 			if (esnprintf(res->field[RES_CONTENT_RANGE],
    767 			              sizeof(res->field[RES_CONTENT_RANGE]),
    768 			              "bytes */%zu", st.st_size)) {
    769 				s = S_INTERNAL_SERVER_ERROR;
    770 				goto err;
    771 			}
    772 
    773 			return;
    774 		} else {
    775 			goto err;
    776 		}
    777 	}
    778 
    779 	/* mime */
    780 	mime = "application/octet-stream";
    781 	if ((p = strrchr(realuri, '.'))) {
    782 		for (i = 0; i < LEN(mimes); i++) {
    783 			if (!strcmp(mimes[i].ext, p + 1)) {
    784 				mime = mimes[i].type;
    785 				break;
    786 			}
    787 		}
    788 	}
    789 
    790 	/* fill response struct */
    791 	res->type = RESTYPE_FILE;
    792 
    793 	/* check if file is readable */
    794 	res->status = (access(res->path, R_OK)) ? S_FORBIDDEN :
    795 	              (req->field[REQ_RANGE][0] != '\0') ?
    796 	              S_PARTIAL_CONTENT : S_OK;
    797 
    798 	if (esnprintf(res->field[RES_ACCEPT_RANGES],
    799 	              sizeof(res->field[RES_ACCEPT_RANGES]),
    800 		      "%s", "bytes")) {
    801 		s = S_INTERNAL_SERVER_ERROR;
    802 		goto err;
    803 	}
    804 
    805 	if (esnprintf(res->field[RES_CONTENT_LENGTH],
    806 	              sizeof(res->field[RES_CONTENT_LENGTH]),
    807 	              "%zu", res->file.upper - res->file.lower + 1)) {
    808 		s = S_INTERNAL_SERVER_ERROR;
    809 		goto err;
    810 	}
    811 	if (req->field[REQ_RANGE][0] != '\0') {
    812 		if (esnprintf(res->field[RES_CONTENT_RANGE],
    813 		              sizeof(res->field[RES_CONTENT_RANGE]),
    814 		              "bytes %zd-%zd/%zu", res->file.lower,
    815 			      res->file.upper, st.st_size)) {
    816 			s = S_INTERNAL_SERVER_ERROR;
    817 			goto err;
    818 		}
    819 	}
    820 	if (esnprintf(res->field[RES_CONTENT_TYPE],
    821 	              sizeof(res->field[RES_CONTENT_TYPE]),
    822 	              "%s", mime)) {
    823 		s = S_INTERNAL_SERVER_ERROR;
    824 		goto err;
    825 	}
    826 	if (timestamp(res->field[RES_LAST_MODIFIED],
    827 	              sizeof(res->field[RES_LAST_MODIFIED]),
    828 	              st.st_mtim.tv_sec)) {
    829 		s = S_INTERNAL_SERVER_ERROR;
    830 		goto err;
    831 	}
    832 
    833 	return;
    834 err:
    835 	http_prepare_error_response(req, res, s);
    836 }
    837 
    838 void
    839 http_prepare_error_response(const struct request *req,
    840                             struct response *res, enum status s)
    841 {
    842 	/* used later */
    843 	(void)req;
    844 
    845 	/* empty all response fields */
    846 	memset(res, 0, sizeof(*res));
    847 
    848 	res->type = RESTYPE_ERROR;
    849 	res->status = s;
    850 
    851 	if (esnprintf(res->field[RES_CONTENT_TYPE],
    852 	              sizeof(res->field[RES_CONTENT_TYPE]),
    853 	              "text/html; charset=utf-8")) {
    854 		res->status = S_INTERNAL_SERVER_ERROR;
    855 	}
    856 
    857 	if (res->status == S_METHOD_NOT_ALLOWED) {
    858 		if (esnprintf(res->field[RES_ALLOW],
    859 		              sizeof(res->field[RES_ALLOW]),
    860 			      "Allow: GET, HEAD")) {
    861 			res->status = S_INTERNAL_SERVER_ERROR;
    862 		}
    863 	}
    864 }