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