quark

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

resp.c (4073B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <dirent.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <sys/stat.h>
      7 #include <time.h>
      8 #include <unistd.h>
      9 
     10 #include "http.h"
     11 #include "resp.h"
     12 #include "util.h"
     13 
     14 static int
     15 compareent(const struct dirent **d1, const struct dirent **d2)
     16 {
     17 	int v;
     18 
     19 	v = ((*d2)->d_type == DT_DIR ? 1 : -1) -
     20 	    ((*d1)->d_type == DT_DIR ? 1 : -1);
     21 	if (v) {
     22 		return v;
     23 	}
     24 
     25 	return strcmp((*d1)->d_name, (*d2)->d_name);
     26 }
     27 
     28 static char *
     29 suffix(int t)
     30 {
     31 	switch (t) {
     32 	case DT_FIFO: return "|";
     33 	case DT_DIR:  return "/";
     34 	case DT_LNK:  return "@";
     35 	case DT_SOCK: return "=";
     36 	}
     37 
     38 	return "";
     39 }
     40 
     41 enum status
     42 resp_dir(int fd, char *name, struct request *r)
     43 {
     44 	struct dirent **e;
     45 	size_t i;
     46 	int dirlen, s;
     47 	static char t[TIMESTAMP_LEN];
     48 
     49 	/* read directory */
     50 	if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) {
     51 		return http_send_status(fd, S_FORBIDDEN);
     52 	}
     53 
     54 	/* send header as late as possible */
     55 	if (dprintf(fd,
     56 	            "HTTP/1.1 %d %s\r\n"
     57 	            "Date: %s\r\n"
     58 	            "Connection: close\r\n"
     59 		    "Content-Type: text/html; charset=utf-8\r\n"
     60 		    "\r\n",
     61 	            S_OK, status_str[S_OK], timestamp(time(NULL), t)) < 0) {
     62 		s = S_REQUEST_TIMEOUT;
     63 		goto cleanup;
     64 	}
     65 
     66 	if (r->method == M_GET) {
     67 		/* listing header */
     68 		if (dprintf(fd,
     69 		            "<!DOCTYPE html>\n<html>\n\t<head>"
     70 		            "<title>Index of %s</title></head>\n"
     71 		            "\t<body>\n\t\t<a href=\"..\">..</a>",
     72 		            name) < 0) {
     73 			s = S_REQUEST_TIMEOUT;
     74 			goto cleanup;
     75 		}
     76 
     77 		/* listing */
     78 		for (i = 0; i < (size_t)dirlen; i++) {
     79 			/* skip hidden files, "." and ".." */
     80 			if (e[i]->d_name[0] == '.') {
     81 				continue;
     82 			}
     83 
     84 			/* entry line */
     85 			if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
     86 			            e[i]->d_name,
     87 			            (e[i]->d_type == DT_DIR) ? "/" : "",
     88 			            e[i]->d_name,
     89 			            suffix(e[i]->d_type)) < 0) {
     90 				s = S_REQUEST_TIMEOUT;
     91 				goto cleanup;
     92 			}
     93 		}
     94 
     95 		/* listing footer */
     96 		if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
     97 			s = S_REQUEST_TIMEOUT;
     98 			goto cleanup;
     99 		}
    100 	}
    101 	s = S_OK;
    102 
    103 cleanup:
    104 	while (dirlen--) {
    105 		free(e[dirlen]);
    106 	}
    107 	free(e);
    108 
    109 	return s;
    110 }
    111 
    112 enum status
    113 resp_file(int fd, char *name, struct request *r, struct stat *st, char *mime,
    114           off_t lower, off_t upper)
    115 {
    116 	FILE *fp;
    117 	enum status s;
    118 	ssize_t bread, bwritten;
    119 	off_t remaining;
    120 	int range;
    121 	static char buf[BUFSIZ], *p, t1[TIMESTAMP_LEN], t2[TIMESTAMP_LEN];
    122 
    123 	/* open file */
    124 	if (!(fp = fopen(name, "r"))) {
    125 		s = http_send_status(fd, S_FORBIDDEN);
    126 		goto cleanup;
    127 	}
    128 
    129 	/* seek to lower bound */
    130 	if (fseek(fp, lower, SEEK_SET)) {
    131 		s = http_send_status(fd, S_INTERNAL_SERVER_ERROR);
    132 		goto cleanup;
    133 	}
    134 
    135 	/* send header as late as possible */
    136 	range = r->field[REQ_RANGE][0];
    137 	s = range ? S_PARTIAL_CONTENT : S_OK;
    138 
    139 	if (dprintf(fd,
    140 	            "HTTP/1.1 %d %s\r\n"
    141 	            "Date: %s\r\n"
    142 	            "Connection: close\r\n"
    143 	            "Last-Modified: %s\r\n"
    144 	            "Content-Type: %s\r\n"
    145 	            "Content-Length: %zu\r\n",
    146 	            s, status_str[s], timestamp(time(NULL), t1),
    147 	            timestamp(st->st_mtim.tv_sec, t2), mime,
    148 	            upper - lower + 1) < 0) {
    149 		s = S_REQUEST_TIMEOUT;
    150 		goto cleanup;
    151 	}
    152 	if (range) {
    153 		if (dprintf(fd, "Content-Range: bytes %zd-%zd/%zu\r\n",
    154 		            lower, upper + (upper < 0), st->st_size) < 0) {
    155 			s = S_REQUEST_TIMEOUT;
    156 			goto cleanup;
    157 		}
    158 	}
    159 	if (dprintf(fd, "\r\n") < 0) {
    160 		s = S_REQUEST_TIMEOUT;
    161 		goto cleanup;
    162 	}
    163 
    164 	if (r->method == M_GET) {
    165 		/* write data until upper bound is hit */
    166 		remaining = upper - lower + 1;
    167 
    168 		while ((bread = fread(buf, 1, MIN(sizeof(buf),
    169 		                      (size_t)remaining), fp))) {
    170 			if (bread < 0) {
    171 				return S_INTERNAL_SERVER_ERROR;
    172 			}
    173 			remaining -= bread;
    174 			p = buf;
    175 			while (bread > 0) {
    176 				bwritten = write(fd, p, bread);
    177 				if (bwritten <= 0) {
    178 					return S_REQUEST_TIMEOUT;
    179 				}
    180 				bread -= bwritten;
    181 				p += bwritten;
    182 			}
    183 		}
    184 	}
    185 cleanup:
    186 	if (fp) {
    187 		fclose(fp);
    188 	}
    189 
    190 	return s;
    191 }