1 2 /* 3 * Copyright (C) NGINX, Inc. 4 */ 5 6 #include <nxt_router.h> 7 #include <nxt_http.h> 8 9 10 #define NXT_HTTP_STATIC_BUF_COUNT 2 11 #define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024) 12 13 14 static void nxt_http_static_extract_extension(nxt_str_t *path, 15 nxt_str_t *extension); 16 static void nxt_http_static_body_handler(nxt_task_t *task, void *obj, 17 void *data); 18 static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj, 19 void *data); 20 21 static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, 22 void *data); 23 static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size); 24 static void nxt_http_static_mtypes_hash_free(void *data, void *p); 25 26 27 static const nxt_http_request_state_t nxt_http_static_send_state; 28 29 30 nxt_http_action_t * 31 nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, 32 nxt_http_action_t *action) 33 { 34 size_t length, encode; 35 u_char *p, *fname; 36 struct tm tm; 37 nxt_buf_t *fb; 38 nxt_int_t ret; 39 nxt_str_t index, extension, *mtype, *chroot; 40 nxt_uint_t level; 41 nxt_bool_t need_body; 42 nxt_file_t *f, file; 43 nxt_file_info_t fi; 44 nxt_http_field_t *field; 45 nxt_http_status_t status; 46 nxt_router_conf_t *rtcf; 47 nxt_work_handler_t body_handler; 48 49 if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) { 50 51 if (!nxt_str_eq(r->method, "HEAD", 4)) { 52 if (action->u.share.fallback != NULL) { 53 return action->u.share.fallback; 54 } 55 56 nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED); 57 return NULL; 58 } 59 60 need_body = 0; 61 62 } else { 63 need_body = 1; 64 } 65 66 if (r->path->start[r->path->length - 1] == '/') { 67 /* TODO: dynamic index setting. */ 68 nxt_str_set(&index, "index.html"); 69 nxt_str_set(&extension, ".html"); 70 71 } else { 72 nxt_str_set(&index, ""); 73 nxt_str_null(&extension); 74 } 75 76 f = NULL; 77 78 length = action->name.length + r->path->length + index.length; 79 80 fname = nxt_mp_nget(r->mem_pool, length + 1); 81 if (nxt_slow_path(fname == NULL)) { 82 goto fail; 83 } 84 85 p = fname; 86 p = nxt_cpymem(p, action->name.start, action->name.length); 87 p = nxt_cpymem(p, r->path->start, r->path->length); 88 p = nxt_cpymem(p, index.start, index.length); 89 *p = '\0'; 90 91 nxt_memzero(&file, sizeof(nxt_file_t)); 92 93 file.name = fname; 94 95 chroot = &action->u.share.chroot; 96 97 #if (NXT_HAVE_OPENAT2) 98 99 if (action->u.share.resolve != 0) { 100 101 if (chroot->length > 0) { 102 file.name = chroot->start; 103 104 if (length > chroot->length 105 && nxt_memcmp(fname, chroot->start, chroot->length) == 0) 106 { 107 fname += chroot->length; 108 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 109 0); 110 111 } else { 112 file.error = NXT_EACCES; 113 ret = NXT_ERROR; 114 } 115 116 } else if (fname[0] == '/') { 117 file.name = (u_char *) "/"; 118 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0); 119 120 } else { 121 file.name = (u_char *) "."; 122 file.fd = AT_FDCWD; 123 ret = NXT_OK; 124 } 125 126 if (nxt_fast_path(ret == NXT_OK)) { 127 nxt_file_t af; 128 129 af = file; 130 nxt_memzero(&file, sizeof(nxt_file_t)); 131 file.name = fname; 132 133 ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY, 134 NXT_FILE_OPEN, 0, af.fd, 135 action->u.share.resolve); 136 137 if (af.fd != AT_FDCWD) { 138 nxt_file_close(task, &af); 139 } 140 } 141 142 } else { 143 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); 144 } 145 146 #else 147 148 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); 149 150 #endif 151 152 if (nxt_slow_path(ret != NXT_OK)) { 153 154 switch (file.error) { 155 156 /* 157 * For Unix domain sockets "errno" is set to: 158 * - ENXIO on Linux; 159 * - EOPNOTSUPP on *BSD, MacOSX, and Solaris. 160 */ 161 162 case NXT_ENOENT: 163 case NXT_ENOTDIR: 164 case NXT_ENAMETOOLONG: 165 #if (NXT_LINUX) 166 case NXT_ENXIO: 167 #else 168 case NXT_EOPNOTSUPP: 169 #endif 170 level = NXT_LOG_ERR; 171 status = NXT_HTTP_NOT_FOUND; 172 break; 173 174 case NXT_EACCES: 175 #if (NXT_HAVE_OPENAT2) 176 case NXT_ELOOP: 177 case NXT_EXDEV: 178 #endif 179 level = NXT_LOG_ERR; 180 status = NXT_HTTP_FORBIDDEN; 181 break; 182 183 default: 184 level = NXT_LOG_ALERT; 185 status = NXT_HTTP_INTERNAL_SERVER_ERROR; 186 break; 187 } 188 189 if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) { 190 return action->u.share.fallback; 191 } 192 193 if (status != NXT_HTTP_NOT_FOUND) { 194 if (chroot->length > 0) { 195 nxt_log(task, level, "opening \"%FN\" at \"%FN\" failed %E", 196 fname, chroot, file.error); 197 198 } else { 199 nxt_log(task, level, "opening \"%FN\" failed %E", 200 fname, file.error); 201 } 202 } 203 204 nxt_http_request_error(task, r, status); 205 return NULL; 206 } 207 208 f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t)); 209 if (nxt_slow_path(f == NULL)) { 210 goto fail; 211 } 212 213 *f = file; 214 215 ret = nxt_file_info(f, &fi); 216 if (nxt_slow_path(ret != NXT_OK)) { 217 goto fail; 218 } 219 220 if (nxt_fast_path(nxt_is_file(&fi))) { 221 r->status = NXT_HTTP_OK; 222 r->resp.content_length_n = nxt_file_size(&fi); 223 224 field = nxt_list_zero_add(r->resp.fields); 225 if (nxt_slow_path(field == NULL)) { 226 goto fail; 227 } 228 229 nxt_http_field_name_set(field, "Last-Modified"); 230 231 p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN); 232 if (nxt_slow_path(p == NULL)) { 233 goto fail; 234 } 235 236 nxt_localtime(nxt_file_mtime(&fi), &tm); 237 238 field->value = p; 239 field->value_length = nxt_http_date(p, &tm) - p; 240 241 field = nxt_list_zero_add(r->resp.fields); 242 if (nxt_slow_path(field == NULL)) { 243 goto fail; 244 } 245 246 nxt_http_field_name_set(field, "ETag"); 247 248 length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; 249 250 p = nxt_mp_nget(r->mem_pool, length); 251 if (nxt_slow_path(p == NULL)) { 252 goto fail; 253 } 254 255 field->value = p; 256 field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"", 257 nxt_file_mtime(&fi), 258 nxt_file_size(&fi)) 259 - p; 260 261 if (extension.start == NULL) { 262 nxt_http_static_extract_extension(r->path, &extension); 263 } 264 265 rtcf = r->conf->socket_conf->router_conf; 266 267 mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, 268 &extension); 269 270 if (mtype != NULL) { 271 field = nxt_list_zero_add(r->resp.fields); 272 if (nxt_slow_path(field == NULL)) { 273 goto fail; 274 } 275 276 nxt_http_field_name_set(field, "Content-Type"); 277 278 field->value = mtype->start; 279 field->value_length = mtype->length; 280 } 281 282 if (need_body && nxt_file_size(&fi) > 0) { 283 fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE); 284 if (nxt_slow_path(fb == NULL)) { 285 goto fail; 286 } 287 288 fb->file = f; 289 fb->file_end = nxt_file_size(&fi); 290 291 r->out = fb; 292 293 body_handler = &nxt_http_static_body_handler; 294 295 } else { 296 nxt_file_close(task, f); 297 body_handler = NULL; 298 } 299 300 } else { 301 /* Not a file. */ 302 303 nxt_file_close(task, f); 304 305 if (nxt_slow_path(!nxt_is_dir(&fi))) { 306 if (action->u.share.fallback != NULL) { 307 return action->u.share.fallback; 308 } 309 310 nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file", 311 f->name); 312 313 nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND); 314 return NULL; 315 } 316 317 f = NULL; 318 319 r->status = NXT_HTTP_MOVED_PERMANENTLY; 320 r->resp.content_length_n = 0; 321 322 field = nxt_list_zero_add(r->resp.fields); 323 if (nxt_slow_path(field == NULL)) { 324 goto fail; 325 } 326 327 nxt_http_field_name_set(field, "Location"); 328 329 encode = nxt_encode_uri(NULL, r->path->start, r->path->length); 330 length = r->path->length + encode * 2 + 1; 331 332 if (r->args->length > 0) { 333 length += 1 + r->args->length; 334 } 335 336 p = nxt_mp_nget(r->mem_pool, length); 337 if (nxt_slow_path(p == NULL)) { 338 goto fail; 339 } 340 341 field->value = p; 342 field->value_length = length; 343 344 if (encode > 0) { 345 p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length); 346 347 } else { 348 p = nxt_cpymem(p, r->path->start, r->path->length); 349 } 350 351 *p++ = '/'; 352 353 if (r->args->length > 0) { 354 *p++ = '?'; 355 nxt_memcpy(p, r->args->start, r->args->length); 356 } 357 358 body_handler = NULL; 359 } 360 361 nxt_http_request_header_send(task, r, body_handler, NULL); 362 363 r->state = &nxt_http_static_send_state; 364 return NULL; 365 366 fail: 367 368 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); 369 370 if (f != NULL) { 371 nxt_file_close(task, f); 372 } 373 374 return NULL; 375 } 376 377 378 static void 379 nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension) 380 { 381 u_char ch, *p, *end; 382 383 end = path->start + path->length; 384 p = end; 385 386 for ( ;; ) { 387 /* There's always '/' in the beginning of the request path. */ 388 389 p--; 390 ch = *p; 391 392 switch (ch) { 393 case '/': 394 p++; 395 /* Fall through. */ 396 case '.': 397 extension->length = end - p; 398 extension->start = p; 399 return; 400 } 401 } 402 } 403 404 405 static void 406 nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data) 407 { 408 size_t alloc; 409 nxt_buf_t *fb, *b, **next, *out; 410 nxt_off_t rest; 411 nxt_int_t n; 412 nxt_work_queue_t *wq; 413 nxt_http_request_t *r; 414 415 r = obj; 416 fb = r->out; 417 418 rest = fb->file_end - fb->file_pos; 419 out = NULL; 420 next = &out; 421 n = 0; 422 423 do { 424 alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE); 425 426 b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0); 427 if (nxt_slow_path(b == NULL)) { 428 goto fail; 429 } 430 431 b->completion_handler = nxt_http_static_buf_completion; 432 b->parent = r; 433 434 nxt_mp_retain(r->mem_pool); 435 436 *next = b; 437 next = &b->next; 438 439 rest -= alloc; 440 441 } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT); 442 443 wq = &task->thread->engine->fast_work_queue; 444 445 nxt_sendbuf_drain(task, wq, out); 446 return; 447 448 fail: 449 450 while (out != NULL) { 451 b = out; 452 out = b->next; 453 454 nxt_mp_free(r->mem_pool, b); 455 nxt_mp_release(r->mem_pool); 456 } 457 } 458 459 460 static const nxt_http_request_state_t nxt_http_static_send_state 461 nxt_aligned(64) = 462 { 463 .error_handler = nxt_http_request_error_handler, 464 }; 465 466 467 static void 468 nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data) 469 { 470 ssize_t n, size; 471 nxt_buf_t *b, *fb, *next; 472 nxt_off_t rest; 473 nxt_http_request_t *r; 474 475 b = obj; 476 r = data; 477 478 complete_buf: 479 480 fb = r->out; 481 482 if (nxt_slow_path(fb == NULL || r->error)) { 483 goto clean; 484 } 485 486 rest = fb->file_end - fb->file_pos; 487 size = nxt_buf_mem_size(&b->mem); 488 489 size = nxt_min(rest, (nxt_off_t) size); 490 491 n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos); 492 493 if (n != size) { 494 if (n >= 0) { 495 nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed " 496 "while sending response to a client", fb->file->name); 497 } 498 499 nxt_http_request_error_handler(task, r, r->proto.any); 500 goto clean; 501 } 502 503 next = b->next; 504 505 if (n == rest) { 506 nxt_file_close(task, fb->file); 507 r->out = NULL; 508 509 b->next = nxt_http_buf_last(r); 510 511 } else { 512 fb->file_pos += n; 513 b->next = NULL; 514 } 515 516 b->mem.pos = b->mem.start; 517 b->mem.free = b->mem.pos + n; 518 519 nxt_http_request_send(task, r, b); 520 521 if (next != NULL) { 522 b = next; 523 goto complete_buf; 524 } 525 526 return; 527 528 clean: 529 530 do { 531 next = b->next; 532 533 nxt_mp_free(r->mem_pool, b); 534 nxt_mp_release(r->mem_pool); 535 536 b = next; 537 } while (b != NULL); 538 539 if (fb != NULL) { 540 nxt_file_close(task, fb->file); 541 r->out = NULL; 542 } 543 } 544 545 546 nxt_int_t 547 nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) 548 { 549 nxt_str_t *type, extension; 550 nxt_int_t ret; 551 nxt_uint_t i; 552 553 static const struct { 554 nxt_str_t type; 555 const char *extension; 556 } default_types[] = { 557 558 { nxt_string("text/html"), ".html" }, 559 { nxt_string("text/html"), ".htm" }, 560 { nxt_string("text/css"), ".css" }, 561 562 { nxt_string("image/svg+xml"), ".svg" }, 563 { nxt_string("image/webp"), ".webp" }, 564 { nxt_string("image/png"), ".png" }, 565 { nxt_string("image/apng"), ".apng" }, 566 { nxt_string("image/jpeg"), ".jpeg" }, 567 { nxt_string("image/jpeg"), ".jpg" }, 568 { nxt_string("image/gif"), ".gif" }, 569 { nxt_string("image/x-icon"), ".ico" }, 570 571 { nxt_string("image/avif"), ".avif" }, 572 { nxt_string("image/avif-sequence"), ".avifs" }, 573 574 { nxt_string("font/woff"), ".woff" }, 575 { nxt_string("font/woff2"), ".woff2" }, 576 { nxt_string("font/otf"), ".otf" }, 577 { nxt_string("font/ttf"), ".ttf" }, 578 579 { nxt_string("text/plain"), ".txt" }, 580 { nxt_string("text/markdown"), ".md" }, 581 { nxt_string("text/x-rst"), ".rst" }, 582 583 { nxt_string("application/javascript"), ".js" }, 584 { nxt_string("application/json"), ".json" }, 585 { nxt_string("application/xml"), ".xml" }, 586 { nxt_string("application/rss+xml"), ".rss" }, 587 { nxt_string("application/atom+xml"), ".atom" }, 588 { nxt_string("application/pdf"), ".pdf" }, 589 590 { nxt_string("application/zip"), ".zip" }, 591 592 { nxt_string("audio/mpeg"), ".mp3" }, 593 { nxt_string("audio/ogg"), ".ogg" }, 594 { nxt_string("audio/midi"), ".midi" }, 595 { nxt_string("audio/midi"), ".mid" }, 596 { nxt_string("audio/flac"), ".flac" }, 597 { nxt_string("audio/aac"), ".aac" }, 598 { nxt_string("audio/wav"), ".wav" }, 599 600 { nxt_string("video/mpeg"), ".mpeg" }, 601 { nxt_string("video/mpeg"), ".mpg" }, 602 { nxt_string("video/mp4"), ".mp4" }, 603 { nxt_string("video/webm"), ".webm" }, 604 { nxt_string("video/x-msvideo"), ".avi" }, 605 606 { nxt_string("application/octet-stream"), ".exe" }, 607 { nxt_string("application/octet-stream"), ".bin" }, 608 { nxt_string("application/octet-stream"), ".dll" }, 609 { nxt_string("application/octet-stream"), ".iso" }, 610 { nxt_string("application/octet-stream"), ".img" }, 611 { nxt_string("application/octet-stream"), ".msi" }, 612 613 { nxt_string("application/octet-stream"), ".deb" }, 614 { nxt_string("application/octet-stream"), ".rpm" }, 615 }; 616 617 for (i = 0; i < nxt_nitems(default_types); i++) { 618 type = (nxt_str_t *) &default_types[i].type; 619 620 extension.start = (u_char *) default_types[i].extension; 621 extension.length = nxt_strlen(extension.start); 622 623 ret = nxt_http_static_mtypes_hash_add(mp, hash, &extension, type); 624 if (nxt_slow_path(ret != NXT_OK)) { 625 return NXT_ERROR; 626 } 627 } 628 629 return NXT_OK; 630 } 631 632 633 static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto 634 nxt_aligned(64) = 635 { 636 NXT_LVLHSH_DEFAULT, 637 nxt_http_static_mtypes_hash_test, 638 nxt_http_static_mtypes_hash_alloc, 639 nxt_http_static_mtypes_hash_free, 640 }; 641 642 643 typedef struct { 644 nxt_str_t extension; 645 nxt_str_t *type; 646 } nxt_http_static_mtype_t; 647 648 649 nxt_int_t 650 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash, 651 nxt_str_t *extension, nxt_str_t *type) 652 { 653 nxt_lvlhsh_query_t lhq; 654 nxt_http_static_mtype_t *mtype; 655 656 mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t)); 657 if (nxt_slow_path(mtype == NULL)) { 658 return NXT_ERROR; 659 } 660 661 mtype->extension = *extension; 662 mtype->type = type; 663 664 lhq.key = *extension; 665 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); 666 lhq.replace = 1; 667 lhq.value = mtype; 668 lhq.proto = &nxt_http_static_mtypes_hash_proto; 669 lhq.pool = mp; 670 671 return nxt_lvlhsh_insert(hash, &lhq); 672 } 673 674 675 nxt_str_t * 676 nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) 677 { 678 nxt_lvlhsh_query_t lhq; 679 nxt_http_static_mtype_t *mtype; 680 681 lhq.key = *extension; 682 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); 683 lhq.proto = &nxt_http_static_mtypes_hash_proto; 684 685 if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) { 686 mtype = lhq.value; 687 return mtype->type; 688 } 689 690 return NULL; 691 } 692 693 694 static nxt_int_t 695 nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data) 696 { 697 nxt_http_static_mtype_t *mtype; 698 699 mtype = data; 700 701 return nxt_strcasestr_eq(&lhq->key, &mtype->extension) ? NXT_OK 702 : NXT_DECLINED; 703 } 704 705 706 static void * 707 nxt_http_static_mtypes_hash_alloc(void *data, size_t size) 708 { 709 return nxt_mp_align(data, size, size); 710 } 711 712 713 static void 714 nxt_http_static_mtypes_hash_free(void *data, void *p) 715 { 716 nxt_mp_free(data, p); 717 } 718