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