nxt_http_static.c (2137:96f3ac16391b) nxt_http_static.c (2139:99d792169ffb)
1
2/*
3 * Copyright (C) NGINX, Inc.
4 */
5
6#include <nxt_router.h>
7#include <nxt_http.h>
8
9
10typedef struct {
11 nxt_var_t *var;
12#if (NXT_HAVE_OPENAT2)
13 u_char *fname;
14#endif
15 uint8_t is_const; /* 1 bit */
16} nxt_http_static_share_t;
17
18
19typedef struct {
20 nxt_uint_t nshares;
21 nxt_http_static_share_t *shares;
22 nxt_str_t index;
23#if (NXT_HAVE_OPENAT2)
24 nxt_var_t *chroot;
25 nxt_uint_t resolve;
26#endif
27 nxt_http_route_rule_t *types;
28} nxt_http_static_conf_t;
29
30
31typedef struct {
32 nxt_http_action_t *action;
33 nxt_str_t share;
34#if (NXT_HAVE_OPENAT2)
35 nxt_str_t chroot;
36#endif
37 uint32_t share_idx;
38 uint8_t need_body; /* 1 bit */
39} nxt_http_static_ctx_t;
40
41
42#define NXT_HTTP_STATIC_BUF_COUNT 2
43#define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024)
44
45
46static nxt_http_action_t *nxt_http_static(nxt_task_t *task,
47 nxt_http_request_t *r, nxt_http_action_t *action);
48static void nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
49 nxt_http_static_ctx_t *ctx);
50static void nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data);
51static void nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data);
52static void nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
53 nxt_http_static_ctx_t *ctx, nxt_http_status_t status);
54#if (NXT_HAVE_OPENAT2)
55static u_char *nxt_http_static_chroot_match(u_char *chr, u_char *shr);
56#endif
57static void nxt_http_static_extract_extension(nxt_str_t *path,
58 nxt_str_t *exten);
59static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
60 void *data);
61static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
62 void *data);
63
64static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
65 void *data);
66static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
67static void nxt_http_static_mtypes_hash_free(void *data, void *p);
68
69
70static const nxt_http_request_state_t nxt_http_static_send_state;
71
72
73nxt_int_t
74nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
75 nxt_http_action_t *action, nxt_http_action_conf_t *acf)
76{
77 uint32_t i;
78 nxt_mp_t *mp;
79 nxt_str_t str, *ret;
80 nxt_var_t *var;
81 nxt_conf_value_t *cv;
82 nxt_http_static_conf_t *conf;
83
84 mp = tmcf->router_conf->mem_pool;
85
86 conf = nxt_mp_zget(mp, sizeof(nxt_http_static_conf_t));
87 if (nxt_slow_path(conf == NULL)) {
88 return NXT_ERROR;
89 }
90
91 action->handler = nxt_http_static;
92 action->u.conf = conf;
93
94 conf->nshares = nxt_conf_array_elements_count_or_1(acf->share);
95 conf->shares = nxt_mp_zget(mp, sizeof(nxt_http_static_share_t)
96 * conf->nshares);
97 if (nxt_slow_path(conf->shares == NULL)) {
98 return NXT_ERROR;
99 }
100
101 for (i = 0; i < conf->nshares; i++) {
102 cv = nxt_conf_get_array_element_or_itself(acf->share, i);
103 nxt_conf_get_string(cv, &str);
104
105 var = nxt_var_compile(&str, mp, 1);
106 if (nxt_slow_path(var == NULL)) {
107 return NXT_ERROR;
108 }
109
110 conf->shares[i].var = var;
111 conf->shares[i].is_const = nxt_var_is_const(var);
112 }
113
114 if (acf->index == NULL) {
115 nxt_str_set(&conf->index, "index.html");
116
117 } else {
118 nxt_conf_get_string(acf->index, &str);
119
120 ret = nxt_str_dup(mp, &conf->index, &str);
121 if (nxt_slow_path(ret == NULL)) {
122 return NXT_ERROR;
123 }
124 }
125
126#if (NXT_HAVE_OPENAT2)
127 if (acf->chroot.length > 0) {
128 nxt_str_t chr, shr;
129 nxt_bool_t is_const;
130
131 conf->chroot = nxt_var_compile(&acf->chroot, mp, 1);
132 if (nxt_slow_path(conf->chroot == NULL)) {
133 return NXT_ERROR;
134 }
135
136 is_const = nxt_var_is_const(conf->chroot);
137
138 for (i = 0; i < conf->nshares; i++) {
139 conf->shares[i].is_const &= is_const;
140
141 if (conf->shares[i].is_const) {
142 nxt_var_raw(conf->chroot, &chr);
143 nxt_var_raw(conf->shares[i].var, &shr);
144
145 conf->shares[i].fname = nxt_http_static_chroot_match(chr.start,
146 shr.start);
147 }
148 }
149 }
150
151 if (acf->follow_symlinks != NULL
152 && !nxt_conf_get_boolean(acf->follow_symlinks))
153 {
154 conf->resolve |= RESOLVE_NO_SYMLINKS;
155 }
156
157 if (acf->traverse_mounts != NULL
158 && !nxt_conf_get_boolean(acf->traverse_mounts))
159 {
160 conf->resolve |= RESOLVE_NO_XDEV;
161 }
162#endif
163
164 if (acf->types != NULL) {
165 conf->types = nxt_http_route_types_rule_create(task, mp, acf->types);
166 if (nxt_slow_path(conf->types == NULL)) {
167 return NXT_ERROR;
168 }
169 }
170
171 if (acf->fallback != NULL) {
172 action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
173 if (nxt_slow_path(action->fallback == NULL)) {
174 return NXT_ERROR;
175 }
176
177 return nxt_http_action_init(task, tmcf, acf->fallback,
178 action->fallback);
179 }
180
181 return NXT_OK;
182}
183
184
185static nxt_http_action_t *
186nxt_http_static(nxt_task_t *task, nxt_http_request_t *r,
187 nxt_http_action_t *action)
188{
189 nxt_bool_t need_body;
190 nxt_http_static_ctx_t *ctx;
191
192 if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
193
194 if (!nxt_str_eq(r->method, "HEAD", 4)) {
195 if (action->fallback != NULL) {
196 return action->fallback;
197 }
198
199 nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
200 return NULL;
201 }
202
203 need_body = 0;
204
205 } else {
206 need_body = 1;
207 }
208
209 ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
210 if (nxt_slow_path(ctx == NULL)) {
211 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
212 return NULL;
213 }
214
215 ctx->action = action;
216 ctx->need_body = need_body;
217
218 nxt_http_static_iterate(task, r, ctx);
219
220 return NULL;
221}
222
223
224static void
225nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
226 nxt_http_static_ctx_t *ctx)
227{
228 nxt_int_t ret;
229 nxt_http_static_conf_t *conf;
230 nxt_http_static_share_t *share;
231
232 conf = ctx->action->u.conf;
233
234 share = &conf->shares[ctx->share_idx];
235
236#if (NXT_DEBUG)
237 nxt_str_t shr;
238 nxt_str_t idx;
239
240 nxt_var_raw(share->var, &shr);
241 idx = conf->index;
242
243#if (NXT_HAVE_OPENAT2)
244 nxt_str_t chr;
245
246 if (conf->chroot != NULL) {
247 nxt_var_raw(conf->chroot, &chr);
248
249 } else {
250 nxt_str_set(&chr, "");
251 }
252
253 nxt_debug(task, "http static: \"%V\", index: \"%V\" (chroot: \"%V\")",
254 &shr, &idx, &chr);
255#else
256 nxt_debug(task, "http static: \"%V\", index: \"%V\"", &shr, &idx);
257#endif
258#endif /* NXT_DEBUG */
259
260 if (share->is_const) {
261 nxt_var_raw(share->var, &ctx->share);
262
263#if (NXT_HAVE_OPENAT2)
264 if (conf->chroot != NULL && ctx->share_idx == 0) {
265 nxt_var_raw(conf->chroot, &ctx->chroot);
266 }
267#endif
268
269 nxt_http_static_send_ready(task, r, ctx);
270
271 } else {
272 ret = nxt_var_query_init(&r->var_query, r, r->mem_pool);
273 if (nxt_slow_path(ret != NXT_OK)) {
274 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
275 return;
276 }
277
278 nxt_var_query(task, r->var_query, share->var, &ctx->share);
279
280#if (NXT_HAVE_OPENAT2)
281 if (conf->chroot != NULL && ctx->share_idx == 0) {
282 nxt_var_query(task, r->var_query, conf->chroot, &ctx->chroot);
283 }
284#endif
285
286 nxt_var_query_resolve(task, r->var_query, ctx,
287 nxt_http_static_send_ready,
288 nxt_http_static_var_error);
289 }
290}
291
292
293static void
294nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
295{
296 size_t length, encode;
297 u_char *p, *fname;
298 struct tm tm;
299 nxt_buf_t *fb;
300 nxt_int_t ret;
301 nxt_str_t *shr, *index, exten, *mtype;
302 nxt_uint_t level;
303 nxt_file_t *f, file;
304 nxt_file_info_t fi;
305 nxt_http_field_t *field;
306 nxt_http_status_t status;
307 nxt_router_conf_t *rtcf;
308 nxt_http_action_t *action;
309 nxt_http_request_t *r;
310 nxt_work_handler_t body_handler;
311 nxt_http_static_ctx_t *ctx;
312 nxt_http_static_conf_t *conf;
313
314 r = obj;
315 ctx = data;
316 action = ctx->action;
317 conf = action->u.conf;
318 rtcf = r->conf->socket_conf->router_conf;
319
320 f = NULL;
321 mtype = NULL;
322
323 shr = &ctx->share;
324 index = &conf->index;
325
326 if (shr->start[shr->length - 1] == '/') {
327 nxt_http_static_extract_extension(index, &exten);
328
329 length = shr->length + index->length;
330
331 fname = nxt_mp_nget(r->mem_pool, length + 1);
332 if (nxt_slow_path(fname == NULL)) {
333 goto fail;
334 }
335
336 p = fname;
337 p = nxt_cpymem(p, shr->start, shr->length);
338 p = nxt_cpymem(p, index->start, index->length);
339 *p = '\0';
340
341 } else {
342 if (conf->types == NULL) {
343 nxt_str_null(&exten);
344
345 } else {
346 nxt_http_static_extract_extension(shr, &exten);
347 mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
348
349 ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
350 mtype->length);
351 if (nxt_slow_path(ret == NXT_ERROR)) {
352 goto fail;
353 }
354
355 if (ret == 0) {
356 nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
357 return;
358 }
359 }
360
361 fname = ctx->share.start;
362 }
363
364 nxt_memzero(&file, sizeof(nxt_file_t));
365
366 file.name = fname;
367
368#if (NXT_HAVE_OPENAT2)
369 if (conf->resolve != 0 || ctx->chroot.length > 0) {
370 nxt_str_t *chr;
371 nxt_uint_t resolve;
372 nxt_http_static_share_t *share;
373
374 share = &conf->shares[ctx->share_idx];
375
376 resolve = conf->resolve;
377 chr = &ctx->chroot;
378
379 if (chr->length > 0) {
380 resolve |= RESOLVE_IN_ROOT;
381
382 fname = share->is_const
383 ? share->fname
384 : nxt_http_static_chroot_match(chr->start, file.name);
385
386 if (fname != NULL) {
387 file.name = chr->start;
388 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
389 0);
390
391 } else {
392 file.error = NXT_EACCES;
393 ret = NXT_ERROR;
394 }
395
396 } else if (fname[0] == '/') {
397 file.name = (u_char *) "/";
398 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
399
400 } else {
401 file.name = (u_char *) ".";
402 file.fd = AT_FDCWD;
403 ret = NXT_OK;
404 }
405
406 if (nxt_fast_path(ret == NXT_OK)) {
407 nxt_file_t af;
408
409 af = file;
410 nxt_memzero(&file, sizeof(nxt_file_t));
411 file.name = fname;
412
413 ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
414 NXT_FILE_OPEN, 0, af.fd, resolve);
415
416 if (af.fd != AT_FDCWD) {
417 nxt_file_close(task, &af);
418 }
419 }
420
421 } else {
422 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
423 }
424
425#else
426 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
427#endif
428
429 if (nxt_slow_path(ret != NXT_OK)) {
430
431 switch (file.error) {
432
433 /*
434 * For Unix domain sockets "errno" is set to:
435 * - ENXIO on Linux;
436 * - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
437 */
438
439 case NXT_ENOENT:
440 case NXT_ENOTDIR:
441 case NXT_ENAMETOOLONG:
442#if (NXT_LINUX)
443 case NXT_ENXIO:
444#else
445 case NXT_EOPNOTSUPP:
446#endif
447 level = NXT_LOG_ERR;
448 status = NXT_HTTP_NOT_FOUND;
449 break;
450
451 case NXT_EACCES:
452#if (NXT_HAVE_OPENAT2)
453 case NXT_ELOOP:
454 case NXT_EXDEV:
455#endif
456 level = NXT_LOG_ERR;
457 status = NXT_HTTP_FORBIDDEN;
458 break;
459
460 default:
461 level = NXT_LOG_ALERT;
462 status = NXT_HTTP_INTERNAL_SERVER_ERROR;
463 break;
464 }
465
466 if (status != NXT_HTTP_NOT_FOUND) {
467#if (NXT_HAVE_OPENAT2)
468 nxt_str_t *chr = &ctx->chroot;
469
470 if (chr->length > 0) {
471 nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
472 fname, chr, file.error);
473
474 } else {
475 nxt_log(task, level, "opening \"%s\" failed %E",
476 fname, file.error);
477 }
478
479#else
480 nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
481#endif
482 }
483
484 if (level == NXT_LOG_ERR) {
485 nxt_http_static_next(task, r, ctx, status);
486 return;
487 }
488
489 goto fail;
490 }
491
492 f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
493 if (nxt_slow_path(f == NULL)) {
494 nxt_file_close(task, &file);
495 goto fail;
496 }
497
498 *f = file;
499
500 ret = nxt_file_info(f, &fi);
501 if (nxt_slow_path(ret != NXT_OK)) {
502 goto fail;
503 }
504
505 if (nxt_fast_path(nxt_is_file(&fi))) {
506 r->status = NXT_HTTP_OK;
507 r->resp.content_length_n = nxt_file_size(&fi);
508
509 field = nxt_list_zero_add(r->resp.fields);
510 if (nxt_slow_path(field == NULL)) {
511 goto fail;
512 }
513
514 nxt_http_field_name_set(field, "Last-Modified");
515
516 p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
517 if (nxt_slow_path(p == NULL)) {
518 goto fail;
519 }
520
521 nxt_localtime(nxt_file_mtime(&fi), &tm);
522
523 field->value = p;
524 field->value_length = nxt_http_date(p, &tm) - p;
525
526 field = nxt_list_zero_add(r->resp.fields);
527 if (nxt_slow_path(field == NULL)) {
528 goto fail;
529 }
530
531 nxt_http_field_name_set(field, "ETag");
532
533 length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
534
535 p = nxt_mp_nget(r->mem_pool, length);
536 if (nxt_slow_path(p == NULL)) {
537 goto fail;
538 }
539
540 field->value = p;
541 field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
542 nxt_file_mtime(&fi),
543 nxt_file_size(&fi))
544 - p;
545
546 if (exten.start == NULL) {
547 nxt_http_static_extract_extension(shr, &exten);
548 }
549
550 if (mtype == NULL) {
551 mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
552 }
553
554 if (mtype->length != 0) {
555 field = nxt_list_zero_add(r->resp.fields);
556 if (nxt_slow_path(field == NULL)) {
557 goto fail;
558 }
559
560 nxt_http_field_name_set(field, "Content-Type");
561
562 field->value = mtype->start;
563 field->value_length = mtype->length;
564 }
565
566 if (ctx->need_body && nxt_file_size(&fi) > 0) {
567 fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
568 if (nxt_slow_path(fb == NULL)) {
569 goto fail;
570 }
571
572 fb->file = f;
573 fb->file_end = nxt_file_size(&fi);
574
575 r->out = fb;
576
577 body_handler = &nxt_http_static_body_handler;
578
579 } else {
580 nxt_file_close(task, f);
581 body_handler = NULL;
582 }
583
584 } else {
585 /* Not a file. */
586 nxt_file_close(task, f);
587
588 if (nxt_slow_path(!nxt_is_dir(&fi)
589 || shr->start[shr->length - 1] == '/'))
590 {
591 nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
592 f->name);
593
594 nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
595 return;
596 }
597
598 f = NULL;
599
600 r->status = NXT_HTTP_MOVED_PERMANENTLY;
601 r->resp.content_length_n = 0;
602
603 field = nxt_list_zero_add(r->resp.fields);
604 if (nxt_slow_path(field == NULL)) {
605 goto fail;
606 }
607
608 nxt_http_field_name_set(field, "Location");
609
610 encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
611 length = r->path->length + encode * 2 + 1;
612
613 if (r->args->length > 0) {
614 length += 1 + r->args->length;
615 }
616
617 p = nxt_mp_nget(r->mem_pool, length);
618 if (nxt_slow_path(p == NULL)) {
619 goto fail;
620 }
621
622 field->value = p;
623 field->value_length = length;
624
625 if (encode > 0) {
626 p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
627
628 } else {
629 p = nxt_cpymem(p, r->path->start, r->path->length);
630 }
631
632 *p++ = '/';
633
634 if (r->args->length > 0) {
635 *p++ = '?';
636 nxt_memcpy(p, r->args->start, r->args->length);
637 }
638
639 body_handler = NULL;
640 }
641
642 nxt_http_request_header_send(task, r, body_handler, NULL);
643
644 r->state = &nxt_http_static_send_state;
645 return;
646
647fail:
648
649 if (f != NULL) {
650 nxt_file_close(task, f);
651 }
652
653 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
654}
655
656
657static void
658nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data)
659{
660 nxt_http_request_t *r;
661
662 r = obj;
663
664 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
665}
666
667
668static void
669nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
670 nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
671{
672 nxt_http_action_t *action;
673 nxt_http_static_conf_t *conf;
674
675 action = ctx->action;
676 conf = action->u.conf;
677
678 ctx->share_idx++;
679
680 if (ctx->share_idx < conf->nshares) {
681 nxt_http_static_iterate(task, r, ctx);
682 return;
683 }
684
685 if (action->fallback != NULL) {
686 nxt_http_request_action(task, r, action->fallback);
687 return;
688 }
689
690 nxt_http_request_error(task, r, status);
691}
692
693
694#if (NXT_HAVE_OPENAT2)
695
696static u_char *
697nxt_http_static_chroot_match(u_char *chr, u_char *shr)
698{
699 if (*chr != *shr) {
700 return NULL;
701 }
702
703 chr++;
704 shr++;
705
706 for ( ;; ) {
707 if (*shr == '\0') {
708 return NULL;
709 }
710
711 if (*chr == *shr) {
712 chr++;
713 shr++;
714 continue;
715 }
716
717 if (*chr == '\0') {
718 break;
719 }
720
721 if (*chr == '/') {
722 if (chr[-1] == '/') {
723 chr++;
724 continue;
725 }
726
727 } else if (*shr == '/') {
728 if (shr[-1] == '/') {
729 shr++;
730 continue;
731 }
732 }
733
734 return NULL;
735 }
736
737 if (shr[-1] != '/' && *shr != '/') {
738 return NULL;
739 }
740
741 while (*shr == '/') {
742 shr++;
743 }
744
745 return (*shr != '\0') ? shr : NULL;
746}
747
748#endif
749
750
751static void
752nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten)
753{
754 u_char ch, *p, *end;
755
756 end = path->start + path->length;
757 p = end;
758
759 while (p > path->start) {
760 p--;
761 ch = *p;
762
763 switch (ch) {
764 case '/':
765 p++;
766 /* Fall through. */
767 case '.':
768 goto extension;
769 }
770 }
771
772extension:
773
774 exten->length = end - p;
775 exten->start = p;
776}
777
778
779static void
780nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
781{
782 size_t alloc;
783 nxt_buf_t *fb, *b, **next, *out;
784 nxt_off_t rest;
785 nxt_int_t n;
786 nxt_work_queue_t *wq;
787 nxt_http_request_t *r;
788
789 r = obj;
790 fb = r->out;
791
792 rest = fb->file_end - fb->file_pos;
793 out = NULL;
794 next = &out;
795 n = 0;
796
797 do {
798 alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
799
800 b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
801 if (nxt_slow_path(b == NULL)) {
802 goto fail;
803 }
804
805 b->completion_handler = nxt_http_static_buf_completion;
806 b->parent = r;
807
808 nxt_mp_retain(r->mem_pool);
809
810 *next = b;
811 next = &b->next;
812
813 rest -= alloc;
814
815 } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
816
817 wq = &task->thread->engine->fast_work_queue;
818
819 nxt_sendbuf_drain(task, wq, out);
820 return;
821
822fail:
823
824 while (out != NULL) {
825 b = out;
826 out = b->next;
827
828 nxt_mp_free(r->mem_pool, b);
829 nxt_mp_release(r->mem_pool);
830 }
831}
832
833
834static const nxt_http_request_state_t nxt_http_static_send_state
835 nxt_aligned(64) =
836{
837 .error_handler = nxt_http_request_error_handler,
838};
839
840
841static void
842nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
843{
844 ssize_t n, size;
845 nxt_buf_t *b, *fb, *next;
846 nxt_off_t rest;
847 nxt_http_request_t *r;
848
849 b = obj;
850 r = data;
851
852complete_buf:
853
854 fb = r->out;
855
856 if (nxt_slow_path(fb == NULL || r->error)) {
857 goto clean;
858 }
859
860 rest = fb->file_end - fb->file_pos;
861 size = nxt_buf_mem_size(&b->mem);
862
863 size = nxt_min(rest, (nxt_off_t) size);
864
865 n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
866
867 if (n != size) {
868 if (n >= 0) {
869 nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
870 "while sending response to a client", fb->file->name);
871 }
872
873 nxt_http_request_error_handler(task, r, r->proto.any);
874 goto clean;
875 }
876
877 next = b->next;
878
879 if (n == rest) {
880 nxt_file_close(task, fb->file);
881 r->out = NULL;
882
883 b->next = nxt_http_buf_last(r);
884
885 } else {
886 fb->file_pos += n;
887 b->next = NULL;
888 }
889
890 b->mem.pos = b->mem.start;
891 b->mem.free = b->mem.pos + n;
892
893 nxt_http_request_send(task, r, b);
894
895 if (next != NULL) {
896 b = next;
897 goto complete_buf;
898 }
899
900 return;
901
902clean:
903
904 do {
905 next = b->next;
906
907 nxt_mp_free(r->mem_pool, b);
908 nxt_mp_release(r->mem_pool);
909
910 b = next;
911 } while (b != NULL);
912
913 if (fb != NULL) {
914 nxt_file_close(task, fb->file);
915 r->out = NULL;
916 }
917}
918
919
920nxt_int_t
921nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
922{
923 nxt_str_t *type, exten;
924 nxt_int_t ret;
925 nxt_uint_t i;
926
927 static const struct {
928 nxt_str_t type;
929 const char *exten;
930 } default_types[] = {
931
932 { nxt_string("text/html"), ".html" },
933 { nxt_string("text/html"), ".htm" },
934 { nxt_string("text/css"), ".css" },
935
936 { nxt_string("image/svg+xml"), ".svg" },
937 { nxt_string("image/webp"), ".webp" },
938 { nxt_string("image/png"), ".png" },
939 { nxt_string("image/apng"), ".apng" },
940 { nxt_string("image/jpeg"), ".jpeg" },
941 { nxt_string("image/jpeg"), ".jpg" },
942 { nxt_string("image/gif"), ".gif" },
943 { nxt_string("image/x-icon"), ".ico" },
944
945 { nxt_string("image/avif"), ".avif" },
946 { nxt_string("image/avif-sequence"), ".avifs" },
947
948 { nxt_string("font/woff"), ".woff" },
949 { nxt_string("font/woff2"), ".woff2" },
950 { nxt_string("font/otf"), ".otf" },
951 { nxt_string("font/ttf"), ".ttf" },
952
953 { nxt_string("text/plain"), ".txt" },
954 { nxt_string("text/markdown"), ".md" },
955 { nxt_string("text/x-rst"), ".rst" },
956
957 { nxt_string("application/javascript"), ".js" },
958 { nxt_string("application/json"), ".json" },
959 { nxt_string("application/xml"), ".xml" },
960 { nxt_string("application/rss+xml"), ".rss" },
961 { nxt_string("application/atom+xml"), ".atom" },
962 { nxt_string("application/pdf"), ".pdf" },
963
964 { nxt_string("application/zip"), ".zip" },
965
966 { nxt_string("audio/mpeg"), ".mp3" },
967 { nxt_string("audio/ogg"), ".ogg" },
968 { nxt_string("audio/midi"), ".midi" },
969 { nxt_string("audio/midi"), ".mid" },
970 { nxt_string("audio/flac"), ".flac" },
971 { nxt_string("audio/aac"), ".aac" },
972 { nxt_string("audio/wav"), ".wav" },
973
974 { nxt_string("video/mpeg"), ".mpeg" },
975 { nxt_string("video/mpeg"), ".mpg" },
976 { nxt_string("video/mp4"), ".mp4" },
977 { nxt_string("video/webm"), ".webm" },
978 { nxt_string("video/x-msvideo"), ".avi" },
979
980 { nxt_string("application/octet-stream"), ".exe" },
981 { nxt_string("application/octet-stream"), ".bin" },
982 { nxt_string("application/octet-stream"), ".dll" },
983 { nxt_string("application/octet-stream"), ".iso" },
984 { nxt_string("application/octet-stream"), ".img" },
985 { nxt_string("application/octet-stream"), ".msi" },
986
987 { nxt_string("application/octet-stream"), ".deb" },
988 { nxt_string("application/octet-stream"), ".rpm" },
989
990 { nxt_string("application/x-httpd-php"), ".php" },
991 };
992
993 for (i = 0; i < nxt_nitems(default_types); i++) {
994 type = (nxt_str_t *) &default_types[i].type;
995
996 exten.start = (u_char *) default_types[i].exten;
997 exten.length = nxt_strlen(exten.start);
998
999 ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
1000 if (nxt_slow_path(ret != NXT_OK)) {
1001 return NXT_ERROR;
1002 }
1003 }
1004
1005 return NXT_OK;
1006}
1007
1008
1009static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto
1010 nxt_aligned(64) =
1011{
1012 NXT_LVLHSH_DEFAULT,
1013 nxt_http_static_mtypes_hash_test,
1014 nxt_http_static_mtypes_hash_alloc,
1015 nxt_http_static_mtypes_hash_free,
1016};
1017
1018
1019typedef struct {
1020 nxt_str_t exten;
1021 nxt_str_t *type;
1022} nxt_http_static_mtype_t;
1023
1024
1025nxt_int_t
1026nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1
2/*
3 * Copyright (C) NGINX, Inc.
4 */
5
6#include <nxt_router.h>
7#include <nxt_http.h>
8
9
10typedef struct {
11 nxt_var_t *var;
12#if (NXT_HAVE_OPENAT2)
13 u_char *fname;
14#endif
15 uint8_t is_const; /* 1 bit */
16} nxt_http_static_share_t;
17
18
19typedef struct {
20 nxt_uint_t nshares;
21 nxt_http_static_share_t *shares;
22 nxt_str_t index;
23#if (NXT_HAVE_OPENAT2)
24 nxt_var_t *chroot;
25 nxt_uint_t resolve;
26#endif
27 nxt_http_route_rule_t *types;
28} nxt_http_static_conf_t;
29
30
31typedef struct {
32 nxt_http_action_t *action;
33 nxt_str_t share;
34#if (NXT_HAVE_OPENAT2)
35 nxt_str_t chroot;
36#endif
37 uint32_t share_idx;
38 uint8_t need_body; /* 1 bit */
39} nxt_http_static_ctx_t;
40
41
42#define NXT_HTTP_STATIC_BUF_COUNT 2
43#define NXT_HTTP_STATIC_BUF_SIZE (128 * 1024)
44
45
46static nxt_http_action_t *nxt_http_static(nxt_task_t *task,
47 nxt_http_request_t *r, nxt_http_action_t *action);
48static void nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
49 nxt_http_static_ctx_t *ctx);
50static void nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data);
51static void nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data);
52static void nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
53 nxt_http_static_ctx_t *ctx, nxt_http_status_t status);
54#if (NXT_HAVE_OPENAT2)
55static u_char *nxt_http_static_chroot_match(u_char *chr, u_char *shr);
56#endif
57static void nxt_http_static_extract_extension(nxt_str_t *path,
58 nxt_str_t *exten);
59static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
60 void *data);
61static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
62 void *data);
63
64static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
65 void *data);
66static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
67static void nxt_http_static_mtypes_hash_free(void *data, void *p);
68
69
70static const nxt_http_request_state_t nxt_http_static_send_state;
71
72
73nxt_int_t
74nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
75 nxt_http_action_t *action, nxt_http_action_conf_t *acf)
76{
77 uint32_t i;
78 nxt_mp_t *mp;
79 nxt_str_t str, *ret;
80 nxt_var_t *var;
81 nxt_conf_value_t *cv;
82 nxt_http_static_conf_t *conf;
83
84 mp = tmcf->router_conf->mem_pool;
85
86 conf = nxt_mp_zget(mp, sizeof(nxt_http_static_conf_t));
87 if (nxt_slow_path(conf == NULL)) {
88 return NXT_ERROR;
89 }
90
91 action->handler = nxt_http_static;
92 action->u.conf = conf;
93
94 conf->nshares = nxt_conf_array_elements_count_or_1(acf->share);
95 conf->shares = nxt_mp_zget(mp, sizeof(nxt_http_static_share_t)
96 * conf->nshares);
97 if (nxt_slow_path(conf->shares == NULL)) {
98 return NXT_ERROR;
99 }
100
101 for (i = 0; i < conf->nshares; i++) {
102 cv = nxt_conf_get_array_element_or_itself(acf->share, i);
103 nxt_conf_get_string(cv, &str);
104
105 var = nxt_var_compile(&str, mp, 1);
106 if (nxt_slow_path(var == NULL)) {
107 return NXT_ERROR;
108 }
109
110 conf->shares[i].var = var;
111 conf->shares[i].is_const = nxt_var_is_const(var);
112 }
113
114 if (acf->index == NULL) {
115 nxt_str_set(&conf->index, "index.html");
116
117 } else {
118 nxt_conf_get_string(acf->index, &str);
119
120 ret = nxt_str_dup(mp, &conf->index, &str);
121 if (nxt_slow_path(ret == NULL)) {
122 return NXT_ERROR;
123 }
124 }
125
126#if (NXT_HAVE_OPENAT2)
127 if (acf->chroot.length > 0) {
128 nxt_str_t chr, shr;
129 nxt_bool_t is_const;
130
131 conf->chroot = nxt_var_compile(&acf->chroot, mp, 1);
132 if (nxt_slow_path(conf->chroot == NULL)) {
133 return NXT_ERROR;
134 }
135
136 is_const = nxt_var_is_const(conf->chroot);
137
138 for (i = 0; i < conf->nshares; i++) {
139 conf->shares[i].is_const &= is_const;
140
141 if (conf->shares[i].is_const) {
142 nxt_var_raw(conf->chroot, &chr);
143 nxt_var_raw(conf->shares[i].var, &shr);
144
145 conf->shares[i].fname = nxt_http_static_chroot_match(chr.start,
146 shr.start);
147 }
148 }
149 }
150
151 if (acf->follow_symlinks != NULL
152 && !nxt_conf_get_boolean(acf->follow_symlinks))
153 {
154 conf->resolve |= RESOLVE_NO_SYMLINKS;
155 }
156
157 if (acf->traverse_mounts != NULL
158 && !nxt_conf_get_boolean(acf->traverse_mounts))
159 {
160 conf->resolve |= RESOLVE_NO_XDEV;
161 }
162#endif
163
164 if (acf->types != NULL) {
165 conf->types = nxt_http_route_types_rule_create(task, mp, acf->types);
166 if (nxt_slow_path(conf->types == NULL)) {
167 return NXT_ERROR;
168 }
169 }
170
171 if (acf->fallback != NULL) {
172 action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
173 if (nxt_slow_path(action->fallback == NULL)) {
174 return NXT_ERROR;
175 }
176
177 return nxt_http_action_init(task, tmcf, acf->fallback,
178 action->fallback);
179 }
180
181 return NXT_OK;
182}
183
184
185static nxt_http_action_t *
186nxt_http_static(nxt_task_t *task, nxt_http_request_t *r,
187 nxt_http_action_t *action)
188{
189 nxt_bool_t need_body;
190 nxt_http_static_ctx_t *ctx;
191
192 if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
193
194 if (!nxt_str_eq(r->method, "HEAD", 4)) {
195 if (action->fallback != NULL) {
196 return action->fallback;
197 }
198
199 nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
200 return NULL;
201 }
202
203 need_body = 0;
204
205 } else {
206 need_body = 1;
207 }
208
209 ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
210 if (nxt_slow_path(ctx == NULL)) {
211 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
212 return NULL;
213 }
214
215 ctx->action = action;
216 ctx->need_body = need_body;
217
218 nxt_http_static_iterate(task, r, ctx);
219
220 return NULL;
221}
222
223
224static void
225nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
226 nxt_http_static_ctx_t *ctx)
227{
228 nxt_int_t ret;
229 nxt_http_static_conf_t *conf;
230 nxt_http_static_share_t *share;
231
232 conf = ctx->action->u.conf;
233
234 share = &conf->shares[ctx->share_idx];
235
236#if (NXT_DEBUG)
237 nxt_str_t shr;
238 nxt_str_t idx;
239
240 nxt_var_raw(share->var, &shr);
241 idx = conf->index;
242
243#if (NXT_HAVE_OPENAT2)
244 nxt_str_t chr;
245
246 if (conf->chroot != NULL) {
247 nxt_var_raw(conf->chroot, &chr);
248
249 } else {
250 nxt_str_set(&chr, "");
251 }
252
253 nxt_debug(task, "http static: \"%V\", index: \"%V\" (chroot: \"%V\")",
254 &shr, &idx, &chr);
255#else
256 nxt_debug(task, "http static: \"%V\", index: \"%V\"", &shr, &idx);
257#endif
258#endif /* NXT_DEBUG */
259
260 if (share->is_const) {
261 nxt_var_raw(share->var, &ctx->share);
262
263#if (NXT_HAVE_OPENAT2)
264 if (conf->chroot != NULL && ctx->share_idx == 0) {
265 nxt_var_raw(conf->chroot, &ctx->chroot);
266 }
267#endif
268
269 nxt_http_static_send_ready(task, r, ctx);
270
271 } else {
272 ret = nxt_var_query_init(&r->var_query, r, r->mem_pool);
273 if (nxt_slow_path(ret != NXT_OK)) {
274 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
275 return;
276 }
277
278 nxt_var_query(task, r->var_query, share->var, &ctx->share);
279
280#if (NXT_HAVE_OPENAT2)
281 if (conf->chroot != NULL && ctx->share_idx == 0) {
282 nxt_var_query(task, r->var_query, conf->chroot, &ctx->chroot);
283 }
284#endif
285
286 nxt_var_query_resolve(task, r->var_query, ctx,
287 nxt_http_static_send_ready,
288 nxt_http_static_var_error);
289 }
290}
291
292
293static void
294nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
295{
296 size_t length, encode;
297 u_char *p, *fname;
298 struct tm tm;
299 nxt_buf_t *fb;
300 nxt_int_t ret;
301 nxt_str_t *shr, *index, exten, *mtype;
302 nxt_uint_t level;
303 nxt_file_t *f, file;
304 nxt_file_info_t fi;
305 nxt_http_field_t *field;
306 nxt_http_status_t status;
307 nxt_router_conf_t *rtcf;
308 nxt_http_action_t *action;
309 nxt_http_request_t *r;
310 nxt_work_handler_t body_handler;
311 nxt_http_static_ctx_t *ctx;
312 nxt_http_static_conf_t *conf;
313
314 r = obj;
315 ctx = data;
316 action = ctx->action;
317 conf = action->u.conf;
318 rtcf = r->conf->socket_conf->router_conf;
319
320 f = NULL;
321 mtype = NULL;
322
323 shr = &ctx->share;
324 index = &conf->index;
325
326 if (shr->start[shr->length - 1] == '/') {
327 nxt_http_static_extract_extension(index, &exten);
328
329 length = shr->length + index->length;
330
331 fname = nxt_mp_nget(r->mem_pool, length + 1);
332 if (nxt_slow_path(fname == NULL)) {
333 goto fail;
334 }
335
336 p = fname;
337 p = nxt_cpymem(p, shr->start, shr->length);
338 p = nxt_cpymem(p, index->start, index->length);
339 *p = '\0';
340
341 } else {
342 if (conf->types == NULL) {
343 nxt_str_null(&exten);
344
345 } else {
346 nxt_http_static_extract_extension(shr, &exten);
347 mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
348
349 ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
350 mtype->length);
351 if (nxt_slow_path(ret == NXT_ERROR)) {
352 goto fail;
353 }
354
355 if (ret == 0) {
356 nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
357 return;
358 }
359 }
360
361 fname = ctx->share.start;
362 }
363
364 nxt_memzero(&file, sizeof(nxt_file_t));
365
366 file.name = fname;
367
368#if (NXT_HAVE_OPENAT2)
369 if (conf->resolve != 0 || ctx->chroot.length > 0) {
370 nxt_str_t *chr;
371 nxt_uint_t resolve;
372 nxt_http_static_share_t *share;
373
374 share = &conf->shares[ctx->share_idx];
375
376 resolve = conf->resolve;
377 chr = &ctx->chroot;
378
379 if (chr->length > 0) {
380 resolve |= RESOLVE_IN_ROOT;
381
382 fname = share->is_const
383 ? share->fname
384 : nxt_http_static_chroot_match(chr->start, file.name);
385
386 if (fname != NULL) {
387 file.name = chr->start;
388 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
389 0);
390
391 } else {
392 file.error = NXT_EACCES;
393 ret = NXT_ERROR;
394 }
395
396 } else if (fname[0] == '/') {
397 file.name = (u_char *) "/";
398 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
399
400 } else {
401 file.name = (u_char *) ".";
402 file.fd = AT_FDCWD;
403 ret = NXT_OK;
404 }
405
406 if (nxt_fast_path(ret == NXT_OK)) {
407 nxt_file_t af;
408
409 af = file;
410 nxt_memzero(&file, sizeof(nxt_file_t));
411 file.name = fname;
412
413 ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
414 NXT_FILE_OPEN, 0, af.fd, resolve);
415
416 if (af.fd != AT_FDCWD) {
417 nxt_file_close(task, &af);
418 }
419 }
420
421 } else {
422 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
423 }
424
425#else
426 ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
427#endif
428
429 if (nxt_slow_path(ret != NXT_OK)) {
430
431 switch (file.error) {
432
433 /*
434 * For Unix domain sockets "errno" is set to:
435 * - ENXIO on Linux;
436 * - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
437 */
438
439 case NXT_ENOENT:
440 case NXT_ENOTDIR:
441 case NXT_ENAMETOOLONG:
442#if (NXT_LINUX)
443 case NXT_ENXIO:
444#else
445 case NXT_EOPNOTSUPP:
446#endif
447 level = NXT_LOG_ERR;
448 status = NXT_HTTP_NOT_FOUND;
449 break;
450
451 case NXT_EACCES:
452#if (NXT_HAVE_OPENAT2)
453 case NXT_ELOOP:
454 case NXT_EXDEV:
455#endif
456 level = NXT_LOG_ERR;
457 status = NXT_HTTP_FORBIDDEN;
458 break;
459
460 default:
461 level = NXT_LOG_ALERT;
462 status = NXT_HTTP_INTERNAL_SERVER_ERROR;
463 break;
464 }
465
466 if (status != NXT_HTTP_NOT_FOUND) {
467#if (NXT_HAVE_OPENAT2)
468 nxt_str_t *chr = &ctx->chroot;
469
470 if (chr->length > 0) {
471 nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
472 fname, chr, file.error);
473
474 } else {
475 nxt_log(task, level, "opening \"%s\" failed %E",
476 fname, file.error);
477 }
478
479#else
480 nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
481#endif
482 }
483
484 if (level == NXT_LOG_ERR) {
485 nxt_http_static_next(task, r, ctx, status);
486 return;
487 }
488
489 goto fail;
490 }
491
492 f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
493 if (nxt_slow_path(f == NULL)) {
494 nxt_file_close(task, &file);
495 goto fail;
496 }
497
498 *f = file;
499
500 ret = nxt_file_info(f, &fi);
501 if (nxt_slow_path(ret != NXT_OK)) {
502 goto fail;
503 }
504
505 if (nxt_fast_path(nxt_is_file(&fi))) {
506 r->status = NXT_HTTP_OK;
507 r->resp.content_length_n = nxt_file_size(&fi);
508
509 field = nxt_list_zero_add(r->resp.fields);
510 if (nxt_slow_path(field == NULL)) {
511 goto fail;
512 }
513
514 nxt_http_field_name_set(field, "Last-Modified");
515
516 p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
517 if (nxt_slow_path(p == NULL)) {
518 goto fail;
519 }
520
521 nxt_localtime(nxt_file_mtime(&fi), &tm);
522
523 field->value = p;
524 field->value_length = nxt_http_date(p, &tm) - p;
525
526 field = nxt_list_zero_add(r->resp.fields);
527 if (nxt_slow_path(field == NULL)) {
528 goto fail;
529 }
530
531 nxt_http_field_name_set(field, "ETag");
532
533 length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
534
535 p = nxt_mp_nget(r->mem_pool, length);
536 if (nxt_slow_path(p == NULL)) {
537 goto fail;
538 }
539
540 field->value = p;
541 field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
542 nxt_file_mtime(&fi),
543 nxt_file_size(&fi))
544 - p;
545
546 if (exten.start == NULL) {
547 nxt_http_static_extract_extension(shr, &exten);
548 }
549
550 if (mtype == NULL) {
551 mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
552 }
553
554 if (mtype->length != 0) {
555 field = nxt_list_zero_add(r->resp.fields);
556 if (nxt_slow_path(field == NULL)) {
557 goto fail;
558 }
559
560 nxt_http_field_name_set(field, "Content-Type");
561
562 field->value = mtype->start;
563 field->value_length = mtype->length;
564 }
565
566 if (ctx->need_body && nxt_file_size(&fi) > 0) {
567 fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
568 if (nxt_slow_path(fb == NULL)) {
569 goto fail;
570 }
571
572 fb->file = f;
573 fb->file_end = nxt_file_size(&fi);
574
575 r->out = fb;
576
577 body_handler = &nxt_http_static_body_handler;
578
579 } else {
580 nxt_file_close(task, f);
581 body_handler = NULL;
582 }
583
584 } else {
585 /* Not a file. */
586 nxt_file_close(task, f);
587
588 if (nxt_slow_path(!nxt_is_dir(&fi)
589 || shr->start[shr->length - 1] == '/'))
590 {
591 nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
592 f->name);
593
594 nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
595 return;
596 }
597
598 f = NULL;
599
600 r->status = NXT_HTTP_MOVED_PERMANENTLY;
601 r->resp.content_length_n = 0;
602
603 field = nxt_list_zero_add(r->resp.fields);
604 if (nxt_slow_path(field == NULL)) {
605 goto fail;
606 }
607
608 nxt_http_field_name_set(field, "Location");
609
610 encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
611 length = r->path->length + encode * 2 + 1;
612
613 if (r->args->length > 0) {
614 length += 1 + r->args->length;
615 }
616
617 p = nxt_mp_nget(r->mem_pool, length);
618 if (nxt_slow_path(p == NULL)) {
619 goto fail;
620 }
621
622 field->value = p;
623 field->value_length = length;
624
625 if (encode > 0) {
626 p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
627
628 } else {
629 p = nxt_cpymem(p, r->path->start, r->path->length);
630 }
631
632 *p++ = '/';
633
634 if (r->args->length > 0) {
635 *p++ = '?';
636 nxt_memcpy(p, r->args->start, r->args->length);
637 }
638
639 body_handler = NULL;
640 }
641
642 nxt_http_request_header_send(task, r, body_handler, NULL);
643
644 r->state = &nxt_http_static_send_state;
645 return;
646
647fail:
648
649 if (f != NULL) {
650 nxt_file_close(task, f);
651 }
652
653 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
654}
655
656
657static void
658nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data)
659{
660 nxt_http_request_t *r;
661
662 r = obj;
663
664 nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
665}
666
667
668static void
669nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
670 nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
671{
672 nxt_http_action_t *action;
673 nxt_http_static_conf_t *conf;
674
675 action = ctx->action;
676 conf = action->u.conf;
677
678 ctx->share_idx++;
679
680 if (ctx->share_idx < conf->nshares) {
681 nxt_http_static_iterate(task, r, ctx);
682 return;
683 }
684
685 if (action->fallback != NULL) {
686 nxt_http_request_action(task, r, action->fallback);
687 return;
688 }
689
690 nxt_http_request_error(task, r, status);
691}
692
693
694#if (NXT_HAVE_OPENAT2)
695
696static u_char *
697nxt_http_static_chroot_match(u_char *chr, u_char *shr)
698{
699 if (*chr != *shr) {
700 return NULL;
701 }
702
703 chr++;
704 shr++;
705
706 for ( ;; ) {
707 if (*shr == '\0') {
708 return NULL;
709 }
710
711 if (*chr == *shr) {
712 chr++;
713 shr++;
714 continue;
715 }
716
717 if (*chr == '\0') {
718 break;
719 }
720
721 if (*chr == '/') {
722 if (chr[-1] == '/') {
723 chr++;
724 continue;
725 }
726
727 } else if (*shr == '/') {
728 if (shr[-1] == '/') {
729 shr++;
730 continue;
731 }
732 }
733
734 return NULL;
735 }
736
737 if (shr[-1] != '/' && *shr != '/') {
738 return NULL;
739 }
740
741 while (*shr == '/') {
742 shr++;
743 }
744
745 return (*shr != '\0') ? shr : NULL;
746}
747
748#endif
749
750
751static void
752nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten)
753{
754 u_char ch, *p, *end;
755
756 end = path->start + path->length;
757 p = end;
758
759 while (p > path->start) {
760 p--;
761 ch = *p;
762
763 switch (ch) {
764 case '/':
765 p++;
766 /* Fall through. */
767 case '.':
768 goto extension;
769 }
770 }
771
772extension:
773
774 exten->length = end - p;
775 exten->start = p;
776}
777
778
779static void
780nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
781{
782 size_t alloc;
783 nxt_buf_t *fb, *b, **next, *out;
784 nxt_off_t rest;
785 nxt_int_t n;
786 nxt_work_queue_t *wq;
787 nxt_http_request_t *r;
788
789 r = obj;
790 fb = r->out;
791
792 rest = fb->file_end - fb->file_pos;
793 out = NULL;
794 next = &out;
795 n = 0;
796
797 do {
798 alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
799
800 b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
801 if (nxt_slow_path(b == NULL)) {
802 goto fail;
803 }
804
805 b->completion_handler = nxt_http_static_buf_completion;
806 b->parent = r;
807
808 nxt_mp_retain(r->mem_pool);
809
810 *next = b;
811 next = &b->next;
812
813 rest -= alloc;
814
815 } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
816
817 wq = &task->thread->engine->fast_work_queue;
818
819 nxt_sendbuf_drain(task, wq, out);
820 return;
821
822fail:
823
824 while (out != NULL) {
825 b = out;
826 out = b->next;
827
828 nxt_mp_free(r->mem_pool, b);
829 nxt_mp_release(r->mem_pool);
830 }
831}
832
833
834static const nxt_http_request_state_t nxt_http_static_send_state
835 nxt_aligned(64) =
836{
837 .error_handler = nxt_http_request_error_handler,
838};
839
840
841static void
842nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
843{
844 ssize_t n, size;
845 nxt_buf_t *b, *fb, *next;
846 nxt_off_t rest;
847 nxt_http_request_t *r;
848
849 b = obj;
850 r = data;
851
852complete_buf:
853
854 fb = r->out;
855
856 if (nxt_slow_path(fb == NULL || r->error)) {
857 goto clean;
858 }
859
860 rest = fb->file_end - fb->file_pos;
861 size = nxt_buf_mem_size(&b->mem);
862
863 size = nxt_min(rest, (nxt_off_t) size);
864
865 n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
866
867 if (n != size) {
868 if (n >= 0) {
869 nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
870 "while sending response to a client", fb->file->name);
871 }
872
873 nxt_http_request_error_handler(task, r, r->proto.any);
874 goto clean;
875 }
876
877 next = b->next;
878
879 if (n == rest) {
880 nxt_file_close(task, fb->file);
881 r->out = NULL;
882
883 b->next = nxt_http_buf_last(r);
884
885 } else {
886 fb->file_pos += n;
887 b->next = NULL;
888 }
889
890 b->mem.pos = b->mem.start;
891 b->mem.free = b->mem.pos + n;
892
893 nxt_http_request_send(task, r, b);
894
895 if (next != NULL) {
896 b = next;
897 goto complete_buf;
898 }
899
900 return;
901
902clean:
903
904 do {
905 next = b->next;
906
907 nxt_mp_free(r->mem_pool, b);
908 nxt_mp_release(r->mem_pool);
909
910 b = next;
911 } while (b != NULL);
912
913 if (fb != NULL) {
914 nxt_file_close(task, fb->file);
915 r->out = NULL;
916 }
917}
918
919
920nxt_int_t
921nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
922{
923 nxt_str_t *type, exten;
924 nxt_int_t ret;
925 nxt_uint_t i;
926
927 static const struct {
928 nxt_str_t type;
929 const char *exten;
930 } default_types[] = {
931
932 { nxt_string("text/html"), ".html" },
933 { nxt_string("text/html"), ".htm" },
934 { nxt_string("text/css"), ".css" },
935
936 { nxt_string("image/svg+xml"), ".svg" },
937 { nxt_string("image/webp"), ".webp" },
938 { nxt_string("image/png"), ".png" },
939 { nxt_string("image/apng"), ".apng" },
940 { nxt_string("image/jpeg"), ".jpeg" },
941 { nxt_string("image/jpeg"), ".jpg" },
942 { nxt_string("image/gif"), ".gif" },
943 { nxt_string("image/x-icon"), ".ico" },
944
945 { nxt_string("image/avif"), ".avif" },
946 { nxt_string("image/avif-sequence"), ".avifs" },
947
948 { nxt_string("font/woff"), ".woff" },
949 { nxt_string("font/woff2"), ".woff2" },
950 { nxt_string("font/otf"), ".otf" },
951 { nxt_string("font/ttf"), ".ttf" },
952
953 { nxt_string("text/plain"), ".txt" },
954 { nxt_string("text/markdown"), ".md" },
955 { nxt_string("text/x-rst"), ".rst" },
956
957 { nxt_string("application/javascript"), ".js" },
958 { nxt_string("application/json"), ".json" },
959 { nxt_string("application/xml"), ".xml" },
960 { nxt_string("application/rss+xml"), ".rss" },
961 { nxt_string("application/atom+xml"), ".atom" },
962 { nxt_string("application/pdf"), ".pdf" },
963
964 { nxt_string("application/zip"), ".zip" },
965
966 { nxt_string("audio/mpeg"), ".mp3" },
967 { nxt_string("audio/ogg"), ".ogg" },
968 { nxt_string("audio/midi"), ".midi" },
969 { nxt_string("audio/midi"), ".mid" },
970 { nxt_string("audio/flac"), ".flac" },
971 { nxt_string("audio/aac"), ".aac" },
972 { nxt_string("audio/wav"), ".wav" },
973
974 { nxt_string("video/mpeg"), ".mpeg" },
975 { nxt_string("video/mpeg"), ".mpg" },
976 { nxt_string("video/mp4"), ".mp4" },
977 { nxt_string("video/webm"), ".webm" },
978 { nxt_string("video/x-msvideo"), ".avi" },
979
980 { nxt_string("application/octet-stream"), ".exe" },
981 { nxt_string("application/octet-stream"), ".bin" },
982 { nxt_string("application/octet-stream"), ".dll" },
983 { nxt_string("application/octet-stream"), ".iso" },
984 { nxt_string("application/octet-stream"), ".img" },
985 { nxt_string("application/octet-stream"), ".msi" },
986
987 { nxt_string("application/octet-stream"), ".deb" },
988 { nxt_string("application/octet-stream"), ".rpm" },
989
990 { nxt_string("application/x-httpd-php"), ".php" },
991 };
992
993 for (i = 0; i < nxt_nitems(default_types); i++) {
994 type = (nxt_str_t *) &default_types[i].type;
995
996 exten.start = (u_char *) default_types[i].exten;
997 exten.length = nxt_strlen(exten.start);
998
999 ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
1000 if (nxt_slow_path(ret != NXT_OK)) {
1001 return NXT_ERROR;
1002 }
1003 }
1004
1005 return NXT_OK;
1006}
1007
1008
1009static const nxt_lvlhsh_proto_t nxt_http_static_mtypes_hash_proto
1010 nxt_aligned(64) =
1011{
1012 NXT_LVLHSH_DEFAULT,
1013 nxt_http_static_mtypes_hash_test,
1014 nxt_http_static_mtypes_hash_alloc,
1015 nxt_http_static_mtypes_hash_free,
1016};
1017
1018
1019typedef struct {
1020 nxt_str_t exten;
1021 nxt_str_t *type;
1022} nxt_http_static_mtype_t;
1023
1024
1025nxt_int_t
1026nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1027 nxt_str_t *exten, nxt_str_t *type)
1027 const nxt_str_t *exten, nxt_str_t *type)
1028{
1029 nxt_lvlhsh_query_t lhq;
1030 nxt_http_static_mtype_t *mtype;
1031
1032 mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
1033 if (nxt_slow_path(mtype == NULL)) {
1034 return NXT_ERROR;
1035 }
1036
1037 mtype->exten = *exten;
1038 mtype->type = type;
1039
1040 lhq.key = *exten;
1041 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1042 lhq.replace = 1;
1043 lhq.value = mtype;
1044 lhq.proto = &nxt_http_static_mtypes_hash_proto;
1045 lhq.pool = mp;
1046
1047 return nxt_lvlhsh_insert(hash, &lhq);
1048}
1049
1050
1051nxt_str_t *
1028{
1029 nxt_lvlhsh_query_t lhq;
1030 nxt_http_static_mtype_t *mtype;
1031
1032 mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
1033 if (nxt_slow_path(mtype == NULL)) {
1034 return NXT_ERROR;
1035 }
1036
1037 mtype->exten = *exten;
1038 mtype->type = type;
1039
1040 lhq.key = *exten;
1041 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1042 lhq.replace = 1;
1043 lhq.value = mtype;
1044 lhq.proto = &nxt_http_static_mtypes_hash_proto;
1045 lhq.pool = mp;
1046
1047 return nxt_lvlhsh_insert(hash, &lhq);
1048}
1049
1050
1051nxt_str_t *
1052nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, nxt_str_t *exten)
1052nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, const nxt_str_t *exten)
1053{
1054 nxt_lvlhsh_query_t lhq;
1055 nxt_http_static_mtype_t *mtype;
1056
1057 static nxt_str_t empty = nxt_string("");
1058
1059 lhq.key = *exten;
1060 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1061 lhq.proto = &nxt_http_static_mtypes_hash_proto;
1062
1063 if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
1064 mtype = lhq.value;
1065 return mtype->type;
1066 }
1067
1068 return &empty;
1069}
1070
1071
1072static nxt_int_t
1073nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1074{
1075 nxt_http_static_mtype_t *mtype;
1076
1077 mtype = data;
1078
1079 return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
1080}
1081
1082
1083static void *
1084nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1085{
1086 return nxt_mp_align(data, size, size);
1087}
1088
1089
1090static void
1091nxt_http_static_mtypes_hash_free(void *data, void *p)
1092{
1093 nxt_mp_free(data, p);
1094}
1053{
1054 nxt_lvlhsh_query_t lhq;
1055 nxt_http_static_mtype_t *mtype;
1056
1057 static nxt_str_t empty = nxt_string("");
1058
1059 lhq.key = *exten;
1060 lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1061 lhq.proto = &nxt_http_static_mtypes_hash_proto;
1062
1063 if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
1064 mtype = lhq.value;
1065 return mtype->type;
1066 }
1067
1068 return &empty;
1069}
1070
1071
1072static nxt_int_t
1073nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1074{
1075 nxt_http_static_mtype_t *mtype;
1076
1077 mtype = data;
1078
1079 return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
1080}
1081
1082
1083static void *
1084nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1085{
1086 return nxt_mp_align(data, size, size);
1087}
1088
1089
1090static void
1091nxt_http_static_mtypes_hash_free(void *data, void *p)
1092{
1093 nxt_mp_free(data, p);
1094}