xref: /lighttpd1.4/src/mod_rewrite.c (revision 3392e8fb)
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 } plugin_config;
19 
20 enum { REWRITE_STATE_REWRITTEN = 1024, REWRITE_STATE_FINISHED = 2048}; /*flags*/
21 
22 typedef struct {
23     PLUGIN_DATA;
24     plugin_config defaults;
25     plugin_config conf;
26 } plugin_data;
27 
28 INIT_FUNC(mod_rewrite_init) {
29     return calloc(1, sizeof(plugin_data));
30 }
31 
32 FREE_FUNC(mod_rewrite_free) {
33     plugin_data * const p = p_d;
34     if (NULL == p->cvlist) return;
35     /* (init i to 0 if global context; to 1 to skip empty global context) */
36     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
37         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
38         /* kvb value might be copied in multiple directives; free only once */
39         pcre_keyvalue_buffer *kvb = NULL, *kvb_NF = NULL;
40         for (; -1 != cpv->k_id; ++cpv) {
41             switch (cpv->k_id) {
42               case 0: /* url.rewrite-once */
43               case 1: /* url.rewrite-final */
44               case 2: /* url.rewrite */
45               case 3: /* url.rewrite-repeat */
46                 if (cpv->vtype == T_CONFIG_LOCAL)
47                     kvb = cpv->v.v;
48                 break;
49               case 4: /* url.rewrite-if-not-file */
50               case 5: /* url.rewrite-repeat-if-not-file */
51                 if (cpv->vtype == T_CONFIG_LOCAL)
52                     kvb_NF = cpv->v.v;
53               default:
54                 break;
55             }
56         }
57         if (kvb)    pcre_keyvalue_buffer_free(kvb);
58         if (kvb_NF) pcre_keyvalue_buffer_free(kvb_NF);
59     }
60 }
61 
62 static void mod_rewrite_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
63     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
64       case 0: /* url.rewrite-once */
65       case 1: /* url.rewrite-final */
66       case 2: /* url.rewrite */
67       case 3: /* url.rewrite-repeat */
68         /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here in mod_rewrite*/
69             pconf->rewrite = cpv->v.v;
70         break;
71       case 4: /* url.rewrite-if-not-file */
72       case 5: /* url.rewrite-repeat-if-not-file */
73         /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here in mod_rewrite*/
74             pconf->rewrite_NF = cpv->v.v;
75         break;
76       default:/* should not happen */
77         return;
78     }
79 }
80 
81 static void mod_rewrite_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
82     do {
83         mod_rewrite_merge_config_cpv(pconf, cpv);
84     } while ((++cpv)->k_id != -1);
85 }
86 
87 static void mod_rewrite_patch_config(request_st * const r, plugin_data * const p) {
88     p->conf = p->defaults; /* copy small struct instead of memcpy() */
89     /*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
90     for (int i = 1, used = p->nconfig; i < used; ++i) {
91         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
92             mod_rewrite_merge_config(&p->conf, p->cvlist+p->cvlist[i].v.u2[0]);
93     }
94 }
95 
96 static pcre_keyvalue_buffer * mod_rewrite_parse_list(server *srv, const array *a, pcre_keyvalue_buffer *kvb, const int condidx) {
97     const int pcre_jit =
98       !srv->srvconf.feature_flags
99       || config_plugin_value_tobool(
100           array_get_element_klen(srv->srvconf.feature_flags,
101                                  CONST_STR_LEN("server.pcre_jit")), 1);
102     int allocated = 0;
103     if (NULL == kvb) {
104         allocated = 1;
105         kvb = pcre_keyvalue_buffer_init();
106         kvb->x0 = (unsigned short)condidx;
107     }
108 
109     buffer * const tb = srv->tmp_buf;
110     for (uint32_t j = 0; j < a->used; ++j) {
111         data_string *ds = (data_string *)a->data[j];
112         if (srv->srvconf.http_url_normalize) {
113             pcre_keyvalue_burl_normalize_key(&ds->key, tb);
114             pcre_keyvalue_burl_normalize_value(&ds->value, tb);
115         }
116         if (!pcre_keyvalue_buffer_append(srv->errh, kvb, &ds->key, &ds->value,
117                                          pcre_jit)) {
118             log_error(srv->errh, __FILE__, __LINE__,
119               "pcre-compile failed for %s", ds->key.ptr);
120             if (allocated) pcre_keyvalue_buffer_free(kvb);
121             return NULL;
122         }
123     }
124 
125     return kvb;
126 }
127 
128 SETDEFAULTS_FUNC(mod_rewrite_set_defaults) {
129     static const config_plugin_keys_t cpk[] = {
130       { CONST_STR_LEN("url.rewrite-once"),
131         T_CONFIG_ARRAY_KVSTRING,
132         T_CONFIG_SCOPE_CONNECTION }
133      ,{ CONST_STR_LEN("url.rewrite-final"),  /* old name => url.rewrite-once */
134         T_CONFIG_ARRAY_KVSTRING,
135         T_CONFIG_SCOPE_CONNECTION }
136      ,{ CONST_STR_LEN("url.rewrite"),        /* old name => url.rewrite-once */
137         T_CONFIG_ARRAY_KVSTRING,
138         T_CONFIG_SCOPE_CONNECTION }
139      ,{ CONST_STR_LEN("url.rewrite-repeat"),
140         T_CONFIG_ARRAY_KVSTRING,
141         T_CONFIG_SCOPE_CONNECTION }
142      ,{ CONST_STR_LEN("url.rewrite-if-not-file"), /* rewrite-once if ENOENT */
143         T_CONFIG_ARRAY_KVSTRING,
144         T_CONFIG_SCOPE_CONNECTION }
145      ,{ CONST_STR_LEN("url.rewrite-repeat-if-not-file"), /* repeat if ENOENT */
146         T_CONFIG_ARRAY_KVSTRING,
147         T_CONFIG_SCOPE_CONNECTION }
148      ,{ NULL, 0,
149         T_CONFIG_UNSET,
150         T_CONFIG_SCOPE_UNSET }
151     };
152 
153     plugin_data * const p = p_d;
154     if (!config_plugin_values_init(srv, p, cpk, "mod_rewrite"))
155         return HANDLER_ERROR;
156 
157     /* process and validate config directives
158      * (init i to 0 if global context; to 1 to skip empty global context) */
159     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
160         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
161         /* parse directives in specific order to encode repeat_idx in kvb->x1 */
162         config_plugin_value_t *rewrite_once = NULL, *rewrite_repeat = NULL,
163                               *rewrite_NF = NULL,   *rewrite_repeat_NF = NULL,
164                               *rewrite = NULL,      *rewrite_final = NULL;
165         for (; -1 != cpv->k_id; ++cpv) {
166             switch (cpv->k_id) {
167               case 0: /* url.rewrite-once */
168                 rewrite_once = cpv;
169                 break;
170               case 1: /* url.rewrite-final */
171                 rewrite_final = cpv;
172                 break;
173               case 2: /* url.rewrite */
174                 rewrite = cpv;
175                 break;
176               case 3: /* url.rewrite-repeat */
177                 rewrite_repeat = cpv;
178                 break;
179               case 4: /* url.rewrite-if-not-file */
180                 rewrite_NF = cpv;
181                 break;
182               case 5: /* url.rewrite-repeat-if-not-file */
183                 rewrite_repeat_NF = cpv;
184                 break;
185               default:/* should not happen */
186                 break;
187             }
188         }
189 
190         const int condidx = p->cvlist[i].k_id;
191         pcre_keyvalue_buffer *kvb = NULL, *kvb_NF = NULL;
192 
193         if ((cpv = rewrite_once)) {
194             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb, condidx);
195             if (NULL == cpv->v.v) return HANDLER_ERROR;
196             cpv->vtype = T_CONFIG_LOCAL;
197             kvb = cpv->v.v;
198         }
199 
200         if ((cpv = rewrite_final)) {
201             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb, condidx);
202             if (NULL == cpv->v.v) return HANDLER_ERROR;
203             cpv->vtype = T_CONFIG_LOCAL;
204             kvb = cpv->v.v;
205         }
206 
207         if ((cpv = rewrite)) {
208             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb, condidx);
209             if (NULL == cpv->v.v) return HANDLER_ERROR;
210             cpv->vtype = T_CONFIG_LOCAL;
211             kvb = cpv->v.v;
212         }
213 
214         if (kvb) kvb->x1 = (unsigned short)kvb->used; /* repeat_idx */
215 
216         if ((cpv = rewrite_repeat)) {
217             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb, condidx);
218             if (NULL == cpv->v.v) return HANDLER_ERROR;
219             cpv->vtype = T_CONFIG_LOCAL;
220             /*kvb = cpv->v.v;*/
221         }
222 
223         if ((cpv = rewrite_NF)) {
224             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb_NF, condidx);
225             if (NULL == cpv->v.v) return HANDLER_ERROR;
226             cpv->vtype = T_CONFIG_LOCAL;
227             kvb_NF = cpv->v.v;
228         }
229 
230         if (kvb_NF) kvb_NF->x1 = (unsigned short)kvb_NF->used; /* repeat_idx */
231 
232         if ((cpv = rewrite_repeat_NF)) {
233             cpv->v.v = mod_rewrite_parse_list(srv, cpv->v.a, kvb_NF, condidx);
234             if (NULL == cpv->v.v) return HANDLER_ERROR;
235             cpv->vtype = T_CONFIG_LOCAL;
236             /*kvb_NF = cpv->v.v;*/
237         }
238     }
239 
240     /* initialize p->defaults from global config context */
241     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
242         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
243         if (-1 != cpv->k_id)
244             mod_rewrite_merge_config(&p->defaults, cpv);
245     }
246 
247     return HANDLER_GO_ON;
248 }
249 
250 URIHANDLER_FUNC(mod_rewrite_con_reset) {
251     r->plugin_ctx[((plugin_data *)p_d)->id] = NULL;
252     return HANDLER_GO_ON;
253 }
254 
255 static handler_t process_rewrite_rules(request_st * const r, plugin_data *p, const pcre_keyvalue_buffer *kvb) {
256 	struct burl_parts_t burl;
257 	pcre_keyvalue_ctx ctx;
258 	handler_t rc;
259 
260 	if (r->plugin_ctx[p->id]) {
261 		uintptr_t * const hctx = (uintptr_t *)(r->plugin_ctx + p->id);
262 
263 		if (((++*hctx) & 0x1FF) > 100) {
264 			if (0 != kvb->x0) {
265 				config_cond_info cfginfo;
266 				config_get_config_cond_info(&cfginfo, kvb->x0);
267 				log_error(r->conf.errh, __FILE__, __LINE__,
268 				  "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request, "
269 				  "perhaps you want to use url.rewrite-once instead of "
270 				  "url.rewrite-repeat (%s)", cfginfo.comp_key);
271 				return HANDLER_ERROR;
272 			}
273 
274 			log_error(r->conf.errh, __FILE__, __LINE__,
275 			  "ENDLESS LOOP IN rewrite-rule DETECTED ... aborting request");
276 			return HANDLER_ERROR;
277 		}
278 
279 		if (*hctx & REWRITE_STATE_FINISHED) return HANDLER_GO_ON;
280 	}
281 
282 	ctx.cache = NULL;
283 	if (kvb->x0) { /*(kvb->x0 is context_idx)*/
284 		ctx.cond_match_count =
285 		  r->cond_cache[kvb->x0].patterncount;
286 		ctx.cache = r->cond_match + kvb->x0;
287         }
288 	ctx.burl = &burl;
289 	burl.scheme    = &r->uri.scheme;
290 	burl.authority = &r->uri.authority;
291 	burl.port      = sock_addr_get_port(&r->con->srv_socket->addr);
292 	burl.path      = &r->target; /*(uri-encoded and includes query-part)*/
293 	burl.query     = &r->uri.query;
294 	if (buffer_string_is_empty(burl.authority))
295 		burl.authority = r->server_name;
296 
297 	buffer * const tb = r->tmp_buf;
298 	rc = pcre_keyvalue_buffer_process(kvb, &ctx, &r->target, tb);
299 	if (HANDLER_FINISHED == rc && !buffer_is_empty(tb) && tb->ptr[0] == '/') {
300 		buffer_copy_buffer(&r->target, tb);
301 		uintptr_t * const hctx = (uintptr_t *)(r->plugin_ctx + p->id);
302 		*hctx |= REWRITE_STATE_REWRITTEN;
303 		/*(kvb->x1 is repeat_idx)*/
304 		if (ctx.m < kvb->x1) *hctx |= REWRITE_STATE_FINISHED;
305 		buffer_reset(&r->physical.path);
306 		rc = HANDLER_COMEBACK;
307 	}
308 	else if (HANDLER_FINISHED == rc) {
309 		rc = HANDLER_ERROR;
310 		log_error(r->conf.errh, __FILE__, __LINE__,
311 		  "mod_rewrite invalid result (not beginning with '/') "
312 		  "while processing uri: %s", r->target.ptr);
313 	}
314 	else if (HANDLER_ERROR == rc) {
315 		log_error(r->conf.errh, __FILE__, __LINE__,
316 		  "pcre_exec() error "
317 		  "while processing uri: %s", r->target.ptr);
318 	}
319 	return rc;
320 }
321 
322 URIHANDLER_FUNC(mod_rewrite_physical) {
323     plugin_data * const p = p_d;
324 
325     if (NULL != r->handler_module) return HANDLER_GO_ON;
326 
327     mod_rewrite_patch_config(r, p);
328     if (!p->conf.rewrite_NF || !p->conf.rewrite_NF->used) return HANDLER_GO_ON;
329 
330     /* skip if physical.path is a regular file */
331     const stat_cache_st * const st = stat_cache_path_stat(&r->physical.path);
332     if (st && S_ISREG(st->st_mode)) return HANDLER_GO_ON;
333 
334     return process_rewrite_rules(r, p, p->conf.rewrite_NF);
335 }
336 
337 URIHANDLER_FUNC(mod_rewrite_uri_handler) {
338     plugin_data *p = p_d;
339 
340     mod_rewrite_patch_config(r, p);
341     if (!p->conf.rewrite || !p->conf.rewrite->used) return HANDLER_GO_ON;
342 
343     return process_rewrite_rules(r, p, p->conf.rewrite);
344 }
345 
346 int mod_rewrite_plugin_init(plugin *p);
347 int mod_rewrite_plugin_init(plugin *p) {
348 	p->version     = LIGHTTPD_VERSION_ID;
349 	p->name        = "rewrite";
350 
351 	p->init        = mod_rewrite_init;
352 	/* it has to stay _raw as we are matching on uri + querystring
353 	 */
354 
355 	p->handle_uri_raw = mod_rewrite_uri_handler;
356 	p->handle_physical = mod_rewrite_physical;
357 	p->cleanup     = mod_rewrite_free;
358 	p->handle_request_reset = mod_rewrite_con_reset;
359 	p->set_defaults = mod_rewrite_set_defaults;
360 
361 	return 0;
362 }
363