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);
|