- added ? and * as placeholder chars for DomainTreeAlias
[m6w6/mod-domaintree] / mod_domaintree.c
1 /*
2 * mod_domaintree.c - Apache2
3 *
4 * $Id$
5 *
6 * Copyright 2005 Michael Wallner <mike@iworks.at
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 *
21 * <pre>
22 * DomainTreeEnabled On
23 * DomainTreeMaxdepth 25
24 * DomainTreeStripWWW On
25 * DomainTreePrefix /sites
26 * DomainTreeSuffix /html
27 * DomainTreeAliasRecursion Off
28 * DomainTreeAlias /??/exmaple /com/exmaple
29 * DomainTreeAlias /???/example /com/example
30 * DomainTreeAlais /*one/ /anyone/
31 *
32 * /sites
33 * +- /at
34 * | +- /co
35 * | | +- /company
36 * | | +- /html
37 * | | +- /sub1
38 * | | | +- /html
39 * | | +- /sub2
40 * | | +- /html
41 * | +- /or
42 * | +- /organisation
43 * | +- /html
44 * +- /com
45 * +- /example
46 * +- /html
47 * </pre>
48 */
49
50 #define MODULE "mod_domaintree"
51 #define AUTHOR "mike@php.net"
52 #define VERSION "1.2"
53
54 /* {{{ Includes */
55
56 #include "httpd.h"
57 #include "http_config.h"
58
59 #define CORE_PRIVATE
60 #include "http_core.h"
61 #undef CORE_PRIVATE
62
63 #include "http_log.h"
64 #include "http_protocol.h"
65 #include "http_request.h"
66
67 #include "apr.h"
68 #include "apr_lib.h"
69 #include "apr_ring.h"
70 #include "apr_hash.h"
71 #include "apr_strings.h"
72
73 #define APR_WANT_STRFUNC
74 #include "apr_want.h"
75
76 /* }}} */
77 /* {{{ domaintree_module */
78
79 module AP_MODULE_DECLARE_DATA domaintree_module;
80
81 /* }}} */
82 /* {{{ Macros & Types */
83
84 #define MOD_DT_CNF domaintree_conf
85 #define MOD_DT_PTR (&domaintree_module)
86
87 #define NUL '\0'
88 #define EMPTY(str) ((str == NULL) || (*(str) == NUL))
89
90 #define DT_LOG_ERR ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, DT->server,
91 #define DT_LOG_WRN ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, DT->server,
92 #define DT_LOG_DBG ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server,
93 #define DT_LOG_END );
94
95 typedef int STATUS;
96
97 typedef struct {
98 apr_hash_t *hashtable;
99 size_t recursion;
100 } aliases_t;
101
102 typedef struct {
103 server_rec *server;
104 int enabled;
105 int stripwww;
106 size_t maxdepth;
107 char *prefix;
108 char *suffix;
109 aliases_t aliases;
110 } domaintree_conf;
111
112 struct domaintree_entry {
113 char *name;
114 APR_RING_ENTRY(domaintree_entry) link;
115 };
116 APR_RING_HEAD(domaintree, domaintree_entry);
117
118 /* }}} */
119 /* {{{ Helpers */
120
121 static APR_INLINE char *strtr(char *string, char from, char to)
122 {
123 char *ptr = string;
124
125 if (from != to) {
126 while ((ptr = strchr(ptr, from))) {
127 *ptr = to;
128 }
129 }
130 return string;
131 }
132
133 static APR_INLINE char *trim(char *string, size_t length, char what, int l, int r)
134 {
135 if (r) {
136 while (length-- && (string[length] == what)) {
137 string[length] = NUL;
138 }
139 }
140 if (l) {
141 while (*string == what) {
142 ++string;
143 }
144 }
145 return string;
146 }
147
148 static APR_INLINE char *strcase(char *string, int case_type)
149 {
150 #ifndef CASE_LOWER
151 # define CASE_LOWER 1
152 #endif
153 #ifndef CASE_UPPER
154 # define CASE_UPPER 2
155 #endif
156
157 char *ptr = string;
158
159 switch (case_type)
160 {
161 case CASE_LOWER:
162 while (*ptr) {
163 apr_tolower(*ptr++);
164 }
165 break;
166
167 case CASE_UPPER:
168 while (*ptr) {
169 apr_toupper(*ptr++);
170 }
171 break;
172
173 default:
174 break;
175 }
176 return string;
177 }
178
179 static APR_INLINE int strmatch(char *match, char *string, char **begin, char **end)
180 {
181 *begin = *end = NULL;
182
183 while (*match)
184 {
185 switch (*match)
186 {
187 case '*':
188 while (*match == '*' || *match == '?') {
189 ++match;
190 }
191
192 if (!*begin) {
193 *begin = string;
194 }
195
196 if (!*match) {
197 *end = string + strlen(string);
198 return 1;
199 }
200
201 if (!(string = strchr(string, *match))) {
202 *end = string;
203 return 0;
204 }
205 break;
206
207 case '?':
208 if (!*begin) {
209 *begin = string;
210 }
211 ++string;
212 ++match;
213 break;
214
215 default:
216 if (*match == *string) {
217 if (!*begin) {
218 *begin = string;
219 }
220 ++match;
221 } else {
222 if (*begin) {
223 *end = string - 1;
224 return 0;
225 }
226 }
227 ++string;
228 break;
229 }
230 }
231
232 *end = string;
233 return 1;
234 }
235
236 static APR_INLINE char *struniqchr(char *string, char uniq)
237 {
238 char *ptr = string;
239
240 while (*ptr) {
241 if (*ptr == uniq && *(ptr + 1) == uniq) {
242 char *pos = ptr + 1;
243
244 while (*(pos + 1) == uniq) {
245 ++pos;
246 }
247
248 memmove(ptr, pos, strlen(pos) + 1);
249 }
250 ++ptr;
251 }
252
253 return string;
254 }
255
256 static APR_INLINE char *domaintree_host(apr_pool_t *pool, MOD_DT_CNF *DT, const char *name)
257 {
258 if (EMPTY(name)) {
259 DT_LOG_WRN
260 "DomainTree: no host/server name"
261 DT_LOG_END
262 return NULL;
263 } else {
264 size_t len;
265 char *port, *ptr, *host;
266
267 ptr = host = apr_pstrdup(pool, name);
268
269 DT_LOG_DBG
270 "DomainTree: host name = %s", host
271 DT_LOG_END
272
273 /* check for :NN port */
274 if ((port = strchr(ptr, ':'))) {
275 len = port - ptr;
276 } else {
277 len = strlen(ptr);
278 }
279
280 /* strip leading & trailing dots, then lowercase */
281 ptr = host = strcase(trim(ptr, len, '.', 1, 1), CASE_LOWER);
282
283 DT_LOG_DBG
284 "DomainTree: sane host = %s", host
285 DT_LOG_END
286
287 return host;
288 }
289 }
290
291 static APR_INLINE const char *domaintree_elem(apr_pool_t *pool, struct domaintree *tree, const char *name, size_t length)
292 {
293 struct domaintree_entry *elem = apr_palloc(pool, sizeof(struct domaintree_entry));
294
295 APR_RING_ELEM_INIT(elem, link);
296 APR_RING_INSERT_HEAD(tree, elem, domaintree_entry, link);
297
298 return elem->name = apr_pstrndup(pool, name, length);
299 }
300
301 static APR_INLINE struct domaintree *domaintree_tree(apr_pool_t *pool, MOD_DT_CNF *DT, char *host)
302 {
303 size_t depth = 0;
304 char *host_ptr = host;
305 struct domaintree *tree = apr_palloc(pool, sizeof(struct domaintree));
306
307 APR_RING_INIT(tree, domaintree_entry, link);
308
309 while ((host_ptr = strchr(host, '.'))) {
310
311 /* check max depth */
312 if (++depth > DT->maxdepth) {
313 DT_LOG_ERR
314 "DomainTree: maxdepth exceeded = %s", host
315 DT_LOG_END
316 return NULL;
317 }
318
319 /* append part */
320 if (host_ptr - host) {
321
322 /* strip WWW */
323 if (DT->stripwww && (depth == 1) && (!strncmp(host, "www.", sizeof("www")))) {
324 DT_LOG_DBG
325 "DomainTree: stripping www."
326 DT_LOG_END
327 } else {
328 DT_LOG_DBG
329 "DomainTree: host part (%d) = %s", depth - 1,
330 domaintree_elem(pool, tree, host, host_ptr - host)
331 DT_LOG_END
332 }
333 }
334
335 host = host_ptr + 1;
336 }
337
338 /* append last part */
339 if (!EMPTY(host)) {
340 DT_LOG_DBG
341 "DomainTree: host part (%d) = %s", depth,
342 domaintree_elem(pool, tree, host, strlen(host))
343 DT_LOG_END
344 }
345
346 return tree;
347 }
348
349 static APR_INLINE char *domaintree_path(apr_pool_t *pool, MOD_DT_CNF *DT, struct domaintree *tree)
350 {
351 struct domaintree_entry *elem;
352 char *path = "";
353
354 APR_RING_FOREACH(elem, tree, domaintree_entry, link) {
355 path = apr_pstrcat(pool, path, "/", elem->name, NULL);
356 }
357
358 return path;
359 }
360
361 static APR_INLINE void domaintree_fake(apr_pool_t *pool, MOD_DT_CNF *DT, char **path)
362 {
363 int more;
364 apr_hash_index_t *idx;
365 size_t recurlevel = 0;
366
367 do {
368 more = 0;
369
370 if (recurlevel++ > DT->aliases.recursion) {
371 DT_LOG_ERR
372 "DomainTree: maximum alias recursion level (%d) exceeded! "
373 "Check if you have recursive definitions of DomainTreeAlias directives.",
374 DT->aliases.recursion
375 DT_LOG_END
376 break;
377 }
378
379 for (idx = apr_hash_first(pool, DT->aliases.hashtable); idx; idx = apr_hash_next(idx)) {
380 char *fake, *real, *begin, *end;
381
382 apr_hash_this(idx, (const void **) &fake, NULL, (void **) &real);
383
384 DT_LOG_DBG
385 "DomainTree: fake test %s on %s", fake, *path
386 DT_LOG_END
387
388 if (strmatch(fake, *path, &begin, &end)) {
389 *path = apr_pstrcat(pool, "/", apr_pstrndup(pool, *path, begin - *path), "/", real, "/", end, NULL);
390 struniqchr(*path, '/');
391
392 DT_LOG_DBG
393 "DomainTree: fake done %s<>%s = %s", fake, real, *path
394 DT_LOG_END
395
396 more = 1;
397 }
398 }
399 } while (more && DT->aliases.recursion);
400 }
401
402 /* }}} */
403 /* {{{ Hooks */
404
405 static STATUS domaintree_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
406 {
407 ap_add_version_component(pconf, MODULE "/" VERSION);
408 return OK;
409 }
410
411 static STATUS domaintree_hook_translate_name(request_rec *r)
412 {
413 MOD_DT_CNF *DT = NULL;
414 struct domaintree *tree;
415 char *path, *host;
416
417 DT = ap_get_module_config(r->server->module_config, MOD_DT_PTR);
418 if ((!DT) || (!DT->enabled)) {
419 return DECLINED;
420 }
421
422 /* get a usable host name */
423 if (!(host = domaintree_host(r->pool, DT, ap_get_server_name(r)))) {
424 return DECLINED;
425 }
426
427 /* build domain tree */
428 if (!(tree = domaintree_tree(r->pool, DT, host))) {
429 return DECLINED;
430 }
431
432 /* build path */
433 if (!(path = domaintree_path(r->pool, DT, tree))) {
434 return DECLINED;
435 }
436
437 /* apply any aliases */
438 domaintree_fake(r->pool, DT, &path);
439
440 /* done */
441 r->canonical_filename = "";
442 r->filename = apr_pstrcat(r->pool, DT->prefix, "/", path, "/", DT->suffix, r->uri, NULL);
443 struniqchr(r->filename, '/');
444
445 DT_LOG_DBG
446 "DomainTree: path done = %s", r->filename
447 DT_LOG_END
448
449 return OK;
450 }
451
452 static void domaintree_hooks(apr_pool_t *pool)
453 {
454 ap_hook_post_config(domaintree_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
455 ap_hook_translate_name(domaintree_hook_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
456 }
457
458 /* }}} */
459 /* {{{ Configuration */
460
461 static void *domaintree_create_srv(apr_pool_t *p, server_rec *s)
462 {
463 MOD_DT_CNF *DT;
464
465 DT = (MOD_DT_CNF *) apr_palloc(p, sizeof(MOD_DT_CNF));
466
467 DT->server = s;
468 DT->enabled = 0;
469 DT->stripwww = 1;
470 DT->maxdepth = 20;
471
472 DT->prefix = "/var/www";
473 DT->suffix = "public_html";
474
475 DT->aliases.hashtable = apr_hash_make(p);
476 DT->aliases.recursion = 0;
477
478 return DT;
479 }
480
481 static const char *domaintree_enable(cmd_parms *cmd, void *conf, int flag)
482 {
483 MOD_DT_CNF *DT;
484
485 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
486 DT->enabled = flag;
487
488 return NULL;
489 }
490
491 static const char *domaintree_stripwww(cmd_parms *cmd, void *conf, int flag)
492 {
493 MOD_DT_CNF *DT;
494
495 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
496 DT->stripwww = flag;
497
498 return NULL;
499 }
500
501 static const char *domaintree_prefix(cmd_parms *cmd, void *conf, const char *prefix)
502 {
503 MOD_DT_CNF *DT;
504
505 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
506 DT->prefix = EMPTY(prefix) ? "/" : trim(apr_pstrdup(cmd->pool, prefix), strlen(prefix), '/', 0, 1);
507
508 return NULL;
509 }
510
511 static const char *domaintree_suffix(cmd_parms *cmd, void *conf, const char *suffix)
512 {
513 MOD_DT_CNF *DT;
514
515 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
516 DT->suffix = EMPTY(suffix) ? "" : trim(apr_pstrdup(cmd->pool, suffix), strlen(suffix), '/', 1, 1);
517
518 return NULL;
519 }
520
521 static const char *domaintree_maxdepth(cmd_parms *cmd, void *conf, const char *max_depth)
522 {
523 long depth;
524
525 if ((depth = atol(max_depth))) {
526 if (depth > 0L) {
527 MOD_DT_CNF *DT;
528 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
529 DT->maxdepth = (size_t) depth;
530 } else {
531 return "Maximum DomainTree depth cannot be negative.";
532 }
533 }
534
535 return NULL;
536 }
537
538 static const char *domaintree_aliasrecursion(cmd_parms *cmd, void *conf, const char *alias_recursion)
539 {
540 long recursion;
541
542 if ((recursion = atol(alias_recursion))) {
543 if (recursion > 0L) {
544 MOD_DT_CNF *DT;
545 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
546 DT->aliases.recursion = (size_t) recursion;
547 } else {
548 return "DomainTree alias recursion cannot be negative.";
549 }
550 }
551
552 return NULL;
553 }
554
555 static const char *domaintree_alias(cmd_parms *cmd, void *conf, const char *fake, const char *real)
556 {
557 MOD_DT_CNF *DT;
558 char *f = strtr(apr_pstrdup(cmd->pool, fake), '.', '/'), *r = strtr(apr_pstrdup(cmd->pool, real), '.', '/');
559
560 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
561 apr_hash_set(DT->aliases.hashtable, trim(f, strlen(f), '/', 1, 1), APR_HASH_KEY_STRING, trim(r, strlen(r), '/', 1, 1));
562
563 return NULL;
564 }
565
566
567 /* }}} */
568 /* {{{ Commands */
569
570 static command_rec domaintree_commands[] = {
571 AP_INIT_FLAG(
572 "DomainTreeEnabled", domaintree_enable, NULL, RSRC_CONF,
573 "Turn the module on or off."
574 ),
575
576 AP_INIT_FLAG(
577 "DomainTreeStripWWW", domaintree_stripwww, NULL, RSRC_CONF,
578 "Strip leading www from host. (default On)"
579 ),
580
581 AP_INIT_TAKE1(
582 "DomainTreePrefix", domaintree_prefix, NULL, RSRC_CONF,
583 "DomainTree path prefix. (default /var/www) Do not forget the leading slash!"
584 ),
585
586 AP_INIT_TAKE1(
587 "DomainTreeSuffix", domaintree_suffix, NULL, RSRC_CONF,
588 "DomainTree path suffix. (default public_html)"
589 ),
590
591 AP_INIT_TAKE1(
592 "DomainTreeMaxdepth", domaintree_maxdepth, NULL, RSRC_CONF,
593 "DomainTree max path depth. (default 20)"
594 ),
595
596 AP_INIT_TAKE1(
597 "DomainTreeAliasRecursion", domaintree_aliasrecursion, NULL, RSRC_CONF,
598 "Whether (and how often at the maximum) DomainTree should walk recursively "
599 "through the aliases list as long as matching aliases are found. (Default: 0 = turned off)"
600 ),
601
602 AP_INIT_TAKE2(
603 "DomainTreeAlias", domaintree_alias, NULL, RSRC_CONF,
604 "DomainTree aliases; e.g. DomainTreeAlias com/example/tickets com/example/support (dots or slashes equal)"
605 ),
606
607 { NULL }
608 };
609
610 /* }}} */
611 /* {{{ Module Administrativa */
612
613 module AP_MODULE_DECLARE_DATA domaintree_module = {
614 STANDARD20_MODULE_STUFF,
615 NULL, /* create per-dir */
616 NULL, /* merge per-dir */
617 domaintree_create_srv, /* create per-server */
618 NULL, /* merge per-server */
619 domaintree_commands, /* apr_table_t commands */
620 domaintree_hooks /* hooks */
621 };
622
623 /* }}} */
624
625 /*
626 * Local variables:
627 * tab-width: 4
628 * c-basic-offset: 4
629 * End:
630 * vim600: noet sw=4 ts=4 fdm=marker
631 * vim<600: noet sw=4 ts=4
632 */