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 }