xref: /unit/src/nxt_http_static.c (revision 2381:a68b5f5bf46c)
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         nxt_conf_get_string(acf->index, &str);
121 
122         ret = nxt_str_dup(mp, &conf->index, &str);
123         if (nxt_slow_path(ret == NULL)) {
124             return NXT_ERROR;
125         }
126     }
127 
128 #if (NXT_HAVE_OPENAT2)
129     if (acf->chroot.length > 0) {
130         nxt_str_t   chr, shr;
131         nxt_bool_t  is_const;
132 
133         conf->chroot = nxt_tstr_compile(rtcf->tstr_state, &acf->chroot,
134                                         NXT_TSTR_STRZ);
135         if (nxt_slow_path(conf->chroot == NULL)) {
136             return NXT_ERROR;
137         }
138 
139         is_const = nxt_tstr_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_tstr_str(conf->chroot, &chr);
146                 nxt_tstr_str(conf->shares[i].tstr, &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 *
nxt_http_static(nxt_task_t * task,nxt_http_request_t * r,nxt_http_action_t * action)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                 if (nxt_slow_path(r->log_route)) {
200                     nxt_log(task, NXT_LOG_NOTICE, "\"fallback\" taken");
201                 }
202                 return action->fallback;
203             }
204 
205             nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
206             return NULL;
207         }
208 
209         need_body = 0;
210 
211     } else {
212         need_body = 1;
213     }
214 
215     ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t));
216     if (nxt_slow_path(ctx == NULL)) {
217         nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
218         return NULL;
219     }
220 
221     ctx->action = action;
222     ctx->need_body = need_body;
223 
224     nxt_http_static_iterate(task, r, ctx);
225 
226     return NULL;
227 }
228 
229 
230 static void
nxt_http_static_iterate(nxt_task_t * task,nxt_http_request_t * r,nxt_http_static_ctx_t * ctx)231 nxt_http_static_iterate(nxt_task_t *task, nxt_http_request_t *r,
232     nxt_http_static_ctx_t *ctx)
233 {
234     nxt_int_t                ret;
235     nxt_router_conf_t        *rtcf;
236     nxt_http_static_conf_t   *conf;
237     nxt_http_static_share_t  *share;
238 
239     conf = ctx->action->u.conf;
240 
241     share = &conf->shares[ctx->share_idx];
242 
243 #if (NXT_DEBUG)
244     nxt_str_t  shr;
245     nxt_str_t  idx;
246 
247     nxt_tstr_str(share->tstr, &shr);
248     idx = conf->index;
249 
250 #if (NXT_HAVE_OPENAT2)
251     nxt_str_t  chr;
252 
253     if (conf->chroot != NULL) {
254         nxt_tstr_str(conf->chroot, &chr);
255 
256     } else {
257         nxt_str_set(&chr, "");
258     }
259 
260     nxt_debug(task, "http static: \"%V\", index: \"%V\" (chroot: \"%V\")",
261               &shr, &idx, &chr);
262 #else
263     nxt_debug(task, "http static: \"%V\", index: \"%V\"", &shr, &idx);
264 #endif
265 #endif /* NXT_DEBUG */
266 
267     if (share->is_const) {
268         nxt_tstr_str(share->tstr, &ctx->share);
269 
270 #if (NXT_HAVE_OPENAT2)
271         if (conf->chroot != NULL && ctx->share_idx == 0) {
272             nxt_tstr_str(conf->chroot, &ctx->chroot);
273         }
274 #endif
275 
276         nxt_http_static_send_ready(task, r, ctx);
277 
278     } else {
279         rtcf = r->conf->socket_conf->router_conf;
280 
281         ret = nxt_tstr_query_init(&r->tstr_query, rtcf->tstr_state,
282                                   &r->tstr_cache, r, r->mem_pool);
283         if (nxt_slow_path(ret != NXT_OK)) {
284             nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
285             return;
286         }
287 
288         nxt_tstr_query(task, r->tstr_query, share->tstr, &ctx->share);
289 
290 #if (NXT_HAVE_OPENAT2)
291         if (conf->chroot != NULL && ctx->share_idx == 0) {
292             nxt_tstr_query(task, r->tstr_query, conf->chroot, &ctx->chroot);
293         }
294 #endif
295 
296         nxt_tstr_query_resolve(task, r->tstr_query, ctx,
297                                nxt_http_static_send_ready,
298                                nxt_http_static_send_error);
299      }
300 }
301 
302 
303 static void
nxt_http_static_send_ready(nxt_task_t * task,void * obj,void * data)304 nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data)
305 {
306     size_t                  length, encode;
307     u_char                  *p, *fname;
308     struct tm               tm;
309     nxt_buf_t               *fb;
310     nxt_int_t               ret;
311     nxt_str_t               *shr, *index, exten, *mtype;
312     nxt_uint_t              level;
313     nxt_file_t              *f, file;
314     nxt_file_info_t         fi;
315     nxt_http_field_t        *field;
316     nxt_http_status_t       status;
317     nxt_router_conf_t       *rtcf;
318     nxt_http_action_t       *action;
319     nxt_http_request_t      *r;
320     nxt_work_handler_t      body_handler;
321     nxt_http_static_ctx_t   *ctx;
322     nxt_http_static_conf_t  *conf;
323 
324     r = obj;
325     ctx = data;
326     action = ctx->action;
327     conf = action->u.conf;
328     rtcf = r->conf->socket_conf->router_conf;
329 
330     f = NULL;
331     mtype = NULL;
332 
333     shr = &ctx->share;
334     index = &conf->index;
335 
336     if (shr->start[shr->length - 1] == '/') {
337         nxt_http_static_extract_extension(index, &exten);
338 
339         length = shr->length + index->length;
340 
341         fname = nxt_mp_nget(r->mem_pool, length + 1);
342         if (nxt_slow_path(fname == NULL)) {
343             goto fail;
344         }
345 
346         p = fname;
347         p = nxt_cpymem(p, shr->start, shr->length);
348         p = nxt_cpymem(p, index->start, index->length);
349         *p = '\0';
350 
351     } else {
352         if (conf->types == NULL) {
353             nxt_str_null(&exten);
354 
355         } else {
356             nxt_http_static_extract_extension(shr, &exten);
357             mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
358 
359             ret = nxt_http_route_test_rule(r, conf->types, mtype->start,
360                                            mtype->length);
361             if (nxt_slow_path(ret == NXT_ERROR)) {
362                 goto fail;
363             }
364 
365             if (ret == 0) {
366                 nxt_http_static_next(task, r, ctx, NXT_HTTP_FORBIDDEN);
367                 return;
368             }
369         }
370 
371         fname = ctx->share.start;
372     }
373 
374     nxt_memzero(&file, sizeof(nxt_file_t));
375 
376     file.name = fname;
377 
378 #if (NXT_HAVE_OPENAT2)
379     if (conf->resolve != 0 || ctx->chroot.length > 0) {
380         nxt_str_t                *chr;
381         nxt_uint_t               resolve;
382         nxt_http_static_share_t  *share;
383 
384         share = &conf->shares[ctx->share_idx];
385 
386         resolve = conf->resolve;
387         chr = &ctx->chroot;
388 
389         if (chr->length > 0) {
390             resolve |= RESOLVE_IN_ROOT;
391 
392             fname = share->is_const
393                     ? share->fname
394                     : nxt_http_static_chroot_match(chr->start, file.name);
395 
396             if (fname != NULL) {
397                 file.name = chr->start;
398                 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
399                                     0);
400 
401             } else {
402                 file.error = NXT_EACCES;
403                 ret = NXT_ERROR;
404             }
405 
406         } else if (fname[0] == '/') {
407             file.name = (u_char *) "/";
408             ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
409 
410         } else {
411             file.name = (u_char *) ".";
412             file.fd = AT_FDCWD;
413             ret = NXT_OK;
414         }
415 
416         if (nxt_fast_path(ret == NXT_OK)) {
417             nxt_file_t  af;
418 
419             af = file;
420             nxt_memzero(&file, sizeof(nxt_file_t));
421             file.name = fname;
422 
423             ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
424                                    NXT_FILE_OPEN, 0, af.fd, resolve);
425 
426             if (af.fd != AT_FDCWD) {
427                 nxt_file_close(task, &af);
428             }
429         }
430 
431     } else {
432         ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
433     }
434 
435 #else
436     ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
437 #endif
438 
439     if (nxt_slow_path(ret != NXT_OK)) {
440 
441         switch (file.error) {
442 
443         /*
444          * For Unix domain sockets "errno" is set to:
445          *  - ENXIO on Linux;
446          *  - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
447          */
448 
449         case NXT_ENOENT:
450         case NXT_ENOTDIR:
451         case NXT_ENAMETOOLONG:
452 #if (NXT_LINUX)
453         case NXT_ENXIO:
454 #else
455         case NXT_EOPNOTSUPP:
456 #endif
457             level = NXT_LOG_ERR;
458             status = NXT_HTTP_NOT_FOUND;
459             break;
460 
461         case NXT_EACCES:
462 #if (NXT_HAVE_OPENAT2)
463         case NXT_ELOOP:
464         case NXT_EXDEV:
465 #endif
466             level = NXT_LOG_ERR;
467             status = NXT_HTTP_FORBIDDEN;
468             break;
469 
470         default:
471             level = NXT_LOG_ALERT;
472             status = NXT_HTTP_INTERNAL_SERVER_ERROR;
473             break;
474         }
475 
476         if (status != NXT_HTTP_NOT_FOUND) {
477 #if (NXT_HAVE_OPENAT2)
478             nxt_str_t  *chr = &ctx->chroot;
479 
480             if (chr->length > 0) {
481                 nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E",
482                         fname, chr, file.error);
483 
484             } else {
485                 nxt_log(task, level, "opening \"%s\" failed %E",
486                         fname, file.error);
487             }
488 
489 #else
490             nxt_log(task, level, "opening \"%s\" failed %E", fname, file.error);
491 #endif
492         }
493 
494         if (level == NXT_LOG_ERR) {
495             nxt_http_static_next(task, r, ctx, status);
496             return;
497         }
498 
499         goto fail;
500     }
501 
502     f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
503     if (nxt_slow_path(f == NULL)) {
504         nxt_file_close(task, &file);
505         goto fail;
506     }
507 
508     *f = file;
509 
510     ret = nxt_file_info(f, &fi);
511     if (nxt_slow_path(ret != NXT_OK)) {
512         goto fail;
513     }
514 
515     if (nxt_fast_path(nxt_is_file(&fi))) {
516         r->status = NXT_HTTP_OK;
517         r->resp.content_length_n = nxt_file_size(&fi);
518 
519         field = nxt_list_zero_add(r->resp.fields);
520         if (nxt_slow_path(field == NULL)) {
521             goto fail;
522         }
523 
524         nxt_http_field_name_set(field, "Last-Modified");
525 
526         p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
527         if (nxt_slow_path(p == NULL)) {
528             goto fail;
529         }
530 
531         nxt_localtime(nxt_file_mtime(&fi), &tm);
532 
533         field->value = p;
534         field->value_length = nxt_http_date(p, &tm) - p;
535 
536         field = nxt_list_zero_add(r->resp.fields);
537         if (nxt_slow_path(field == NULL)) {
538             goto fail;
539         }
540 
541         nxt_http_field_name_set(field, "ETag");
542 
543         length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
544 
545         p = nxt_mp_nget(r->mem_pool, length);
546         if (nxt_slow_path(p == NULL)) {
547             goto fail;
548         }
549 
550         field->value = p;
551         field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
552                                           nxt_file_mtime(&fi),
553                                           nxt_file_size(&fi))
554                               - p;
555 
556         if (exten.start == NULL) {
557             nxt_http_static_extract_extension(shr, &exten);
558         }
559 
560         if (mtype == NULL) {
561             mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten);
562         }
563 
564         if (mtype->length != 0) {
565             field = nxt_list_zero_add(r->resp.fields);
566             if (nxt_slow_path(field == NULL)) {
567                 goto fail;
568             }
569 
570             nxt_http_field_name_set(field, "Content-Type");
571 
572             field->value = mtype->start;
573             field->value_length = mtype->length;
574         }
575 
576         if (ctx->need_body && nxt_file_size(&fi) > 0) {
577             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
578             if (nxt_slow_path(fb == NULL)) {
579                 goto fail;
580             }
581 
582             fb->file = f;
583             fb->file_end = nxt_file_size(&fi);
584 
585             r->out = fb;
586 
587             body_handler = &nxt_http_static_body_handler;
588 
589         } else {
590             nxt_file_close(task, f);
591             body_handler = NULL;
592         }
593 
594     } else {
595         /* Not a file. */
596         nxt_file_close(task, f);
597 
598         if (nxt_slow_path(!nxt_is_dir(&fi)
599                           || shr->start[shr->length - 1] == '/'))
600         {
601             nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
602                     f->name);
603 
604             nxt_http_static_next(task, r, ctx, NXT_HTTP_NOT_FOUND);
605             return;
606         }
607 
608         f = NULL;
609 
610         r->status = NXT_HTTP_MOVED_PERMANENTLY;
611         r->resp.content_length_n = 0;
612 
613         field = nxt_list_zero_add(r->resp.fields);
614         if (nxt_slow_path(field == NULL)) {
615             goto fail;
616         }
617 
618         nxt_http_field_name_set(field, "Location");
619 
620         encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
621         length = r->path->length + encode * 2 + 1;
622 
623         if (r->args->length > 0) {
624             length += 1 + r->args->length;
625         }
626 
627         p = nxt_mp_nget(r->mem_pool, length);
628         if (nxt_slow_path(p == NULL)) {
629             goto fail;
630         }
631 
632         field->value = p;
633         field->value_length = length;
634 
635         if (encode > 0) {
636             p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
637 
638         } else {
639             p = nxt_cpymem(p, r->path->start, r->path->length);
640         }
641 
642         *p++ = '/';
643 
644         if (r->args->length > 0) {
645             *p++ = '?';
646             nxt_memcpy(p, r->args->start, r->args->length);
647         }
648 
649         body_handler = NULL;
650     }
651 
652     nxt_http_request_header_send(task, r, body_handler, NULL);
653 
654     r->state = &nxt_http_static_send_state;
655     return;
656 
657 fail:
658 
659     if (f != NULL) {
660         nxt_file_close(task, f);
661     }
662 
663     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
664 }
665 
666 
667 static void
nxt_http_static_send_error(nxt_task_t * task,void * obj,void * data)668 nxt_http_static_send_error(nxt_task_t *task, void *obj, void *data)
669 {
670     nxt_http_request_t  *r;
671 
672     r = obj;
673 
674     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
675 }
676 
677 
678 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)679 nxt_http_static_next(nxt_task_t *task, nxt_http_request_t *r,
680     nxt_http_static_ctx_t *ctx, nxt_http_status_t status)
681 {
682     nxt_http_action_t       *action;
683     nxt_http_static_conf_t  *conf;
684 
685     action = ctx->action;
686     conf = action->u.conf;
687 
688     ctx->share_idx++;
689 
690     if (ctx->share_idx < conf->nshares) {
691         nxt_http_static_iterate(task, r, ctx);
692         return;
693     }
694 
695     if (action->fallback != NULL) {
696         if (nxt_slow_path(r->log_route)) {
697             nxt_log(task, NXT_LOG_NOTICE, "\"fallback\" taken");
698         }
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 (n != size) {
881         if (n >= 0) {
882             nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
883                     "while sending response to a client", fb->file->name);
884         }
885 
886         nxt_http_request_error_handler(task, r, r->proto.any);
887         goto clean;
888     }
889 
890     next = b->next;
891 
892     if (n == rest) {
893         nxt_file_close(task, fb->file);
894         r->out = NULL;
895 
896         b->next = nxt_http_buf_last(r);
897 
898     } else {
899         fb->file_pos += n;
900         b->next = NULL;
901     }
902 
903     b->mem.pos = b->mem.start;
904     b->mem.free = b->mem.pos + n;
905 
906     nxt_http_request_send(task, r, b);
907 
908     if (next != NULL) {
909         b = next;
910         goto complete_buf;
911     }
912 
913     return;
914 
915 clean:
916 
917     do {
918         next = b->next;
919 
920         nxt_mp_free(r->mem_pool, b);
921         nxt_mp_release(r->mem_pool);
922 
923         b = next;
924     } while (b != NULL);
925 
926     if (fb != NULL) {
927         nxt_file_close(task, fb->file);
928         r->out = NULL;
929     }
930 }
931 
932 
933 nxt_int_t
nxt_http_static_mtypes_init(nxt_mp_t * mp,nxt_lvlhsh_t * hash)934 nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
935 {
936     nxt_str_t   *type, exten;
937     nxt_int_t   ret;
938     nxt_uint_t  i;
939 
940     static const struct {
941         nxt_str_t   type;
942         const char  *exten;
943     } default_types[] = {
944 
945         { nxt_string("text/html"),      ".html"  },
946         { nxt_string("text/html"),      ".htm"   },
947         { nxt_string("text/css"),       ".css"   },
948 
949         { nxt_string("image/svg+xml"),  ".svg"   },
950         { nxt_string("image/webp"),     ".webp"  },
951         { nxt_string("image/png"),      ".png"   },
952         { nxt_string("image/apng"),     ".apng"  },
953         { nxt_string("image/jpeg"),     ".jpeg"  },
954         { nxt_string("image/jpeg"),     ".jpg"   },
955         { nxt_string("image/gif"),      ".gif"   },
956         { nxt_string("image/x-icon"),   ".ico"   },
957 
958         { nxt_string("image/avif"),           ".avif"  },
959         { nxt_string("image/avif-sequence"),  ".avifs" },
960 
961         { nxt_string("font/woff"),      ".woff"  },
962         { nxt_string("font/woff2"),     ".woff2" },
963         { nxt_string("font/otf"),       ".otf"   },
964         { nxt_string("font/ttf"),       ".ttf"   },
965 
966         { nxt_string("text/plain"),     ".txt"   },
967         { nxt_string("text/markdown"),  ".md"    },
968         { nxt_string("text/x-rst"),     ".rst"   },
969 
970         { nxt_string("application/javascript"),  ".js"   },
971         { nxt_string("application/json"),        ".json" },
972         { nxt_string("application/xml"),         ".xml"  },
973         { nxt_string("application/rss+xml"),     ".rss"  },
974         { nxt_string("application/atom+xml"),    ".atom" },
975         { nxt_string("application/pdf"),         ".pdf"  },
976 
977         { nxt_string("application/zip"),         ".zip"  },
978 
979         { nxt_string("audio/mpeg"),       ".mp3"  },
980         { nxt_string("audio/ogg"),        ".ogg"  },
981         { nxt_string("audio/midi"),       ".midi" },
982         { nxt_string("audio/midi"),       ".mid"  },
983         { nxt_string("audio/flac"),       ".flac" },
984         { nxt_string("audio/aac"),        ".aac"  },
985         { nxt_string("audio/wav"),        ".wav"  },
986 
987         { nxt_string("video/mpeg"),       ".mpeg" },
988         { nxt_string("video/mpeg"),       ".mpg"  },
989         { nxt_string("video/mp4"),        ".mp4"  },
990         { nxt_string("video/webm"),       ".webm" },
991         { nxt_string("video/x-msvideo"),  ".avi"  },
992 
993         { nxt_string("application/octet-stream"),  ".exe" },
994         { nxt_string("application/octet-stream"),  ".bin" },
995         { nxt_string("application/octet-stream"),  ".dll" },
996         { nxt_string("application/octet-stream"),  ".iso" },
997         { nxt_string("application/octet-stream"),  ".img" },
998         { nxt_string("application/octet-stream"),  ".msi" },
999 
1000         { nxt_string("application/octet-stream"),  ".deb" },
1001         { nxt_string("application/octet-stream"),  ".rpm" },
1002 
1003         { nxt_string("application/x-httpd-php"),   ".php" },
1004     };
1005 
1006     for (i = 0; i < nxt_nitems(default_types); i++) {
1007         type = (nxt_str_t *) &default_types[i].type;
1008 
1009         exten.start = (u_char *) default_types[i].exten;
1010         exten.length = nxt_strlen(exten.start);
1011 
1012         ret = nxt_http_static_mtypes_hash_add(mp, hash, &exten, type);
1013         if (nxt_slow_path(ret != NXT_OK)) {
1014             return NXT_ERROR;
1015         }
1016     }
1017 
1018     return NXT_OK;
1019 }
1020 
1021 
1022 static const nxt_lvlhsh_proto_t  nxt_http_static_mtypes_hash_proto
1023     nxt_aligned(64) =
1024 {
1025     NXT_LVLHSH_DEFAULT,
1026     nxt_http_static_mtypes_hash_test,
1027     nxt_http_static_mtypes_hash_alloc,
1028     nxt_http_static_mtypes_hash_free,
1029 };
1030 
1031 
1032 typedef struct {
1033     nxt_str_t  exten;
1034     nxt_str_t  *type;
1035 } nxt_http_static_mtype_t;
1036 
1037 
1038 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)1039 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
1040     const nxt_str_t *exten, nxt_str_t *type)
1041 {
1042     nxt_lvlhsh_query_t       lhq;
1043     nxt_http_static_mtype_t  *mtype;
1044 
1045     mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
1046     if (nxt_slow_path(mtype == NULL)) {
1047         return NXT_ERROR;
1048     }
1049 
1050     mtype->exten = *exten;
1051     mtype->type = type;
1052 
1053     lhq.key = *exten;
1054     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1055     lhq.replace = 1;
1056     lhq.value = mtype;
1057     lhq.proto = &nxt_http_static_mtypes_hash_proto;
1058     lhq.pool = mp;
1059 
1060     return nxt_lvlhsh_insert(hash, &lhq);
1061 }
1062 
1063 
1064 nxt_str_t *
nxt_http_static_mtype_get(nxt_lvlhsh_t * hash,const nxt_str_t * exten)1065 nxt_http_static_mtype_get(nxt_lvlhsh_t *hash, const nxt_str_t *exten)
1066 {
1067     nxt_lvlhsh_query_t       lhq;
1068     nxt_http_static_mtype_t  *mtype;
1069 
1070     static nxt_str_t  empty = nxt_string("");
1071 
1072     lhq.key = *exten;
1073     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
1074     lhq.proto = &nxt_http_static_mtypes_hash_proto;
1075 
1076     if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
1077         mtype = lhq.value;
1078         return mtype->type;
1079     }
1080 
1081     return &empty;
1082 }
1083 
1084 
1085 static nxt_int_t
nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t * lhq,void * data)1086 nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
1087 {
1088     nxt_http_static_mtype_t  *mtype;
1089 
1090     mtype = data;
1091 
1092     return nxt_strcasestr_eq(&lhq->key, &mtype->exten) ? NXT_OK : NXT_DECLINED;
1093 }
1094 
1095 
1096 static void *
nxt_http_static_mtypes_hash_alloc(void * data,size_t size)1097 nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
1098 {
1099     return nxt_mp_align(data, size, size);
1100 }
1101 
1102 
1103 static void
nxt_http_static_mtypes_hash_free(void * data,void * p)1104 nxt_http_static_mtypes_hash_free(void *data, void *p)
1105 {
1106     nxt_mp_free(data, p);
1107 }
1108