8 #include "stat_cache.h"
28 } rewrite_rule_buffer
;
31 rewrite_rule_buffer
*rewrite
;
32 rewrite_rule_buffer
*rewrite_NF
;
33 data_config
*context
, *context_NF
; /* to which apply me */
37 enum { REWRITE_STATE_UNSET
, REWRITE_STATE_FINISHED
} state
;
45 plugin_config
**config_storage
;
50 static handler_ctx
* handler_ctx_init(void) {
53 hctx
= calloc(1, sizeof(*hctx
));
55 hctx
->state
= REWRITE_STATE_UNSET
;
61 static void handler_ctx_free(handler_ctx
*hctx
) {
65 static rewrite_rule_buffer
*rewrite_rule_buffer_init(void) {
66 rewrite_rule_buffer
*kvb
;
68 kvb
= calloc(1, sizeof(*kvb
));
73 static int rewrite_rule_buffer_append(rewrite_rule_buffer
*kvb
, buffer
*key
, buffer
*value
, int once
) {
84 kvb
->ptr
= malloc(kvb
->size
* sizeof(*kvb
->ptr
));
86 for(i
= 0; i
< kvb
->size
; i
++) {
87 kvb
->ptr
[i
] = calloc(1, sizeof(**kvb
->ptr
));
89 } else if (kvb
->used
== kvb
->size
) {
92 kvb
->ptr
= realloc(kvb
->ptr
, kvb
->size
* sizeof(*kvb
->ptr
));
94 for(i
= kvb
->used
; i
< kvb
->size
; i
++) {
95 kvb
->ptr
[i
] = calloc(1, sizeof(**kvb
->ptr
));
99 if (NULL
== (kvb
->ptr
[kvb
->used
]->key
= pcre_compile(key
->ptr
,
100 0, &errptr
, &erroff
, NULL
))) {
105 kvb
->ptr
[kvb
->used
]->value
= buffer_init();
106 buffer_copy_buffer(kvb
->ptr
[kvb
->used
]->value
, value
);
107 kvb
->ptr
[kvb
->used
]->once
= once
;
114 static void rewrite_rule_buffer_free(rewrite_rule_buffer
*kvb
) {
117 for (i
= 0; i
< kvb
->size
; i
++) {
118 if (kvb
->ptr
[i
]->key
) pcre_free(kvb
->ptr
[i
]->key
);
119 if (kvb
->ptr
[i
]->value
) buffer_free(kvb
->ptr
[i
]->value
);
123 if (kvb
->ptr
) free(kvb
->ptr
);
129 INIT_FUNC(mod_rewrite_init
) {
132 p
= calloc(1, sizeof(*p
));
134 p
->match_buf
= buffer_init();
139 FREE_FUNC(mod_rewrite_free
) {
140 plugin_data
*p
= p_d
;
144 if (!p
) return HANDLER_GO_ON
;
146 buffer_free(p
->match_buf
);
147 if (p
->config_storage
) {
149 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
150 plugin_config
*s
= p
->config_storage
[i
];
152 if (NULL
== s
) continue;
154 rewrite_rule_buffer_free(s
->rewrite
);
155 rewrite_rule_buffer_free(s
->rewrite_NF
);
159 free(p
->config_storage
);
164 return HANDLER_GO_ON
;
167 static int parse_config_entry(server
*srv
, array
*ca
, rewrite_rule_buffer
*kvb
, const char *option
, size_t olen
, int once
) {
170 if (NULL
!= (du
= array_get_element_klen(ca
, option
, olen
))) {
174 da
= (data_array
*)du
;
176 if (du
->type
!= TYPE_ARRAY
|| !array_is_kvstring(da
->value
)) {
177 log_error_write(srv
, __FILE__
, __LINE__
, "SSS",
178 "unexpected value for ", option
, "; expected list of \"regex\" => \"subst\"");
179 return HANDLER_ERROR
;
182 for (j
= 0; j
< da
->value
->used
; j
++) {
183 if (0 != rewrite_rule_buffer_append(kvb
,
184 ((data_string
*)(da
->value
->data
[j
]))->key
,
185 ((data_string
*)(da
->value
->data
[j
]))->value
,
187 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
188 "pcre-compile failed for", da
->value
->data
[j
]->key
);
189 return HANDLER_ERROR
;
197 static int parse_config_entry(server
*srv
, array
*ca
, const char *option
, size_t olen
) {
198 static int logged_message
= 0;
199 if (logged_message
) return 0;
200 if (NULL
!= array_get_element_klen(ca
, option
, olen
)) {
202 log_error_write(srv
, __FILE__
, __LINE__
, "s",
203 "pcre support is missing, please install libpcre and the headers");
209 SETDEFAULTS_FUNC(mod_rewrite_set_defaults
) {
211 config_values_t cv
[] = {
212 { "url.rewrite-repeat", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
213 { "url.rewrite-once", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
215 /* these functions only rewrite if the target is not already in the filestore
217 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
218 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
221 { "url.rewrite-repeat-if-not-file", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
222 { "url.rewrite-if-not-file", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
224 /* old names, still supported
226 * url.rewrite remapped to url.rewrite-once
227 * url.rewrite-final is url.rewrite-once
230 { "url.rewrite", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
231 { "url.rewrite-final", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
232 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
236 plugin_data
*p
= p_d
;
238 if (!p
) return HANDLER_ERROR
;
241 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
246 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
247 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
251 s
= calloc(1, sizeof(plugin_config
));
252 s
->rewrite
= rewrite_rule_buffer_init();
253 s
->rewrite_NF
= rewrite_rule_buffer_init();
254 p
->config_storage
[i
] = s
;
257 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
258 return HANDLER_ERROR
;
262 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
264 parse_config_entry(srv
, config
->value
, s
->rewrite
, CONST_STR_LEN("url.rewrite-once"), 1);
265 parse_config_entry(srv
, config
->value
, s
->rewrite
, CONST_STR_LEN("url.rewrite-final"), 1);
266 parse_config_entry(srv
, config
->value
, s
->rewrite_NF
, CONST_STR_LEN("url.rewrite-if-not-file"), 1);
267 parse_config_entry(srv
, config
->value
, s
->rewrite_NF
, CONST_STR_LEN("url.rewrite-repeat-if-not-file"), 0);
268 parse_config_entry(srv
, config
->value
, s
->rewrite
, CONST_STR_LEN("url.rewrite"), 1);
269 parse_config_entry(srv
, config
->value
, s
->rewrite
, CONST_STR_LEN("url.rewrite-repeat"), 0);
272 return HANDLER_GO_ON
;
279 static int mod_rewrite_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
281 plugin_config
*s
= p
->config_storage
[0];
285 p
->conf
.context
= NULL
;
286 p
->conf
.context_NF
= NULL
;
288 /* skip the first, the global context */
289 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
290 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
291 s
= p
->config_storage
[i
];
293 /* condition didn't match */
294 if (!config_check_cond(srv
, con
, dc
)) continue;
297 for (j
= 0; j
< dc
->value
->used
; j
++) {
298 data_unset
*du
= dc
->value
->data
[j
];
300 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite"))) {
302 p
->conf
.context
= dc
;
303 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-once"))) {
305 p
->conf
.context
= dc
;
306 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-repeat"))) {
308 p
->conf
.context
= dc
;
309 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-if-not-file"))) {
311 p
->conf
.context_NF
= dc
;
312 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
314 p
->conf
.context_NF
= dc
;
315 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-final"))) {
317 p
->conf
.context
= dc
;
325 URIHANDLER_FUNC(mod_rewrite_con_reset
) {
326 plugin_data
*p
= p_d
;
330 if (con
->plugin_ctx
[p
->id
]) {
331 handler_ctx_free(con
->plugin_ctx
[p
->id
]);
332 con
->plugin_ctx
[p
->id
] = NULL
;
335 return HANDLER_GO_ON
;
338 static handler_t
process_rewrite_rules(server
*srv
, connection
*con
, plugin_data
*p
, rewrite_rule_buffer
*kvb
) {
342 if (con
->plugin_ctx
[p
->id
]) {
343 hctx
= con
->plugin_ctx
[p
->id
];
345 if (hctx
->loops
++ > 100) {
346 data_config
*dc
= p
->conf
.context
;
347 log_error_write(srv
, __FILE__
, __LINE__
, "SbbSBS",
348 "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
, "\")");
350 return HANDLER_ERROR
;
353 if (hctx
->state
== REWRITE_STATE_FINISHED
) return HANDLER_GO_ON
;
356 buffer_copy_buffer(p
->match_buf
, con
->request
.uri
);
358 for (i
= 0; i
< kvb
->used
; i
++) {
363 rewrite_rule
*rule
= kvb
->ptr
[i
];
368 pattern
= rule
->value
->ptr
;
369 pattern_len
= buffer_string_length(rule
->value
);
371 if ((n
= pcre_exec(match
, NULL
, CONST_BUF_LEN(p
->match_buf
), 0, 0, ovec
, 3 * N
)) < 0) {
372 if (n
!= PCRE_ERROR_NOMATCH
) {
373 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
374 "execution error while matching: ", n
);
375 return HANDLER_ERROR
;
377 } else if (0 == pattern_len
) {
378 /* short-circuit if blank replacement pattern
379 * (do not attempt to match against remaining rewrite rules) */
380 return HANDLER_GO_ON
;
387 pcre_get_substring_list(p
->match_buf
->ptr
, ovec
, n
, &list
);
389 /* search for $[0-9] */
391 buffer_reset(con
->request
.uri
);
394 for (k
= 0; k
+1 < pattern_len
; k
++) {
395 if (pattern
[k
] == '$' || pattern
[k
] == '%') {
398 size_t num
= pattern
[k
+ 1] - '0';
400 buffer_append_string_len(con
->request
.uri
, pattern
+ start
, k
- start
);
402 if (!isdigit((unsigned char)pattern
[k
+ 1])) {
403 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
404 buffer_append_string_len(con
->request
.uri
, pattern
+k
, pattern
[k
] == pattern
[k
+1] ? 1 : 2);
405 } else if (pattern
[k
] == '$') {
406 /* n is always > 0 */
407 if (num
< (size_t)n
) {
408 buffer_append_string(con
->request
.uri
, list
[num
]);
410 } else if (p
->conf
.context
== NULL
) {
411 /* we have no context, we are global */
412 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
413 "used a redirect containing a %[0-9]+ in the global scope, ignored:",
417 config_append_cond_match_buffer(con
, p
->conf
.context
, con
->request
.uri
, num
);
425 buffer_append_string_len(con
->request
.uri
, pattern
+ start
, pattern_len
- start
);
429 if (con
->plugin_ctx
[p
->id
] == NULL
) {
430 hctx
= handler_ctx_init();
431 con
->plugin_ctx
[p
->id
] = hctx
;
433 hctx
= con
->plugin_ctx
[p
->id
];
436 if (rule
->once
) hctx
->state
= REWRITE_STATE_FINISHED
;
438 return HANDLER_COMEBACK
;
443 return HANDLER_GO_ON
;
446 URIHANDLER_FUNC(mod_rewrite_physical
) {
447 plugin_data
*p
= p_d
;
449 stat_cache_entry
*sce
;
451 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
453 mod_rewrite_patch_connection(srv
, con
, p
);
454 p
->conf
.context
= p
->conf
.context_NF
;
456 if (!p
->conf
.rewrite_NF
) return HANDLER_GO_ON
;
458 /* skip if physical.path is a regular file */
460 if (HANDLER_ERROR
!= stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
461 if (S_ISREG(sce
->st
.st_mode
)) return HANDLER_GO_ON
;
464 switch(r
= process_rewrite_rules(srv
, con
, p
, p
->conf
.rewrite_NF
)) {
465 case HANDLER_COMEBACK
:
466 buffer_reset(con
->physical
.path
);
472 return HANDLER_GO_ON
;
475 URIHANDLER_FUNC(mod_rewrite_uri_handler
) {
476 plugin_data
*p
= p_d
;
478 mod_rewrite_patch_connection(srv
, con
, p
);
480 if (!p
->conf
.rewrite
) return HANDLER_GO_ON
;
482 return process_rewrite_rules(srv
, con
, p
, p
->conf
.rewrite
);
486 int mod_rewrite_plugin_init(plugin
*p
);
487 int mod_rewrite_plugin_init(plugin
*p
) {
488 p
->version
= LIGHTTPD_VERSION_ID
;
489 p
->name
= buffer_init_string("rewrite");
492 p
->init
= mod_rewrite_init
;
493 /* it has to stay _raw as we are matching on uri + querystring
496 p
->handle_uri_raw
= mod_rewrite_uri_handler
;
497 p
->handle_physical
= mod_rewrite_physical
;
498 p
->cleanup
= mod_rewrite_free
;
499 p
->connection_reset
= mod_rewrite_con_reset
;
501 p
->set_defaults
= mod_rewrite_set_defaults
;