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