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 { '&', "&" }, 56 { '<', "<" }, 57 { '>', ">" }, 58 { '"', """ }, 59 { '\'', "'" }, 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 }