]> granicus.if.org Git - apache/blob - modules/mappers/mod_rewrite.c
Pass a valid pool to apr_create_lock().
[apache] / modules / mappers / mod_rewrite.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /*                       _                            _ _
60 **   _ __ ___   ___   __| |    _ __ _____      ___ __(_) |_ ___
61 **  | '_ ` _ \ / _ \ / _` |   | '__/ _ \ \ /\ / / '__| | __/ _ \
62 **  | | | | | | (_) | (_| |   | | |  __/\ V  V /| |  | | ||  __/
63 **  |_| |_| |_|\___/ \__,_|___|_|  \___| \_/\_/ |_|  |_|\__\___|
64 **                       |_____|
65 **
66 **  URL Rewriting Module
67 **
68 **  This module uses a rule-based rewriting engine (based on a
69 **  regular-expression parser) to rewrite requested URLs on the fly.
70 **
71 **  It supports an unlimited number of additional rule conditions (which can
72 **  operate on a lot of variables, even on HTTP headers) for granular
73 **  matching and even external database lookups (either via plain text
74 **  tables, DBM hash files or even external processes) for advanced URL
75 **  substitution.
76 **
77 **  It operates on the full URLs (including the PATH_INFO part) both in
78 **  per-server context (httpd.conf) and per-dir context (.htaccess) and even
79 **  can generate QUERY_STRING parts on result.   The rewriting result finally
80 **  can lead to internal subprocessing, external request redirection or even
81 **  to internal proxy throughput.
82 **
83 **  This module was originally written in April 1996 and
84 **  gifted exclusively to the The Apache Software Foundation in July 1997 by
85 **
86 **      Ralf S. Engelschall
87 **      rse@engelschall.com
88 **      www.engelschall.com
89 */
90
91 #include "ap_config.h"
92 #include "httpd.h"
93 #include "http_config.h"
94 #include "http_request.h"
95 #include "http_core.h"
96 #include "http_log.h"
97 #include "http_protocol.h"
98 #include "mod_rewrite.h"
99 #include "apr_strings.h"
100 #include "apr_user.h"
101
102 #if !defined(OS2) && !defined(WIN32)
103 #include "unixd.h"
104 #endif
105
106 #ifndef NO_WRITEV
107 #ifndef NETWARE
108 #ifdef HAVE_SYS_TYPES_H
109 #include <sys/types.h>
110 #endif
111 #endif
112 #if APR_HAVE_SYS_UIO_H
113 #include <sys/uio.h>
114 #endif
115 #endif
116 #ifdef HAVE_PWD_H
117 #include <pwd.h>
118 #endif
119 #ifdef HAVE_GRP_H
120 #include <grp.h>
121 #endif
122 #ifdef HAVE_UNISTD_H
123 #include <unistd.h>
124 #endif
125 #ifdef HAVE_STRINGS_H
126 #include <strings.h>
127 #endif
128
129 /*
130 ** +-------------------------------------------------------+
131 ** |                                                       |
132 ** |             static module configuration
133 ** |                                                       |
134 ** +-------------------------------------------------------+
135 */
136
137
138 /*
139 **  Our interface to the Apache server kernel:
140 **
141 **  o  Runtime logic of a request is as following:
142 **       while(request or subrequest)
143 **           foreach(stage #0...#9)
144 **               foreach(module) (**)
145 **                   try to run hook
146 **
147 **  o  the order of modules at (**) is the inverted order as
148 **     given in the "Configuration" file, i.e. the last module
149 **     specified is the first one called for each hook!
150 **     The core module is always the last!
151 **
152 **  o  there are two different types of result checking and
153 **     continue processing:
154 **     for hook #0,#1,#4,#5,#6,#8:
155 **         hook run loop stops on first modules which gives
156 **         back a result != DECLINED, i.e. it usually returns OK
157 **         which says "OK, module has handled this _stage_" and for #1
158 **         this have not to mean "Ok, the filename is now valid".
159 **     for hook #2,#3,#7,#9:
160 **         all hooks are run, independend of result
161 **
162 **  o  at the last stage, the core module always
163 **       - says "HTTP_BAD_REQUEST" if r->filename does not begin with "/"
164 **       - prefix URL with document_root or replaced server_root
165 **         with document_root and sets r->filename
166 **       - always return a "OK" independed if the file really exists
167 **         or not!
168 */
169
170     /* The section for the Configure script:
171     * XXX: this needs updating for apache-2.0 configuration method
172      * MODULE-DEFINITION-START
173      * Name: rewrite_module
174      * ConfigStart
175     . ./helpers/find-dbm-lib
176     if [ "x$found_dbm" = "x1" ]; then
177         echo "      enabling DBM support for mod_rewrite"
178     else
179         echo "      disabling DBM support for mod_rewrite"
180         echo "      (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
181         CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
182     fi
183      * ConfigEnd
184      * MODULE-DEFINITION-END
185      */
186
187     /* the apr_table_t of commands we provide */
188 static const command_rec command_table[] = {
189     AP_INIT_FLAG(    "RewriteEngine",   cmd_rewriteengine,  NULL, OR_FILEINFO,
190                      "On or Off to enable or disable (default) the whole "
191                      "rewriting engine"),
192     AP_INIT_ITERATE( "RewriteOptions",  cmd_rewriteoptions,  NULL, OR_FILEINFO,
193                      "List of option strings to set"),
194     AP_INIT_TAKE1(   "RewriteBase",     cmd_rewritebase,     NULL, OR_FILEINFO, 
195                      "the base URL of the per-directory context"),
196     AP_INIT_RAW_ARGS("RewriteCond",     cmd_rewritecond,     NULL, OR_FILEINFO,
197                      "an input string and a to be applied regexp-pattern"),
198     AP_INIT_RAW_ARGS("RewriteRule",     cmd_rewriterule,     NULL, OR_FILEINFO,
199                      "an URL-applied regexp-pattern and a substitution URL"),
200     AP_INIT_TAKE2(   "RewriteMap",      cmd_rewritemap,      NULL, RSRC_CONF,
201                      "a mapname and a filename"),
202     AP_INIT_TAKE1(   "RewriteLock",     cmd_rewritelock,     NULL, RSRC_CONF,
203                      "the filename of a lockfile used for inter-process "
204                      "synchronization"),
205     AP_INIT_TAKE1(   "RewriteLog",      cmd_rewritelog,      NULL, RSRC_CONF,
206                      "the filename of the rewriting logfile"),
207     AP_INIT_TAKE1(   "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF,
208                      "the level of the rewriting logfile verbosity "
209                      "(0=none, 1=std, .., 9=max)"),
210     { NULL }
211 };
212
213     /* the apr_table_t of content handlers we provide */
214 static const handler_rec handler_table[] = {
215     { "redirect-handler", handler_redirect },
216     { NULL }
217 };
218
219 static void register_hooks(void)
220 {
221     ap_hook_post_config(init_module,NULL,NULL,AP_HOOK_MIDDLE);
222     ap_hook_child_init(init_child,NULL,NULL,AP_HOOK_MIDDLE);
223
224     ap_hook_fixups(hook_fixup,NULL,NULL,AP_HOOK_FIRST);
225     ap_hook_translate_name(hook_uri2file,NULL,NULL,AP_HOOK_FIRST);
226     ap_hook_type_checker(hook_mimetype,NULL,NULL,AP_HOOK_MIDDLE);
227 }
228
229     /* the main config structure */
230 module AP_MODULE_DECLARE_DATA rewrite_module = {
231    STANDARD20_MODULE_STUFF,
232    config_perdir_create,        /* create per-dir    config structures */
233    config_perdir_merge,         /* merge  per-dir    config structures */
234    config_server_create,        /* create per-server config structures */
235    config_server_merge,         /* merge  per-server config structures */
236    command_table,               /* apr_table_t of config file commands  */
237    handler_table,               /* [#8] MIME-typed-dispatched handlers */
238    register_hooks               /* register hooks                      */
239 };
240
241     /* the cache */
242 static cache *cachep;
243
244     /* whether proxy module is available or not */
245 static int proxy_available;
246
247 static const char *lockname;
248 static apr_lock_t *rewrite_mapr_lock = NULL;
249 static apr_lock_t *rewrite_log_lock = NULL;
250
251 /*
252 ** +-------------------------------------------------------+
253 ** |                                                       |
254 ** |           configuration directive handling
255 ** |                                                       |
256 ** +-------------------------------------------------------+
257 */
258
259 /*
260 **
261 **  per-server configuration structure handling
262 **
263 */
264
265 static void *config_server_create(apr_pool_t *p, server_rec *s)
266 {
267     rewrite_server_conf *a;
268
269     a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
270
271     a->state           = ENGINE_DISABLED;
272     a->options         = OPTION_NONE;
273     a->rewritelogfile  = NULL;
274     a->rewritelogfp    = NULL;
275     a->rewriteloglevel = 0;
276     a->rewritemaps     = apr_make_array(p, 2, sizeof(rewritemap_entry));
277     a->rewriteconds    = apr_make_array(p, 2, sizeof(rewritecond_entry));
278     a->rewriterules    = apr_make_array(p, 2, sizeof(rewriterule_entry));
279     a->server          = s;
280
281     return (void *)a;
282 }
283
284 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
285 {
286     rewrite_server_conf *a, *base, *overrides;
287
288     a         = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
289     base      = (rewrite_server_conf *)basev;
290     overrides = (rewrite_server_conf *)overridesv;
291
292     a->state   = overrides->state;
293     a->options = overrides->options;
294     a->server  = overrides->server;
295
296     if (a->options & OPTION_INHERIT) {
297         /*
298          *  local directives override
299          *  and anything else is inherited
300          */
301         a->rewriteloglevel = overrides->rewriteloglevel != 0 
302                              ? overrides->rewriteloglevel
303                              : base->rewriteloglevel;
304         a->rewritelogfile  = overrides->rewritelogfile != NULL 
305                              ? overrides->rewritelogfile
306                              : base->rewritelogfile;
307         a->rewritelogfp    = overrides->rewritelogfp != NULL 
308                              ? overrides->rewritelogfp 
309                              : base->rewritelogfp;
310         a->rewritemaps     = apr_append_arrays(p, overrides->rewritemaps,
311                                               base->rewritemaps);
312         a->rewriteconds    = apr_append_arrays(p, overrides->rewriteconds,
313                                               base->rewriteconds);
314         a->rewriterules    = apr_append_arrays(p, overrides->rewriterules,
315                                               base->rewriterules);
316     }
317     else {
318         /*
319          *  local directives override
320          *  and anything else gets defaults
321          */
322         a->rewriteloglevel = overrides->rewriteloglevel;
323         a->rewritelogfile  = overrides->rewritelogfile;
324         a->rewritelogfp    = overrides->rewritelogfp;
325         a->rewritemaps     = overrides->rewritemaps;
326         a->rewriteconds    = overrides->rewriteconds;
327         a->rewriterules    = overrides->rewriterules;
328     }
329
330     return (void *)a;
331 }
332
333
334 /*
335 **
336 **  per-directory configuration structure handling
337 **
338 */
339
340 static void *config_perdir_create(apr_pool_t *p, char *path)
341 {
342     rewrite_perdir_conf *a;
343
344     a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
345
346     a->state           = ENGINE_DISABLED;
347     a->options         = OPTION_NONE;
348     a->baseurl         = NULL;
349     a->rewriteconds    = apr_make_array(p, 2, sizeof(rewritecond_entry));
350     a->rewriterules    = apr_make_array(p, 2, sizeof(rewriterule_entry));
351
352     if (path == NULL) {
353         a->directory = NULL;
354     }
355     else {
356         /* make sure it has a trailing slash */
357         if (path[strlen(path)-1] == '/') {
358             a->directory = apr_pstrdup(p, path);
359         }
360         else {
361             a->directory = apr_pstrcat(p, path, "/", NULL);
362         }
363     }
364
365     return (void *)a;
366 }
367
368 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
369 {
370     rewrite_perdir_conf *a, *base, *overrides;
371
372     a         = (rewrite_perdir_conf *)apr_pcalloc(p,
373                                                   sizeof(rewrite_perdir_conf));
374     base      = (rewrite_perdir_conf *)basev;
375     overrides = (rewrite_perdir_conf *)overridesv;
376
377     a->state     = overrides->state;
378     a->options   = overrides->options;
379     a->directory = overrides->directory;
380     a->baseurl   = overrides->baseurl;
381
382     if (a->options & OPTION_INHERIT) {
383         a->rewriteconds = apr_append_arrays(p, overrides->rewriteconds,
384                                            base->rewriteconds);
385         a->rewriterules = apr_append_arrays(p, overrides->rewriterules,
386                                            base->rewriterules);
387     }
388     else {
389         a->rewriteconds = overrides->rewriteconds;
390         a->rewriterules = overrides->rewriterules;
391     }
392
393     return (void *)a;
394 }
395
396
397 /*
398 **
399 **  the configuration commands
400 **
401 */
402
403 static const char *cmd_rewriteengine(cmd_parms *cmd,
404                                      void *in_dconf, int flag)
405 {
406     rewrite_perdir_conf *dconf = in_dconf;
407     rewrite_server_conf *sconf;
408
409     sconf = 
410         (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
411                                                     &rewrite_module);
412
413     if (cmd->path == NULL) { /* is server command */
414         sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
415     }
416     else                   /* is per-directory command */ {
417         dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
418     }
419
420     return NULL;
421 }
422
423 static const char *cmd_rewriteoptions(cmd_parms *cmd,
424                                       void *in_dconf, const char *option)
425 {
426     rewrite_perdir_conf *dconf = in_dconf;
427     rewrite_server_conf *sconf;
428     const char *err;
429
430     sconf = (rewrite_server_conf *)
431             ap_get_module_config(cmd->server->module_config, &rewrite_module);
432
433     if (cmd->path == NULL) { /* is server command */
434         err = cmd_rewriteoptions_setoption(cmd->pool,
435                                            &(sconf->options), option);
436     }
437     else {                 /* is per-directory command */
438         err = cmd_rewriteoptions_setoption(cmd->pool,
439                                            &(dconf->options), option);
440     }
441
442     return err;
443 }
444
445 static const char *cmd_rewriteoptions_setoption(apr_pool_t *p, int *options,
446                                                 const char *name)
447 {
448     if (strcasecmp(name, "inherit") == 0) {
449         *options |= OPTION_INHERIT;
450     }
451     else {
452         return apr_pstrcat(p, "RewriteOptions: unknown option '",
453                           name, "'", NULL);
454     }
455     return NULL;
456 }
457
458 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1)
459 {
460     rewrite_server_conf *sconf;
461
462     sconf = (rewrite_server_conf *)
463             ap_get_module_config(cmd->server->module_config, &rewrite_module);
464
465     sconf->rewritelogfile = a1;
466
467     return NULL;
468 }
469
470 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, const char *a1)
471 {
472     rewrite_server_conf *sconf;
473
474     sconf = (rewrite_server_conf *)
475             ap_get_module_config(cmd->server->module_config, &rewrite_module);
476
477     sconf->rewriteloglevel = atoi(a1);
478
479     return NULL;
480 }
481
482 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
483                                   const char *a2)
484 {
485     rewrite_server_conf *sconf;
486     rewritemap_entry *newmap;
487     apr_finfo_t st;
488
489     sconf = (rewrite_server_conf *)
490             ap_get_module_config(cmd->server->module_config, &rewrite_module);
491
492     newmap = apr_push_array(sconf->rewritemaps);
493
494     newmap->name = a1;
495     newmap->func = NULL;
496     if (strncmp(a2, "txt:", 4) == 0) {
497         newmap->type      = MAPTYPE_TXT;
498         newmap->datafile  = a2+4;
499         newmap->checkfile = a2+4;
500     }
501     else if (strncmp(a2, "rnd:", 4) == 0) {
502         newmap->type      = MAPTYPE_RND;
503         newmap->datafile  = a2+4;
504         newmap->checkfile = a2+4;
505     }
506     else if (strncmp(a2, "dbm:", 4) == 0) {
507 #ifndef NO_DBM_REWRITEMAP
508         newmap->type      = MAPTYPE_DBM;
509         newmap->datafile  = a2+4;
510         newmap->checkfile = apr_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
511 #else
512         return apr_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
513                           "because no NDBM support is compiled in");
514 #endif
515     }
516     else if (strncmp(a2, "prg:", 4) == 0) {
517         newmap->type = MAPTYPE_PRG;
518         newmap->datafile = a2+4;
519         newmap->checkfile = a2+4;
520     }
521     else if (strncmp(a2, "int:", 4) == 0) {
522         newmap->type      = MAPTYPE_INT;
523         newmap->datafile  = NULL;
524         newmap->checkfile = NULL;
525         if (strcmp(a2+4, "tolower") == 0) {
526             newmap->func = rewrite_mapfunc_tolower;
527         }
528         else if (strcmp(a2+4, "toupper") == 0) {
529             newmap->func = rewrite_mapfunc_toupper;
530         }
531         else if (strcmp(a2+4, "escape") == 0) {
532             newmap->func = rewrite_mapfunc_escape;
533         }
534         else if (strcmp(a2+4, "unescape") == 0) {
535             newmap->func = rewrite_mapfunc_unescape;
536         }
537         else if (sconf->state == ENGINE_ENABLED) {
538             return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
539                               a2+4, NULL);
540         }
541     }
542     else {
543         newmap->type      = MAPTYPE_TXT;
544         newmap->datafile  = a2;
545         newmap->checkfile = a2;
546     }
547     newmap->fpin  = NULL;
548     newmap->fpout = NULL;
549
550     if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
551         && (apr_stat(&st, newmap->checkfile, cmd->pool) != APR_SUCCESS)) {
552         return apr_pstrcat(cmd->pool,
553                           "RewriteMap: map file or program not found:",
554                           newmap->checkfile, NULL);
555     }
556
557     return NULL;
558 }
559
560 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1)
561 {
562     const char *error;
563
564     if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
565         return error;
566
567     lockname = a1;
568
569     return NULL;
570 }
571
572 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
573                                    const char *a1)
574 {
575     rewrite_perdir_conf *dconf = in_dconf;
576
577     if (cmd->path == NULL || dconf == NULL) {
578         return "RewriteBase: only valid in per-directory config files";
579     }
580     if (a1[0] == '\0') {
581         return "RewriteBase: empty URL not allowed";
582     }
583     if (a1[0] != '/') {
584         return "RewriteBase: argument is not a valid URL";
585     }
586
587     dconf->baseurl = a1;
588
589     return NULL;
590 }
591
592 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
593                                    const char *in_str)
594 {
595     rewrite_perdir_conf *dconf = in_dconf;
596     char *str = apr_pstrdup(cmd->pool, in_str);
597     rewrite_server_conf *sconf;
598     rewritecond_entry *newcond;
599     regex_t *regexp;
600     char *a1;
601     char *a2;
602     char *a3;
603     char *cp;
604     const char *err;
605     int rc;
606
607     sconf = (rewrite_server_conf *)
608             ap_get_module_config(cmd->server->module_config, &rewrite_module);
609
610     /*  make a new entry in the internal temporary rewrite rule list */
611     if (cmd->path == NULL) {   /* is server command */
612         newcond = apr_push_array(sconf->rewriteconds);
613     }
614     else {                     /* is per-directory command */
615         newcond = apr_push_array(dconf->rewriteconds);
616     }
617
618     /*  parse the argument line ourself */
619     if (parseargline(str, &a1, &a2, &a3)) {
620         return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
621                           "'", NULL);
622     }
623
624     /*  arg1: the input string */
625     newcond->input = apr_pstrdup(cmd->pool, a1);
626
627     /* arg3: optional flags field
628        (this have to be first parsed, because we need to
629         know if the regex should be compiled with ICASE!) */
630     newcond->flags = CONDFLAG_NONE;
631     if (a3 != NULL) {
632         if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
633                                                   a3)) != NULL) {
634             return err;
635         }
636     }
637
638     /*  arg2: the pattern
639         try to compile the regexp to test if is ok */
640     cp = a2;
641     if (cp[0] == '!') {
642         newcond->flags |= CONDFLAG_NOTMATCH;
643         cp++;
644     }
645
646     /* now be careful: Under the POSIX regex library
647        we can compile the pattern for case insensitive matching,
648        under the old V8 library we have to do it self via a hack */
649     if (newcond->flags & CONDFLAG_NOCASE) {
650         rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
651               == NULL);
652     }
653     else {
654         rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
655     }
656     if (rc) {
657         return apr_pstrcat(cmd->pool,
658                           "RewriteCond: cannot compile regular expression '",
659                           a2, "'", NULL);
660     }
661
662     newcond->pattern = apr_pstrdup(cmd->pool, cp);
663     newcond->regexp  = regexp;
664
665     return NULL;
666 }
667
668 static const char *cmd_rewritecond_parseflagfield(apr_pool_t *p,
669                                                   rewritecond_entry *cfg,
670                                                   char *str)
671 {
672     char *cp;
673     char *cp1;
674     char *cp2;
675     char *cp3;
676     char *key;
677     char *val;
678     const char *err;
679
680     if (str[0] != '[' || str[strlen(str)-1] != ']') {
681         return "RewriteCond: bad flag delimiters";
682     }
683
684     cp = str+1;
685     str[strlen(str)-1] = ','; /* for simpler parsing */
686     for ( ; *cp != '\0'; ) {
687         /* skip whitespaces */
688         for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
689             ;
690         if (*cp == '\0') {
691             break;
692         }
693         cp1 = cp;
694         if ((cp2 = strchr(cp, ',')) != NULL) {
695             cp = cp2+1;
696             for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
697                 ;
698             *cp2 = '\0';
699             if ((cp3 = strchr(cp1, '=')) != NULL) {
700                 *cp3 = '\0';
701                 key = cp1;
702                 val = cp3+1;
703             }
704             else {
705                 key = cp1;
706                 val = "";
707             }
708             if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
709                 return err;
710             }
711         }
712         else {
713             break;
714         }
715     }
716
717     return NULL;
718 }
719
720 static const char *cmd_rewritecond_setflag(apr_pool_t *p, rewritecond_entry *cfg,
721                                            char *key, char *val)
722 {
723     if (   strcasecmp(key, "nocase") == 0
724         || strcasecmp(key, "NC") == 0    ) {
725         cfg->flags |= CONDFLAG_NOCASE;
726     }
727     else if (   strcasecmp(key, "ornext") == 0
728              || strcasecmp(key, "OR") == 0    ) {
729         cfg->flags |= CONDFLAG_ORNEXT;
730     }
731     else {
732         return apr_pstrcat(p, "RewriteCond: unknown flag '", key, "'", NULL);
733     }
734     return NULL;
735 }
736
737 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
738                                    const char *in_str)
739 {
740     rewrite_perdir_conf *dconf = in_dconf;
741     char *str = apr_pstrdup(cmd->pool, in_str);
742     rewrite_server_conf *sconf;
743     rewriterule_entry *newrule;
744     regex_t *regexp;
745     char *a1;
746     char *a2;
747     char *a3;
748     char *cp;
749     const char *err;
750     int mode;
751
752     sconf = (rewrite_server_conf *)
753             ap_get_module_config(cmd->server->module_config, &rewrite_module);
754
755     /*  make a new entry in the internal rewrite rule list */
756     if (cmd->path == NULL) {   /* is server command */
757         newrule = apr_push_array(sconf->rewriterules);
758     }
759     else {                     /* is per-directory command */
760         newrule = apr_push_array(dconf->rewriterules);
761     }
762
763     /*  parse the argument line ourself */
764     if (parseargline(str, &a1, &a2, &a3)) {
765         return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
766                           "'", NULL);
767     }
768
769     /* arg3: optional flags field */
770     newrule->forced_mimetype     = NULL;
771     newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
772     newrule->flags  = RULEFLAG_NONE;
773     newrule->env[0] = NULL;
774     newrule->skip   = 0;
775     if (a3 != NULL) {
776         if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
777                                                   a3)) != NULL) {
778             return err;
779         }
780     }
781
782     /*  arg1: the pattern
783      *  try to compile the regexp to test if is ok
784      */
785     cp = a1;
786     if (cp[0] == '!') {
787         newrule->flags |= RULEFLAG_NOTMATCH;
788         cp++;
789     }
790     mode = REG_EXTENDED;
791     if (newrule->flags & RULEFLAG_NOCASE) {
792         mode |= REG_ICASE;
793     }
794     if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
795         return apr_pstrcat(cmd->pool,
796                           "RewriteRule: cannot compile regular expression '",
797                           a1, "'", NULL);
798     }
799     newrule->pattern = apr_pstrdup(cmd->pool, cp);
800     newrule->regexp  = regexp;
801
802     /*  arg2: the output string
803      *  replace the $<N> by \<n> which is needed by the currently
804      *  used Regular Expression library
805      *
806      * TODO: Is this still required for PCRE?  If not, does it *work* with PCRE?
807      */
808     newrule->output = apr_pstrdup(cmd->pool, a2);
809
810     /* now, if the server or per-dir config holds an
811      * array of RewriteCond entries, we take it for us
812      * and clear the array
813      */
814     if (cmd->path == NULL) {  /* is server command */
815         newrule->rewriteconds   = sconf->rewriteconds;
816         sconf->rewriteconds = apr_make_array(cmd->pool, 2,
817                                             sizeof(rewritecond_entry));
818     }
819     else {                    /* is per-directory command */
820         newrule->rewriteconds   = dconf->rewriteconds;
821         dconf->rewriteconds = apr_make_array(cmd->pool, 2,
822                                             sizeof(rewritecond_entry));
823     }
824
825     return NULL;
826 }
827
828 static const char *cmd_rewriterule_parseflagfield(apr_pool_t *p,
829                                                   rewriterule_entry *cfg,
830                                                   char *str)
831 {
832     char *cp;
833     char *cp1;
834     char *cp2;
835     char *cp3;
836     char *key;
837     char *val;
838     const char *err;
839
840     if (str[0] != '[' || str[strlen(str)-1] != ']') {
841         return "RewriteRule: bad flag delimiters";
842     }
843
844     cp = str+1;
845     str[strlen(str)-1] = ','; /* for simpler parsing */
846     for ( ; *cp != '\0'; ) {
847         /* skip whitespaces */
848         for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
849             ;
850         if (*cp == '\0') {
851             break;
852         }
853         cp1 = cp;
854         if ((cp2 = strchr(cp, ',')) != NULL) {
855             cp = cp2+1;
856             for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
857                 ;
858             *cp2 = '\0';
859             if ((cp3 = strchr(cp1, '=')) != NULL) {
860                 *cp3 = '\0';
861                 key = cp1;
862                 val = cp3+1;
863             }
864             else {
865                 key = cp1;
866                 val = "";
867             }
868             if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
869                 return err;
870             }
871         }
872         else {
873             break;
874         }
875     }
876
877     return NULL;
878 }
879
880 static const char *cmd_rewriterule_setflag(apr_pool_t *p, rewriterule_entry *cfg,
881                                            char *key, char *val)
882 {
883     int status = 0;
884     int i;
885
886     if (   strcasecmp(key, "redirect") == 0
887         || strcasecmp(key, "R") == 0       ) {
888         cfg->flags |= RULEFLAG_FORCEREDIRECT;
889         if (strlen(val) > 0) {
890             if (strcasecmp(val, "permanent") == 0) {
891                 status = HTTP_MOVED_PERMANENTLY;
892             }
893             else if (strcasecmp(val, "temp") == 0) {
894                 status = HTTP_MOVED_TEMPORARILY;
895             }
896             else if (strcasecmp(val, "seeother") == 0) {
897                 status = HTTP_SEE_OTHER;
898             }
899             else if (apr_isdigit(*val)) {
900                 status = atoi(val);
901             }
902             if (!ap_is_HTTP_REDIRECT(status)) {
903                 return "RewriteRule: invalid HTTP response code "
904                        "for flag 'R'";
905             }
906             cfg->forced_responsecode = status;
907         }
908     }
909     else if (   strcasecmp(key, "last") == 0
910              || strcasecmp(key, "L") == 0   ) {
911         cfg->flags |= RULEFLAG_LASTRULE;
912     }
913     else if (   strcasecmp(key, "next") == 0
914              || strcasecmp(key, "N") == 0   ) {
915         cfg->flags |= RULEFLAG_NEWROUND;
916     }
917     else if (   strcasecmp(key, "chain") == 0
918              || strcasecmp(key, "C") == 0    ) {
919         cfg->flags |= RULEFLAG_CHAIN;
920     }
921     else if (   strcasecmp(key, "type") == 0
922              || strcasecmp(key, "T") == 0   ) {
923         cfg->forced_mimetype = apr_pstrdup(p, val);
924         ap_str_tolower(cfg->forced_mimetype);
925     }
926     else if (   strcasecmp(key, "env") == 0
927              || strcasecmp(key, "E") == 0   ) {
928         for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
929             ;
930         if (i < MAX_ENV_FLAGS) {
931             cfg->env[i] = apr_pstrdup(p, val);
932             cfg->env[i+1] = NULL;
933         }
934         else {
935             return "RewriteRule: too many environment flags 'E'";
936         }
937     }
938     else if (   strcasecmp(key, "nosubreq") == 0
939              || strcasecmp(key, "NS") == 0      ) {
940         cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
941     }
942     else if (   strcasecmp(key, "proxy") == 0
943              || strcasecmp(key, "P") == 0      ) {
944         cfg->flags |= RULEFLAG_PROXY;
945     }
946     else if (   strcasecmp(key, "passthrough") == 0
947              || strcasecmp(key, "PT") == 0      ) {
948         cfg->flags |= RULEFLAG_PASSTHROUGH;
949     }
950     else if (   strcasecmp(key, "skip") == 0
951              || strcasecmp(key, "S") == 0   ) {
952         cfg->skip = atoi(val);
953     }
954     else if (   strcasecmp(key, "forbidden") == 0
955              || strcasecmp(key, "F") == 0   ) {
956         cfg->flags |= RULEFLAG_FORBIDDEN;
957     }
958     else if (   strcasecmp(key, "gone") == 0
959              || strcasecmp(key, "G") == 0   ) {
960         cfg->flags |= RULEFLAG_GONE;
961     }
962     else if (   strcasecmp(key, "qsappend") == 0
963              || strcasecmp(key, "QSA") == 0   ) {
964         cfg->flags |= RULEFLAG_QSAPPEND;
965     }
966     else if (   strcasecmp(key, "nocase") == 0
967              || strcasecmp(key, "NC") == 0    ) {
968         cfg->flags |= RULEFLAG_NOCASE;
969     }
970     else {
971         return apr_pstrcat(p, "RewriteRule: unknown flag '", key, "'", NULL);
972     }
973     return NULL;
974 }
975
976
977 /*
978 **
979 **  Global Module Initialization
980 **  [called from read_config() after all
981 **  config commands were already called]
982 **
983 */
984
985 static void init_module(apr_pool_t *p,
986                         apr_pool_t *plog,
987                         apr_pool_t *ptemp,
988                         server_rec *s)
989 {
990     apr_status_t rv;
991     void *data;
992     int first_time = 0;
993     const char *userdata_key = "rewrite_init_module";
994
995     apr_get_userdata(&data, userdata_key, s->process->pool);
996     if (!data) {
997         first_time = 1;
998         apr_set_userdata((const void *)1, userdata_key,
999                          apr_null_cleanup, s->process->pool);
1000     }
1001
1002     /* check if proxy module is available */
1003     proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
1004
1005     /* create the rewriting lockfiles in the parent */
1006     if ((rv = apr_create_lock (&rewrite_log_lock, APR_MUTEX, APR_LOCKALL,
1007                                NULL, p)) != APR_SUCCESS) {
1008         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
1009                      "mod_rewrite: could not create rewrite_log_lock");
1010         exit(1);
1011     }
1012
1013     rewritelock_create(s, p);
1014     apr_register_cleanup(p, (void *)s, rewritelock_remove, apr_null_cleanup);
1015
1016     /* step through the servers and
1017      * - open each rewriting logfile
1018      * - open the RewriteMap prg:xxx programs
1019      */
1020     for (; s; s = s->next) {
1021         open_rewritelog(s, p);
1022         if (!first_time)
1023            run_rewritemap_programs(s, p);
1024     }
1025 }
1026
1027
1028 /*
1029 **
1030 **  Per-Child Module Initialization
1031 **  [called after a child process is spawned]
1032 **
1033 */
1034
1035 static void init_child(apr_pool_t *p, server_rec *s)
1036 {
1037     apr_status_t rv;
1038
1039     if (lockname != NULL && *(lockname) != '\0')
1040     {
1041         rv = apr_child_init_lock (&rewrite_mapr_lock, lockname, p);
1042         if (rv != APR_SUCCESS) {
1043             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
1044                          "mod_rewrite: could not init rewrite_mapr_lock "
1045                          "in child");
1046         }
1047     }
1048
1049     /* create the lookup cache */
1050     cachep = init_cache(p);
1051 }
1052
1053
1054 /*
1055 ** +-------------------------------------------------------+
1056 ** |                                                       |
1057 ** |                     runtime hooks
1058 ** |                                                       |
1059 ** +-------------------------------------------------------+
1060 */
1061
1062 /*
1063 **
1064 **  URI-to-filename hook
1065 **
1066 **  [used for the rewriting engine triggered by
1067 **  the per-server 'RewriteRule' directives]
1068 **
1069 */
1070
1071 static int hook_uri2file(request_rec *r)
1072 {
1073     void *sconf;
1074     rewrite_server_conf *conf;
1075     const char *var;
1076     const char *thisserver;
1077     char *thisport;
1078     const char *thisurl;
1079     char buf[512];
1080     char docroot[512];
1081     char *cp, *cp2;
1082     const char *ccp;
1083     apr_finfo_t finfo;
1084     unsigned int port;
1085     int n;
1086     int l;
1087
1088     /*
1089      *  retrieve the config structures
1090      */
1091     sconf = r->server->module_config;
1092     conf  = (rewrite_server_conf *)ap_get_module_config(sconf,
1093                                                         &rewrite_module);
1094
1095     /*
1096      *  only do something under runtime if the engine is really enabled,
1097      *  else return immediately!
1098      */
1099     if (conf->state == ENGINE_DISABLED) {
1100         return DECLINED;
1101     }
1102
1103     /*
1104      *  check for the ugly API case of a virtual host section where no
1105      *  mod_rewrite directives exists. In this situation we became no chance
1106      *  by the API to setup our default per-server config so we have to
1107      *  on-the-fly assume we have the default config. But because the default
1108      *  config has a disabled rewriting engine we are lucky because can
1109      *  just stop operating now.
1110      */
1111     if (conf->server != r->server) {
1112         return DECLINED;
1113     }
1114
1115     /*
1116      *  add the SCRIPT_URL variable to the env. this is a bit complicated
1117      *  due to the fact that apache uses subrequests and internal redirects
1118      */
1119
1120     if (r->main == NULL) {
1121          var = apr_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1122          var = apr_table_get(r->subprocess_env, var);
1123          if (var == NULL) {
1124              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1125          }
1126          else {
1127              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1128          }
1129     }
1130     else {
1131          var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1132          apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1133     }
1134
1135     /*
1136      *  create the SCRIPT_URI variable for the env
1137      */
1138
1139     /* add the canonical URI of this URL */
1140     thisserver = ap_get_server_name(r);
1141     port = ap_get_server_port(r);
1142     if (ap_is_default_port(port, r)) {
1143         thisport = "";
1144     }
1145     else {
1146         apr_snprintf(buf, sizeof(buf), ":%u", port);
1147         thisport = buf;
1148     }
1149     thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1150
1151     /* set the variable */
1152     var = apr_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1153                      thisurl, NULL);
1154     apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1155
1156     /* if filename was not initially set,
1157      * we start with the requested URI
1158      */
1159     if (r->filename == NULL) {
1160         r->filename = apr_pstrdup(r->pool, r->uri);
1161         rewritelog(r, 2, "init rewrite engine with requested uri %s",
1162                    r->filename);
1163     }
1164
1165     /*
1166      *  now apply the rules ...
1167      */
1168     if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1169
1170         if (strlen(r->filename) > 6 &&
1171             strncmp(r->filename, "proxy:", 6) == 0) {
1172             /* it should be go on as an internal proxy request */
1173
1174             /* check if the proxy module is enabled, so
1175              * we can actually use it!
1176              */
1177             if (!proxy_available) {
1178                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1179                              "attempt to make remote request from mod_rewrite "
1180                              "without proxy enabled: %s", r->filename);
1181                 return HTTP_FORBIDDEN;
1182             }
1183
1184             /* make sure the QUERY_STRING and
1185              * PATH_INFO parts get incorporated
1186              */
1187             if (r->path_info != NULL) {
1188                 r->filename = apr_pstrcat(r->pool, r->filename,
1189                                          r->path_info, NULL);
1190             }
1191             if (r->args != NULL &&
1192                 r->uri == r->unparsed_uri) {
1193                 /* see proxy_http:proxy_http_canon() */
1194                 r->filename = apr_pstrcat(r->pool, r->filename,
1195                                          "?", r->args, NULL);
1196             }
1197
1198             /* now make sure the request gets handled by the proxy handler */
1199             r->proxyreq = 1;
1200             r->handler  = "proxy-server";
1201
1202             rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1203                        r->filename);
1204             return OK;
1205         }
1206         else if (is_absolute_uri(r->filename)) {
1207             /* it was finally rewritten to a remote URL */
1208
1209             /* skip 'scheme:' */
1210             for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1211                 ;
1212             /* skip '://' */
1213             cp += 3;
1214             /* skip host part */
1215             for ( ; *cp != '/' && *cp != '\0'; cp++)
1216                 ;
1217             if (*cp != '\0') {
1218                 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1219                 cp2 = ap_escape_uri(r->pool, cp);
1220                 *cp = '\0';
1221                 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1222             }
1223
1224             /* append the QUERY_STRING part */
1225             if (r->args != NULL) {
1226                 r->filename = apr_pstrcat(r->pool, r->filename, "?", 
1227                                          ap_escape_uri(r->pool, r->args), NULL);
1228             }
1229
1230             /* determine HTTP redirect response code */
1231             if (ap_is_HTTP_REDIRECT(r->status)) {
1232                 n = r->status;
1233                 r->status = HTTP_OK; /* make Apache kernel happy */
1234             }
1235             else {
1236                 n = HTTP_MOVED_TEMPORARILY;
1237             }
1238
1239             /* now do the redirection */
1240             apr_table_setn(r->headers_out, "Location", r->filename);
1241             rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1242             return n;
1243         }
1244         else if (strlen(r->filename) > 10 &&
1245                  strncmp(r->filename, "forbidden:", 10) == 0) {
1246             /* This URLs is forced to be forbidden for the requester */
1247             return HTTP_FORBIDDEN;
1248         }
1249         else if (strlen(r->filename) > 5 &&
1250                  strncmp(r->filename, "gone:", 5) == 0) {
1251             /* This URLs is forced to be gone */
1252             return HTTP_GONE;
1253         }
1254         else if (strlen(r->filename) > 12 &&
1255                  strncmp(r->filename, "passthrough:", 12) == 0) {
1256             /*
1257              * Hack because of underpowered API: passing the current
1258              * rewritten filename through to other URL-to-filename handlers
1259              * just as it were the requested URL. This is to enable
1260              * post-processing by mod_alias, etc.  which always act on
1261              * r->uri! The difference here is: We do not try to
1262              * add the document root
1263              */
1264             r->uri = apr_pstrdup(r->pool, r->filename+12);
1265             return DECLINED;
1266         }
1267         else {
1268             /* it was finally rewritten to a local path */
1269
1270             /* expand "/~user" prefix */
1271 #if APR_HAS_USER
1272             r->filename = expand_tildepaths(r, r->filename);
1273 #endif
1274             rewritelog(r, 2, "local path result: %s", r->filename);
1275
1276             /* the filename has to start with a slash! */
1277             if (r->filename[0] != '/') {
1278                 return HTTP_BAD_REQUEST;
1279             }
1280
1281             /* if there is no valid prefix, we have
1282              * to emulate the translator from the core and
1283              * prefix the filename with document_root
1284              *
1285              * NOTICE:
1286              * We cannot leave out the prefix_stat because
1287              * - when we always prefix with document_root
1288              *   then no absolute path can be created, e.g. via
1289              *   emulating a ScriptAlias directive, etc.
1290              * - when we always NOT prefix with document_root
1291              *   then the files under document_root have to
1292              *   be references directly and document_root
1293              *   gets never used and will be a dummy parameter -
1294              *   this is also bad
1295              *
1296              * BUT:
1297              * Under real Unix systems this is no problem,
1298              * because we only do stat() on the first directory
1299              * and this gets cached by the kernel for along time!
1300              */
1301             n = prefix_stat(r->filename, &finfo);
1302             if (n == 0) {
1303                 if ((ccp = ap_document_root(r)) != NULL) {
1304                     l = apr_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1305
1306                     /* always NOT have a trailing slash */
1307                     if (docroot[l-1] == '/') {
1308                         docroot[l-1] = '\0';
1309                     }
1310                     if (r->server->path
1311                         && !strncmp(r->filename, r->server->path,
1312                                     r->server->pathlen)) {
1313                         r->filename = apr_pstrcat(r->pool, docroot,
1314                                                  (r->filename +
1315                                                   r->server->pathlen), NULL);
1316                     }
1317                     else {
1318                         r->filename = apr_pstrcat(r->pool, docroot, 
1319                                                  r->filename, NULL);
1320                     }
1321                     rewritelog(r, 2, "prefixed with document_root to %s",
1322                                r->filename);
1323                 }
1324             }
1325
1326             rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1327             return OK;
1328         }
1329     }
1330     else {
1331         rewritelog(r, 1, "pass through %s", r->filename);
1332         return DECLINED;
1333     }
1334 }
1335
1336
1337 /*
1338 **
1339 **  MIME-type hook
1340 **
1341 **  [used to support the forced-MIME-type feature]
1342 **
1343 */
1344
1345 static int hook_mimetype(request_rec *r)
1346 {
1347     const char *t;
1348
1349     /* now check if we have to force a MIME-type */
1350     t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1351     if (t == NULL) {
1352         return DECLINED;
1353     }
1354     else {
1355         rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1356                    r->filename, t);
1357         r->content_type = t;
1358         return OK;
1359     }
1360 }
1361
1362
1363 /*
1364 **
1365 **  Fixup hook
1366 **
1367 **  [used for the rewriting engine triggered by
1368 **  the per-directory 'RewriteRule' directives]
1369 **
1370 */
1371
1372 static int hook_fixup(request_rec *r)
1373 {
1374     rewrite_perdir_conf *dconf;
1375     char *cp;
1376     char *cp2;
1377     const char *ccp;
1378     char *prefix;
1379     int l;
1380     int n;
1381     char *ofilename;
1382
1383     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1384                                                         &rewrite_module);
1385
1386     /* if there is no per-dir config we return immediately */
1387     if (dconf == NULL) {
1388         return DECLINED;
1389     }
1390
1391     /* we shouldn't do anything in subrequests */
1392     if (r->main != NULL) {
1393         return DECLINED;
1394     }
1395
1396     /* if there are no real (i.e. no RewriteRule directives!)
1397        per-dir config of us, we return also immediately */
1398     if (dconf->directory == NULL) {
1399         return DECLINED;
1400     }
1401
1402     /*
1403      *  only do something under runtime if the engine is really enabled,
1404      *  for this directory, else return immediately!
1405      */
1406     if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1407         /* FollowSymLinks is mandatory! */
1408         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1409                      "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1410                      "which implies that RewriteRule directive is forbidden: "
1411                      "%s", r->filename);
1412         return HTTP_FORBIDDEN;
1413     }
1414     else {
1415         /* FollowSymLinks is given, but the user can
1416          * still turn off the rewriting engine
1417          */
1418         if (dconf->state == ENGINE_DISABLED) {
1419             return DECLINED;
1420         }
1421     }
1422
1423     /*
1424      *  remember the current filename before rewriting for later check
1425      *  to prevent deadlooping because of internal redirects
1426      *  on final URL/filename which can be equal to the inital one.
1427      */
1428     ofilename = r->filename;
1429
1430     /*
1431      *  now apply the rules ...
1432      */
1433     if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1434
1435         if (strlen(r->filename) > 6 &&
1436             strncmp(r->filename, "proxy:", 6) == 0) {
1437             /* it should go on as an internal proxy request */
1438
1439             /* make sure the QUERY_STRING and
1440              * PATH_INFO parts get incorporated
1441              * (r->path_info was already appended by the
1442              * rewriting engine because of the per-dir context!)
1443              */
1444             if (r->args != NULL) {
1445                 r->filename = apr_pstrcat(r->pool, r->filename,
1446                                          "?", r->args, NULL);
1447             }
1448
1449             /* now make sure the request gets handled by the proxy handler */
1450             r->proxyreq = 1;
1451             r->handler  = "proxy-server";
1452
1453             rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1454                        "%s [OK]", dconf->directory, r->filename);
1455             return OK;
1456         }
1457         else if (is_absolute_uri(r->filename)) {
1458             /* it was finally rewritten to a remote URL */
1459
1460             /* because we are in a per-dir context
1461              * first try to replace the directory with its base-URL
1462              * if there is a base-URL available
1463              */
1464             if (dconf->baseurl != NULL) {
1465                 /* skip 'scheme:' */
1466                 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1467                     ;
1468                 /* skip '://' */
1469                 cp += 3;
1470                 if ((cp = strchr(cp, '/')) != NULL) {
1471                     rewritelog(r, 2,
1472                                "[per-dir %s] trying to replace "
1473                                "prefix %s with %s",
1474                                dconf->directory, dconf->directory,
1475                                dconf->baseurl);
1476                     cp2 = subst_prefix_path(r, cp, dconf->directory,
1477                                             dconf->baseurl);
1478                     if (strcmp(cp2, cp) != 0) {
1479                         *cp = '\0';
1480                         r->filename = apr_pstrcat(r->pool, r->filename,
1481                                                  cp2, NULL);
1482                     }
1483                 }
1484             }
1485
1486             /* now prepare the redirect... */
1487
1488             /* skip 'scheme:' */
1489             for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1490                 ;
1491             /* skip '://' */
1492             cp += 3;
1493             /* skip host part */
1494             for ( ; *cp != '/' && *cp != '\0'; cp++)
1495                 ;
1496             if (*cp != '\0') {
1497                 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1498                            dconf->directory, r->filename);
1499                 cp2 = ap_escape_uri(r->pool, cp);
1500                 *cp = '\0';
1501                 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1502             }
1503
1504             /* append the QUERY_STRING part */
1505             if (r->args != NULL) {
1506                 r->filename = apr_pstrcat(r->pool, r->filename, "?", 
1507                                          ap_escape_uri(r->pool, r->args), NULL);
1508             }
1509
1510             /* determine HTTP redirect response code */
1511             if (ap_is_HTTP_REDIRECT(r->status)) {
1512                 n = r->status;
1513                 r->status = HTTP_OK; /* make Apache kernel happy */
1514             }
1515             else {
1516                 n = HTTP_MOVED_TEMPORARILY;
1517             }
1518
1519             /* now do the redirection */
1520             apr_table_setn(r->headers_out, "Location", r->filename);
1521             rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1522                        dconf->directory, r->filename, n);
1523             return n;
1524         }
1525         else if (strlen(r->filename) > 10 &&
1526                  strncmp(r->filename, "forbidden:", 10) == 0) {
1527             /* This URL is forced to be forbidden for the requester */
1528             return HTTP_FORBIDDEN;
1529         }
1530         else if (strlen(r->filename) > 5 &&
1531                  strncmp(r->filename, "gone:", 5) == 0) {
1532             /* This URL is forced to be gone */
1533             return HTTP_GONE;
1534         }
1535         else {
1536             /* it was finally rewritten to a local path */
1537
1538             /* if someone used the PASSTHROUGH flag in per-dir
1539              * context we just ignore it. It is only useful
1540              * in per-server context
1541              */
1542             if (strlen(r->filename) > 12 &&
1543                 strncmp(r->filename, "passthrough:", 12) == 0) {
1544                 r->filename = apr_pstrdup(r->pool, r->filename+12);
1545             }
1546
1547             /* the filename has to start with a slash! */
1548             if (r->filename[0] != '/') {
1549                 return HTTP_BAD_REQUEST;
1550             }
1551
1552             /* Check for deadlooping:
1553              * At this point we KNOW that at least one rewriting
1554              * rule was applied, but when the resulting URL is
1555              * the same as the initial URL, we are not allowed to
1556              * use the following internal redirection stuff because
1557              * this would lead to a deadloop.
1558              */
1559             if (strcmp(r->filename, ofilename) == 0) {
1560                 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1561                            "URL: %s [IGNORING REWRITE]",
1562                            dconf->directory, r->filename);
1563                 return OK;
1564             }
1565
1566             /* if there is a valid base-URL then substitute
1567              * the per-dir prefix with this base-URL if the
1568              * current filename still is inside this per-dir
1569              * context. If not then treat the result as a
1570              * plain URL
1571              */
1572             if (dconf->baseurl != NULL) {
1573                 rewritelog(r, 2,
1574                            "[per-dir %s] trying to replace prefix %s with %s",
1575                            dconf->directory, dconf->directory, dconf->baseurl);
1576                 r->filename = subst_prefix_path(r, r->filename,
1577                                                 dconf->directory,
1578                                                 dconf->baseurl);
1579             }
1580             else {
1581                 /* if no explicit base-URL exists we assume
1582                  * that the directory prefix is also a valid URL
1583                  * for this webserver and only try to remove the
1584                  * document_root if it is prefix
1585                  */
1586                 if ((ccp = ap_document_root(r)) != NULL) {
1587                     prefix = apr_pstrdup(r->pool, ccp);
1588                     /* always NOT have a trailing slash */
1589                     l = strlen(prefix);
1590                     if (prefix[l-1] == '/') {
1591                         prefix[l-1] = '\0';
1592                         l--;
1593                     }
1594                     if (strncmp(r->filename, prefix, l) == 0) {
1595                         rewritelog(r, 2,
1596                                    "[per-dir %s] strip document_root "
1597                                    "prefix: %s -> %s",
1598                                    dconf->directory, r->filename,
1599                                    r->filename+l);
1600                         r->filename = apr_pstrdup(r->pool, r->filename+l);
1601                     }
1602                 }
1603             }
1604
1605             /* now initiate the internal redirect */
1606             rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1607                        "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1608             r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
1609             r->handler = "redirect-handler";
1610             return OK;
1611         }
1612     }
1613     else {
1614         rewritelog(r, 1, "[per-dir %s] pass through %s", 
1615                    dconf->directory, r->filename);
1616         return DECLINED;
1617     }
1618 }
1619
1620
1621 /*
1622 **
1623 **  Content-Handlers
1624 **
1625 **  [used for redirect support]
1626 **
1627 */
1628
1629 static int handler_redirect(request_rec *r)
1630 {
1631     /* just make sure that we are really meant! */
1632     if (strncmp(r->filename, "redirect:", 9) != 0) {
1633         return DECLINED;
1634     }
1635
1636     /* now do the internal redirect */
1637     ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
1638                                     r->args ? "?" : NULL, r->args, NULL), r);
1639
1640     /* and return gracefully */
1641     return OK;
1642 }
1643
1644
1645 /*
1646 ** +-------------------------------------------------------+
1647 ** |                                                       |
1648 ** |                  the rewriting engine
1649 ** |                                                       |
1650 ** +-------------------------------------------------------+
1651 */
1652
1653 /*
1654  *  Apply a complete rule set,
1655  *  i.e. a list of rewrite rules
1656  */
1657 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
1658                               char *perdir)
1659 {
1660     rewriterule_entry *entries;
1661     rewriterule_entry *p;
1662     int i;
1663     int changed;
1664     int rc;
1665     int s;
1666
1667     /*
1668      *  Iterate over all existing rules
1669      */
1670     entries = (rewriterule_entry *)rewriterules->elts;
1671     changed = 0;
1672     loop:
1673     for (i = 0; i < rewriterules->nelts; i++) {
1674         p = &entries[i];
1675
1676         /*
1677          *  Ignore this rule on subrequests if we are explicitly
1678          *  asked to do so or this is a proxy-throughput or a
1679          *  forced redirect rule.
1680          */
1681         if (r->main != NULL &&
1682             (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1683              p->flags & RULEFLAG_PROXY          ||
1684              p->flags & RULEFLAG_FORCEREDIRECT    )) {
1685             continue;
1686         }
1687
1688         /*
1689          *  Apply the current rule.
1690          */
1691         rc = apply_rewrite_rule(r, p, perdir);
1692         if (rc) {
1693             /*
1694              *  Indicate a change if this was not a match-only rule.
1695              */
1696             if (rc != 2) {
1697                 changed = 1;
1698             }
1699
1700             /*
1701              *  Pass-Through Feature (`RewriteRule .. .. [PT]'):
1702              *  Because the Apache 1.x API is very limited we
1703              *  need this hack to pass the rewritten URL to other
1704              *  modules like mod_alias, mod_userdir, etc.
1705              */
1706             if (p->flags & RULEFLAG_PASSTHROUGH) {
1707                 rewritelog(r, 2, "forcing '%s' to get passed through "
1708                            "to next API URI-to-filename handler", r->filename);
1709                 r->filename = apr_pstrcat(r->pool, "passthrough:",
1710                                          r->filename, NULL);
1711                 changed = 1;
1712                 break;
1713             }
1714
1715             /*
1716              *  Rule has the "forbidden" flag set which means that
1717              *  we stop processing and indicate this to the caller.
1718              */
1719             if (p->flags & RULEFLAG_FORBIDDEN) {
1720                 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1721                 r->filename = apr_pstrcat(r->pool, "forbidden:",
1722                                          r->filename, NULL);
1723                 changed = 1;
1724                 break;
1725             }
1726
1727             /*
1728              *  Rule has the "gone" flag set which means that
1729              *  we stop processing and indicate this to the caller.
1730              */
1731             if (p->flags & RULEFLAG_GONE) {
1732                 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1733                 r->filename = apr_pstrcat(r->pool, "gone:", r->filename, NULL);
1734                 changed = 1;
1735                 break;
1736             }
1737
1738             /*
1739              *  Stop processing also on proxy pass-through and
1740              *  last-rule and new-round flags.
1741              */
1742             if (p->flags & RULEFLAG_PROXY) {
1743                 break;
1744             }
1745             if (p->flags & RULEFLAG_LASTRULE) {
1746                 break;
1747             }
1748
1749             /*
1750              *  On "new-round" flag we just start from the top of
1751              *  the rewriting ruleset again.
1752              */
1753             if (p->flags & RULEFLAG_NEWROUND) {
1754                 goto loop;
1755             }
1756
1757             /*
1758              *  If we are forced to skip N next rules, do it now.
1759              */
1760             if (p->skip > 0) {
1761                 s = p->skip;
1762                 while (   i < rewriterules->nelts
1763                        && s > 0) {
1764                     i++;
1765                     p = &entries[i];
1766                     s--;
1767                 }
1768             }
1769         }
1770         else {
1771             /*
1772              *  If current rule is chained with next rule(s),
1773              *  skip all this next rule(s)
1774              */
1775             while (   i < rewriterules->nelts
1776                    && p->flags & RULEFLAG_CHAIN) {
1777                 i++;
1778                 p = &entries[i];
1779             }
1780         }
1781     }
1782     return changed;
1783 }
1784
1785 /*
1786  *  Apply a single(!) rewrite rule
1787  */
1788 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1789                               char *perdir)
1790 {
1791     char *uri;
1792     char *output;
1793     const char *vary;
1794     char newuri[MAX_STRING_LEN];
1795     regex_t *regexp;
1796     regmatch_t regmatch[MAX_NMATCH];
1797     backrefinfo *briRR = NULL;
1798     backrefinfo *briRC = NULL;
1799     int prefixstrip;
1800     int failed;
1801     apr_array_header_t *rewriteconds;
1802     rewritecond_entry *conds;
1803     rewritecond_entry *c;
1804     int i;
1805     int rc;
1806
1807     /*
1808      *  Initialisation
1809      */
1810     uri     = r->filename;
1811     regexp  = p->regexp;
1812     output  = p->output;
1813
1814     /*
1815      *  Add (perhaps splitted away) PATH_INFO postfix to URL to
1816      *  make sure we really match against the complete URL.
1817      */
1818     if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1819         rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1820                    perdir, uri, uri, r->path_info);
1821         uri = apr_pstrcat(r->pool, uri, r->path_info, NULL);
1822     }
1823
1824     /*
1825      *  On per-directory context (.htaccess) strip the location
1826      *  prefix from the URL to make sure patterns apply only to
1827      *  the local part.  Additionally indicate this special
1828      *  threatment in the logfile.
1829      */
1830     prefixstrip = 0;
1831     if (perdir != NULL) {
1832         if (   strlen(uri) >= strlen(perdir)
1833             && strncmp(uri, perdir, strlen(perdir)) == 0) {
1834             rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1835                        perdir, uri, uri+strlen(perdir));
1836             uri = uri+strlen(perdir);
1837             prefixstrip = 1;
1838         }
1839     }
1840
1841     /*
1842      *  Try to match the URI against the RewriteRule pattern
1843      *  and exit immeddiately if it didn't apply.
1844      */
1845     if (perdir == NULL) {
1846         rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1847                    p->pattern, uri);
1848     }
1849     else {
1850         rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1851                    perdir, p->pattern, uri);
1852     }
1853     rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1854     if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1855            (!rc &&  (p->flags & RULEFLAG_NOTMATCH))   ) ) {
1856         return 0;
1857     }
1858
1859     /*
1860      *  Else create the RewriteRule `regsubinfo' structure which
1861      *  holds the substitution information.
1862      */
1863     briRR = (backrefinfo *)apr_palloc(r->pool, sizeof(backrefinfo));
1864     if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1865         /*  empty info on negative patterns  */
1866         briRR->source = "";
1867         briRR->nsub   = 0;
1868     }
1869     else {
1870         briRR->source = apr_pstrdup(r->pool, uri);
1871         briRR->nsub   = regexp->re_nsub;
1872         memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1873                sizeof(regmatch));
1874     }
1875
1876     /*
1877      *  Initiallally create the RewriteCond backrefinfo with
1878      *  empty backrefinfo, i.e. not subst parts
1879      *  (this one is adjusted inside apply_rewrite_cond() later!!)
1880      */
1881     briRC = (backrefinfo *)apr_pcalloc(r->pool, sizeof(backrefinfo));
1882     briRC->source = "";
1883     briRC->nsub   = 0;
1884
1885     /*
1886      *  Ok, we already know the pattern has matched, but we now
1887      *  additionally have to check for all existing preconditions
1888      *  (RewriteCond) which have to be also true. We do this at
1889      *  this very late stage to avoid unnessesary checks which
1890      *  would slow down the rewriting engine!!
1891      */
1892     rewriteconds = p->rewriteconds;
1893     conds = (rewritecond_entry *)rewriteconds->elts;
1894     failed = 0;
1895     for (i = 0; i < rewriteconds->nelts; i++) {
1896         c = &conds[i];
1897         rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1898         if (c->flags & CONDFLAG_ORNEXT) {
1899             /*
1900              *  The "OR" case
1901              */
1902             if (rc == 0) {
1903                 /*  One condition is false, but another can be
1904                  *  still true, so we have to continue...
1905                  */
1906                 apr_table_unset(r->notes, VARY_KEY_THIS);
1907                 continue;
1908             }
1909             else {
1910                 /*  One true condition is enough in "or" case, so
1911                  *  skip the other conditions which are "ornext"
1912                  *  chained
1913                  */
1914                 while (   i < rewriteconds->nelts
1915                        && c->flags & CONDFLAG_ORNEXT) {
1916                     i++;
1917                     c = &conds[i];
1918                 }
1919                 continue;
1920             }
1921         }
1922         else {
1923             /*
1924              *  The "AND" case, i.e. no "or" flag,
1925              *  so a single failure means total failure.
1926              */
1927             if (rc == 0) {
1928                 failed = 1;
1929                 break;
1930             }
1931         }
1932         vary = apr_table_get(r->notes, VARY_KEY_THIS);
1933         if (vary != NULL) {
1934             apr_table_merge(r->notes, VARY_KEY, vary);
1935             apr_table_unset(r->notes, VARY_KEY_THIS);
1936         }
1937     }
1938     /*  if any condition fails the complete rule fails  */
1939     if (failed) {
1940         apr_table_unset(r->notes, VARY_KEY);
1941         apr_table_unset(r->notes, VARY_KEY_THIS);
1942         return 0;
1943     }
1944
1945     /*
1946      * Regardless of what we do next, we've found a match.  Check to see
1947      * if any of the request header fields were involved, and add them
1948      * to the Vary field of the response.
1949      */
1950     if ((vary = apr_table_get(r->notes, VARY_KEY)) != NULL) {
1951         apr_table_merge(r->headers_out, "Vary", vary);
1952         apr_table_unset(r->notes, VARY_KEY);
1953     }
1954
1955     /*
1956      *  If this is a pure matching rule (`RewriteRule <pat> -')
1957      *  we stop processing and return immediately. The only thing
1958      *  we have not to forget are the environment variables
1959      *  (`RewriteRule <pat> - [E=...]')
1960      */
1961     if (strcmp(output, "-") == 0) {
1962         do_expand_env(r, p->env, briRR, briRC);
1963         if (p->forced_mimetype != NULL) {
1964             if (perdir == NULL) {
1965                 /* In the per-server context we can force the MIME-type
1966                  * the correct way by notifying our MIME-type hook handler
1967                  * to do the job when the MIME-type API stage is reached.
1968                  */
1969                 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1970                            r->filename, p->forced_mimetype);
1971                 apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1972                               p->forced_mimetype);
1973             }
1974             else {
1975                 /* In per-directory context we operate in the Fixup API hook
1976                  * which is after the MIME-type hook, so our MIME-type handler
1977                  * has no chance to set r->content_type. And because we are
1978                  * in the situation where no substitution takes place no
1979                  * sub-request will happen (which could solve the
1980                  * restriction). As a workaround we do it ourself now
1981                  * immediately although this is not strictly API-conforming.
1982                  * But it's the only chance we have...
1983                  */
1984                 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1985                            "'%s'", perdir, r->filename, p->forced_mimetype);
1986                 r->content_type = p->forced_mimetype;
1987             }
1988         }
1989         return 2;
1990     }
1991
1992     /*
1993      *  Ok, now we finally know all patterns have matched and
1994      *  that there is something to replace, so we create the
1995      *  substitution URL string in `newuri'.
1996      */
1997     do_expand(r, output, newuri, sizeof(newuri), briRR, briRC);
1998     if (perdir == NULL) {
1999         rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
2000     }
2001     else {
2002         rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
2003     }
2004
2005     /*
2006      *  Additionally do expansion for the environment variable
2007      *  strings (`RewriteRule .. .. [E=<string>]').
2008      */
2009     do_expand_env(r, p->env, briRR, briRC);
2010
2011     /*
2012      *  Now replace API's knowledge of the current URI:
2013      *  Replace r->filename with the new URI string and split out
2014      *  an on-the-fly generated QUERY_STRING part into r->args
2015      */
2016     r->filename = apr_pstrdup(r->pool, newuri);
2017     splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
2018
2019     /*
2020      *   Again add the previously stripped per-directory location
2021      *   prefix if the new URI is not a new one for this
2022      *   location, i.e. if it's not starting with either a slash
2023      *   or a fully qualified URL scheme.
2024      */
2025     if (prefixstrip && r->filename[0] != '/'
2026         && !is_absolute_uri(r->filename)) {
2027         rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2028                    perdir, r->filename, perdir, r->filename);
2029         r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
2030     }
2031
2032     /*
2033      *  If this rule is forced for proxy throughput
2034      *  (`RewriteRule ... ... [P]') then emulate mod_proxy's
2035      *  URL-to-filename handler to be sure mod_proxy is triggered
2036      *  for this URL later in the Apache API. But make sure it is
2037      *  a fully-qualified URL. (If not it is qualified with
2038      *  ourself).
2039      */
2040     if (p->flags & RULEFLAG_PROXY) {
2041         fully_qualify_uri(r);
2042         if (perdir == NULL) {
2043             rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
2044         }
2045         else {
2046             rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
2047                        perdir, r->filename);
2048         }
2049         r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
2050         return 1;
2051     }
2052
2053     /*
2054      *  If this rule is explicitly forced for HTTP redirection
2055      *  (`RewriteRule .. .. [R]') then force an external HTTP
2056      *  redirect. But make sure it is a fully-qualified URL. (If
2057      *  not it is qualified with ourself).
2058      */
2059     if (p->flags & RULEFLAG_FORCEREDIRECT) {
2060         fully_qualify_uri(r);
2061         if (perdir == NULL) {
2062             rewritelog(r, 2,
2063                        "explicitly forcing redirect with %s", r->filename);
2064         }
2065         else {
2066             rewritelog(r, 2,
2067                        "[per-dir %s] explicitly forcing redirect with %s",
2068                        perdir, r->filename);
2069         }
2070         r->status = p->forced_responsecode;
2071         return 1;
2072     }
2073
2074     /*
2075      *  Special Rewriting Feature: Self-Reduction
2076      *  We reduce the URL by stripping a possible
2077      *  http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2078      *  corresponds to ourself. This is to simplify rewrite maps
2079      *  and to avoid recursion, etc. When this prefix is not a
2080      *  coincidence then the user has to use [R] explicitly (see
2081      *  above).
2082      */
2083     reduce_uri(r);
2084
2085     /*
2086      *  If this rule is still implicitly forced for HTTP
2087      *  redirection (`RewriteRule .. <scheme>://...') then
2088      *  directly force an external HTTP redirect.
2089      */
2090     if (is_absolute_uri(r->filename)) {
2091         if (perdir == NULL) {
2092             rewritelog(r, 2,
2093                        "implicitly forcing redirect (rc=%d) with %s",
2094                        p->forced_responsecode, r->filename);
2095         }
2096         else {
2097             rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2098                        "(rc=%d) with %s", perdir, p->forced_responsecode,
2099                        r->filename);
2100         }
2101         r->status = p->forced_responsecode;
2102         return 1;
2103     }
2104
2105     /*
2106      *  Now we are sure it is not a fully qualified URL.  But
2107      *  there is still one special case left: A local rewrite in
2108      *  per-directory context, i.e. a substitution URL which does
2109      *  not start with a slash. Here we add again the initially
2110      *  stripped per-directory prefix.
2111      */
2112     if (prefixstrip && r->filename[0] != '/') {
2113         rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2114                    perdir, r->filename, perdir, r->filename);
2115         r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
2116     }
2117
2118     /*
2119      *  Finally we had to remember if a MIME-type should be
2120      *  forced for this URL (`RewriteRule .. .. [T=<type>]')
2121      *  Later in the API processing phase this is forced by our
2122      *  MIME API-hook function. This time its no problem even for
2123      *  the per-directory context (where the MIME-type hook was
2124      *  already processed) because a sub-request happens ;-)
2125      */
2126     if (p->forced_mimetype != NULL) {
2127         apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2128                       p->forced_mimetype);
2129         if (perdir == NULL) {
2130             rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2131                        r->filename, p->forced_mimetype);
2132         }
2133         else {
2134             rewritelog(r, 2,
2135                        "[per-dir %s] remember %s to have MIME-type '%s'",
2136                        perdir, r->filename, p->forced_mimetype);
2137         }
2138     }
2139
2140     /*
2141      *  Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2142      *  But now we're done for this particular rule.
2143      */
2144     return 1;
2145 }
2146
2147 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2148                               char *perdir, backrefinfo *briRR,
2149                               backrefinfo *briRC)
2150 {
2151     char input[MAX_STRING_LEN];
2152     apr_finfo_t sb;
2153     request_rec *rsub;
2154     regmatch_t regmatch[MAX_NMATCH];
2155     int rc;
2156
2157     /*
2158      *   Construct the string we match against
2159      */
2160
2161     do_expand(r, p->input, input, sizeof(input), briRR, briRC);
2162
2163     /*
2164      *   Apply the patterns
2165      */
2166
2167     rc = 0;
2168     if (strcmp(p->pattern, "-f") == 0) {
2169         if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2170             if (sb.filetype == APR_REG) {
2171                 rc = 1;
2172             }
2173         }
2174     }
2175     else if (strcmp(p->pattern, "-s") == 0) {
2176         if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2177             if ((sb.filetype == APR_REG) && sb.size > 0) {
2178                 rc = 1;
2179             }
2180         }
2181     }
2182     else if (strcmp(p->pattern, "-l") == 0) {
2183 #if !defined(OS2)
2184         if (apr_lstat(&sb, input, r->pool) == APR_SUCCESS) {
2185             if (sb.filetype == APR_LNK) {
2186                 rc = 1;
2187             }
2188         }
2189 #endif
2190     }
2191     else if (strcmp(p->pattern, "-d") == 0) {
2192         if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2193             if (sb.filetype == APR_DIR) {
2194                 rc = 1;
2195             }
2196         }
2197     }
2198     else if (strcmp(p->pattern, "-U") == 0) {
2199         /* avoid infinite subrequest recursion */
2200         if (strlen(input) > 0 && subreq_ok(r)) {
2201
2202             /* run a URI-based subrequest */
2203             rsub = ap_sub_req_lookup_uri(input, r, NULL);
2204
2205             /* URI exists for any result up to 3xx, redirects allowed */
2206             if (rsub->status < 400)
2207                 rc = 1;
2208
2209             /* log it */
2210             rewritelog(r, 5, "RewriteCond URI (-U) check: "
2211                        "path=%s -> status=%d", input, rsub->status);
2212
2213             /* cleanup by destroying the subrequest */
2214             ap_destroy_sub_req(rsub);
2215         }
2216     }
2217     else if (strcmp(p->pattern, "-F") == 0) {
2218         /* avoid infinite subrequest recursion */
2219         if (strlen(input) > 0 && subreq_ok(r)) {
2220
2221             /* process a file-based subrequest:
2222              * this differs from -U in that no path translation is done.
2223              */
2224             rsub = ap_sub_req_lookup_file(input, r, NULL);
2225
2226             /* file exists for any result up to 2xx, no redirects */
2227             if (rsub->status < 300 &&
2228                 /* double-check that file exists since default result is 200 */
2229                 apr_stat(&sb, rsub->filename, r->pool) == APR_SUCCESS) {
2230                 rc = 1;
2231             }
2232
2233             /* log it */
2234             rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2235                        "-> file=%s status=%d", input, rsub->filename, 
2236                        rsub->status);
2237
2238             /* cleanup by destroying the subrequest */
2239             ap_destroy_sub_req(rsub);
2240         }
2241     }
2242     else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2243         rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2244     }
2245     else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2246         rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2247     }
2248     else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2249         if (strcmp(p->pattern+1, "\"\"") == 0) {
2250             rc = (*input == '\0');
2251         }
2252         else {
2253             rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2254         }
2255     }
2256     else {
2257         /* it is really a regexp pattern, so apply it */
2258         rc = (ap_regexec(p->regexp, input,
2259                          p->regexp->re_nsub+1, regmatch,0) == 0);
2260
2261         /* if it isn't a negated pattern and really matched
2262            we update the passed-through regex subst info structure */
2263         if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2264             briRC->source = apr_pstrdup(r->pool, input);
2265             briRC->nsub   = p->regexp->re_nsub;
2266             memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2267                    sizeof(regmatch));
2268         }
2269     }
2270
2271     /* if this is a non-matching regexp, just negate the result */
2272     if (p->flags & CONDFLAG_NOTMATCH) {
2273         rc = !rc;
2274     }
2275
2276     rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2277                input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2278                p->pattern, rc ? "matched" : "not-matched");
2279
2280     /* end just return the result */
2281     return rc;
2282 }
2283
2284
2285 /*
2286 ** +-------------------------------------------------------+
2287 ** |                                                       |
2288 ** |              URL transformation functions
2289 ** |                                                       |
2290 ** +-------------------------------------------------------+
2291 */
2292
2293
2294 /*
2295 **
2296 **  perform all the expansions on the input string
2297 **  leaving the result in the supplied buffer
2298 **
2299 */
2300
2301 static void do_expand(request_rec *r, char *input, char *buffer, int nbuf,
2302                        backrefinfo *briRR, backrefinfo *briRC)
2303 {
2304     char *inp, *outp;
2305     size_t span, space;
2306
2307     /*
2308      * for security reasons this expansion must be perfomed in a
2309      * single pass, otherwise an attacker can arrange for the result
2310      * of an earlier expansion to include expansion specifiers that
2311      * are interpreted by a later expansion, producing results that
2312      * were not intended by the administrator.
2313      */
2314
2315     inp = input;
2316     outp = buffer;
2317     space = nbuf - 1; /* room for '\0' */
2318
2319     for (;;) {
2320         span = strcspn(inp, "$%");
2321         if (span > space) {
2322             span = space;
2323         }
2324         memcpy(outp, inp, span);
2325         inp += span;
2326         outp += span;
2327         space -= span;
2328         if (space == 0 || *inp == '\0') {
2329             break;
2330         }
2331         /* now we have a '$' or a '%' */
2332         if (inp[1] == '{') {
2333             char *endp;
2334             endp = find_closing_bracket(inp+2, '{', '}');
2335             if (endp == NULL) {
2336                 goto skip;
2337             }
2338             *endp = '\0';
2339             if (inp[0] == '$') {
2340                 /* ${...} map lookup expansion */
2341                 /*
2342                  * To make rewrite maps useful the lookup key and
2343                  * default values must be expanded, so we make
2344                  * recursive calls to do the work. For security
2345                  * reasons we must never expand a string that includes
2346                  * verbatim data from the network. The recursion here
2347                  * isn't a problem because the result of expansion is
2348                  * only passed to lookup_map() so it cannot be
2349                  * re-expanded, only re-looked-up. Another way of
2350                  * looking at it is that the recursion is entirely
2351                  * driven by the syntax of the nested curly brackets.
2352                  */
2353                 char *key, *dflt, *result;
2354                 char xkey[MAX_STRING_LEN];
2355                 char xdflt[MAX_STRING_LEN];
2356                 char *empty = "";
2357                 key = strchr(inp, ':');
2358                 if (key == NULL) {
2359                     *endp = '}';
2360                     goto skip;
2361                 }
2362                 *key++ = '\0';
2363                 dflt = strchr(key, '|');
2364                 if (dflt == NULL) {
2365                     dflt = empty;
2366                 }
2367                 else {
2368                     *dflt++ = '\0';
2369                 }
2370                 do_expand(r, key,  xkey,  sizeof(xkey),  briRR, briRC);
2371                 do_expand(r, dflt, xdflt, sizeof(xdflt), briRR, briRC);
2372                 result = lookup_map(r, inp+2, xkey);
2373                 if (result == NULL) {
2374                     result = xdflt;
2375                 }
2376                 span = apr_cpystrn(outp, result, space) - outp;
2377                 key[-1] = ':';
2378                 if (dflt != empty) {
2379                     dflt[-1] = '|';
2380                 }
2381             }
2382             else if (inp[0] == '%') {
2383                 /* %{...} variable lookup expansion */
2384                 span = apr_cpystrn(outp, lookup_variable(r, inp+2), space) - outp;
2385             }
2386             else {
2387                 span = 0;
2388             }
2389             *endp = '}';
2390             inp = endp+1;
2391             outp += span;
2392             space -= span;
2393             continue;
2394         }
2395         else if (apr_isdigit(inp[1])) {
2396             int n = inp[1] - '0';
2397             backrefinfo *bri = NULL;
2398             if (inp[0] == '$') {
2399                 /* $N RewriteRule regexp backref expansion */
2400                 bri = briRR;
2401             }
2402             else if (inp[0] == '%') {
2403                 /* %N RewriteCond regexp backref expansion */
2404                 bri = briRC;
2405             }
2406             /* see ap_pregsub() in src/main/util.c */
2407             if (bri && n <= bri->nsub &&
2408                 bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2409                 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2410                 if (span > space) {
2411                     span = space;
2412                 }
2413                 memcpy(outp, bri->source + bri->regmatch[n].rm_so, span);
2414                 outp += span;
2415                 space -= span;
2416             }
2417             inp += 2;
2418             continue;
2419         }
2420     skip:
2421         *outp++ = *inp++;
2422         space--;
2423     }
2424     *outp++ = '\0';
2425 }
2426
2427
2428 /*
2429 **
2430 **  perform all the expansions on the environment variables
2431 **
2432 */
2433
2434 static void do_expand_env(request_rec *r, char *env[],
2435                           backrefinfo *briRR, backrefinfo *briRC)
2436 {
2437     int i;
2438     char buf[MAX_STRING_LEN];
2439
2440     for (i = 0; env[i] != NULL; i++) {
2441         do_expand(r, env[i], buf, sizeof(buf), briRR, briRC);
2442         add_env_variable(r, buf);
2443     }
2444 }
2445
2446
2447 /*
2448 **
2449 **  split out a QUERY_STRING part from
2450 **  the current URI string
2451 **
2452 */
2453
2454 static void splitout_queryargs(request_rec *r, int qsappend)
2455 {
2456     char *q;
2457     char *olduri;
2458
2459     q = strchr(r->filename, '?');
2460     if (q != NULL) {
2461         olduri = apr_pstrdup(r->pool, r->filename);
2462         *q++ = '\0';
2463         if (qsappend) {
2464             r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL);
2465         }
2466         else {
2467             r->args = apr_pstrdup(r->pool, q);
2468         }
2469         if (strlen(r->args) == 0) {
2470             r->args = NULL;
2471             rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2472                        r->filename);
2473         }
2474         else {
2475             if (r->args[strlen(r->args)-1] == '&') {
2476                 r->args[strlen(r->args)-1] = '\0';
2477             }
2478             rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2479                        r->filename, r->args);
2480         }
2481     }
2482     return;
2483 }
2484
2485
2486 /*
2487 **
2488 **  strip 'http[s]://ourhost/' from URI
2489 **
2490 */
2491
2492 static void reduce_uri(request_rec *r)
2493 {
2494     char *cp;
2495     unsigned short port;
2496     char *portp;
2497     char *hostp;
2498     char *url;
2499     char c;
2500     char host[LONG_STRING_LEN];
2501     char buf[MAX_STRING_LEN];
2502     char *olduri;
2503     int l;
2504
2505     cp = (char *)ap_http_method(r);
2506     l  = strlen(cp);
2507     if (   strlen(r->filename) > l+3 
2508         && strncasecmp(r->filename, cp, l) == 0
2509         && r->filename[l]   == ':'
2510         && r->filename[l+1] == '/'
2511         && r->filename[l+2] == '/'             ) {
2512         /* there was really a rewrite to a remote path */
2513
2514         olduri = apr_pstrdup(r->pool, r->filename); /* save for logging */
2515
2516         /* cut the hostname and port out of the URI */
2517         apr_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2518         hostp = buf;
2519         for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2520             ;
2521         if (*cp == ':') {
2522             /* set host */
2523             *cp++ = '\0';
2524             apr_cpystrn(host, hostp, sizeof(host));
2525             /* set port */
2526             portp = cp;
2527             for (; *cp != '\0' && *cp != '/'; cp++)
2528                 ;
2529             c = *cp;
2530             *cp = '\0';
2531             port = atoi(portp);
2532             *cp = c;
2533             /* set remaining url */
2534             url = cp;
2535         }
2536         else if (*cp == '/') {
2537             /* set host */
2538             *cp = '\0';
2539             apr_cpystrn(host, hostp, sizeof(host));
2540             *cp = '/';
2541             /* set port */
2542             port = ap_default_port(r);
2543             /* set remaining url */
2544             url = cp;
2545         }
2546         else {
2547             /* set host */
2548             apr_cpystrn(host, hostp, sizeof(host));
2549             /* set port */
2550             port = ap_default_port(r);
2551             /* set remaining url */
2552             url = "/";
2553         }
2554
2555         /* now check whether we could reduce it to a local path... */
2556         if (ap_matches_request_vhost(r, host, port)) {
2557             /* this is our host, so only the URL remains */
2558             r->filename = apr_pstrdup(r->pool, url);
2559             rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2560         }
2561     }
2562     return;
2563 }
2564
2565
2566 /*
2567 **
2568 **  add 'http[s]://ourhost[:ourport]/' to URI
2569 **  if URI is still not fully qualified
2570 **
2571 */
2572
2573 static void fully_qualify_uri(request_rec *r)
2574 {
2575     char buf[32];
2576     const char *thisserver;
2577     char *thisport;
2578     int port;
2579
2580     if (!is_absolute_uri(r->filename)) {
2581
2582         thisserver = ap_get_server_name(r);
2583         port = ap_get_server_port(r);
2584         if (ap_is_default_port(port,r)) {
2585             thisport = "";
2586         }
2587         else {
2588             apr_snprintf(buf, sizeof(buf), ":%u", port);
2589             thisport = buf;
2590         }
2591
2592         if (r->filename[0] == '/') {
2593             r->filename = apr_psprintf(r->pool, "%s://%s%s%s",
2594                                       ap_http_method(r), thisserver,
2595                                       thisport, r->filename);
2596         }
2597         else {
2598             r->filename = apr_psprintf(r->pool, "%s://%s%s/%s",
2599                                       ap_http_method(r), thisserver,
2600                                       thisport, r->filename);
2601         }
2602     }
2603     return;
2604 }
2605
2606
2607 /*
2608 **
2609 **  return non-zero if the URI is absolute (includes a scheme etc.)
2610 **
2611 */
2612
2613 static int is_absolute_uri(char *uri)
2614 {
2615     int i = strlen(uri);
2616     if (   (i > 7 && strncasecmp(uri, "http://",   7) == 0)
2617         || (i > 8 && strncasecmp(uri, "https://",  8) == 0)
2618         || (i > 9 && strncasecmp(uri, "gopher://", 9) == 0)
2619         || (i > 6 && strncasecmp(uri, "ftp://",    6) == 0)
2620         || (i > 5 && strncasecmp(uri, "ldap:",     5) == 0)
2621         || (i > 5 && strncasecmp(uri, "news:",     5) == 0)
2622         || (i > 7 && strncasecmp(uri, "mailto:",   7) == 0) ) {
2623         return 1;
2624     }
2625     else {
2626         return 0;
2627     }
2628 }
2629
2630
2631 /*
2632 **
2633 **  Expand tilde-paths (/~user) through Unix /etc/passwd 
2634 **  database information (or other OS-specific database)
2635 **
2636 */
2637 #if APR_HAS_USER
2638 static char *expand_tildepaths(request_rec *r, char *uri)
2639 {
2640     char user[LONG_STRING_LEN];
2641     char *newuri;
2642     int i, j;
2643     char *homedir;
2644
2645     newuri = uri;
2646     if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2647         /* cut out the username */
2648         for (j = 0, i = 2; j < sizeof(user)-1
2649                && uri[i] != '\0'
2650                && uri[i] != '/'  ; ) {
2651             user[j++] = uri[i++];
2652         }
2653         user[j] = '\0';
2654
2655         /* lookup username in systems passwd file */
2656         if (apr_get_home_directory(&homedir, user, r->pool) == APR_SUCCESS) {
2657             /* ok, user was found, so expand the ~user string */
2658             if (uri[i] != '\0') {
2659                 /* ~user/anything...  has to be expanded */
2660                 if (homedir[strlen(homedir)-1] == '/') {
2661                     homedir[strlen(homedir)-1] = '\0';
2662                 }
2663                 newuri = apr_pstrcat(r->pool, homedir, uri+i, NULL);
2664             }
2665             else {
2666                 /* only ~user has to be expanded */
2667                 newuri = homedir;
2668             }
2669         }
2670     }
2671     return newuri;
2672 }
2673 #endif  /* if APR_HAS_USER */
2674
2675
2676
2677 /*
2678 ** +-------------------------------------------------------+
2679 ** |                                                       |
2680 ** |              DBM hashfile support
2681 ** |                                                       |
2682 ** +-------------------------------------------------------+
2683 */
2684
2685
2686 static char *lookup_map(request_rec *r, char *name, char *key)
2687 {
2688     void *sconf;
2689     rewrite_server_conf *conf;
2690     apr_array_header_t *rewritemaps;
2691     rewritemap_entry *entries;
2692     rewritemap_entry *s;
2693     char *value;
2694     apr_finfo_t st;
2695     apr_status_t rv;
2696     int i;
2697
2698     /* get map configuration */
2699     sconf = r->server->module_config;
2700     conf  = (rewrite_server_conf *)ap_get_module_config(sconf, 
2701                                                         &rewrite_module);
2702     rewritemaps = conf->rewritemaps;
2703
2704     entries = (rewritemap_entry *)rewritemaps->elts;
2705     for (i = 0; i < rewritemaps->nelts; i++) {
2706         s = &entries[i];
2707         if (strcmp(s->name, name) == 0) {
2708             if (s->type == MAPTYPE_TXT) {
2709                 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2710                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2711                                  "mod_rewrite: can't access text RewriteMap "
2712                                  "file %s", s->checkfile);
2713                     rewritelog(r, 1, "can't open RewriteMap file, "
2714                                "see error log");
2715                     return NULL;
2716                 }
2717                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2718                                          st.mtime, key);
2719                 if (value == NULL) {
2720                     rewritelog(r, 6, "cache lookup FAILED, forcing new "
2721                                "map lookup");
2722                     if ((value =
2723                          lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2724                         rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2725                                    "-> val=%s", s->name, key, value);
2726                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2727                                          st.mtime, key, value);
2728                         return value;
2729                     }
2730                     else {
2731                         rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2732                                    "key=%s", s->name, key);
2733                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2734                                          st.mtime, key, "");
2735                         return NULL;
2736                     }
2737                 }
2738                 else {
2739                     rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2740                                "-> val=%s", s->name, key, value);
2741                     return value[0] != '\0' ? value : NULL;
2742                 }
2743             }
2744             else if (s->type == MAPTYPE_DBM) {
2745 #ifndef NO_DBM_REWRITEMAP
2746                 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2747                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2748                                  "mod_rewrite: can't access DBM RewriteMap "
2749                                  "file %s", s->checkfile);
2750                     rewritelog(r, 1, "can't open DBM RewriteMap file, "
2751                                "see error log");
2752                     return NULL;
2753                 }
2754                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2755                                          st.mtime, key);
2756                 if (value == NULL) {
2757                     rewritelog(r, 6,
2758                                "cache lookup FAILED, forcing new map lookup");
2759                     if ((value =
2760                          lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2761                         rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2762                                    "-> val=%s", s->name, key, value);
2763                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2764                                          st.mtime, key, value);
2765                         return value;
2766                     }
2767                     else {
2768                         rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2769                                    "key=%s", s->name, key);
2770                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2771                                          st.mtime, key, "");
2772                         return NULL;
2773                     }
2774                 }
2775                 else {
2776                     rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2777                                "-> val=%s", s->name, key, value);
2778                     return value[0] != '\0' ? value : NULL;
2779                 }
2780 #else
2781                 return NULL;
2782 #endif
2783             }
2784             else if (s->type == MAPTYPE_PRG) {
2785                 if ((value =
2786                      lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2787                     rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2788                                s->name, key, value);
2789                     return value;
2790                 }
2791                 else {
2792                     rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2793                                s->name, key);
2794                 }
2795             }
2796             else if (s->type == MAPTYPE_INT) {
2797                 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2798                     rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2799                                s->name, key, value);
2800                     return value;
2801                 }
2802                 else {
2803                     rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2804                                s->name, key);
2805                 }
2806             }
2807             else if (s->type == MAPTYPE_RND) {
2808                 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2809                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2810                                  "mod_rewrite: can't access text RewriteMap "
2811                                  "file %s", s->checkfile);
2812                     rewritelog(r, 1, "can't open RewriteMap file, "
2813                                "see error log");
2814                     return NULL;
2815                 }
2816                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2817                                          st.mtime, key);
2818                 if (value == NULL) {
2819                     rewritelog(r, 6, "cache lookup FAILED, forcing new "
2820                                "map lookup");
2821                     if ((value =
2822                          lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2823                         rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2824                                    "-> val=%s", s->name, key, value);
2825                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2826                                          st.mtime, key, value);
2827                     }
2828                     else {
2829                         rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2830                                    "key=%s", s->name, key);
2831                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2832                                          st.mtime, key, "");
2833                         return NULL;
2834                     }
2835                 }
2836                 else {
2837                     rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2838                                "-> val=%s", s->name, key, value);
2839                 }
2840                 if (value[0] != '\0') {
2841                    value = select_random_value_part(r, value);
2842                    rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2843                 }
2844                 else {
2845                     value = NULL;
2846                 }
2847                 return value;
2848             }
2849         }
2850     }
2851     return NULL;
2852 }
2853
2854 static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
2855 {
2856     apr_file_t *fp = NULL;
2857     apr_status_t rc;
2858     char line[1024];
2859     char *value = NULL;
2860     char *cpT;
2861     size_t skip;
2862     char *curkey;
2863     char *curval;
2864
2865     rc = apr_open(&fp, file, APR_READ, APR_OS_DEFAULT, r->pool);
2866     if (rc != APR_SUCCESS) {
2867        return NULL;
2868     }
2869
2870     while (apr_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2871         if (line[0] == '#')
2872             continue; /* ignore comments */
2873         cpT = line;
2874         curkey = cpT;
2875         skip = strcspn(cpT," \t\r\n");
2876         if (skip == 0)
2877             continue; /* ignore lines that start with a space, tab, CR, or LF */
2878         cpT += skip;
2879         *cpT = '\0';
2880         if (strcmp(curkey, key) != 0)
2881             continue; /* key does not match... */
2882             
2883         /* found a matching key; now extract and return the value */
2884         ++cpT;
2885         skip = strspn(cpT, " \t\r\n");
2886         cpT += skip;
2887         curval = cpT;
2888         skip = strcspn(cpT, " \t\r\n");
2889         if (skip == 0)
2890             continue; /* no value... */
2891         cpT += skip;
2892         *cpT = '\0';
2893         value = apr_pstrdup(r->pool, curval);
2894         break;
2895     }
2896     apr_close(fp);
2897     return value;
2898 }
2899
2900 #ifndef NO_DBM_REWRITEMAP
2901 static char *lookup_map_dbmfile(request_rec *r, const char *file, char *key)
2902 {
2903     DBM *dbmfp = NULL;
2904     datum dbmkey;
2905     datum dbmval;
2906     char *value = NULL;
2907     char buf[MAX_STRING_LEN];
2908
2909     dbmkey.dptr  = key;
2910     dbmkey.dsize = strlen(key);
2911     if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2912         dbmval = dbm_fetch(dbmfp, dbmkey);
2913         if (dbmval.dptr != NULL) {
2914             memcpy(buf, dbmval.dptr, 
2915                    dbmval.dsize < sizeof(buf)-1 ? 
2916                    dbmval.dsize : sizeof(buf)-1  );
2917             buf[dbmval.dsize] = '\0';
2918             value = apr_pstrdup(r->pool, buf);
2919         }
2920         dbm_close(dbmfp);
2921     }
2922     return value;
2923 }
2924 #endif
2925
2926 static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
2927                                 apr_file_t *fpout, char *key)
2928 {
2929     char buf[LONG_STRING_LEN];
2930     char c;
2931     int i;
2932     apr_size_t nbytes;
2933
2934 #ifndef NO_WRITEV
2935     struct iovec iova[2];
2936     apr_size_t niov;
2937 #endif
2938
2939     /* when `RewriteEngine off' was used in the per-server
2940      * context then the rewritemap-programs were not spawned.
2941      * In this case using such a map (usually in per-dir context)
2942      * is useless because it is not available.
2943      */
2944     if (fpin == NULL || fpout == NULL) {
2945         return NULL;
2946     }
2947
2948     /* take the lock */
2949
2950     if (rewrite_mapr_lock) {
2951         apr_lock(rewrite_mapr_lock);
2952     }
2953
2954     /* write out the request key */
2955 #ifdef NO_WRITEV
2956     nbytes = strlen(key);
2957     apr_write(fpin, key, &nbytes);
2958     nbytes = 1;
2959     apr_write(fpin, "\n", &nbytes);
2960 #else
2961     iova[0].iov_base = key;
2962     iova[0].iov_len = strlen(key);
2963     iova[1].iov_base = "\n";
2964     iova[1].iov_len = 1;
2965
2966     niov = 2;
2967     apr_writev(fpin, iova, niov, &nbytes);
2968 #endif
2969
2970     /* read in the response value */
2971     i = 0;
2972     nbytes = 1;
2973     apr_read(fpout, &c, &nbytes);
2974     while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
2975         if (c == '\n') {
2976             break;
2977         }
2978         buf[i++] = c;
2979
2980         apr_read(fpout, &c, &nbytes);
2981     }
2982     buf[i] = '\0';
2983
2984     /* give the lock back */
2985     if (rewrite_mapr_lock) {
2986         apr_unlock(rewrite_mapr_lock);
2987     }
2988
2989     if (strcasecmp(buf, "NULL") == 0) {
2990         return NULL;
2991     }
2992     else {
2993         return apr_pstrdup(r->pool, buf);
2994     }
2995 }
2996
2997 static char *lookup_map_internal(request_rec *r,
2998                                  char *(*func)(request_rec *, char *),
2999                                  char *key)
3000 {
3001     /* currently we just let the function convert
3002        the key to a corresponding value */
3003     return func(r, key);
3004 }
3005
3006 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
3007 {
3008     char *value, *cp;
3009
3010     for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3011          cp++) {
3012         *cp = apr_toupper(*cp);
3013     }
3014     return value;
3015 }
3016
3017 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
3018 {
3019     char *value, *cp;
3020
3021     for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3022          cp++) {
3023         *cp = apr_tolower(*cp);
3024     }
3025     return value;
3026 }
3027
3028 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
3029 {
3030     char *value;
3031
3032     value = ap_escape_uri(r->pool, key);
3033     return value;
3034 }
3035
3036 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
3037 {
3038     char *value;
3039
3040     value = apr_pstrdup(r->pool, key);
3041     ap_unescape_url(value);
3042     return value;
3043 }
3044
3045 static int rewrite_rand_init_done = 0;
3046
3047 static void rewrite_rand_init(void)
3048 {
3049     if (!rewrite_rand_init_done) {
3050         srand((unsigned)(getpid()));
3051         rewrite_rand_init_done = 1;
3052     }
3053     return;
3054 }
3055
3056 static int rewrite_rand(int l, int h)
3057 {
3058     rewrite_rand_init();
3059
3060     /* Get [0,1) and then scale to the appropriate range. Note that using
3061      * a floating point value ensures that we use all bits of the rand()
3062      * result. Doing an integer modulus would only use the lower-order bits
3063      * which may not be as uniformly random.
3064      */
3065     return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3066 }
3067
3068 static char *select_random_value_part(request_rec *r, char *value)
3069 {
3070     char *buf;
3071     int n, i, k;
3072
3073     /*  count number of distinct values  */
3074     for (n = 1, i = 0; value[i] != '\0'; i++) {
3075         if (value[i] == '|') {
3076             n++;
3077         }
3078     }
3079
3080     /*  when only one value we have no option to choose  */
3081     if (n == 1) {
3082         return value;
3083     }
3084
3085     /*  else randomly select one  */
3086     k = rewrite_rand(1, n);
3087
3088     /*  and grep it out  */
3089     for (n = 1, i = 0; value[i] != '\0'; i++) {
3090         if (n == k) {
3091             break;
3092         }
3093         if (value[i] == '|') {
3094             n++;
3095         }
3096     }
3097     buf = apr_pstrdup(r->pool, &value[i]);
3098     for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3099         ;
3100     buf[i] = '\0';
3101     return buf;
3102 }
3103
3104
3105 /*
3106 ** +-------------------------------------------------------+
3107 ** |                                                       |
3108 ** |              rewriting logfile support
3109 ** |                                                       |
3110 ** +-------------------------------------------------------+
3111 */
3112
3113
3114 static void open_rewritelog(server_rec *s, apr_pool_t *p)
3115 {
3116     rewrite_server_conf *conf;
3117     const char *fname;
3118     apr_status_t rc;
3119     piped_log *pl;
3120     int    rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3121     mode_t rewritelog_mode  = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3122
3123     conf = ap_get_module_config(s->module_config, &rewrite_module);
3124
3125     if (conf->rewritelogfile == NULL) {
3126         return;
3127     }
3128     if (*(conf->rewritelogfile) == '\0') {
3129         return;
3130     }
3131     if (conf->rewritelogfp != NULL) {
3132         return; /* virtual log shared w/ main server */
3133     }
3134
3135     fname = ap_server_root_relative(p, conf->rewritelogfile);
3136
3137     if (*conf->rewritelogfile == '|') {
3138         if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3139             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s, 
3140                          "mod_rewrite: could not open reliable pipe "
3141                          "to RewriteLog filter %s", conf->rewritelogfile+1);
3142             exit(1);
3143         }
3144         conf->rewritelogfp = ap_piped_log_write_fd(pl);
3145     }
3146     else if (*conf->rewritelogfile != '\0') {
3147         rc = apr_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3148         if (rc != APR_SUCCESS)  {
3149             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, 
3150                          "mod_rewrite: could not open RewriteLog "
3151                          "file %s", fname);
3152             exit(1);
3153         }
3154     }
3155     return;
3156 }
3157
3158 static void rewritelog(request_rec *r, int level, const char *text, ...)
3159 {
3160     rewrite_server_conf *conf;
3161     conn_rec *conn;
3162     char *str1;
3163     char str2[512];
3164     char str3[1024];
3165     char type[20];
3166     char redir[20];
3167     va_list ap;
3168     int i;
3169     apr_size_t nbytes;
3170     request_rec *req;
3171     char *ruser;
3172     const char *rhost;
3173
3174     va_start(ap, text);
3175     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3176     conn = r->connection;
3177
3178     if (conf->rewritelogfp == NULL) {
3179         return;
3180     }
3181     if (conf->rewritelogfile == NULL) {
3182         return;
3183     }
3184     if (*(conf->rewritelogfile) == '\0') {
3185         return;
3186     }
3187
3188     if (level > conf->rewriteloglevel) {
3189         return;
3190     }
3191
3192     if (r->user == NULL) {
3193         ruser = "-";
3194     }
3195     else if (strlen(r->user) != 0) {
3196         ruser = r->user;
3197     }
3198     else {
3199         ruser = "\"\"";
3200     }
3201
3202     rhost = ap_get_remote_host(conn, r->server->module_config, 
3203                                REMOTE_NOLOOKUP);
3204     if (rhost == NULL) {
3205         rhost = "UNKNOWN-HOST";
3206     }
3207
3208     str1 = apr_pstrcat(r->pool, rhost, " ",
3209                       (conn->remote_logname != NULL ?
3210                       conn->remote_logname : "-"), " ",
3211                       ruser, NULL);
3212     apr_vsnprintf(str2, sizeof(str2), text, ap);
3213
3214     if (r->main == NULL) {
3215         strcpy(type, "initial");
3216     }
3217     else {
3218         strcpy(type, "subreq");
3219     }
3220
3221     for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3222         i++;
3223     }
3224     if (i == 0) {
3225         redir[0] = '\0';
3226     }
3227     else {
3228         apr_snprintf(redir, sizeof(redir), "/redir#%d", i);
3229     }
3230
3231     apr_snprintf(str3, sizeof(str3),
3232                 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3233                 current_logtime(r), ap_get_server_name(r),
3234                 (unsigned long)(r->server), (unsigned long)r,
3235                 type, redir, level, str2);
3236
3237     apr_lock(rewrite_log_lock);
3238     nbytes = strlen(str3);
3239     apr_write(conf->rewritelogfp, str3, &nbytes);
3240     apr_unlock(rewrite_log_lock);
3241
3242     va_end(ap);
3243     return;
3244 }
3245
3246 static char *current_logtime(request_rec *r)
3247 {
3248     apr_exploded_time_t t;
3249     char tstr[80];
3250     apr_size_t len;
3251
3252     apr_explode_localtime(&t, apr_now());
3253
3254     apr_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3255     apr_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3256                 t.tm_gmtoff < 0 ? '-' : '+',
3257                 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3258     return apr_pstrdup(r->pool, tstr);
3259 }
3260
3261
3262
3263
3264 /*
3265 ** +-------------------------------------------------------+
3266 ** |                                                       |
3267 ** |              rewriting lockfile support
3268 ** |                                                       |
3269 ** +-------------------------------------------------------+
3270 */
3271
3272 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3273
3274 static void rewritelock_create(server_rec *s, apr_pool_t *p)
3275 {
3276     apr_status_t rc;
3277
3278     /* only operate if a lockfile is used */
3279     if (lockname == NULL || *(lockname) == '\0') {
3280         return;
3281     }
3282
3283     /* fixup the path, especially for rewritelock_remove() */
3284     lockname = ap_server_root_relative(p, lockname);
3285
3286     /* create the lockfile */
3287     rc = apr_create_lock (&rewrite_mapr_lock, APR_MUTEX, APR_LOCKALL, lockname, p);
3288     if (rc != APR_SUCCESS) {
3289         ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3290                      "mod_rewrite: Parent could not create RewriteLock "
3291                      "file %s", lockname);
3292         exit(1);
3293     }
3294
3295     return;
3296 }
3297
3298 static apr_status_t rewritelock_remove(void *data)
3299 {
3300     /* only operate if a lockfile is used */
3301     if (lockname == NULL || *(lockname) == '\0') {
3302         return APR_SUCCESS;
3303     }
3304
3305     /* destroy the rewritelock */
3306     apr_destroy_lock (rewrite_mapr_lock);
3307     rewrite_mapr_lock = NULL;
3308     lockname = NULL;
3309     return(0);
3310 }
3311
3312
3313 /*
3314 ** +-------------------------------------------------------+
3315 ** |                                                       |
3316 ** |                  program map support
3317 ** |                                                       |
3318 ** +-------------------------------------------------------+
3319 */
3320
3321 static void run_rewritemap_programs(server_rec *s, apr_pool_t *p)
3322 {
3323     rewrite_server_conf *conf;
3324     apr_file_t *fpin = NULL;
3325     apr_file_t *fpout = NULL;
3326     apr_file_t *fperr = NULL;
3327     apr_array_header_t *rewritemaps;
3328     rewritemap_entry *entries;
3329     rewritemap_entry *map;
3330     int i;
3331     apr_status_t rc;
3332
3333     conf = ap_get_module_config(s->module_config, &rewrite_module);
3334
3335     /*  If the engine isn't turned on,
3336      *  don't even try to do anything.
3337      */
3338     if (conf->state == ENGINE_DISABLED) {
3339         return;
3340     }
3341
3342     rewritemaps = conf->rewritemaps;
3343     entries = (rewritemap_entry *)rewritemaps->elts;
3344     for (i = 0; i < rewritemaps->nelts; i++) {
3345         map = &entries[i];
3346         if (map->type != MAPTYPE_PRG) {
3347             continue;
3348         }
3349         if (map->datafile == NULL
3350             || *(map->datafile) == '\0'
3351             || map->fpin  != NULL
3352             || map->fpout != NULL        ) {
3353             continue;
3354         }
3355         fpin  = NULL;
3356         fpout = NULL;
3357         rc = rewritemap_program_child(p, map->datafile,
3358                                      &fpout, &fpin, &fperr);
3359         if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3360             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3361                          "mod_rewrite: could not fork child for "
3362                          "RewriteMap process");
3363             exit(1);
3364         }
3365         map->fpin  = fpin;
3366         map->fpout = fpout;
3367         map->fperr = fperr;
3368     }
3369     return;
3370 }
3371
3372 /* child process code */
3373 static apr_status_t rewritemap_program_child(apr_pool_t *p, const char *progname,
3374                                              apr_file_t **fpout, apr_file_t **fpin,
3375                                              apr_file_t **fperr)
3376 {
3377     apr_status_t rc;
3378     apr_procattr_t *procattr;
3379     apr_proc_t *procnew;
3380
3381 #ifdef SIGHUP
3382     apr_signal(SIGHUP, SIG_IGN);
3383 #endif
3384
3385     
3386     if (((rc = apr_createprocattr_init(&procattr, p)) != APR_SUCCESS) ||
3387         ((rc = apr_setprocattr_io(procattr, APR_FULL_BLOCK,
3388                                   APR_FULL_NONBLOCK,
3389                                   APR_FULL_NONBLOCK)) != APR_SUCCESS) ||
3390         ((rc = apr_setprocattr_dir(procattr, 
3391                                    ap_make_dirstr_parent(p, progname)))
3392          != APR_SUCCESS) ||
3393         ((rc = apr_setprocattr_cmdtype(procattr, APR_PROGRAM)) != APR_SUCCESS)) {
3394         /* Something bad happened, give up and go away. */
3395     }
3396     else {
3397         procnew = apr_pcalloc(p, sizeof(*procnew));
3398         rc = apr_create_process(procnew, progname, NULL, NULL, procattr, p);
3399     
3400         if (rc == APR_SUCCESS) {
3401             apr_note_subprocess(p, procnew, kill_after_timeout);
3402
3403             if (fpin) {
3404                 (*fpin) = procnew->in;
3405             }
3406
3407             if (fpout) {
3408                 (*fpout) = procnew->out;
3409             }
3410
3411             if (fperr) {
3412                 (*fperr) = procnew->err;
3413             }
3414         }
3415     }
3416
3417     return (rc);
3418 }
3419
3420
3421
3422
3423 /*
3424 ** +-------------------------------------------------------+
3425 ** |                                                       |
3426 ** |             environment variable support
3427 ** |                                                       |
3428 ** +-------------------------------------------------------+
3429 */
3430
3431
3432 static char *lookup_variable(request_rec *r, char *var)
3433 {
3434     const char *result;
3435     char resultbuf[LONG_STRING_LEN];
3436     apr_exploded_time_t tm;
3437     request_rec *rsub;
3438 #ifndef WIN32
3439     struct passwd *pw;
3440     struct group *gr;
3441     apr_finfo_t finfo;
3442 #endif
3443
3444     result = NULL;
3445
3446     /* HTTP headers */
3447     if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3448         result = lookup_header(r, "User-Agent");
3449     }
3450     else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3451         result = lookup_header(r, "Referer");
3452     }
3453     else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3454         result = lookup_header(r, "Cookie");
3455     }
3456     else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3457         result = lookup_header(r, "Forwarded");
3458     }
3459     else if (strcasecmp(var, "HTTP_HOST") == 0) {
3460         result = lookup_header(r, "Host");
3461     }
3462     else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3463         result = lookup_header(r, "Proxy-Connection");
3464     }
3465     else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3466         result = lookup_header(r, "Accept");
3467     }
3468     /* all other headers from which we are still not know about */
3469     else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3470         result = lookup_header(r, var+5);
3471     }
3472
3473     /* connection stuff */
3474     else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3475         result = r->connection->remote_ip;
3476     }
3477     else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3478         result = (char *)ap_get_remote_host(r->connection,
3479                                          r->per_dir_config, REMOTE_NAME);
3480     }
3481     else if (strcasecmp(var, "REMOTE_USER") == 0) {
3482         result = r->user;
3483     }
3484     else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3485         result = (char *)ap_get_remote_logname(r);
3486     }
3487
3488     /* request stuff */
3489     else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3490         result = r->the_request;
3491     }
3492     else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3493         result = r->method;
3494     }
3495     else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3496         result = r->uri;
3497     }
3498     else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3499              strcasecmp(var, "REQUEST_FILENAME") == 0  ) {
3500         result = r->filename;
3501     }
3502     else if (strcasecmp(var, "PATH_INFO") == 0) {
3503         result = r->path_info;
3504     }
3505     else if (strcasecmp(var, "QUERY_STRING") == 0) {
3506         result = r->args;
3507     }
3508     else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3509         result = r->ap_auth_type;
3510     }
3511     else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3512         result = (r->main != NULL ? "true" : "false");
3513     }
3514
3515     /* internal server stuff */
3516     else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3517         result = ap_document_root(r);
3518     }
3519     else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3520         result = r->server->server_admin;
3521     }
3522     else if (strcasecmp(var, "SERVER_NAME") == 0) {
3523         result = ap_get_server_name(r);
3524     }
3525     else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3526         result = r->connection->local_ip;
3527     }
3528     else if (strcasecmp(var, "SERVER_PORT") == 0) {
3529         apr_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3530         result = resultbuf;
3531     }
3532     else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3533         result = r->protocol;
3534     }
3535     else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3536         result = ap_get_server_version();
3537     }
3538     else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3539         apr_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3540                     MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3541         result = resultbuf;
3542     }
3543
3544 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3545     /* underlaying Unix system stuff */
3546     else if (strcasecmp(var, "TIME_YEAR") == 0) {
3547         apr_explode_localtime(&tm, apr_now());
3548         apr_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3549         result = resultbuf;
3550     }
3551 #define MKTIMESTR(format, tmfield) \
3552     apr_explode_localtime(&tm, apr_now()); \
3553     apr_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3554     result = resultbuf;
3555     else if (strcasecmp(var, "TIME_MON") == 0) {
3556         MKTIMESTR("%02d", tm_mon+1)
3557     }
3558     else if (strcasecmp(var, "TIME_DAY") == 0) {
3559         MKTIMESTR("%02d", tm_mday)
3560     }
3561     else if (strcasecmp(var, "TIME_HOUR") == 0) {
3562         MKTIMESTR("%02d", tm_hour)
3563     }
3564     else if (strcasecmp(var, "TIME_MIN") == 0) {
3565         MKTIMESTR("%02d", tm_min)
3566     }
3567     else if (strcasecmp(var, "TIME_SEC") == 0) {
3568         MKTIMESTR("%02d", tm_sec)
3569     }
3570     else if (strcasecmp(var, "TIME_WDAY") == 0) {
3571         MKTIMESTR("%d", tm_wday)
3572     }
3573     else if (strcasecmp(var, "TIME") == 0) {
3574         apr_explode_localtime(&tm, apr_now());
3575         apr_snprintf(resultbuf, sizeof(resultbuf),
3576                     "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3577                     tm.tm_mon+1, tm.tm_mday,
3578                     tm.tm_hour, tm.tm_min, tm.tm_sec);
3579         result = resultbuf;
3580         rewritelog(r, 1, "RESULT='%s'", result);
3581     }
3582
3583     /* all other env-variables from the parent Apache process */
3584     else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3585         /* first try the internal Apache notes structure */
3586         result = apr_table_get(r->notes, var+4);
3587         /* second try the internal Apache env structure  */
3588         if (result == NULL) {
3589             result = apr_table_get(r->subprocess_env, var+4);
3590         }
3591         /* third try the external OS env */
3592         if (result == NULL) {
3593             result = getenv(var+4);
3594         }
3595     }
3596
3597 #define LOOKAHEAD(subrecfunc) \
3598         if ( \
3599           /* filename is safe to use */ \
3600           r->filename != NULL \
3601               /* - and we're either not in a subrequest */ \
3602               && ( r->main == NULL \
3603                   /* - or in a subrequest where paths are non-NULL... */ \
3604                     || ( r->main->uri != NULL && r->uri != NULL \
3605                         /*   ...and sub and main paths differ */ \
3606                         && strcmp(r->main->uri, r->uri) != 0))) { \
3607             /* process a file-based subrequest */ \
3608             rsub = subrecfunc(r->filename, r, NULL); \
3609             /* now recursively lookup the variable in the sub_req */ \
3610             result = lookup_variable(rsub, var+5); \
3611             /* copy it up to our scope before we destroy sub_req's apr_pool_t */ \
3612             result = apr_pstrdup(r->pool, result); \
3613             /* cleanup by destroying the subrequest */ \
3614             ap_destroy_sub_req(rsub); \
3615             /* log it */ \
3616             rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3617                        r->filename, var+5, result); \
3618             /* return ourself to prevent re-pstrdup */ \
3619             return (char *)result; \
3620         }
3621
3622     /* look-ahead for parameter through URI-based sub-request */
3623     else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3624         LOOKAHEAD(ap_sub_req_lookup_uri)
3625     }
3626     /* look-ahead for parameter through file-based sub-request */
3627     else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3628         LOOKAHEAD(ap_sub_req_lookup_file)
3629     }
3630
3631 #if !defined(WIN32) && !defined(NETWARE)
3632     /* Win32 has a rather different view of file ownerships.
3633        For now, just forget it */
3634
3635     /* file stuff */
3636     else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3637         result = "<unknown>";
3638         if (r->finfo.protection != 0) {
3639             if ((pw = getpwuid(r->finfo.user)) != NULL) {
3640                 result = pw->pw_name;
3641             }
3642         }
3643         else {
3644             if (apr_stat(&finfo, r->filename, r->pool) == APR_SUCCESS) {
3645                 if ((pw = getpwuid(finfo.user)) != NULL) {
3646                     result = pw->pw_name;
3647                 }
3648             }
3649         }
3650     }
3651     else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3652         result = "<unknown>";
3653         if (r->finfo.protection != 0) {
3654             if ((gr = getgrgid(r->finfo.group)) != NULL) {
3655                 result = gr->gr_name;
3656             }
3657         }
3658         else {
3659             if (apr_stat(&finfo, r->filename, r->pool) == 0) {
3660                 if ((gr = getgrgid(finfo.group)) != NULL) {
3661                     result = gr->gr_name;
3662                 }
3663             }
3664         }
3665     }
3666 #endif /* ndef WIN32 && NETWARE*/
3667
3668     if (result == NULL) {
3669         return apr_pstrdup(r->pool, "");
3670     }
3671     else {
3672         return apr_pstrdup(r->pool, result);
3673     }
3674 }
3675
3676 static char *lookup_header(request_rec *r, const char *name)
3677 {
3678     apr_array_header_t *hdrs_arr;
3679     apr_table_entry_t *hdrs;
3680     int i;
3681
3682     hdrs_arr = apr_table_elts(r->headers_in);
3683     hdrs = (apr_table_entry_t *)hdrs_arr->elts;
3684     for (i = 0; i < hdrs_arr->nelts; ++i) {
3685         if (hdrs[i].key == NULL) {
3686             continue;
3687         }
3688         if (strcasecmp(hdrs[i].key, name) == 0) {
3689             apr_table_merge(r->notes, VARY_KEY_THIS, name);
3690             return hdrs[i].val;
3691         }
3692     }
3693     return NULL;
3694 }
3695
3696
3697
3698
3699 /*
3700 ** +-------------------------------------------------------+
3701 ** |                                                       |
3702 ** |                    caching support
3703 ** |                                                       |
3704 ** +-------------------------------------------------------+
3705 */
3706
3707
3708 static cache *init_cache(apr_pool_t *p)
3709 {
3710     cache *c;
3711
3712     c = (cache *)apr_palloc(p, sizeof(cache));
3713     if (apr_create_pool(&c->pool, p) != APR_SUCCESS)
3714                 return NULL;
3715     c->lists = apr_make_array(c->pool, 2, sizeof(cachelist));
3716     return c;
3717 }
3718
3719 static void set_cache_string(cache *c, const char *res, int mode, time_t t,
3720                              char *key, char *value)
3721 {
3722     cacheentry ce;
3723
3724     ce.time  = t;
3725     ce.key   = key;
3726     ce.value = value;
3727     store_cache_string(c, res, &ce);
3728     return;
3729 }
3730
3731 static char *get_cache_string(cache *c, const char *res, int mode,
3732                               time_t t, char *key)
3733 {
3734     cacheentry *ce;
3735
3736     ce = retrieve_cache_string(c, res, key);
3737     if (ce == NULL) {
3738         return NULL;
3739     }
3740     if (mode & CACHEMODE_TS) {
3741         if (t != ce->time) {
3742             return NULL;
3743         }
3744     }
3745     else if (mode & CACHEMODE_TTL) {
3746         if (t > ce->time) {
3747             return NULL;
3748         }
3749     }
3750     return apr_pstrdup(c->pool, ce->value);
3751 }
3752
3753 static int cache_tlb_hash(char *key)
3754 {
3755     unsigned long n;
3756     char *p;
3757
3758     n = 0;
3759     for (p = key; *p != '\0'; p++) {
3760         n = ((n << 5) + n) ^ (unsigned long)(*p++);
3761     }
3762
3763     return n % CACHE_TLB_ROWS;
3764 }
3765
3766 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3767                                     char *key)
3768 {
3769     int ix = cache_tlb_hash(key);
3770     int i;
3771     int j;
3772
3773     for (i=0; i < CACHE_TLB_COLS; ++i) {
3774         j = tlb[ix].t[i];
3775         if (j < 0)
3776             return NULL;
3777         if (strcmp(elt[j].key, key) == 0)
3778             return &elt[j];
3779     }
3780     return NULL;
3781 }
3782
3783 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3784                               cacheentry *e)
3785 {
3786     int ix = cache_tlb_hash(e->key);
3787     int i;
3788
3789     tlb = &tlb[ix];
3790
3791     for (i=1; i < CACHE_TLB_COLS; ++i)
3792         tlb->t[i] = tlb->t[i-1];
3793
3794     tlb->t[0] = e - elt;
3795 }
3796
3797 static void store_cache_string(cache *c, const char *res, cacheentry *ce)
3798 {
3799     int i;
3800     int j;
3801     cachelist *l;
3802     cacheentry *e;
3803     cachetlbentry *t;
3804     int found_list;
3805
3806     found_list = 0;
3807     /* first try to edit an existing entry */
3808     for (i = 0; i < c->lists->nelts; i++) {
3809         l = &(((cachelist *)c->lists->elts)[i]);
3810         if (strcmp(l->resource, res) == 0) {
3811             found_list = 1;
3812
3813             e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3814                                  (cacheentry *)l->entries->elts, ce->key);
3815             if (e != NULL) {
3816                 e->time  = ce->time;
3817                 e->value = apr_pstrdup(c->pool, ce->value);
3818                 return;
3819             }
3820
3821             for (j = 0; j < l->entries->nelts; j++) {
3822                 e = &(((cacheentry *)l->entries->elts)[j]);
3823                 if (strcmp(e->key, ce->key) == 0) {
3824                     e->time  = ce->time;
3825                     e->value = apr_pstrdup(c->pool, ce->value);
3826                   cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3827                                     (cacheentry *)l->entries->elts, e);
3828                     return;
3829                 }
3830             }
3831         }
3832     }
3833
3834     /* create a needed new list */
3835     if (!found_list) {
3836         l = apr_push_array(c->lists);
3837         l->resource = apr_pstrdup(c->pool, res);
3838         l->entries  = apr_make_array(c->pool, 2, sizeof(cacheentry));
3839         l->tlb      = apr_make_array(c->pool, CACHE_TLB_ROWS,
3840                                     sizeof(cachetlbentry));
3841         for (i=0; i<CACHE_TLB_ROWS; ++i) {
3842             t = &((cachetlbentry *)l->tlb->elts)[i];
3843                 for (j=0; j<CACHE_TLB_COLS; ++j)
3844                     t->t[j] = -1;
3845         }
3846     }
3847
3848     /* create the new entry */
3849     for (i = 0; i < c->lists->nelts; i++) {
3850         l = &(((cachelist *)c->lists->elts)[i]);
3851         if (strcmp(l->resource, res) == 0) {
3852             e = apr_push_array(l->entries);
3853             e->time  = ce->time;
3854             e->key   = apr_pstrdup(c->pool, ce->key);
3855             e->value = apr_pstrdup(c->pool, ce->value);
3856             cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3857                               (cacheentry *)l->entries->elts, e);
3858             return;
3859         }
3860     }
3861
3862     /* not reached, but when it is no problem... */
3863     return;
3864 }
3865
3866 static cacheentry *retrieve_cache_string(cache *c, const char *res, char *key)
3867 {
3868     int i;
3869     int j;
3870     cachelist *l;
3871     cacheentry *e;
3872
3873     for (i = 0; i < c->lists->nelts; i++) {
3874         l = &(((cachelist *)c->lists->elts)[i]);
3875         if (strcmp(l->resource, res) == 0) {
3876
3877             e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3878                                  (cacheentry *)l->entries->elts, key);
3879             if (e != NULL)
3880                 return e;
3881
3882             for (j = 0; j < l->entries->nelts; j++) {
3883                 e = &(((cacheentry *)l->entries->elts)[j]);
3884                 if (strcmp(e->key, key) == 0) {
3885                     return e;
3886                 }
3887             }
3888         }
3889     }
3890     return NULL;
3891 }
3892
3893
3894
3895
3896 /*
3897 ** +-------------------------------------------------------+
3898 ** |                                                       |
3899 ** |                    misc functions
3900 ** |                                                       |
3901 ** +-------------------------------------------------------+
3902 */
3903
3904 static char *subst_prefix_path(request_rec *r, char *input, char *match,
3905                                const char *subst)
3906 {
3907     char matchbuf[LONG_STRING_LEN];
3908     char substbuf[LONG_STRING_LEN];
3909     char *output;
3910     int l;
3911
3912     output = input;
3913
3914     /* first create a match string which always has a trailing slash */
3915     l = apr_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
3916     if (matchbuf[l-1] != '/') {
3917        matchbuf[l] = '/';
3918        matchbuf[l+1] = '\0';
3919        l++;
3920     }
3921     /* now compare the prefix */
3922     if (strncmp(input, matchbuf, l) == 0) {
3923         rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
3924         output = apr_pstrdup(r->pool, output+l);
3925
3926         /* and now add the base-URL as replacement prefix */
3927         l = apr_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
3928         if (substbuf[l-1] != '/') {
3929            substbuf[l] = '/';
3930            substbuf[l+1] = '\0';
3931            l++;
3932         }
3933         if (output[0] == '/') {
3934             rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3935                        output, substbuf, output+1);
3936             output = apr_pstrcat(r->pool, substbuf, output+1, NULL);
3937         }
3938         else {
3939             rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3940                        output, substbuf, output);
3941             output = apr_pstrcat(r->pool, substbuf, output, NULL);
3942         }
3943     }
3944     return output;
3945 }
3946
3947
3948 /*
3949 **
3950 **  own command line parser which don't have the '\\' problem
3951 **
3952 */
3953
3954 static int parseargline(char *str, char **a1, char **a2, char **a3)
3955 {
3956     char *cp;
3957     int isquoted;
3958
3959 #define SKIP_WHITESPACE(cp) \
3960     for ( ; *cp == ' ' || *cp == '\t'; ) { \
3961         cp++; \
3962     };
3963
3964 #define CHECK_QUOTATION(cp,isquoted) \
3965     isquoted = 0; \
3966     if (*cp == '"') { \
3967         isquoted = 1; \
3968         cp++; \
3969     }
3970
3971 #define DETERMINE_NEXTSTRING(cp,isquoted) \
3972     for ( ; *cp != '\0'; cp++) { \
3973         if (   (isquoted    && (*cp     == ' ' || *cp     == '\t')) \
3974             || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
3975             cp++; \
3976             continue; \
3977         } \
3978         if (   (!isquoted && (*cp == ' ' || *cp == '\t')) \
3979             || (isquoted  && *cp == '"')                  ) { \
3980             break; \
3981         } \
3982     }
3983
3984     cp = str;
3985     SKIP_WHITESPACE(cp);
3986
3987     /*  determine first argument */
3988     CHECK_QUOTATION(cp, isquoted);
3989     *a1 = cp;
3990     DETERMINE_NEXTSTRING(cp, isquoted);
3991     if (*cp == '\0') {
3992         return 1;
3993     }
3994     *cp++ = '\0';
3995
3996     SKIP_WHITESPACE(cp);
3997
3998     /*  determine second argument */
3999     CHECK_QUOTATION(cp, isquoted);
4000     *a2 = cp;
4001     DETERMINE_NEXTSTRING(cp, isquoted);
4002     if (*cp == '\0') {
4003         *cp++ = '\0';
4004         *a3 = NULL;
4005         return 0;
4006     }
4007     *cp++ = '\0';
4008
4009     SKIP_WHITESPACE(cp);
4010
4011     /* again check if there are only two arguments */
4012     if (*cp == '\0') {
4013         *cp++ = '\0';
4014         *a3 = NULL;
4015         return 0;
4016     }
4017
4018     /*  determine second argument */
4019     CHECK_QUOTATION(cp, isquoted);
4020     *a3 = cp;
4021     DETERMINE_NEXTSTRING(cp, isquoted);
4022     *cp++ = '\0';
4023
4024     return 0;
4025 }
4026
4027
4028 static void add_env_variable(request_rec *r, char *s)
4029 {
4030     char var[MAX_STRING_LEN];
4031     char val[MAX_STRING_LEN];
4032     char *cp;
4033     int n;
4034
4035     if ((cp = strchr(s, ':')) != NULL) {
4036         n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
4037         memcpy(var, s, n);
4038         var[n] = '\0';
4039         apr_cpystrn(val, cp+1, sizeof(val));
4040         apr_table_set(r->subprocess_env, var, val);
4041         rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
4042     }
4043 }
4044
4045
4046 /*
4047 **
4048 **  check that a subrequest won't cause infinite recursion
4049 **
4050 */
4051
4052 static int subreq_ok(request_rec *r)
4053 {
4054     /*
4055      * either not in a subrequest, or in a subrequest
4056      * and URIs aren't NULL and sub/main URIs differ
4057      */
4058     return (r->main == NULL ||
4059             (r->main->uri != NULL && r->uri != NULL &&
4060              strcmp(r->main->uri, r->uri) != 0));
4061 }
4062
4063
4064 /*
4065 **
4066 **  stat() for only the prefix of a path
4067 **
4068 */
4069
4070 static int prefix_stat(const char *path, apr_finfo_t *sb)
4071 {
4072     char curpath[LONG_STRING_LEN];
4073     char *cp;
4074
4075     apr_cpystrn(curpath, path, sizeof(curpath));
4076     if (curpath[0] != '/') {
4077         return 0;
4078     }
4079     if ((cp = strchr(curpath+1, '/')) != NULL) {
4080         *cp = '\0';
4081     }
4082     if (apr_stat(sb, curpath, NULL) == 0) {
4083         return 1;
4084     }
4085     else {
4086         return 0;
4087     }
4088 }
4089
4090
4091 /*
4092 **
4093 **  Lexicographic Compare
4094 **
4095 */
4096
4097 static int compare_lexicography(char *cpNum1, char *cpNum2)
4098 {
4099     int i;
4100     int n1, n2;
4101
4102     n1 = strlen(cpNum1);
4103     n2 = strlen(cpNum2);
4104     if (n1 > n2) {
4105         return 1;
4106     }
4107     if (n1 < n2) {
4108         return -1;
4109     }
4110     for (i = 0; i < n1; i++) {
4111         if (cpNum1[i] > cpNum2[i]) {
4112             return 1;
4113         }
4114         if (cpNum1[i] < cpNum2[i]) {
4115             return -1;
4116         }
4117     }
4118     return 0;
4119 }
4120
4121 /*
4122 **
4123 **  Find end of bracketed expression
4124 **  s points after the opening bracket
4125 **
4126 */
4127
4128 static char *find_closing_bracket(char *s, int left, int right)
4129 {
4130     int depth;
4131
4132     for (depth = 1; *s; ++s) {
4133         if (*s == right && --depth == 0) {
4134             return s;
4135         }
4136         else if (*s == left) {
4137             ++depth;
4138         }
4139     }
4140     return NULL;
4141 }
4142
4143 #ifdef NETWARE
4144 int main(int argc, char *argv[]) 
4145 {
4146     ExitThread(TSR_THREAD, 0);
4147 }
4148 #endif
4149  
4150 /*EOF*/