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