quark

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

data.c (4933B)


      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 "data.h"
     11 #include "http.h"
     12 #include "util.h"
     13 
     14 enum status (* const data_fct[])(const struct response *,
     15                                  struct buffer *, size_t *) = {
     16 	[RESTYPE_DIRLISTING] = data_prepare_dirlisting_buf,
     17 	[RESTYPE_ERROR]      = data_prepare_error_buf,
     18 	[RESTYPE_FILE]       = data_prepare_file_buf,
     19 };
     20 
     21 static int
     22 compareent(const struct dirent **d1, const struct dirent **d2)
     23 {
     24 	int v;
     25 
     26 	v = ((*d2)->d_type == DT_DIR ? 1 : -1) -
     27 	    ((*d1)->d_type == DT_DIR ? 1 : -1);
     28 	if (v) {
     29 		return v;
     30 	}
     31 
     32 	return strcmp((*d1)->d_name, (*d2)->d_name);
     33 }
     34 
     35 static char *
     36 suffix(int t)
     37 {
     38 	switch (t) {
     39 	case DT_FIFO: return "|";
     40 	case DT_DIR:  return "/";
     41 	case DT_LNK:  return "@";
     42 	case DT_SOCK: return "=";
     43 	}
     44 
     45 	return "";
     46 }
     47 
     48 static void
     49 html_escape(const char *src, char *dst, size_t dst_siz)
     50 {
     51 	const struct {
     52 		char c;
     53 		char *s;
     54 	} escape[] = {
     55 		{ '&',  "&amp;"  },
     56 		{ '<',  "&lt;"   },
     57 		{ '>',  "&gt;"   },
     58 		{ '"',  "&quot;" },
     59 		{ '\'', "&#x27;" },
     60 	};
     61 	size_t i, j, k, esclen;
     62 
     63 	for (i = 0, j = 0; src[i] != '\0'; i++) {
     64 		for (k = 0; k < LEN(escape); k++) {
     65 			if (src[i] == escape[k].c) {
     66 				break;
     67 			}
     68 		}
     69 		if (k == LEN(escape)) {
     70 			/* no escape char at src[i] */
     71 			if (j == dst_siz - 1) {
     72 				/* silent truncation */
     73 				break;
     74 			} else {
     75 				dst[j++] = src[i];
     76 			}
     77 		} else {
     78 			/* escape char at src[i] */
     79 			esclen = strlen(escape[k].s);
     80 
     81 			if (j >= dst_siz - esclen) {
     82 				/* silent truncation */
     83 				break;
     84 			} else {
     85 				memcpy(&dst[j], escape[k].s, esclen);
     86 				j += esclen;
     87 			}
     88 		}
     89 	}
     90 	dst[j] = '\0';
     91 }
     92 
     93 enum status
     94 data_prepare_dirlisting_buf(const struct response *res,
     95                             struct buffer *buf, size_t *progress)
     96 {
     97 	enum status s = 0;
     98 	struct dirent **e;
     99 	size_t i;
    100 	int dirlen;
    101 	char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
    102 
    103 	/* reset buffer */
    104 	memset(buf, 0, sizeof(*buf));
    105 
    106 	/* read directory */
    107 	if ((dirlen = scandir(res->internal_path, &e, NULL, compareent)) < 0) {
    108 		return S_FORBIDDEN;
    109 	}
    110 
    111 	if (*progress == 0) {
    112 		/* write listing header (sizeof(esc) >= PATH_MAX) */
    113 		html_escape(res->path, esc, MIN(PATH_MAX, sizeof(esc)));
    114 		if (buffer_appendf(buf,
    115 		                   "<!DOCTYPE html>\n<html>\n\t<head>"
    116 		                   "<title>Index of %s</title></head>\n"
    117 		                   "\t<body>\n\t\t<a href=\"..\">..</a>",
    118 		                   esc) < 0) {
    119 			s = S_REQUEST_TIMEOUT;
    120 			goto cleanup;
    121 		}
    122 	}
    123 
    124 	/* listing entries */
    125 	for (i = *progress; i < (size_t)dirlen; i++) {
    126 		/* skip hidden files, "." and ".." */
    127 		if (e[i]->d_name[0] == '.') {
    128 			continue;
    129 		}
    130 
    131 		/* entry line */
    132 		html_escape(e[i]->d_name, esc, sizeof(esc));
    133 		if (buffer_appendf(buf,
    134 		                   "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
    135 		                   esc,
    136 		                   (e[i]->d_type == DT_DIR) ? "/" : "",
    137 		                   esc,
    138 		                   suffix(e[i]->d_type))) {
    139 			/* buffer full */
    140 			break;
    141 		}
    142 	}
    143 	*progress = i;
    144 
    145 	if (*progress == (size_t)dirlen) {
    146 		/* listing footer */
    147 		if (buffer_appendf(buf, "\n\t</body>\n</html>\n") < 0) {
    148 			s = S_REQUEST_TIMEOUT;
    149 			goto cleanup;
    150 		}
    151 		(*progress)++;
    152 	}
    153 
    154 cleanup:
    155 	while (dirlen--) {
    156 		free(e[dirlen]);
    157 	}
    158 	free(e);
    159 
    160 	return s;
    161 }
    162 
    163 enum status
    164 data_prepare_error_buf(const struct response *res, struct buffer *buf,
    165                    size_t *progress)
    166 {
    167 	/* reset buffer */
    168 	memset(buf, 0, sizeof(*buf));
    169 
    170 	if (*progress == 0) {
    171 		/* write error body */
    172 		if (buffer_appendf(buf,
    173 		                   "<!DOCTYPE html>\n<html>\n\t<head>\n"
    174 		                   "\t\t<title>%d %s</title>\n\t</head>\n"
    175 		                   "\t<body>\n\t\t<h1>%d %s</h1>\n"
    176 		                   "\t</body>\n</html>\n",
    177 		                   res->status, status_str[res->status],
    178 			           res->status, status_str[res->status])) {
    179 			return S_INTERNAL_SERVER_ERROR;
    180 		}
    181 		(*progress)++;
    182 	}
    183 
    184 	return 0;
    185 }
    186 
    187 enum status
    188 data_prepare_file_buf(const struct response *res, struct buffer *buf,
    189                   size_t *progress)
    190 {
    191 	FILE *fp;
    192 	enum status s = 0;
    193 	ssize_t r;
    194 	size_t remaining;
    195 
    196 	/* reset buffer */
    197 	memset(buf, 0, sizeof(*buf));
    198 
    199 	/* open file */
    200 	if (!(fp = fopen(res->internal_path, "r"))) {
    201 		s = S_FORBIDDEN;
    202 		goto cleanup;
    203 	}
    204 
    205 	/* seek to lower bound + progress */
    206 	if (fseek(fp, res->file.lower + *progress, SEEK_SET)) {
    207 		s = S_INTERNAL_SERVER_ERROR;
    208 		goto cleanup;
    209 	}
    210 
    211 	/* read data into buf */
    212 	remaining = res->file.upper - res->file.lower + 1 - *progress;
    213 	while ((r = fread(buf->data + buf->len, 1,
    214 	                  MIN(sizeof(buf->data) - buf->len,
    215 			  remaining), fp))) {
    216 		if (r < 0) {
    217 			s = S_INTERNAL_SERVER_ERROR;
    218 			goto cleanup;
    219 		}
    220 		buf->len += r;
    221 		*progress += r;
    222 		remaining -= r;
    223 	}
    224 
    225 cleanup:
    226 	if (fp) {
    227 		fclose(fp);
    228 	}
    229 
    230 	return s;
    231 }