You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by "Ralf S. Engelschall" <rs...@en.muc.de> on 1996/07/07 14:50:48 UTC

Some questions regarding the API for mod_rewrite

            [       To all API-specialists:
              Please listen and give me some hints,
              even this mail is not a short one! Thanks! ]


I'm still working on the next version of mod_rewrite (1.8) which will contain
a lot of new things including PER-DIRECTORY rewriting support (all RewriteXX
directives valid in .htaccess files).

Now I've tested this version also under 1.1.0 and it worked fine. But before
I will officially release it next week, I want to step through the code and
make sure that it is really correctly implemented and does "not only work" ;-).

                 Some things are left unclear:

Current per-directory support works fine on my test machine, but the way I do
it _is very special and not the obvious way_, even the only possible I think.
The per-dir rewriting engine is run in the "fixup" hook of the API and does
the following at exit:

   - When no rewriting rule applies, then we simply return DECLINED
     (I'm sure that this is the correct way)
     
   - When a external redirect should be made, then I escape the URL,
     set Location: and return with REDIRECT.
     (I'm sure that this is the correct way)

   - When a local URL rewriting occured, then I do a _partitial_ stat() on
     the file (i.e. a stat() on all prefixes). Then

         if the whole file (i.e. not only some prefix) exists then I

            - set r->finfo to the result of the stat
            - do a internal_redirect()
            - set r->handler to a own null-handler which does nothing
              in the next API hook which will be run after our return
            - return OK
            (I'm NOT sure if this is the 100% correct way, even if 
             I'm sure that this is API conform and the only working way)

         else if only some prefix exists then I

            - set r->finfo.st_mode = 0
            - do a internal_redirect()
            - set r->handler to a own null-handler which does nothing
              in the next API hook which will be run after our return
            - return OK
            (I'm NOT sure if this is the 100% correct way, even if 
             I'm sure that this is API conform and the only working way)

         else 

            - set r->finfo.st_mode = 0
            - return DECLINED
            (I'm sure that this is the correct way)
 
This approach is very difficult, but _works_ in all situations which occur on
my test machine. The curious partitial stat is really needed, because

   when we rewrite some URL to a URL which is only _partitially_ valid then
   we have to mark the filename as invalid (finfo.st_mode) but return a OK to
   prevent the core module from prefixing it with the document_root.
   Returning DECLINED is not the correct action!  

And the trick with the null-handler is needed because

   we need to do a real internal_redirect() instead of just a OK return,
   because at the "fixup" hook a rewrite is sometimes to late, e.g. if the
   URL got rewritten to a (valid) directory, then mod_dir will no more
   examine the index.xxxx default files at this stage. But a external
   redirect is not what the user wanted (this should only occur if a http://
   URL occurs or the "redirect" flag is used in a RewriteRule command). So
   the only chance to become a correct result is a real internal_redirect().

   BUT: When we do a internal_redirect() in a fixup hook and then return OK,
   this would lead to a handling of the current (still obsolete because
   already rewritten) request, i.e.  I GOT TWO FILES SEND OUT FOR THE HTTP
   REQUEST!

   The only chance is to _silently_ finish the current never more needed
   (because it was already replaced by the internal_redirect) request.  To
   accomplish this I use the null-handler trick which worked correctly!

And now the question:

    ARE THESE TWO SPECIAL TRICKS REALLY OK
    OR ARE THERE ANY LESS-SPECIAL ALTERNATIVES ?????

                                        Ralf S. Engelschall    
                                        rse@en.muc.de
                                        http://www.muc.de/~rse


PS: I will append (although it is not very short) the current mod_rewrite.c
    to this message to give you the chance to review the code in more detail:

------
/* ====================================================================
 * Copyright (c) 1996 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/*
**  Module mod_rewrite
**
**  URL Rewriting Module, Version 1.8 (06-07-1996)
**
**  This module uses a regular-expression parser to rewrite requested URLs
**  on the fly. It can use external databases (either plain text, or
**  DBM) to provide a mapping function or generate real URIs (including
**  QUERY_INFO parts) for internal subprocessing or external request
**  redirection.
**
**  The latest release can be found on
**  http://www.engelschall.com/mod_rewrite/
**
**  Copyright (c) 1996 The Apache Group
**  Copyright (c) 1996 Ralf S. Engelschall
**
**  Written for The Apache Group by
**      Ralf S. Engelschall
**      rse@engelschall.com
**      http://www.engelschall.com/~rse
*/

/*
**  The complete documentation can be found on
**  http://www.engelschall.com/mod_rewrite/mod_rewrite.html
*/


/*
**
**  Some defines
**
*/

#define ENVVAR_SCRIPT_URL "SCRIPT_URL"
#define ENVVAR_SCRIPT_URI "SCRIPT_URI"

#define DEFAULT_REWRITELOGFILE "logs/rewrite_log"

#ifndef SUPPORT_DBM_REWRITEMAP
#define SUPPORT_DBM_REWRITEMAP 0
#endif

#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"

#define REWRITEFLAG_NONE               0
#define REWRITEFLAG_FORCEREDIRECT      1
#define REWRITEFLAG_LASTRULE           2
#define REWRITEFLAG_NEWROUND           4
#define REWRITEFLAG_CHAIN              8
#define REWRITEFLAG_IGNOREONSUBREQ     16

#define ENGINE_ENABLED  1
#define ENGINE_DISABLED 0


/*
**
**  Includes to energize us
**
*/

    /* from the underlying Unix system ... */
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>

    /* from the Apache server ... */
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_log.h"

    /* from us ... */
#include "regexp/regexp.h"
#if SUPPORT_DBM_REWRITEMAP
#include "sdbm/sdbm.h"
#endif


/*
**
**  our private data structures we handle with
**
*/

    /* the list structures for holding the mapfile information
       and the rewrite rules */

typedef struct {
    char *name;
    char *file;
} rewritemap_entry;

typedef struct {
    char *pattern;
    regexp *regexp;
    char *output;
    int flags;
    char *forced_mimetype;
} rewriterule_entry;

    /* the per-server or per-virtual-server configuration
       statically generated once on startup for every server */

typedef struct {
    int           state;           /* the RewriteEngine state */
    char         *rewritelogfile;  /* the RewriteLog filename */
    int           rewritelogfp;    /* the RewriteLog open filepointer */
    array_header *rewritemaps;     /* the RewriteMap entries */
    array_header *rewriterules;    /* the RewriteRule entries */
} rewrite_server_conf;

    /* the per-directory configuration
       individually generated on-the-fly by Apache server for current request */

typedef struct {
    int           state;           /* the RewriteEngine state */
    char         *rewritelogfile;  /* the RewriteLog filename */
    int           rewritelogfp;    /* the RewriteLog open filepointer */
    array_header *rewritemaps;     /* the RewriteMap entries */
    array_header *rewriterules;    /* the RewriteRule entries */
    char *directory;               /* the directory where it applies */
} rewrite_perdir_conf;


/*
**
**  function forward declarations
**
*/

static command_rec command_table[];
static handler_rec handler_table[];

static void *config_server_create(pool *p, server_rec *s);
static void *config_server_merge(pool *p, void *basev, void *overridesv);
static void *config_perdir_create(pool *p, char *path);
static void *config_perdir_merge(pool *p, void *basev, void *overridesv);

static char *cmd_rewriteengine(cmd_parms *cmd, rewrite_perdir_conf *dconf, int flag);
static char *cmd_rewritelog(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1);
static char *cmd_rewritemap(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1, char *a2);
static char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *str);
static int   cmd_rewriterule_parseargline(char *str, char **a1, char **a2, char **a3);
static int   cmd_rewriterule_parseflagfield(rewriterule_entry *new, char *str);
static void  cmd_rewriterule_setflag(rewriterule_entry *cfg, char *key, char *val);

static int hook_uri2file(request_rec *r);
static int hook_mimetype(request_rec *r);
static int hook_fixup(request_rec *r);

static int apply_rewrite_list(request_rec *r, array_header *rewriterules, int doesc);
static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p);

static int null_handler(request_rec *r);

static void reduce_uri(request_rec *r);
static void splitout_queryargs(request_rec *r);
static int is_this_our_host(request_rec *r, char *testhost);
static char **make_hostname_list(request_rec *r, char *hostname);

static void expand_map_lookups(request_rec *r, char *uri);
static char *lookup_map(request_rec *r, char *name, char *key);
static char *lookup_map_txtfile(request_rec *r, char *file, char *key);
#if SUPPORT_DBM_REWRITEMAP
static char *lookup_map_dbmfile(request_rec *r, char *file, char *key);
#endif
static void expand_tildepaths(request_rec *r, char *uri);
static int partitial_stat(const char *path, struct stat *sb);

static void init_module(server_rec *s, pool *p);
static void open_rewritelog(server_rec *s, pool *p);
static void rewritelog_child(void *cmd);
static void rewritelog(request_rec *r, char *text, ...);
static char *current_logtime(request_rec *r);


/*
**
**  our interface to the Apache server kernel
**
**  keep in mind:
**
**  o  Runtime logic of a request:
**       while(request or subrequest) {
**           foreach(stage #1...#8) {
**               foreach(module) { (**)
**                   try to run hook
**               }
**           }
**       }
**
**  o  the order of modules is the inverted order as
**     given in the Configuration file, i.e. the last module
**     specified is the first one called! The core module
**     is allways the last!
**
**  o  there are two different types of result checking and continue processing:
**     for hook #1,#3,#4,#5,#7:
**         hook run loop stops on first modules which gives
**         back a result != DECLINED, i.e. it usually returns OK
**         which says "OK, module has handled this _stage_" and for #1
**         this have not to mean "Ok, the filename is now valid".
**     for hook #2,#6,#8:
**         all hooks are run, independend of result
**
**  o  at the last stage, the core module allways 
**         - says "BAD_REQUEST" if r->uri does not begin with "/"
**         - prefix URL with document_root or replaced server_root
**           with document_root and sets r->filename
**         - allways return a "OK" independed if the file really exists
**           or not!
**
*/

module rewrite_module = {
   /* STANDARD_MODULE_STUFF, */ /* use the following if we want to be
                                   allways up-to-date with the Apache
                                   server API ;-) */
   19960526, 0, NULL,           /* else use this to force the server to only 
                                   work with us if we were programmed with
                                   an up-to-date server API in mind! */

   init_module,                 /* module initializer */

   config_perdir_create,        /* create per-dir    config structures */
   config_perdir_merge,         /* merge  per-dir    config structures */
   config_server_create,        /* create per-server config structures */
   config_server_merge,         /* merge  per-server config structures */
   command_table,               /* table of config file commands */

   handler_table,               /* [#7] table of MIME-typed-dispatched request action handlers */

   hook_uri2file,               /* [#1] URI to filename translation */

   NULL,                        /* [#3] check_user_id: get and validate user id from the HTTP request */
   NULL,                        /* [#4] check_auth:    check if the user is ok _here_ */
   NULL,                        /* [#2] check_access:  check access by host address, etc. */

   hook_mimetype,               /* [#5] determine MIME type */

   hook_fixup,                  /* [#6] pre-run fixups */
   NULL                         /* [#8] log a transaction */
};

static command_rec command_table[] = {
    { "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG, 
      "On or Off to enable or disable (default) the whole rewriting engine" },
    { "RewriteLog",    cmd_rewritelog,    NULL, OR_FILEINFO, TAKE1, 
      "the filename of the rewriting logfile" },
    { "RewriteMap",    cmd_rewritemap,    NULL, OR_FILEINFO, TAKE2, 
      "a mapname and a filename" },
    { "RewriteRule",   cmd_rewriterule,   NULL, OR_FILEINFO, RAW_ARGS, 
      "a URL-applied regexp-pattern and a substitution URL" },
    { NULL }
};

static handler_rec handler_table[] = {
    { "null-handler", null_handler },
    { NULL }
};



/*
**
**  configuration file stuff
**
*/


static void *config_server_create(pool *p, server_rec *s)
{
    rewrite_server_conf *a;

    a = (rewrite_server_conf *)pcalloc(p, sizeof(rewrite_server_conf));

    a->state          = ENGINE_DISABLED;
    a->rewritelogfile = DEFAULT_REWRITELOGFILE;
    a->rewritelogfp   = -1;
    a->rewritemaps    = make_array(p, 2, sizeof(rewritemap_entry));
    a->rewriterules   = make_array(p, 2, sizeof(rewriterule_entry));

    return (void *)a;
}

static void *config_server_merge(pool *p, void *basev, void *overridesv)
{
    rewrite_server_conf *a, *base, *overrides;

    a         = (rewrite_server_conf *)pcalloc(p, sizeof(rewrite_server_conf));
    base      = (rewrite_server_conf *)basev;
    overrides = (rewrite_server_conf *)overridesv;

    a->state          = overrides->state;
    a->rewritelogfile = overrides->rewritelogfile;
    a->rewritelogfp   = overrides->rewritelogfp;
    a->rewritemaps    = append_arrays(p, overrides->rewritemaps,  base->rewritemaps);
    a->rewriterules   = append_arrays(p, overrides->rewriterules, base->rewriterules);

    return (void *)a;
}

static void *config_perdir_create(pool *p, char *path)
{
    rewrite_perdir_conf *a;

    a = (rewrite_perdir_conf *)pcalloc(p, sizeof(rewrite_perdir_conf));

    a->state          = ENGINE_DISABLED;
    a->rewritelogfile = DEFAULT_REWRITELOGFILE;
    a->rewritelogfp   = -1;
    a->rewritemaps    = make_array(p, 2, sizeof(rewritemap_entry));
    a->rewriterules   = make_array(p, 2, sizeof(rewriterule_entry));
    a->directory      = pstrdup(p, path);

    return (void *)a;
}

static void *config_perdir_merge(pool *p, void *basev, void *overridesv)
{
    rewrite_perdir_conf *a, *base, *overrides;

    a         = (rewrite_perdir_conf *)pcalloc(p, sizeof(rewrite_perdir_conf));
    base      = (rewrite_perdir_conf *)basev;
    overrides = (rewrite_perdir_conf *)overridesv;

    a->state          = overrides->state;
    a->rewritelogfile = overrides->rewritelogfile;
    a->rewritelogfp   = overrides->rewritelogfp;
    a->rewritemaps    = append_arrays(p, overrides->rewritemaps,  base->rewritemaps);
    a->rewriterules   = append_arrays(p, overrides->rewriterules, base->rewriterules);
    a->directory      = overrides->directory;

    return (void *)a;
}


static char *cmd_rewriteengine(cmd_parms *cmd, rewrite_perdir_conf *dconf, int flag)
{
    rewrite_server_conf *sconf;

    sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);

    if (cmd->path == NULL) {   /* is server command */
        if (flag) 
            sconf->state = ENGINE_ENABLED;
        else
            sconf->state = ENGINE_DISABLED;
    }
    else {                     /* is per-directory command */
        if (flag) 
            dconf->state = ENGINE_ENABLED;
        else
            dconf->state = ENGINE_DISABLED;
    }

    return NULL;
}

static char *cmd_rewritelog(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1)
{
    rewrite_server_conf *sconf;

    sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);

    if (cmd->path == NULL)   /* is server command */
        sconf->rewritelogfile = a1;
    else                     /* is per-directory command */
        dconf->rewritelogfile = a1;

    return NULL;
}

static char *cmd_rewritemap(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1, char *a2)
{
    rewrite_server_conf *sconf;
    rewritemap_entry *new;

    sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);

    if (cmd->path == NULL)   /* is server command */
        new = push_array(sconf->rewritemaps);
    else                     /* is per-directory command */
        new = push_array(dconf->rewritemaps);
    new->name = a1;
    new->file = a2;

    return NULL;
}

static char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *str)
{
    rewrite_server_conf *sconf;
    rewriterule_entry *new;
    regexp *regexp;
    int i;
    char *a1;
    char *a2;
    char *a3;

    sconf = (rewrite_server_conf *)get_module_config(cmd->server->module_config, &rewrite_module);

    /*  make a new entry in the internal rewrite rule list */
    if (cmd->path == NULL)   /* is server command */
        new = push_array(sconf->rewriterules);
    else                     /* is per-directory command */
        new = push_array(dconf->rewriterules);

    /*  parse the argument line ourself */
    if (cmd_rewriterule_parseargline(str, &a1, &a2, &a3)) {
        fprintf(stderr, "RewriteRule: bad argument line\n");
        exit(1);
    }

    /*  arg1: the pattern
        try to compile the regexp to test if is ok */
    if ((regexp = regcomp(a1)) == NULL) {
        fprintf(stderr, "RewriteRule: cannot compile regular expression '%s'\n", a1);
        exit(1);
    }
    new->pattern = strdup(a1);
    new->regexp  = regexp;

    /*  arg2: the output string
        replace the $<N> by \<n> which is needed by the currently
        used Regular Expression library */
    for (i = 0; a2[i] != '\0'; i++) {
        if (a2[i] == '$' && a2[i+1] >= '1' && a2[i+1] <= '9') 
            a2[i] = '\\';
    }
    new->output  = strdup(a2);

    /* arg3: optional flags field */
    new->flags = REWRITEFLAG_NONE;
    new->forced_mimetype = NULL;
    if (a3 != NULL) {
        if (cmd_rewriterule_parseflagfield(new, a3)) {
            fprintf(stderr, "RewriteRule: bad flags field '%s'\n", str);
            exit(1);
        }
    }

    return NULL;
}

static int cmd_rewriterule_parseargline(char *str, char **a1, char **a2, char **a3)
{
    char *cp;

    /*  strip whitespaces */
    for (cp = str; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
        ;

    /*  determine first argument */
    *a1 = cp;
    for ( ; *cp != '\0'; cp++) {
        if (*cp == '\\' && *(cp+1) == ' ') {
            cp++;
            continue;
        }
        if (*cp == ' ' || *cp == '\t')
            break;
    }
    if (*cp == '\0')
        return 1;
    *cp++ = '\0';

    /*  strip whitespaces */
    for ( ; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
        ;

    /*  determine second argument */
    *a2 = cp;
    for ( ; *cp != '\0'; cp++) {
        if (*cp == '\\' && *(cp+1) == ' ') {
            cp++;
            continue;
        }
        if (*cp == ' ' || *cp == '\t')
            break;
    }

    if (*cp == '\0') {
        /* there were only two args */
        *cp++ = '\0';
        *a3 = NULL;
        return 0;
    }
    *cp++ = '\0';
    /*  strip whitespaces */
    for ( ; *cp != '\0' && (*cp == ' ' || *cp == '\t'); cp++)
        ;
    /* again check if there are only two arguments */
    if (*cp == '\0') {
        *cp++ = '\0';
        *a3 = NULL;
        return 0;

    }

    /*  determine second argument */
    *a3 = cp;
    for ( ; *cp != '\0'; cp++) {
        if (*cp == '\\' && *(cp+1) == ' ') {
            cp++;
            continue;
        }
        if (*cp == ' ' || *cp == '\t')
            break;
    }
    *cp++ = '\0';

    return 0;
}

static int cmd_rewriterule_parseflagfield(rewriterule_entry *cfg, char *str)
{
    char *cp;
    char *cp1;
    char *cp2;
    char *cp3;
    char *key;
    char *val;

    if (str[0] != '[' || str[strlen(str)-1] != ']') 
        return 1;

    cp = str+1;
    str[strlen(str)-1] = ','; /* for simpler parsing */
    for ( ; *cp != '\0'; ) {
        /* skip whitespaces */
        for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
            ;
        if (*cp == '\0')
            break;
        cp1 = cp;
        if ((cp2 = strchr(cp, ',')) != NULL) {
            cp = cp2+1;
            for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
                ;
            *cp2 = '\0';
            if ((cp3 = strchr(cp1, '=')) != NULL) {
                *cp3 = '\0';
                key = cp1;
                val = cp3+1;
            }
            else {
                key = cp1;
                val = "yes";
            }
            cmd_rewriterule_setflag(cfg, key, val);
        }
        else
            break;
    }
    
    return 0;
}

static void cmd_rewriterule_setflag(rewriterule_entry *cfg, char *key, char *val)
{
    if (   strcasecmp(key, "redirect") == 0
        || strcasecmp(key, "R") == 0       ) {
        cfg->flags |= REWRITEFLAG_FORCEREDIRECT;
    }
    else if (   strcasecmp(key, "last") == 0
             || strcasecmp(key, "L") == 0   ) {
        cfg->flags |= REWRITEFLAG_LASTRULE;
    }
    else if (   strcasecmp(key, "next") == 0
             || strcasecmp(key, "N") == 0   ) {
        cfg->flags |= REWRITEFLAG_NEWROUND;
    }
    else if (   strcasecmp(key, "chain") == 0
             || strcasecmp(key, "C") == 0    ) {
        cfg->flags |= REWRITEFLAG_CHAIN;
    }
    else if (   strcasecmp(key, "type") == 0
             || strcasecmp(key, "T") == 0   ) {
        cfg->forced_mimetype = strdup(val);
    }
    else if (   strcasecmp(key, "nosubreq") == 0
             || strcasecmp(key, "NS") == 0      ) {
        cfg->flags |= REWRITEFLAG_IGNOREONSUBREQ;
    }
    else {
        fprintf(stderr, "RewriteRule: unknown flag '%s'\n", key);
        exit(1);
    }
    return;
}


/*
**
**  Global RewriteRule runtime engine
**
*/

static int hook_uri2file(request_rec *r)
{
    void *sconf;
    rewrite_server_conf *conf;
    char *var;
    char *thisserver, *thisport, *thisurl;
    char buf[512];
    char *cp, *cp2;
    struct stat finfo;
    int n;


    /*
     *  retrieve the config structures
     */
    sconf = r->server->module_config;
    conf  = (rewrite_server_conf *)get_module_config(sconf, &rewrite_module);

    /*
     *  only do something under runtime if the engine is really enabled,
     *  else return immediately!
     */
    if (conf->state == ENGINE_DISABLED)
        return DECLINED;


    /*
     *  add the SCRIPT_URL variable to the env. this is a bit complicated
     *  due to the fact that apache uses subrequests and internal redirects
     */

    if (r->main == NULL) {
         var = pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
         var = table_get(r->subprocess_env, var);
         if (var == NULL) 
             table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, r->uri));
         else 
             table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, var));
    } 
    else {
         var = table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
         table_set(r->subprocess_env, ENVVAR_SCRIPT_URL, pstrdup(r->pool, var));
    }

    /*
     *  create the SCRIPT_URI variable for the env
     */

    /* add the canonical URI of this URL */
    thisserver = r->server->server_hostname;
    if (r->server->port == 80) 
        thisport = "";
    else {
        sprintf(buf, ":%d", r->server->port);
        thisport = pstrdup(r->pool, buf);
    }
    thisurl = table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);

    /* set the variable */
    var = pstrcat(r->pool, "http://", thisserver, thisport, thisurl, NULL);
    table_set(r->subprocess_env, ENVVAR_SCRIPT_URI, pstrdup(r->pool, var));


    /*
     *  now apply the rules ... 
     */
    if (apply_rewrite_list(r, conf->rewriterules, 0)) {

        if (strncmp(r->uri, "http://", 7) == 0) {
            /* it was finally rewritten to a remote path */


            for (cp = r->uri+7; *cp != '/' && *cp != '\0'; cp++)
                ;
            if (*cp != '\0') {
                rewritelog(r, "escaping %s for redirect", r->uri);
                cp2 = escape_uri(r->pool, cp);
                *cp = '\0';
                r->uri = pstrcat(r->pool, r->uri, cp2, NULL);
            }
            table_set(r->headers_out, "Location", r->uri);
            rewritelog(r, "redirect to %s [REDIRECT]", r->uri);
            return REDIRECT;
        }
        else {
            /* it was finally rewritten to a local path */

            r->filename = pstrdup(r->pool, r->uri);

            n = partitial_stat(r->filename, &finfo);
            if (n == strlen(r->filename)) {
                /* it does completely exists, i.e. the whole filename is valid */

                /* we do a real stat again to be sure that the r->finfo
                   gets a correct value too. And we return "OK" to
                   stop the URI->filename translation phase here. */
                stat(r->filename, &r->finfo);
                rewritelog(r, "go-ahead with (valid) %s [OK]", r->uri);
                return OK; 
            }
            else if (n > 0 && r->main == NULL) {
                /* only a partitial part is valid */

                /* we don't do a real stat because
                   the complete filename is not valid.
                   Instead we mark the filename as invalid
                   but return "OK" to prevent the core module
                   from doing any more processing... */
                r->finfo.st_mode = 0;
                rewritelog(r, "go-ahead with (partitial valid) %s [OK]", r->uri);
                return OK; 
            }
            else /* n == 0 */ {
                /* it doesn't exist, not even a partitial prefix path does exist */

                /* a explicit mark as invalid for the filename 
                   and a return of "DECLINED" is the best we can do.
                   Notice: the uri/filename is still rewritten! 
                   Sometimes that could be enough... because
                   of the "DECLINED" the core module will now
                   prefix it with document_root and says "OK" which
                   could be really ok... */
                r->finfo.st_mode = 0;
                rewritelog(r, "go-ahead with (invalid) %s [DECLINED]", r->uri);
                return DECLINED;
            }
        }
    }
    else {
        rewritelog(r, "pass through %s", r->uri);
        return DECLINED;
    }
}

static int apply_rewrite_list(request_rec *r, array_header *rewriterules, int doesc)
{
    rewriterule_entry *entries;
    rewriterule_entry *p;
    int i;
    int changed;
    
    entries = (rewriterule_entry *)rewriterules->elts;
    changed = 0;
    loop:
    for (i = 0; i < rewriterules->nelts; i++) {
        p = &entries[i];
        if (   p->flags & REWRITEFLAG_IGNOREONSUBREQ
            && r->main != NULL                      )
            continue;
        if (apply_rewrite_rule(r, p)) {
            changed = 1;
            if (p->flags & REWRITEFLAG_LASTRULE) 
                break;
            if (p->flags & REWRITEFLAG_NEWROUND) 
                goto loop;
        }
        else {
            /* if current rule is chained with next rule(s),
               skip all this next rule(s) */
            while (   i < rewriterules->nelts
                   && p->flags & REWRITEFLAG_CHAIN) {
                i++;
                p = &entries[i];
            }
        }
    }
    return changed;
}

static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p)
{
    int changed;
    char *uri;
    char *output;
    int flags;
    char newuri[MAX_STRING_LEN];
    regexp *regexp;

    uri     = r->uri;
    regexp  = p->regexp;
    output  = p->output;
    flags   = p->flags;

    changed = 0;
    if (regexec(regexp, uri)) {                         /* try to match the pattern */

        regsub(regexp, output, newuri);                      /* substitute in output */
        expand_map_lookups(r, newuri);                       /* expand ${...} */
        expand_tildepaths(r, newuri);                        /* expand ~user prefix */

        rewritelog(r, "rewrite %s -> %s", r->uri, newuri);
        r->uri = pstrdup(r->pool, newuri);

        /* reduce http://<ourhost> */
        reduce_uri(r);

        /* split out on-the-fly generated QUERY_STRING '....?xxxxx&xxxx...' */
        splitout_queryargs(r);

        /* if a MIME-type shoudl be later forced for this URL, then remeber this */
        if (p->forced_mimetype != NULL) {
            table_set(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, p->forced_mimetype);
            rewritelog(r, "remember %s to have MIME-type '%s'", r->uri, p->forced_mimetype);
        }

        /* if we are forced to do a explicit redirect
           finally prefix the new URI with http://<ourname> explicitly */
        if (flags & REWRITEFLAG_FORCEREDIRECT) {
            if (strncmp(r->uri, "http://", 7) != 0) {
                sprintf(newuri, "http://%s%s", r->server->server_hostname, r->uri);
                rewritelog(r, "prepare forced redirect %s -> %s", r->uri, newuri);
                r->uri = pstrdup(r->pool, newuri);
            }
        }
        changed = 1;
    }
    return changed;
}


/*
**
**  forced MIME type support
**
*/

static int hook_mimetype(request_rec *r)
{
    char *t;
    
    /* now check if we have to force a MIME-type */
    t = table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
    if (t == NULL) 
        return DECLINED;
    else {
        rewritelog(r, "force filename %s to have MIME-type '%s'", r->filename, t);
        r->content_type = t;
        return OK;
    }
}



/*
**
**  Per-Directory RewriteRule runtime engine
**
*/

static int hook_fixup(request_rec *r)
{
    rewrite_perdir_conf *dconf;
    char *cp;
    char *cp2;
    struct stat finfo;
    int n;
    
    dconf = (rewrite_perdir_conf *)get_module_config(r->per_dir_config, &rewrite_module);

    /* if there is no per-dir config we return immediately */
    if (dconf == NULL)
        return DECLINED;

    /* we shouldn't do anything in subrequests */
    if (r->main != NULL) 
        return DECLINED;

    /* if there are no real (i.e. no RewriteRule directives!)
       per-dir config of us, we return also immediately */
    if (dconf->directory == NULL) 
        return DECLINED;

    /*
     *  only do something under runtime if the engine is really enabled,
     *  for this directory, else return immediately!
     */
    if (dconf->state == ENGINE_DISABLED)
        return DECLINED;

    /*
     *  now apply the rules ... 
     */
    if (apply_rewrite_list(r, dconf->rewriterules, 0)) {

        if (strncmp(r->uri, "http://", 7) == 0) {
            /* it was finally rewritten to a remote path */

            for (cp = r->uri+7; *cp != '/' && *cp != '\0'; cp++)
                ;
            if (*cp != '\0') {
                rewritelog(r, "[per-dir %s] escaping %s for redirect", dconf->directory, r->uri);
                cp2 = escape_uri(r->pool, cp);
                *cp = '\0';
                r->uri = pstrcat(r->pool, r->uri, cp2, NULL);
            }
            table_set(r->headers_out, "Location", r->uri);
            rewritelog(r, "[per-dir %s] redirect to %s [REDIRECT]", dconf->directory, r->uri);
            return REDIRECT;
        }
        else {
            /* it was finally rewritten to a local path */

            r->filename = pstrdup(r->pool, r->uri);

            n = partitial_stat(r->filename, &finfo);
            if (n == strlen(r->filename)) {
                /* it does completely exists, i.e. the whole filename is valid */

                /* we do a real stat again to be sure that the r->finfo
                   gets a correct value too. And we return "OK" to
                   stop the URI->filename translation phase here. */
                stat(r->filename, &r->finfo);

                /* now do a _internal_  redirect instead just
                   returning OK. This is needed because at this 
                   time (hook_fixup) when a rewrite occured we need to
                   restart the processing from the top (hook_uri2file). If we
                   would return just OK the redirected file will be served
                   _AND_ the current request (which could lead to an error
                   message) !  */
                rewritelog(r, "[per-dir %s] internal redirect with (valid) %s [INTERNAL REDIRECT]", dconf->directory, r->uri);
                internal_redirect(pstrcat(r->pool, r->uri, r->args ? "?" : NULL, r->args, NULL), r);

                /* ok, now make sure that the current request
                   will not get served, too! We do this by using
                   an own null handler. */
                r->filename = pstrcat(r->pool, "null:", r->uri, NULL);
                r->handler = "null-handler";
                return OK; 
            }
            else if (n > 0 && r->main == NULL) {
                /* only a partitial part is valid */

                /* we don't do a real stat because
                   the complete filename is not valid.
                   Instead we mark the filename as invalid
                   but return "OK" to prevent the core module
                   from doing any more processing... */
                r->finfo.st_mode = 0;

                /* now do a _internal_  redirect instead just
                   returning OK. This is needed because at this 
                   time (hook_fixup) when a rewrite occured we need to
                   restart the processing from the top (hook_uri2file). If we
                   would return just OK the redirected file will be served
                   _AND_ the current request (which could lead to an error
                   message) !  */
                rewritelog(r, "[per-dir %s] internal redirect with (partitial valid) %s [INTERNAL REDIRECT]", dconf->directory, r->uri);
                internal_redirect(pstrcat(r->pool, r->uri, r->args ? "?" : NULL, r->args, NULL), r);

                /* ok, now make sure that the current request
                   will not get served, too! We do this by using
                   an own null handler. */
                r->filename = pstrcat(r->pool, "null:", r->uri, NULL);
                r->handler = "null-handler";
                return OK; 
            }
            else /* n == 0 */ {
                /* it doesn't exist, not even a partitial prefix path does exist */

                /* a explicit mark as invalid for the filename 
                   and a return of "DECLINED" is the best we can do.
                   Notice: the uri/filename is still rewritten! 
                   Sometimes that could be enough... because
                   of the "DECLINED" the core module will now
                   prefix it with document_root and says "OK" which
                   could be really ok... */
                r->finfo.st_mode = 0;
                rewritelog(r, "[per-dir %s] go-ahead with (invalid) %s [DECLINED]", dconf->directory, r->uri);
                return DECLINED;
            }
        }
    }
    else {
        rewritelog(r, "[per-dir %s] pass through %s", dconf->directory, r->uri);
        return DECLINED;
    }
}

/*
**
**  real handlers
**
*/


/* the null handler - it does nothing! This is just be able
   to silently finish a request which was already served by
   a subrequest, i.e. to prevent to output another request */
static int null_handler(request_rec *r)
{
    /* just make sure that we are really wanted! */
    if (strncmp(r->filename, "null:", 5) != 0) 
        return DECLINED;

    /* ok, we do nothing and return immediately,
       which will cause the current request to be finished
       without any HTTP header or body output, etc. 
       Only action which still happen: the logging... */
    return OK;
}


/*
**
**  DNS hostname check
**
*/

static int is_this_our_host(request_rec *r, char *testhost)
{
    char **cppHNLour;
    char **cppHNLtest;
    int i, j;

    if ((cppHNLour = make_hostname_list(r, r->server->server_hostname)) == NULL)
        return 0;
    if ((cppHNLtest = make_hostname_list(r, testhost)) == NULL)
        return 0;

    for (i = 0; cppHNLtest[i] != NULL; i++) {
        for (j = 0; cppHNLour[j] != NULL; j++) {
            if (strcmp(cppHNLtest[i], cppHNLour[j]) == 0) {
                return 1;
            }
        }
    }
    return 0;
}

static char **make_hostname_list(request_rec *r, char *hostname)
{
    char **cppHNL;
    struct hostent *hep;
    int i;

    if ((hep = gethostbyname(hostname)) == NULL)
        return NULL;
    for (i = 0; hep->h_aliases[i]; i++)
        ;
    cppHNL = (char **)palloc(r->pool, sizeof(char *)*(i+2));
    cppHNL[0] = pstrdup(r->pool, hep->h_name);
    for (i = 0; hep->h_aliases[i]; i++)
        cppHNL[1+i] = pstrdup(r->pool, hep->h_aliases[i]);
    cppHNL[1+i] = NULL;
    return cppHNL;
}

static void reduce_uri(request_rec *r)
{
    char *a;
    char *e;
    int n;
    char hostname[MAX_STRING_LEN];
    char olduri[MAX_STRING_LEN];

    if (strncmp(r->uri, "http://", 7) == 0) {
        /* there was a rewrite to a remote path */

        strcpy(olduri, r->uri); /* save for logging */

        /* cut the hostname out of the URI */
        a = r->uri+7;
        e = strchr(a, '/');
        if (e == NULL)
            e = a+strlen(a);
        n = e-a;
        memcpy(hostname, a, n);
        hostname[n] = '\0';

        /* check whether we could reduce it to a local path... */
        if (is_this_our_host(r, hostname)) {
            /* this is our host, so remove http://ourhostname/ */
            if (*e == '\0')
                r->uri = pstrdup(r->pool, "/");
            else
                r->uri = pstrdup(r->pool, e);
            rewritelog(r, "reduce %s -> %s", olduri, r->uri);
        }
    }
    return;            
}

static void splitout_queryargs(request_rec *r)
{
    char *q;
    char olduri[MAX_STRING_LEN];

    q = strchr(r->uri, '?');
    if (q != NULL) {
        strcpy(olduri, r->uri);
        *q++ = '\0';
        r->args = pstrcat(r->pool, q, "&", r->args, NULL);
        if (r->args[strlen(r->args)-1] == '&')
            r->args[strlen(r->args)-1] = '\0';
        rewritelog(r, "split uri=%s -> uri=%s, args=%s", olduri, r->uri, r->args);
    }
    return;            
}


/*
**
**  mapfile expansion support
**  i.e. expansion of MAP lookup directives
**  ${<mapname>:<key>} in RewriteRule rhs
**
*/

static void expand_map_lookups(request_rec *r, char *uri)
{
    char newuri[MAX_STRING_LEN];
    char *cpI;
    char *cpIE;
    char *cpO;
    char *cpT;
    char *cpT2;
    char mapname[MAX_STRING_LEN];
    char mapkey[MAX_STRING_LEN];
    char defaultvalue[MAX_STRING_LEN];
    int n;

    cpI = uri;
    cpIE = cpI+strlen(cpI);
    cpO = newuri;
    while (cpI < cpIE) {
        if (cpI+6 < cpIE && strncmp(cpI, "${", 2) == 0) {
            /* missing delimiter -> take it as plain text */
            if (   strchr(cpI+2, ':') == NULL
                || strchr(cpI+2, '}') == NULL) {
                memcpy(cpO, cpI, 2);
                cpO += 2;
                cpI += 2;
                continue;
            }
            cpI += 2;

            cpT = strchr(cpI, ':');
            n = cpT-cpI;
            memcpy(mapname, cpI, n);
            mapname[n] = '\0';
            cpI += n+1;

            cpT2 = strchr(cpI, '|');
            cpT = strchr(cpI, '}');
            if (cpT2 != NULL && cpT2 < cpT) {
                n = cpT2-cpI;
                memcpy(mapkey, cpI, n);
                mapkey[n] = '\0';
                cpI += n+1;

                n = cpT-cpI;
                memcpy(defaultvalue, cpI, n);
                defaultvalue[n] = '\0';
                cpI += n+1;
            }
            else {
                n = cpT-cpI;
                memcpy(mapkey, cpI, n);
                mapkey[n] = '\0';
                cpI += n+1;

                defaultvalue[0] = '\0';
            }

            cpT = lookup_map(r, mapname, mapkey);
            if (cpT != NULL) {
                n = strlen(cpT);
                memcpy(cpO, cpT, n);
                cpO += n;
            }
            else {
                n = strlen(defaultvalue);
                memcpy(cpO, defaultvalue, n);
                cpO += n;
            }
        }
        else {
            cpT = strstr(cpI, "${");
            if (cpT == NULL)
                cpT = cpI+strlen(cpI);
            n = cpT-cpI;
            memcpy(cpO, cpI, n);
            cpO += n;
            cpI += n;
        }
    }
    *cpO = '\0';
    strcpy(uri, newuri);
    return;
}

static char *lookup_map(request_rec *r, char *name, char *key)
{
    void *sconf;
    rewrite_server_conf *conf;
    array_header *rewritemaps;
    rewritemap_entry *entries;
    rewritemap_entry *s;
    char *value;
    int i;

    /* get map configuration */
    sconf = r->server->module_config;
    conf  = (rewrite_server_conf *)get_module_config(sconf, &rewrite_module);
    rewritemaps = conf->rewritemaps;

    entries = (rewritemap_entry *)rewritemaps->elts;
    for (i = 0; i < rewritemaps->nelts; i++) {
        s = &entries[i];
        if (strcmp(s->name, name) == 0) {
            if (strlen(s->file) > 4 && strncmp(s->file, "txt:", 4) == 0) {
                if ((value = lookup_map_txtfile(r, s->file+4, key)) != NULL)
                    return value;
            }
            else if (strlen(s->file) > 4 && strncmp(s->file, "dbm:", 4) == 0) {
#if SUPPORT_DBM_REWRITEMAP
                if ((value = lookup_map_dbmfile(r, s->file+4, key)) != NULL)
                    return value;
#else
                return NULL;
#endif
            }
            else {
                if ((value = lookup_map_txtfile(r, s->file, key)) != NULL)
                    return value;
            }
        }
    }
    return NULL;
}

#define PATTERN "^([^ ]+) +([^ ]+).*$"
#define OUTPUT  "\\1,\\2"
static regexp *lookup_map_txtfile_regexp = NULL;

static char *lookup_map_txtfile(request_rec *r, char *file, char *key)
{
    FILE *fp = NULL;
    char line[1024];
    char output[1024];
    char result[1024];
    char *value = NULL;
    char *cpT;
    char *curkey;
    char *curval;

    if ((fp = pfopen(r->pool, file, "r")) == NULL)
        return NULL;

    if (lookup_map_txtfile_regexp == NULL)
        lookup_map_txtfile_regexp = regcomp(PATTERN);

    strcpy(output,  OUTPUT);
    while (fgets(line, sizeof(line), fp) != NULL) {
        if (line[strlen(line)-1] == '\n')
            line[strlen(line)-1] = '\0';
        if (regexec(lookup_map_txtfile_regexp, line)) {
            regsub(lookup_map_txtfile_regexp, output, result);
            cpT = strchr(result, ',');
            *cpT = '\0';
            curkey = result;
            curval = cpT+1;

            if (strcmp(curkey, key) == 0) {
                value = pstrdup(r->pool, curval);
                break;
            }
        }
    }
    pfclose(r->pool, fp);
    return value;
}

#if SUPPORT_DBM_REWRITEMAP
static char *lookup_map_dbmfile(request_rec *r, char *file, char *key)
{
    DBM *dbmfp = NULL;
    datum dbmkey;
    datum dbmval;
    char *value = NULL;
    char buf[MAX_STRING_LEN];

    dbmkey.dptr  = key;
    dbmkey.dsize = strlen(key)+1;
    if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
        dbmval = dbm_fetch(dbmfp, dbmkey);
        if (dbmval.dptr != NULL) {
            memcpy(buf, dbmval.dptr, dbmval.dsize);
            buf[dbmval.dsize] = '\0';
            value = pstrdup(r->pool, buf);
        }
        dbm_close(dbmfp);
    }
    return value;
}
#endif

static void expand_tildepaths(request_rec *r, char *uri)
{
    char newuri[MAX_STRING_LEN];
    char user[MAX_STRING_LEN];
    struct passwd *pw;
    int i, j;

    if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
        /* cut out the username */
        for (j = 0, i = 2; uri[i] != '\0' && 
                       (   (uri[i] >= '0' && uri[i] <= '9')
                        || (uri[i] >= 'a' && uri[i] <= 'z')
                        || (uri[i] >= 'A' && uri[i] <= 'Z')); )
            user[j++] = uri[i++];
        user[j] = '\0';

        /* lookup username in systems passwd file */
        if ((pw = getpwnam(user)) != NULL) {
            /* ok, user was found, so expand the ~user string */
            if (uri[i] != '\0') {
                /* ~user/anything...  has to be expanded */
                if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/') 
                    pw->pw_dir[strlen(pw->pw_dir)-1] = '\0';
                sprintf(newuri, "%s%s", pw->pw_dir, uri+i);
                strcpy(uri, newuri);
            }
            else {
                /* only ~user has to be expanded */
                strcpy(uri, pw->pw_dir);
            }
        }
    }
    return;
}

/* partitial stat() function which does 
   a interative stat on the remaining path while
   stripping of a dir/file part at the end of the path */
static int partitial_stat(const char *path, struct stat *sb)
{
    char curpath[MAX_STRING_LEN];
    char *cp;
    int n;

    strcpy(curpath, path);
    while (1) {
       n = strlen(curpath);
       if (stat(curpath, sb) == 0) {
           return n;
       }
       if ((cp = strrchr(curpath, '/')) == NULL)
           break;
       *cp = '\0';
    }
    return 0;
}

/*
**
**  RewriteLog support
**
*/

static void init_module(server_rec *s, pool *p)
{
    for (; s; s = s->next)
        open_rewritelog(s, p);
}


static void open_rewritelog(server_rec *s, pool *p)
{
    rewrite_server_conf *conf;
    char *fname;
    FILE *fp;
    static int    rewritelog_flags = ( O_WRONLY|O_APPEND|O_CREAT );
    static mode_t rewritelog_mode  = ( S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH );
  
    conf = get_module_config(s->module_config, &rewrite_module);
    
    if (conf->rewritelogfile == NULL)
        return; 
    if (*(conf->rewritelogfile) == '\0')
        return;
    if (conf->rewritelogfp > 0)
        return; /* virtual log shared w/main server */

    fname = server_root_relative(p, conf->rewritelogfile);
    
    if (*conf->rewritelogfile == '|') {
        spawn_child(p, rewritelog_child, (void *)(conf->rewritelogfile+1),
                    kill_after_timeout, &fp, NULL);
        if (fp == NULL) {
            fprintf (stderr, "Couldn't fork child for RewriteLog process\n");
            exit (1);
        }
        conf->rewritelogfp = fileno(fp);
    }
    else if (*conf->rewritelogfile != '\0') {
        if ((conf->rewritelogfp = popenf(p, fname, rewritelog_flags, rewritelog_mode)) < 0) {
            fprintf(stderr, "httpd: could not open rewrite log file %s.\n", fname);
            perror("open");
            exit(1);
        }
    }
}

/* Child process code for 'RewriteLog "|..."' */
static void rewritelog_child(void *cmd)
{
    cleanup_for_exec();
    signal(SIGHUP, SIG_IGN);
    execl(SHELL_PATH, SHELL_PATH, "-c", (char *)cmd, NULL);
    exit(1);
}

static void rewritelog(request_rec *r, char *text, ...)
{
    rewrite_server_conf *conf;
    conn_rec *connect;
    char *str1;
    char str2[HUGE_STRING_LEN];
    char str3[HUGE_STRING_LEN];
    char type[20];
    char redir[20];
    va_list ap;
    int i;
    request_rec *req;
    
    va_start(ap, text);
    conf = get_module_config(r->server->module_config, &rewrite_module);
    connect = r->connection;

    if (conf->rewritelogfp <0)
        return;
    if (conf->rewritelogfile == NULL)
        return;
    if (*(conf->rewritelogfile) == '\0')
        return;

    str1 = pstrcat(r->pool, get_remote_host(connect, r->server->module_config, REMOTE_NAME), " ",
                            (connect->remote_logname != NULL ? connect->remote_logname : "-"), " ",
                            (connect->user != NULL ? connect->user : "-"),
                            NULL);
    vsprintf(str2, text, ap);

    if (r->main == NULL)
        strcpy(type, "initial");
    else
        strcpy(type, "subreq");

    for (i = 0, req = r->prev; req != NULL; req = req->prev) 
        ;
    if (i == 0)
        strcpy(redir, "");
    else
        sprintf(redir, "/redir#%d", i);

    sprintf(str3, "%s %s [id#%x/%s%s] %s\n", str1, current_logtime(r), (unsigned int)r, type, redir, str2);

    write(conf->rewritelogfp, str3, strlen(str3));

    va_end(ap);
    return;
}

static char *current_logtime(request_rec *r)
{
    long timz;
    struct tm *t;
    char tstr[MAX_STRING_LEN], sign;
    
    t = get_gmtoff(&timz);
    sign = (timz < 0 ? '-' : '+');
    if(timz < 0) 
        timz = -timz;

    strftime(tstr, MAX_STRING_LEN,"[%d/%b/%Y:%H:%M:%S ",t);

    sprintf(tstr + strlen(tstr), "%c%02ld%02ld]", sign, timz/3600, timz%3600);

    return pstrdup(r->pool, tstr);
}


/*EOF*/
------