[mod_ssi] produce content in subrequest hook
[lighttpd.git] / src / mod_rewrite.c
blob2e5fd0632e400ec899e447d5e8abf074fe08c29e
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, int once) {
168 data_unset *du;
170 if (NULL != (du = array_get_element(ca, option))) {
171 data_array *da;
172 size_t j;
174 if (du->type != TYPE_ARRAY) {
175 log_error_write(srv, __FILE__, __LINE__, "sss",
176 "unexpected type for key: ", option, "array of strings");
178 return HANDLER_ERROR;
181 da = (data_array *)du;
183 for (j = 0; j < da->value->used; j++) {
184 if (da->value->data[j]->type != TYPE_STRING) {
185 log_error_write(srv, __FILE__, __LINE__, "sssbs",
186 "unexpected type for key: ",
187 option,
188 "[", da->value->data[j]->key, "](string)");
190 return HANDLER_ERROR;
193 if (0 != rewrite_rule_buffer_append(kvb,
194 ((data_string *)(da->value->data[j]))->key,
195 ((data_string *)(da->value->data[j]))->value,
196 once)) {
197 log_error_write(srv, __FILE__, __LINE__, "sb",
198 "pcre-compile failed for", da->value->data[j]->key);
199 return HANDLER_ERROR;
204 return 0;
206 #else
207 static int parse_config_entry(server *srv, array *ca, const char *option) {
208 static int logged_message = 0;
209 if (logged_message) return 0;
210 if (NULL != array_get_element(ca, option)) {
211 logged_message = 1;
212 log_error_write(srv, __FILE__, __LINE__, "s",
213 "pcre support is missing, please install libpcre and the headers");
215 return 0;
217 #endif
219 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
220 size_t i = 0;
221 config_values_t cv[] = {
222 { "url.rewrite-repeat", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
223 { "url.rewrite-once", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
225 /* these functions only rewrite if the target is not already in the filestore
227 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
228 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
231 { "url.rewrite-repeat-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
232 { "url.rewrite-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
234 /* old names, still supported
236 * url.rewrite remapped to url.rewrite-once
237 * url.rewrite-final is url.rewrite-once
240 { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
241 { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
242 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
245 #ifdef HAVE_PCRE_H
246 plugin_data *p = p_d;
248 if (!p) return HANDLER_ERROR;
250 /* 0 */
251 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
252 #else
253 UNUSED(p_d);
254 #endif
256 for (i = 0; i < srv->config_context->used; i++) {
257 data_config const* config = (data_config const*)srv->config_context->data[i];
258 #ifdef HAVE_PCRE_H
259 plugin_config *s;
261 s = calloc(1, sizeof(plugin_config));
262 s->rewrite = rewrite_rule_buffer_init();
263 s->rewrite_NF = rewrite_rule_buffer_init();
264 p->config_storage[i] = s;
265 #endif
267 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
268 return HANDLER_ERROR;
271 #ifndef HAVE_PCRE_H
272 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
273 #endif
274 parse_config_entry(srv, config->value, s->rewrite, "url.rewrite-once", 1);
275 parse_config_entry(srv, config->value, s->rewrite, "url.rewrite-final", 1);
276 parse_config_entry(srv, config->value, s->rewrite_NF, "url.rewrite-if-not-file", 1);
277 parse_config_entry(srv, config->value, s->rewrite_NF, "url.rewrite-repeat-if-not-file", 0);
278 parse_config_entry(srv, config->value, s->rewrite, "url.rewrite", 1);
279 parse_config_entry(srv, config->value, s->rewrite, "url.rewrite-repeat", 0);
282 return HANDLER_GO_ON;
285 #ifdef HAVE_PCRE_H
287 #define PATCH(x) \
288 p->conf.x = s->x;
289 static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
290 size_t i, j;
291 plugin_config *s = p->config_storage[0];
293 PATCH(rewrite);
294 PATCH(rewrite_NF);
295 p->conf.context = NULL;
296 p->conf.context_NF = NULL;
298 /* skip the first, the global context */
299 for (i = 1; i < srv->config_context->used; i++) {
300 data_config *dc = (data_config *)srv->config_context->data[i];
301 s = p->config_storage[i];
303 /* condition didn't match */
304 if (!config_check_cond(srv, con, dc)) continue;
306 /* merge config */
307 for (j = 0; j < dc->value->used; j++) {
308 data_unset *du = dc->value->data[j];
310 if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
311 PATCH(rewrite);
312 p->conf.context = dc;
313 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
314 PATCH(rewrite);
315 p->conf.context = dc;
316 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
317 PATCH(rewrite);
318 p->conf.context = dc;
319 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-if-not-file"))) {
320 PATCH(rewrite_NF);
321 p->conf.context_NF = dc;
322 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
323 PATCH(rewrite_NF);
324 p->conf.context_NF = dc;
325 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
326 PATCH(rewrite);
327 p->conf.context = dc;
332 return 0;
335 URIHANDLER_FUNC(mod_rewrite_con_reset) {
336 plugin_data *p = p_d;
338 UNUSED(srv);
340 if (con->plugin_ctx[p->id]) {
341 handler_ctx_free(con->plugin_ctx[p->id]);
342 con->plugin_ctx[p->id] = NULL;
345 return HANDLER_GO_ON;
348 static handler_t process_rewrite_rules(server *srv, connection *con, plugin_data *p, rewrite_rule_buffer *kvb) {
349 size_t i;
350 handler_ctx *hctx;
352 if (con->plugin_ctx[p->id]) {
353 hctx = con->plugin_ctx[p->id];
355 if (hctx->loops++ > 100) {
356 data_config *dc = p->conf.context;
357 log_error_write(srv, __FILE__, __LINE__, "SbbSBS",
358 "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, "\")");
360 return HANDLER_ERROR;
363 if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
366 buffer_copy_buffer(p->match_buf, con->request.uri);
368 for (i = 0; i < kvb->used; i++) {
369 pcre *match;
370 const char *pattern;
371 size_t pattern_len;
372 int n;
373 rewrite_rule *rule = kvb->ptr[i];
374 # define N 10
375 int ovec[N * 3];
377 match = rule->key;
378 pattern = rule->value->ptr;
379 pattern_len = buffer_string_length(rule->value);
381 if ((n = pcre_exec(match, NULL, CONST_BUF_LEN(p->match_buf), 0, 0, ovec, 3 * N)) < 0) {
382 if (n != PCRE_ERROR_NOMATCH) {
383 log_error_write(srv, __FILE__, __LINE__, "sd",
384 "execution error while matching: ", n);
385 return HANDLER_ERROR;
387 } else if (0 == pattern_len) {
388 /* short-circuit if blank replacement pattern
389 * (do not attempt to match against remaining rewrite rules) */
390 return HANDLER_GO_ON;
391 } else {
392 const char **list;
393 size_t start;
394 size_t k;
396 /* it matched */
397 pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
399 /* search for $[0-9] */
401 buffer_reset(con->request.uri);
403 start = 0;
404 for (k = 0; k+1 < pattern_len; k++) {
405 if (pattern[k] == '$' || pattern[k] == '%') {
406 /* got one */
408 size_t num = pattern[k + 1] - '0';
410 buffer_append_string_len(con->request.uri, pattern + start, k - start);
412 if (!isdigit((unsigned char)pattern[k + 1])) {
413 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
414 buffer_append_string_len(con->request.uri, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2);
415 } else if (pattern[k] == '$') {
416 /* n is always > 0 */
417 if (num < (size_t)n) {
418 buffer_append_string(con->request.uri, list[num]);
420 } else if (p->conf.context == NULL) {
421 /* we have no context, we are global */
422 log_error_write(srv, __FILE__, __LINE__, "sb",
423 "used a redirect containing a %[0-9]+ in the global scope, ignored:",
424 rule->value);
426 } else {
427 config_append_cond_match_buffer(con, p->conf.context, con->request.uri, num);
430 k++;
431 start = k + 1;
435 buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
437 pcre_free(list);
439 if (con->plugin_ctx[p->id] == NULL) {
440 hctx = handler_ctx_init();
441 con->plugin_ctx[p->id] = hctx;
442 } else {
443 hctx = con->plugin_ctx[p->id];
446 if (rule->once) hctx->state = REWRITE_STATE_FINISHED;
448 return HANDLER_COMEBACK;
450 #undef N
453 return HANDLER_GO_ON;
456 URIHANDLER_FUNC(mod_rewrite_physical) {
457 plugin_data *p = p_d;
458 handler_t r;
459 stat_cache_entry *sce;
461 if (con->mode != DIRECT) return HANDLER_GO_ON;
463 mod_rewrite_patch_connection(srv, con, p);
464 p->conf.context = p->conf.context_NF;
466 if (!p->conf.rewrite_NF) return HANDLER_GO_ON;
468 /* skip if physical.path is a regular file */
469 sce = NULL;
470 if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
471 if (S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
474 switch(r = process_rewrite_rules(srv, con, p, p->conf.rewrite_NF)) {
475 case HANDLER_COMEBACK:
476 buffer_reset(con->physical.path);
477 default:
478 return r;
481 return HANDLER_GO_ON;
484 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
485 plugin_data *p = p_d;
487 mod_rewrite_patch_connection(srv, con, p);
489 if (!p->conf.rewrite) return HANDLER_GO_ON;
491 return process_rewrite_rules(srv, con, p, p->conf.rewrite);
493 #endif
495 int mod_rewrite_plugin_init(plugin *p);
496 int mod_rewrite_plugin_init(plugin *p) {
497 p->version = LIGHTTPD_VERSION_ID;
498 p->name = buffer_init_string("rewrite");
500 #ifdef HAVE_PCRE_H
501 p->init = mod_rewrite_init;
502 /* it has to stay _raw as we are matching on uri + querystring
505 p->handle_uri_raw = mod_rewrite_uri_handler;
506 p->handle_physical = mod_rewrite_physical;
507 p->cleanup = mod_rewrite_free;
508 p->connection_reset = mod_rewrite_con_reset;
509 #endif
510 p->set_defaults = mod_rewrite_set_defaults;
512 p->data = NULL;
514 return 0;