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 43int server_file_access(struct httpd *, struct client *, 44 char *, size_t); 45int server_file_request(struct httpd *, struct client *, 46 struct media_type *, int, const struct stat *); 47int server_partial_file_request(struct httpd *, struct client *, 48 struct media_type *, int, const struct stat *, 49 char *); 50int server_file_index(struct httpd *, struct client *, int, 51 struct stat *); 52int server_file_modified_since(struct http_descriptor *, 53 const struct timespec *); 54int server_file_method(struct client *); 55int parse_range_spec(char *, size_t, struct range *); 56int parse_ranges(struct client *, char *, size_t); 57static int select_visible(const struct dirent *); 58 59int 60server_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 214int 215server_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 253int 254server_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 270int 271server_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 345int 346server_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 */ 471static int 472select_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 481int 482server_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 655void 656server_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 715int 716server_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 740int 741parse_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 780int 781parse_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