xref: /unit/src/nxt_http_static.c (revision 1855:a946d8cd7f8c)
1 
2 /*
3  * Copyright (C) NGINX, Inc.
4  */
5 
6 #include <nxt_router.h>
7 #include <nxt_http.h>
8 
9 
10 #define NXT_HTTP_STATIC_BUF_COUNT  2
11 #define NXT_HTTP_STATIC_BUF_SIZE   (128 * 1024)
12 
13 
14 static void nxt_http_static_extract_extension(nxt_str_t *path,
15     nxt_str_t *extension);
16 static void nxt_http_static_body_handler(nxt_task_t *task, void *obj,
17     void *data);
18 static void nxt_http_static_buf_completion(nxt_task_t *task, void *obj,
19     void *data);
20 
21 static nxt_int_t nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq,
22     void *data);
23 static void *nxt_http_static_mtypes_hash_alloc(void *data, size_t size);
24 static void nxt_http_static_mtypes_hash_free(void *data, void *p);
25 
26 
27 static const nxt_http_request_state_t  nxt_http_static_send_state;
28 
29 
30 nxt_http_action_t *
31 nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r,
32     nxt_http_action_t *action)
33 {
34     size_t              length, encode;
35     u_char              *p, *fname;
36     struct tm           tm;
37     nxt_buf_t           *fb;
38     nxt_int_t           ret;
39     nxt_str_t           index, extension, *mtype, *chroot;
40     nxt_uint_t          level;
41     nxt_bool_t          need_body;
42     nxt_file_t          *f, af, file;
43     nxt_file_info_t     fi;
44     nxt_http_field_t    *field;
45     nxt_http_status_t   status;
46     nxt_router_conf_t   *rtcf;
47     nxt_work_handler_t  body_handler;
48 
49     if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) {
50 
51         if (!nxt_str_eq(r->method, "HEAD", 4)) {
52             if (action->u.share.fallback != NULL) {
53                 return action->u.share.fallback;
54             }
55 
56             nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED);
57             return NULL;
58         }
59 
60         need_body = 0;
61 
62     } else {
63         need_body = 1;
64     }
65 
66     if (r->path->start[r->path->length - 1] == '/') {
67         /* TODO: dynamic index setting. */
68         nxt_str_set(&index, "index.html");
69         nxt_str_set(&extension, ".html");
70 
71     } else {
72         nxt_str_set(&index, "");
73         nxt_str_null(&extension);
74     }
75 
76     f = NULL;
77 
78     length = action->name.length + r->path->length + index.length;
79 
80     fname = nxt_mp_nget(r->mem_pool, length + 1);
81     if (nxt_slow_path(fname == NULL)) {
82         goto fail;
83     }
84 
85     p = fname;
86     p = nxt_cpymem(p, action->name.start, action->name.length);
87     p = nxt_cpymem(p, r->path->start, r->path->length);
88     p = nxt_cpymem(p, index.start, index.length);
89     *p = '\0';
90 
91     nxt_memzero(&file, sizeof(nxt_file_t));
92 
93     file.name = fname;
94 
95     chroot = &action->u.share.chroot;
96 
97 #if (NXT_HAVE_OPENAT2)
98 
99     if (action->u.share.resolve != 0) {
100 
101         if (chroot->length > 0) {
102             file.name = chroot->start;
103 
104             if (length > chroot->length
105                 && nxt_memcmp(fname, chroot->start, chroot->length) == 0)
106             {
107                 fname += chroot->length;
108                 ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN,
109                                     0);
110 
111             } else {
112                 file.error = NXT_EACCES;
113                 ret = NXT_ERROR;
114             }
115 
116         } else if (fname[0] == '/') {
117             file.name = (u_char *) "/";
118             ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0);
119 
120         } else {
121             file.name = (u_char *) ".";
122             file.fd = AT_FDCWD;
123             ret = NXT_OK;
124         }
125 
126         if (nxt_fast_path(ret == NXT_OK)) {
127             af = file;
128             nxt_memzero(&file, sizeof(nxt_file_t));
129             file.name = fname;
130 
131             ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY,
132                                    NXT_FILE_OPEN, 0, af.fd,
133                                    action->u.share.resolve);
134 
135             if (af.fd != AT_FDCWD) {
136                 nxt_file_close(task, &af);
137             }
138         }
139 
140     } else {
141         ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
142     }
143 
144 #else
145 
146     ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0);
147 
148 #endif
149 
150     if (nxt_slow_path(ret != NXT_OK)) {
151 
152         switch (file.error) {
153 
154         /*
155          * For Unix domain sockets "errno" is set to:
156          *  - ENXIO on Linux;
157          *  - EOPNOTSUPP on *BSD, MacOSX, and Solaris.
158          */
159 
160         case NXT_ENOENT:
161         case NXT_ENOTDIR:
162         case NXT_ENAMETOOLONG:
163 #if (NXT_LINUX)
164         case NXT_ENXIO:
165 #else
166         case NXT_EOPNOTSUPP:
167 #endif
168             level = NXT_LOG_ERR;
169             status = NXT_HTTP_NOT_FOUND;
170             break;
171 
172         case NXT_EACCES:
173 #if (NXT_HAVE_OPENAT2)
174         case NXT_ELOOP:
175         case NXT_EXDEV:
176 #endif
177             level = NXT_LOG_ERR;
178             status = NXT_HTTP_FORBIDDEN;
179             break;
180 
181         default:
182             level = NXT_LOG_ALERT;
183             status = NXT_HTTP_INTERNAL_SERVER_ERROR;
184             break;
185         }
186 
187         if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) {
188             return action->u.share.fallback;
189         }
190 
191         if (status != NXT_HTTP_NOT_FOUND) {
192             if (chroot->length > 0) {
193                 nxt_log(task, level, "opening \"%FN\" at \"%FN\" failed %E",
194                         fname, chroot, file.error);
195 
196             } else {
197                 nxt_log(task, level, "opening \"%FN\" failed %E",
198                         fname, file.error);
199             }
200         }
201 
202         nxt_http_request_error(task, r, status);
203         return NULL;
204     }
205 
206     f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t));
207     if (nxt_slow_path(f == NULL)) {
208         goto fail;
209     }
210 
211     *f = file;
212 
213     ret = nxt_file_info(f, &fi);
214     if (nxt_slow_path(ret != NXT_OK)) {
215         goto fail;
216     }
217 
218     if (nxt_fast_path(nxt_is_file(&fi))) {
219         r->status = NXT_HTTP_OK;
220         r->resp.content_length_n = nxt_file_size(&fi);
221 
222         field = nxt_list_zero_add(r->resp.fields);
223         if (nxt_slow_path(field == NULL)) {
224             goto fail;
225         }
226 
227         nxt_http_field_name_set(field, "Last-Modified");
228 
229         p = nxt_mp_nget(r->mem_pool, NXT_HTTP_DATE_LEN);
230         if (nxt_slow_path(p == NULL)) {
231             goto fail;
232         }
233 
234         nxt_localtime(nxt_file_mtime(&fi), &tm);
235 
236         field->value = p;
237         field->value_length = nxt_http_date(p, &tm) - p;
238 
239         field = nxt_list_zero_add(r->resp.fields);
240         if (nxt_slow_path(field == NULL)) {
241             goto fail;
242         }
243 
244         nxt_http_field_name_set(field, "ETag");
245 
246         length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3;
247 
248         p = nxt_mp_nget(r->mem_pool, length);
249         if (nxt_slow_path(p == NULL)) {
250             goto fail;
251         }
252 
253         field->value = p;
254         field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"",
255                                           nxt_file_mtime(&fi),
256                                           nxt_file_size(&fi))
257                               - p;
258 
259         if (extension.start == NULL) {
260             nxt_http_static_extract_extension(r->path, &extension);
261         }
262 
263         rtcf = r->conf->socket_conf->router_conf;
264 
265         mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash,
266                                                  &extension);
267 
268         if (mtype != NULL) {
269             field = nxt_list_zero_add(r->resp.fields);
270             if (nxt_slow_path(field == NULL)) {
271                 goto fail;
272             }
273 
274             nxt_http_field_name_set(field, "Content-Type");
275 
276             field->value = mtype->start;
277             field->value_length = mtype->length;
278         }
279 
280         if (need_body && nxt_file_size(&fi) > 0) {
281             fb = nxt_mp_zget(r->mem_pool, NXT_BUF_FILE_SIZE);
282             if (nxt_slow_path(fb == NULL)) {
283                 goto fail;
284             }
285 
286             fb->file = f;
287             fb->file_end = nxt_file_size(&fi);
288 
289             r->out = fb;
290 
291             body_handler = &nxt_http_static_body_handler;
292 
293         } else {
294             nxt_file_close(task, f);
295             body_handler = NULL;
296         }
297 
298     } else {
299         /* Not a file. */
300 
301         nxt_file_close(task, f);
302 
303         if (nxt_slow_path(!nxt_is_dir(&fi))) {
304             if (action->u.share.fallback != NULL) {
305                 return action->u.share.fallback;
306             }
307 
308             nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file",
309                     f->name);
310 
311             nxt_http_request_error(task, r, NXT_HTTP_NOT_FOUND);
312             return NULL;
313         }
314 
315         f = NULL;
316 
317         r->status = NXT_HTTP_MOVED_PERMANENTLY;
318         r->resp.content_length_n = 0;
319 
320         field = nxt_list_zero_add(r->resp.fields);
321         if (nxt_slow_path(field == NULL)) {
322             goto fail;
323         }
324 
325         nxt_http_field_name_set(field, "Location");
326 
327         encode = nxt_encode_uri(NULL, r->path->start, r->path->length);
328         length = r->path->length + encode * 2 + 1;
329 
330         if (r->args->length > 0) {
331             length += 1 + r->args->length;
332         }
333 
334         p = nxt_mp_nget(r->mem_pool, length);
335         if (nxt_slow_path(p == NULL)) {
336             goto fail;
337         }
338 
339         field->value = p;
340         field->value_length = length;
341 
342         if (encode > 0) {
343             p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length);
344 
345         } else {
346             p = nxt_cpymem(p, r->path->start, r->path->length);
347         }
348 
349         *p++ = '/';
350 
351         if (r->args->length > 0) {
352             *p++ = '?';
353             nxt_memcpy(p, r->args->start, r->args->length);
354         }
355 
356         body_handler = NULL;
357     }
358 
359     nxt_http_request_header_send(task, r, body_handler, NULL);
360 
361     r->state = &nxt_http_static_send_state;
362     return NULL;
363 
364 fail:
365 
366     nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR);
367 
368     if (f != NULL) {
369         nxt_file_close(task, f);
370     }
371 
372     return NULL;
373 }
374 
375 
376 static void
377 nxt_http_static_extract_extension(nxt_str_t *path, nxt_str_t *extension)
378 {
379     u_char  ch, *p, *end;
380 
381     end = path->start + path->length;
382     p = end;
383 
384     for ( ;; ) {
385         /* There's always '/' in the beginning of the request path. */
386 
387         p--;
388         ch = *p;
389 
390         switch (ch) {
391         case '/':
392             p++;
393             /* Fall through. */
394         case '.':
395             extension->length = end - p;
396             extension->start = p;
397             return;
398         }
399     }
400 }
401 
402 
403 static void
404 nxt_http_static_body_handler(nxt_task_t *task, void *obj, void *data)
405 {
406     size_t              alloc;
407     nxt_buf_t           *fb, *b, **next, *out;
408     nxt_off_t           rest;
409     nxt_int_t           n;
410     nxt_work_queue_t    *wq;
411     nxt_http_request_t  *r;
412 
413     r = obj;
414     fb = r->out;
415 
416     rest = fb->file_end - fb->file_pos;
417     out = NULL;
418     next = &out;
419     n = 0;
420 
421     do {
422         alloc = nxt_min(rest, NXT_HTTP_STATIC_BUF_SIZE);
423 
424         b = nxt_buf_mem_alloc(r->mem_pool, alloc, 0);
425         if (nxt_slow_path(b == NULL)) {
426             goto fail;
427         }
428 
429         b->completion_handler = nxt_http_static_buf_completion;
430         b->parent = r;
431 
432         nxt_mp_retain(r->mem_pool);
433 
434         *next = b;
435         next = &b->next;
436 
437         rest -= alloc;
438 
439     } while (rest > 0 && ++n < NXT_HTTP_STATIC_BUF_COUNT);
440 
441     wq = &task->thread->engine->fast_work_queue;
442 
443     nxt_sendbuf_drain(task, wq, out);
444     return;
445 
446 fail:
447 
448     while (out != NULL) {
449         b = out;
450         out = b->next;
451 
452         nxt_mp_free(r->mem_pool, b);
453         nxt_mp_release(r->mem_pool);
454     }
455 }
456 
457 
458 static const nxt_http_request_state_t  nxt_http_static_send_state
459     nxt_aligned(64) =
460 {
461     .error_handler = nxt_http_request_error_handler,
462 };
463 
464 
465 static void
466 nxt_http_static_buf_completion(nxt_task_t *task, void *obj, void *data)
467 {
468     ssize_t             n, size;
469     nxt_buf_t           *b, *fb, *next;
470     nxt_off_t           rest;
471     nxt_http_request_t  *r;
472 
473     b = obj;
474     r = data;
475 
476 complete_buf:
477 
478     fb = r->out;
479 
480     if (nxt_slow_path(fb == NULL || r->error)) {
481         goto clean;
482     }
483 
484     rest = fb->file_end - fb->file_pos;
485     size = nxt_buf_mem_size(&b->mem);
486 
487     size = nxt_min(rest, (nxt_off_t) size);
488 
489     n = nxt_file_read(fb->file, b->mem.start, size, fb->file_pos);
490 
491     if (n != size) {
492         if (n >= 0) {
493             nxt_log(task, NXT_LOG_ERR, "file \"%FN\" has changed "
494                     "while sending response to a client", fb->file->name);
495         }
496 
497         nxt_http_request_error_handler(task, r, r->proto.any);
498         goto clean;
499     }
500 
501     next = b->next;
502 
503     if (n == rest) {
504         nxt_file_close(task, fb->file);
505         r->out = NULL;
506 
507         b->next = nxt_http_buf_last(r);
508 
509     } else {
510         fb->file_pos += n;
511         b->next = NULL;
512     }
513 
514     b->mem.pos = b->mem.start;
515     b->mem.free = b->mem.pos + n;
516 
517     nxt_http_request_send(task, r, b);
518 
519     if (next != NULL) {
520         b = next;
521         goto complete_buf;
522     }
523 
524     return;
525 
526 clean:
527 
528     do {
529         next = b->next;
530 
531         nxt_mp_free(r->mem_pool, b);
532         nxt_mp_release(r->mem_pool);
533 
534         b = next;
535     } while (b != NULL);
536 
537     if (fb != NULL) {
538         nxt_file_close(task, fb->file);
539         r->out = NULL;
540     }
541 }
542 
543 
544 nxt_int_t
545 nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash)
546 {
547     nxt_str_t   *type, extension;
548     nxt_int_t   ret;
549     nxt_uint_t  i;
550 
551     static const struct {
552         nxt_str_t   type;
553         const char  *extension;
554     } default_types[] = {
555 
556         { nxt_string("text/html"),      ".html"  },
557         { nxt_string("text/html"),      ".htm"   },
558         { nxt_string("text/css"),       ".css"   },
559 
560         { nxt_string("image/svg+xml"),  ".svg"   },
561         { nxt_string("image/webp"),     ".webp"  },
562         { nxt_string("image/png"),      ".png"   },
563         { nxt_string("image/apng"),     ".apng"  },
564         { nxt_string("image/jpeg"),     ".jpeg"  },
565         { nxt_string("image/jpeg"),     ".jpg"   },
566         { nxt_string("image/gif"),      ".gif"   },
567         { nxt_string("image/x-icon"),   ".ico"   },
568 
569         { nxt_string("image/avif"),           ".avif"  },
570         { nxt_string("image/avif-sequence"),  ".avifs" },
571 
572         { nxt_string("font/woff"),      ".woff"  },
573         { nxt_string("font/woff2"),     ".woff2" },
574         { nxt_string("font/otf"),       ".otf"   },
575         { nxt_string("font/ttf"),       ".ttf"   },
576 
577         { nxt_string("text/plain"),     ".txt"   },
578         { nxt_string("text/markdown"),  ".md"    },
579         { nxt_string("text/x-rst"),     ".rst"   },
580 
581         { nxt_string("application/javascript"),  ".js"   },
582         { nxt_string("application/json"),        ".json" },
583         { nxt_string("application/xml"),         ".xml"  },
584         { nxt_string("application/rss+xml"),     ".rss"  },
585         { nxt_string("application/atom+xml"),    ".atom" },
586         { nxt_string("application/pdf"),         ".pdf"  },
587 
588         { nxt_string("application/zip"),         ".zip"  },
589 
590         { nxt_string("audio/mpeg"),       ".mp3"  },
591         { nxt_string("audio/ogg"),        ".ogg"  },
592         { nxt_string("audio/midi"),       ".midi" },
593         { nxt_string("audio/midi"),       ".mid"  },
594         { nxt_string("audio/flac"),       ".flac" },
595         { nxt_string("audio/aac"),        ".aac"  },
596         { nxt_string("audio/wav"),        ".wav"  },
597 
598         { nxt_string("video/mpeg"),       ".mpeg" },
599         { nxt_string("video/mpeg"),       ".mpg"  },
600         { nxt_string("video/mp4"),        ".mp4"  },
601         { nxt_string("video/webm"),       ".webm" },
602         { nxt_string("video/x-msvideo"),  ".avi"  },
603 
604         { nxt_string("application/octet-stream"),  ".exe" },
605         { nxt_string("application/octet-stream"),  ".bin" },
606         { nxt_string("application/octet-stream"),  ".dll" },
607         { nxt_string("application/octet-stream"),  ".iso" },
608         { nxt_string("application/octet-stream"),  ".img" },
609         { nxt_string("application/octet-stream"),  ".msi" },
610 
611         { nxt_string("application/octet-stream"),  ".deb" },
612         { nxt_string("application/octet-stream"),  ".rpm" },
613     };
614 
615     for (i = 0; i < nxt_nitems(default_types); i++) {
616         type = (nxt_str_t *) &default_types[i].type;
617 
618         extension.start = (u_char *) default_types[i].extension;
619         extension.length = nxt_strlen(extension.start);
620 
621         ret = nxt_http_static_mtypes_hash_add(mp, hash, &extension, type);
622         if (nxt_slow_path(ret != NXT_OK)) {
623             return NXT_ERROR;
624         }
625     }
626 
627     return NXT_OK;
628 }
629 
630 
631 static const nxt_lvlhsh_proto_t  nxt_http_static_mtypes_hash_proto
632     nxt_aligned(64) =
633 {
634     NXT_LVLHSH_DEFAULT,
635     nxt_http_static_mtypes_hash_test,
636     nxt_http_static_mtypes_hash_alloc,
637     nxt_http_static_mtypes_hash_free,
638 };
639 
640 
641 typedef struct {
642     nxt_str_t  extension;
643     nxt_str_t  *type;
644 } nxt_http_static_mtype_t;
645 
646 
647 nxt_int_t
648 nxt_http_static_mtypes_hash_add(nxt_mp_t *mp, nxt_lvlhsh_t *hash,
649     nxt_str_t *extension, nxt_str_t *type)
650 {
651     nxt_lvlhsh_query_t       lhq;
652     nxt_http_static_mtype_t  *mtype;
653 
654     mtype = nxt_mp_get(mp, sizeof(nxt_http_static_mtype_t));
655     if (nxt_slow_path(mtype == NULL)) {
656         return NXT_ERROR;
657     }
658 
659     mtype->extension = *extension;
660     mtype->type = type;
661 
662     lhq.key = *extension;
663     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
664     lhq.replace = 1;
665     lhq.value = mtype;
666     lhq.proto = &nxt_http_static_mtypes_hash_proto;
667     lhq.pool = mp;
668 
669     return nxt_lvlhsh_insert(hash, &lhq);
670 }
671 
672 
673 nxt_str_t *
674 nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension)
675 {
676     nxt_lvlhsh_query_t       lhq;
677     nxt_http_static_mtype_t  *mtype;
678 
679     lhq.key = *extension;
680     lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length);
681     lhq.proto = &nxt_http_static_mtypes_hash_proto;
682 
683     if (nxt_lvlhsh_find(hash, &lhq) == NXT_OK) {
684         mtype = lhq.value;
685         return mtype->type;
686     }
687 
688     return NULL;
689 }
690 
691 
692 static nxt_int_t
693 nxt_http_static_mtypes_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
694 {
695     nxt_http_static_mtype_t  *mtype;
696 
697     mtype = data;
698 
699     return nxt_strcasestr_eq(&lhq->key, &mtype->extension) ? NXT_OK
700                                                            : NXT_DECLINED;
701 }
702 
703 
704 static void *
705 nxt_http_static_mtypes_hash_alloc(void *data, size_t size)
706 {
707     return nxt_mp_align(data, size, size);
708 }
709 
710 
711 static void
712 nxt_http_static_mtypes_hash_free(void *data, void *p)
713 {
714     nxt_mp_free(data, p);
715 }
716