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