LCOV - code coverage report
Current view: top level - lib - lwan-mod-rewrite.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 261 491 53.2 %
Date: 2023-04-18 16:19:03 Functions: 19 23 82.6 %

          Line data    Source code
       1             : /*
       2             :  * lwan - web server
       3             :  * Copyright (c) 2015 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 <ctype.h>
      23             : #include <limits.h>
      24             : #include <stdlib.h>
      25             : #include <string.h>
      26             : #include <sys/stat.h>
      27             : 
      28             : #include "lwan-private.h"
      29             : 
      30             : #include "patterns.h"
      31             : #include "lwan-array.h"
      32             : #include "lwan-mod-rewrite.h"
      33             : #include "lwan-strbuf.h"
      34             : 
      35             : #ifdef LWAN_HAVE_LUA
      36             : #include <lauxlib.h>
      37             : #include <lua.h>
      38             : #include <lualib.h>
      39             : 
      40             : #include "lwan-lua.h"
      41             : #endif
      42             : 
      43             : enum pattern_flag {
      44             :     PATTERN_HANDLE_REWRITE = 1 << 0,
      45             :     PATTERN_HANDLE_REDIRECT = 1 << 1,
      46             :     PATTERN_HANDLE_MASK = PATTERN_HANDLE_REWRITE | PATTERN_HANDLE_REDIRECT,
      47             : 
      48             :     PATTERN_EXPAND_LWAN = 1 << 2,
      49             :     PATTERN_EXPAND_LUA = 1 << 3,
      50             :     PATTERN_EXPAND_MASK = PATTERN_EXPAND_LWAN | PATTERN_EXPAND_LUA,
      51             : 
      52             :     PATTERN_COND_COOKIE = 1 << 4,
      53             :     PATTERN_COND_ENV_VAR = 1 << 5,
      54             :     PATTERN_COND_STAT = 1 << 6,
      55             :     PATTERN_COND_QUERY_VAR = 1 << 7,
      56             :     PATTERN_COND_POST_VAR = 1 << 8,
      57             :     PATTERN_COND_HEADER = 1 << 9,
      58             :     PATTERN_COND_LUA = 1 << 10,
      59             :     PATTERN_COND_METHOD = 1 << 11,
      60             :     PATTERN_COND_ACCEPT_ENCODING = 1 << 12,
      61             :     PATTERN_COND_PROXIED = 1 << 13,
      62             :     PATTERN_COND_HTTP10 = 1 << 14,
      63             :     PATTERN_COND_HAS_QUERY_STRING = 1 << 15,
      64             :     PATTERN_COND_HTTPS = 1 << 16,
      65             :     PATTERN_COND_BACKREF = 1 << 17,
      66             :     PATTERN_COND_MASK = PATTERN_COND_COOKIE | PATTERN_COND_ENV_VAR |
      67             :                         PATTERN_COND_STAT | PATTERN_COND_QUERY_VAR |
      68             :                         PATTERN_COND_POST_VAR | PATTERN_COND_HEADER |
      69             :                         PATTERN_COND_LUA | PATTERN_COND_METHOD |
      70             :                         PATTERN_COND_ACCEPT_ENCODING |
      71             :                         PATTERN_COND_PROXIED | PATTERN_COND_HTTP10 |
      72             :                         PATTERN_COND_HAS_QUERY_STRING |
      73             :                         PATTERN_COND_HTTPS | PATTERN_COND_BACKREF,
      74             : 
      75             :     PATTERN_COND_STAT__HAS_IS_FILE = 1 << 18,
      76             :     PATTERN_COND_STAT__HAS_IS_DIR = 1 << 19,
      77             :     PATTERN_COND_STAT__IS_FILE = 1 << 20,
      78             :     PATTERN_COND_STAT__IS_DIR = 1 << 21,
      79             : 
      80             :     PATTERN_COND_STAT__FILE_CHECK =
      81             :         PATTERN_COND_STAT__HAS_IS_FILE | PATTERN_COND_STAT__IS_FILE,
      82             :     PATTERN_COND_STAT__DIR_CHECK =
      83             :         PATTERN_COND_STAT__HAS_IS_DIR | PATTERN_COND_STAT__IS_DIR,
      84             : 
      85             :     PATTERN_COND_HTTPS__IS_HTTPS = 1 << 22,
      86             : };
      87             : 
      88             : struct pattern {
      89             :     char *pattern;
      90             :     char *expand_pattern;
      91             :     struct {
      92             :         struct lwan_key_value cookie;
      93             :         struct lwan_key_value env_var;
      94             :         struct lwan_key_value query_var;
      95             :         struct lwan_key_value post_var;
      96             :         struct lwan_key_value header;
      97             :         struct {
      98             :             char *path;
      99             :         } stat;
     100             :         struct {
     101             :             char *script;
     102             :         } lua;
     103             :         struct {
     104             :             int index;
     105             :             char *str;
     106             :         } backref;
     107             :         enum lwan_request_flags request_flags;
     108             :         /* FIXME: Use pahole to find alignment holes? */
     109             :     } condition;
     110             :     enum pattern_flag flags;
     111             : };
     112             : 
     113        1827 : DEFINE_ARRAY_TYPE(pattern_array, struct pattern)
     114             : 
     115             : struct private_data {
     116             :     struct pattern_array patterns;
     117             : };
     118             : 
     119           2 : static enum lwan_http_status module_redirect_to(struct lwan_request *request,
     120             :                                                 const char *url)
     121             : {
     122           4 :     const struct lwan_key_value headers[] = {
     123           2 :         {"Location", coro_strdup(request->conn->coro, url)},
     124             :         {},
     125             :     };
     126             : 
     127           2 :     request->response.headers =
     128           2 :         coro_memdup(request->conn->coro, headers, sizeof(headers));
     129             : 
     130           2 :     if (LIKELY(headers[0].value && request->response.headers))
     131           2 :         return HTTP_MOVED_PERMANENTLY;
     132             : 
     133           0 :     return HTTP_INTERNAL_ERROR;
     134             : }
     135             : 
     136          17 : static enum lwan_http_status module_rewrite_as(struct lwan_request *request,
     137             :                                                const char *url)
     138             : {
     139          17 :     request->url.value = coro_strdup(request->conn->coro, url);
     140             : 
     141          17 :     if (UNLIKELY(!request->url.value))
     142           0 :         return HTTP_INTERNAL_ERROR;
     143             : 
     144          17 :     request->url.len = strlen(request->url.value);
     145          17 :     request->original_url = request->url;
     146          17 :     request->flags |= RESPONSE_URL_REWRITTEN;
     147             : 
     148          17 :     return HTTP_OK;
     149             : }
     150             : 
     151             : #define MAX_INT_DIGITS (3 * sizeof(int))
     152             : 
     153           4 : static __attribute__((noinline)) int parse_int_len(const char *s, size_t len,
     154             :                                                    int default_value)
     155             : {
     156           4 :     if (UNLIKELY(len > MAX_INT_DIGITS))
     157           0 :         return default_value;
     158             : 
     159           4 :     return parse_int(strndupa(s, len), default_value);
     160             : }
     161             : 
     162          21 : static const char *expand_string(const char *expand_pattern,
     163             :                                  const char *orig,
     164             :                                  char buffer[static PATH_MAX],
     165             :                                  const struct str_find *sf,
     166             :                                  int captures)
     167             : {
     168             :     struct lwan_strbuf strbuf;
     169             :     const char *ptr;
     170             : 
     171          21 :     ptr = strchr(expand_pattern, '%');
     172          21 :     if (!ptr)
     173          19 :         return expand_pattern;
     174             : 
     175           2 :     if (!lwan_strbuf_init_with_fixed_buffer(&strbuf, buffer, PATH_MAX))
     176           0 :         return NULL;
     177             : 
     178             :     do {
     179           4 :         size_t index_len = strspn(ptr + 1, "0123456789");
     180             : 
     181           4 :         if (ptr > expand_pattern) {
     182           4 :             const size_t len = (size_t)(ptr - expand_pattern);
     183             : 
     184           4 :             if (UNLIKELY(!lwan_strbuf_append_str(&strbuf, expand_pattern, len)))
     185           0 :                 return NULL;
     186             : 
     187           4 :             expand_pattern += len;
     188             :         }
     189             : 
     190           4 :         if (LIKELY(index_len > 0)) {
     191           4 :             const int index = parse_int_len(ptr + 1, index_len, -1);
     192             : 
     193           4 :             if (UNLIKELY(index < 0 || index > captures))
     194           0 :                 return NULL;
     195             : 
     196           4 :             if (UNLIKELY(!lwan_strbuf_append_str(
     197             :                     &strbuf, orig + sf[index].sm_so,
     198             :                     (size_t)(sf[index].sm_eo - sf[index].sm_so))))
     199           0 :                 return NULL;
     200             : 
     201           4 :             expand_pattern += index_len;
     202           0 :         } else if (UNLIKELY(!lwan_strbuf_append_char(&strbuf, '%'))) {
     203           0 :             return NULL;
     204             :         }
     205             : 
     206           4 :         expand_pattern++;
     207           4 :     } while ((ptr = strchr(expand_pattern, '%')));
     208             : 
     209           2 :     const size_t remaining_len = strlen(expand_pattern);
     210           2 :     if (remaining_len &&
     211           1 :         !lwan_strbuf_append_str(&strbuf, expand_pattern, remaining_len))
     212           0 :         return NULL;
     213             : 
     214           2 :     if (UNLIKELY(!lwan_strbuf_get_length(&strbuf)))
     215           0 :         return NULL;
     216             : 
     217           2 :     return lwan_strbuf_get_buffer(&strbuf);
     218             : }
     219             : 
     220             : static ALWAYS_INLINE const char *expand(const struct pattern *pattern,
     221             :                                         const char *orig,
     222             :                                         char buffer[static PATH_MAX],
     223             :                                         const struct str_find *sf,
     224             :                                         int captures)
     225             : {
     226          17 :     return expand_string(pattern->expand_pattern, orig, buffer, sf, captures);
     227             : }
     228             : 
     229             : #ifdef LWAN_HAVE_LUA
     230             : static void
     231           2 : lua_close_defer(void *data)
     232             : {
     233           2 :     lua_close((lua_State *)data);
     234           2 : }
     235             : 
     236           2 : static const char *expand_lua(struct lwan_request *request,
     237             :                               struct pattern *pattern, const char *orig,
     238             :                               char buffer[static PATH_MAX],
     239             :                               const struct str_find *sf, int captures)
     240             : {
     241             :     const char *output;
     242             :     size_t output_len;
     243             :     int i;
     244             :     lua_State *L;
     245             : 
     246           2 :     L = lwan_lua_create_state(NULL, pattern->expand_pattern);
     247           2 :     if (UNLIKELY(!L))
     248           0 :         return NULL;
     249           2 :     coro_defer(request->conn->coro, lua_close_defer, L);
     250             : 
     251           2 :     lua_getglobal(L, "handle_rewrite");
     252           2 :     if (!lua_isfunction(L, -1)) {
     253           0 :         lwan_status_error(
     254             :             "Could not obtain reference to `handle_rewrite()` function: %s",
     255             :             lwan_lua_state_last_error(L));
     256           0 :         return NULL;
     257             :     }
     258             : 
     259           2 :     lwan_lua_state_push_request(L, request);
     260             : 
     261           2 :     lua_createtable(L, captures, 0);
     262           8 :     for (i = 0; i < captures; i++) {
     263           6 :         lua_pushinteger(L, i);
     264           6 :         lua_pushlstring(L, orig + sf[i].sm_so,
     265           6 :                         (size_t)(sf[i].sm_eo - sf[i].sm_so));
     266           6 :         lua_settable(L, -3);
     267             :     }
     268             : 
     269           2 :     if (lua_pcall(L, 2, 1, 0) != 0) {
     270           0 :         lwan_status_error("Could not execute `handle_rewrite()` function: %s",
     271             :                           lwan_lua_state_last_error(L));
     272           0 :         return NULL;
     273             :     }
     274             : 
     275           2 :     output = lua_tolstring(L, -1, &output_len);
     276           2 :     if (output_len >= PATH_MAX) {
     277           0 :         lwan_status_error("Rewritten URL exceeds %d bytes (got %zu bytes)",
     278             :                           PATH_MAX, output_len);
     279           0 :         return NULL;
     280             :     }
     281             : 
     282           2 :     return memcpy(buffer, output, output_len + 1);
     283             : }
     284             : #endif
     285             : 
     286          44 : static bool condition_matches(struct lwan_request *request,
     287             :                               const struct pattern *p,
     288             :                               const struct str_find *sf,
     289             :                               int captures,
     290             :                               char expanded_buf[static PATH_MAX])
     291             : {
     292          44 :     if (LIKELY(!(p->flags & PATTERN_COND_MASK)))
     293          17 :         return true;
     294             : 
     295          27 :     const char *url = request->url.value;
     296             : 
     297          27 :     if (p->flags & PATTERN_COND_METHOD) {
     298           0 :         const enum lwan_request_flags method =
     299           0 :             p->condition.request_flags & REQUEST_METHOD_MASK;
     300           0 :         if (lwan_request_get_method(request) != method)
     301           0 :             return false;
     302             :     }
     303             : 
     304          27 :     if (p->flags & PATTERN_COND_BACKREF) {
     305           2 :         assert(p->condition.backref.index >= 0);
     306           2 :         assert(p->condition.backref.str);
     307             : 
     308           2 :         if (p->condition.backref.index > captures)
     309           0 :             return false;
     310             : 
     311           2 :         const struct str_find *s = &sf[p->condition.backref.index];
     312           2 :         const size_t len = strlen(p->condition.backref.str);
     313             : 
     314           2 :         if ((size_t)(s->sm_eo - s->sm_so) != len)
     315           1 :             return false;
     316             : 
     317           1 :         if (memcmp(request->url.value + s->sm_so, p->condition.backref.str, len))
     318           0 :             return false;
     319             :     }
     320             : 
     321          26 :     if (p->flags & PATTERN_COND_HTTPS) {
     322           0 :         bool is_tls = request->conn->flags & CONN_TLS;
     323           0 :         if (p->flags & PATTERN_COND_HTTPS__IS_HTTPS) {
     324           0 :             if (!is_tls)
     325           0 :                 return false;
     326           0 :         } else if (is_tls) {
     327           0 :             return false;
     328             :         }
     329             :     }
     330             : 
     331          26 :     if (p->flags & PATTERN_COND_ACCEPT_ENCODING) {
     332           0 :         const enum lwan_request_flags accept =
     333           0 :             p->condition.request_flags & REQUEST_ACCEPT_MASK;
     334           0 :         if (!(lwan_request_get_accept_encoding(request) & accept))
     335           0 :             return false;
     336             :     }
     337             : 
     338          26 :     if (p->flags & PATTERN_COND_PROXIED) {
     339           0 :         if (!(request->flags & p->condition.request_flags & REQUEST_PROXIED))
     340           0 :             return false;
     341             :     }
     342             : 
     343          26 :     if (p->flags & PATTERN_COND_HTTP10) {
     344           0 :         if (!(request->flags & p->condition.request_flags & REQUEST_IS_HTTP_1_0))
     345           0 :             return false;
     346             :     }
     347             : 
     348          26 :     if (p->flags & PATTERN_COND_HAS_QUERY_STRING) {
     349           0 :         if (!(request->flags & p->condition.request_flags & REQUEST_HAS_QUERY_STRING))
     350           0 :             return false;
     351             :     }
     352             : 
     353          26 :     if (p->flags & PATTERN_COND_COOKIE) {
     354          13 :         assert(p->condition.cookie.key);
     355          13 :         assert(p->condition.cookie.value);
     356             : 
     357             :         const char *cookie =
     358          13 :             lwan_request_get_cookie(request, p->condition.cookie.key);
     359          13 :         if (!cookie)
     360           9 :             return false;
     361             : 
     362           4 :         const char *val = expand_string(p->condition.cookie.value, url,
     363             :                                         expanded_buf, sf, captures);
     364           4 :         if (!val || !streq(val, cookie))
     365           3 :             return false;
     366             :     }
     367             : 
     368          14 :     if (p->flags & PATTERN_COND_ENV_VAR) {
     369          12 :         assert(p->condition.env_var.key);
     370          12 :         assert(p->condition.env_var.value);
     371             : 
     372          12 :         const char *env_var = secure_getenv(p->condition.env_var.key);
     373          12 :         if (!env_var)
     374          12 :             return false;
     375             : 
     376           0 :         const char *val = expand_string(p->condition.env_var.value, url,
     377             :                                         expanded_buf, sf, captures);
     378           0 :         if (!val || !streq(val, env_var))
     379           0 :             return false;
     380             :     }
     381             : 
     382           2 :     if (p->flags & PATTERN_COND_QUERY_VAR) {
     383           0 :         assert(p->condition.query_var.key);
     384           0 :         assert(p->condition.query_var.value);
     385             : 
     386             :         const char *query =
     387           0 :             lwan_request_get_query_param(request, p->condition.query_var.key);
     388             : 
     389           0 :         if (!query)
     390           0 :             return false;
     391             : 
     392           0 :         const char *val = expand_string(p->condition.query_var.value, url,
     393             :                                         expanded_buf, sf, captures);
     394           0 :         if (!val || !streq(val, query))
     395           0 :             return false;
     396             :     }
     397             : 
     398           2 :     if (p->flags & PATTERN_COND_POST_VAR) {
     399           0 :         assert(p->condition.post_var.key);
     400           0 :         assert(p->condition.post_var.value);
     401             : 
     402             :         const char *post =
     403           0 :             lwan_request_get_post_param(request, p->condition.post_var.key);
     404           0 :         if (!post)
     405           0 :             return false;
     406             : 
     407           0 :         const char *val = expand_string(p->condition.post_var.value, url,
     408             :                                         expanded_buf, sf, captures);
     409           0 :         if (!val || !streq(val, post))
     410           0 :             return false;
     411             :     }
     412             : 
     413           2 :     if (p->flags & PATTERN_COND_STAT) {
     414           0 :         assert(p->condition.stat.path);
     415             : 
     416             :         struct stat st;
     417             : 
     418             :         /* FIXME: Expanding path from a user-controlled URL and use the
     419             :          * resulting path to call stat(2) on could lead to some information
     420             :          * disclosure vulnerability. Would require the server to be configured
     421             :          * in a certain way, though.
     422             :          */
     423           0 :         const char *path = expand_string(p->condition.stat.path, url,
     424             :                                          expanded_buf, sf, captures);
     425           0 :         if (!path || stat(path, &st) < 0)
     426           0 :             return false;
     427             : 
     428           0 :         if ((p->flags & PATTERN_COND_STAT__FILE_CHECK) ==
     429           0 :                 PATTERN_COND_STAT__FILE_CHECK &&
     430           0 :             !S_ISREG(st.st_mode))
     431           0 :             return false;
     432           0 :         if ((p->flags & PATTERN_COND_STAT__DIR_CHECK) ==
     433           0 :                 PATTERN_COND_STAT__DIR_CHECK &&
     434           0 :             !S_ISDIR(st.st_mode))
     435           0 :             return false;
     436             :     }
     437             : 
     438             : #ifdef LWAN_HAVE_LUA
     439           2 :     if (p->flags & PATTERN_COND_LUA) {
     440           0 :         assert(p->condition.lua.script);
     441             : 
     442           0 :         lua_State *L = lwan_lua_create_state(NULL, p->condition.lua.script);
     443           0 :         if (!L)
     444           0 :             return false;
     445           0 :         coro_defer(request->conn->coro, lua_close_defer, L);
     446             : 
     447           0 :         lua_getglobal(L, "matches");
     448           0 :         if (!lua_isfunction(L, -1)) {
     449           0 :             lwan_status_error(
     450             :                 "Could not obtain reference to `matches()` function: %s",
     451             :                 lwan_lua_state_last_error(L));
     452           0 :             return false;
     453             :         }
     454             : 
     455           0 :         lwan_lua_state_push_request(L, request);
     456             : 
     457           0 :         if (lua_pcall(L, 1, 1, 0) != 0) {
     458           0 :             lwan_status_error("Could not execute `matches()` function: %s",
     459             :                               lwan_lua_state_last_error(L));
     460           0 :             return false;
     461             :         }
     462             : 
     463           0 :         if (!lua_toboolean(L, -1))
     464           0 :             return false;
     465             :     }
     466             : #else
     467             :     assert(!(p->flags & PATTERN_COND_LUA));
     468             : #endif
     469             : 
     470           2 :     return true;
     471             : }
     472             : 
     473             : static enum lwan_http_status
     474          20 : rewrite_handle_request(struct lwan_request *request,
     475             :                        struct lwan_response *response __attribute__((unused)),
     476             :                        void *instance)
     477             : {
     478          20 :     struct private_data *pd = instance;
     479          20 :     const char *url = request->url.value;
     480             :     char final_url[PATH_MAX];
     481             :     struct pattern *p;
     482             : 
     483          59 :     LWAN_ARRAY_FOREACH(&pd->patterns, p) {
     484             :         struct str_find sf[MAXCAPTURES];
     485          58 :         const char *expanded = NULL;
     486             :         const char *errmsg;
     487             :         int captures;
     488             : 
     489          58 :         captures = str_find(url, p->pattern, sf, MAXCAPTURES, &errmsg);
     490          58 :         if (captures <= 0)
     491          39 :             continue;
     492             : 
     493          44 :         if (!condition_matches(request, p, sf, captures, final_url))
     494          25 :             continue;
     495             : 
     496          19 :         switch (p->flags & PATTERN_EXPAND_MASK) {
     497             : #ifdef LWAN_HAVE_LUA
     498           2 :         case PATTERN_EXPAND_LUA:
     499           2 :             expanded = expand_lua(request, p, url, final_url, sf, captures);
     500           2 :             break;
     501             : #endif
     502          17 :         case PATTERN_EXPAND_LWAN:
     503          17 :             expanded = expand(p, url, final_url, sf, captures);
     504          17 :             break;
     505             :         }
     506             : 
     507          19 :         if (LIKELY(expanded)) {
     508          19 :             switch (p->flags & PATTERN_HANDLE_MASK) {
     509           2 :             case PATTERN_HANDLE_REDIRECT:
     510          19 :                 return module_redirect_to(request, expanded);
     511          17 :             case PATTERN_HANDLE_REWRITE:
     512          17 :                 return module_rewrite_as(request, expanded);
     513             :             }
     514             :         }
     515             : 
     516           0 :         return HTTP_INTERNAL_ERROR;
     517             :     }
     518             : 
     519           1 :     return HTTP_NOT_FOUND;
     520             : }
     521             : 
     522         261 : static void *rewrite_create(const char *prefix __attribute__((unused)),
     523             :                             void *instance __attribute__((unused)))
     524             : {
     525         261 :     struct private_data *pd = malloc(sizeof(*pd));
     526             : 
     527         261 :     if (!pd)
     528           0 :         return NULL;
     529             : 
     530         261 :     pattern_array_init(&pd->patterns);
     531             : 
     532         261 :     return pd;
     533             : }
     534             : 
     535           0 : static void rewrite_destroy(void *instance)
     536             : {
     537           0 :     struct private_data *pd = instance;
     538             :     struct pattern *iter;
     539             : 
     540           0 :     LWAN_ARRAY_FOREACH(&pd->patterns, iter) {
     541           0 :         free(iter->pattern);
     542           0 :         free(iter->expand_pattern);
     543           0 :         if (iter->flags & PATTERN_COND_COOKIE) {
     544           0 :             free(iter->condition.cookie.key);
     545           0 :             free(iter->condition.cookie.value);
     546             :         }
     547           0 :         if (iter->flags & PATTERN_COND_ENV_VAR) {
     548           0 :             free(iter->condition.env_var.key);
     549           0 :             free(iter->condition.env_var.value);
     550             :         }
     551           0 :         if (iter->flags & PATTERN_COND_QUERY_VAR) {
     552           0 :             free(iter->condition.query_var.key);
     553           0 :             free(iter->condition.query_var.value);
     554             :         }
     555           0 :         if (iter->flags & PATTERN_COND_POST_VAR) {
     556           0 :             free(iter->condition.post_var.key);
     557           0 :             free(iter->condition.post_var.value);
     558             :         }
     559           0 :         if (iter->flags & PATTERN_COND_HEADER) {
     560           0 :             free(iter->condition.header.key);
     561           0 :             free(iter->condition.header.value);
     562             :         }
     563           0 :         if (iter->flags & PATTERN_COND_STAT) {
     564           0 :             free(iter->condition.stat.path);
     565             :         }
     566             : #ifdef LWAN_HAVE_LUA
     567           0 :         if (iter->flags & PATTERN_COND_LUA) {
     568           0 :             free(iter->condition.lua.script);
     569             :         }
     570             : #endif
     571           0 :         if (iter->flags & PATTERN_COND_BACKREF) {
     572           0 :             free(iter->condition.backref.str);
     573             :         }
     574             :     }
     575             : 
     576           0 :     pattern_array_reset(&pd->patterns);
     577           0 :     free(pd);
     578           0 : }
     579             : 
     580         261 : static void *rewrite_create_from_hash(const char *prefix,
     581             :                                       const struct hash *hash
     582             :                                       __attribute__((unused)))
     583             : {
     584         261 :     return rewrite_create(prefix, NULL);
     585             : }
     586             : 
     587         174 : static void parse_condition_key_value(struct pattern *pattern,
     588             :                                       struct lwan_key_value *key_value,
     589             :                                       enum pattern_flag condition_type,
     590             :                                       struct config *config,
     591             :                                       const struct config_line *line)
     592             : {
     593         174 :     char *key = NULL, *value = NULL;
     594             : 
     595         522 :     while ((line = config_read_line(config))) {
     596         348 :         switch (line->type) {
     597           0 :         case CONFIG_LINE_TYPE_SECTION:
     598           0 :             config_error(config, "Unexpected section: %s", line->key);
     599           0 :             goto out;
     600             : 
     601         174 :         case CONFIG_LINE_TYPE_SECTION_END:
     602         174 :             if (!key || !value) {
     603           0 :                 config_error(config, "Key/value has not been specified");
     604           0 :                 goto out;
     605             :             }
     606             : 
     607         174 :             *key_value = (struct lwan_key_value){key, value};
     608         174 :             pattern->flags |= condition_type & PATTERN_COND_MASK;
     609         174 :             return;
     610             : 
     611         174 :         case CONFIG_LINE_TYPE_LINE:
     612         174 :             if (key || value) {
     613           0 :                 config_error(config,
     614             :                              "Can only condition on a single key/value pair. "
     615             :                              "Currently has: %s=%s",
     616             :                              key, value);
     617           0 :                 goto out;
     618             :             }
     619             : 
     620         174 :             key = strdup(line->key);
     621         174 :             if (!key) {
     622           0 :                 config_error(config,
     623             :                              "Could not copy key while parsing condition");
     624           0 :                 goto out;
     625             :             }
     626             : 
     627         174 :             value = strdup(line->value);
     628         174 :             if (!value) {
     629           0 :                 config_error(config,
     630             :                              "Could not copy value while parsing condition");
     631           0 :                 goto out;
     632             :             }
     633         174 :             break;
     634             :         }
     635             :     }
     636             : 
     637           0 : out:
     638           0 :     free(key);
     639           0 :     free(value);
     640             : }
     641             : 
     642          87 : static void parse_condition_stat(struct pattern *pattern,
     643             :                                  struct config *config,
     644             :                                  const struct config_line *line)
     645             : {
     646          87 :     char *path = NULL;
     647          87 :     bool has_is_dir = false, is_dir = false;
     648          87 :     bool has_is_file = false, is_file = false;
     649             : 
     650         348 :     while ((line = config_read_line(config))) {
     651         261 :         switch (line->type) {
     652           0 :         case CONFIG_LINE_TYPE_SECTION:
     653           0 :             config_error(config, "Unexpected section: %s", line->key);
     654           0 :             goto out;
     655             : 
     656          87 :         case CONFIG_LINE_TYPE_SECTION_END:
     657          87 :             if (!path) {
     658           0 :                 config_error(config, "Path not specified");
     659           0 :                 goto out;
     660             :             }
     661             : 
     662          87 :             pattern->condition.stat.path = path;
     663          87 :             if (has_is_dir) {
     664           0 :                 pattern->flags |= PATTERN_COND_STAT__HAS_IS_DIR;
     665           0 :                 if (is_dir)
     666           0 :                     pattern->flags |= PATTERN_COND_STAT__IS_DIR;
     667             :             }
     668          87 :             if (has_is_file) {
     669          87 :                 pattern->flags |= PATTERN_COND_STAT__HAS_IS_FILE;
     670          87 :                 if (is_file)
     671          87 :                     pattern->flags |= PATTERN_COND_STAT__IS_FILE;
     672             :             }
     673          87 :             pattern->flags |= PATTERN_COND_STAT;
     674          87 :             return;
     675             : 
     676         174 :         case CONFIG_LINE_TYPE_LINE:
     677         174 :             if (streq(line->key, "path")) {
     678          87 :                 if (path) {
     679           0 :                     config_error(config, "Path `%s` already specified", path);
     680           0 :                     goto out;
     681             :                 }
     682          87 :                 path = strdup(line->value);
     683          87 :                 if (!path) {
     684           0 :                     config_error(config, "Could not copy path");
     685           0 :                     goto out;
     686             :                 }
     687          87 :             } else if (streq(line->key, "is_dir")) {
     688           0 :                 is_dir = parse_bool(line->value, false);
     689           0 :                 has_is_dir = true;
     690          87 :             } else if (streq(line->key, "is_file")) {
     691          87 :                 is_file = parse_bool(line->value, false);
     692          87 :                 has_is_file = true;
     693             :             } else {
     694           0 :                 config_error(config, "Unexpected key: %s", line->key);
     695           0 :                 goto out;
     696             :             }
     697             : 
     698         174 :             break;
     699             :         }
     700             :     }
     701             : 
     702           0 : out:
     703           0 :     free(path);
     704             : }
     705             : 
     706           0 : static void parse_condition_accept_encoding(struct pattern *pattern,
     707             :                                             struct config *config)
     708             : {
     709             :     const struct config_line *line;
     710             : 
     711           0 :     while ((line = config_read_line(config))) {
     712           0 :         switch (line->type) {
     713           0 :         case CONFIG_LINE_TYPE_SECTION:
     714           0 :             config_error(config, "Unexpected section: %s", line->key);
     715           0 :             return;
     716             : 
     717           0 :         case CONFIG_LINE_TYPE_SECTION_END:
     718           0 :             pattern->flags |= PATTERN_COND_ACCEPT_ENCODING;
     719           0 :             return;
     720             : 
     721           0 :         case CONFIG_LINE_TYPE_LINE:
     722           0 :             if (streq(line->key, "deflate")) {
     723           0 :                 if (parse_bool(line->value, false))
     724           0 :                     pattern->condition.request_flags |= REQUEST_ACCEPT_DEFLATE;
     725           0 :             } else if (streq(line->key, "gzip")) {
     726           0 :                 if (parse_bool(line->value, false))
     727           0 :                     pattern->condition.request_flags |= REQUEST_ACCEPT_GZIP;
     728           0 :             } else if (streq(line->key, "brotli")) {
     729           0 :                 if (parse_bool(line->value, false))
     730           0 :                     pattern->condition.request_flags |= REQUEST_ACCEPT_BROTLI;
     731           0 :             } else if (streq(line->key, "zstd")) {
     732           0 :                 if (parse_bool(line->value, false))
     733           0 :                     pattern->condition.request_flags |= REQUEST_ACCEPT_ZSTD;
     734           0 :             } else if (!streq(line->key, "none")) {
     735           0 :                 config_error(config, "Unsupported encoding for condition: %s",
     736           0 :                              line->key);
     737           0 :                 return;
     738             :             }
     739           0 :             break;
     740             :         }
     741             :     }
     742             : }
     743             : 
     744          87 : static void parse_condition_backref(struct pattern *pattern,
     745             :                                     struct config *config)
     746             : {
     747             :     const struct config_line *line;
     748          87 :     int index = -1;
     749          87 :     char *str = NULL;
     750             : 
     751         261 :     while ((line = config_read_line(config))) {
     752         174 :         switch (line->type) {
     753           0 :         case CONFIG_LINE_TYPE_SECTION:
     754           0 :             config_error(config, "Unexpected section: %s", line->key);
     755           0 :             goto out;
     756             : 
     757          87 :         case CONFIG_LINE_TYPE_SECTION_END:
     758          87 :             if (index < 0) {
     759           0 :                 config_error(config, "Backref index not provided");
     760           0 :                 goto out;
     761             :             }
     762             : 
     763          87 :             pattern->flags |= PATTERN_COND_BACKREF;
     764          87 :             pattern->condition.backref.index = index;
     765          87 :             pattern->condition.backref.str = str;
     766          87 :             return;
     767             : 
     768          87 :         case CONFIG_LINE_TYPE_LINE:
     769          87 :             index = parse_int(line->key, -1);
     770          87 :             if (index < 0) {
     771           0 :                 config_error(config, "Expecting backref index, got ``%s''", line->key);
     772           0 :                 goto out;
     773             :             }
     774             : 
     775          87 :             free(str);
     776          87 :             str = strdup(line->value);
     777          87 :             if (!str) {
     778           0 :                 lwan_status_critical(
     779             :                     "Couldn't allocate memory for backref key");
     780             :             }
     781             : 
     782          87 :             break;
     783             :         }
     784             :     }
     785             : 
     786           0 : out:
     787           0 :     free(str);
     788             : }
     789             : 
     790           0 : static bool get_method_from_string(struct pattern *pattern, const char *string)
     791             : {
     792             : #define GENERATE_CMP(upper, lower, mask, constant, probability)                \
     793             :     if (strcaseequal_neutral(string, #upper)) {                                \
     794             :         pattern->condition.request_flags |= (mask);                            \
     795             :         return true;                                                           \
     796             :     }
     797             : 
     798           0 :     FOR_EACH_REQUEST_METHOD(GENERATE_CMP)
     799             : 
     800             : #undef GENERATE_CMP
     801             : 
     802           0 :     return false;
     803             : }
     804             : 
     805         348 : static void parse_condition(struct pattern *pattern,
     806             :                             struct config *config,
     807             :                             const struct config_line *line)
     808             : {
     809         348 :     if (streq(line->value, "cookie")) {
     810          87 :         return parse_condition_key_value(pattern, &pattern->condition.cookie,
     811             :                                          PATTERN_COND_COOKIE, config, line);
     812             :     }
     813         261 :     if (streq(line->value, "query")) {
     814           0 :         return parse_condition_key_value(pattern, &pattern->condition.query_var,
     815             :                                          PATTERN_COND_QUERY_VAR, config, line);
     816             :     }
     817         261 :     if (streq(line->value, "post")) {
     818           0 :         return parse_condition_key_value(pattern, &pattern->condition.post_var,
     819             :                                          PATTERN_COND_POST_VAR, config, line);
     820             :     }
     821         261 :     if (streq(line->value, "environment")) {
     822          87 :         return parse_condition_key_value(pattern, &pattern->condition.env_var,
     823             :                                          PATTERN_COND_ENV_VAR, config, line);
     824             :     }
     825         174 :     if (streq(line->value, "header")) {
     826           0 :         return parse_condition_key_value(pattern, &pattern->condition.header,
     827             :                                          PATTERN_COND_HEADER, config, line);
     828             :     }
     829         174 :     if (streq(line->value, "stat")) {
     830          87 :         return parse_condition_stat(pattern, config, line);
     831             :     }
     832          87 :     if (streq(line->value, "encoding")) {
     833           0 :         return parse_condition_accept_encoding(pattern, config);
     834             :     }
     835          87 :     if (streq(line->value, "backref")) {
     836          87 :         return parse_condition_backref(pattern, config);
     837             :     }
     838             : 
     839           0 :     config_error(config, "Condition `%s' not supported", line->value);
     840             : }
     841             : 
     842         783 : static bool rewrite_parse_conf_pattern(struct private_data *pd,
     843             :                                        struct config *config,
     844             :                                        const struct config_line *line)
     845             : {
     846             :     struct pattern *pattern;
     847         783 :     char *redirect_to = NULL, *rewrite_as = NULL;
     848         783 :     bool expand_with_lua = false;
     849             : 
     850         783 :     pattern = pattern_array_append0(&pd->patterns);
     851         783 :     if (!pattern)
     852           0 :         goto out_no_free;
     853             : 
     854         783 :     pattern->pattern = strdup(line->value);
     855         783 :     if (!pattern->pattern)
     856           0 :         goto out;
     857             : 
     858        2958 :     while ((line = config_read_line(config))) {
     859        2175 :         switch (line->type) {
     860        1044 :         case CONFIG_LINE_TYPE_LINE:
     861        1044 :             if (streq(line->key, "redirect_to")) {
     862         174 :                 free(redirect_to);
     863             : 
     864         174 :                 redirect_to = strdup(line->value);
     865         174 :                 if (!redirect_to)
     866           0 :                     goto out;
     867         870 :             } else if (streq(line->key, "rewrite_as")) {
     868         609 :                 free(rewrite_as);
     869             : 
     870         609 :                 rewrite_as = strdup(line->value);
     871         609 :                 if (!rewrite_as)
     872           0 :                     goto out;
     873         261 :             } else if (streq(line->key, "expand_with_lua")) {
     874         174 :                 expand_with_lua = parse_bool(line->value, false);
     875          87 :             } else if (streq(line->key, "condition_proxied")) {
     876           0 :                 if (parse_bool(line->value, false))
     877           0 :                     pattern->flags |= PATTERN_COND_PROXIED;
     878          87 :             } else if (streq(line->key, "condition_http_1.0")) {
     879           0 :                 if (parse_bool(line->value, false))
     880           0 :                     pattern->flags |= PATTERN_COND_HTTP10;
     881          87 :             } else if (streq(line->key, "condition_has_query_string")) {
     882           0 :                 if (parse_bool(line->value, false))
     883           0 :                     pattern->flags |= PATTERN_COND_HAS_QUERY_STRING;
     884          87 :             } else if (streq(line->key, "condition_is_https")) {
     885           0 :                 if (parse_bool(line->value, false))
     886           0 :                     pattern->flags |= PATTERN_COND_HTTPS__IS_HTTPS;
     887           0 :                 pattern->flags |= PATTERN_COND_HTTPS;
     888          87 :             } else if (streq(line->key, "condition_method")) {
     889           0 :                 if (!get_method_from_string(pattern, line->value)) {
     890           0 :                     config_error(config, "Unknown HTTP method: %s", line->value);
     891           0 :                     goto out;
     892             :                 }
     893           0 :                 pattern->flags |= PATTERN_COND_METHOD;
     894             :             } else
     895             : #ifdef LWAN_HAVE_LUA
     896          87 :             if (streq(line->key, "condition_lua")) {
     897          87 :                 pattern->condition.lua.script = strdup(line->value);
     898          87 :                 if (!pattern->condition.lua.script)
     899           0 :                     lwan_status_critical("Couldn't copy Lua script");
     900          87 :                 pattern->flags |= PATTERN_COND_LUA;
     901             :             } else
     902             : #endif
     903             :             {
     904           0 :                 config_error(config, "Unexpected key: %s", line->key);
     905           0 :                 goto out;
     906             :             }
     907        1044 :             break;
     908         348 :         case CONFIG_LINE_TYPE_SECTION:
     909         348 :             if (streq(line->key, "condition")) {
     910         348 :                 parse_condition(pattern, config, line);
     911             :             } else {
     912           0 :                 config_error(config, "Unexpected section: %s", line->key);
     913             :             }
     914         348 :             break;
     915         783 :         case CONFIG_LINE_TYPE_SECTION_END:
     916         783 :             if (redirect_to && rewrite_as) {
     917           0 :                 config_error(
     918             :                     config,
     919             :                     "`redirect to` and `rewrite as` are mutually exclusive");
     920           0 :                 goto out;
     921             :             }
     922         783 :             if (redirect_to) {
     923         174 :                 pattern->expand_pattern = redirect_to;
     924         174 :                 pattern->flags |= PATTERN_HANDLE_REDIRECT;
     925         609 :             } else if (rewrite_as) {
     926         609 :                 pattern->expand_pattern = rewrite_as;
     927         609 :                 pattern->flags |= PATTERN_HANDLE_REWRITE;
     928             :             } else {
     929           0 :                 config_error(
     930             :                     config,
     931             :                     "either `redirect to` or `rewrite as` are required");
     932           0 :                 goto out;
     933             :             }
     934         783 :             if (expand_with_lua) {
     935             : #ifdef LWAN_HAVE_LUA
     936         174 :                 pattern->flags |= PATTERN_EXPAND_LUA;
     937             : #else
     938             :                 config_error(config, "Lwan has been built without Lua. "
     939             :                                      "`expand_with_lua` is not available");
     940             :                 goto out;
     941             : #endif
     942             :             } else {
     943         609 :                 pattern->flags |= PATTERN_EXPAND_LWAN;
     944             :             }
     945             : 
     946         783 :             return true;
     947             :         }
     948             :     }
     949             : 
     950           0 : out:
     951           0 :     free(pattern->pattern);
     952           0 :     free(redirect_to);
     953           0 :     free(rewrite_as);
     954           0 : out_no_free:
     955           0 :     config_error(config, "Could not copy pattern");
     956           0 :     return false;
     957             : }
     958             : 
     959         261 : static bool rewrite_parse_conf(void *instance, struct config *config)
     960             : {
     961         261 :     struct private_data *pd = instance;
     962             :     const struct config_line *line;
     963             : 
     964        1305 :     while ((line = config_read_line(config))) {
     965        1044 :         switch (line->type) {
     966           0 :         case CONFIG_LINE_TYPE_LINE:
     967           0 :             config_error(config, "Unknown option: %s", line->key);
     968           0 :             break;
     969         783 :         case CONFIG_LINE_TYPE_SECTION:
     970         783 :             if (streq(line->key, "pattern")) {
     971         783 :                 rewrite_parse_conf_pattern(pd, config, line);
     972             :             } else {
     973           0 :                 config_error(config, "Unknown section: %s", line->key);
     974             :             }
     975         783 :             break;
     976         261 :         case CONFIG_LINE_TYPE_SECTION_END:
     977         261 :             break;
     978             :         }
     979             :     }
     980             : 
     981         261 :     return !config_last_error(config);
     982             : }
     983             : 
     984             : static const struct lwan_module module = {
     985             :     .create = rewrite_create,
     986             :     .create_from_hash = rewrite_create_from_hash,
     987             :     .parse_conf = rewrite_parse_conf,
     988             :     .destroy = rewrite_destroy,
     989             :     .handle_request = rewrite_handle_request,
     990             :     .flags = HANDLER_CAN_REWRITE_URL
     991             : };
     992             : 
     993             : LWAN_REGISTER_MODULE(rewrite, &module);

Generated by: LCOV version 1.15-2-gb9d6727