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