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