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