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