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