Introduction
Introduction Statistics Contact Development Disclaimer Help
http.c - 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 termin…
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 notat…
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 ad…
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 *re…
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…
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].chos…
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 lo…
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_MA…
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), &er…
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->…
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,…
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 reque…
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), "/…
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, NUL…
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-a…
821 * in URLs, so we need to check if our host is o…
822 * honor that later when we fill the "Location"-…
823 if ((ipv6host = inet_pton(AF_INET6, res->vhost->…
824 &addr)) < 0) {
825 s = S_INTERNAL_SERVER_ERROR;
826 goto err;
827 }
828
829 /*
830 * write location to response struct (re-includi…
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 st…
852 * (re-including the query and fragment, if pres…
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) != …
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_TYP…
902 sizeof(res->field[RES_CONT…
903 "%s", "text/html; charset=…
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 == EA…
912 S_FORBIDDEN : S_NOT_FOUND;
913 goto err;
914 }
915 } else {
916 /* the docindex exists; copy tmppath to internal…
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_RANG…
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 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.