xref: /lighttpd1.4/src/mod_rewrite.c (revision 49e9f0ac)
1 #include "first.h"
2 
3 #include "base.h"
4 #include "keyvalue.h"
5 #include "log.h"
6 #include "buffer.h"
7 #include "burl.h"
8 
9 #include "plugin.h"
10 #include "stat_cache.h"
11 
12 #include <stdlib.h>
13 #include <string.h>
14 
15 typedef struct {
16 	pcre_keyvalue_buffer *rewrite;
17 	pcre_keyvalue_buffer *rewrite_NF;
18 	data_config *context, *context_NF; /* to which apply me */
19 	int rewrite_repeat_idx, rewrite_NF_repeat_idx;
20 } plugin_config;
21 
22 typedef struct {
23 	enum { REWRITE_STATE_UNSET, REWRITE_STATE_FINISHED} state;
24 	int loops;
25 } handler_ctx;
26 
27 typedef struct {
28 	PLUGIN_DATA;
29 	plugin_config **config_storage;
30 	plugin_config conf;
31 } plugin_data;
32 
33 static handler_ctx * handler_ctx_init(void) {
34 	handler_ctx * hctx;
35 
36 	hctx = calloc(1, sizeof(*hctx));
37 
38 	hctx->state = REWRITE_STATE_UNSET;
39 	hctx->loops = 0;
40 
41 	return hctx;
42 }
43 
44 static void handler_ctx_free(handler_ctx *hctx) {
45 	free(hctx);
46 }
47 
48 INIT_FUNC(mod_rewrite_init) {
49 	return calloc(1, sizeof(plugin_data));
50 }
51 
52 FREE_FUNC(mod_rewrite_free) {
53 	plugin_data *p = p_d;
54 	if (!p) return HANDLER_GO_ON;
55 
56 	if (p->config_storage) {
57 		size_t i;
58 		for (i = 0; i < srv->config_context->used; i++) {
59 			plugin_config *s = p->config_storage[i];
60 			if (NULL == s) continue;
61 			pcre_keyvalue_buffer_free(s->rewrite);
62 			pcre_keyvalue_buffer_free(s->rewrite_NF);
63 			free(s);
64 		}
65 		free(p->config_storage);
66 	}
67 
68 	free(p);
69 	return HANDLER_GO_ON;
70 }
71 
72 static int parse_config_entry(server *srv, array *ca, pcre_keyvalue_buffer *kvb, const char *option, size_t olen) {
73 	data_unset *du;
74 
75 	if (NULL != (du = array_get_element_klen(ca, option, olen))) {
76 		data_array *da;
77 		size_t j;
78 
79 		da = (data_array *)du;
80 
81 		if (du->type != TYPE_ARRAY || !array_is_kvstring(da->value)) {
82 			log_error_write(srv, __FILE__, __LINE__, "SSS",
83 					"unexpected value for ", option, "; expected list of \"regex\" => \"subst\"");
84 			return HANDLER_ERROR;
85 		}
86 
87 		for (j = 0; j < da->value->used; j++) {
88 			data_string *ds = (data_string *)da->value->data[j];
89 			if (srv->srvconf.http_url_normalize) {
90 				pcre_keyvalue_burl_normalize_key(ds->key, srv->tmp_buf);
91 				pcre_keyvalue_burl_normalize_value(ds->value, srv->tmp_buf);
92 			}
93 			if (0 != pcre_keyvalue_buffer_append(srv, kvb, ds->key, ds->value)) {
94 				log_error_write(srv, __FILE__, __LINE__, "sb",
95 						"pcre-compile failed for", ds->key);
96 				return HANDLER_ERROR;
97 			}
98 		}
99 	}
100 
101 	return 0;
102 }
103 
104 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
105 	size_t i = 0;
106 	config_values_t cv[] = {
107 		{ "url.rewrite-repeat",        NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
108 		{ "url.rewrite-once",          NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
109 
110 		/* these functions only rewrite if the target is not already in the filestore
111 		 *
112 		 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
113 		 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
114 		 *
115 		 */
116 		{ "url.rewrite-repeat-if-not-file", NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
117 		{ "url.rewrite-if-not-file",        NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
118 
119 		/* old names, still supported
120 		 *
121 		 * url.rewrite remapped to url.rewrite-once
122 		 * url.rewrite-final    is url.rewrite-once
123 		 *
124 		 */
125 		{ "url.rewrite",               NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
126 		{ "url.rewrite-final",         NULL, T_CONFIG_LOCAL, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
127 		{ NULL,                        NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
128 	};
129 
130 	plugin_data *p = p_d;
131 
132 	if (!p) return HANDLER_ERROR;
133 
134 	/* 0 */
135 	p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
136 
137 	for (i = 0; i < srv->config_context->used; i++) {
138 		data_config const* config = (data_config const*)srv->config_context->data[i];
139 		plugin_config *s;
140 
141 		s = calloc(1, sizeof(plugin_config));
142 		s->rewrite = pcre_keyvalue_buffer_init();
143 		s->rewrite_NF = pcre_keyvalue_buffer_init();
144 		p->config_storage[i] = s;
145 
146 		if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
147 			return HANDLER_ERROR;
148 		}
149 
150 		parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-once"));
151 		parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-final"));
152 		parse_config_entry(srv, config->value, s->rewrite_NF, CONST_STR_LEN("url.rewrite-if-not-file"));
153 		s->rewrite_NF_repeat_idx = (int)s->rewrite_NF->used;
154 		parse_config_entry(srv, config->value, s->rewrite_NF, CONST_STR_LEN("url.rewrite-repeat-if-not-file"));
155 		parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite"));
156 		s->rewrite_repeat_idx = (int)s->rewrite->used;
157 		parse_config_entry(srv, config->value, s->rewrite, CONST_STR_LEN("url.rewrite-repeat"));
158 	}
159 
160 	return HANDLER_GO_ON;
161 }
162 
163 #define PATCH(x) \
164 	p->conf.x = s->x;
165 static int mod_rewrite_patch_connection(server *srv, connection *con, plugin_data *p) {
166 	size_t i, j;
167 	plugin_config *s = p->config_storage[0];
168 
169 	PATCH(rewrite);
170 	PATCH(rewrite_NF);
171 	p->conf.context = NULL;
172 	p->conf.context_NF = NULL;
173 	PATCH(rewrite_repeat_idx);
174 	PATCH(rewrite_NF_repeat_idx);
175 
176 	/* skip the first, the global context */
177 	for (i = 1; i < srv->config_context->used; i++) {
178 		data_config *dc = (data_config *)srv->config_context->data[i];
179 		s = p->config_storage[i];
180 
181 		/* condition didn't match */
182 		if (!config_check_cond(srv, con, dc)) continue;
183 
184 		/* merge config */
185 		for (j = 0; j < dc->value->used; j++) {
186 			data_unset *du = dc->value->data[j];
187 
188 			if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite"))) {
189 				PATCH(rewrite);
190 				p->conf.context = dc;
191 				PATCH(rewrite_repeat_idx);
192 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-once"))) {
193 				PATCH(rewrite);
194 				p->conf.context = dc;
195 				PATCH(rewrite_repeat_idx);
196 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat"))) {
197 				PATCH(rewrite);
198 				p->conf.context = dc;
199 				PATCH(rewrite_repeat_idx);
200 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-if-not-file"))) {
201 				PATCH(rewrite_NF);
202 				p->conf.context_NF = dc;
203 				PATCH(rewrite_NF_repeat_idx);
204 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
205 				PATCH(rewrite_NF);
206 				p->conf.context_NF = dc;
207 				PATCH(rewrite_NF_repeat_idx);
208 			} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("url.rewrite-final"))) {
209 				PATCH(rewrite);
210 				p->conf.context = dc;
211 				PATCH(rewrite_repeat_idx);
212 			}
213 		}
214 	}
215 
216 	return 0;
217 }
218 
219 URIHANDLER_FUNC(mod_rewrite_con_reset) {
220 	plugin_data *p = p_d;
221 
222 	UNUSED(srv);
223 
224 	if (con->plugin_ctx[p->id]) {
225 		handler_ctx_free(con->plugin_ctx[p->id]);
226 		con->plugin_ctx[p->id] = NULL;
227 	}
228 
229 	return HANDLER_GO_ON;
230 }
231 
232 static handler_t process_rewrite_rules(server *srv, connection *con, plugin_data *p, pcre_keyvalue_buffer *kvb, int repeat_idx) {
233 	handler_ctx *hctx;
234 	struct burl_parts_t burl;
235 	pcre_keyvalue_ctx ctx;
236 	handler_t rc;
237 
238 	if (con->plugin_ctx[p->id]) {
239 		hctx = con->plugin_ctx[p->id];
240 
241 		if (hctx->loops++ > 100) {
242 			data_config *dc = p->conf.context;
243 			if (NULL == dc) {
244 				log_error_write(srv, __FILE__, __LINE__,  "s",
245 						"ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request");
246 				return HANDLER_ERROR;
247 			}
248 			log_error_write(srv, __FILE__, __LINE__,  "SbbSBS",
249 					"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, "\")");
250 
251 			return HANDLER_ERROR;
252 		}
253 
254 		if (hctx->state == REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
255 	}
256 
257 	ctx.cache = p->conf.context ? &con->cond_cache[p->conf.context->context_ndx] : NULL;
258 	ctx.burl = &burl;
259 	burl.scheme    = con->uri.scheme;
260 	burl.authority = con->uri.authority;
261 	burl.port      = sock_addr_get_port(&con->srv_socket->addr);
262 	burl.path      = con->uri.path_raw;
263 	burl.query     = con->uri.query;
264 	if (buffer_string_is_empty(burl.authority))
265 		burl.authority = con->server_name;
266 
267 	rc = pcre_keyvalue_buffer_process(kvb, &ctx, con->request.uri, srv->tmp_buf);
268 	if (HANDLER_FINISHED == rc && !buffer_is_empty(srv->tmp_buf) && srv->tmp_buf->ptr[0] == '/') {
269 		buffer_copy_buffer(con->request.uri, srv->tmp_buf);
270 		if (con->plugin_ctx[p->id] == NULL) {
271 			hctx = handler_ctx_init();
272 			con->plugin_ctx[p->id] = hctx;
273 		} else {
274 			hctx = con->plugin_ctx[p->id];
275 		}
276 		if (ctx.m < repeat_idx) hctx->state = REWRITE_STATE_FINISHED;
277 		buffer_reset(con->physical.path);
278 		rc = HANDLER_COMEBACK;
279 	}
280 	else if (HANDLER_FINISHED == rc) {
281 		rc = HANDLER_ERROR;
282 		log_error_write(srv, __FILE__, __LINE__, "sb",
283 				"mod_rewrite invalid result (not beginning with '/') while processing uri:",
284 				con->request.uri);
285 	}
286 	else if (HANDLER_ERROR == rc) {
287 		log_error_write(srv, __FILE__, __LINE__, "sb",
288 				"pcre_exec() error while processing uri:",
289 				con->request.uri);
290 	}
291 	return rc;
292 }
293 
294 URIHANDLER_FUNC(mod_rewrite_physical) {
295 	plugin_data *p = p_d;
296 	stat_cache_entry *sce;
297 
298 	if (con->mode != DIRECT) return HANDLER_GO_ON;
299 
300 	mod_rewrite_patch_connection(srv, con, p);
301 	p->conf.context = p->conf.context_NF;
302 	if (!p->conf.rewrite_NF->used) return HANDLER_GO_ON;
303 
304 	/* skip if physical.path is a regular file */
305 	sce = NULL;
306 	if (HANDLER_ERROR != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
307 		if (S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
308 	}
309 
310 	return process_rewrite_rules(srv, con, p, p->conf.rewrite_NF, p->conf.rewrite_NF_repeat_idx);
311 }
312 
313 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
314 	plugin_data *p = p_d;
315 
316 	mod_rewrite_patch_connection(srv, con, p);
317 	if (!p->conf.rewrite->used) return HANDLER_GO_ON;
318 
319 	return process_rewrite_rules(srv, con, p, p->conf.rewrite, p->conf.rewrite_repeat_idx);
320 }
321 
322 int mod_rewrite_plugin_init(plugin *p);
323 int mod_rewrite_plugin_init(plugin *p) {
324 	p->version     = LIGHTTPD_VERSION_ID;
325 	p->name        = buffer_init_string("rewrite");
326 
327 	p->init        = mod_rewrite_init;
328 	/* it has to stay _raw as we are matching on uri + querystring
329 	 */
330 
331 	p->handle_uri_raw = mod_rewrite_uri_handler;
332 	p->handle_physical = mod_rewrite_physical;
333 	p->cleanup     = mod_rewrite_free;
334 	p->connection_reset = mod_rewrite_con_reset;
335 	p->set_defaults = mod_rewrite_set_defaults;
336 
337 	p->data        = NULL;
338 
339 	return 0;
340 }
341