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