[core] consolidate backend network write handlers
[lighttpd.git] / src / mod_rewrite.c
blob70b258455c4e2f768e4a943eefb3a03636ebe9a5
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
8 #include "stat_cache.h"
10 #include <ctype.h>
11 #include <stdlib.h>
12 #include <string.h>
14 #ifdef HAVE_PCRE_H
15 typedef struct {
16 pcre *key;
18 buffer *value;
20 int once;
21 } rewrite_rule;
23 typedef struct {
24 rewrite_rule **ptr;
26 size_t used;
27 size_t size;
28 } rewrite_rule_buffer;
30 typedef struct {
31 rewrite_rule_buffer *rewrite;
32 rewrite_rule_buffer *rewrite_NF;
33 data_config *context, *context_NF; /* to which apply me */
34 } plugin_config;
36 typedef struct {
37 enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
38 int loops;
39 } handler_ctx;
41 typedef struct {
42 PLUGIN_DATA;
43 buffer *match_buf;
45 plugin_config **config_storage;
47 plugin_config conf;
48 } plugin_data;
50 static handler_ctx * handler_ctx_init(void) {
51 handler_ctx * hctx;
53 hctx = calloc(1, sizeof(*hctx));
55 hctx->state = REWRITE_STATE_UNSET;
56 hctx->loops = 0;
58 return hctx;
61 static void handler_ctx_free(handler_ctx *hctx) {
62 free(hctx);
65 static rewrite_rule_buffer *rewrite_rule_buffer_init(void) {
66 rewrite_rule_buffer *kvb;
68 kvb = calloc(1, sizeof(*kvb));
70 return kvb;
73 static int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int once) {
74 size_t i;
75 const char *errptr;
76 int erroff;
78 if (!key) return -1;
80 if (kvb->size == 0) {
81 kvb->size = 4;
82 kvb->used = 0;
84 kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr));
86 for(i = 0; i < kvb->size; i++) {
87 kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
89 } else if (kvb->used == kvb->size) {
90 kvb->size += 4;
92 kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr));
94 for(i = kvb->used; i < kvb->size; i++) {
95 kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
99 if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr,
100 0, &errptr, &erroff, NULL))) {
102 return -1;
105 kvb->ptr[kvb->used]->value = buffer_init();
106 buffer_copy_buffer(kvb->ptr[kvb->used]->value, value);
107 kvb->ptr[kvb->used]->once = once;
109 kvb->used++;
111 return 0;
114 static void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) {
115 size_t i;
117 for (i = 0; i < kvb->size; i++) {
118 if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key);
119 if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value);
120 free(kvb->ptr[i]);
123 if (kvb->ptr) free(kvb->ptr);
125 free(kvb);
129 INIT_FUNC(mod_rewrite_init) {
130 plugin_data *p;
132 p = calloc(1, sizeof(*p));
134 p->match_buf = buffer_init();
136 return p;
139 FREE_FUNC(mod_rewrite_free) {
140 plugin_data *p = p_d;
142 UNUSED(srv);
144 if (!p) return HANDLER_GO_ON;
146 buffer_free(p->match_buf);
147 if (p->config_storage) {
148 size_t i;
149 for (i = 0; i < srv->config_context->used; i++) {
150 plugin_config *s = p->config_storage[i];
152 if (NULL == s) continue;
154 rewrite_rule_buffer_free(s->rewrite);
155 rewrite_rule_buffer_free(s->rewrite_NF);
157 free(s);
159 free(p->config_storage);
162 free(p);
164 return HANDLER_GO_ON;
167 static int parse_config_entry(server *srv, array *ca, rewrite_rule_buffer *kvb, const char *option, size_t olen, int once) {
168 data_unset *du;
170 if (NULL != (du = array_get_element_klen(ca, option, olen))) {
171 data_array *da;
172 size_t j;
174 da = (data_array *)du;
176 if (du->type != TYPE_ARRAY || !array_is_kvstring(da->value)) {
177 log_error_write(srv, __FILE__, __LINE__, "SSS",
178 "unexpected value for ", option, "; expected list of \"regex\" => \"subst\"");
179 return HANDLER_ERROR;
182 for (j = 0; j < da->value->used; j++) {
183 if (0 != rewrite_rule_buffer_append(kvb,
184 ((data_string *)(da->value->data[j]))->key,
185 ((data_string *)(da->value->data[j]))->value,
186 once)) {
187 log_error_write(srv, __FILE__, __LINE__, "sb",
188 "pcre-compile failed for", da->value->data[j]->key);
189 return HANDLER_ERROR;
194 return 0;
196 #else
197 static int parse_config_entry(server *srv, array *ca, const char *option, size_t olen) {
198 static int logged_message = 0;
199 if (logged_message) return 0;
200 if (NULL != array_get_element_klen(ca, option, olen)) {
201 logged_message = 1;
202 log_error_write(srv, __FILE__, __LINE__, "s",
203 "pcre support is missing, please install libpcre and the headers");
205 return 0;
207 #endif
209 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
210 size_t i = 0;
211 config_values_t cv[] = {
212 { "url.rewrite-repeat", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
213 { "url.rewrite-once", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
215 /* these functions only rewrite if the target is not already in the filestore
217 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
218 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
221 { "url.rewrite-repeat-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
222 { "url.rewrite-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
224 /* old names, still supported
226 * url.rewrite remapped to url.rewrite-once
227 * url.rewrite-final is url.rewrite-once
230 { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
231 { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
232 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
235 #ifdef HAVE_PCRE_H
236 plugin_data *p = p_d;
238 if (!p) return HANDLER_ERROR;
240 /* 0 */
241 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
242 #else
243 UNUSED(p_d);
244 #endif
246 for (i = 0; i < srv->config_context->used; i++) {
247 data_config const* config = (data_config const*)srv->config_context->data[i];
248 #ifdef HAVE_PCRE_H
249 plugin_config *s;
251 s = calloc(1, sizeof(plugin_config));
252 s->rewrite = rewrite_rule_buffer_init();
253 s->rewrite_NF = rewrite_rule_buffer_init();
254 p->config_storage[i] = s;
255 #endif
257 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
258 return HANDLER_ERROR;
261 #ifndef HAVE_PCRE_H
262 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
263 #endif
264 parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-once"), 1);
265 parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-final"), 1);
266 parse_config_entry(srv, config->value, s->rewrite_NF, CONST_STR_LEN("url.rewrite-if-not-file"), 1);
267 parse_config_entry(srv, config->value, s->rewrite_NF, CONST_STR_LEN("url.rewrite-repeat-if-not-file"), 0);
268 parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite"), 1);
269 parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-repeat"), 0);
272 return HANDLER_GO_ON;
275 #ifdef HAVE_PCRE_H
277 #define PATCH(x) \
278 p->conf.x = s->x;
279 static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
280 size_t i, j;
281 plugin_config *s = p->config_storage[0];
283 PATCH(rewrite);
284 PATCH(rewrite_NF);
285 p->conf.context = NULL;
286 p->conf.context_NF = NULL;
288 /* skip the first, the global context */
289 for (i = 1; i < srv->config_context->used; i++) {
290 data_config *dc = (data_config *)srv->config_context->data[i];
291 s = p->config_storage[i];
293 /* condition didn't match */
294 if (!config_check_cond(srv, con, dc)) continue;
296 /* merge config */
297 for (j = 0; j < dc->value->used; j++) {
298 data_unset *du = dc->value->data[j];
300 if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
301 PATCH(rewrite);
302 p->conf.context = dc;
303 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
304 PATCH(rewrite);
305 p->conf.context = dc;
306 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
307 PATCH(rewrite);
308 p->conf.context = dc;
309 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-if-not-file"))) {
310 PATCH(rewrite_NF);
311 p->conf.context_NF = dc;
312 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
313 PATCH(rewrite_NF);
314 p->conf.context_NF = dc;
315 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
316 PATCH(rewrite);
317 p->conf.context = dc;
322 return 0;
325 URIHANDLER_FUNC(mod_rewrite_con_reset) {
326 plugin_data *p = p_d;
328 UNUSED(srv);
330 if (con->plugin_ctx[p->id]) {
331 handler_ctx_free(con->plugin_ctx[p->id]);
332 con->plugin_ctx[p->id] = NULL;
335 return HANDLER_GO_ON;
338 static handler_t process_rewrite_rules(server *srv, connection *con, plugin_data *p, rewrite_rule_buffer *kvb) {
339 size_t i;
340 handler_ctx *hctx;
342 if (con->plugin_ctx[p->id]) {
343 hctx = con->plugin_ctx[p->id];
345 if (hctx->loops++ > 100) {
346 data_config *dc = p->conf.context;
347 log_error_write(srv, __FILE__, __LINE__, "SbbSBS",
348 "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, perhaps you want to use url.rewrite-once instead of url.rewrite-repeat ($", dc->comp_key, dc->op, "\"", dc->string, "\")");
350 return HANDLER_ERROR;
353 if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
356 buffer_copy_buffer(p->match_buf, con->request.uri);
358 for (i = 0; i < kvb->used; i++) {
359 pcre *match;
360 const char *pattern;
361 size_t pattern_len;
362 int n;
363 rewrite_rule *rule = kvb->ptr[i];
364 # define N 10
365 int ovec[N * 3];
367 match = rule->key;
368 pattern = rule->value->ptr;
369 pattern_len = buffer_string_length(rule->value);
371 if ((n = pcre_exec(match, NULL, CONST_BUF_LEN(p->match_buf), 0, 0, ovec, 3 * N)) < 0) {
372 if (n != PCRE_ERROR_NOMATCH) {
373 log_error_write(srv, __FILE__, __LINE__, "sd",
374 "execution error while matching: ", n);
375 return HANDLER_ERROR;
377 } else if (0 == pattern_len) {
378 /* short-circuit if blank replacement pattern
379 * (do not attempt to match against remaining rewrite rules) */
380 return HANDLER_GO_ON;
381 } else {
382 const char **list;
383 size_t start;
384 size_t k;
386 /* it matched */
387 pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
389 /* search for $[0-9] */
391 buffer_reset(con->request.uri);
393 start = 0;
394 for (k = 0; k+1 < pattern_len; k++) {
395 if (pattern[k] == '$' || pattern[k] == '%') {
396 /* got one */
398 size_t num = pattern[k + 1] - '0';
400 buffer_append_string_len(con->request.uri, pattern + start, k - start);
402 if (!isdigit((unsigned char)pattern[k + 1])) {
403 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
404 buffer_append_string_len(con->request.uri, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2);
405 } else if (pattern[k] == '$') {
406 /* n is always > 0 */
407 if (num < (size_t)n) {
408 buffer_append_string(con->request.uri, list[num]);
410 } else if (p->conf.context == NULL) {
411 /* we have no context, we are global */
412 log_error_write(srv, __FILE__, __LINE__, "sb",
413 "used a redirect containing a %[0-9]+ in the global scope, ignored:",
414 rule->value);
416 } else {
417 config_append_cond_match_buffer(con, p->conf.context, con->request.uri, num);
420 k++;
421 start = k + 1;
425 buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
427 pcre_free(list);
429 if (con->plugin_ctx[p->id] == NULL) {
430 hctx = handler_ctx_init();
431 con->plugin_ctx[p->id] = hctx;
432 } else {
433 hctx = con->plugin_ctx[p->id];
436 if (rule->once) hctx->state = REWRITE_STATE_FINISHED;
438 return HANDLER_COMEBACK;
440 #undef N
443 return HANDLER_GO_ON;
446 URIHANDLER_FUNC(mod_rewrite_physical) {
447 plugin_data *p = p_d;
448 handler_t r;
449 stat_cache_entry *sce;
451 if (con->mode != DIRECT) return HANDLER_GO_ON;
453 mod_rewrite_patch_connection(srv, con, p);
454 p->conf.context = p->conf.context_NF;
456 if (!p->conf.rewrite_NF) return HANDLER_GO_ON;
458 /* skip if physical.path is a regular file */
459 sce = NULL;
460 if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
461 if (S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
464 switch(r = process_rewrite_rules(srv, con, p, p->conf.rewrite_NF)) {
465 case HANDLER_COMEBACK:
466 buffer_reset(con->physical.path);
467 /* fall through */
468 default:
469 return r;
472 return HANDLER_GO_ON;
475 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
476 plugin_data *p = p_d;
478 mod_rewrite_patch_connection(srv, con, p);
480 if (!p->conf.rewrite) return HANDLER_GO_ON;
482 return process_rewrite_rules(srv, con, p, p->conf.rewrite);
484 #endif
486 int mod_rewrite_plugin_init(plugin *p);
487 int mod_rewrite_plugin_init(plugin *p) {
488 p->version = LIGHTTPD_VERSION_ID;
489 p->name = buffer_init_string("rewrite");
491 #ifdef HAVE_PCRE_H
492 p->init = mod_rewrite_init;
493 /* it has to stay _raw as we are matching on uri + querystring
496 p->handle_uri_raw = mod_rewrite_uri_handler;
497 p->handle_physical = mod_rewrite_physical;
498 p->cleanup = mod_rewrite_free;
499 p->connection_reset = mod_rewrite_con_reset;
500 #endif
501 p->set_defaults = mod_rewrite_set_defaults;
503 p->data = NULL;
505 return 0;