data.c - 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 { '&', "&" }, | |
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))… | |
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>\… | |
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 } |