xref: /unit/src/nxt_http_static.c (revision 1962:13542e2a30e3)
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         nxt_file_close(task, &file);
498         goto fail;
499     }
500 
501     *f = file;
502 
503     ret = nxt_file_info(f, &fi);
504     if (nxt_slow_path(ret != NXT_OK)) {
505         goto fail;
506     }
507 
508     if (nxt_fast_path(nxt_is_file(&fi))) {
509         r->status = NXT_HTTP_OK;
510         r->resp.content_length_n = nxt_file_size(&fi);
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, "Last-Modified");
518 
519         p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
520         if (nxt_slow_path(p == NULL)) {
521             goto fail;
522         }
523 
524         nxt_localtime(nxt_file_mtime(&fi), &tm);
525 
526         field->value = p;
527         field->value_length = nxt_http_date(p, &tm) - p;
528 
529         field = nxt_list_zero_add(r->resp.fields);
530         if (nxt_slow_path(field == NULL)) {
531             goto fail;
532         }
533 
534         nxt_http_field_name_set(field, "ETag");
535 
536         length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
537 
538         p = nxt_mp_nget(r->mem_pool, length);
539         if (nxt_slow_path(p == NULL)) {
540             goto fail;
541         }
542 
543         field->value = p;
544         field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
545                                           nxt_file_mtime(&fi),
546                                           nxt_file_size(&fi))
547                               - p;
548 
549         if (exten.start == NULL) {
550             nxt_http_static_extract_extension(shr, &exten);
551         }
552 
553         if (mtype == NULL) {
554             mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
555         }
556 
557         if (mtype->length != 0) {
558             field = nxt_list_zero_add(r->resp.fields);
559             if (nxt_slow_path(field == NULL)) {
560                 goto fail;
561             }
562 
563             nxt_http_field_name_set(field, "Content-Type");
564 
565             field->value = mtype->start;
566             field->value_length = mtype->length;
567         }
568 
569         if (ctx->need_body && nxt_file_size(&fi) > 0) {
570             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
571             if (nxt_slow_path(fb == NULL)) {
572                 goto fail;
573             }
574 
575             fb->file = f;
576             fb->file_end = nxt_file_size(&fi);
577 
578             r->out = fb;
579 
580             body_handler = &nxt_http_static_body_handler;
581 
582         } else {
583             nxt_file_close(task, f);
584             body_handler = NULL;
585         }
586 
587     } else {
588         /* Not a file. */
589         nxt_file_close(task, f);
590 
591         if (nxt_slow_path(!nxt_is_dir(&fi))) {
592             nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
593                     f->name);
594 
595             nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
596             return;
597         }
598 
599         f = NULL;
600 
601         r->status = NXT_HTTP_MOVED_PERMANENTLY;
602         r->resp.content_length_n = 0;
603 
604         field = nxt_list_zero_add(r->resp.fields);
605         if (nxt_slow_path(field == NULL)) {
606             goto fail;
607         }
608 
609         nxt_http_field_name_set(field, "Location");
610 
611         encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
612         length = r->path->length + encode * 2 + 1;
613 
614         if (r->args->length > 0) {
615             length += 1 + r->args->length;
616         }
617 
618         p = nxt_mp_nget(r->mem_pool, length);
619         if (nxt_slow_path(p == NULL)) {
620             goto fail;
621         }
622 
623         field->value = p;
624         field->value_length = length;
625 
626         if (encode > 0) {
627             p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
628 
629         } else {
630             p = nxt_cpymem(p, r->path->start, r->path->length);
631         }
632 
633         *p++ = '/';
634 
635         if (r->args->length > 0) {
636             *p++ = '?';
637             nxt_memcpy(p, r->args->start, r->args->length);
638         }
639 
640         body_handler = NULL;
641     }
642 
643     nxt_http_request_header_send(task, r, body_handler, NULL);
644 
645     r->state = &nxt_http_static_send_state;
646     return;
647 
648 fail:
649 
650     if (f != NULL) {
651         nxt_file_close(task, f);
652     }
653 
654     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
655 }
656 
657 
658 static void
659 nxt_http_static_var_error(nxt_task_t *task, void *obj, void *data)
660 {
661     nxt_http_request_t  *r;
662 
663     r = obj;
664 
665     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
666 }
667 
668 
669 static void
670 nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
671     nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
672 {
673     nxt_http_action_t       *action;
674     nxt_http_static_conf_t  *conf;
675 
676     action = ctx->action;
677     conf = action->u.conf;
678 
679     ctx->index++;
680 
681     if (ctx->index < conf->nshares) {
682         nxt_http_static_iterate(task, r, ctx);
683         return;
684     }
685 
686     if (action->fallback != NULL) {
687         nxt_http_request_action(task, r, action->fallback);
688         return;
689     }
690 
691     nxt_http_request_error(task, r, status);
692 }
693 
694 
695 #if (NXT_HAVE_OPENAT2)
696 
697 static u_char *
698 nxt_http_static_chroot_match(u_char *chr, u_char *shr)
699 {
700     if (*chr != *shr) {
701         return NULL;
702     }
703 
704     chr++;
705     shr++;
706 
707     for ( ;; ) {
708         if (*shr == '\0') {
709             return NULL;
710         }
711 
712         if (*chr == *shr) {
713             chr++;
714             shr++;
715             continue;
716         }
717 
718         if (*chr == '\0') {
719             break;
720         }
721 
722         if (*chr == '/') {
723             if (chr[-1] == '/') {
724                 chr++;
725                 continue;
726             }
727 
728         } else if (*shr == '/') {
729             if (shr[-1] == '/') {
730                 shr++;
731                 continue;
732             }
733         }
734 
735         return NULL;
736     }
737 
738     if (shr[-1] != '/' && *shr != '/') {
739         return NULL;
740     }
741 
742     while (*shr == '/') {
743         shr++;
744     }
745 
746     return (*shr != '\0') ? shr : NULL;
747 }
748 
749 #endif
750 
751 
752 static void
753 nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *exten)
754 {
755     u_char  ch, *p, *end;
756 
757     end = path->start + path->length;
758     p = end;
759 
760     for ( ;; ) {
761         /* There's always '/' in the beginning of the request path. */
762 
763         p--;
764         ch = *p;
765 
766         switch (ch) {
767         case '/':
768             p++;
769             /* Fall through. */
770         case '.':
771             exten->length = end - p;
772             exten->start = p;
773             return;
774         }
775     }
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