LCOV - code coverage report
Current view: top level - lib - lwan-mod-serve-files.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 376 546 68.9 %
Date: 2023-04-18 16:19:03 Functions: 33 37 89.2 %

          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>&nbsp;</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);

Generated by: LCOV version 1.15-2-gb9d6727