1 #include "base.h"
2 #include "log.h"
3 #include "buffer.h"
4
5 #include "plugin.h"
6 #include "stat_cache.h"
7
8 #include <ctype.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #ifdef HAVE_PCRE_H
13 typedef struct {
14 pcre *key;
15
16 buffer *value;
17
18 int once;
19 } rewrite_rule;
20
21 typedef struct {
22 rewrite_rule **ptr;
23
24 size_t used;
25 size_t size;
26 } rewrite_rule_buffer;
27
28 typedef struct {
29 rewrite_rule_buffer *rewrite;
30 rewrite_rule_buffer *rewrite_NF;
31 data_config *context, *context_NF; /* to which apply me */
32 } plugin_config;
33
34 typedef struct {
35 enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
36 int loops;
37 } handler_ctx;
38
39 typedef struct {
40 PLUGIN_DATA;
41 buffer *match_buf;
42
43 plugin_config **config_storage;
44
45 plugin_config conf;
46 } plugin_data;
47
handler_ctx_init(void)48 static handler_ctx * handler_ctx_init(void) {
49 handler_ctx * hctx;
50
51 hctx = calloc(1, sizeof(*hctx));
52
53 hctx->state = REWRITE_STATE_UNSET;
54 hctx->loops = 0;
55
56 return hctx;
57 }
58
handler_ctx_free(handler_ctx * hctx)59 static void handler_ctx_free(handler_ctx *hctx) {
60 free(hctx);
61 }
62
rewrite_rule_buffer_init(void)63 static rewrite_rule_buffer *rewrite_rule_buffer_init(void) {
64 rewrite_rule_buffer *kvb;
65
66 kvb = calloc(1, sizeof(*kvb));
67
68 return kvb;
69 }
70
rewrite_rule_buffer_append(rewrite_rule_buffer * kvb,buffer * key,buffer * value,int once)71 static int rewrite_rule_buffer_append(rewrite_rule_buffer *kvb, buffer *key, buffer *value, int once) {
72 size_t i;
73 const char *errptr;
74 int erroff;
75
76 if (!key) return -1;
77
78 if (kvb->size == 0) {
79 kvb->size = 4;
80 kvb->used = 0;
81
82 kvb->ptr = malloc(kvb->size * sizeof(*kvb->ptr));
83
84 for(i = 0; i < kvb->size; i++) {
85 kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
86 }
87 } else if (kvb->used == kvb->size) {
88 kvb->size += 4;
89
90 kvb->ptr = realloc(kvb->ptr, kvb->size * sizeof(*kvb->ptr));
91
92 for(i = kvb->used; i < kvb->size; i++) {
93 kvb->ptr[i] = calloc(1, sizeof(**kvb->ptr));
94 }
95 }
96
97 if (NULL == (kvb->ptr[kvb->used]->key = pcre_compile(key->ptr,
98 0, &errptr, &erroff, NULL))) {
99
100 return -1;
101 }
102
103 kvb->ptr[kvb->used]->value = buffer_init();
104 buffer_copy_string_buffer(kvb->ptr[kvb->used]->value, value);
105 kvb->ptr[kvb->used]->once = once;
106
107 kvb->used++;
108
109 return 0;
110 }
111
rewrite_rule_buffer_free(rewrite_rule_buffer * kvb)112 static void rewrite_rule_buffer_free(rewrite_rule_buffer *kvb) {
113 size_t i;
114
115 for (i = 0; i < kvb->size; i++) {
116 if (kvb->ptr[i]->key) pcre_free(kvb->ptr[i]->key);
117 if (kvb->ptr[i]->value) buffer_free(kvb->ptr[i]->value);
118 free(kvb->ptr[i]);
119 }
120
121 if (kvb->ptr) free(kvb->ptr);
122
123 free(kvb);
124 }
125
126
INIT_FUNC(mod_rewrite_init)127 INIT_FUNC(mod_rewrite_init) {
128 plugin_data *p;
129
130 p = calloc(1, sizeof(*p));
131
132 p->match_buf = buffer_init();
133
134 return p;
135 }
136
FREE_FUNC(mod_rewrite_free)137 FREE_FUNC(mod_rewrite_free) {
138 plugin_data *p = p_d;
139
140 UNUSED(srv);
141
142 if (!p) return HANDLER_GO_ON;
143
144 buffer_free(p->match_buf);
145 if (p->config_storage) {
146 size_t i;
147 for (i = 0; i < srv->config_context->used; i++) {
148 plugin_config *s = p->config_storage[i];
149 rewrite_rule_buffer_free(s->rewrite);
150 rewrite_rule_buffer_free(s->rewrite_NF);
151
152 free(s);
153 }
154 free(p->config_storage);
155 }
156
157 free(p);
158
159 return HANDLER_GO_ON;
160 }
161
parse_config_entry(server * srv,array * ca,rewrite_rule_buffer * kvb,const char * option,int once)162 static int parse_config_entry(server *srv, array *ca, rewrite_rule_buffer *kvb, const char *option, int once) {
163 data_unset *du;
164
165 if (NULL != (du = array_get_element(ca, option))) {
166 data_array *da;
167 size_t j;
168
169 if (du->type != TYPE_ARRAY) {
170 log_error_write(srv, __FILE__, __LINE__, "sss",
171 "unexpected type for key: ", option, "array of strings");
172
173 return HANDLER_ERROR;
174 }
175
176 da = (data_array *)du;
177
178 for (j = 0; j < da->value->used; j++) {
179 if (da->value->data[j]->type != TYPE_STRING) {
180 log_error_write(srv, __FILE__, __LINE__, "sssbs",
181 "unexpected type for key: ",
182 option,
183 "[", da->value->data[j]->key, "](string)");
184
185 return HANDLER_ERROR;
186 }
187
188 if (0 != rewrite_rule_buffer_append(kvb,
189 ((data_string *)(da->value->data[j]))->key,
190 ((data_string *)(da->value->data[j]))->value,
191 once)) {
192 log_error_write(srv, __FILE__, __LINE__, "sb",
193 "pcre-compile failed for", da->value->data[j]->key);
194 }
195 }
196 }
197
198 return 0;
199 }
200 #else
parse_config_entry(server * srv,array * ca,const char * option)201 static int parse_config_entry(server *srv, array *ca, const char *option) {
202 static int logged_message = 0;
203 if (logged_message) return 0;
204 if (NULL != array_get_element(ca, option)) {
205 logged_message = 1;
206 log_error_write(srv, __FILE__, __LINE__, "s",
207 "pcre support is missing, please install libpcre and the headers");
208 }
209 return 0;
210 }
211 #endif
212
SETDEFAULTS_FUNC(mod_rewrite_set_defaults)213 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
214 size_t i = 0;
215 config_values_t cv[] = {
216 { "url.rewrite-repeat", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
217 { "url.rewrite-once", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
218
219 /* these functions only rewrite if the target is not already in the filestore
220 *
221 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
222 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
223 *
224 */
225 { "url.rewrite-repeat-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
226 { "url.rewrite-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
227
228 /* old names, still supported
229 *
230 * url.rewrite remapped to url.rewrite-once
231 * url.rewrite-final is url.rewrite-once
232 *
233 */
234 { "url.rewrite", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
235 { "url.rewrite-final", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
236 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
237 };
238
239 #ifdef HAVE_PCRE_H
240 plugin_data *p = p_d;
241
242 if (!p) return HANDLER_ERROR;
243
244 /* 0 */
245 p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
246 #else
247 UNUSED(p_d);
248 #endif
249
250 for (i = 0; i < srv->config_context->used; i++) {
251 array *ca;
252 #ifdef HAVE_PCRE_H
253 plugin_config *s;
254
255 s = calloc(1, sizeof(plugin_config));
256 s->rewrite = rewrite_rule_buffer_init();
257 s->rewrite_NF = rewrite_rule_buffer_init();
258 p->config_storage[i] = s;
259 #endif
260
261 ca = ((data_config *)srv->config_context->data[i])->value;
262
263 if (0 != config_insert_values_global(srv, ca, cv)) {
264 return HANDLER_ERROR;
265 }
266
267 #ifndef HAVE_PCRE_H
268 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
269 #endif
270 parse_config_entry(srv, ca, s->rewrite, "url.rewrite-once", 1);
271 parse_config_entry(srv, ca, s->rewrite, "url.rewrite-final", 1);
272 parse_config_entry(srv, ca, s->rewrite_NF, "url.rewrite-if-not-file", 1);
273 parse_config_entry(srv, ca, s->rewrite_NF, "url.rewrite-repeat-if-not-file", 0);
274 parse_config_entry(srv, ca, s->rewrite, "url.rewrite", 1);
275 parse_config_entry(srv, ca, s->rewrite, "url.rewrite-repeat", 0);
276 }
277
278 return HANDLER_GO_ON;
279 }
280
281 #ifdef HAVE_PCRE_H
282
283 #define PATCH(x) \
284 p->conf.x = s->x;
mod_rewrite_patch_connection(server * srv,connection * con,plugin_data * p)285 static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
286 size_t i, j;
287 plugin_config *s = p->config_storage[0];
288
289 PATCH(rewrite);
290 PATCH(rewrite_NF);
291 p->conf.context = NULL;
292 p->conf.context_NF = NULL;
293
294 /* skip the first, the global context */
295 for (i = 1; i < srv->config_context->used; i++) {
296 data_config *dc = (data_config *)srv->config_context->data[i];
297 s = p->config_storage[i];
298
299 if (COMP_HTTP_URL == dc->comp) continue;
300
301 /* condition didn't match */
302 if (!config_check_cond(srv, con, dc)) continue;
303
304 /* merge config */
305 for (j = 0; j < dc->value->used; j++) {
306 data_unset *du = dc->value->data[j];
307
308 if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
309 PATCH(rewrite);
310 p->conf.context = dc;
311 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
312 PATCH(rewrite);
313 p->conf.context = dc;
314 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
315 PATCH(rewrite);
316 p->conf.context = dc;
317 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-if-not-file"))) {
318 PATCH(rewrite_NF);
319 p->conf.context_NF = dc;
320 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
321 PATCH(rewrite_NF);
322 p->conf.context_NF = dc;
323 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
324 PATCH(rewrite);
325 p->conf.context = dc;
326 }
327 }
328 }
329
330 return 0;
331 }
332
URIHANDLER_FUNC(mod_rewrite_con_reset)333 URIHANDLER_FUNC(mod_rewrite_con_reset) {
334 plugin_data *p = p_d;
335
336 UNUSED(srv);
337
338 if (con->plugin_ctx[p->id]) {
339 handler_ctx_free(con->plugin_ctx[p->id]);
340 con->plugin_ctx[p->id] = NULL;
341 }
342
343 return HANDLER_GO_ON;
344 }
345
process_rewrite_rules(server * srv,connection * con,plugin_data * p,rewrite_rule_buffer * kvb)346 static int process_rewrite_rules(server *srv, connection *con, plugin_data *p, rewrite_rule_buffer *kvb) {
347 size_t i;
348 handler_ctx *hctx;
349
350 if (con->plugin_ctx[p->id]) {
351 hctx = con->plugin_ctx[p->id];
352
353 if (hctx->loops++ > 100) {
354 log_error_write(srv, __FILE__, __LINE__, "s",
355 "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, perhaps you want to use url.rewrite-once instead of url.rewrite-repeat");
356
357 return HANDLER_ERROR;
358 }
359
360 if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
361 }
362
363 buffer_copy_string_buffer(p->match_buf, con->request.uri);
364
365 for (i = 0; i < kvb->used; i++) {
366 pcre *match;
367 const char *pattern;
368 size_t pattern_len;
369 int n;
370 rewrite_rule *rule = kvb->ptr[i];
371 # define N 10
372 int ovec[N * 3];
373
374 match = rule->key;
375 pattern = rule->value->ptr;
376 pattern_len = rule->value->used - 1;
377
378 if ((n = pcre_exec(match, NULL, p->match_buf->ptr, p->match_buf->used - 1, 0, 0, ovec, 3 * N)) < 0) {
379 if (n != PCRE_ERROR_NOMATCH) {
380 log_error_write(srv, __FILE__, __LINE__, "sd",
381 "execution error while matching: ", n);
382 return HANDLER_ERROR;
383 }
384 } else {
385 const char **list;
386 size_t start;
387 size_t k;
388
389 /* it matched */
390 pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list);
391
392 /* search for $[0-9] */
393
394 buffer_reset(con->request.uri);
395
396 start = 0;
397 for (k = 0; k+1 < pattern_len; k++) {
398 if (pattern[k] == '$' || pattern[k] == '%') {
399 /* got one */
400
401 size_t num = pattern[k + 1] - '0';
402
403 buffer_append_string_len(con->request.uri, pattern + start, k - start);
404
405 if (!isdigit((unsigned char)pattern[k + 1])) {
406 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
407 buffer_append_string_len(con->request.uri, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2);
408 } else if (pattern[k] == '$') {
409 /* n is always > 0 */
410 if (num < (size_t)n) {
411 buffer_append_string(con->request.uri, list[num]);
412 }
413 } else if (p->conf.context == NULL) {
414 /* we have no context, we are global */
415 log_error_write(srv, __FILE__, __LINE__, "sb",
416 "used a redirect containing a %[0-9]+ in the global scope, ignored:",
417 rule->value);
418
419 } else {
420 config_append_cond_match_buffer(con, p->conf.context, con->request.uri, num);
421 }
422
423 k++;
424 start = k + 1;
425 }
426 }
427
428 buffer_append_string_len(con->request.uri, pattern + start, pattern_len - start);
429
430 pcre_free(list);
431
432 if (con->plugin_ctx[p->id] == NULL) {
433 hctx = handler_ctx_init();
434 con->plugin_ctx[p->id] = hctx;
435 } else {
436 hctx = con->plugin_ctx[p->id];
437 }
438
439 if (rule->once) hctx->state = REWRITE_STATE_FINISHED;
440
441 return HANDLER_COMEBACK;
442 }
443 #undef N
444 }
445
446 return HANDLER_GO_ON;
447 }
448
URIHANDLER_FUNC(mod_rewrite_physical)449 URIHANDLER_FUNC(mod_rewrite_physical) {
450 plugin_data *p = p_d;
451 handler_t r;
452 stat_cache_entry *sce;
453
454 if (con->mode != DIRECT) return HANDLER_GO_ON;
455
456 mod_rewrite_patch_connection(srv, con, p);
457 p->conf.context = p->conf.context_NF;
458
459 if (!p->conf.rewrite_NF) return HANDLER_GO_ON;
460
461 /* skip if physical.path is a regular file */
462 sce = NULL;
463 if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
464 if (S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
465 }
466
467 switch(r = process_rewrite_rules(srv, con, p, p->conf.rewrite_NF)) {
468 case HANDLER_COMEBACK:
469 buffer_reset(con->physical.path);
470 default:
471 return r;
472 }
473
474 return HANDLER_GO_ON;
475 }
476
URIHANDLER_FUNC(mod_rewrite_uri_handler)477 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
478 plugin_data *p = p_d;
479
480 mod_rewrite_patch_connection(srv, con, p);
481
482 if (!p->conf.rewrite) return HANDLER_GO_ON;
483
484 return process_rewrite_rules(srv, con, p, p->conf.rewrite);
485
486 return HANDLER_GO_ON;
487 }
488 #endif
489
490 int mod_rewrite_plugin_init(plugin *p);
mod_rewrite_plugin_init(plugin * p)491 int mod_rewrite_plugin_init(plugin *p) {
492 p->version = LIGHTTPD_VERSION_ID;
493 p->name = buffer_init_string("rewrite");
494
495 #ifdef HAVE_PCRE_H
496 p->init = mod_rewrite_init;
497 /* it has to stay _raw as we are matching on uri + querystring
498 */
499
500 p->handle_uri_raw = mod_rewrite_uri_handler;
501 p->handle_physical = mod_rewrite_physical;
502 p->cleanup = mod_rewrite_free;
503 p->connection_reset = mod_rewrite_con_reset;
504 #endif
505 p->set_defaults = mod_rewrite_set_defaults;
506
507 p->data = NULL;
508
509 return 0;
510 }
511