Bug Summary

File:src/usr.sbin/httpd/server_file.c
Warning:line 238, column 8
Although the value stored to 'ret' is used in the enclosing expression, the value is never actually read from 'ret'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name server_file.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.sbin/httpd/obj -resource-dir /usr/local/llvm16/lib/clang/16 -I /usr/src/usr.sbin/httpd -I /usr/src/usr.sbin/httpd/obj -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.sbin/httpd/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.sbin/httpd/server_file.c
1/* $OpenBSD: server_file.c,v 1.78 2024/01/06 11:29:00 espie 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(*__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#include <util.h>
34
35#include "httpd.h"
36#include "http.h"
37#include "css.h"
38#include "js.h"
39
40#define MINIMUM(a, b)(((a) < (b)) ? (a) : (b)) (((a) < (b)) ? (a) : (b))
41#define MAXIMUM(a, b)(((a) > (b)) ? (a) : (b)) (((a) > (b)) ? (a) : (b))
42
43int server_file_access(struct httpd *, struct client *,
44 char *, size_t);
45int server_file_request(struct httpd *, struct client *,
46 char *, struct timespec *);
47int server_partial_file_request(struct httpd *, struct client *,
48 char *, struct timespec *, char *);
49int server_file_index(struct httpd *, struct client *);
50int server_file_modified_since(struct http_descriptor *,
51 struct timespec *);
52int server_file_method(struct client *);
53int parse_range_spec(char *, size_t, struct range *);
54int parse_ranges(struct client *, char *, size_t);
55static int select_visible(const struct dirent *);
56
57int
58server_file_access(struct httpd *env, struct client *clt,
59 char *path, size_t len)
60{
61 struct http_descriptor *desc = clt->clt_descreq;
62 struct server_config *srv_conf = clt->clt_srv_conf;
63 struct stat st;
64 struct kv *r, key;
65 char *newpath, *encodedpath;
66 int ret;
67
68 errno(*__errno()) = 0;
69
70 if (stat(path, &st) == -1) {
71 goto fail;
72 } else if (S_ISDIR(st.st_mode)((st.st_mode & 0170000) == 0040000)) {
73 /* Deny access if directory indexing is disabled */
74 if (srv_conf->flags & SRVFLAG_NO_INDEX0x00000002) {
75 errno(*__errno()) = EACCES13;
76 goto fail;
77 }
78
79 if (desc->http_path_alias != NULL((void *)0)) {
80 /* Recursion - the index "file" is a directory? */
81 errno(*__errno()) = EINVAL22;
82 goto fail;
83 }
84
85 /* Redirect to path with trailing "/" */
86 if (path[strlen(path) - 1] != '/') {
87 if ((encodedpath = url_encode(desc->http_pathhttp_pathquery.kv_key)) == NULL((void *)0))
88 return (500);
89 if (asprintf(&newpath, "%s/", encodedpath) == -1) {
90 free(encodedpath);
91 return (500);
92 }
93 free(encodedpath);
94
95 /* Path alias will be used for the redirection */
96 desc->http_path_alias = newpath;
97
98 /* Indicate that the file has been moved */
99 return (301);
100 }
101
102 /* Append the default index file to the location */
103 if (asprintf(&newpath, "%s%s", desc->http_pathhttp_pathquery.kv_key,
104 srv_conf->index) == -1)
105 return (500);
106 desc->http_path_alias = newpath;
107 if (server_getlocation(clt, newpath) != srv_conf) {
108 /* The location has changed */
109 return (server_file(env, clt));
110 }
111
112 /* Otherwise append the default index file to the path */
113 if (strlcat(path, srv_conf->index, len) >= len) {
114 errno(*__errno()) = EACCES13;
115 goto fail;
116 }
117
118 ret = server_file_access(env, clt, path, len);
119 if (ret == 404) {
120 /*
121 * Index file not found; fail if auto-indexing is
122 * not enabled, otherwise return success but
123 * indicate directory with S_ISDIR of the previous
124 * stat.
125 */
126 if ((srv_conf->flags & SRVFLAG_AUTO_INDEX0x00000004) == 0) {
127 errno(*__errno()) = EACCES13;
128 goto fail;
129 }
130
131 return (server_file_index(env, clt));
132 }
133 return (ret);
134 } else if (!S_ISREG(st.st_mode)((st.st_mode & 0170000) == 0100000)) {
135 /* Don't follow symlinks and ignore special files */
136 errno(*__errno()) = EACCES13;
137 goto fail;
138 }
139
140 key.kv_key = "Range";
141 r = kv_find(&desc->http_headers, &key);
142 if (r != NULL((void *)0))
143 return (server_partial_file_request(env, clt, path,
144 &st.st_mtim, r->kv_value));
145 else
146 return (server_file_request(env, clt, path, &st.st_mtim));
147
148 fail:
149 switch (errno(*__errno())) {
150 case ENOENT2:
151 case ENOTDIR20:
152 return (404);
153 case EACCES13:
154 return (403);
155 default:
156 return (500);
157 }
158
159 /* NOTREACHED */
160}
161
162int
163server_file(struct httpd *env, struct client *clt)
164{
165 struct http_descriptor *desc = clt->clt_descreq;
166 struct server_config *srv_conf = clt->clt_srv_conf;
167 char path[PATH_MAX1024];
168 const char *stripped, *errstr = NULL((void *)0);
169 int ret = 500;
170
171 if (srv_conf->flags & SRVFLAG_FCGI0x00000040)
172 return (server_fcgi(env, clt));
173
174 /* Request path is already canonicalized */
175 stripped = server_root_strip(
176 desc->http_path_alias != NULL((void *)0) ?
177 desc->http_path_alias : desc->http_pathhttp_pathquery.kv_key,
178 srv_conf->strip);
179 if ((size_t)snprintf(path, sizeof(path), "%s%s",
180 srv_conf->root, stripped) >= sizeof(path)) {
181 errstr = desc->http_pathhttp_pathquery.kv_key;
182 goto abort;
183 }
184
185 /* Returns HTTP status code on error */
186 if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
187 errstr = desc->http_path_alias != NULL((void *)0) ?
188 desc->http_path_alias : desc->http_pathhttp_pathquery.kv_key;
189 goto abort;
190 }
191
192 return (ret);
193
194 abort:
195 if (errstr == NULL((void *)0))
196 errstr = strerror(errno(*__errno()));
197 server_abort_http(clt, ret, errstr);
198 return (-1);
199}
200
201int
202server_file_method(struct client *clt)
203{
204 struct http_descriptor *desc = clt->clt_descreq;
205
206 switch (desc->http_method) {
207 case HTTP_METHOD_GET:
208 case HTTP_METHOD_HEAD:
209 return (0);
210 default:
211 /* Other methods are not allowed */
212 errno(*__errno()) = EACCES13;
213 return (405);
214 }
215 /* NOTREACHED */
216}
217
218int
219server_file_request(struct httpd *env, struct client *clt, char *path,
220 struct timespec *mtim)
221{
222 struct server_config *srv_conf = clt->clt_srv_conf;
223 struct media_type *media;
224 const char *errstr = NULL((void *)0);
225 int fd = -1, ret, code = 500;
226 struct stat st;
227 size_t bufsiz;
228 char gzpath[PATH_MAX1024];
229
230 if ((ret = server_file_method(clt)) != 0) {
231 code = ret;
232 goto abort;
233 }
234
235 media = media_find_config(env, srv_conf, path);
236 if ((ret = server_file_modified_since(clt->clt_descreq, mtim)) != -1) {
237 /* send the header without a body */
238 if ((ret = server_response_http(clt, ret, media, -1,
Although the value stored to 'ret' is used in the enclosing expression, the value is never actually read from 'ret'
239 MINIMUM(time(NULL), mtim->tv_sec)(((time(((void *)0))) < (mtim->tv_sec)) ? (time(((void *
)0))) : (mtim->tv_sec))
)
) == -1)
240 goto fail;
241 goto done;
242 }
243
244 /* change path to path.gz if necessary. */
245 if (srv_conf->flags & SRVFLAG_GZIP_STATIC0x04000000) {
246 struct http_descriptor *req = clt->clt_descreq;
247 struct http_descriptor *resp = clt->clt_descresp;
248 struct kv *r, key;
249
250 /* check Accept-Encoding header */
251 key.kv_key = "Accept-Encoding";
252 r = kv_find(&req->http_headers, &key);
253
254 if (r != NULL((void *)0) && strstr(r->kv_value, "gzip") != NULL((void *)0)) {
255 /* append ".gz" to path and check existence */
256 ret = snprintf(gzpath, sizeof(gzpath), "%s.gz", path);
257 if (ret < 0 || (size_t)ret >= sizeof(gzpath))
258 goto abort;
259 if ((fd = open(gzpath, O_RDONLY0x0000)) != -1) {
260 /* .gz must be a file, and not older */
261 if (fstat(fd, &st) != -1 &&
262 S_ISREG(st.st_mode)((st.st_mode & 0170000) == 0100000) &&
263 timespeccmp(&st.st_mtim, mtim, >=)(((&st.st_mtim)->tv_sec == (mtim)->tv_sec) ? ((&
st.st_mtim)->tv_nsec >= (mtim)->tv_nsec) : ((&st
.st_mtim)->tv_sec >= (mtim)->tv_sec))
) {
264 kv_add(&resp->http_headers,
265 "Content-Encoding", "gzip");
266 /* Use original file timestamp */
267 st.st_mtim = *mtim;
268 } else {
269 close(fd);
270 fd = -1;
271 }
272 }
273 }
274 }
275
276 /* Now open the file, should be readable or we have another problem */
277 if (fd == -1) {
278 if ((fd = open(path, O_RDONLY0x0000)) == -1)
279 goto abort;
280 if (fstat(fd, &st) == -1)
281 goto abort;
282 }
283
284 ret = server_response_http(clt, 200, media, st.st_size,
285 MINIMUM(time(NULL), st.st_mtim.tv_sec)(((time(((void *)0))) < (st.st_mtim.tv_sec)) ? (time(((void
*)0))) : (st.st_mtim.tv_sec))
);
286 switch (ret) {
287 case -1:
288 goto fail;
289 case 0:
290 /* Connection is already finished */
291 close(fd);
292 goto done;
293 default:
294 break;
295 }
296
297 clt->clt_fd = fd;
298 if (clt->clt_srvbev != NULL((void *)0))
299 bufferevent_free(clt->clt_srvbev);
300
301 clt->clt_srvbev_throttled = 0;
302 clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read,
303 server_write, server_file_error, clt);
304 if (clt->clt_srvbev == NULL((void *)0)) {
305 errstr = "failed to allocate file buffer event";
306 goto fail;
307 }
308
309 /* Adjust read watermark to the optimal file io size */
310 bufsiz = MAXIMUM(st.st_blksize, 64 * 1024)(((st.st_blksize) > (64 * 1024)) ? (st.st_blksize) : (64 *
1024))
;
311 bufferevent_setwatermark(clt->clt_srvbev, EV_READ0x02, 0,
312 bufsiz);
313
314 bufferevent_settimeout(clt->clt_srvbev,
315 srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
316 bufferevent_enable(clt->clt_srvbev, EV_READ0x02);
317 bufferevent_disable(clt->clt_bev, EV_READ0x02);
318
319 done:
320 server_reset_http(clt);
321 return (0);
322 fail:
323 bufferevent_disable(clt->clt_bev, EV_READ0x02|EV_WRITE0x04);
324 bufferevent_free(clt->clt_bev);
325 clt->clt_bev = NULL((void *)0);
326 abort:
327 if (fd != -1)
328 close(fd);
329 if (errstr == NULL((void *)0))
330 errstr = strerror(errno(*__errno()));
331 server_abort_http(clt, code, errstr);
332 return (-1);
333}
334
335int
336server_partial_file_request(struct httpd *env, struct client *clt, char *path,
337 struct timespec *mtim, char *range_str)
338{
339 struct server_config *srv_conf = clt->clt_srv_conf;
340 struct http_descriptor *resp = clt->clt_descresp;
341 struct http_descriptor *desc = clt->clt_descreq;
342 struct media_type *media, multipart_media;
343 struct range_data *r = &clt->clt_ranges;
344 struct range *range;
345 size_t content_length = 0, bufsiz;
346 struct stat st;
347 int code = 500, fd = -1, i, nranges, ret;
348 char content_range[64];
349 const char *errstr = NULL((void *)0);
350
351 /* Ignore range request for methods other than GET */
352 if (desc->http_method != HTTP_METHOD_GET)
353 return server_file_request(env, clt, path, mtim);
354
355 /* Now open the file, should be readable or we have another problem */
356 if ((fd = open(path, O_RDONLY0x0000)) == -1)
357 goto abort;
358 if (fstat(fd, &st) == -1)
359 goto abort;
360
361 if ((nranges = parse_ranges(clt, range_str, st.st_size)) < 1) {
362 code = 416;
363 (void)snprintf(content_range, sizeof(content_range),
364 "bytes */%lld", st.st_size);
365 errstr = content_range;
366 goto abort;
367 }
368
369 media = media_find_config(env, srv_conf, path);
370 r->range_media = media;
371
372 if (nranges == 1) {
373 range = &r->range[0];
374 (void)snprintf(content_range, sizeof(content_range),
375 "bytes %lld-%lld/%lld", range->start, range->end,
376 st.st_size);
377 if (kv_add(&resp->http_headers, "Content-Range",
378 content_range) == NULL((void *)0))
379 goto abort;
380
381 range = &r->range[0];
382 content_length += range->end - range->start + 1;
383 } else {
384 /* Add boundary, all parts will be handled by the callback */
385 arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary));
386
387 /* Calculate Content-Length of the complete multipart body */
388 for (i = 0; i < nranges; i++) {
389 range = &r->range[i];
390
391 /* calculate Content-Length of the complete body */
392 if ((ret = snprintf(NULL((void *)0), 0,
393 "\r\n--%llu\r\n"
394 "Content-Type: %s/%s\r\n"
395 "Content-Range: bytes %lld-%lld/%lld\r\n\r\n",
396 clt->clt_boundary,
397 media->media_type, media->media_subtype,
398 range->start, range->end, st.st_size)) < 0)
399 goto abort;
400
401 /* Add data length */
402 content_length += ret + range->end - range->start + 1;
403
404 }
405 if ((ret = snprintf(NULL((void *)0), 0, "\r\n--%llu--\r\n",
406 clt->clt_boundary)) < 0)
407 goto abort;
408 content_length += ret;
409
410 /* prepare multipart/byteranges media type */
411 (void)strlcpy(multipart_media.media_type, "multipart",
412 sizeof(multipart_media.media_type));
413 (void)snprintf(multipart_media.media_subtype,
414 sizeof(multipart_media.media_subtype),
415 "byteranges; boundary=%llu", clt->clt_boundary);
416 media = &multipart_media;
417 }
418
419 /* Start with first range */
420 r->range_toread = TOREAD_HTTP_RANGE;
421
422 ret = server_response_http(clt, 206, media, content_length,
423 MINIMUM(time(NULL), st.st_mtim.tv_sec)(((time(((void *)0))) < (st.st_mtim.tv_sec)) ? (time(((void
*)0))) : (st.st_mtim.tv_sec))
);
424 switch (ret) {
425 case -1:
426 goto fail;
427 case 0:
428 /* Connection is already finished */
429 close(fd);
430 goto done;
431 default:
432 break;
433 }
434
435 clt->clt_fd = fd;
436 if (clt->clt_srvbev != NULL((void *)0))
437 bufferevent_free(clt->clt_srvbev);
438
439 clt->clt_srvbev_throttled = 0;
440 clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange,
441 server_write, server_file_error, clt);
442 if (clt->clt_srvbev == NULL((void *)0)) {
443 errstr = "failed to allocate file buffer event";
444 goto fail;
445 }
446
447 /* Adjust read watermark to the optimal file io size */
448 bufsiz = MAXIMUM(st.st_blksize, 64 * 1024)(((st.st_blksize) > (64 * 1024)) ? (st.st_blksize) : (64 *
1024))
;
449 bufferevent_setwatermark(clt->clt_srvbev, EV_READ0x02, 0,
450 bufsiz);
451
452 bufferevent_settimeout(clt->clt_srvbev,
453 srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
454 bufferevent_enable(clt->clt_srvbev, EV_READ0x02);
455 bufferevent_disable(clt->clt_bev, EV_READ0x02);
456
457 done:
458 server_reset_http(clt);
459 return (0);
460 fail:
461 bufferevent_disable(clt->clt_bev, EV_READ0x02|EV_WRITE0x04);
462 bufferevent_free(clt->clt_bev);
463 clt->clt_bev = NULL((void *)0);
464 abort:
465 if (fd != -1)
466 close(fd);
467 if (errstr == NULL((void *)0))
468 errstr = strerror(errno(*__errno()));
469 server_abort_http(clt, code, errstr);
470 return (-1);
471}
472
473/* ignore hidden files starting with a dot */
474static int
475select_visible(const struct dirent *dp)
476{
477 if (dp->d_name[0] == '.' &&
478 !(dp->d_name[1] == '.' && dp->d_name[2] == '\0'))
479 return 0;
480 else
481 return 1;
482}
483
484int
485server_file_index(struct httpd *env, struct client *clt)
486{
487 char path[PATH_MAX1024];
488 char tmstr[21];
489 struct http_descriptor *desc = clt->clt_descreq;
490 struct server_config *srv_conf = clt->clt_srv_conf;
491 struct dirent **namelist, *dp;
492 int namesize, i, ret, fd = -1, skip;
493 int code = 500;
494 struct evbuffer *evb = NULL((void *)0);
495 struct media_type *media;
496 const char *stripped;
497 char *escapeduri, *escapedhtml, *escapedpath;
498 struct tm tm;
499 struct stat st;
500 time_t t, dir_mtime;
501 char human_size[FMT_SCALED_STRSIZE7];
502
503 if ((ret = server_file_method(clt)) != 0) {
504 code = ret;
505 goto abort;
506 }
507
508 /* Request path is already canonicalized */
509 stripped = server_root_strip(desc->http_pathhttp_pathquery.kv_key, srv_conf->strip);
510 if ((size_t)snprintf(path, sizeof(path), "%s%s",
511 srv_conf->root, stripped) >= sizeof(path))
512 goto abort;
513
514 /* Now open the file, should be readable or we have another problem */
515 if ((fd = open(path, O_RDONLY0x0000)) == -1)
516 goto abort;
517 if (fstat(fd, &st) == -1)
518 goto abort;
519
520 /* Save last modification time */
521 dir_mtime = MINIMUM(time(NULL), st.st_mtim.tv_sec)(((time(((void *)0))) < (st.st_mtim.tv_sec)) ? (time(((void
*)0))) : (st.st_mtim.tv_sec))
;
522
523 if ((evb = evbuffer_new()) == NULL((void *)0))
524 goto abort;
525
526 if ((escapedpath = escape_html(desc->http_pathhttp_pathquery.kv_key)) == NULL((void *)0))
527 goto abort;
528
529 /* Generate simple HTML index document */
530 if (evbuffer_add_printf(evb,
531 "<!DOCTYPE html>\n"
532 "<html lang=\"en\">\n"
533 "<head>\n"
534 "<meta charset=\"utf-8\">\n"
535 "<title>Index of %s</title>\n"
536 "<style><!--\n%s--></style>\n"
537 "</head>\n"
538 "<body>\n"
539 "<h1>Index of %s</h1>\n"
540 "<table><thead>\n"
541 "<tr class=\"sort\"><th class=\"sorted\">Name</th>\n"
542 " <th>Date</th><th>Size</th></tr>\n"
543 "</thead><tbody>\n",
544 escapedpath, css, escapedpath) == -1) {
545 free(escapedpath);
546 goto abort;
547 }
548
549 free(escapedpath);
550
551 if ((namesize = scandir(path, &namelist, select_visible,
552 alphasort)) == -1)
553 goto abort;
554
555 /* Indicate failure but continue going through the list */
556 skip = 0;
557
558 for (i = 0; i < namesize; i++) {
559 struct stat subst;
560
561 dp = namelist[i];
562
563 if (skip ||
564 fstatat(fd, dp->d_name, &subst, 0) == -1) {
565 free(dp);
566 continue;
567 }
568
569 t = subst.st_mtimest_mtim.tv_sec;
570 localtime_r(&t, &tm);
571 strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
572
573 if ((escapeduri = url_encode(dp->d_name)) == NULL((void *)0)) {
574 skip = 1;
575 free(dp);
576 continue;
577 }
578 if ((escapedhtml = escape_html(dp->d_name)) == NULL((void *)0)) {
579 skip = 1;
580 free(escapeduri);
581 free(dp);
582 continue;
583 }
584
585 if (S_ISDIR(subst.st_mode)((subst.st_mode & 0170000) == 0040000)) {
586 if (evbuffer_add_printf(evb,
587 "<tr class=\"dir\">"
588 "<td><a href=\"%s%s/\">%s/</a></td>\n"
589 " <td data-o=\"%lld\">%s</td><td>%s</td></tr>\n",
590 strchr(escapeduri, ':') != NULL((void *)0) ? "./" : "",
591 escapeduri, escapedhtml,
592 (long long)t, tmstr, "-") == -1)
593 skip = 1;
594 } else if (S_ISREG(subst.st_mode)((subst.st_mode & 0170000) == 0100000)) {
595 if ((fmt_scaled(subst.st_size, human_size) != 0) ||
596 (evbuffer_add_printf(evb,
597 "<tr><td><a href=\"%s%s\">%s</a></td>\n"
598 " <td data-o=\"%lld\">%s</td>"
599 "<td title=\"%llu\">%s</td></tr>\n",
600 strchr(escapeduri, ':') != NULL((void *)0) ? "./" : "",
601 escapeduri, escapedhtml,
602 (long long)t, tmstr,
603 subst.st_size, human_size) == -1))
604 skip = 1;
605 }
606 free(escapeduri);
607 free(escapedhtml);
608 free(dp);
609 }
610 free(namelist);
611
612 if (skip ||
613 evbuffer_add_printf(evb,
614 "</tbody></table>\n<script>\n"
615 "%s\n"
616 "</script>\n</body>\n</html>\n", js) == -1)
617 goto abort;
618
619 close(fd);
620 fd = -1;
621
622 media = media_find_config(env, srv_conf, "index.html");
623 ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb)(evb)->off,
624 dir_mtime);
625 switch (ret) {
626 case -1:
627 goto fail;
628 case 0:
629 /* Connection is already finished */
630 evbuffer_free(evb);
631 goto done;
632 default:
633 break;
634 }
635
636 if (server_bufferevent_write_buffer(clt, evb) == -1)
637 goto fail;
638 evbuffer_free(evb);
639 evb = NULL((void *)0);
640
641 bufferevent_enable(clt->clt_bev, EV_READ0x02|EV_WRITE0x04);
642 if (clt->clt_persist)
643 clt->clt_toread = TOREAD_HTTP_HEADER;
644 else
645 clt->clt_toread = TOREAD_HTTP_NONE;
646 clt->clt_done = 0;
647
648 done:
649 server_reset_http(clt);
650 return (0);
651 fail:
652 bufferevent_disable(clt->clt_bev, EV_READ0x02|EV_WRITE0x04);
653 bufferevent_free(clt->clt_bev);
654 clt->clt_bev = NULL((void *)0);
655 abort:
656 if (fd != -1)
657 close(fd);
658 if (evb != NULL((void *)0))
659 evbuffer_free(evb);
660 server_abort_http(clt, code, desc->http_pathhttp_pathquery.kv_key);
661 return (-1);
662}
663
664void
665server_file_error(struct bufferevent *bev, short error, void *arg)
666{
667 struct client *clt = arg;
668 struct evbuffer *src, *dst;
669
670 if (error & EVBUFFER_TIMEOUT0x40) {
671 server_close(clt, "buffer event timeout");
672 return;
673 }
674 if (error & EVBUFFER_ERROR0x20) {
675 if (errno(*__errno()) == EFBIG27) {
676 bufferevent_enable(bev, EV_READ0x02);
677 return;
678 }
679 server_close(clt, "buffer event error");
680 return;
681 }
682 if (error & (EVBUFFER_READ0x01|EVBUFFER_WRITE0x02|EVBUFFER_EOF0x10)) {
683 bufferevent_disable(bev, EV_READ0x02|EV_WRITE0x04);
684
685 clt->clt_done = 1;
686
687 src = EVBUFFER_INPUT(clt->clt_bev)(clt->clt_bev)->input;
688
689 /* Close the connection if a previous pipeline is empty */
690 if (clt->clt_pipelining && EVBUFFER_LENGTH(src)(src)->off == 0)
691 clt->clt_persist = 0;
692
693 if (clt->clt_persist) {
694 /* Close input file and wait for next HTTP request */
695 if (clt->clt_fd != -1)
696 close(clt->clt_fd);
697 clt->clt_fd = -1;
698 clt->clt_toread = TOREAD_HTTP_HEADER;
699 server_reset_http(clt);
700 bufferevent_enable(clt->clt_bev, EV_READ0x02|EV_WRITE0x04);
701
702 /* Start pipelining if the buffer is not empty */
703 if (EVBUFFER_LENGTH(src)(src)->off) {
704 clt->clt_pipelining++;
705 server_read_http(clt->clt_bev, arg);
706 }
707 return;
708 }
709
710 dst = EVBUFFER_OUTPUT(clt->clt_bev)(clt->clt_bev)->output;
711 if (EVBUFFER_LENGTH(dst)(dst)->off) {
712 /* Finish writing all data first */
713 bufferevent_enable(clt->clt_bev, EV_WRITE0x04);
714 return;
715 }
716
717 server_close(clt, "done");
718 return;
719 }
720 server_close(clt, "unknown event error");
721 return;
722}
723
724int
725server_file_modified_since(struct http_descriptor *desc, struct timespec *mtim)
726{
727 struct kv key, *since;
728 struct tm tm;
729
730 key.kv_key = "If-Modified-Since";
731 if ((since = kv_find(&desc->http_headers, &key)) != NULL((void *)0) &&
732 since->kv_value != NULL((void *)0)) {
733 memset(&tm, 0, sizeof(struct tm));
734
735 /*
736 * Return "Not modified" if the file hasn't changed since
737 * the requested time.
738 */
739 if (strptime(since->kv_value,
740 "%a, %d %h %Y %T %Z", &tm) != NULL((void *)0) &&
741 timegm(&tm) >= mtim->tv_sec)
742 return (304);
743 }
744
745 return (-1);
746}
747
748int
749parse_ranges(struct client *clt, char *str, size_t file_sz)
750{
751 int i = 0;
752 char *p, *q;
753 struct range_data *r = &clt->clt_ranges;
754
755 memset(r, 0, sizeof(*r));
756
757 /* Extract range unit */
758 if ((p = strchr(str, '=')) == NULL((void *)0))
759 return (-1);
760
761 *p++ = '\0';
762 /* Check if it's a bytes range spec */
763 if (strcmp(str, "bytes") != 0)
764 return (-1);
765
766 while ((q = strchr(p, ',')) != NULL((void *)0)) {
767 *q++ = '\0';
768
769 /* Extract start and end positions */
770 if (parse_range_spec(p, file_sz, &r->range[i]) == 0)
771 continue;
772
773 i++;
774 if (i == SERVER_MAX_RANGES4)
775 return (-1);
776
777 p = q;
778 }
779
780 if (parse_range_spec(p, file_sz, &r->range[i]) != 0)
781 i++;
782
783 r->range_total = file_sz;
784 r->range_count = i;
785 return (i);
786}
787
788int
789parse_range_spec(char *str, size_t size, struct range *r)
790{
791 size_t start_str_len, end_str_len;
792 char *p, *start_str, *end_str;
793 const char *errstr;
794
795 if ((p = strchr(str, '-')) == NULL((void *)0))
796 return (0);
797
798 *p++ = '\0';
799 start_str = str;
800 end_str = p;
801 start_str_len = strlen(start_str);
802 end_str_len = strlen(end_str);
803
804 /* Either 'start' or 'end' is optional but not both */
805 if ((start_str_len == 0) && (end_str_len == 0))
806 return (0);
807
808 if (end_str_len) {
809 r->end = strtonum(end_str, 0, LLONG_MAX0x7fffffffffffffffLL, &errstr);
810 if (errstr)
811 return (0);
812
813 if ((size_t)r->end >= size)
814 r->end = size - 1;
815 } else
816 r->end = size - 1;
817
818 if (start_str_len) {
819 r->start = strtonum(start_str, 0, LLONG_MAX0x7fffffffffffffffLL, &errstr);
820 if (errstr)
821 return (0);
822
823 if ((size_t)r->start >= size)
824 return (0);
825 } else {
826 r->start = size - r->end;
827 r->end = size - 1;
828 }
829
830 if (r->end < r->start)
831 return (0);
832
833 return (1);
834}