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