xref: /unit/src/nxt_http_static.c (revision 1961:69d823e5710a)
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                    index;
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
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_bool_t              array;
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     array = (nxt_conf_type(acf->share) == NXT_CONF_ARRAY);
95     conf->nshares = array ? nxt_conf_array_elements_count(acf->share) : 1;
96 
97     conf->shares = nxt_mp_zget(mp, sizeof(nxt_http_static_share_t)
98                                    * conf->nshares);
99     if (nxt_slow_path(conf->shares == NULL)) {
100         return NXT_ERROR;
101     }
102 
103     if (array) {
104         for (i = 0; i < conf->nshares; i++) {
105             cv = nxt_conf_get_array_element(acf->share, i);
106             nxt_conf_get_string(cv, &str);
107 
108             var = nxt_var_compile(&str, mp, 1);
109             if (nxt_slow_path(var == NULL)) {
110                 return NXT_ERROR;
111             }
112 
113             conf->shares[i].var = var;
114             conf->shares[i].is_const = nxt_var_is_const(var);
115         }
116 
117     } else {
118         nxt_conf_get_string(acf->share, &str);
119 
120         var = nxt_var_compile(&str, mp, 1);
121         if (nxt_slow_path(var == NULL)) {
122             return NXT_ERROR;
123         }
124 
125         conf->shares[0].var = var;
126         conf->shares[0].is_const = nxt_var_is_const(var);
127     }
128 
129 #if (NXT_HAVE_OPENAT2)
130     if (acf->chroot.length > 0) {
131         nxt_str_t   chr, shr;
132         nxt_bool_t  is_const;
133 
134         conf->chroot = nxt_var_compile(&acf->chroot, mp, 1);
135         if (nxt_slow_path(conf->chroot == NULL)) {
136             return NXT_ERROR;
137         }
138 
139         is_const = nxt_var_is_const(conf->chroot);
140 
141         for (i = 0; i < conf->nshares; i++) {
142             conf->shares[i].is_const &= is_const;
143 
144             if (conf->shares[i].is_const) {
145                 nxt_var_raw(conf->chroot, &chr);
146                 nxt_var_raw(conf->shares[i].var, &shr);
147 
148                 conf->shares[i].fname = nxt_http_static_chroot_match(chr.start,
149                                                                      shr.start);
150             }
151         }
152     }
153 
154     if (acf->follow_symlinks != NULL
155         && !nxt_conf_get_boolean(acf->follow_symlinks))
156     {
157         conf->resolve |= RESOLVE_NO_SYMLINKS;
158     }
159 
160     if (acf->traverse_mounts != NULL
161         && !nxt_conf_get_boolean(acf->traverse_mounts))
162     {
163         conf->resolve |= RESOLVE_NO_XDEV;
164     }
165 #endif
166 
167     if (acf->types != NULL) {
168         conf->types = nxt_http_route_types_rule_create(task, mp, acf->types);
169         if (nxt_slow_path(conf->types == NULL)) {
170             return NXT_ERROR;
171         }
172     }
173 
174     if (acf->fallback != NULL) {
175         action->fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t));
176         if (nxt_slow_path(action->fallback == NULL)) {
177             return NXT_ERROR;
178         }
179 
180         return nxt_http_action_init(task, tmcf, acf->fallback,
181                                     action->fallback);
182     }
183 
184     return NXT_OK;
185 }
186 
187 
188 static nxt_http_action_t *
189 nxt_http_static(nxt_task_t *task, nxt_http_request_t *r,
190     nxt_http_action_t *action)
191 {
192     nxt_bool_t             need_body;
193     nxt_http_static_ctx_t  *ctx;
194 
195     if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
196 
197         if (!nxt_str_eq(r->method, "HEAD", 4)) {
198             if (action->fallback != NULL) {
199                 return action->fallback;
200             }
201 
202             nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
203             return NULL;
204         }
205 
206         need_body = 0;
207 
208     } else {
209         need_body = 1;
210     }
211 
212     ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
213     if (nxt_slow_path(ctx == NULL)) {
214         nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
215         return NULL;
216     }
217 
218     ctx->action = action;
219     ctx->need_body = need_body;
220 
221     nxt_http_static_iterate(task, r, ctx);
222 
223     return NULL;
224 }
225 
226 
227 static void
228 nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
229     nxt_http_static_ctx_t *ctx)
230 {
231     nxt_int_t                ret;
232     nxt_http_static_conf_t   *conf;
233     nxt_http_static_share_t  *share;
234 
235     conf = ctx->action->u.conf;
236 
237     share = &conf->shares[ctx->index];
238 
239 #if (NXT_DEBUG)
240     nxt_str_t  shr;
241 
242     nxt_var_raw(share->var, &shr);
243 
244 #if (NXT_HAVE_OPENAT2)
245     nxt_str_t  chr;
246 
247     if (conf->chroot != NULL) {
248         nxt_var_raw(conf->chroot, &chr);
249 
250     } else {
251         nxt_str_set(&chr, "");
252     }
253 
254     nxt_debug(task, "http static: \"%V\" (chroot: \"%V\")", &shr, &chr);
255 #else
256     nxt_debug(task, "http static: \"%V\"", &shr);
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->index == 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->index == 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, 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     static const nxt_str_t  index = nxt_string("index.html");
315 
316     r = obj;
317     ctx = data;
318     action = ctx->action;
319     conf = action->u.conf;
320     rtcf = r->conf->socket_conf->router_conf;
321 
322     f = NULL;
323     mtype = NULL;
324     status = NXT_HTTP_INTERNAL_SERVER_ERROR;
325 
326     shr = &ctx->share;
327 
328     if (shr->start[shr->length - 1] == '/') {
329         /* TODO: dynamic index setting. */
330         nxt_str_set(&exten, ".html");
331 
332         length = shr->length + index.length;
333 
334         fname = nxt_mp_nget(r->mem_pool, length + 1);
335         if (nxt_slow_path(fname == NULL)) {
336             goto fail;
337         }
338 
339         p = fname;
340         p = nxt_cpymem(p, shr->start, shr->length);
341         p = nxt_cpymem(p, index.start, index.length);
342         *p = '\0';
343 
344     } else {
345         if (conf->types == NULL) {
346             nxt_str_null(&exten);
347 
348         } else {
349             nxt_http_static_extract_extension(shr, &exten);
350             mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
351 
352             ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
353                                            mtype->length);
354             if (nxt_slow_path(ret == NXT_ERROR)) {
355                 goto fail;
356             }
357 
358             if (ret == 0) {
359                 nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
360                 return;
361             }
362         }
363 
364         fname = ctx->share.start;
365     }
366 
367     nxt_memzero(&file, sizeof(nxt_file_t));
368 
369     file.name = fname;
370 
371 #if (NXT_HAVE_OPENAT2)
372     if (conf->resolve != 0 || ctx->chroot.length > 0) {
373         nxt_str_t                *chr;
374         nxt_uint_t               resolve;
375         nxt_http_static_share_t  *share;
376 
377         share = &conf->shares[ctx->index];
378 
379         resolve = conf->resolve;
380         chr = &ctx->chroot;
381 
382         if (chr->length > 0) {
383             resolve |= RESOLVE_IN_ROOT;
384 
385             fname = share->is_const
386                     ? share->fname
387                     : nxt_http_static_chroot_match(chr->start, file.name);
388 
389             if (fname != NULL) {
390                 file.name = chr->start;
391                 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
392                                     0);
393 
394             } else {
395                 file.error = NXT_EACCES;
396                 ret = NXT_ERROR;
397             }
398 
399         } else if (fname[0] == '/') {
400             file.name = (u_char *) "/";
401             ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
402 
403         } else {
404             file.name = (u_char *) ".";
405             file.fd = AT_FDCWD;
406             ret = NXT_OK;
407         }
408 
409         if (nxt_fast_path(ret == NXT_OK)) {
410             nxt_file_t  af;
411 
412             af = file;
413             nxt_memzero(&file, sizeof(nxt_file_t));
414             file.name = fname;
415 
416             ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
417                                    NXT_FILE_OPEN, 0, af.fd, resolve);
418 
419             if (af.fd != AT_FDCWD) {
420                 nxt_file_close(task, &af);
421             }
422         }
423 
424     } else {
425         ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
426     }
427 
428 #else
429     ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
430 #endif
431 
432     if (nxt_slow_path(ret != NXT_OK)) {
433 
434         switch (file.error) {
435 
436         /*
437          * For Unix domain sockets "errno" is set to:
438          *  - ENXIO on Linux;
439          *  - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
440          */
441 
442         case NXT_ENOENT:
443         case NXT_ENOTDIR:
444         case NXT_ENAMETOOLONG:
445 #if (NXT_LINUX)
446         case NXT_ENXIO:
447 #else
448         case NXT_EOPNOTSUPP:
449 #endif
450             level = NXT_LOG_ERR;
451             status = NXT_HTTP_NOT_FOUND;
452             break;
453 
454         case NXT_EACCES:
455 #if (NXT_HAVE_OPENAT2)
456         case NXT_ELOOP:
457         case NXT_EXDEV:
458 #endif
459             level = NXT_LOG_ERR;
460             status = NXT_HTTP_FORBIDDEN;
461             break;
462 
463         default:
464             level = NXT_LOG_ALERT;
465             status = NXT_HTTP_INTERNAL_SERVER_ERROR;
466             break;
467         }
468 
469         if (status != NXT_HTTP_NOT_FOUND) {
470 #if (NXT_HAVE_OPENAT2)
471             nxt_str_t  *chr = &ctx->chroot;
472 
473             if (chr->length > 0) {
474                 nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
475                         fname, chr, file.error);
476 
477             } else {
478                 nxt_log(task, level, "opening \"%s\" failed %E",
479                         fname, file.error);
480             }
481 
482 #else
483             nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
484 #endif
485         }
486 
487         if (level == NXT_LOG_ERR) {
488             nxt_http_static_next(task, r, ctx, status);
489             return;
490         }
491 
492         goto fail;
493     }
494 
495     f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
496     if (nxt_slow_path(f == NULL)) {
497         goto fail;
498     }
499 
500     *f = file;
501 
502     ret = nxt_file_info(f, &fi);
503     if (nxt_slow_path(ret != NXT_OK)) {
504         goto fail;
505     }
506 
507     if (nxt_fast_path(nxt_is_file(&fi))) {
508         r->status = NXT_HTTP_OK;
509         r->resp.content_length_n = nxt_file_size(&fi);
510 
511         field = nxt_list_zero_add(r->resp.fields);
512         if (nxt_slow_path(field == NULL)) {
513             goto fail;
514         }
515 
516         nxt_http_field_name_set(field, "Last-Modified");
517 
518         p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
519         if (nxt_slow_path(p == NULL)) {
520             goto fail;
521         }
522 
523         nxt_localtime(nxt_file_mtime(&fi), &tm);
524 
525         field->value = p;
526         field->value_length = nxt_http_date(p, &tm) - p;
527 
528         field = nxt_list_zero_add(r->resp.fields);
529         if (nxt_slow_path(field == NULL)) {
530             goto fail;
531         }
532 
533         nxt_http_field_name_set(field, "ETag");
534 
535         length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
536 
537         p = nxt_mp_nget(r->mem_pool, length);
538         if (nxt_slow_path(p == NULL)) {
539             goto fail;
540         }
541 
542         field->value = p;
543         field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
544                                           nxt_file_mtime(&fi),
545                                           nxt_file_size(&fi))
546                               - p;
547 
548         if (exten.start == NULL) {
549             nxt_http_static_extract_extension(shr, &exten);
550         }
551 
552         if (mtype == NULL) {
553             mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
554         }
555 
556         if (mtype->length != 0) {
557             field = nxt_list_zero_add(r->resp.fields);
558             if (nxt_slow_path(field == NULL)) {
559                 goto fail;
560             }
561 
562             nxt_http_field_name_set(field, "Content-Type");
563 
564             field->value = mtype->start;
565             field->value_length = mtype->length;
566         }
567 
568         if (ctx->need_body && nxt_file_size(&fi) > 0) {
569             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
570             if (nxt_slow_path(fb == NULL)) {
571                 goto fail;
572             }
573 
574             fb->file = f;
575             fb->file_end = nxt_file_size(&fi);
576 
577             r->out = fb;
578 
579             body_handler = &nxt_http_static_body_handler;
580 
581         } else {
582             nxt_file_close(task, f);
583             body_handler = NULL;
584         }
585 
586     } else {
587         /* Not a file. */
588         nxt_file_close(task, f);
589 
590         if (nxt_slow_path(!nxt_is_dir(&fi))) {
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->index++;
679 
680     if (ctx->index < 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     for ( ;; ) {
760         /* There's always '/' in the beginning of the request path. */
761 
762         p--;
763         ch = *p;
764 
765         switch (ch) {
766         case '/':
767             p++;
768             /* Fall through. */
769         case '.':
770             exten->length = end - p;
771             exten->start = p;
772             return;
773         }
774     }
775 }
776 
777 
778 static void
779 nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
780 {
781     size_t              alloc;
782     nxt_buf_t           *fb, *b, **next, *out;
783     nxt_off_t           rest;
784     nxt_int_t           n;
785     nxt_work_queue_t    *wq;
786     nxt_http_request_t  *r;
787 
788     r = obj;
789     fb = r->out;
790 
791     rest = fb->file_end - fb->file_pos;
792     out = NULL;
793     next = &out;
794     n = 0;
795 
796     do {
797         alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
798 
799         b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
800         if (nxt_slow_path(b == NULL)) {
801             goto fail;
802         }
803 
804         b->completion_handler = nxt_http_static_buf_completion;
805         b->parent = r;
806 
807         nxt_mp_retain(r->mem_pool);
808 
809         *next = b;
810         next = &b->next;
811 
812         rest -= alloc;
813 
814     } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
815 
816     wq = &task->thread->engine->fast_work_queue;
817 
818     nxt_sendbuf_drain(task, wq, out);
819     return;
820 
821 fail:
822 
823     while (out != NULL) {
824         b = out;
825         out = b->next;
826 
827         nxt_mp_free(r->mem_pool, b);
828         nxt_mp_release(r->mem_pool);
829     }
830 }
831 
832 
833 static const nxt_http_request_state_t  nxt_http_static_send_state
834     nxt_aligned(64) =
835 {
836     .error_handler = nxt_http_request_error_handler,
837 };
838 
839 
840 static void
841 nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
842 {
843     ssize_t             n, size;
844     nxt_buf_t           *b, *fb, *next;
845     nxt_off_t           rest;
846     nxt_http_request_t  *r;
847 
848     b = obj;
849     r = data;
850 
851 complete_buf:
852 
853     fb = r->out;
854 
855     if (nxt_slow_path(fb == NULL || r->error)) {
856         goto clean;
857     }
858 
859     rest = fb->file_end - fb->file_pos;
860     size = nxt_buf_mem_size(&b->mem);
861 
862     size = nxt_min(rest, (nxt_off_t) size);
863 
864     n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
865 
866     if (n != size) {
867         if (n >= 0) {
868             nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
869                     "while sending response to a client", fb->file->name);
870         }
871 
872         nxt_http_request_error_handler(task, r, r->proto.any);
873         goto clean;
874     }
875 
876     next = b->next;
877 
878     if (n == rest) {
879         nxt_file_close(task, fb->file);
880         r->out = NULL;
881 
882         b->next = nxt_http_buf_last(r);
883 
884     } else {
885         fb->file_pos += n;
886         b->next = NULL;
887     }
888 
889     b->mem.pos = b->mem.start;
890     b->mem.free = b->mem.pos + n;
891 
892     nxt_http_request_send(task, r, b);
893 
894     if (next != NULL) {
895         b = next;
896         goto complete_buf;
897     }
898 
899     return;
900 
901 clean:
902 
903     do {
904         next = b->next;
905 
906         nxt_mp_free(r->mem_pool, b);
907         nxt_mp_release(r->mem_pool);
908 
909         b = next;
910     } while (b != NULL);
911 
912     if (fb != NULL) {
913         nxt_file_close(task, fb->file);
914         r->out = NULL;
915     }
916 }
917 
918 
919 nxt_int_t
920 nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
921 {
922     nxt_str_t   *type, exten;
923     nxt_int_t   ret;
924     nxt_uint_t  i;
925 
926     static const struct {
927         nxt_str_t   type;
928         const char  *exten;
929     } default_types[] = {
930 
931         { nxt_string("text/html"),      ".html"  },
932         { nxt_string("text/html"),      ".htm"   },
933         { nxt_string("text/css"),       ".css"   },
934 
935         { nxt_string("image/svg+xml"),  ".svg"   },
936         { nxt_string("image/webp"),     ".webp"  },
937         { nxt_string("image/png"),      ".png"   },
938         { nxt_string("image/apng"),     ".apng"  },
939         { nxt_string("image/jpeg"),     ".jpeg"  },
940         { nxt_string("image/jpeg"),     ".jpg"   },
941         { nxt_string("image/gif"),      ".gif"   },
942         { nxt_string("image/x-icon"),   ".ico"   },
943 
944         { nxt_string("image/avif"),           ".avif"  },
945         { nxt_string("image/avif-sequence"),  ".avifs" },
946 
947         { nxt_string("font/woff"),      ".woff"  },
948         { nxt_string("font/woff2"),     ".woff2" },
949         { nxt_string("font/otf"),       ".otf"   },
950         { nxt_string("font/ttf"),       ".ttf"   },
951 
952         { nxt_string("text/plain"),     ".txt"   },
953         { nxt_string("text/markdown"),  ".md"    },
954         { nxt_string("text/x-rst"),     ".rst"   },
955 
956         { nxt_string("application/javascript"),  ".js"   },
957         { nxt_string("application/json"),        ".json" },
958         { nxt_string("application/xml"),         ".xml"  },
959         { nxt_string("application/rss+xml"),     ".rss"  },
960         { nxt_string("application/atom+xml"),    ".atom" },
961         { nxt_string("application/pdf"),         ".pdf"  },
962 
963         { nxt_string("application/zip"),         ".zip"  },
964 
965         { nxt_string("audio/mpeg"),       ".mp3"  },
966         { nxt_string("audio/ogg"),        ".ogg"  },
967         { nxt_string("audio/midi"),       ".midi" },
968         { nxt_string("audio/midi"),       ".mid"  },
969         { nxt_string("audio/flac"),       ".flac" },
970         { nxt_string("audio/aac"),        ".aac"  },
971         { nxt_string("audio/wav"),        ".wav"  },
972 
973         { nxt_string("video/mpeg"),       ".mpeg" },
974         { nxt_string("video/mpeg"),       ".mpg"  },
975         { nxt_string("video/mp4"),        ".mp4"  },
976         { nxt_string("video/webm"),       ".webm" },
977         { nxt_string("video/x-msvideo"),  ".avi"  },
978 
979         { nxt_string("application/octet-stream"),  ".exe" },
980         { nxt_string("application/octet-stream"),  ".bin" },
981         { nxt_string("application/octet-stream"),  ".dll" },
982         { nxt_string("application/octet-stream"),  ".iso" },
983         { nxt_string("application/octet-stream"),  ".img" },
984         { nxt_string("application/octet-stream"),  ".msi" },
985 
986         { nxt_string("application/octet-stream"),  ".deb" },
987         { nxt_string("application/octet-stream"),  ".rpm" },
988 
989         { nxt_string("application/x-httpd-php"),   ".php" },
990     };
991 
992     for (i = 0; i < nxt_nitems(default_types); i++) {
993         type = (nxt_str_t *) &default_types[i].type;
994 
995         exten.start = (u_char *) default_types[i].exten;
996         exten.length = nxt_strlen(exten.start);
997 
998         ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
999         if (nxt_slow_path(ret != NXT_OK)) {
1000             return NXT_ERROR;
1001         }
1002     }
1003 
1004     return NXT_OK;
1005 }
1006 
1007 
1008 static const nxt_lvlhsh_proto_t  nxt_http_static_mtypes_hash_proto
1009     nxt_aligned(64) =
1010 {
1011     NXT_LVLHSH_DEFAULT,
1012     nxt_http_static_mtypes_hash_test,
1013     nxt_http_static_mtypes_hash_alloc,
1014     nxt_http_static_mtypes_hash_free,
1015 };
1016 
1017 
1018 typedef struct {
1019     nxt_str_t  exten;
1020     nxt_str_t  *type;
1021 } nxt_http_static_mtype_t;
1022 
1023 
1024 nxt_int_t
1025 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1026     nxt_str_t *exten, nxt_str_t *type)
1027 {
1028     nxt_lvlhsh_query_t       lhq;
1029     nxt_http_static_mtype_t  *mtype;
1030 
1031     mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
1032     if (nxt_slow_path(mtype == NULL)) {
1033         return NXT_ERROR;
1034     }
1035 
1036     mtype->exten = *exten;
1037     mtype->type = type;
1038 
1039     lhq.key = *exten;
1040     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1041     lhq.replace = 1;
1042     lhq.value = mtype;
1043     lhq.proto = &nxt_http_static_mtypes_hash_proto;
1044     lhq.pool = mp;
1045 
1046     return nxt_lvlhsh_insert(hash, &lhq);
1047 }
1048 
1049 
1050 nxt_str_t *
1051 nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, nxt_str_t *exten)
1052 {
1053     nxt_lvlhsh_query_t       lhq;
1054     nxt_http_static_mtype_t  *mtype;
1055 
1056     static nxt_str_t  empty = nxt_string("");
1057 
1058     lhq.key = *exten;
1059     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1060     lhq.proto = &nxt_http_static_mtypes_hash_proto;
1061 
1062     if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
1063         mtype = lhq.value;
1064         return mtype->type;
1065     }
1066 
1067     return &empty;
1068 }
1069 
1070 
1071 static nxt_int_t
1072 nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1073 {
1074     nxt_http_static_mtype_t  *mtype;
1075 
1076     mtype = data;
1077 
1078     return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
1079 }
1080 
1081 
1082 static void *
1083 nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1084 {
1085     return nxt_mp_align(data, size, size);
1086 }
1087 
1088 
1089 static void
1090 nxt_http_static_mtypes_hash_free(void *data, void *p)
1091 {
1092     nxt_mp_free(data, p);
1093 }
1094