- added DomainTree aliases
[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 *
28 * /sites
29 * +- /at
30 * | +- /co
31 * | | +- /company
32 * | | +- /html
33 * | | +- /sub1
34 * | | | +- /html
35 * | | +- /sub2
36 * | | +- /html
37 * | +- /or
38 * | | +- /organisation
39 * | | +- /html
40 * | +- /example
41 * | +- /html
42 * +- /com
43 * +- /example
44 * +- /html
45 * </pre>
46 */
47
48 #define MODULE "mod_domaintree"
49 #define AUTHOR "mike@php.net"
50 #define VERSION "1.1"
51
52 /* {{{ Includes */
53
54 #include "httpd.h"
55 #include "http_config.h"
56
57 #define CORE_PRIVATE
58 #include "http_core.h"
59 #undef CORE_PRIVATE
60
61 #include "http_log.h"
62 #include "http_protocol.h"
63 #include "http_request.h"
64
65 #include "apr.h"
66 #include "apr_lib.h"
67 #include "apr_ring.h"
68 #include "apr_hash.h"
69 #include "apr_strings.h"
70
71 #define APR_WANT_STRFUNC
72 #include "apr_want.h"
73
74 /* }}} */
75 /* {{{ domaintree_module */
76
77 module AP_MODULE_DECLARE_DATA domaintree_module;
78
79 /* }}} */
80 /* {{{ Macros & Types */
81
82 #define MOD_DT_CNF domaintree_conf
83 #define MOD_DT_PTR (&domaintree_module)
84
85 #define NUL '\0'
86 #define EMPTY(str) ((str == NULL) || (*(str) == NUL))
87
88 typedef int STATUS;
89
90 typedef struct {
91 apr_hash_t *hashtable;
92 size_t recursion;
93 } aliases_t;
94
95 typedef struct {
96 server_rec *server;
97 int enabled;
98 int stripwww;
99 size_t maxdepth;
100 char *prefix;
101 char *suffix;
102 aliases_t aliases;
103 } domaintree_conf;
104
105 struct domaintree_entry {
106 char *name;
107 APR_RING_ENTRY(domaintree_entry) link;
108 };
109 APR_RING_HEAD(domaintree, domaintree_entry);
110
111 /* }}} */
112 /* {{{ Helpers */
113
114 static APR_INLINE char *strtr(char *string, char from, char to)
115 {
116 char *ptr = string;
117
118 if (from != to) {
119 while ((ptr = strchr(ptr, from))) {
120 *ptr = to;
121 }
122 }
123 return string;
124 }
125
126 static APR_INLINE char *trim(char *string, size_t length, char what, int l, int r)
127 {
128 if (r) {
129 while (length-- && (string[length] == what)) {
130 string[length] = NUL;
131 }
132 }
133 if (l) {
134 while (*string == what) {
135 ++string;
136 }
137 }
138 return string;
139 }
140
141 static APR_INLINE char *domaintree_host(apr_pool_t *pool, MOD_DT_CNF *DT, const char *name)
142 {
143 if (EMPTY(name)) {
144 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, DT->server, "DomainTree: no host/server name");
145 return NULL;
146 } else {
147 size_t len;
148 char *port, *ptr, *host;
149
150 ptr = host = apr_pstrdup(pool, name);
151 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: host name = %s", host);
152
153 /* check for :NN port */
154 if ((port = strchr(ptr, ':'))) {
155 len = port - ptr;
156 } else {
157 len = strlen(ptr);
158 }
159
160 /* strip leading & trailing dots */
161 ptr = host = trim(ptr, len, '.', 1, 1);
162 /* lowercase */
163 while (*ptr) {
164 apr_tolower(*ptr++);
165 }
166
167 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: sane host = %s", host);
168
169 return host;
170 }
171 }
172
173 static APR_INLINE const char *domaintree_elem(apr_pool_t *pool, struct domaintree *tree, const char *name, size_t length)
174 {
175 struct domaintree_entry *elem = apr_palloc(pool, sizeof(struct domaintree_entry));
176
177 APR_RING_ELEM_INIT(elem, link);
178 APR_RING_INSERT_HEAD(tree, elem, domaintree_entry, link);
179
180 return elem->name = apr_pstrndup(pool, name, length);
181 }
182
183 static APR_INLINE struct domaintree *domaintree_tree(apr_pool_t *pool, MOD_DT_CNF *DT, char *host)
184 {
185 size_t depth = 0;
186 char *host_ptr = host;
187 struct domaintree *tree = apr_palloc(pool, sizeof(struct domaintree));
188
189 APR_RING_INIT(tree, domaintree_entry, link);
190
191 while ((host_ptr = strchr(host, '.'))) {
192
193 /* check max depth */
194 if (++depth > DT->maxdepth) {
195 ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, DT->server, "DomainTree: maxdepth exceeded = %s", host);
196 return NULL;
197 }
198
199 /* append part */
200 if (host_ptr - host) {
201
202 /* strip WWW */
203 if (DT->stripwww && (depth == 1) && (!strncmp(host, "www.", sizeof("www")))) {
204 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: stripping www.");
205 } else {
206 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: host part (%d) = %s",
207 depth - 1, domaintree_elem(pool, tree, host, host_ptr - host)
208 );
209 }
210 }
211
212 host = host_ptr + 1;
213 }
214
215 /* append last part */
216 if (!EMPTY(host)) {
217 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: host part (%d) = %s",
218 depth, domaintree_elem(pool, tree, host, strlen(host))
219 );
220 }
221
222 return tree;
223 }
224
225 static APR_INLINE char *domaintree_path(apr_pool_t *pool, MOD_DT_CNF *DT, struct domaintree *tree)
226 {
227 struct domaintree_entry *elem;
228 char *path = apr_pstrdup(pool, DT->prefix);
229
230 APR_RING_FOREACH(elem, tree, domaintree_entry, link) {
231 path = apr_pstrcat(pool, path, "/", elem->name, NULL);
232 }
233
234 return path = apr_pstrcat(pool, path, "/", DT->suffix, NULL);
235 }
236
237 static APR_INLINE void domaintree_fake(apr_pool_t *pool, MOD_DT_CNF *DT, char **path)
238 {
239 int more;
240 apr_hash_index_t *idx;
241 size_t recurlevel = 0, plen = strlen(DT->prefix) + 1;
242
243 begin:
244 more = 0;
245
246 if (recurlevel++ > DT->aliases.recursion) {
247 ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, DT->server, "DomainTree: maximum alias recursion level (%d) exceeded!"
248 " Check if you have recursive definitions of DomainTreeAlias directives.", DT->aliases.recursion);
249 return;
250 }
251
252 for (idx = apr_hash_first(pool, DT->aliases.hashtable); idx; idx = apr_hash_next(idx)) {
253 apr_ssize_t flen;
254 char *fake, *real, *poff;
255
256 poff = *path + plen;
257 apr_hash_this(idx, (const void **) &fake, &flen, (void **) &real);
258
259 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: fake test %s in %s", fake, poff);
260
261 if ((!strncasecmp(poff, fake, flen)) && ((!poff[flen]) || (poff[flen] == '/'))) {
262 char *padd;
263
264 more = 1;
265 padd = apr_pstrndup(pool, poff + flen + 1, strlen(poff) - flen - 1);
266 *path = apr_pstrcat(pool, DT->prefix, "/", real, "/", padd, NULL);
267
268 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: fake done %s<>%s = %s", fake, real, *path);
269 }
270 }
271
272 if (more && DT->aliases.recursion) {
273 goto begin;
274 }
275 }
276
277 /* }}} */
278 /* {{{ Hooks */
279
280 static STATUS domaintree_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
281 {
282 ap_add_version_component(pconf, MODULE "/" VERSION);
283 return OK;
284 }
285
286 static STATUS domaintree_hook_translate_name(request_rec *r)
287 {
288 MOD_DT_CNF *DT = NULL;
289 struct domaintree *tree;
290 char *path, *host;
291
292 DT = ap_get_module_config(r->server->module_config, MOD_DT_PTR);
293 if ((!DT) || (!DT->enabled)) {
294 return DECLINED;
295 }
296
297 /* get a usable host name */
298 if (!(host = domaintree_host(r->pool, DT, ap_get_server_name(r)))) {
299 return DECLINED;
300 }
301
302 /* build domain tree */
303 if (!(tree = domaintree_tree(r->pool, DT, host))) {
304 return DECLINED;
305 }
306
307 /* build path */
308 if (!(path = domaintree_path(r->pool, DT, tree))) {
309 return DECLINED;
310 }
311
312 /* apply any aliases.hashtable */
313 domaintree_fake(r->pool, DT, &path);
314
315 /* done */
316 r->canonical_filename = "";
317 r->filename = apr_pstrcat(r->pool, path, r->uri, NULL);
318 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server, "DomainTree: path done = %s", r->filename);
319
320 return OK;
321 }
322
323 static void domaintree_hooks(apr_pool_t *pool)
324 {
325 ap_hook_post_config(domaintree_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
326 ap_hook_translate_name(domaintree_hook_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
327 }
328
329 /* }}} */
330 /* {{{ Configuration */
331
332 static void *domaintree_create_srv(apr_pool_t *p, server_rec *s)
333 {
334 MOD_DT_CNF *DT;
335
336 DT = (MOD_DT_CNF *) apr_palloc(p, sizeof(MOD_DT_CNF));
337
338 DT->server = s;
339 DT->enabled = 1;
340 DT->stripwww = 1;
341 DT->maxdepth = 20;
342
343 DT->prefix = "/var/www";
344 DT->suffix = "public_html";
345
346 DT->aliases.hashtable = apr_hash_make(p);
347 DT->aliases.recursion = 0;
348
349 return DT;
350 }
351
352 static const char *domaintree_enable(cmd_parms *cmd, void *conf, int flag)
353 {
354 MOD_DT_CNF *DT;
355
356 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
357 DT->enabled = flag;
358
359 return NULL;
360 }
361
362 static const char *domaintree_stripwww(cmd_parms *cmd, void *conf, int flag)
363 {
364 MOD_DT_CNF *DT;
365
366 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
367 DT->stripwww = flag;
368
369 return NULL;
370 }
371
372 static const char *domaintree_prefix(cmd_parms *cmd, void *conf, const char *prefix)
373 {
374 MOD_DT_CNF *DT;
375
376 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
377 DT->prefix = EMPTY(prefix) ? "/" : trim(apr_pstrdup(cmd->pool, prefix), strlen(prefix), '/', 0, 1);
378
379 return NULL;
380 }
381
382 static const char *domaintree_suffix(cmd_parms *cmd, void *conf, const char *suffix)
383 {
384 MOD_DT_CNF *DT;
385
386 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
387 DT->suffix = EMPTY(suffix) ? "" : trim(apr_pstrdup(cmd->pool, suffix), strlen(suffix), '/', 1, 1);
388
389 return NULL;
390 }
391
392 static const char *domaintree_maxdepth(cmd_parms *cmd, void *conf, const char *max_depth)
393 {
394 int depth;
395
396 if ((depth = atoi(max_depth))) {
397 if (depth > 0) {
398 MOD_DT_CNF *DT;
399 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
400 DT->maxdepth = (size_t) depth;
401 } else {
402 return "Maximum DomainTree depth cannot be negative.";
403 }
404 }
405
406 return NULL;
407 }
408
409 static const char *domaintree_aliasrecursion(cmd_parms *cmd, void *conf, const char *alias_recursion)
410 {
411 int recursion;
412
413 if ((recursion = atoi(alias_recursion))) {
414 if (recursion > 0) {
415 MOD_DT_CNF *DT;
416 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
417 DT->aliases.recursion = (size_t) recursion;
418 } else {
419 return "DomainTree alias recursion cannot be negative.";
420 }
421 }
422
423 return NULL;
424 }
425
426 static const char *domaintree_alias(cmd_parms *cmd, void *conf, const char *fake, const char *real)
427 {
428 MOD_DT_CNF *DT;
429 char *f = strtr(apr_pstrdup(cmd->pool, fake), '.', '/'), *r = strtr(apr_pstrdup(cmd->pool, real), '.', '/');
430
431 DT = ap_get_module_config(cmd->server->module_config, MOD_DT_PTR);
432 apr_hash_set(DT->aliases.hashtable, trim(f, strlen(f), '/', 1, 1), APR_HASH_KEY_STRING, trim(r, strlen(r), '/', 1, 1));
433
434 return NULL;
435 }
436
437
438 /* }}} */
439 /* {{{ Commands */
440
441 static command_rec domaintree_commands[] = {
442 AP_INIT_FLAG(
443 "DomainTreeEnabled", domaintree_enable, NULL, RSRC_CONF,
444 "Turn the module on or off."
445 ),
446
447 AP_INIT_FLAG(
448 "DomainTreeStripWWW", domaintree_stripwww, NULL, RSRC_CONF,
449 "Strip leading www from host. (default On)"
450 ),
451
452 AP_INIT_TAKE1(
453 "DomainTreePrefix", domaintree_prefix, NULL, RSRC_CONF,
454 "DomainTree path prefix. (default /var/www) Do not forget the leading slash!"
455 ),
456
457 AP_INIT_TAKE1(
458 "DomainTreeSuffix", domaintree_suffix, NULL, RSRC_CONF,
459 "DomainTree path suffix. (default public_html)"
460 ),
461
462 AP_INIT_TAKE1(
463 "DomainTreeMaxdepth", domaintree_maxdepth, NULL, RSRC_CONF,
464 "DomainTree max path depth. (default 20)"
465 ),
466
467 AP_INIT_TAKE1(
468 "DomainTreeAliasRecursion", domaintree_aliasrecursion, NULL, RSRC_CONF,
469 "Whether (and how often at the maximum) DomainTree should walk recursively "
470 "through the aliases.hashtable list as long as matching aliases.hashtable are found. (Default: 0 = turned off)"
471 ),
472
473 AP_INIT_TAKE2(
474 "DomainTreeAlias", domaintree_alias, NULL, RSRC_CONF,
475 "DomainTree aliases.hashtable; e.g. DomainTreeAlias com/example/tickets com/example/support (dots or slashes equal)"
476 ),
477
478 { NULL }
479 };
480
481 /* }}} */
482 /* {{{ Module Administrativa */
483
484 module AP_MODULE_DECLARE_DATA domaintree_module = {
485 STANDARD20_MODULE_STUFF,
486 NULL, /* create per-dir */
487 NULL, /* merge per-dir */
488 domaintree_create_srv, /* create per-server */
489 NULL, /* merge per-server */
490 domaintree_commands, /* apr_table_t commands */
491 domaintree_hooks /* register hooks */
492 };
493
494 /* }}} */
495
496 /*
497 * Local variables:
498 * tab-width: 4
499 * c-basic-offset: 4
500 * End:
501 * vim600: noet sw=4 ts=4 fdm=marker
502 * vim<600: noet sw=4 ts=4
503 */