Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2012 L. A. F. Pereira <l@tia.mat.br>
4 : *
5 : * This program is free software; you can redistribute it and/or
6 : * modify it under the terms of the GNU General Public License
7 : * as published by the Free Software Foundation; either version 2
8 : * of the License, or any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18 : * USA.
19 : */
20 :
21 : #define _GNU_SOURCE
22 : #include <dirent.h>
23 : #include <errno.h>
24 : #include <fcntl.h>
25 : #include <limits.h>
26 : #include <stdlib.h>
27 : #include <string.h>
28 : #include <sys/mman.h>
29 : #include <sys/stat.h>
30 : #include <zlib.h>
31 :
32 : #include "lwan-private.h"
33 :
34 : #include "hash.h"
35 : #include "realpathat.h"
36 : #include "lwan-cache.h"
37 : #include "lwan-config.h"
38 : #include "lwan-io-wrappers.h"
39 : #include "lwan-mod-serve-files.h"
40 : #include "lwan-template.h"
41 : #include "int-to-str.h"
42 :
43 : #include "auto-index-icons.h"
44 :
45 : #if defined(LWAN_HAVE_BROTLI)
46 : #include <brotli/encode.h>
47 : #endif
48 :
49 : #if defined(LWAN_HAVE_ZSTD)
50 : #include <zstd.h>
51 : #endif
52 :
53 : static const struct lwan_key_value deflate_compression_hdr[] = {
54 : {"Content-Encoding", "deflate"}, {}
55 : };
56 : static const struct lwan_key_value gzip_compression_hdr[] = {
57 : {"Content-Encoding", "gzip"}, {}
58 : };
59 : #if defined(LWAN_HAVE_BROTLI)
60 : static const struct lwan_key_value br_compression_hdr[] = {
61 : {"Content-Encoding", "br"}, {}
62 : };
63 : #endif
64 : #if defined(LWAN_HAVE_ZSTD)
65 : static const struct lwan_key_value zstd_compression_hdr[] = {
66 : {"Content-Encoding", "zstd"}, {}
67 : };
68 : #endif
69 :
70 : static const int open_mode = O_RDONLY | O_NONBLOCK | O_CLOEXEC;
71 :
72 : struct file_cache_entry;
73 :
74 : enum serve_files_priv_flags {
75 : SERVE_FILES_SERVE_PRECOMPRESSED = 1 << 0,
76 : SERVE_FILES_AUTO_INDEX = 1 << 1,
77 : SERVE_FILES_AUTO_INDEX_README = 1 << 2,
78 : };
79 :
80 : struct serve_files_priv {
81 : struct cache *cache;
82 :
83 : char *root_path;
84 : size_t root_path_len;
85 : int root_fd;
86 :
87 : enum serve_files_priv_flags flags;
88 :
89 : const char *index_html;
90 : char *prefix;
91 :
92 : struct lwan_tpl *directory_list_tpl;
93 :
94 : size_t read_ahead;
95 : };
96 :
97 : struct cache_funcs {
98 : enum lwan_http_status (*serve)(struct lwan_request *request, void *data);
99 : bool (*init)(struct file_cache_entry *ce,
100 : struct serve_files_priv *priv,
101 : const char *full_path,
102 : struct stat *st);
103 : void (*free)(struct file_cache_entry *ce);
104 : };
105 :
106 : struct mmap_cache_data {
107 : struct lwan_value uncompressed;
108 : struct lwan_value gzip;
109 : struct lwan_value deflated;
110 : #if defined(LWAN_HAVE_BROTLI)
111 : struct lwan_value brotli;
112 : #endif
113 : #if defined(LWAN_HAVE_ZSTD)
114 : struct lwan_value zstd;
115 : #endif
116 : };
117 :
118 : struct sendfile_cache_data {
119 : struct {
120 : int fd;
121 : size_t size;
122 : } compressed, uncompressed;
123 : };
124 :
125 : struct dir_list_cache_data {
126 : struct lwan_strbuf rendered;
127 : struct lwan_value deflated;
128 : #if defined(LWAN_HAVE_BROTLI)
129 : struct lwan_value brotli;
130 : #endif
131 : };
132 :
133 : struct redir_cache_data {
134 : char *redir_to;
135 : };
136 :
137 : struct file_cache_entry {
138 : struct cache_entry base;
139 :
140 : struct {
141 : char string[30];
142 : time_t integer;
143 : } last_modified;
144 :
145 : const char *mime_type;
146 : const struct cache_funcs *funcs;
147 :
148 : union {
149 : struct mmap_cache_data mmap_cache_data;
150 : struct sendfile_cache_data sendfile_cache_data;
151 : struct dir_list_cache_data dir_list_cache_data;
152 : struct redir_cache_data redir_cache_data;
153 : };
154 : };
155 :
156 : struct file_list {
157 : const char *full_path;
158 : const char *rel_path;
159 : const char *readme;
160 : struct {
161 : coro_function_t generator;
162 :
163 : const char *icon;
164 : const char *icon_alt;
165 : const char *name;
166 : const char *type;
167 :
168 : int size;
169 : const char *unit;
170 :
171 : const char *zebra_class;
172 : const char *slash_if_dir;
173 : } file_list;
174 : };
175 :
176 : static int directory_list_generator(struct coro *coro, void *data);
177 :
178 : static bool mmap_init(struct file_cache_entry *ce,
179 : struct serve_files_priv *priv,
180 : const char *full_path,
181 : struct stat *st);
182 : static void mmap_free(struct file_cache_entry *ce);
183 : static enum lwan_http_status mmap_serve(struct lwan_request *request,
184 : void *data);
185 :
186 : static bool sendfile_init(struct file_cache_entry *ce,
187 : struct serve_files_priv *priv,
188 : const char *full_path,
189 : struct stat *st);
190 : static void sendfile_free(struct file_cache_entry *ce);
191 : static enum lwan_http_status sendfile_serve(struct lwan_request *request,
192 : void *data);
193 :
194 : static bool dirlist_init(struct file_cache_entry *ce,
195 : struct serve_files_priv *priv,
196 : const char *full_path,
197 : struct stat *st);
198 : static void dirlist_free(struct file_cache_entry *ce);
199 : static enum lwan_http_status dirlist_serve(struct lwan_request *request,
200 : void *data);
201 :
202 : static bool redir_init(struct file_cache_entry *ce,
203 : struct serve_files_priv *priv,
204 : const char *full_path,
205 : struct stat *st);
206 : static void redir_free(struct file_cache_entry *ce);
207 : static enum lwan_http_status redir_serve(struct lwan_request *request,
208 : void *data);
209 :
210 : static const struct cache_funcs mmap_funcs = {
211 : .init = mmap_init,
212 : .free = mmap_free,
213 : .serve = mmap_serve,
214 : };
215 :
216 : static const struct cache_funcs sendfile_funcs = {
217 : .init = sendfile_init,
218 : .free = sendfile_free,
219 : .serve = sendfile_serve,
220 : };
221 :
222 : static const struct cache_funcs dirlist_funcs = {
223 : .init = dirlist_init,
224 : .free = dirlist_free,
225 : .serve = dirlist_serve,
226 : };
227 :
228 : static const struct cache_funcs redir_funcs = {
229 : .init = redir_init,
230 : .free = redir_free,
231 : .serve = redir_serve,
232 : };
233 :
234 : #undef TPL_STRUCT
235 : #define TPL_STRUCT struct file_list
236 : static const struct lwan_var_descriptor file_list_desc[] = {
237 : TPL_VAR_STR_ESCAPE(full_path),
238 : TPL_VAR_STR_ESCAPE(rel_path),
239 : TPL_VAR_STR_ESCAPE(readme),
240 : TPL_VAR_SEQUENCE(file_list,
241 : directory_list_generator,
242 : ((const struct lwan_var_descriptor[]){
243 : TPL_VAR_STR(file_list.icon),
244 : TPL_VAR_STR(file_list.icon_alt),
245 : TPL_VAR_STR(file_list.name),
246 : TPL_VAR_STR(file_list.type),
247 : TPL_VAR_INT(file_list.size),
248 : TPL_VAR_STR(file_list.unit),
249 : TPL_VAR_STR(file_list.zebra_class),
250 : TPL_VAR_STR(file_list.slash_if_dir),
251 : TPL_VAR_SENTINEL,
252 : })),
253 : TPL_VAR_SENTINEL,
254 : };
255 :
256 :
257 : static const char *directory_list_tpl_str =
258 : "<html>\n"
259 : "<head>\n"
260 : "{{rel_path?}} <title>Index of {{rel_path}}</title>{{/rel_path?}}\n"
261 : "{{^rel_path?}} <title>Index of /</title>{{/rel_path?}}\n"
262 : "<style>\n"
263 : " body { background: #fff }\n"
264 : " tr.odd>td { background: #fff }\n"
265 : " tr.even>td { background: #eee }\n"
266 : "</style>\n"
267 : "</head>\n"
268 : "<body>\n"
269 : "{{rel_path?}} <h1>Index of {{rel_path}}</h1>\n{{/rel_path?}}"
270 : "{{^rel_path?}} <h1>Index of /</h1>\n{{/rel_path?}}"
271 : "{{readme?}}<pre>{{readme}}</pre>\n{{/readme?}}"
272 : " <table>\n"
273 : " <tr>\n"
274 : " <td><img src=\"?icon=back\"></td>\n"
275 : " <td colspan=\"3\"><a href=\"..\">Parent directory</a></td>\n"
276 : " </tr>\n"
277 : " <tr>\n"
278 : " <td> </td>\n"
279 : " <th>File name</th>\n"
280 : " <th>Type</th>\n"
281 : " <th>Size</th>\n"
282 : " </tr>\n"
283 : "{{#file_list}}"
284 : " <tr class=\"{{file_list.zebra_class}}\">\n"
285 : " <td><img src=\"?icon={{file_list.icon}}\" "
286 : "alt=\"{{file_list.icon_alt}}\"></td>\n"
287 : " <td><a href=\"{{{file_list.name}}}{{file_list.slash_if_dir}}\">{{{file_list.name}}}</a></td>\n"
288 : " <td>{{file_list.type}}</td>\n"
289 : " <td align=\"right\"><tt>{{file_list.size}}{{file_list.unit}}</tt></td>\n"
290 : " </tr>\n"
291 : "{{/file_list}}"
292 : "{{^#file_list}}"
293 : " <tr>\n"
294 : " <td colspan=\"4\">Empty directory.</td>\n"
295 : " </tr>\n"
296 : "{{/file_list}}"
297 : " </table>\n"
298 : "</body>\n"
299 : "</html>\n";
300 :
301 4 : static int directory_list_generator(struct coro *coro, void *data)
302 : {
303 : static const char *zebra_classes[] = {"odd", "even"};
304 4 : struct file_list *fl = data;
305 : struct dirent *entry;
306 4 : int zebra_class = 0;
307 : DIR *dir;
308 : int fd;
309 :
310 4 : dir = opendir(fl->full_path);
311 4 : if (!dir)
312 0 : return 0;
313 :
314 4 : fd = dirfd(dir);
315 4 : if (fd < 0)
316 0 : goto out;
317 :
318 18 : while ((entry = readdir(dir))) {
319 : struct stat st;
320 :
321 16 : if (entry->d_name[0] == '.')
322 6 : continue;
323 :
324 10 : if (fstatat(fd, entry->d_name, &st, 0) < 0)
325 0 : continue;
326 :
327 10 : if (S_ISDIR(st.st_mode)) {
328 0 : fl->file_list.icon = "folder";
329 0 : fl->file_list.icon_alt = "DIR";
330 0 : fl->file_list.type = "directory";
331 0 : fl->file_list.slash_if_dir = "/";
332 10 : } else if (S_ISREG(st.st_mode)) {
333 10 : fl->file_list.icon = "file";
334 10 : fl->file_list.icon_alt = "FILE";
335 10 : fl->file_list.type =
336 10 : lwan_determine_mime_type_for_file_name(entry->d_name);
337 10 : fl->file_list.slash_if_dir = "";
338 : } else {
339 0 : continue;
340 : }
341 :
342 10 : if (st.st_size < 1024) {
343 10 : fl->file_list.size = (int)st.st_size;
344 10 : fl->file_list.unit = "B";
345 0 : } else if (st.st_size < 1024 * 1024) {
346 0 : fl->file_list.size = (int)(st.st_size / 1024);
347 0 : fl->file_list.unit = "KiB";
348 0 : } else if (st.st_size < 1024 * 1024 * 1024) {
349 0 : fl->file_list.size = (int)(st.st_size / (1024 * 1024));
350 0 : fl->file_list.unit = "MiB";
351 : } else {
352 0 : fl->file_list.size = (int)(st.st_size / (1024 * 1024 * 1024));
353 0 : fl->file_list.unit = "GiB";
354 : }
355 :
356 10 : fl->file_list.name = entry->d_name;
357 10 : fl->file_list.zebra_class = zebra_classes[zebra_class++ % 2];
358 :
359 10 : if (coro_yield(coro, 1))
360 2 : break;
361 : }
362 :
363 2 : out:
364 4 : closedir(dir);
365 4 : return 0;
366 : }
367 :
368 : static ALWAYS_INLINE bool is_compression_worthy(const size_t compressed_sz,
369 : const size_t uncompressed_sz)
370 : {
371 : /* FIXME: gzip encoding is also supported but not considered here */
372 : static const size_t deflated_header_size =
373 : sizeof("Content-Encoding: deflate\r\n") - 1;
374 17 : return ((compressed_sz + deflated_header_size) < uncompressed_sz);
375 : }
376 :
377 32 : static void realloc_if_needed(struct lwan_value *value, size_t bound)
378 : {
379 32 : if (bound > value->len) {
380 32 : char *tmp = realloc(value->value, value->len);
381 :
382 32 : if (tmp)
383 32 : value->value = tmp;
384 : }
385 32 : }
386 :
387 17 : static void deflate_value(const struct lwan_value *uncompressed,
388 : struct lwan_value *compressed)
389 : {
390 17 : const unsigned long bound = compressBound(uncompressed->len);
391 :
392 17 : compressed->len = bound;
393 :
394 17 : if (UNLIKELY(!(compressed->value = malloc(bound))))
395 0 : goto error_zero_out;
396 :
397 17 : if (UNLIKELY(compress((Bytef *)compressed->value, &compressed->len,
398 : (Bytef *)uncompressed->value,
399 : uncompressed->len) != Z_OK))
400 0 : goto error_free_compressed;
401 :
402 34 : if (is_compression_worthy(compressed->len, uncompressed->len))
403 16 : return realloc_if_needed(compressed, bound);
404 :
405 1 : error_free_compressed:
406 1 : free(compressed->value);
407 1 : compressed->value = NULL;
408 1 : error_zero_out:
409 1 : compressed->len = 0;
410 : }
411 :
412 : #if defined(LWAN_HAVE_BROTLI)
413 17 : static void brotli_value(const struct lwan_value *uncompressed,
414 : struct lwan_value *brotli,
415 : const struct lwan_value *deflated)
416 : {
417 : const unsigned long bound =
418 17 : BrotliEncoderMaxCompressedSize(uncompressed->len);
419 :
420 17 : brotli->len = bound;
421 :
422 17 : if (UNLIKELY(!(brotli->value = malloc(bound))))
423 0 : goto error_zero_out;
424 :
425 17 : if (UNLIKELY(
426 : BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW,
427 : BROTLI_DEFAULT_MODE, uncompressed->len,
428 : (uint8_t *)uncompressed->value, &brotli->len,
429 : (uint8_t *)brotli->value) != BROTLI_TRUE))
430 0 : goto error_free_compressed;
431 :
432 : /* is_compression_worthy() is already called for deflate-compressed data,
433 : * so only consider brotli-compressed data if it's worth it WRT deflate */
434 17 : if (LIKELY(brotli->len < deflated->len))
435 16 : return realloc_if_needed(brotli, bound);
436 :
437 1 : error_free_compressed:
438 1 : free(brotli->value);
439 1 : brotli->value = NULL;
440 1 : error_zero_out:
441 1 : brotli->len = 0;
442 : }
443 : #endif
444 :
445 : #if defined(LWAN_HAVE_ZSTD)
446 15 : static void zstd_value(const struct lwan_value *uncompressed,
447 : struct lwan_value *zstd,
448 : const struct lwan_value *deflated)
449 : {
450 15 : const size_t bound = ZSTD_compressBound(uncompressed->len);
451 :
452 15 : zstd->len = bound;
453 :
454 15 : if (UNLIKELY(!(zstd->value = malloc(zstd->len))))
455 0 : goto error_zero_out;
456 :
457 30 : zstd->len = ZSTD_compress(zstd->value, zstd->len, uncompressed->value,
458 15 : uncompressed->len, 1);
459 15 : if (UNLIKELY(ZSTD_isError(zstd->len)))
460 0 : goto error_free_compressed;
461 :
462 : /* is_compression_worthy() is already called for deflate-compressed data,
463 : * so only consider zstd-compressed data if it's worth it WRT deflate */
464 15 : if (LIKELY(zstd->len < deflated->len))
465 0 : return realloc_if_needed(zstd, bound);
466 :
467 15 : error_free_compressed:
468 15 : free(zstd->value);
469 15 : zstd->value = NULL;
470 15 : error_zero_out:
471 15 : zstd->len = 0;
472 : }
473 : #endif
474 :
475 : static void
476 10 : try_readahead(const struct serve_files_priv *priv, int fd, size_t size)
477 : {
478 10 : lwan_readahead_queue(fd, 0, LWAN_MIN(size, priv->read_ahead));
479 10 : }
480 :
481 34 : static inline bool is_world_readable(mode_t mode)
482 : {
483 34 : const mode_t world_readable = S_IRUSR | S_IRGRP | S_IROTH;
484 :
485 34 : return (mode & world_readable) == world_readable;
486 : }
487 :
488 25 : static int try_open_compressed(const char *relpath,
489 : const struct serve_files_priv *priv,
490 : const struct stat *uncompressed,
491 : size_t *compressed_sz)
492 : {
493 : char gzpath[PATH_MAX];
494 : struct stat st;
495 : int ret, fd;
496 :
497 : /* Try to serve a compressed file using sendfile() if $FILENAME.gz exists */
498 25 : ret = snprintf(gzpath, PATH_MAX, "%s.gz", relpath);
499 25 : if (UNLIKELY(ret < 0 || ret >= PATH_MAX))
500 0 : goto out;
501 :
502 25 : fd = openat(priv->root_fd, gzpath, open_mode);
503 25 : if (UNLIKELY(fd < 0))
504 25 : goto out;
505 :
506 0 : ret = fstat(fd, &st);
507 0 : if (UNLIKELY(ret < 0))
508 0 : goto close_and_out;
509 :
510 0 : if (UNLIKELY(st.st_mtime < uncompressed->st_mtime))
511 0 : goto close_and_out;
512 :
513 0 : if (UNLIKELY(!is_world_readable(st.st_mode)))
514 0 : goto close_and_out;
515 :
516 0 : if (LIKELY(is_compression_worthy((size_t)st.st_size,
517 : (size_t)uncompressed->st_size))) {
518 0 : *compressed_sz = (size_t)st.st_size;
519 :
520 0 : try_readahead(priv, fd, *compressed_sz);
521 :
522 0 : return fd;
523 : }
524 :
525 0 : close_and_out:
526 0 : close(fd);
527 25 : out:
528 25 : *compressed_sz = 0;
529 25 : return -ENOENT;
530 : }
531 :
532 30 : static bool mmap_fd(const struct serve_files_priv *priv,
533 : int fd,
534 : const size_t size,
535 : struct lwan_value *value)
536 : {
537 : void *ptr;
538 :
539 30 : if (UNLIKELY(fd < 0))
540 15 : goto fail;
541 :
542 15 : ptr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
543 15 : close(fd);
544 15 : if (UNLIKELY(ptr == MAP_FAILED))
545 0 : goto fail;
546 :
547 15 : *value = (struct lwan_value){.value = ptr, .len = size};
548 15 : return true;
549 :
550 15 : fail:
551 15 : *value = (struct lwan_value){};
552 15 : return false;
553 : }
554 :
555 15 : static bool mmap_init(struct file_cache_entry *ce,
556 : struct serve_files_priv *priv,
557 : const char *full_path,
558 : struct stat *st)
559 : {
560 15 : struct mmap_cache_data *md = &ce->mmap_cache_data;
561 15 : const char *path = full_path + priv->root_path_len;
562 : int file_fd;
563 :
564 15 : file_fd = openat(priv->root_fd, path, open_mode);
565 15 : if (UNLIKELY(file_fd < 0))
566 0 : return false;
567 15 : if (!mmap_fd(priv, file_fd, (size_t)st->st_size, &md->uncompressed))
568 0 : return false;
569 15 : lwan_madvise_queue(md->uncompressed.value, md->uncompressed.len);
570 :
571 15 : if (LIKELY(priv->flags & SERVE_FILES_SERVE_PRECOMPRESSED)) {
572 : size_t compressed_size;
573 :
574 15 : file_fd = try_open_compressed(path, priv, st, &compressed_size);
575 15 : mmap_fd(priv, file_fd, compressed_size, &md->gzip);
576 : } else {
577 0 : md->gzip = (struct lwan_value){};
578 : }
579 :
580 15 : md->uncompressed.len = (size_t)st->st_size;
581 15 : deflate_value(&md->uncompressed, &md->deflated);
582 : #if defined(LWAN_HAVE_BROTLI)
583 15 : brotli_value(&md->uncompressed, &md->brotli, &md->deflated);
584 : #endif
585 : #if defined(LWAN_HAVE_ZSTD)
586 15 : zstd_value(&md->uncompressed, &md->zstd, &md->deflated);
587 : #endif
588 :
589 15 : ce->mime_type =
590 15 : lwan_determine_mime_type_for_file_name(full_path + priv->root_path_len);
591 :
592 15 : return true;
593 : }
594 :
595 10 : static bool sendfile_init(struct file_cache_entry *ce,
596 : struct serve_files_priv *priv,
597 : const char *full_path,
598 : struct stat *st)
599 : {
600 10 : struct sendfile_cache_data *sd = &ce->sendfile_cache_data;
601 10 : const char *relpath = full_path + priv->root_path_len;
602 :
603 10 : ce->mime_type = lwan_determine_mime_type_for_file_name(relpath);
604 :
605 10 : sd->uncompressed.fd = openat(priv->root_fd, relpath, open_mode);
606 10 : if (UNLIKELY(sd->uncompressed.fd < 0)) {
607 0 : switch (errno) {
608 0 : case ENFILE:
609 : case EMFILE:
610 : case EACCES:
611 : /* These errors should produce responses other than 404, so
612 : * store errno as the file descriptor. */
613 0 : sd->uncompressed.fd = sd->compressed.fd = -errno;
614 0 : sd->compressed.size = sd->uncompressed.size = 0;
615 :
616 0 : return true;
617 : }
618 :
619 0 : return false;
620 : }
621 :
622 : /* If precompressed files can be served, try opening it */
623 10 : if (LIKELY(priv->flags & SERVE_FILES_SERVE_PRECOMPRESSED)) {
624 : size_t compressed_sz;
625 10 : int fd = try_open_compressed(relpath, priv, st, &compressed_sz);
626 :
627 10 : sd->compressed.fd = fd;
628 10 : sd->compressed.size = compressed_sz;
629 : }
630 :
631 10 : sd->uncompressed.size = (size_t)st->st_size;
632 10 : try_readahead(priv, sd->uncompressed.fd, sd->uncompressed.size);
633 :
634 10 : return true;
635 : }
636 :
637 5 : static const char *get_rel_path(const char *full_path,
638 : struct serve_files_priv *priv)
639 : {
640 5 : const char *root_path = full_path + priv->root_path_len;
641 :
642 5 : if (priv->root_path_len == 1) {
643 : /* If root path length is 1, it's actually "/". Don't skip
644 : * the first forward slash if serving from root directory. */
645 0 : root_path--;
646 : }
647 :
648 5 : if (*root_path)
649 5 : return root_path;
650 :
651 0 : if (streq(priv->prefix, "/"))
652 0 : return "";
653 :
654 0 : return priv->prefix;
655 : }
656 :
657 2 : static const char *dirlist_find_readme(struct lwan_strbuf *readme,
658 : struct serve_files_priv *priv,
659 : const char *full_path)
660 : {
661 : static const char *candidates[] = {"readme", "readme.txt", "read.me",
662 : "README.TXT", "README"};
663 :
664 2 : if (!(priv->flags & SERVE_FILES_AUTO_INDEX_README))
665 0 : return NULL;
666 :
667 8 : for (size_t i = 0; i < N_ELEMENTS(candidates); i++) {
668 : char readme_path[PATH_MAX];
669 : int r;
670 :
671 8 : r = snprintf(readme_path, PATH_MAX, "%s/%s", full_path, candidates[i]);
672 8 : if (r < 0 || r >= PATH_MAX)
673 0 : continue;
674 :
675 8 : if (lwan_strbuf_init_from_file(readme, readme_path))
676 2 : return lwan_strbuf_get_buffer(readme);
677 : }
678 :
679 0 : return NULL;
680 : }
681 :
682 2 : static bool dirlist_init(struct file_cache_entry *ce,
683 : struct serve_files_priv *priv,
684 : const char *full_path,
685 : struct stat *st __attribute__((unused)))
686 : {
687 2 : struct dir_list_cache_data *dd = &ce->dir_list_cache_data;
688 : struct lwan_strbuf readme;
689 2 : bool ret = false;
690 :
691 2 : if (!lwan_strbuf_init(&readme))
692 0 : return false;
693 2 : if (!lwan_strbuf_init(&dd->rendered))
694 0 : goto out_free_readme;
695 :
696 6 : struct file_list vars = {
697 : .full_path = full_path,
698 2 : .rel_path = get_rel_path(full_path, priv),
699 2 : .readme = dirlist_find_readme(&readme, priv, full_path),
700 : };
701 :
702 2 : if (!lwan_tpl_apply_with_buffer(priv->directory_list_tpl, &dd->rendered,
703 : &vars))
704 0 : goto out_free_rendered;
705 :
706 2 : ce->mime_type = "text/html";
707 :
708 4 : struct lwan_value rendered = {
709 2 : .value = lwan_strbuf_get_buffer(&dd->rendered),
710 2 : .len = lwan_strbuf_get_length(&dd->rendered),
711 : };
712 2 : deflate_value(&rendered, &dd->deflated);
713 : #if defined(LWAN_HAVE_BROTLI)
714 2 : brotli_value(&rendered, &dd->brotli, &dd->deflated);
715 : #endif
716 :
717 2 : ret = true;
718 2 : goto out_free_readme;
719 :
720 0 : out_free_rendered:
721 0 : lwan_strbuf_free(&dd->rendered);
722 2 : out_free_readme:
723 2 : lwan_strbuf_free(&readme);
724 2 : return ret;
725 : }
726 :
727 3 : static bool redir_init(struct file_cache_entry *ce,
728 : struct serve_files_priv *priv,
729 : const char *full_path,
730 : struct stat *st __attribute__((unused)))
731 : {
732 3 : struct redir_cache_data *rd = &ce->redir_cache_data;
733 :
734 3 : return asprintf(&rd->redir_to, "%s%s/", priv->prefix,
735 3 : get_rel_path(full_path, priv)) >= 0;
736 : }
737 :
738 30 : static const struct cache_funcs *get_funcs(struct serve_files_priv *priv,
739 : const char *key,
740 : char *full_path,
741 : struct stat *st)
742 : {
743 : char index_html_path_buf[PATH_MAX];
744 30 : char *index_html_path = index_html_path_buf;
745 :
746 30 : if (S_ISDIR(st->st_mode)) {
747 : size_t index_html_path_len;
748 :
749 : /* It is a directory. It might be the root directory (empty key), or
750 : * something else. In either case, tack priv->index_html to the
751 : * path. */
752 9 : if (*key == '\0') {
753 4 : index_html_path = (char *)priv->index_html;
754 4 : index_html_path_len = strlen(index_html_path);
755 : } else {
756 : /* Redirect /path to /path/. This is to help cases where there's
757 : * something like <img src="../foo.png">, so that actually
758 : * /path/../foo.png is served instead of /path../foo.png. */
759 5 : const char *key_end = key + strlen(key);
760 5 : if (*(key_end - 1) != '/')
761 3 : return &redir_funcs;
762 :
763 2 : int ret = snprintf(index_html_path, PATH_MAX, "%s%s", key,
764 : priv->index_html);
765 2 : if (UNLIKELY(ret < 0 || ret >= PATH_MAX))
766 0 : return NULL;
767 :
768 2 : index_html_path_len = (size_t)ret;
769 : }
770 :
771 : /* See if it exists. */
772 6 : if (fstatat(priv->root_fd, index_html_path, st, 0) < 0) {
773 2 : if (UNLIKELY(errno != ENOENT))
774 0 : return NULL;
775 :
776 2 : if (LIKELY(priv->flags & SERVE_FILES_AUTO_INDEX)) {
777 : /* If it doesn't, we want to generate a directory list. */
778 2 : return &dirlist_funcs;
779 : }
780 :
781 : /* Auto index is disabled. */
782 0 : return NULL;
783 : }
784 :
785 : /* Only serve world-readable indexes. */
786 4 : if (UNLIKELY(!is_world_readable(st->st_mode)))
787 0 : return NULL;
788 :
789 : /* If it does, we want its full path. */
790 4 : if (UNLIKELY(priv->root_path_len + index_html_path_len + 1 >= PATH_MAX))
791 0 : return NULL;
792 :
793 4 : strncpy(full_path + priv->root_path_len, index_html_path,
794 4 : PATH_MAX - priv->root_path_len);
795 : }
796 :
797 : /* Only serve regular files. */
798 25 : if (UNLIKELY(!S_ISREG(st->st_mode)))
799 0 : return NULL;
800 :
801 : /* It's not a directory: choose the fastest way to serve the file
802 : * judging by its size. */
803 25 : if (st->st_size < 16384)
804 15 : return &mmap_funcs;
805 :
806 10 : return &sendfile_funcs;
807 : }
808 :
809 : static struct file_cache_entry *
810 30 : create_cache_entry_from_funcs(struct serve_files_priv *priv,
811 : const char *full_path,
812 : struct stat *st,
813 : const struct cache_funcs *funcs)
814 : {
815 : struct file_cache_entry *fce;
816 :
817 30 : fce = malloc(sizeof(*fce));
818 30 : if (UNLIKELY(!fce))
819 0 : return NULL;
820 :
821 30 : if (LIKELY(funcs->init(fce, priv, full_path, st))) {
822 30 : fce->funcs = funcs;
823 30 : return fce;
824 : }
825 :
826 0 : free(fce);
827 :
828 0 : if (funcs != &mmap_funcs)
829 0 : return NULL;
830 :
831 0 : return create_cache_entry_from_funcs(priv, full_path, st, &sendfile_funcs);
832 : }
833 :
834 2 : static void destroy_cache_entry(struct cache_entry *entry,
835 : void *context __attribute__((unused)))
836 : {
837 2 : struct file_cache_entry *fce = (struct file_cache_entry *)entry;
838 :
839 2 : fce->funcs->free(fce);
840 2 : free(fce);
841 2 : }
842 :
843 35 : static struct cache_entry *create_cache_entry(const void *key, void *context)
844 : {
845 35 : struct serve_files_priv *priv = context;
846 : struct file_cache_entry *fce;
847 : struct stat st;
848 : const struct cache_funcs *funcs;
849 : char full_path[PATH_MAX];
850 :
851 35 : if (UNLIKELY(
852 : !realpathat2(priv->root_fd, priv->root_path, key, full_path, &st)))
853 5 : return NULL;
854 :
855 30 : if (UNLIKELY(!is_world_readable(st.st_mode)))
856 0 : return NULL;
857 :
858 30 : if (UNLIKELY(strncmp(full_path, priv->root_path, priv->root_path_len)))
859 0 : return NULL;
860 :
861 30 : funcs = get_funcs(priv, key, full_path, &st);
862 30 : if (UNLIKELY(!funcs))
863 0 : return NULL;
864 :
865 30 : fce = create_cache_entry_from_funcs(priv, full_path, &st, funcs);
866 30 : if (UNLIKELY(!fce))
867 0 : return NULL;
868 :
869 30 : if (UNLIKELY(lwan_format_rfc_time(st.st_mtime, fce->last_modified.string) <
870 : 0)) {
871 0 : destroy_cache_entry((struct cache_entry *)fce, NULL);
872 0 : return NULL;
873 : }
874 30 : fce->last_modified.integer = st.st_mtime;
875 :
876 30 : return (struct cache_entry *)fce;
877 : }
878 :
879 2 : static void mmap_free(struct file_cache_entry *fce)
880 : {
881 2 : struct mmap_cache_data *md = &fce->mmap_cache_data;
882 :
883 2 : munmap(md->uncompressed.value, md->uncompressed.len);
884 2 : if (md->gzip.value)
885 0 : munmap(md->gzip.value, md->gzip.len);
886 2 : free(md->deflated.value);
887 : #if defined(LWAN_HAVE_BROTLI)
888 2 : free(md->brotli.value);
889 : #endif
890 : #if defined(LWAN_HAVE_ZSTD)
891 2 : free(md->zstd.value);
892 : #endif
893 2 : }
894 :
895 0 : static void sendfile_free(struct file_cache_entry *fce)
896 : {
897 0 : struct sendfile_cache_data *sd = &fce->sendfile_cache_data;
898 :
899 0 : if (sd->compressed.fd >= 0)
900 0 : close(sd->compressed.fd);
901 0 : if (sd->uncompressed.fd >= 0)
902 0 : close(sd->uncompressed.fd);
903 0 : }
904 :
905 0 : static void dirlist_free(struct file_cache_entry *fce)
906 : {
907 0 : struct dir_list_cache_data *dd = &fce->dir_list_cache_data;
908 :
909 0 : lwan_strbuf_free(&dd->rendered);
910 0 : free(dd->deflated.value);
911 : #if defined(LWAN_HAVE_BROTLI)
912 0 : free(dd->brotli.value);
913 : #endif
914 0 : }
915 :
916 0 : static void redir_free(struct file_cache_entry *fce)
917 : {
918 0 : struct redir_cache_data *rd = &fce->redir_cache_data;
919 :
920 0 : free(rd->redir_to);
921 0 : }
922 :
923 87 : static char *get_real_root_path(const char *root_path)
924 : {
925 : char path_buf[PATH_MAX];
926 : char *path;
927 :
928 87 : path = realpath(root_path, path_buf);
929 87 : if (!path)
930 0 : return NULL;
931 :
932 87 : char *last_slash = strrchr(path, '/');
933 87 : if (!last_slash)
934 0 : return NULL;
935 :
936 87 : if (*(last_slash + 1) == '\0')
937 0 : return strdup(path);
938 :
939 : char *ret;
940 87 : if (asprintf(&ret, "%s/", path))
941 87 : return ret;
942 :
943 0 : return NULL;
944 : }
945 :
946 87 : static void *serve_files_create(const char *prefix, void *args)
947 : {
948 87 : struct lwan_serve_files_settings *settings = args;
949 : struct serve_files_priv *priv;
950 : char *canonical_root;
951 : int root_fd;
952 :
953 87 : if (!settings->root_path) {
954 0 : lwan_status_error("root_path not specified");
955 0 : return NULL;
956 : }
957 :
958 87 : canonical_root = get_real_root_path(settings->root_path);
959 87 : if (!canonical_root) {
960 0 : lwan_status_perror("Could not obtain real path of \"%s\"",
961 : settings->root_path);
962 0 : goto out_realpath;
963 : }
964 :
965 87 : root_fd = open(canonical_root, open_mode | O_DIRECTORY | O_PATH);
966 87 : if (root_fd < 0) {
967 0 : lwan_status_perror("Could not open directory \"%s\"", canonical_root);
968 0 : goto out_open;
969 : }
970 :
971 87 : priv = malloc(sizeof(*priv));
972 87 : if (!priv) {
973 0 : lwan_status_perror("malloc");
974 0 : goto out_malloc;
975 : }
976 :
977 87 : priv->cache = cache_create(create_cache_entry, destroy_cache_entry, priv,
978 : settings->cache_for);
979 87 : if (!priv->cache) {
980 0 : lwan_status_error("Couldn't create cache");
981 0 : goto out_cache_create;
982 : }
983 :
984 87 : if (settings->directory_list_template) {
985 0 : priv->directory_list_tpl = lwan_tpl_compile_file(
986 : settings->directory_list_template, file_list_desc);
987 : } else {
988 87 : priv->directory_list_tpl =
989 87 : lwan_tpl_compile_string_full(directory_list_tpl_str, file_list_desc,
990 : LWAN_TPL_FLAG_CONST_TEMPLATE);
991 : }
992 87 : if (!priv->directory_list_tpl) {
993 0 : lwan_status_error("Could not compile directory list template");
994 0 : goto out_tpl_compile;
995 : }
996 :
997 87 : priv->prefix = strdup(prefix);
998 87 : if (!priv->prefix) {
999 0 : lwan_status_error("Could not copy prefix");
1000 0 : goto out_tpl_prefix_copy;
1001 : }
1002 :
1003 87 : priv->root_path = canonical_root;
1004 87 : priv->root_path_len = strlen(canonical_root);
1005 87 : priv->root_fd = root_fd;
1006 87 : priv->index_html =
1007 87 : settings->index_html ? settings->index_html : "index.html";
1008 :
1009 87 : priv->read_ahead = settings->read_ahead;
1010 :
1011 87 : if (settings->serve_precompressed_files)
1012 87 : priv->flags |= SERVE_FILES_SERVE_PRECOMPRESSED;
1013 87 : if (settings->auto_index)
1014 87 : priv->flags |= SERVE_FILES_AUTO_INDEX;
1015 87 : if (settings->auto_index_readme)
1016 87 : priv->flags |= SERVE_FILES_AUTO_INDEX_README;
1017 :
1018 87 : return priv;
1019 :
1020 0 : out_tpl_prefix_copy:
1021 0 : out_tpl_compile:
1022 0 : cache_destroy(priv->cache);
1023 0 : out_cache_create:
1024 0 : free(priv);
1025 0 : out_malloc:
1026 0 : close(root_fd);
1027 0 : out_open:
1028 0 : free(canonical_root);
1029 0 : out_realpath:
1030 0 : return NULL;
1031 : }
1032 :
1033 87 : static void *serve_files_create_from_hash(const char *prefix,
1034 : const struct hash *hash)
1035 : {
1036 696 : struct lwan_serve_files_settings settings = {
1037 87 : .root_path = hash_find(hash, "path"),
1038 87 : .index_html = hash_find(hash, "index_path"),
1039 : .serve_precompressed_files =
1040 87 : parse_bool(hash_find(hash, "serve_precompressed_files"), true),
1041 87 : .auto_index = parse_bool(hash_find(hash, "auto_index"), true),
1042 87 : .directory_list_template = hash_find(hash, "directory_list_template"),
1043 87 : .read_ahead = (size_t)parse_long(hash_find(hash, "read_ahead"),
1044 : SERVE_FILES_READ_AHEAD_BYTES),
1045 : .auto_index_readme =
1046 87 : parse_bool(hash_find(hash, "auto_index_readme"), true),
1047 87 : .cache_for = (time_t)parse_time_period(hash_find(hash, "cache_for"),
1048 : SERVE_FILES_CACHE_FOR),
1049 : };
1050 :
1051 87 : return serve_files_create(prefix, &settings);
1052 : }
1053 :
1054 0 : static void serve_files_destroy(void *data)
1055 : {
1056 0 : struct serve_files_priv *priv = data;
1057 :
1058 0 : if (!priv) {
1059 0 : lwan_status_warning("Nothing to shutdown");
1060 0 : return;
1061 : }
1062 :
1063 0 : lwan_tpl_free(priv->directory_list_tpl);
1064 0 : cache_destroy(priv->cache);
1065 0 : close(priv->root_fd);
1066 0 : free(priv->root_path);
1067 0 : free(priv->prefix);
1068 0 : free(priv);
1069 : }
1070 :
1071 : static ALWAYS_INLINE bool client_has_fresh_content(struct lwan_request *request,
1072 : time_t mtime)
1073 : {
1074 : time_t header;
1075 46 : int r = lwan_request_get_if_modified_since(request, &header);
1076 :
1077 46 : return LIKELY(!r) ? mtime <= header : false;
1078 : }
1079 :
1080 7 : static size_t prepare_headers(struct lwan_request *request,
1081 : enum lwan_http_status return_status,
1082 : struct file_cache_entry *fce,
1083 : size_t size,
1084 : const struct lwan_key_value *user_hdr,
1085 : char header_buf[static DEFAULT_HEADERS_SIZE])
1086 : {
1087 : char content_length[INT_TO_STR_BUFFER_SIZE];
1088 : size_t discard;
1089 14 : struct lwan_key_value additional_headers[4] = {
1090 : {
1091 : .key = "Last-Modified",
1092 7 : .value = fce->last_modified.string,
1093 : },
1094 : {
1095 : .key = "Content-Length",
1096 7 : .value = uint_to_string(size, content_length, &discard),
1097 : },
1098 : };
1099 :
1100 7 : if (user_hdr)
1101 0 : additional_headers[2] = *user_hdr;
1102 :
1103 7 : return lwan_prepare_response_header_full(request, return_status, header_buf,
1104 : DEFAULT_HEADERS_SIZE,
1105 : additional_headers);
1106 : }
1107 :
1108 : static enum lwan_http_status
1109 15 : compute_range(struct lwan_request *request, off_t *from, off_t *to, off_t size)
1110 : {
1111 : off_t f, t;
1112 15 : int r = lwan_request_get_range(request, &f, &t);
1113 :
1114 : /* No Range: header present */
1115 15 : if (LIKELY(r < 0 || (f < 0 && t < 0))) {
1116 9 : *from = 0;
1117 9 : *to = size;
1118 :
1119 9 : return HTTP_OK;
1120 : }
1121 :
1122 : /* To must be greater than From; it doesn't make any sense to be
1123 : * equal, either. */
1124 6 : if (UNLIKELY(f >= t && t >= 0))
1125 2 : return HTTP_RANGE_UNSATISFIABLE;
1126 :
1127 : /* Range goes beyond the size of the file */
1128 4 : if (UNLIKELY(f >= size || t >= size))
1129 1 : return HTTP_RANGE_UNSATISFIABLE;
1130 :
1131 : /* t < 0: ranges from f to the file size */
1132 3 : if (t < 0) {
1133 1 : *to = size;
1134 : } else {
1135 2 : if (UNLIKELY(__builtin_sub_overflow(t, f, to)))
1136 0 : return HTTP_RANGE_UNSATISFIABLE;
1137 : }
1138 :
1139 3 : *from = f;
1140 :
1141 3 : return HTTP_PARTIAL_CONTENT;
1142 : }
1143 :
1144 64 : static inline bool accepts_encoding(struct lwan_request *request,
1145 : const enum lwan_request_flags encoding)
1146 : {
1147 64 : return lwan_request_get_accept_encoding(request) & encoding;
1148 : }
1149 :
1150 10 : static enum lwan_http_status sendfile_serve(struct lwan_request *request,
1151 : void *data)
1152 : {
1153 : const struct lwan_key_value *compression_hdr;
1154 10 : struct file_cache_entry *fce = data;
1155 10 : struct sendfile_cache_data *sd = &fce->sendfile_cache_data;
1156 : char headers[DEFAULT_HEADERS_SIZE];
1157 : size_t header_len;
1158 : enum lwan_http_status return_status;
1159 : off_t from, to;
1160 : size_t size;
1161 : int fd;
1162 :
1163 10 : if (sd->compressed.size && accepts_encoding(request, REQUEST_ACCEPT_GZIP)) {
1164 0 : from = 0;
1165 0 : to = (off_t)sd->compressed.size;
1166 :
1167 0 : compression_hdr = gzip_compression_hdr;
1168 0 : fd = sd->compressed.fd;
1169 0 : size = sd->compressed.size;
1170 :
1171 0 : return_status = HTTP_OK;
1172 : } else {
1173 : return_status =
1174 10 : compute_range(request, &from, &to, (off_t)sd->uncompressed.size);
1175 10 : if (UNLIKELY(return_status == HTTP_RANGE_UNSATISFIABLE))
1176 3 : return HTTP_RANGE_UNSATISFIABLE;
1177 :
1178 7 : compression_hdr = NULL;
1179 7 : fd = sd->uncompressed.fd;
1180 7 : size = (size_t)(to - from);
1181 : }
1182 7 : if (UNLIKELY(fd < 0)) {
1183 0 : switch (-fd) {
1184 0 : case EACCES:
1185 0 : return HTTP_FORBIDDEN;
1186 0 : case EMFILE:
1187 : case ENFILE:
1188 0 : return HTTP_UNAVAILABLE;
1189 0 : default:
1190 0 : return HTTP_INTERNAL_ERROR;
1191 : }
1192 : }
1193 :
1194 7 : header_len = prepare_headers(request, return_status, fce, size,
1195 : compression_hdr, headers);
1196 7 : if (UNLIKELY(!header_len))
1197 0 : return HTTP_INTERNAL_ERROR;
1198 :
1199 7 : if (lwan_request_get_method(request) == REQUEST_METHOD_HEAD) {
1200 2 : lwan_send(request, headers, header_len, 0);
1201 : } else {
1202 5 : lwan_sendfile(request, fd, from, (size_t)to, headers, header_len);
1203 : }
1204 :
1205 6 : return return_status;
1206 : }
1207 :
1208 33 : static enum lwan_http_status serve_buffer(struct lwan_request *request,
1209 : const char *mime_type,
1210 : const void *buffer,
1211 : size_t buffer_len,
1212 : const struct lwan_key_value *headers,
1213 : enum lwan_http_status status_code)
1214 : {
1215 33 : request->response.mime_type = mime_type;
1216 33 : request->response.headers = headers;
1217 :
1218 33 : lwan_strbuf_set_static(request->response.buffer, buffer, buffer_len);
1219 :
1220 33 : return status_code;
1221 : }
1222 :
1223 : static ALWAYS_INLINE enum lwan_http_status
1224 : serve_value(struct lwan_request *request,
1225 : const char *mime_type,
1226 : const struct lwan_value *value,
1227 : const struct lwan_key_value *headers,
1228 : enum lwan_http_status status_code)
1229 : {
1230 27 : return serve_buffer(request, mime_type, value->value, value->len, headers,
1231 : status_code);
1232 : }
1233 :
1234 : static ALWAYS_INLINE enum lwan_http_status
1235 : serve_value_ok(struct lwan_request *request,
1236 : const char *mime_type,
1237 : const struct lwan_value *value,
1238 : const struct lwan_key_value *headers)
1239 : {
1240 27 : return serve_value(request, mime_type, value, headers, HTTP_OK);
1241 : }
1242 :
1243 : static const struct lwan_value *
1244 31 : mmap_best_data(struct lwan_request *request,
1245 : struct mmap_cache_data *md,
1246 : const struct lwan_key_value **header)
1247 : {
1248 31 : const struct lwan_value *best = &md->uncompressed;
1249 :
1250 31 : *header = NULL;
1251 :
1252 : #if defined(LWAN_HAVE_ZSTD)
1253 31 : if (md->zstd.len && md->zstd.len < best->len &&
1254 0 : accepts_encoding(request, REQUEST_ACCEPT_ZSTD)) {
1255 0 : best = &md->zstd;
1256 0 : *header = zstd_compression_hdr;
1257 : }
1258 : #endif
1259 :
1260 : #if defined(LWAN_HAVE_BROTLI)
1261 61 : if (md->brotli.len && md->brotli.len < best->len &&
1262 30 : accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) {
1263 0 : best = &md->brotli;
1264 0 : *header = br_compression_hdr;
1265 : }
1266 : #endif
1267 :
1268 31 : if (md->gzip.len && md->gzip.len < best->len &&
1269 0 : accepts_encoding(request, REQUEST_ACCEPT_GZIP)) {
1270 0 : best = &md->gzip;
1271 0 : *header = gzip_compression_hdr;
1272 : }
1273 :
1274 61 : if (md->deflated.len && md->deflated.len < best->len &&
1275 30 : accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) {
1276 26 : best = &md->deflated;
1277 26 : *header = deflate_compression_hdr;
1278 : }
1279 :
1280 31 : return best;
1281 : }
1282 :
1283 31 : static enum lwan_http_status mmap_serve(struct lwan_request *request,
1284 : void *data)
1285 : {
1286 31 : struct file_cache_entry *fce = data;
1287 31 : struct mmap_cache_data *md = &fce->mmap_cache_data;
1288 : const struct lwan_key_value *compression_hdr;
1289 : const struct lwan_value *to_serve =
1290 31 : mmap_best_data(request, md, &compression_hdr);
1291 :
1292 31 : if (compression_hdr)
1293 52 : return serve_value_ok(request, fce->mime_type, to_serve,
1294 : compression_hdr);
1295 :
1296 : off_t from, to;
1297 : enum lwan_http_status status =
1298 5 : compute_range(request, &from, &to, (off_t)to_serve->len);
1299 5 : if (status != HTTP_OK && status != HTTP_PARTIAL_CONTENT)
1300 0 : return status;
1301 :
1302 5 : return serve_buffer(request, fce->mime_type, (char *)to_serve->value + from,
1303 5 : (size_t)(to - from), NULL, status);
1304 : }
1305 :
1306 2 : static enum lwan_http_status dirlist_serve(struct lwan_request *request,
1307 : void *data)
1308 : {
1309 2 : struct file_cache_entry *fce = data;
1310 2 : struct dir_list_cache_data *dd = &fce->dir_list_cache_data;
1311 2 : const char *icon = lwan_request_get_query_param(request, "icon");
1312 :
1313 2 : if (!icon) {
1314 : #if defined(LWAN_HAVE_BROTLI)
1315 2 : if (dd->brotli.len && accepts_encoding(request, REQUEST_ACCEPT_BROTLI)) {
1316 0 : return serve_value_ok(request, fce->mime_type, &dd->brotli,
1317 : br_compression_hdr);
1318 : }
1319 : #endif
1320 :
1321 2 : if (dd->deflated.len && accepts_encoding(request, REQUEST_ACCEPT_DEFLATE)) {
1322 2 : return serve_value_ok(request, fce->mime_type, &dd->deflated,
1323 : deflate_compression_hdr);
1324 : }
1325 :
1326 1 : return serve_buffer(
1327 1 : request, fce->mime_type, lwan_strbuf_get_buffer(&dd->rendered),
1328 1 : lwan_strbuf_get_length(&dd->rendered), NULL, HTTP_OK);
1329 : }
1330 :
1331 0 : STRING_SWITCH (icon) {
1332 0 : case STR4_INT('b', 'a', 'c', 'k'):
1333 0 : return serve_value_ok(request, "image/gif", &back_gif_value, NULL);
1334 :
1335 0 : case STR4_INT('f', 'i', 'l', 'e'):
1336 0 : return serve_value_ok(request, "image/gif", &file_gif_value, NULL);
1337 :
1338 0 : case STR4_INT('f', 'o', 'l', 'd'):
1339 0 : return serve_value_ok(request, "image/gif", &folder_gif_value, NULL);
1340 : }
1341 :
1342 0 : return HTTP_NOT_FOUND;
1343 : }
1344 :
1345 3 : static enum lwan_http_status redir_serve(struct lwan_request *request,
1346 : void *data)
1347 : {
1348 3 : struct file_cache_entry *fce = data;
1349 3 : struct redir_cache_data *rd = &fce->redir_cache_data;
1350 3 : struct lwan_key_value headers[] = {{"Location", rd->redir_to}, {}};
1351 :
1352 3 : lwan_strbuf_set_staticz(request->response.buffer, rd->redir_to);
1353 3 : request->response.mime_type = "text/plain";
1354 3 : request->response.headers =
1355 3 : coro_memdup(request->conn->coro, headers, sizeof(headers));
1356 :
1357 3 : return request->response.headers ? HTTP_MOVED_PERMANENTLY
1358 3 : : HTTP_INTERNAL_ERROR;
1359 : }
1360 :
1361 : static enum lwan_http_status
1362 51 : serve_files_handle_request(struct lwan_request *request,
1363 : struct lwan_response *response,
1364 : void *instance)
1365 : {
1366 51 : struct serve_files_priv *priv = instance;
1367 : struct file_cache_entry *fce;
1368 : struct cache_entry *ce;
1369 :
1370 51 : ce = cache_coro_get_and_ref_entry(priv->cache, request->conn->coro,
1371 51 : request->url.value);
1372 51 : if (UNLIKELY(!ce))
1373 5 : return HTTP_NOT_FOUND;
1374 :
1375 46 : fce = (struct file_cache_entry *)ce;
1376 92 : if (client_has_fresh_content(request, fce->last_modified.integer))
1377 0 : return HTTP_NOT_MODIFIED;
1378 :
1379 46 : if (fce->funcs->serve == sendfile_serve) {
1380 10 : response->mime_type = fce->mime_type;
1381 10 : response->stream.callback = fce->funcs->serve;
1382 10 : response->stream.data = fce;
1383 :
1384 10 : request->flags |= RESPONSE_STREAM;
1385 :
1386 10 : return HTTP_OK;
1387 : }
1388 :
1389 36 : return fce->funcs->serve(request, fce);
1390 : }
1391 :
1392 : static const struct lwan_module module = {
1393 : .create = serve_files_create,
1394 : .create_from_hash = serve_files_create_from_hash,
1395 : .destroy = serve_files_destroy,
1396 : .handle_request = serve_files_handle_request,
1397 : };
1398 :
1399 : LWAN_REGISTER_MODULE(serve_files, &module);
|