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
, int once
) {
170 if (NULL
!= (du
= array_get_element(ca
, option
))) {
174 if (du
->type
!= TYPE_ARRAY
) {
175 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
176 "unexpected type for key: ", option
, "array of strings");
178 return HANDLER_ERROR
;
181 da
= (data_array
*)du
;
183 for (j
= 0; j
< da
->value
->used
; j
++) {
184 if (da
->value
->data
[j
]->type
!= TYPE_STRING
) {
185 log_error_write(srv
, __FILE__
, __LINE__
, "sssbs",
186 "unexpected type for key: ",
188 "[", da
->value
->data
[j
]->key
, "](string)");
190 return HANDLER_ERROR
;
193 if (0 != rewrite_rule_buffer_append(kvb
,
194 ((data_string
*)(da
->value
->data
[j
]))->key
,
195 ((data_string
*)(da
->value
->data
[j
]))->value
,
197 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
198 "pcre-compile failed for", da
->value
->data
[j
]->key
);
199 return HANDLER_ERROR
;
207 static int parse_config_entry(server
*srv
, array
*ca
, const char *option
) {
208 static int logged_message
= 0;
209 if (logged_message
) return 0;
210 if (NULL
!= array_get_element(ca
, option
)) {
212 log_error_write(srv
, __FILE__
, __LINE__
, "s",
213 "pcre support is missing, please install libpcre and the headers");
219 SETDEFAULTS_FUNC(mod_rewrite_set_defaults
) {
221 config_values_t cv
[] = {
222 { "url.rewrite-repeat", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
223 { "url.rewrite-once", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
225 /* these functions only rewrite if the target is not already in the filestore
227 * url.rewrite-repeat-if-not-file is the equivalent of url.rewrite-repeat
228 * url.rewrite-if-not-file is the equivalent of url.rewrite-once
231 { "url.rewrite-repeat-if-not-file", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
232 { "url.rewrite-if-not-file", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
234 /* old names, still supported
236 * url.rewrite remapped to url.rewrite-once
237 * url.rewrite-final is url.rewrite-once
240 { "url.rewrite", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
241 { "url.rewrite-final", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
}, /* 5 */
242 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
246 plugin_data
*p
= p_d
;
248 if (!p
) return HANDLER_ERROR
;
251 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
256 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
257 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
261 s
= calloc(1, sizeof(plugin_config
));
262 s
->rewrite
= rewrite_rule_buffer_init();
263 s
->rewrite_NF
= rewrite_rule_buffer_init();
264 p
->config_storage
[i
] = s
;
267 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
268 return HANDLER_ERROR
;
272 # define parse_config_entry(srv, ca, x, option, y) parse_config_entry(srv, ca, option)
274 parse_config_entry(srv
, config
->value
, s
->rewrite
, "url.rewrite-once", 1);
275 parse_config_entry(srv
, config
->value
, s
->rewrite
, "url.rewrite-final", 1);
276 parse_config_entry(srv
, config
->value
, s
->rewrite_NF
, "url.rewrite-if-not-file", 1);
277 parse_config_entry(srv
, config
->value
, s
->rewrite_NF
, "url.rewrite-repeat-if-not-file", 0);
278 parse_config_entry(srv
, config
->value
, s
->rewrite
, "url.rewrite", 1);
279 parse_config_entry(srv
, config
->value
, s
->rewrite
, "url.rewrite-repeat", 0);
282 return HANDLER_GO_ON
;
289 static int mod_rewrite_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
291 plugin_config
*s
= p
->config_storage
[0];
295 p
->conf
.context
= NULL
;
296 p
->conf
.context_NF
= NULL
;
298 /* skip the first, the global context */
299 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
300 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
301 s
= p
->config_storage
[i
];
303 /* condition didn't match */
304 if (!config_check_cond(srv
, con
, dc
)) continue;
307 for (j
= 0; j
< dc
->value
->used
; j
++) {
308 data_unset
*du
= dc
->value
->data
[j
];
310 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite"))) {
312 p
->conf
.context
= dc
;
313 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-once"))) {
315 p
->conf
.context
= dc
;
316 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-repeat"))) {
318 p
->conf
.context
= dc
;
319 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-if-not-file"))) {
321 p
->conf
.context_NF
= dc
;
322 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-repeat-if-not-file"))) {
324 p
->conf
.context_NF
= dc
;
325 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("url.rewrite-final"))) {
327 p
->conf
.context
= dc
;
335 URIHANDLER_FUNC(mod_rewrite_con_reset
) {
336 plugin_data
*p
= p_d
;
340 if (con
->plugin_ctx
[p
->id
]) {
341 handler_ctx_free(con
->plugin_ctx
[p
->id
]);
342 con
->plugin_ctx
[p
->id
] = NULL
;
345 return HANDLER_GO_ON
;
348 static handler_t
process_rewrite_rules(server
*srv
, connection
*con
, plugin_data
*p
, rewrite_rule_buffer
*kvb
) {
352 if (con
->plugin_ctx
[p
->id
]) {
353 hctx
= con
->plugin_ctx
[p
->id
];
355 if (hctx
->loops
++ > 100) {
356 data_config
*dc
= p
->conf
.context
;
357 log_error_write(srv
, __FILE__
, __LINE__
, "SbbSBS",
358 "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
, "\")");
360 return HANDLER_ERROR
;
363 if (hctx
->state
== REWRITE_STATE_FINISHED
) return HANDLER_GO_ON
;
366 buffer_copy_buffer(p
->match_buf
, con
->request
.uri
);
368 for (i
= 0; i
< kvb
->used
; i
++) {
373 rewrite_rule
*rule
= kvb
->ptr
[i
];
378 pattern
= rule
->value
->ptr
;
379 pattern_len
= buffer_string_length(rule
->value
);
381 if ((n
= pcre_exec(match
, NULL
, CONST_BUF_LEN(p
->match_buf
), 0, 0, ovec
, 3 * N
)) < 0) {
382 if (n
!= PCRE_ERROR_NOMATCH
) {
383 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
384 "execution error while matching: ", n
);
385 return HANDLER_ERROR
;
387 } else if (0 == pattern_len
) {
388 /* short-circuit if blank replacement pattern
389 * (do not attempt to match against remaining rewrite rules) */
390 return HANDLER_GO_ON
;
397 pcre_get_substring_list(p
->match_buf
->ptr
, ovec
, n
, &list
);
399 /* search for $[0-9] */
401 buffer_reset(con
->request
.uri
);
404 for (k
= 0; k
+1 < pattern_len
; k
++) {
405 if (pattern
[k
] == '$' || pattern
[k
] == '%') {
408 size_t num
= pattern
[k
+ 1] - '0';
410 buffer_append_string_len(con
->request
.uri
, pattern
+ start
, k
- start
);
412 if (!isdigit((unsigned char)pattern
[k
+ 1])) {
413 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */
414 buffer_append_string_len(con
->request
.uri
, pattern
+k
, pattern
[k
] == pattern
[k
+1] ? 1 : 2);
415 } else if (pattern
[k
] == '$') {
416 /* n is always > 0 */
417 if (num
< (size_t)n
) {
418 buffer_append_string(con
->request
.uri
, list
[num
]);
420 } else if (p
->conf
.context
== NULL
) {
421 /* we have no context, we are global */
422 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
423 "used a redirect containing a %[0-9]+ in the global scope, ignored:",
427 config_append_cond_match_buffer(con
, p
->conf
.context
, con
->request
.uri
, num
);
435 buffer_append_string_len(con
->request
.uri
, pattern
+ start
, pattern_len
- start
);
439 if (con
->plugin_ctx
[p
->id
] == NULL
) {
440 hctx
= handler_ctx_init();
441 con
->plugin_ctx
[p
->id
] = hctx
;
443 hctx
= con
->plugin_ctx
[p
->id
];
446 if (rule
->once
) hctx
->state
= REWRITE_STATE_FINISHED
;
448 return HANDLER_COMEBACK
;
453 return HANDLER_GO_ON
;
456 URIHANDLER_FUNC(mod_rewrite_physical
) {
457 plugin_data
*p
= p_d
;
459 stat_cache_entry
*sce
;
461 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
463 mod_rewrite_patch_connection(srv
, con
, p
);
464 p
->conf
.context
= p
->conf
.context_NF
;
466 if (!p
->conf
.rewrite_NF
) return HANDLER_GO_ON
;
468 /* skip if physical.path is a regular file */
470 if (HANDLER_ERROR
!= stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
471 if (S_ISREG(sce
->st
.st_mode
)) return HANDLER_GO_ON
;
474 switch(r
= process_rewrite_rules(srv
, con
, p
, p
->conf
.rewrite_NF
)) {
475 case HANDLER_COMEBACK
:
476 buffer_reset(con
->physical
.path
);
481 return HANDLER_GO_ON
;
484 URIHANDLER_FUNC(mod_rewrite_uri_handler
) {
485 plugin_data
*p
= p_d
;
487 mod_rewrite_patch_connection(srv
, con
, p
);
489 if (!p
->conf
.rewrite
) return HANDLER_GO_ON
;
491 return process_rewrite_rules(srv
, con
, p
, p
->conf
.rewrite
);
495 int mod_rewrite_plugin_init(plugin
*p
);
496 int mod_rewrite_plugin_init(plugin
*p
) {
497 p
->version
= LIGHTTPD_VERSION_ID
;
498 p
->name
= buffer_init_string("rewrite");
501 p
->init
= mod_rewrite_init
;
502 /* it has to stay _raw as we are matching on uri + querystring
505 p
->handle_uri_raw
= mod_rewrite_uri_handler
;
506 p
->handle_physical
= mod_rewrite_physical
;
507 p
->cleanup
= mod_rewrite_free
;
508 p
->connection_reset
= mod_rewrite_con_reset
;
510 p
->set_defaults
= mod_rewrite_set_defaults
;