xref: /unit/src/nxt_http_static.c (revision 2137:96f3ac16391b)
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     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 
31 typedef 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 
46 static nxt_http_action_t *nxt_http_static(nxt_task_t *task,
47     nxt_http_request_t *r, nxt_http_action_t *action);
48 static void nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
49     nxt_http_static_ctx_t *ctx);
50 static void nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data);
51 static void nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data);
52 static 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)
55 static u_char *nxt_http_static_chroot_match(u_char *chr, u_char *shr);
56 #endif
57 static void nxt_http_static_extract_extension(nxt_str_t *path,
58     nxt_str_t *exten);
59 static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
60     void *data);
61 static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
62     void *data);
63 
64 static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
65     void *data);
66 static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
67 static void nxt_http_static_mtypes_hash_free(void *data, void *p);
68 
69 
70 static const nxt_http_request_state_t  nxt_http_static_send_state;
71 
72 
73 nxt_int_t
74 nxt_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 
185 static nxt_http_action_t *
186 nxt_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 
224 static void
225 nxt_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 
293 static void
294 nxt_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 
647 fail:
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 
657 static void
658 nxt_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 
668 static void
669 nxt_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 
696 static u_char *
697 nxt_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 
751 static void
752 nxt_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 
772 extension:
773 
774     exten->length = end - p;
775     exten->start = p;
776 }
777 
778 
779 static void
780 nxt_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 
822 fail:
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 
834 static 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 
841 static void
842 nxt_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 
852 complete_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 
902 clean:
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 
920 nxt_int_t
921 nxt_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 
1009 static 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 
1019 typedef struct {
1020     nxt_str_t  exten;
1021     nxt_str_t  *type;
1022 } nxt_http_static_mtype_t;
1023 
1024 
1025 nxt_int_t
1026 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1027     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 
1051 nxt_str_t *
1052 nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, 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 
1072 static nxt_int_t
1073 nxt_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 
1083 static void *
1084 nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1085 {
1086     return nxt_mp_align(data, size, size);
1087 }
1088 
1089 
1090 static void
1091 nxt_http_static_mtypes_hash_free(void *data, void *p)
1092 {
1093     nxt_mp_free(data, p);
1094 }
1095