1 |
|
|
/* $OpenBSD: server_file.c,v 1.65 2017/02/02 22:19:59 reyk Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2006 - 2017 Reyk Floeter <reyk@openbsd.org> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/time.h> |
21 |
|
|
#include <sys/stat.h> |
22 |
|
|
|
23 |
|
|
#include <limits.h> |
24 |
|
|
#include <errno.h> |
25 |
|
|
#include <fcntl.h> |
26 |
|
|
#include <stdlib.h> |
27 |
|
|
#include <string.h> |
28 |
|
|
#include <unistd.h> |
29 |
|
|
#include <stdio.h> |
30 |
|
|
#include <dirent.h> |
31 |
|
|
#include <time.h> |
32 |
|
|
#include <event.h> |
33 |
|
|
|
34 |
|
|
#include "httpd.h" |
35 |
|
|
#include "http.h" |
36 |
|
|
|
37 |
|
|
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) |
38 |
|
|
#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) |
39 |
|
|
|
40 |
|
|
int server_file_access(struct httpd *, struct client *, |
41 |
|
|
char *, size_t); |
42 |
|
|
int server_file_request(struct httpd *, struct client *, |
43 |
|
|
char *, struct stat *); |
44 |
|
|
int server_partial_file_request(struct httpd *, struct client *, |
45 |
|
|
char *, struct stat *, char *); |
46 |
|
|
int server_file_index(struct httpd *, struct client *, |
47 |
|
|
struct stat *); |
48 |
|
|
int server_file_modified_since(struct http_descriptor *, |
49 |
|
|
struct stat *); |
50 |
|
|
int server_file_method(struct client *); |
51 |
|
|
int parse_range_spec(char *, size_t, struct range *); |
52 |
|
|
int parse_ranges(struct client *, char *, size_t); |
53 |
|
|
|
54 |
|
|
int |
55 |
|
|
server_file_access(struct httpd *env, struct client *clt, |
56 |
|
|
char *path, size_t len) |
57 |
|
|
{ |
58 |
|
|
struct http_descriptor *desc = clt->clt_descreq; |
59 |
|
|
struct server_config *srv_conf = clt->clt_srv_conf; |
60 |
|
|
struct stat st; |
61 |
|
|
struct kv *r, key; |
62 |
|
|
char *newpath, *encodedpath; |
63 |
|
|
int ret; |
64 |
|
|
|
65 |
|
|
errno = 0; |
66 |
|
|
|
67 |
|
|
if (access(path, R_OK) == -1) { |
68 |
|
|
goto fail; |
69 |
|
|
} else if (stat(path, &st) == -1) { |
70 |
|
|
goto fail; |
71 |
|
|
} else if (S_ISDIR(st.st_mode)) { |
72 |
|
|
/* Deny access if directory indexing is disabled */ |
73 |
|
|
if (srv_conf->flags & SRVFLAG_NO_INDEX) { |
74 |
|
|
errno = EACCES; |
75 |
|
|
goto fail; |
76 |
|
|
} |
77 |
|
|
|
78 |
|
|
if (desc->http_path_alias != NULL) { |
79 |
|
|
/* Recursion - the index "file" is a directory? */ |
80 |
|
|
errno = EINVAL; |
81 |
|
|
goto fail; |
82 |
|
|
} |
83 |
|
|
|
84 |
|
|
/* Redirect to path with trailing "/" */ |
85 |
|
|
if (path[strlen(path) - 1] != '/') { |
86 |
|
|
if ((encodedpath = url_encode(desc->http_path)) == NULL) |
87 |
|
|
return (500); |
88 |
|
|
if (asprintf(&newpath, "http%s://%s%s/", |
89 |
|
|
srv_conf->flags & SRVFLAG_TLS ? "s" : "", |
90 |
|
|
desc->http_host, encodedpath) == -1) { |
91 |
|
|
free(encodedpath); |
92 |
|
|
return (500); |
93 |
|
|
} |
94 |
|
|
free(encodedpath); |
95 |
|
|
|
96 |
|
|
/* Path alias will be used for the redirection */ |
97 |
|
|
desc->http_path_alias = newpath; |
98 |
|
|
|
99 |
|
|
/* Indicate that the file has been moved */ |
100 |
|
|
return (301); |
101 |
|
|
} |
102 |
|
|
|
103 |
|
|
/* Append the default index file to the location */ |
104 |
|
|
if (asprintf(&newpath, "%s%s", desc->http_path, |
105 |
|
|
srv_conf->index) == -1) |
106 |
|
|
return (500); |
107 |
|
|
desc->http_path_alias = newpath; |
108 |
|
|
if (server_getlocation(clt, newpath) != srv_conf) { |
109 |
|
|
/* The location has changed */ |
110 |
|
|
return (server_file(env, clt)); |
111 |
|
|
} |
112 |
|
|
|
113 |
|
|
/* Otherwise append the default index file to the path */ |
114 |
|
|
if (strlcat(path, srv_conf->index, len) >= len) { |
115 |
|
|
errno = EACCES; |
116 |
|
|
goto fail; |
117 |
|
|
} |
118 |
|
|
|
119 |
|
|
ret = server_file_access(env, clt, path, len); |
120 |
|
|
if (ret == 404) { |
121 |
|
|
/* |
122 |
|
|
* Index file not found; fail if auto-indexing is |
123 |
|
|
* not enabled, otherwise return success but |
124 |
|
|
* indicate directory with S_ISDIR of the previous |
125 |
|
|
* stat. |
126 |
|
|
*/ |
127 |
|
|
if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { |
128 |
|
|
errno = EACCES; |
129 |
|
|
goto fail; |
130 |
|
|
} |
131 |
|
|
|
132 |
|
|
return (server_file_index(env, clt, &st)); |
133 |
|
|
} |
134 |
|
|
return (ret); |
135 |
|
|
} else if (!S_ISREG(st.st_mode)) { |
136 |
|
|
/* Don't follow symlinks and ignore special files */ |
137 |
|
|
errno = EACCES; |
138 |
|
|
goto fail; |
139 |
|
|
} |
140 |
|
|
|
141 |
|
|
key.kv_key = "Range"; |
142 |
|
|
r = kv_find(&desc->http_headers, &key); |
143 |
|
|
if (r != NULL) |
144 |
|
|
return (server_partial_file_request(env, clt, path, &st, |
145 |
|
|
r->kv_value)); |
146 |
|
|
else |
147 |
|
|
return (server_file_request(env, clt, path, &st)); |
148 |
|
|
|
149 |
|
|
fail: |
150 |
|
|
switch (errno) { |
151 |
|
|
case ENOENT: |
152 |
|
|
case ENOTDIR: |
153 |
|
|
return (404); |
154 |
|
|
case EACCES: |
155 |
|
|
return (403); |
156 |
|
|
default: |
157 |
|
|
return (500); |
158 |
|
|
} |
159 |
|
|
|
160 |
|
|
/* NOTREACHED */ |
161 |
|
|
} |
162 |
|
|
|
163 |
|
|
int |
164 |
|
|
server_file(struct httpd *env, struct client *clt) |
165 |
|
|
{ |
166 |
|
|
struct http_descriptor *desc = clt->clt_descreq; |
167 |
|
|
struct server_config *srv_conf = clt->clt_srv_conf; |
168 |
|
|
char path[PATH_MAX]; |
169 |
|
|
const char *stripped, *errstr = NULL; |
170 |
|
|
int ret = 500; |
171 |
|
|
|
172 |
|
|
if (srv_conf->flags & SRVFLAG_FCGI) |
173 |
|
|
return (server_fcgi(env, clt)); |
174 |
|
|
|
175 |
|
|
/* Request path is already canonicalized */ |
176 |
|
|
stripped = server_root_strip( |
177 |
|
|
desc->http_path_alias != NULL ? |
178 |
|
|
desc->http_path_alias : desc->http_path, |
179 |
|
|
srv_conf->strip); |
180 |
|
|
if ((size_t)snprintf(path, sizeof(path), "%s%s", |
181 |
|
|
srv_conf->root, stripped) >= sizeof(path)) { |
182 |
|
|
errstr = desc->http_path; |
183 |
|
|
goto abort; |
184 |
|
|
} |
185 |
|
|
|
186 |
|
|
/* Returns HTTP status code on error */ |
187 |
|
|
if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { |
188 |
|
|
errstr = desc->http_path_alias != NULL ? |
189 |
|
|
desc->http_path_alias : desc->http_path; |
190 |
|
|
goto abort; |
191 |
|
|
} |
192 |
|
|
|
193 |
|
|
return (ret); |
194 |
|
|
|
195 |
|
|
abort: |
196 |
|
|
if (errstr == NULL) |
197 |
|
|
errstr = strerror(errno); |
198 |
|
|
server_abort_http(clt, ret, errstr); |
199 |
|
|
return (-1); |
200 |
|
|
} |
201 |
|
|
|
202 |
|
|
int |
203 |
|
|
server_file_method(struct client *clt) |
204 |
|
|
{ |
205 |
|
|
struct http_descriptor *desc = clt->clt_descreq; |
206 |
|
|
|
207 |
|
|
switch (desc->http_method) { |
208 |
|
|
case HTTP_METHOD_GET: |
209 |
|
|
case HTTP_METHOD_HEAD: |
210 |
|
|
return (0); |
211 |
|
|
default: |
212 |
|
|
/* Other methods are not allowed */ |
213 |
|
|
errno = EACCES; |
214 |
|
|
return (405); |
215 |
|
|
} |
216 |
|
|
/* NOTREACHED */ |
217 |
|
|
} |
218 |
|
|
|
219 |
|
|
int |
220 |
|
|
server_file_request(struct httpd *env, struct client *clt, char *path, |
221 |
|
|
struct stat *st) |
222 |
|
|
{ |
223 |
|
|
struct server_config *srv_conf = clt->clt_srv_conf; |
224 |
|
|
struct media_type *media; |
225 |
|
|
const char *errstr = NULL; |
226 |
|
|
int fd = -1, ret, code = 500; |
227 |
|
|
|
228 |
|
|
if ((ret = server_file_method(clt)) != 0) { |
229 |
|
|
code = ret; |
230 |
|
|
goto abort; |
231 |
|
|
} |
232 |
|
|
|
233 |
|
|
if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1) |
234 |
|
|
return (ret); |
235 |
|
|
|
236 |
|
|
/* Now open the file, should be readable or we have another problem */ |
237 |
|
|
if ((fd = open(path, O_RDONLY)) == -1) |
238 |
|
|
goto abort; |
239 |
|
|
|
240 |
|
|
media = media_find_config(env, srv_conf, path); |
241 |
|
|
ret = server_response_http(clt, 200, media, st->st_size, |
242 |
|
|
MINIMUM(time(NULL), st->st_mtim.tv_sec)); |
243 |
|
|
switch (ret) { |
244 |
|
|
case -1: |
245 |
|
|
goto fail; |
246 |
|
|
case 0: |
247 |
|
|
/* Connection is already finished */ |
248 |
|
|
close(fd); |
249 |
|
|
goto done; |
250 |
|
|
default: |
251 |
|
|
break; |
252 |
|
|
} |
253 |
|
|
|
254 |
|
|
clt->clt_fd = fd; |
255 |
|
|
if (clt->clt_srvbev != NULL) |
256 |
|
|
bufferevent_free(clt->clt_srvbev); |
257 |
|
|
|
258 |
|
|
clt->clt_srvbev_throttled = 0; |
259 |
|
|
clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, |
260 |
|
|
server_write, server_file_error, clt); |
261 |
|
|
if (clt->clt_srvbev == NULL) { |
262 |
|
|
errstr = "failed to allocate file buffer event"; |
263 |
|
|
goto fail; |
264 |
|
|
} |
265 |
|
|
|
266 |
|
|
/* Adjust read watermark to the socket output buffer size */ |
267 |
|
|
bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, |
268 |
|
|
clt->clt_sndbufsiz); |
269 |
|
|
|
270 |
|
|
bufferevent_settimeout(clt->clt_srvbev, |
271 |
|
|
srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); |
272 |
|
|
bufferevent_enable(clt->clt_srvbev, EV_READ); |
273 |
|
|
bufferevent_disable(clt->clt_bev, EV_READ); |
274 |
|
|
|
275 |
|
|
done: |
276 |
|
|
server_reset_http(clt); |
277 |
|
|
return (0); |
278 |
|
|
fail: |
279 |
|
|
bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); |
280 |
|
|
bufferevent_free(clt->clt_bev); |
281 |
|
|
clt->clt_bev = NULL; |
282 |
|
|
abort: |
283 |
|
|
if (fd != -1) |
284 |
|
|
close(fd); |
285 |
|
|
if (errstr == NULL) |
286 |
|
|
errstr = strerror(errno); |
287 |
|
|
server_abort_http(clt, code, errstr); |
288 |
|
|
return (-1); |
289 |
|
|
} |
290 |
|
|
|
291 |
|
|
int |
292 |
|
|
server_partial_file_request(struct httpd *env, struct client *clt, char *path, |
293 |
|
|
struct stat *st, char *range_str) |
294 |
|
|
{ |
295 |
|
|
struct server_config *srv_conf = clt->clt_srv_conf; |
296 |
|
|
struct http_descriptor *resp = clt->clt_descresp; |
297 |
|
|
struct http_descriptor *desc = clt->clt_descreq; |
298 |
|
|
struct media_type *media, multipart_media; |
299 |
|
|
struct range_data *r = &clt->clt_ranges; |
300 |
|
|
struct range *range; |
301 |
|
|
size_t content_length = 0; |
302 |
|
|
int code = 500, fd = -1, i, nranges, ret; |
303 |
|
|
char content_range[64]; |
304 |
|
|
const char *errstr = NULL; |
305 |
|
|
|
306 |
|
|
/* Ignore range request for methods other than GET */ |
307 |
|
|
if (desc->http_method != HTTP_METHOD_GET) |
308 |
|
|
return server_file_request(env, clt, path, st); |
309 |
|
|
|
310 |
|
|
if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) { |
311 |
|
|
code = 416; |
312 |
|
|
(void)snprintf(content_range, sizeof(content_range), |
313 |
|
|
"bytes */%lld", st->st_size); |
314 |
|
|
errstr = content_range; |
315 |
|
|
goto abort; |
316 |
|
|
} |
317 |
|
|
|
318 |
|
|
/* Now open the file, should be readable or we have another problem */ |
319 |
|
|
if ((fd = open(path, O_RDONLY)) == -1) |
320 |
|
|
goto abort; |
321 |
|
|
|
322 |
|
|
media = media_find_config(env, srv_conf, path); |
323 |
|
|
r->range_media = media; |
324 |
|
|
|
325 |
|
|
if (nranges == 1) { |
326 |
|
|
range = &r->range[0]; |
327 |
|
|
(void)snprintf(content_range, sizeof(content_range), |
328 |
|
|
"bytes %lld-%lld/%lld", range->start, range->end, |
329 |
|
|
st->st_size); |
330 |
|
|
if (kv_add(&resp->http_headers, "Content-Range", |
331 |
|
|
content_range) == NULL) |
332 |
|
|
goto abort; |
333 |
|
|
|
334 |
|
|
range = &r->range[0]; |
335 |
|
|
content_length += range->end - range->start + 1; |
336 |
|
|
} else { |
337 |
|
|
/* Add boundary, all parts will be handled by the callback */ |
338 |
|
|
arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary)); |
339 |
|
|
|
340 |
|
|
/* Calculate Content-Length of the complete multipart body */ |
341 |
|
|
for (i = 0; i < nranges; i++) { |
342 |
|
|
range = &r->range[i]; |
343 |
|
|
|
344 |
|
|
/* calculate Content-Length of the complete body */ |
345 |
|
|
if ((ret = snprintf(NULL, 0, |
346 |
|
|
"\r\n--%llu\r\n" |
347 |
|
|
"Content-Type: %s/%s\r\n" |
348 |
|
|
"Content-Range: bytes %lld-%lld/%lld\r\n\r\n", |
349 |
|
|
clt->clt_boundary, |
350 |
|
|
media->media_type, media->media_subtype, |
351 |
|
|
range->start, range->end, st->st_size)) < 0) |
352 |
|
|
goto abort; |
353 |
|
|
|
354 |
|
|
/* Add data length */ |
355 |
|
|
content_length += ret + range->end - range->start + 1; |
356 |
|
|
|
357 |
|
|
} |
358 |
|
|
if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n", |
359 |
|
|
clt->clt_boundary)) < 0) |
360 |
|
|
goto abort; |
361 |
|
|
content_length += ret; |
362 |
|
|
|
363 |
|
|
/* prepare multipart/byteranges media type */ |
364 |
|
|
(void)strlcpy(multipart_media.media_type, "multipart", |
365 |
|
|
sizeof(multipart_media.media_type)); |
366 |
|
|
(void)snprintf(multipart_media.media_subtype, |
367 |
|
|
sizeof(multipart_media.media_subtype), |
368 |
|
|
"byteranges; boundary=%llu", clt->clt_boundary); |
369 |
|
|
media = &multipart_media; |
370 |
|
|
} |
371 |
|
|
|
372 |
|
|
/* Start with first range */ |
373 |
|
|
r->range_toread = TOREAD_HTTP_RANGE; |
374 |
|
|
|
375 |
|
|
ret = server_response_http(clt, 206, media, content_length, |
376 |
|
|
MINIMUM(time(NULL), st->st_mtim.tv_sec)); |
377 |
|
|
switch (ret) { |
378 |
|
|
case -1: |
379 |
|
|
goto fail; |
380 |
|
|
case 0: |
381 |
|
|
/* Connection is already finished */ |
382 |
|
|
close(fd); |
383 |
|
|
goto done; |
384 |
|
|
default: |
385 |
|
|
break; |
386 |
|
|
} |
387 |
|
|
|
388 |
|
|
clt->clt_fd = fd; |
389 |
|
|
if (clt->clt_srvbev != NULL) |
390 |
|
|
bufferevent_free(clt->clt_srvbev); |
391 |
|
|
|
392 |
|
|
clt->clt_srvbev_throttled = 0; |
393 |
|
|
clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange, |
394 |
|
|
server_write, server_file_error, clt); |
395 |
|
|
if (clt->clt_srvbev == NULL) { |
396 |
|
|
errstr = "failed to allocate file buffer event"; |
397 |
|
|
goto fail; |
398 |
|
|
} |
399 |
|
|
|
400 |
|
|
/* Adjust read watermark to the socket output buffer size */ |
401 |
|
|
bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, |
402 |
|
|
clt->clt_sndbufsiz); |
403 |
|
|
|
404 |
|
|
bufferevent_settimeout(clt->clt_srvbev, |
405 |
|
|
srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); |
406 |
|
|
bufferevent_enable(clt->clt_srvbev, EV_READ); |
407 |
|
|
bufferevent_disable(clt->clt_bev, EV_READ); |
408 |
|
|
|
409 |
|
|
done: |
410 |
|
|
server_reset_http(clt); |
411 |
|
|
return (0); |
412 |
|
|
fail: |
413 |
|
|
bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); |
414 |
|
|
bufferevent_free(clt->clt_bev); |
415 |
|
|
clt->clt_bev = NULL; |
416 |
|
|
abort: |
417 |
|
|
if (fd != -1) |
418 |
|
|
close(fd); |
419 |
|
|
if (errstr == NULL) |
420 |
|
|
errstr = strerror(errno); |
421 |
|
|
server_abort_http(clt, code, errstr); |
422 |
|
|
return (-1); |
423 |
|
|
} |
424 |
|
|
|
425 |
|
|
int |
426 |
|
|
server_file_index(struct httpd *env, struct client *clt, struct stat *st) |
427 |
|
|
{ |
428 |
|
|
char path[PATH_MAX]; |
429 |
|
|
char tmstr[21]; |
430 |
|
|
struct http_descriptor *desc = clt->clt_descreq; |
431 |
|
|
struct server_config *srv_conf = clt->clt_srv_conf; |
432 |
|
|
struct dirent **namelist, *dp; |
433 |
|
|
int namesize, i, ret, fd = -1, namewidth, skip; |
434 |
|
|
int code = 500; |
435 |
|
|
struct evbuffer *evb = NULL; |
436 |
|
|
struct media_type *media; |
437 |
|
|
const char *stripped, *style; |
438 |
|
|
char *escapeduri, *escapedhtml, *escapedpath; |
439 |
|
|
struct tm tm; |
440 |
|
|
time_t t, dir_mtime; |
441 |
|
|
|
442 |
|
|
if ((ret = server_file_method(clt)) != 0) { |
443 |
|
|
code = ret; |
444 |
|
|
goto abort; |
445 |
|
|
} |
446 |
|
|
|
447 |
|
|
/* Request path is already canonicalized */ |
448 |
|
|
stripped = server_root_strip(desc->http_path, srv_conf->strip); |
449 |
|
|
if ((size_t)snprintf(path, sizeof(path), "%s%s", |
450 |
|
|
srv_conf->root, stripped) >= sizeof(path)) |
451 |
|
|
goto abort; |
452 |
|
|
|
453 |
|
|
/* Now open the file, should be readable or we have another problem */ |
454 |
|
|
if ((fd = open(path, O_RDONLY)) == -1) |
455 |
|
|
goto abort; |
456 |
|
|
|
457 |
|
|
/* Save last modification time */ |
458 |
|
|
dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); |
459 |
|
|
|
460 |
|
|
if ((evb = evbuffer_new()) == NULL) |
461 |
|
|
goto abort; |
462 |
|
|
|
463 |
|
|
if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) |
464 |
|
|
goto abort; |
465 |
|
|
|
466 |
|
|
/* Indicate failure but continue going through the list */ |
467 |
|
|
skip = 0; |
468 |
|
|
|
469 |
|
|
if ((escapedpath = escape_html(desc->http_path)) == NULL) |
470 |
|
|
goto fail; |
471 |
|
|
|
472 |
|
|
/* A CSS stylesheet allows minimal customization by the user */ |
473 |
|
|
style = "body { background-color: white; color: black; font-family: " |
474 |
|
|
"sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; |
475 |
|
|
/* Generate simple HTML index document */ |
476 |
|
|
if (evbuffer_add_printf(evb, |
477 |
|
|
"<!DOCTYPE html>\n" |
478 |
|
|
"<html>\n" |
479 |
|
|
"<head>\n" |
480 |
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; " |
481 |
|
|
"charset=utf-8\"/>\n" |
482 |
|
|
"<title>Index of %s</title>\n" |
483 |
|
|
"<style type=\"text/css\"><!--\n%s\n--></style>\n" |
484 |
|
|
"</head>\n" |
485 |
|
|
"<body>\n" |
486 |
|
|
"<h1>Index of %s</h1>\n" |
487 |
|
|
"<hr>\n<pre>\n", |
488 |
|
|
escapedpath, style, escapedpath) == -1) |
489 |
|
|
skip = 1; |
490 |
|
|
|
491 |
|
|
free(escapedpath); |
492 |
|
|
|
493 |
|
|
for (i = 0; i < namesize; i++) { |
494 |
|
|
dp = namelist[i]; |
495 |
|
|
|
496 |
|
|
if (skip || |
497 |
|
|
fstatat(fd, dp->d_name, st, 0) == -1) { |
498 |
|
|
free(dp); |
499 |
|
|
continue; |
500 |
|
|
} |
501 |
|
|
|
502 |
|
|
t = st->st_mtime; |
503 |
|
|
localtime_r(&t, &tm); |
504 |
|
|
strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); |
505 |
|
|
namewidth = 51 - strlen(dp->d_name); |
506 |
|
|
|
507 |
|
|
if ((escapeduri = url_encode(dp->d_name)) == NULL) |
508 |
|
|
goto fail; |
509 |
|
|
if ((escapedhtml = escape_html(dp->d_name)) == NULL) |
510 |
|
|
goto fail; |
511 |
|
|
|
512 |
|
|
if (dp->d_name[0] == '.' && |
513 |
|
|
!(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { |
514 |
|
|
/* ignore hidden files starting with a dot */ |
515 |
|
|
} else if (S_ISDIR(st->st_mode)) { |
516 |
|
|
namewidth -= 1; /* trailing slash */ |
517 |
|
|
if (evbuffer_add_printf(evb, |
518 |
|
|
"<a href=\"%s%s/\">%s/</a>%*s%s%20s\n", |
519 |
|
|
strchr(escapeduri, ':') != NULL ? "./" : "", |
520 |
|
|
escapeduri, escapedhtml, |
521 |
|
|
MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1) |
522 |
|
|
skip = 1; |
523 |
|
|
} else if (S_ISREG(st->st_mode)) { |
524 |
|
|
if (evbuffer_add_printf(evb, |
525 |
|
|
"<a href=\"%s%s\">%s</a>%*s%s%20llu\n", |
526 |
|
|
strchr(escapeduri, ':') != NULL ? "./" : "", |
527 |
|
|
escapeduri, escapedhtml, |
528 |
|
|
MAXIMUM(namewidth, 0), " ", |
529 |
|
|
tmstr, st->st_size) == -1) |
530 |
|
|
skip = 1; |
531 |
|
|
} |
532 |
|
|
free(escapeduri); |
533 |
|
|
free(escapedhtml); |
534 |
|
|
free(dp); |
535 |
|
|
} |
536 |
|
|
free(namelist); |
537 |
|
|
|
538 |
|
|
if (skip || |
539 |
|
|
evbuffer_add_printf(evb, |
540 |
|
|
"</pre>\n<hr>\n</body>\n</html>\n") == -1) |
541 |
|
|
goto abort; |
542 |
|
|
|
543 |
|
|
close(fd); |
544 |
|
|
fd = -1; |
545 |
|
|
|
546 |
|
|
media = media_find_config(env, srv_conf, "index.html"); |
547 |
|
|
ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), |
548 |
|
|
dir_mtime); |
549 |
|
|
switch (ret) { |
550 |
|
|
case -1: |
551 |
|
|
goto fail; |
552 |
|
|
case 0: |
553 |
|
|
/* Connection is already finished */ |
554 |
|
|
evbuffer_free(evb); |
555 |
|
|
goto done; |
556 |
|
|
default: |
557 |
|
|
break; |
558 |
|
|
} |
559 |
|
|
|
560 |
|
|
if (server_bufferevent_write_buffer(clt, evb) == -1) |
561 |
|
|
goto fail; |
562 |
|
|
evbuffer_free(evb); |
563 |
|
|
evb = NULL; |
564 |
|
|
|
565 |
|
|
bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); |
566 |
|
|
if (clt->clt_persist) |
567 |
|
|
clt->clt_toread = TOREAD_HTTP_HEADER; |
568 |
|
|
else |
569 |
|
|
clt->clt_toread = TOREAD_HTTP_NONE; |
570 |
|
|
clt->clt_done = 0; |
571 |
|
|
|
572 |
|
|
done: |
573 |
|
|
server_reset_http(clt); |
574 |
|
|
return (0); |
575 |
|
|
fail: |
576 |
|
|
bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); |
577 |
|
|
bufferevent_free(clt->clt_bev); |
578 |
|
|
clt->clt_bev = NULL; |
579 |
|
|
abort: |
580 |
|
|
if (fd != -1) |
581 |
|
|
close(fd); |
582 |
|
|
if (evb != NULL) |
583 |
|
|
evbuffer_free(evb); |
584 |
|
|
server_abort_http(clt, code, desc->http_path); |
585 |
|
|
return (-1); |
586 |
|
|
} |
587 |
|
|
|
588 |
|
|
void |
589 |
|
|
server_file_error(struct bufferevent *bev, short error, void *arg) |
590 |
|
|
{ |
591 |
|
|
struct client *clt = arg; |
592 |
|
|
struct evbuffer *src, *dst; |
593 |
|
|
|
594 |
|
|
if (error & EVBUFFER_TIMEOUT) { |
595 |
|
|
server_close(clt, "buffer event timeout"); |
596 |
|
|
return; |
597 |
|
|
} |
598 |
|
|
if (error & EVBUFFER_ERROR) { |
599 |
|
|
if (errno == EFBIG) { |
600 |
|
|
bufferevent_enable(bev, EV_READ); |
601 |
|
|
return; |
602 |
|
|
} |
603 |
|
|
server_close(clt, "buffer event error"); |
604 |
|
|
return; |
605 |
|
|
} |
606 |
|
|
if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { |
607 |
|
|
bufferevent_disable(bev, EV_READ|EV_WRITE); |
608 |
|
|
|
609 |
|
|
clt->clt_done = 1; |
610 |
|
|
|
611 |
|
|
src = EVBUFFER_INPUT(clt->clt_bev); |
612 |
|
|
|
613 |
|
|
/* Close the connection if a previous pipeline is empty */ |
614 |
|
|
if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0) |
615 |
|
|
clt->clt_persist = 0; |
616 |
|
|
|
617 |
|
|
if (clt->clt_persist) { |
618 |
|
|
/* Close input file and wait for next HTTP request */ |
619 |
|
|
if (clt->clt_fd != -1) |
620 |
|
|
close(clt->clt_fd); |
621 |
|
|
clt->clt_fd = -1; |
622 |
|
|
clt->clt_toread = TOREAD_HTTP_HEADER; |
623 |
|
|
server_reset_http(clt); |
624 |
|
|
bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); |
625 |
|
|
|
626 |
|
|
/* Start pipelining if the buffer is not empty */ |
627 |
|
|
if (EVBUFFER_LENGTH(src)) { |
628 |
|
|
clt->clt_pipelining++; |
629 |
|
|
server_read_http(clt->clt_bev, arg); |
630 |
|
|
} |
631 |
|
|
return; |
632 |
|
|
} |
633 |
|
|
|
634 |
|
|
dst = EVBUFFER_OUTPUT(clt->clt_bev); |
635 |
|
|
if (EVBUFFER_LENGTH(dst)) { |
636 |
|
|
/* Finish writing all data first */ |
637 |
|
|
bufferevent_enable(clt->clt_bev, EV_WRITE); |
638 |
|
|
return; |
639 |
|
|
} |
640 |
|
|
|
641 |
|
|
server_close(clt, "done"); |
642 |
|
|
return; |
643 |
|
|
} |
644 |
|
|
server_close(clt, "unknown event error"); |
645 |
|
|
return; |
646 |
|
|
} |
647 |
|
|
|
648 |
|
|
int |
649 |
|
|
server_file_modified_since(struct http_descriptor *desc, struct stat *st) |
650 |
|
|
{ |
651 |
|
|
struct kv key, *since; |
652 |
|
|
struct tm tm; |
653 |
|
|
|
654 |
|
|
key.kv_key = "If-Modified-Since"; |
655 |
|
|
if ((since = kv_find(&desc->http_headers, &key)) != NULL && |
656 |
|
|
since->kv_value != NULL) { |
657 |
|
|
memset(&tm, 0, sizeof(struct tm)); |
658 |
|
|
|
659 |
|
|
/* |
660 |
|
|
* Return "Not modified" if the file hasn't changed since |
661 |
|
|
* the requested time. |
662 |
|
|
*/ |
663 |
|
|
if (strptime(since->kv_value, |
664 |
|
|
"%a, %d %h %Y %T %Z", &tm) != NULL && |
665 |
|
|
timegm(&tm) >= st->st_mtim.tv_sec) |
666 |
|
|
return (304); |
667 |
|
|
} |
668 |
|
|
|
669 |
|
|
return (-1); |
670 |
|
|
} |
671 |
|
|
|
672 |
|
|
int |
673 |
|
|
parse_ranges(struct client *clt, char *str, size_t file_sz) |
674 |
|
|
{ |
675 |
|
|
int i = 0; |
676 |
|
|
char *p, *q; |
677 |
|
|
struct range_data *r = &clt->clt_ranges; |
678 |
|
|
|
679 |
|
|
memset(r, 0, sizeof(*r)); |
680 |
|
|
|
681 |
|
|
/* Extract range unit */ |
682 |
|
|
if ((p = strchr(str, '=')) == NULL) |
683 |
|
|
return (-1); |
684 |
|
|
|
685 |
|
|
*p++ = '\0'; |
686 |
|
|
/* Check if it's a bytes range spec */ |
687 |
|
|
if (strcmp(str, "bytes") != 0) |
688 |
|
|
return (-1); |
689 |
|
|
|
690 |
|
|
while ((q = strchr(p, ',')) != NULL) { |
691 |
|
|
*q++ = '\0'; |
692 |
|
|
|
693 |
|
|
/* Extract start and end positions */ |
694 |
|
|
if (parse_range_spec(p, file_sz, &r->range[i]) == 0) |
695 |
|
|
continue; |
696 |
|
|
|
697 |
|
|
i++; |
698 |
|
|
if (i == SERVER_MAX_RANGES) |
699 |
|
|
return (-1); |
700 |
|
|
|
701 |
|
|
p = q; |
702 |
|
|
} |
703 |
|
|
|
704 |
|
|
if (parse_range_spec(p, file_sz, &r->range[i]) != 0) |
705 |
|
|
i++; |
706 |
|
|
|
707 |
|
|
r->range_total = file_sz; |
708 |
|
|
r->range_count = i; |
709 |
|
|
return (i); |
710 |
|
|
} |
711 |
|
|
|
712 |
|
|
int |
713 |
|
|
parse_range_spec(char *str, size_t size, struct range *r) |
714 |
|
|
{ |
715 |
|
|
size_t start_str_len, end_str_len; |
716 |
|
|
char *p, *start_str, *end_str; |
717 |
|
|
const char *errstr; |
718 |
|
|
|
719 |
|
|
if ((p = strchr(str, '-')) == NULL) |
720 |
|
|
return (0); |
721 |
|
|
|
722 |
|
|
*p++ = '\0'; |
723 |
|
|
start_str = str; |
724 |
|
|
end_str = p; |
725 |
|
|
start_str_len = strlen(start_str); |
726 |
|
|
end_str_len = strlen(end_str); |
727 |
|
|
|
728 |
|
|
/* Either 'start' or 'end' is optional but not both */ |
729 |
|
|
if ((start_str_len == 0) && (end_str_len == 0)) |
730 |
|
|
return (0); |
731 |
|
|
|
732 |
|
|
if (end_str_len) { |
733 |
|
|
r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); |
734 |
|
|
if (errstr) |
735 |
|
|
return (0); |
736 |
|
|
|
737 |
|
|
if ((size_t)r->end >= size) |
738 |
|
|
r->end = size - 1; |
739 |
|
|
} else |
740 |
|
|
r->end = size - 1; |
741 |
|
|
|
742 |
|
|
if (start_str_len) { |
743 |
|
|
r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); |
744 |
|
|
if (errstr) |
745 |
|
|
return (0); |
746 |
|
|
|
747 |
|
|
if ((size_t)r->start >= size) |
748 |
|
|
return (0); |
749 |
|
|
} else { |
750 |
|
|
r->start = size - r->end; |
751 |
|
|
r->end = size - 1; |
752 |
|
|
} |
753 |
|
|
|
754 |
|
|
if (r->end < r->start) |
755 |
|
|
return (0); |
756 |
|
|
|
757 |
|
|
return (1); |
758 |
|
|
} |