#include "first.h" #include "base.h" #include "log.h" #include "buffer.h" #include "plugin.h" #include "response.h" #include #include #include typedef struct { pcre_keyvalue_buffer *redirect; data_config *context; /* to which apply me */ unsigned short redirect_code; } plugin_config; typedef struct { PLUGIN_DATA; buffer *match_buf; buffer *location; plugin_config **config_storage; plugin_config conf; } plugin_data; INIT_FUNC(mod_redirect_init) { plugin_data *p; p = calloc(1, sizeof(*p)); p->match_buf = buffer_init(); p->location = buffer_init(); return p; } FREE_FUNC(mod_redirect_free) { plugin_data *p = p_d; if (!p) return HANDLER_GO_ON; if (p->config_storage) { size_t i; for (i = 0; i < srv->config_context->used; i++) { plugin_config *s = p->config_storage[i]; if (NULL == s) continue; pcre_keyvalue_buffer_free(s->redirect); free(s); } free(p->config_storage); } buffer_free(p->match_buf); buffer_free(p->location); free(p); return HANDLER_GO_ON; } SETDEFAULTS_FUNC(mod_redirect_set_defaults) { plugin_data *p = p_d; size_t i = 0; config_values_t cv[] = { { "url.redirect", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ { "url.redirect-code", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; if (!p) return HANDLER_ERROR; /* 0 */ p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); for (i = 0; i < srv->config_context->used; i++) { data_config const* config = (data_config const*)srv->config_context->data[i]; plugin_config *s; size_t j; data_unset *du; data_array *da; s = calloc(1, sizeof(plugin_config)); s->redirect = pcre_keyvalue_buffer_init(); s->redirect_code = 301; cv[0].destination = s->redirect; cv[1].destination = &(s->redirect_code); p->config_storage[i] = s; if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { return HANDLER_ERROR; } if (NULL == (du = array_get_element(config->value, "url.redirect"))) { /* no url.redirect defined */ continue; } if (du->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "sss", "unexpected type for key: ", "url.redirect", "array of strings"); return HANDLER_ERROR; } da = (data_array *)du; for (j = 0; j < da->value->used; j++) { if (da->value->data[j]->type != TYPE_STRING) { log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "url.redirect", "[", da->value->data[j]->key, "](string)"); return HANDLER_ERROR; } if (0 != pcre_keyvalue_buffer_append(srv, s->redirect, ((data_string *)(da->value->data[j]))->key->ptr, ((data_string *)(da->value->data[j]))->value->ptr)) { log_error_write(srv, __FILE__, __LINE__, "sb", "pcre-compile failed for", da->value->data[j]->key); return HANDLER_ERROR; } } } return HANDLER_GO_ON; } #ifdef HAVE_PCRE_H static int mod_redirect_patch_connection(server *srv, connection *con, plugin_data *p) { size_t i, j; plugin_config *s = p->config_storage[0]; p->conf.redirect = s->redirect; p->conf.redirect_code = s->redirect_code; p->conf.context = NULL; /* skip the first, the global context */ for (i = 1; i < srv->config_context->used; i++) { data_config *dc = (data_config *)srv->config_context->data[i]; s = p->config_storage[i]; /* condition didn't match */ if (!config_check_cond(srv, con, dc)) continue; /* merge config */ for (j = 0; j < dc->value->used; j++) { data_unset *du = dc->value->data[j]; if (0 == strcmp(du->key->ptr, "url.redirect")) { p->conf.redirect = s->redirect; p->conf.context = dc; } else if (0 == strcmp(du->key->ptr, "url.redirect-code")) { p->conf.redirect_code = s->redirect_code; } } } return 0; } #endif static handler_t mod_redirect_uri_handler(server *srv, connection *con, void *p_data) { #ifdef HAVE_PCRE_H plugin_data *p = p_data; size_t i; /* * REWRITE URL * * e.g. redirect /base/ to /index.php?section=base * */ mod_redirect_patch_connection(srv, con, p); buffer_copy_buffer(p->match_buf, con->request.uri); for (i = 0; i < p->conf.redirect->used; i++) { pcre *match; pcre_extra *extra; const char *pattern; size_t pattern_len; int n; pcre_keyvalue *kv = p->conf.redirect->kv[i]; # define N 10 int ovec[N * 3]; match = kv->key; extra = kv->key_extra; pattern = kv->value->ptr; pattern_len = buffer_string_length(kv->value); if ((n = pcre_exec(match, extra, CONST_BUF_LEN(p->match_buf), 0, 0, ovec, 3 * N)) < 0) { if (n != PCRE_ERROR_NOMATCH) { log_error_write(srv, __FILE__, __LINE__, "sd", "execution error while matching: ", n); return HANDLER_ERROR; } } else if (0 == pattern_len) { /* short-circuit if blank replacement pattern * (do not attempt to match against remaining redirect rules) */ return HANDLER_GO_ON; } else { const char **list; size_t start; size_t k; /* it matched */ pcre_get_substring_list(p->match_buf->ptr, ovec, n, &list); /* search for $[0-9] */ buffer_reset(p->location); start = 0; for (k = 0; k + 1 < pattern_len; k++) { if (pattern[k] == '$' || pattern[k] == '%') { /* got one */ size_t num = pattern[k + 1] - '0'; buffer_append_string_len(p->location, pattern + start, k - start); if (!isdigit((unsigned char)pattern[k + 1])) { /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */ buffer_append_string_len(p->location, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2); } else if (pattern[k] == '$') { /* n is always > 0 */ if (num < (size_t)n) { buffer_append_string(p->location, list[num]); } } else if (p->conf.context == NULL) { /* we have no context, we are global */ log_error_write(srv, __FILE__, __LINE__, "sb", "used a rewrite containing a %[0-9]+ in the global scope, ignored:", kv->value); } else { config_append_cond_match_buffer(con, p->conf.context, p->location, num); } k++; start = k + 1; } } buffer_append_string_len(p->location, pattern + start, pattern_len - start); pcre_free(list); response_header_insert(srv, con, CONST_STR_LEN("Location"), CONST_BUF_LEN(p->location)); con->http_status = p->conf.redirect_code > 99 && p->conf.redirect_code < 1000 ? p->conf.redirect_code : 301; con->mode = DIRECT; con->file_finished = 1; return HANDLER_FINISHED; } } #undef N #else UNUSED(srv); UNUSED(con); UNUSED(p_data); #endif return HANDLER_GO_ON; } int mod_redirect_plugin_init(plugin *p); int mod_redirect_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; p->name = buffer_init_string("redirect"); p->init = mod_redirect_init; p->handle_uri_clean = mod_redirect_uri_handler; p->set_defaults = mod_redirect_set_defaults; p->cleanup = mod_redirect_free; p->data = NULL; return 0; }