Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Apache/modules/mappers/   (Apache Software Stiftung Version 2.4.65©)  Datei vom 21.6.2025 mit Größe 184 kB image not shown  

Bilddatei mod_rewrite.c

  Sprache: C
 

/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/*                       _                            _ _
 *   _ __ ___   ___   __| |    _ __ _____      ___ __(_) |_ ___
 *  | '_ ` _ \ / _ \ / _` |   | '__/ _ \ \ /\ / / '__| | __/ _ \
 *  | | | | | | (_) | (_| |   | | |  __/\ V  V /| |  | | ||  __/
 *  |_| |_| |_|\___/ \__,_|___|_|  \___| \_/\_/ |_|  |_|\__\___|
 *                       |_____|
 *
 *  URL Rewriting Module
 *
 *  This module uses a rule-based rewriting engine (based on a
 *  regular-expression parser) to rewrite requested URLs on the fly.
 *
 *  It supports an unlimited number of additional rule conditions (which can
 *  operate on a lot of variables, even on HTTP headers) for granular
 *  matching and even external database lookups (either via plain text
 *  tables, DBM hash files or even external processes) for advanced URL
 *  substitution.
 *
 *  It operates on the full URLs (including the PATH_INFO part) both in
 *  per-server context (httpd.conf) and per-dir context (.htaccess) and even
 *  can generate QUERY_STRING parts on result.   The rewriting result finally
 *  can lead to internal subprocessing, external request redirection or even
 *  to internal proxy throughput.
 *
 *  This module was originally written in April 1996 and
 *  gifted exclusively to the The Apache Software Foundation in July 1997 by
 *
 *      Ralf S. Engelschall
 *      rse engelschall.com
 *      www.engelschall.com
 */


#include "apr.h"
#include "apr_strings.h"
#include "apr_hash.h"
#include "apr_user.h"
#include "apr_lib.h"
#include "apr_signal.h"
#include "apr_global_mutex.h"
#include "apr_dbm.h"
#include "apr_dbd.h"

#include "apr_version.h"
#if !APR_VERSION_AT_LEAST(2,0,0)
#include "apu_version.h"
#endif

#include "mod_dbd.h"

#if APR_HAS_THREADS
#include "apr_thread_mutex.h"
#endif

#define APR_WANT_MEMFUNC
#define APR_WANT_STRFUNC
#define APR_WANT_IOVEC
#include "apr_want.h"

/* XXX: Do we really need these headers? */
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif
#if APR_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if APR_HAVE_STDARG_H
#include <stdarg.h>
#endif
#if APR_HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if APR_HAVE_CTYPE_H
#include <ctype.h>
#endif
#if APR_HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_ssl.h"
#include "http_vhost.h"
#include "util_mutex.h"

#include "mod_rewrite.h"
#include "ap_expr.h"

#include "test_char.h"

static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL;
static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL;
static const char* really_last_key = "rewrite_really_last";

/*
 * in order to improve performance on running production systems, you
 * may strip all rewritelog code entirely from mod_rewrite by using the
 * -DREWRITELOG_DISABLED compiler option.
 *
 * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are
 * responsible for answering all the mod_rewrite questions out there.
 */

/* If logging is limited to APLOG_DEBUG or lower, disable rewrite log, too */
#ifdef  APLOG_MAX_LOGLEVEL
#if     APLOG_MAX_LOGLEVEL < APLOG_TRACE1
#ifndef REWRITELOG_DISABLED
#define REWRITELOG_DISABLED
#endif
#endif
#endif

#ifndef REWRITELOG_DISABLED
#ifdef AP_HAVE_C99
#define rewritelog(...) do_rewritelog(__LINE__, __VA_ARGS__)
#else
#define rewritelog do_rewritelog
#endif
#define REWRITELOG_MODE  ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
#define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE )

#else /* !REWRITELOG_DISABLED */

#ifdef AP_HAVE_C99
#define rewritelog(...)
#else
#define rewritelog do_rewritelog
#endif
#endif /* REWRITELOG_DISABLED */

/* remembered mime-type for [T=...] */
#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"
#define REWRITE_FORCED_HANDLER_NOTEVAR  "rewrite-forced-handler"

#define ENVVAR_SCRIPT_URL "SCRIPT_URL"
#define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL
#define ENVVAR_SCRIPT_URI "SCRIPT_URI"

#define CONDFLAG_NONE               (1<<0)
#define CONDFLAG_NOCASE             (1<<1)
#define CONDFLAG_NOTMATCH           (1<<2)
#define CONDFLAG_ORNEXT             (1<<3)
#define CONDFLAG_NOVARY             (1<<4)

#define RULEFLAG_NONE               (1<<0)
#define RULEFLAG_FORCEREDIRECT      (1<<1)
#define RULEFLAG_LASTRULE           (1<<2)
#define RULEFLAG_NEWROUND           (1<<3)
#define RULEFLAG_CHAIN              (1<<4)
#define RULEFLAG_IGNOREONSUBREQ     (1<<5)
#define RULEFLAG_NOTMATCH           (1<<6)
#define RULEFLAG_PROXY              (1<<7)
#define RULEFLAG_PASSTHROUGH        (1<<8)
#define RULEFLAG_QSAPPEND           (1<<9)
#define RULEFLAG_NOCASE             (1<<10)
#define RULEFLAG_NOESCAPE           (1<<11)
#define RULEFLAG_NOSUB              (1<<12)
#define RULEFLAG_STATUS             (1<<13)
#define RULEFLAG_ESCAPEBACKREF      (1<<14)
#define RULEFLAG_DISCARDPATHINFO    (1<<15)
#define RULEFLAG_QSDISCARD          (1<<16)
#define RULEFLAG_END                (1<<17)
#define RULEFLAG_ESCAPENOPLUS       (1<<18)
#define RULEFLAG_QSLAST             (1<<19)
#define RULEFLAG_QSNONE             (1<<20) /* programattic only */
#define RULEFLAG_ESCAPECTLS         (1<<21)
#define RULEFLAG_UNSAFE_PREFIX_STAT (1<<22)
#define RULEFLAG_UNSAFE_ALLOW3F     (1<<23)
#define RULEFLAG_UNC                (1<<24)

/* return code of the rewrite rule
 * the result may be escaped - or not
 */

#define ACTION_NORMAL               (1<<0)
#define ACTION_NOESCAPE             (1<<1)
#define ACTION_STATUS               (1<<2)
#define ACTION_STATUS_SET           (1<<3)

#define MAPTYPE_TXT                 (1<<0)
#define MAPTYPE_DBM                 (1<<1)
#define MAPTYPE_PRG                 (1<<2)
#define MAPTYPE_INT                 (1<<3)
#define MAPTYPE_RND                 (1<<4)
#define MAPTYPE_DBD                 (1<<5)
#define MAPTYPE_DBD_CACHE           (1<<6)

#define ENGINE_DISABLED             (1<<0)
#define ENGINE_ENABLED              (1<<1)

#define OPTION_NONE                 (1<<0)
#define OPTION_INHERIT              (1<<1)
#define OPTION_INHERIT_BEFORE       (1<<2)
#define OPTION_NOSLASH              (1<<3)
#define OPTION_ANYURI               (1<<4)
#define OPTION_MERGEBASE            (1<<5)
#define OPTION_INHERIT_DOWN         (1<<6)
#define OPTION_INHERIT_DOWN_BEFORE  (1<<7)
#define OPTION_IGNORE_INHERIT       (1<<8)
#define OPTION_IGNORE_CONTEXT_INFO  (1<<9)
#define OPTION_LEGACY_PREFIX_DOCROOT (1<<10)
#define OPTION_UNSAFE_PREFIX_STAT   (1<<12)

#ifndef RAND_MAX
#define RAND_MAX 32767
#endif

/* max cookie size in rfc 2109 */
/* XXX: not used at all. We should do a check somewhere and/or cut the cookie */
#define MAX_COOKIE_LEN 4096

/* max line length (incl.\n) in text rewrite maps */
#ifndef REWRITE_MAX_TXT_MAP_LINE
#define REWRITE_MAX_TXT_MAP_LINE 1024
#endif

/* buffer length for prg rewrite maps */
#ifndef REWRITE_PRG_MAP_BUF
#define REWRITE_PRG_MAP_BUF 1024
#endif

/* for better readbility */
#define LEFT_CURLY  '{'
#define RIGHT_CURLY '}'

/*
 * expansion result items on the stack to save some cycles
 *
 * (5 == about 2 variables like "foo%{var}bar%{var}baz")
 */

#define SMALL_EXPANSION 5

/*
 * check that a subrequest won't cause infinite recursion
 *
 * either not in a subrequest, or in a subrequest
 * and URIs aren't NULL and sub/main URIs differ
 */

#define subreq_ok(r) (!r->main || \
    (r->main->uri && r->uri && strcmp(r->main->uri, r->uri)))

#ifndef REWRITE_MAX_ROUNDS
#define REWRITE_MAX_ROUNDS 32000
#endif

/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                 Types and Structures
 * |                                                       |
 * +-------------------------------------------------------+
 */


typedef struct {
    const char *datafile;          /* filename for map data files         */
    const char *dbmtype;           /* dbm type for dbm map data files     */
    const char *checkfile;         /* filename to check for map existence */
    const char *cachename;         /* for cached maps (txt/rnd/dbm)       */
    int   type;                    /* the type of the map                 */
    apr_file_t *fpin;              /* in  file pointer for program maps   */
    apr_file_t *fpout;             /* out file pointer for program maps   */
    apr_file_t *fperr;             /* err file pointer for program maps   */
    char *(*func)(request_rec *,   /* function pointer for internal maps  */
                  char *);
    char **argv;                   /* argv of the external rewrite map    */
    const char *dbdq;              /* SQL SELECT statement for rewritemap */
    const char *checkfile2;        /* filename to check for map existence
                                      NULL if only one file               */

    const char *user;              /* run RewriteMap program as this user */
    const char *group;             /* run RewriteMap program as this group */
} rewritemap_entry;

/* special pattern types for RewriteCond */
typedef enum {
    CONDPAT_REGEX = 0,
    CONDPAT_FILE_EXISTS,
    CONDPAT_FILE_SIZE,
    CONDPAT_FILE_LINK,
    CONDPAT_FILE_DIR,
    CONDPAT_FILE_XBIT,
    CONDPAT_LU_URL,
    CONDPAT_LU_FILE,
    CONDPAT_STR_LT,
    CONDPAT_STR_LE,
    CONDPAT_STR_EQ,
    CONDPAT_STR_GT,
    CONDPAT_STR_GE,
    CONDPAT_INT_LT,
    CONDPAT_INT_LE,
    CONDPAT_INT_EQ,
    CONDPAT_INT_GT,
    CONDPAT_INT_GE,
    CONDPAT_AP_EXPR
} pattern_type;

typedef enum {
  RULE_RC_NOMATCH = 0,      /* the rule didn't match                        */
  RULE_RC_MATCH = 1,        /* a matching rule w/ substitution              */
  RULE_RC_NOSUB = 2,        /* a matching rule w/ no substitution           */
  RULE_RC_STATUS_SET = 3    /* a matching rule that has set an HTTP error
                               to be returned in r->status */

} rule_return_type;

typedef enum {
  COND_RC_NOMATCH = 0,      /* the cond didn't match                        */
  COND_RC_MATCH = 1,        /* the cond matched                             */
  COND_RC_STATUS_SET = 3    /* The condition eval set a final r->status     */
} cond_return_type;

typedef struct {
    char           *input;   /* Input string of RewriteCond   */
    char           *pattern; /* the RegExp pattern string     */
    ap_regex_t     *regexp;  /* the precompiled regexp        */
    ap_expr_info_t *expr;    /* the compiled ap_expr          */
    int             flags;   /* Flags which control the match */
    pattern_type    ptype;   /* pattern type                  */
    int             pskip;   /* back-index to display pattern */
} rewritecond_entry;

/* single linked list for env vars and cookies */
typedef struct data_item {
    struct data_item *next;
    char *data;
} data_item;

typedef struct {
    apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */
    char      *pattern;              /* the RegExp pattern string             */
    ap_regex_t *regexp;              /* the RegExp pattern compilation        */
    char      *output;               /* the Substitution string               */
    int        flags;                /* Flags which control the substitution  */
    char      *forced_mimetype;      /* forced MIME type of substitution      */
    char      *forced_handler;       /* forced content handler of subst.      */
    int        forced_responsecode;  /* forced HTTP response status           */
    data_item *env;                  /* added environment variables           */
    data_item *cookie;               /* added cookies                         */
    int        skip;                 /* number of next rules to skip          */
    int        maxrounds;            /* limit on number of loops with N flag  */
    const char *escapes;             /* specific backref escapes              */
    const char *noescapes;           /* specific backref chars not to escape  */
} rewriterule_entry;

typedef struct {
    int           state;              /* the RewriteEngine state            */
    int           options;            /* the RewriteOption state            */
    apr_hash_t         *rewritemaps;  /* the RewriteMap entries             */
    apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.)    */
    apr_array_header_t *rewriterules; /* the RewriteRule entries            */
    server_rec   *server;             /* the corresponding server indicator */
    unsigned int state_set:1;
    unsigned int options_set:1;
} rewrite_server_conf;

typedef struct {
    int           state;              /* the RewriteEngine state           */
    int           options;            /* the RewriteOption state           */
    apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.)   */
    apr_array_header_t *rewriterules; /* the RewriteRule entries           */
    char         *directory;          /* the directory where it applies    */
    const char   *baseurl;            /* the base-URL  where it applies    */
    unsigned int state_set:1;
    unsigned int options_set:1;
    unsigned int baseurl_set:1;
} rewrite_perdir_conf;

/* the (per-child) cache structures.
 */

typedef struct cache {
    apr_pool_t         *pool;
    apr_hash_t         *maps;
#if APR_HAS_THREADS
    apr_thread_mutex_t *lock;
#endif
} cache;

/* cached maps contain an mtime for the whole map and live in a subpool
 * of the cachep->pool. That makes it easy to forget them if necessary.
 */

typedef struct {
    apr_time_t mtime;
    apr_pool_t *pool;
    apr_hash_t *entries;
} cachedmap;

/* the regex structure for the
 * substitution of backreferences
 */

typedef struct backrefinfo {
    const char *source;
    ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
} backrefinfo;

/* single linked list used for
 * variable expansion
 */

typedef struct result_list {
    struct result_list *next;
    apr_size_t len;
    const char *string;
} result_list;

/* context structure for variable lookup and expansion
 */

typedef struct {
    request_rec *r;
    const char  *uri;
    const char  *vary_this;
    const char  *vary;
    char        *perdir;
    backrefinfo briRR;
    backrefinfo briRC;
} rewrite_ctx;

/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                 static module data
 * |                                                       |
 * +-------------------------------------------------------+
 */


/* the global module structure */
module AP_MODULE_DECLARE_DATA rewrite_module;

/* rewritemap int: handler function registry */
static apr_hash_t *mapfunc_hash;

/* the cache */
static cache *cachep;

/* whether proxy module is available or not */
static int proxy_available;

/* Locks/Mutexes */
static int rewrite_lock_needed = 0;
static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL;
static const char *rewritemap_mutex_type = "rewrite-map";

/* Optional functions imported from mod_ssl when loaded: */
static char *escape_backref(apr_pool_t *p, const char *path,
                            const char *escapeme, const char *noescapeme,
                            int flags);

/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |              rewriting logfile support
 * |                                                       |
 * +-------------------------------------------------------+
 */


/* Slightly complicated definitions follow here to support
 * compile-time choice of enabled/disabled of rewritelog both with or
 * without C99 support, where varargs macros are only available with
 * C99.
 * 
 * do_rewritelog is defined for (rewritelog enabled) or (rewritelog
 * disabled AND no C99 support) but is an noop for the latter case and
 * should get optimized away.
 *
 * For the (rewritelog disabled) case the function is defined
 * differently for C99/non-C99. For C99, the rewritelog() macro passes
 * __LINE__, allowing accurate logging of line numbers. For non-C99
 * the line number used for rewritelog() tracing is always
 * constant. */


#if !defined(REWRITELOG_DISABLED) || !defined(AP_HAVE_C99)

#ifdef AP_HAVE_C99
static void do_rewritelog(int line,
                          request_rec *r, int level, char *perdir,
                          const char *fmt, ...)
        __attribute__((format(printf,5,6)));
#else
static void do_rewritelog(request_rec *r, int level, char *perdir,
                          const char *fmt, ...)
        __attribute__((format(printf,4,5)));
#endif

static void do_rewritelog(
#ifdef AP_HAVE_C99
                          int line,
#endif
                          request_rec *r, int level, char *perdir,
                          const char *fmt, ...)
{
#ifndef REWRITELOG_DISABLED
    char *logline, *text;
    const char *rhost, *rname;
    int redir;
    request_rec *req;
    va_list ap;

    if (!APLOG_R_IS_LEVEL(r, APLOG_DEBUG + level))
        return;

    rhost = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL);
    rname = ap_get_remote_logname(r);

    for (redir=0, req=r; req->prev; req = req->prev) {
        ++redir;
    }

    va_start(ap, fmt);
    text = apr_pvsprintf(r->pool, fmt, ap);
    va_end(ap);

    logline = apr_psprintf(r->pool, "%s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] "
                                    "%s%s%s%s",
                           rhost ? rhost : "UNKNOWN-HOST",
                           rname ? rname : "-",
                           r->user ? (*r->user ? r->user : "\"\"") : "-",
                           ap_get_server_name(r),
                           (void *)(r->server),
                           (void *)r,
                           r->main ? "subreq" : "initial",
                           redir ? "/redir#" : "",
                           redir ? apr_itoa(r->pool, redir) : "",
                           perdir ? "[perdir " : "",
                           perdir ? perdir : "",
                           perdir ? "] """,
                           text);

    AP_REWRITE_LOG((uintptr_t)r, level, r->main ? 0 : 1, (char *)ap_get_server_name(r), logline);

    /* Intentional no APLOGNO */
#ifdef AP_HAVE_C99
    ap_log_rerror(__FILE__, line, APLOG_MODULE_INDEX, APLOG_DEBUG + level, 0, r, "%s", logline);
#else
    ap_log_rerror(APLOG_MARK, APLOG_DEBUG + level, 0, r, "%s", logline);
#endif
    
#endif /* !REWRITELOG_DISABLED */
}
#endif /* !defined(REWRITELOG_DISABLED) || !defined(AP_HAVE_C99) */


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                URI and path functions
 * |                                                       |
 * +-------------------------------------------------------+
 */


/* return number of chars of the scheme (incl. '://')
 * if the URI is absolute (includes a scheme etc.)
 * otherwise 0.
 * If supportqs is not NULL, we return a whether or not
 * the scheme supports a query string or not.
 *
 * NOTE: If you add new schemes here, please have a
 *       look at escape_absolute_uri and splitout_queryargs.
 *       Not every scheme takes query strings and some schemes
 *       may be handled in a special way.
 *
 * XXX: we may consider a scheme registry, perhaps with
 *      appropriate escape callbacks to allow other modules
 *      to extend mod_rewrite at runtime.
 */

static unsigned is_absolute_uri(char *uri, int *supportsqs)
{
    int dummy, *sqs;

    sqs = (supportsqs ? supportsqs : &dummy);
    *sqs = 0;
    /* fast exit */
    if (*uri == '/' || strlen(uri) <= 5) {
        return 0;
    }

    switch (*uri++) {
    case 'a':
    case 'A':
        if (!ap_cstr_casecmpn(uri, "jp://", 5)) {        /* ajp://    */
          *sqs = 1;
          return 6;
        }
        break;

    case 'b':
    case 'B':
        if (!ap_cstr_casecmpn(uri, "alancer://", 10)) {   /* balancer:// */
          *sqs = 1;
          return 11;
        }
        break;

    case 'f':
    case 'F':
        if (!ap_cstr_casecmpn(uri, "tp://", 5)) {        /* ftp://    */
            return 6;
        }
        if (!ap_cstr_casecmpn(uri, "cgi://", 6)) {       /* fcgi://   */
            *sqs = 1;
            return 7;
        }
        break;

    case 'g':
    case 'G':
        if (!ap_cstr_casecmpn(uri, "opher://", 8)) {     /* gopher:// */
            return 9;
        }
        break;

    case 'h':
    case 'H':
        if (!ap_cstr_casecmpn(uri, "ttp://", 6)) {       /* http://   */
            *sqs = 1;
            return 7;
        }
        else if (!ap_cstr_casecmpn(uri, "ttps://", 7)) { /* https://  */
            *sqs = 1;
            return 8;
        }
        else if (!ap_cstr_casecmpn(uri, "2://", 4)) {    /* h2://     */
            *sqs = 1;
            return 5;
        }
        else if (!ap_cstr_casecmpn(uri, "2c://", 5)) {   /* h2c://    */
            *sqs = 1;
            return 6;
        }
        break;

    case 'l':
    case 'L':
        if (!ap_cstr_casecmpn(uri, "dap://", 6)) {       /* ldap://   */
            return 7;
        }
        break;

    case 'm':
    case 'M':
        if (!ap_cstr_casecmpn(uri, "ailto:", 6)) {       /* mailto:   */
            *sqs = 1;
            return 7;
        }
        break;

    case 'n':
    case 'N':
        if (!ap_cstr_casecmpn(uri, "ews:", 4)) {         /* news:     */
            return 5;
        }
        else if (!ap_cstr_casecmpn(uri, "ntp://", 6)) {  /* nntp://   */
            return 7;
        }
        break;

    case 's':
    case 'S':
        if (!ap_cstr_casecmpn(uri, "cgi://", 6)) {       /* scgi://   */
            *sqs = 1;
            return 7;
        }
        break;

    case 'w':
    case 'W':
        if (!ap_cstr_casecmpn(uri, "s://", 4)) {        /* ws://     */
            *sqs = 1;
            return 5;
        }
        else if (!ap_cstr_casecmpn(uri, "ss://", 5)) {  /* wss://    */
            *sqs = 1;
            return 6;
        }
        break;

    case 'u':
    case 'U':
        if (!ap_cstr_casecmpn(uri, "nix:", 4)) {        /* unix:     */
            *sqs = 1;
            return (uri[4] == '/' && uri[5] == '/') ? 7 : 5;
        }
    }

    return 0;
}

static int is_absolute_path(const char *path)
{
#ifndef CASE_BLIND_FILESYSTEM
    return (path[0] == '/');
#else
    return ((AP_IS_SLASH(path[0]) && path[1] == path[0])
            || (apr_isalpha(path[0]) && path[1] == ':' && AP_IS_SLASH(path[2])));
#endif
}

static const char c2x_table[] = "0123456789abcdef";

static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
                                     unsigned char *where)
{
#if APR_CHARSET_EBCDIC
    what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
#endif /*APR_CHARSET_EBCDIC*/
    *where++ = prefix;
    *where++ = c2x_table[what >> 4];
    *where++ = c2x_table[what & 0xf];
    return where;
}

/*
 * Escapes a backreference in a similar way as php's urlencode does.
 * Based on ap_os_escape_path in server/util.c
 */

static char *escape_backref(apr_pool_t *p, const char *path,
                            const char *escapeme, const char *noescapeme,
                            int flags)
{
    char *copy = apr_palloc(p, 3 * strlen(path) + 1);
    const unsigned char *s = (const unsigned char *)path;
    unsigned char *d = (unsigned char *)copy;
    int noplus = (flags & RULEFLAG_ESCAPENOPLUS) != 0;
    int ctls = (flags & RULEFLAG_ESCAPECTLS) != 0;
    unsigned char c;

    while ((c = *s)) {
        if (((ctls ? !TEST_CHAR(c, T_VCHAR_OBSTEXT) : !escapeme)
             || (escapeme && ap_strchr_c(escapeme, c)))
            && (!noescapeme || !ap_strchr_c(noescapeme, c))) {
            if (apr_isalnum(c) || c == '_') {
                *d++ = c;
            }
            else if (c == ' ' && !noplus) {
                *d++ = '+';
            }
            else {
                d = c2x(c, '%', d);
            }
        }
        else {
            *d++ = c;
        }
        ++s;
    }
    *d = '\0';
    return copy;
}

/*
 * escape absolute uri, which may or may not be path oriented.
 * So let's handle them differently.
 */

static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme)
{
    char *cp;

    /* be safe.
     * NULL should indicate elsewhere, that something's wrong
     */

    if (!scheme || strlen(uri) < scheme) {
        return NULL;
    }

    cp = uri + scheme;

    /* scheme with authority part? */
    if (cp[-1] == '/') {
        /* skip host part */
        while (*cp && *cp != '/') {
            ++cp;
        }

        /* nothing after the hostpart. ready! */
        if (!*cp || !*++cp) {
            return apr_pstrdup(p, uri);
        }

        /* remember the hostname stuff */
        scheme = cp - uri;

        /* special thing for ldap.
         * The parts are separated by question marks. From RFC 2255:
         *     ldapurl = scheme "://" [hostport] ["/"
         *               [dn ["?" [attributes] ["?" [scope]
         *               ["?" [filter] ["?" extensions]]]]]]
         */

        if (!ap_cstr_casecmpn(uri, "ldap", 4)) {
            char *token[5];
            int c = 0;

            token[0] = cp = apr_pstrdup(p, cp);
            while (*cp && c < 4) {
                if (*cp == '?') {
                    token[++c] = cp + 1;
                    *cp = '\0';
                }
                ++cp;
            }

            return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
                                          ap_escape_uri(p, token[0]),
                               (c >= 1) ? "?" : NULL,
                               (c >= 1) ? ap_escape_uri(p, token[1]) : NULL,
                               (c >= 2) ? "?" : NULL,
                               (c >= 2) ? ap_escape_uri(p, token[2]) : NULL,
                               (c >= 3) ? "?" : NULL,
                               (c >= 3) ? ap_escape_uri(p, token[3]) : NULL,
                               (c >= 4) ? "?" : NULL,
                               (c >= 4) ? ap_escape_uri(p, token[4]) : NULL,
                               NULL);
        }
    }

    /* Nothing special here. Apply normal escaping. */
    return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
                       ap_escape_uri(p, cp), NULL);
}

/*
 * split out a QUERY_STRING part from
 * the current URI string
 */

static void splitout_queryargs(request_rec *r, int flags)
{
    char *q;
    int split, skip;
    int qsappend = flags & RULEFLAG_QSAPPEND;
    int qsdiscard = flags & RULEFLAG_QSDISCARD;
    int qslast = flags & RULEFLAG_QSLAST;

    if (flags & RULEFLAG_QSNONE) {
        rewritelog(r, 2, NULL, "discarding query string, no parse from substitution");
        r->args = NULL;
        return;
    }

    /* don't touch, unless it's a scheme for which a query string makes sense.
     * See RFC 1738 and RFC 2368.
     */

    if ((skip = is_absolute_uri(r->filename, &split))
        && !split) {
        r->args = NULL; /* forget the query that's still flying around */
        return;
    }

    if (qsdiscard) {
        r->args = NULL; /* Discard query string */
        rewritelog(r, 2, NULL, "discarding query string");
    }

    q = qslast ? ap_strrchr(r->filename + skip, '?') : ap_strchr(r->filename + skip, '?');

    if (q != NULL) {
        char *olduri;
        apr_size_t len;

        olduri = apr_pstrdup(r->pool, r->filename);
        *q++ = '\0';
        if (qsappend) {
            if (*q) {
                r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL);
            }
        }
        else {
            r->args = apr_pstrdup(r->pool, q);
        }

        if (r->args) {
           len = strlen(r->args);

           if (!len) {
               r->args = NULL;
           }
           else if (r->args[len-1] == '&') {
               r->args[len-1] = '\0';
           }
        }

        rewritelog(r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri,
                   r->filename, r->args ? r->args : "<none>");
    }
}

/*
 * strip 'http[s]://ourhost/' from URI
 */

static void reduce_uri(request_rec *r)
{
    char *cp;
    apr_size_t l;

    cp = (char *)ap_http_scheme(r);
    l  = strlen(cp);
    if (   strlen(r->filename) > l+3
        && ap_cstr_casecmpn(r->filename, cp, l) == 0
        && r->filename[l]   == ':'
        && r->filename[l+1] == '/'
        && r->filename[l+2] == '/' ) {

        unsigned short port;
        char *portp, *host, *url, *scratch;

        scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */

        /* cut the hostname and port out of the URI */
        cp = host = scratch + l + 3;    /* 3 == strlen("://") */
        while (*cp && *cp != '/' && *cp != ':') {
            ++cp;
        }

        if (*cp == ':') {      /* additional port given */
            *cp++ = '\0';
            portp = cp;
            while (*cp && *cp != '/') {
                ++cp;
            }
            *cp = '\0';

            port = atoi(portp);
            url = r->filename + (cp - scratch);
            if (!*url) {
                url = "/";
            }
        }
        else if (*cp == '/') { /* default port */
            *cp = '\0';

            port = ap_default_port(r);
            url = r->filename + (cp - scratch);
        }
        else {
            port = ap_default_port(r);
            url = "/";
        }

        /* now check whether we could reduce it to a local path... */
        if (ap_matches_request_vhost(r, host, port)) {
            rewrite_server_conf *conf = 
                ap_get_module_config(r->server->module_config, &rewrite_module);
            rewritelog(r, 3, NULL, "reduce %s -> %s", r->filename, url);
            r->filename = apr_pstrdup(r->pool, url);

            /* remember that the uri was reduced */
            if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
                apr_table_setn(r->notes, "mod_rewrite_uri_reduced""true");
            }
        }
    }

    return;
}

/*
 * add 'http[s]://ourhost[:ourport]/' to URI
 * if URI is still not fully qualified
 */

static void fully_qualify_uri(request_rec *r)
{
    if (r->method_number == M_CONNECT) {
        return;
    }
    else if (!is_absolute_uri(r->filename, NULL)) {
        const char *thisserver;
        char *thisport;
        int port;

        thisserver = ap_get_server_name_for_url(r);
        port = ap_get_server_port(r);
        thisport = ap_is_default_port(port, r)
                   ? ""
                   : apr_psprintf(r->pool, ":%u", port);

        r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s",
                                   ap_http_scheme(r), thisserver, thisport,
                                   (*r->filename == '/') ? "" : "/",
                                   r->filename);
    }

    return;
}

static int startsWith(request_rec *r, const char *haystack, const char *needle) {
    int rc = (ap_strstr_c(haystack, needle) == haystack);
    rewritelog(r, 5, NULL, "prefix_stat startsWith(%s, %s) %d", haystack, needle, rc);
    return rc;
}
/*
 * stat() only the first segment of a path, and only if it matches the output of the last matching rule
 */

static int prefix_stat(request_rec *r, const char *path, apr_pool_t *pool, rewriterule_entry *lastsub)
{
    const char *curpath = path;
    const char *root;
    const char *slash;
    char *statpath;
    apr_status_t rv;

    rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool);

    if (rv != APR_SUCCESS) {
        return 0;
    }

    /* let's recognize slashes only, the mod_rewrite semantics are opaque
     * enough.
     */

    if ((slash = ap_strchr_c(curpath, '/')) != NULL) {
        rv = apr_filepath_merge(&statpath, root,
                                apr_pstrndup(pool, curpath,
                                             (apr_size_t)(slash - curpath)),
                                APR_FILEPATH_NOTABOVEROOT |
                                APR_FILEPATH_NOTRELATIVE, pool);
    }
    else {
        rv = apr_filepath_merge(&statpath, root, curpath,
                                APR_FILEPATH_NOTABOVEROOT |
                                APR_FILEPATH_NOTRELATIVE, pool);
    }

    if (rv == APR_SUCCESS) {
        apr_finfo_t sb;

        if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) {
            if (!lastsub) {
                rewritelog(r, 3, NULL, "prefix_stat no lastsub subst prefix %s", statpath);
                return 1;
            }

            rewritelog(r, 3, NULL, "prefix_stat compare statpath %s and lastsub output %s STATOK %d ",
                    statpath, lastsub->output, lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT);
            if (lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT) {
                return 1;
            }
            else {
                const char *docroot = ap_document_root(r);
                const char *context_docroot = ap_context_document_root(r);
                /*
                 * As an example, path (r->filename) is /var/foo/bar/baz.html
                 * even if the flag is not set,  we can accept a rule that
                 * began with a literal /var (stapath), or if the entire path
                 * starts with the docroot or context document root
                 */

                if (startsWith(r, lastsub->output, statpath) ||
                        startsWith(r, path, docroot) ||
                        ((docroot != context_docroot) &&
                          startsWith(r, path, context_docroot))) {
                    return 1;
                }
            }
        }
    }

    /* prefix will be added */
    return 0;
}

/*
 * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase)
 */

static char *subst_prefix_path(request_rec *r, char *input, const char *match,
                               const char *subst)
{
    apr_size_t len = strlen(match);

    if (len && match[len - 1] == '/') {
        --len;
    }

    if (!strncmp(input, match, len) && input[len++] == '/') {
        apr_size_t slen, outlen;
        char *output;

        rewritelog(r, 5, NULL, "strip matching prefix: %s -> %s", input,
                   input+len);

        slen = strlen(subst);
        if (slen && subst[slen - 1] != '/') {
            ++slen;
        }

        outlen = strlen(input) + slen - len;
        output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */

        memcpy(output, subst, slen);
        if (slen && !output[slen-1]) {
            output[slen-1] = '/';
        }
        memcpy(output+slen, input+len, outlen - slen);
        output[outlen] = '\0';

        rewritelog(r, 4, NULL, "add subst prefix: %s -> %s", input+len,
                   output);

        return output;
    }

    /* prefix didn't match */
    return input;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                    caching support
 * |                                                       |
 * +-------------------------------------------------------+
 */


static void set_cache_value(const char *name, apr_time_t t, char *key,
                            char *val)
{
    cachedmap *map;

    if (cachep) {
#if APR_HAS_THREADS
        apr_thread_mutex_lock(cachep->lock);
#endif
        map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);

        if (!map) {
            apr_pool_t *p;

            if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) {
#if APR_HAS_THREADS
                apr_thread_mutex_unlock(cachep->lock);
#endif
                return;
            }
            apr_pool_tag(p, "rewrite_cachedmap");

            map = apr_palloc(cachep->pool, sizeof(cachedmap));
            map->pool = p;
            map->entries = apr_hash_make(map->pool);
            map->mtime = t;

            apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map);
        }
        else if (map->mtime != t) {
            apr_pool_clear(map->pool);
            map->entries = apr_hash_make(map->pool);
            map->mtime = t;
        }

        /* Now we should have a valid map->entries hash, where we
         * can store our value.
         *
         * We need to copy the key and the value into OUR pool,
         * so that we don't leave it during the r->pool cleanup.
         */

        apr_hash_set(map->entries,
                     apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING,
                     apr_pstrdup(map->pool, val));

#if APR_HAS_THREADS
        apr_thread_mutex_unlock(cachep->lock);
#endif
    }

    return;
}

static char *get_cache_value(const char *name, apr_time_t t, char *key,
                             apr_pool_t *p)
{
    cachedmap *map;
    char *val = NULL;

    if (cachep) {
#if APR_HAS_THREADS
        apr_thread_mutex_lock(cachep->lock);
#endif
        map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);

        if (map) {
            /* if this map is outdated, forget it. */
            if (map->mtime != t) {
                apr_pool_clear(map->pool);
                map->entries = apr_hash_make(map->pool);
                map->mtime = t;
            }
            else {
                val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING);
                if (val) {
                    /* copy the cached value into the supplied pool,
                     * where it belongs (r->pool usually)
                     */

                    val = apr_pstrdup(p, val);
                }
            }
        }

#if APR_HAS_THREADS
        apr_thread_mutex_unlock(cachep->lock);
#endif
    }

    return val;
}

static int init_cache(apr_pool_t *p)
{
    cachep = apr_palloc(p, sizeof(cache));
    if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) {
        cachep = NULL; /* turns off cache */
        return 0;
    }
    apr_pool_tag(cachep->pool, "rewrite_cachep");

    cachep->maps = apr_hash_make(cachep->pool);
#if APR_HAS_THREADS
    (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p);
#endif

    return 1;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                    Map Functions
 * |                                                       |
 * +-------------------------------------------------------+
 */


/*
 * General Note: key is already a fresh string, created (expanded) just
 * for the purpose to be passed in here. So one can modify key itself.
 */


static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
{
    ap_str_toupper(key);

    return key;
}

static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
{
    ap_str_tolower(key);

    return key;
}

static char *rewrite_mapfunc_escape(request_rec *r, char *key)
{
    return ap_escape_uri(r->pool, key);
}

static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
{
    ap_unescape_url(key);

    return key;
}

static char *select_random_value_part(request_rec *r, char *value)
{
    char *p = value;
    unsigned n = 1;

    /* count number of distinct values */
    while ((p = ap_strchr(p, '|')) != NULL) {
        ++n;
        ++p;
    }

    if (n > 1) {
        n = ap_random_pick(1, n);

        /* extract it from the whole string */
        while (--n && (value = ap_strchr(value, '|')) != NULL) {
            ++value;
        }

        if (value) { /* should not be NULL, but ... */
            p = ap_strchr(value, '|');
            if (p) {
                *p = '\0';
            }
        }
    }

    return value;
}

/* child process code */
static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err,
                                const char *desc)
{
    ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00653) "%s", desc);
}

static apr_status_t rewritemap_program_child(apr_pool_t *p,
                                             const char *progname, char **argv,
                                             const char *user, const char *group,
                                             apr_file_t **fpout,
                                             apr_file_t **fpin)
{
    apr_status_t rc;
    apr_procattr_t *procattr;
    apr_proc_t *procnew;

    if (   APR_SUCCESS == (rc=apr_procattr_create(&procattr, p))
        && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK,
                                                  APR_FULL_BLOCK, APR_NO_PIPE))
        && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr,
                                             ap_make_dirstr_parent(p, argv[0])))
        && (!user || APR_SUCCESS == (rc=apr_procattr_user_set(procattr, user, "")))
        && (!group || APR_SUCCESS == (rc=apr_procattr_group_set(procattr, group)))
        && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
        && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr,
                                                           rewrite_child_errfn))
        && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) {

        procnew = apr_pcalloc(p, sizeof(*procnew));
        rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL,
                             procattr, p);

        if (rc == APR_SUCCESS) {
            apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);

            if (fpin) {
                (*fpin) = procnew->in;
            }

            if (fpout) {
                (*fpout) = procnew->out;
            }
        }
    }

    return (rc);
}

static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p)
{
    rewrite_server_conf *conf;
    apr_hash_index_t *hi;
    apr_status_t rc;

    conf = ap_get_module_config(s->module_config, &rewrite_module);

    /*  If the engine isn't turned on,
     *  don't even try to do anything.
     */

    if (conf->state == ENGINE_DISABLED) {
        return APR_SUCCESS;
    }

    for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){
        apr_file_t *fpin = NULL;
        apr_file_t *fpout = NULL;
        rewritemap_entry *map;
        void *val;

        apr_hash_this(hi, NULL, NULL, &val);
        map = val;

        if (map->type != MAPTYPE_PRG) {
            continue;
        }
        if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) {
            continue;
        }

        rc = rewritemap_program_child(p, map->argv[0], map->argv,
                                      map->user, map->group,
                                      &fpout, &fpin);
        if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00654)
                         "mod_rewrite: could not start RewriteMap "
                         "program %s", map->checkfile);
            return rc;
        }
        map->fpin  = fpin;
        map->fpout = fpout;
    }

    return APR_SUCCESS;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                  Lookup functions
 * |                                                       |
 * +-------------------------------------------------------+
 */


static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
{
    apr_file_t *fp = NULL;
    char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */
    char *value, *keylast;
    apr_status_t rv;

    if ((rv = apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT,
                            r->pool)) != APR_SUCCESS)
    {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00655)
                      "mod_rewrite: can't open text RewriteMap file %s", file);
        return NULL;
    }

    keylast = key + strlen(key);
    value = NULL;
    while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) {
        char *p, *c;

        /* ignore comments and lines starting with whitespaces */
        if (*line == '#' || apr_isspace(*line)) {
            continue;
        }

        p = line;
        c = key;
        while (c < keylast && *p == *c && !apr_isspace(*p)) {
            ++p;
            ++c;
        }

        /* key doesn't match - ignore. */
        if (c != keylast || !apr_isspace(*p)) {
            continue;
        }

        /* jump to the value */
        while (apr_isspace(*p)) {
            ++p;
        }

        /* no value? ignore */
        if (!*p) {
            continue;
        }

        /* extract the value and return. */
        c = p;
        while (*p && !apr_isspace(*p)) {
            ++p;
        }
        value = apr_pstrmemdup(r->pool, c, p - c);
        break;
    }
    apr_file_close(fp);

    return value;
}

static char *lookup_map_dbmfile(request_rec *r, const char *file,
                                const char *dbmtype, char *key)
{
#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
    const apr_dbm_driver_t *driver;
    const apu_err_t *err;
#endif
    apr_dbm_t *dbmfp = NULL;
    apr_datum_t dbmkey;
    apr_datum_t dbmval;
    char *value;
    apr_status_t rv;

#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7)
    if ((rv = apr_dbm_get_driver(&driver, dbmtype, &err,
            r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10287)
                "mod_rewrite: can't load DBM library '%s': %s",
                     err->reason, err->msg);
        return NULL;
    }
    if ((rv = apr_dbm_open2(&dbmfp, driver, file, APR_DBM_READONLY,
                              APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656)
                      "mod_rewrite: can't open DBM RewriteMap %s", file);
        return NULL;
    }
#else
    if ((rv = apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY,
                              APR_OS_DEFAULT, r->pool)) != APR_SUCCESS)
    {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656)
                      "mod_rewrite: can't open DBM RewriteMap %s", file);
        return NULL;
    }
#endif

    dbmkey.dptr  = key;
    dbmkey.dsize = strlen(key);

    if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) {
        value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize);
    }
    else {
        value = NULL;
    }

    apr_dbm_close(dbmfp);

    return value;
}
static char *lookup_map_dbd(request_rec *r, char *key, const char *label)
{
    apr_status_t rv;
    apr_dbd_prepared_t *stmt;
    const char *errmsg;
    apr_dbd_results_t *res = NULL;
    apr_dbd_row_t *row = NULL;
    char *ret = NULL;
    int n = 0;
    ap_dbd_t *db = dbd_acquire(r);
    
    if (db == NULL) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02963)
                      "rewritemap: No db handle available! "
                      "Check your database access");
        return NULL;
   }

    stmt = apr_hash_get(db->prepared, label, APR_HASH_KEY_STRING);

    rv = apr_dbd_pvselect(db->driver, r->pool, db->handle, &res,
                          stmt, 0, key, NULL);
    if (rv != 0) {
        errmsg = apr_dbd_error(db->driver, db->handle, rv);
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00657)
                      "rewritemap: error %s querying for %s", errmsg, key);
        return NULL;
    }
    while ((rv = apr_dbd_get_row(db->driver, r->pool, res, &row, -1)) == 0) {
        ++n;
        if (ret == NULL) {
            ret = apr_pstrdup(r->pool,
                              apr_dbd_get_entry(db->driver, row, 0));
        }
        else {
            /* randomise crudely amongst multiple results */
            if ((double)rand() < (double)RAND_MAX/(double)n) {
                ret = apr_pstrdup(r->pool,
                                  apr_dbd_get_entry(db->driver, row, 0));
            }
        }
    }
    if (rv != -1) {
        errmsg = apr_dbd_error(db->driver, db->handle, rv);
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00658)
                      "rewritemap: error %s looking up %s", errmsg, key);
    }
    switch (n) {
    case 0:
        return NULL;
    case 1:
        return ret;
    default:
        /* what's a fair rewritelog level for this? */
        rewritelog(r, 3, NULL, "Multiple values found for %s", key);
        return ret;
    }
}

static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
                                apr_file_t *fpout, char *key)
{
    char *buf;
    char c;
    apr_size_t i, nbytes, combined_len = 0;
    apr_status_t rv;
    const char *eol = APR_EOL_STR;
    apr_size_t eolc = 0;
    int found_nl = 0;
    result_list *buflist = NULL, *curbuf = NULL;

#ifndef NO_WRITEV
    struct iovec iova[2];
    apr_size_t niov;
#endif

    /* when `RewriteEngine off' was used in the per-server
     * context then the rewritemap-programs were not spawned.
     * In this case using such a map (usually in per-dir context)
     * is useless because it is not available.
     *
     * newlines in the key leave bytes in the pipe and cause
     * bad things to happen (next map lookup will use the chars
     * after the \n instead of the new key etc etc - in other words,
     * the Rewritemap falls out of sync with the requests).
     */

    if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) {
        return NULL;
    }

    /* take the lock */
    if (rewrite_mapr_lock_acquire) {
        rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00659)
                          "apr_global_mutex_lock(rewrite_mapr_lock_acquire) "
                          "failed");
            return NULL; /* Maybe this should be fatal? */
        }
    }

    /* write out the request key */
#ifdef NO_WRITEV
    nbytes = strlen(key);
    /* XXX: error handling */
    apr_file_write_full(fpin, key, nbytes, NULL);
    nbytes = 1;
    apr_file_write_full(fpin, "\n", nbytes, NULL);
#else
    iova[0].iov_base = key;
    iova[0].iov_len = strlen(key);
    iova[1].iov_base = "\n";
    iova[1].iov_len = 1;

    niov = 2;
    /* XXX: error handling */
    apr_file_writev_full(fpin, iova, niov, &nbytes);
#endif

    buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1);

    /* read in the response value */
    nbytes = 1;
    apr_file_read(fpout, &c, &nbytes);
    do {
        i = 0;
        while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) {
            if (c == eol[eolc]) {
                if (!eol[++eolc]) {
                    /* remove eol from the buffer */
                    --eolc;
                    if (i < eolc) {
                        curbuf->len -= eolc-i;
                        i = 0;
                    }
                    else {
                        i -= eolc;
                    }
                    ++found_nl;
                    break;
                }
            }

            /* only partial (invalid) eol sequence -> reset the counter */
            else if (eolc) {
                eolc = 0;
            }

            /* catch binary mode, e.g. on Win32 */
            else if (c == '\n') {
                ++found_nl;
                break;
            }

            buf[i++] = c;
            apr_file_read(fpout, &c, &nbytes);
        }

        /* well, if there wasn't a newline yet, we need to read further */
        if (buflist || (nbytes == 1 && !found_nl)) {
            if (!buflist) {
                curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist));
            }
            else if (i) {
                curbuf->next = apr_palloc(r->pool, sizeof(*buflist));
                curbuf = curbuf->next;

            }
            curbuf->next = NULL;

            if (i) {
                curbuf->string = buf;
                curbuf->len = i;
                combined_len += i;
                buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF);
            }

            if (nbytes == 1 && !found_nl) {
                continue;
            }
        }

        break;
    } while (1);

    /* concat the stuff */
    if (buflist) {
        char *p;

        p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */
        while (buflist) {
            if (buflist->len) {
                memcpy(p, buflist->string, buflist->len);
                p += buflist->len;
            }
            buflist = buflist->next;
        }
        *p = '\0';
        i = combined_len;
    }
    else {
        buf[i] = '\0';
    }

    /* give the lock back */
    if (rewrite_mapr_lock_acquire) {
        rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00660)
                          "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) "
                          "failed");
            return NULL; /* Maybe this should be fatal? */
        }
    }

    /* catch the "failed" case */
    if (i == 4 && !strcasecmp(buf, "NULL")) {
        return NULL;
    }

    return buf;
}

/*
 * generic map lookup
 */

static char *lookup_map(request_rec *r, char *name, char *key)
{
    rewrite_server_conf *conf;
    rewritemap_entry *s;
    char *value;
    apr_finfo_t st;
    apr_status_t rv;

    /* get map configuration */
    conf = ap_get_module_config(r->server->module_config, &rewrite_module);
    s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING);

    /* map doesn't exist */
    if (!s) {
        return NULL;
    }

    switch (s->type) {
    /*
     * Text file map (perhaps random)
     */

    case MAPTYPE_RND:
    case MAPTYPE_TXT:
        rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00661)
                          "mod_rewrite: can't access text RewriteMap file %s",
                          s->checkfile);
            return NULL;
        }

        value = get_cache_value(s->cachename, st.mtime, key, r->pool);
        if (!value) {
            rewritelog(r, 6, NULL,
                       "cache lookup FAILED, forcing new map lookup");
            
            value = lookup_map_txtfile(r, s->datafile, key);
            if (!value) {
                rewritelog(r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s",
                           name, key);
                set_cache_value(s->cachename, st.mtime, key, "");
                return NULL;
            }

            rewritelog(r, 5, NULL, "map lookup OK: map=%s[txt] key=%s -> val=%s",
                       name, key, value);
            set_cache_value(s->cachename, st.mtime, key, value);
        }
        else {
            rewritelog(r, 5, NULL, "cache lookup OK: map=%s[txt] key=%s -> val=%s",
                       name, key, value);
        }

        if (s->type == MAPTYPE_RND && *value) {
            value = select_random_value_part(r, value);
            rewritelog(r, 5, NULL, "randomly chosen the subvalue `%s'",value);
        }

        return *value ? value : NULL;

    /*
     * DBM file map
     */

    case MAPTYPE_DBM:
        rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
        if (rv != APR_SUCCESS) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00662)
                          "mod_rewrite: can't access DBM RewriteMap file %s",
                          s->checkfile);
        }
        else if(s->checkfile2 != NULL) {
            apr_finfo_t st2;

            rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool);
            if (rv != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00663)
                              "mod_rewrite: can't access DBM RewriteMap "
                              "file %s", s->checkfile2);
            }
            else if(st2.mtime > st.mtime) {
                st.mtime = st2.mtime;
            }
        }
        if(rv != APR_SUCCESS) {
            return NULL;
        }

        value = get_cache_value(s->cachename, st.mtime, key, r->pool);
        if (!value) {
            rewritelog(r, 6, NULL,
                       "cache lookup FAILED, forcing new map lookup");

            value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key);
            if (!value) {
                rewritelog(r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s",
                           name, key);
                set_cache_value(s->cachename, st.mtime, key, "");
                return NULL;
            }

            rewritelog(r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> "
                       "val=%s", name, key, value);

            set_cache_value(s->cachename, st.mtime, key, value);
            return value;
        }

        rewritelog(r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s",
                   name, key, value);
        return *value ? value : NULL;

    /*
     * SQL map without cache
     */

    case MAPTYPE_DBD:
        value = lookup_map_dbd(r, key, s->dbdq);
        if (!value) {
            rewritelog(r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
                       name, key);
            return NULL;
        }

        rewritelog(r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
                   name, key, value);

        return value;

    /*
     * SQL map with cache
     */

    case MAPTYPE_DBD_CACHE:
        value = get_cache_value(s->cachename, 0, key, r->pool);
        if (!value) {
            rewritelog(r, 6, NULL,
                       "cache lookup FAILED, forcing new map lookup");

            value = lookup_map_dbd(r, key, s->dbdq);
            if (!value) {
                rewritelog(r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
                           name, key);
                set_cache_value(s->cachename, 0, key, "");
                return NULL;
            }

            rewritelog(r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
                       name, key, value);

            set_cache_value(s->cachename, 0, key, value);
            return value;
        }

        rewritelog(r, 5, NULL, "cache lookup OK: map=%s[SQL] key=%s, val=%s",
                   name, key, value);
        return *value ? value : NULL;

    /*
     * Program file map
     */

    case MAPTYPE_PRG:
        value = lookup_map_program(r, s->fpin, s->fpout, key);
        if (!value) {
            rewritelog(r, 5, NULL, "map lookup FAILED: map=%s key=%s", name,
                       key);
            return NULL;
        }

        rewritelog(r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
                   name, key, value);
        return value;

    /*
     * Internal Map
     */

    case MAPTYPE_INT:
        value = s->func(r, key);
        if (!value) {
            rewritelog(r, 5, NULL, "map lookup FAILED: map=%s key=%s", name,
                       key);
            return NULL;
        }

        rewritelog(r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
                   name, key, value);
        return value;
    }

    return NULL;
}

/*
 * lookup a HTTP header and set VARY note
 */

static const char *lookup_header(const char *name, rewrite_ctx *ctx)
{
    const char *val = apr_table_get(ctx->r->headers_in, name);

    /* Skip the 'Vary: Host' header combination
     * as indicated in rfc7231 section-7.1.4
     */

    if (val && strcasecmp(name, "Host") != 0) {
        ctx->vary_this = ctx->vary_this
                         ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ",
                                       name, NULL)
                         : apr_pstrdup(ctx->r->pool, name);
    }

    return val;
}

/*
 * lookahead helper function
 * Determine the correct URI path in perdir context
 */

static APR_INLINE const char *la_u(rewrite_ctx *ctx)
{
    rewrite_perdir_conf *conf;

    if (*ctx->uri == '/') {
        return ctx->uri;
    }

    conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module);

    return apr_pstrcat(ctx->r->pool, conf->baseurl
                                     ? conf->baseurl : conf->directory,
                       ctx->uri, NULL);
}

/*
 * generic variable lookup
 */

static char *lookup_variable(char *var, rewrite_ctx *ctx)
{
    const char *result;
    request_rec *r = ctx->r;
    apr_size_t varlen = strlen(var);

    /* fast exit */
    if (varlen < 4) {
        return "";
    }

    result = NULL;

    /* fast tests for variable length variables (sic) first */
    if (var[3] == ':') {
        if (var[4] && !strncasecmp(var, "ENV", 3)) {
            var += 4;
            result = apr_table_get(r->notes, var);

            if (!result) {
                result = apr_table_get(r->subprocess_env, var);
            }
            if (!result) {
                result = getenv(var);
            }
        }
        else if (var[4] && !strncasecmp(var, "SSL", 3)) {
            result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r,
                                        var + 4);
        }
    }
    else if (var[4] == ':') {
        if (var[5]) {
            request_rec *rr;
            const char *path;

            if (!strncasecmp(var, "HTTP", 4)) {
                result = lookup_header(var+5, ctx);
            }
            else if (!strncasecmp(var, "LA-U", 4)) {
                if (ctx->uri && subreq_ok(r)) {
                    path = ctx->perdir ? la_u(ctx) : ctx->uri;
                    rr = ap_sub_req_lookup_uri(path, r, NULL);
                    ctx->r = rr;
                    result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
                    ctx->r = r;
                    ap_destroy_sub_req(rr);

                    rewritelog(r, 5, ctx->perdir, "lookahead: path=%s var=%s "
                                "-> val=%s", path, var+5, result);

                    return (char *)result;
                }
            }
            else if (!strncasecmp(var, "LA-F", 4)) {
                if (ctx->uri && subreq_ok(r)) {
                    path = ctx->uri;
                    if (ctx->perdir && *path == '/') {
                        /* sigh, the user wants a file based subrequest, but
                         * we can't do one, since we don't know what the file
                         * path is! In this case behave like LA-U.
                         */

                        rr = ap_sub_req_lookup_uri(path, r, NULL);
                    }
                    else {
                        if (ctx->perdir) {
                            rewrite_perdir_conf *conf;

                            conf = ap_get_module_config(r->per_dir_config,
                                                        &rewrite_module);

                            path = apr_pstrcat(r->pool, conf->directory, path,
                                               NULL);
                        }

                        rr = ap_sub_req_lookup_file(path, r, NULL);
                    }

                    ctx->r = rr;
                    result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
                    ctx->r = r;
                    ap_destroy_sub_req(rr);

                    rewritelog(r, 5, ctx->perdir, "lookahead: path=%s var=%s "
                                "-> val=%s", path, var+5, result);

                    return (char *)result;
                }
            }
        }
    }

    /* well, do it the hard way */
    else {
        apr_time_exp_t tm;

        /* can't do this above, because of the getenv call */
        ap_str_toupper(var);

        switch (varlen) {
        case  4:
            if (!strcmp(var, "TIME")) {
                apr_time_exp_lt(&tm, apr_time_now());
                result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
                                      tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
                                      tm.tm_hour, tm.tm_min, tm.tm_sec);
                rewritelog(r, 1, ctx->perdir, "RESULT='%s'", result);
                return (char *)result;
            }
            else if (!strcmp(var, "IPV6")) {
                int flag = FALSE;
#if APR_HAVE_IPV6
                apr_sockaddr_t *addr = r->useragent_addr;
                flag = (addr->family == AF_INET6 &&
                        !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr));
                rewritelog(r, 1, ctx->perdir, "IPV6='%s'", flag ? "on" : "off");
#else
                rewritelog(r, 1, ctx->perdir, "IPV6='off' (IPv6 is not enabled)");
#endif
                result = (flag ? "on" : "off");
            }
            break;

        case  5:
            if (!strcmp(var, "HTTPS")) {
                int flag = ap_ssl_conn_is_ssl(r->connection);
                return apr_pstrdup(r->pool, flag ? "on" : "off");
            }
            break;

        case  8:
            switch (var[6]) {
            case 'A':
                if (!strcmp(var, "TIME_DAY")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%02d", tm.tm_mday);
                }
                break;

            case 'E':
                if (!strcmp(var, "TIME_SEC")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%02d", tm.tm_sec);
                }
                break;

            case 'I':
                if (!strcmp(var, "TIME_MIN")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%02d", tm.tm_min);
                }
                break;

            case 'O':
                if (!strcmp(var, "TIME_MON")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
                }
                break;
            }
            break;

        case  9:
            switch (var[7]) {
            case 'A':
                if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%d", tm.tm_wday);
                }
                else if (!strcmp(var, "TIME_YEAR")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
                }
                break;

            case 'E':
                if (!strcmp(var, "IS_SUBREQ")) {
                    result = (r->main ? "true" : "false");
                }
                break;

            case 'F':
                if (!strcmp(var, "PATH_INFO")) {
                    result = r->path_info;
                }
                break;

            case 'P':
                if (!strcmp(var, "AUTH_TYPE")) {
                    result = r->ap_auth_type;
                }
                break;

            case 'S':
                if (!strcmp(var, "HTTP_HOST")) {
                    result = lookup_header("Host", ctx);
                }
                break;

            case 'U':
                if (!strcmp(var, "TIME_HOUR")) {
                    apr_time_exp_lt(&tm, apr_time_now());
                    return apr_psprintf(r->pool, "%02d", tm.tm_hour);
                }
                break;
            }
            break;

        case 11:
            switch (var[8]) {
            case 'A':
                if (!strcmp(var, "SERVER_NAME")) {
                    result = ap_get_server_name_for_url(r);
                }
                break;

            case 'D':
                if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
                    result = r->useragent_ip;
                }
                else if (!strcmp(var, "SERVER_ADDR")) {
                    result = r->connection->local_ip;
                }
                break;

            case 'E':
                if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
                    result = lookup_header("Accept", ctx);
                }
                else if (!strcmp(var, "THE_REQUEST")) {
                    result = r->the_request;
                }
                break;

            case 'I':
                if (!strcmp(var, "API_VERSION")) {
                    return apr_psprintf(r->pool, "%d:%d",
                                        MODULE_MAGIC_NUMBER_MAJOR,
                                        MODULE_MAGIC_NUMBER_MINOR);
                }
                break;

            case 'K':
                if (!strcmp(var, "HTTP_COOKIE")) {
                    result = lookup_header("Cookie", ctx);
                }
                break;

            case 'O':
                if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
                    return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
                }
                else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
                    result = ap_get_remote_host(r->connection,r->per_dir_config,
                                                REMOTE_NAME, NULL);
                }
                else if (!strcmp(var, "REMOTE_PORT")) {
                    return apr_itoa(r->pool, r->useragent_addr->port);
                }
                break;

            case 'S':
                if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
                    result = r->user;
                }
                else if (!strcmp(var, "SCRIPT_USER")) {
                    result = "<unknown>";
                    if (r->finfo.valid & APR_FINFO_USER) {
                        apr_uid_name_get((char **)&result, r->finfo.user,
                                         r->pool);
                    }
                }
                break;

            case 'U':
                if (!strcmp(var, "REQUEST_URI")) {
                    result = r->uri;
                }
                break;
            }
            break;

        case 12:
            switch (var[3]) {
            case 'I':
                if (!strcmp(var, "SCRIPT_GROUP")) {
                    result = "<unknown>";
                    if (r->finfo.valid & APR_FINFO_GROUP) {
                        apr_gid_name_get((char **)&result, r->finfo.group,
                                         r->pool);
                    }
                }
                break;

            case 'O':
                if (!strcmp(var, "REMOTE_IDENT")) {
                    result = ap_get_remote_logname(r);
                }
                break;

            case 'P':
                if (!strcmp(var, "HTTP_REFERER")) {
                    result = lookup_header("Referer", ctx);
                }
                break;

            case 'R':
                if (!strcmp(var, "QUERY_STRING")) {
                    result = r->args;
                }
                break;

            case 'V':
                if (!strcmp(var, "SERVER_ADMIN")) {
                    result = r->server->server_admin;
                }
                break;
            }
            break;

        case 13:
            if (!strcmp(var, "DOCUMENT_ROOT")) {
                result = ap_document_root(r);
            }
            break;

        case 14:
            if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
                result = lookup_header("Forwarded", ctx);
            }
            else if (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) {
                result = ap_context_prefix(r);
            }
            else if (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) {
                result = r->method;
            }
            else if (!strcmp(var, "REQUEST_SCHEME")) {
                result = ap_http_scheme(r);
            }
            break;

        case 15:
            switch (var[7]) {
            case 'E':
                if (!strcmp(var, "HTTP_USER_AGENT")) {
                    result = lookup_header("User-Agent", ctx);
                }
                break;

            case 'F':
                if (!strcmp(var, "SCRIPT_FILENAME")) {
                    result = r->filename; /* same as request_filename (16) */
                }
                break;

            case 'P':
                if (!strcmp(var, "SERVER_PROTOCOL")) {
                    result = r->protocol;
                }
                break;

            case 'S':
                if (!strcmp(var, "SERVER_SOFTWARE")) {
                    result = ap_get_server_banner();
                }
                break;
            }
            break;

        case 16:
            if (*var == 'C' && !strcmp(var, "CONN_REMOTE_ADDR")) {
                result = r->connection->client_ip;
            }
            else if (!strcmp(var, "REQUEST_FILENAME")) {
                result = r->filename; /* same as script_filename (15) */
            }
            break;

        case 21:
            if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
                result = lookup_header("Proxy-Connection", ctx);
            }
            else if (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) {
                result = ap_context_document_root(r);
            }
            break;
        }
    }

    return apr_pstrdup(r->pool, result ? result : "");
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                 Expansion functions
 * |                                                       |
 * +-------------------------------------------------------+
 */


/*
 * Bracketed expression handling
 * s points after the opening bracket
 */

static APR_INLINE char *find_closing_curly(char *s)
{
    unsigned depth;

    for (depth = 1; *s; ++s) {
        if (*s == RIGHT_CURLY && --depth == 0) {
            return s;
        }
        else if (*s == LEFT_CURLY) {
            ++depth;
        }
    }

    return NULL;
}

static APR_INLINE char *find_char_in_curlies(char *s, int c)
{
    unsigned depth;

    for (depth = 1; *s; ++s) {
        if (*s == c && depth == 1) {
            return s;
        }
        else if (*s == RIGHT_CURLY && --depth == 0) {
            return NULL;
        }
        else if (*s == LEFT_CURLY) {
            ++depth;
        }
    }

    return NULL;
}

/* perform all the expansions on the input string
 * putting the result into a new string
 *
 * for security reasons this expansion must be performed in a
 * single pass, otherwise an attacker can arrange for the result
 * of an earlier expansion to include expansion specifiers that
 * are interpreted by a later expansion, producing results that
 * were not intended by the administrator.
 *
 * unsafe_qmark if not NULL will be set to 1 or 0 if a question mark
 * is found respectively in a literal or in a lookup/expansion (whether
 * it's the first or last qmark depends on [QSL]). Should be initialized
 * to -1 and remains so if no qmark is found.
 */

static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry,
                       int *unsafe_qmark)
{
#define EXPAND_SPECIALS "\\$%"
    result_list *result, *current;
    result_list sresult[SMALL_EXPANSION];
    unsigned spc = 0;
    apr_size_t span, inputlen, outlen;
    char *p, *c;
    apr_pool_t *pool = ctx->r->pool;

    inputlen = strlen(input);
    if (!unsafe_qmark) {
        span = strcspn(input, EXPAND_SPECIALS);
    }
    else {
        span = strcspn(input, EXPAND_SPECIALS "?");
        if (input[span] == '?') {
            /* this qmark is not from an expansion thus safe */
            *unsafe_qmark = 0;

            /* keep tracking only if interested in the last qmark */
            if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
                unsafe_qmark = NULL;
            }

            /* find the next real special char, any (last) qmark up to
             * there is safe too
             */

            span += strcspn(input + span, EXPAND_SPECIALS);
        }
    }

    /* fast path (no specials) */
    if (span >= inputlen) {
        return apr_pstrmemdup(pool, input, inputlen);
    }

    /* well, actually something to do */
    result = current = &(sresult[spc++]);

    p = input + span;
    current->next = NULL;
    current->string = input;
    current->len = span;
    outlen = span;

    /* loop for specials */
    do {
        int expanded = 0;

        /* prepare next entry */
        if (current->len) {
            current->next = (spc < SMALL_EXPANSION)
                            ? &(sresult[spc++])
                            : (result_list *)apr_palloc(pool,
                                                        sizeof(result_list));
            current = current->next;
            current->next = NULL;
            current->len = 0;
        }

        /* escaped character */
        if (*p == '\\') {
            current->len = 1;
            ++outlen;
            if (!p[1]) {
                current->string = p;
                break;
            }
            else {
                current->string = ++p;
                ++p;
            }
        }

        /* variable or map lookup */
        else if (p[1] == '{') {
            char *endp;

            endp = find_closing_curly(p+2);
            if (!endp) {
                current->len = 2;
                current->string = p;
                outlen += 2;
                p += 2;
            }

            /* variable lookup */
            else if (*p == '%') {
                p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx);

                span = strlen(p);
                current->len = span;
                current->string = p;
                outlen += span;

                expanded = 1;
                p = endp + 1;
            }

            /* map lookup */
            else {     /* *p == '$' */
                char *key;

                /*
                 * To make rewrite maps useful, the lookup key and
                 * default values must be expanded, so we make
                 * recursive calls to do the work. For security
                 * reasons we must never expand a string that includes
                 * verbatim data from the network. The recursion here
                 * isn't a problem because the result of expansion is
                 * only passed to lookup_map() so it cannot be
                 * re-expanded, only re-looked-up. Another way of
                 * looking at it is that the recursion is entirely
                 * driven by the syntax of the nested curly brackets.
                 */


                key = find_char_in_curlies(p+2, ':');
                if (!key) {
                    current->len = 2;
                    current->string = p;
                    outlen += 2;
                    p += 2;
                }
                else {
                    char *map, *dflt;

                    map = apr_pstrmemdup(pool, p+2, endp-p-2);
                    key = map + (key-p-2);
                    *key++ = '\0';
                    dflt = find_char_in_curlies(key, '|');
                    if (dflt) {
                        *dflt++ = '\0';
                    }

                    /* reuse of key variable as result */
                    key = lookup_map(ctx->r, map, do_expand(key, ctx, entry, NULL));
                    if (!key && dflt && *dflt) {
                        key = do_expand(dflt, ctx, entry, NULL);
                    }
                    if (key && *key) {
                        span = strlen(key);
                        current->len = span;
                        current->string = key;
                        outlen += span;
                    }

                    expanded = 1;
                    p = endp + 1;
                }
            }
        }

        /* backreference */
        else if (apr_isdigit(p[1])) {
            int n = p[1] - '0';
            backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC;

            /* see ap_pregsub() in server/util.c */
            if (bri->source && n < AP_MAX_REG_MATCH
                && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
                span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
                if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) {
                    /* escape the backreference */
                    char *tmp2, *tmp;
                    tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span);
                    tmp2 = escape_backref(pool, tmp, entry->escapes, entry->noescapes,
                                          entry->flags);
                    rewritelog(ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'",
                            tmp, tmp2);

                    current->len = span = strlen(tmp2);
                    current->string = tmp2;
                } else {
                    current->len = span;
                    current->string = bri->source + bri->regmatch[n].rm_so;
                }
                outlen += span;

                expanded = 1;
            }

            p += 2;
        }

        /* not for us, just copy it */
        else {
            current->len = 1;
            current->string = p++;
            ++outlen;
        }

        if (unsafe_qmark && expanded && current->len
            && memchr(current->string, '?', current->len)) {
            /* this qmark is from an expansion thus unsafe */
            *unsafe_qmark = 1;

            /* keep tracking only if interested in the last qmark */
            if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
                unsafe_qmark = NULL;
            }
        }

        /* check the remainder */
        if (!unsafe_qmark) {
            span = strcspn(p, EXPAND_SPECIALS);
        }
        else {
            span = strcspn(p, EXPAND_SPECIALS "?");
            if (p[span] == '?') {
                /* this qmark is not from an expansion thus safe */
                *unsafe_qmark = 0;

                /* keep tracking only if interested in the last qmark */
                if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
                    unsafe_qmark = NULL;
                }

                /* find the next real special char, any (last) qmark up to
                 * there is safe too
                 */

                span += strcspn(p + span, EXPAND_SPECIALS);
            }
        }
        if (span > 0) {
            if (current->len) {
                current->next = (spc < SMALL_EXPANSION)
                                ? &(sresult[spc++])
                                : (result_list *)apr_palloc(pool,
                                                           sizeof(result_list));
                current = current->next;
                current->next = NULL;
            }

            current->len = span;
            current->string = p;
            p += span;
            outlen += span;
        }

    } while (p < input+inputlen);

    /* assemble result */
    c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */
    do {
        if (result->len) {
            ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after
                                                   * extensive testing and
                                                   * review
                                                   */

            memcpy(c, result->string, result->len);
            c += result->len;
        }
        result = result->next;
    } while (result);

    p[outlen] = '\0';

    return p;
}

/*
 * perform all the expansions on the environment variables
 */

static void do_expand_env(data_item *env, rewrite_ctx *ctx)
{
    char *name, *val;

    while (env) {
        name = do_expand(env->data, ctx, NULL, NULL);
        if (*name == '!') {
            name++;
            apr_table_unset(ctx->r->subprocess_env, name);
            rewritelog(ctx->r, 5, NULL, "unsetting env variable '%s'", name);
        }
        else {
            if ((val = ap_strchr(name, ':')) != NULL) {
                *val++ = '\0';
            } else {
                val = "";
            }

            apr_table_set(ctx->r->subprocess_env, name, val);
            rewritelog(ctx->r, 5, NULL, "setting env variable '%s' to '%s'",
                        name, val);
        }

        env = env->next;
    }

    return;
}

/*
 * perform all the expansions on the cookies
 *
 * TODO: use cached time similar to how logging does it
 */

static void add_cookie(request_rec *r, char *s)
{
    char *var;
    char *val;
    char *domain;
    char *expires;
    char *path;
    char *secure;
    char *httponly;
    char *samesite;

    char *tok_cntx;
    char *cookie;
    /* long-standing default, but can't use ':' in a cookie */
    const char *sep = ":"

    /* opt-in to ; separator if first character is a ; */
    if (s && *s == ';') { 
        sep = ";"
        s++;
    }

    var = apr_strtok(s, sep, &tok_cntx);
    val = apr_strtok(NULL, sep, &tok_cntx);
    domain = apr_strtok(NULL, sep, &tok_cntx);

    if (var && val && domain) {
        request_rec *rmain = r;
        char *notename;
        void *data;

        while (rmain->main) {
            rmain = rmain->main;
        }

        notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
        apr_pool_userdata_get(&data, notename, rmain->pool);
        if (!data) {
            char *exp_time = NULL;

            expires = apr_strtok(NULL, sep, &tok_cntx);
            path = expires ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
            secure = path ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
            httponly = secure ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
            samesite = httponly ? apr_strtok(NULL, sep, &tok_cntx) : NULL;

            if (expires) {
                apr_time_exp_t tms;
                long exp_min;

                exp_min = atol(expires);
                if (exp_min) {
                    apr_time_exp_gmt(&tms, r->request_time
                                     + apr_time_from_sec((60 * exp_min)));
                    exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d "
                                                     "%.2d:%.2d:%.2d GMT",
                                           apr_day_snames[tms.tm_wday],
                                           tms.tm_mday,
                                           apr_month_snames[tms.tm_mon],
                                           tms.tm_year+1900,
                                           tms.tm_hour, tms.tm_min, tms.tm_sec);
                }
            }

            cookie = apr_pstrcat(rmain->pool,
                                 var, "=", val,
                                 "; path=", path ? path : "/",
                                 "; domain=", domain,
                                 expires ? (exp_time ? "; expires=" : "")
                                 : NULL,
                                 expires ? (exp_time ? exp_time : "")
                                 : NULL,
                                 (secure && (!ap_cstr_casecmp(secure, "true")
                                             || !strcmp(secure, "1")
                                             || !ap_cstr_casecmp(secure,
                                                            "secure"))) ?
                                  "; secure" : NULL,
                                 (httponly && (!ap_cstr_casecmp(httponly, "true")
                                               || !strcmp(httponly, "1")
                                               || !ap_cstr_casecmp(httponly,
                                                              "HttpOnly"))) ?
                                  "; HttpOnly" : NULL,
                                 NULL);

            if (samesite && strcmp(samesite, "0") && ap_cstr_casecmp(samesite,"false")) { 
                cookie = apr_pstrcat(rmain->pool, cookie, "; SameSite="
                                     samesite, NULL);
            }

            apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie);
            apr_pool_userdata_set("set", notename, NULL, rmain->pool);
            rewritelog(rmain, 5, NULL, "setting cookie '%s'", cookie);
        }
        else {
            rewritelog(rmain, 5, NULL, "skipping already set cookie '%s'",
                        var);
        }
    }

    return;
}

static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
{
    while (cookie) {
        add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL, NULL));
        cookie = cookie->next;
    }

    return;
}

#if APR_HAS_USER
/*
 * Expand tilde-paths (/~user) through Unix /etc/passwd
 * database information (or other OS-specific database)
 */

static char *expand_tildepaths(request_rec *r, char *uri)
{
    if (uri && *uri == '/' && uri[1] == '~') {
        char *p, *user;

        p = user = uri + 2;
        while (*p && *p != '/') {
            ++p;
        }

        if (p > user) {
            char *homedir;

            user = apr_pstrmemdup(r->pool, user, p-user);
            if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
                if (*p) {
                    /* reuse of user variable */
                    user = homedir + strlen(homedir) - 1;
                    if (user >= homedir && *user == '/') {
                        *user = '\0';
                    }

                    return apr_pstrcat(r->pool, homedir, p, NULL);
                }
                else {
                    return homedir;
                }
            }
        }
    }

    return uri;
}
#endif  /* if APR_HAS_USER */


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |              rewriting lockfile support
 * |                                                       |
 * +-------------------------------------------------------+
 */


static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p)
{
    apr_status_t rc;

    /* create the lockfile */
    rc = ap_global_mutex_create(&rewrite_mapr_lock_acquire, NULL,
                                rewritemap_mutex_type, NULL, s, p, 0);
    if (rc != APR_SUCCESS) {
        return rc;
    }

    return APR_SUCCESS;
}

static apr_status_t rewritelock_remove(void *data)
{
    /* destroy the rewritelock */
    if (rewrite_mapr_lock_acquire) {
        apr_global_mutex_destroy(rewrite_mapr_lock_acquire);
        rewrite_mapr_lock_acquire = NULL;
    }
    return APR_SUCCESS;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |           configuration directive handling
 * |                                                       |
 * +-------------------------------------------------------+
 */


/*
 * own command line parser for RewriteRule and RewriteCond,
 * which doesn't have the '\\' problem.
 * (returns true on error)
 *
 * XXX: what an inclined parser. Seems we have to leave it so
 *      for backwards compat. *sigh*
 */

static int parseargline(char *str, char **a1, char **a2, char **a2_end, char **a3)
{
    char quote;

    while (apr_isspace(*str)) {
        ++str;
    }

    /*
     * determine first argument
     */

    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a1 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

    if (!*str) {
        return 1;
    }
    *str++ = '\0';

    while (apr_isspace(*str)) {
        ++str;
    }

    /*
     * determine second argument
     */

    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a2 = str;

    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }

    if (!*str) {
        *a3 = NULL; /* 3rd argument is optional */
        *a2_end = str;
        return 0;
    }
    *a2_end = str;
    *str++ = '\0';

    while (apr_isspace(*str)) {
        ++str;
    }

    if (!*str) {
        *a3 = NULL; /* 3rd argument is still optional */
        return 0;
    }

    /*
     * determine third argument
     */

    quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
    *a3 = str;
    for (; *str; ++str) {
        if ((apr_isspace(*str) && !quote) || (*str == quote)) {
            break;
        }
        if (*str == '\\' && apr_isspace(str[1])) {
            ++str;
            continue;
        }
    }
    *str = '\0';

    return 0;
}

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

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

    a->state           = ENGINE_DISABLED;
    a->options         = OPTION_NONE;
    a->rewritemaps     = apr_hash_make(p);
    a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
    a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));
    a->server          = s;

    return (void *)a;
}

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

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

    a->state = (overrides->state_set == 0) ? base->state : overrides->state;
    a->state_set = overrides->state_set || base->state_set;
    a->options = (overrides->options_set == 0) ? base->options : overrides->options;
    a->options_set = overrides->options_set || base->options_set;

    a->server  = overrides->server;

    if (a->options & OPTION_INHERIT ||
            (base->options & OPTION_INHERIT_DOWN &&
             !(a->options & OPTION_IGNORE_INHERIT))) {
        /*
         *  local directives override
         *  and anything else is inherited
         */

        a->rewritemaps     = apr_hash_overlay(p, overrides->rewritemaps,
                                              base->rewritemaps);
        a->rewriteconds    = apr_array_append(p, overrides->rewriteconds,
                                              base->rewriteconds);
        a->rewriterules    = apr_array_append(p, overrides->rewriterules,
                                              base->rewriterules);
    }
    else if (a->options & OPTION_INHERIT_BEFORE || 
            (base->options & OPTION_INHERIT_DOWN_BEFORE &&
             !(a->options & OPTION_IGNORE_INHERIT))) {
        /*
         *  local directives override
         *  and anything else is inherited (preserving order)
         */

        a->rewritemaps     = apr_hash_overlay(p, base->rewritemaps,
                                              overrides->rewritemaps);
        a->rewriteconds    = apr_array_append(p, base->rewriteconds,
                                              overrides->rewriteconds);
        a->rewriterules    = apr_array_append(p, base->rewriterules,
                                              overrides->rewriterules);
    }
    else {
        /*
         *  local directives override
         *  and anything else gets defaults
         */

        a->rewritemaps     = overrides->rewritemaps;
        a->rewriteconds    = overrides->rewriteconds;
        a->rewriterules    = overrides->rewriterules;
    }

    return (void *)a;
}

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

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

    a->state           = ENGINE_DISABLED;
    a->options         = OPTION_NONE;
    a->baseurl         = NULL;
    a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
    a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));

    if (path == NULL) {
        a->directory = NULL;
    }
    else {
        /* make sure it has a trailing slash */
        if (path[strlen(path)-1] == '/') {
            a->directory = apr_pstrdup(p, path);
        }
        else {
            a->directory = apr_pstrcat(p, path, "/", NULL);
        }
    }

    return (void *)a;
}

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

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

    a->state = (overrides->state_set == 0) ? base->state : overrides->state;
    a->state_set = overrides->state_set || base->state_set;
    a->options = (overrides->options_set == 0) ? base->options : overrides->options;
    a->options_set = overrides->options_set || base->options_set;

    if (a->options & OPTION_MERGEBASE) { 
        a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl;
        a->baseurl_set = overrides->baseurl_set || base->baseurl_set;
    }
    else { 
        a->baseurl = overrides->baseurl;
    }

    a->directory  = overrides->directory;

    if (a->options & OPTION_INHERIT ||
            (base->options & OPTION_INHERIT_DOWN &&
             !(a->options & OPTION_IGNORE_INHERIT))) {
        a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
                                           base->rewriteconds);
        a->rewriterules = apr_array_append(p, overrides->rewriterules,
                                           base->rewriterules);
    }
    else if (a->options & OPTION_INHERIT_BEFORE || 
            (base->options & OPTION_INHERIT_DOWN_BEFORE &&
             !(a->options & OPTION_IGNORE_INHERIT))) {
        a->rewriteconds    = apr_array_append(p, base->rewriteconds,
                                              overrides->rewriteconds);
        a->rewriterules    = apr_array_append(p, base->rewriterules,
                                              overrides->rewriterules);
    }
    else {
        a->rewriteconds = overrides->rewriteconds;
        a->rewriterules = overrides->rewriterules;
    }

    return (void *)a;
}

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

    sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);

    /* server command? set both global scope and base directory scope */
    if (cmd->path == NULL) {
        sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
        sconf->state_set = 1;
        dconf->state = sconf->state;
        dconf->state_set = 1;
    }
    /* directory command? set directory scope only */
    else {
        dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
        dconf->state_set = 1;
    }

    return NULL;
}

static const char *cmd_rewriteoptions(cmd_parms *cmd,
                                      void *in_dconf, const char *option)
{
    int options = 0;

    while (*option) {
        char *w = ap_getword_conf(cmd->temp_pool, &option);

        if (!strcasecmp(w, "inherit")) {
            options |= OPTION_INHERIT;
        }
        else if (!strcasecmp(w, "inheritbefore")) {
            options |= OPTION_INHERIT_BEFORE;
        }
        else if (!strcasecmp(w, "inheritdown")) {
            options |= OPTION_INHERIT_DOWN;
        }
        else if(!strcasecmp(w, "inheritdownbefore")) {
            options |= OPTION_INHERIT_DOWN_BEFORE;
        }
        else if (!strcasecmp(w, "ignoreinherit")) {
            options |= OPTION_IGNORE_INHERIT;
        }
        else if (!strcasecmp(w, "allownoslash")) {
            options |= OPTION_NOSLASH;
        }
        else if (!strncasecmp(w, "MaxRedirects=", 13)) {
            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00664)
                         "RewriteOptions: MaxRedirects option has been "
                         "removed in favor of the global "
                         "LimitInternalRecursion directive and will be "
                         "ignored.");
        }
        else if (!strcasecmp(w, "allowanyuri")) {
            options |= OPTION_ANYURI;
        }
        else if (!strcasecmp(w, "mergebase")) {
            options |= OPTION_MERGEBASE;
        }
        else if (!strcasecmp(w, "ignorecontextinfo")) {
            options |= OPTION_IGNORE_CONTEXT_INFO;
        }
        else if (!strcasecmp(w, "legacyprefixdocroot")) {
            options |= OPTION_LEGACY_PREFIX_DOCROOT;
        }
        else if (!strcasecmp(w, "UnsafePrefixStat")) {
            options |= OPTION_UNSAFE_PREFIX_STAT;
        }
        else {
            return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '",
                               w, "'", NULL);
        }
    }

    /* server command? set both global scope and base directory scope */
    if (cmd->path == NULL) { /* is server command */
        rewrite_perdir_conf *dconf = in_dconf;
        rewrite_server_conf *sconf =
            ap_get_module_config(cmd->server->module_config,
                                 &rewrite_module);

        sconf->options |= options;
        sconf->options_set = 1;
        dconf->options |= options;
        dconf->options_set = 1;
    }
    /* directory command? set directory scope only */
    else {                  /* is per-directory command */
        rewrite_perdir_conf *dconf = in_dconf;

        dconf->options |= options;
        dconf->options_set = 1;
    }

    return NULL;
}

static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
                                  const char *a2, const char *a3)
{
    rewrite_server_conf *sconf;
    rewritemap_entry *newmap;
    apr_finfo_t st;
    const char *fname;

    sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);

    newmap = apr_pcalloc(cmd->pool, sizeof(rewritemap_entry));

    if (strncasecmp(a2, "txt:", 4) == 0) {
        if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
                               a2+4, NULL);
        }

        newmap->type      = MAPTYPE_TXT;
        newmap->datafile  = fname;
        newmap->checkfile = fname;
        newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
                                         (void *)cmd->server, a1);
    }
    else if (strncasecmp(a2, "rnd:", 4) == 0) {
        if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ",
                               a2+4, NULL);
        }

        newmap->type      = MAPTYPE_RND;
        newmap->datafile  = fname;
        newmap->checkfile = fname;
        newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
                                         (void *)cmd->server, a1);
    }
    else if (strncasecmp(a2, "dbm", 3) == 0) {
        apr_status_t rv;

        newmap->type = MAPTYPE_DBM;
        fname = NULL;
        newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
                                         (void *)cmd->server, a1);

        if (a2[3] == ':') {
            newmap->dbmtype = "default";
            fname = a2+4;
        }
        else if (a2[3] == '=') {
            const char *colon = ap_strchr_c(a2 + 4, ':');

            if (colon) {
                newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4,
                                               colon - (a2 + 3) - 1);
                fname = colon + 1;
            }
        }

        if (!fname) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad map:",
                               a2, NULL);
        }

        if ((newmap->datafile = ap_server_root_relative(cmd->pool,
                                                        fname)) == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ",
                               fname, NULL);
        }

        rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype,
                                      newmap->datafile, &newmap->checkfile,
                                      &newmap->checkfile2);
        if (rv != APR_SUCCESS) {
            return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ",
                               newmap->dbmtype, " is invalid", NULL);
        }
    }
    else if ((strncasecmp(a2, "dbd:", 4) == 0)
             || (strncasecmp(a2, "fastdbd:", 8) == 0)) {
        if (dbd_prepare == NULL) {
            return "RewriteMap types dbd and fastdbd require mod_dbd!";
        }
        if ((a2[0] == 'd') || (a2[0] == 'D')) {
            newmap->type = MAPTYPE_DBD;
            fname = a2+4;
        }
        else {
            newmap->type = MAPTYPE_DBD_CACHE;
            fname = a2+8;
            newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
                                             (void *)cmd->server, a1);
        }
        newmap->dbdq = a1;
        dbd_prepare(cmd->server, fname, newmap->dbdq);
    }
    else if (strncasecmp(a2, "prg:", 4) == 0) {
        apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool);

        fname = newmap->argv[0];
        if ((newmap->argv[0] = ap_server_root_relative(cmd->pool,
                                                       fname)) == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ",
                               fname, NULL);
        }

        newmap->type      = MAPTYPE_PRG;
        newmap->checkfile = newmap->argv[0];
        rewrite_lock_needed = 1;

        if (a3) {
            char *tok_cntx;
            newmap->user = apr_strtok(apr_pstrdup(cmd->pool, a3), ":", &tok_cntx);
            newmap->group = apr_strtok(NULL, ":", &tok_cntx);
        }
    }
    else if (strncasecmp(a2, "int:", 4) == 0) {
        newmap->type      = MAPTYPE_INT;
        newmap->func      = (char *(*)(request_rec *,char *))
                            apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4));
        if (newmap->func == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
                               a2+4, NULL);
        }
    }
    else {
        if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) {
            return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
                               a2, NULL);
        }

        newmap->type      = MAPTYPE_TXT;
        newmap->datafile  = fname;
        newmap->checkfile = fname;
        newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
                                         (void *)cmd->server, a1);
    }

    if (newmap->checkfile
        && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN,
                     cmd->pool) != APR_SUCCESS)) {
        return apr_pstrcat(cmd->pool,
                           "RewriteMap: file for map ", a1,
                           " not found:", newmap->checkfile, NULL);
    }

    apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap);

    return NULL;
}

static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
                                   const char *a1)
{
    rewrite_perdir_conf *dconf = in_dconf;

    if (cmd->path == NULL || dconf == NULL) {
        return "RewriteBase: only valid in per-directory config files";
    }
    if (a1[0] == '\0') {
        return "RewriteBase: empty URL not allowed";
    }
    if (a1[0] != '/') {
        return "RewriteBase: argument is not a valid URL";
    }

    dconf->baseurl = a1;
    dconf->baseurl_set = 1;

    return NULL;
}

/*
 * generic lexer for RewriteRule and RewriteCond flags.
 * The parser will be passed in as a function pointer
 * and called if a flag was found
 */

static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key,
                                      const char *(*parse)(apr_pool_t *,
                                                           void *,
                                                           char *, char *))
{
    char *val, *nextp, *endp;
    const char *err;

    endp = key + strlen(key) - 1;
    if (*key != '[' || *endp != ']') {
        return "bad flag delimiters";
    }

    *endp = ','/* for simpler parsing */
    ++key;

    while (*key) {
        /* skip leading spaces */
        while (apr_isspace(*key)) {
            ++key;
        }

        if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not
                                                               * happen, but ...
                                                               */

            break;
        }

        /* strip trailing spaces */
        endp = nextp - 1;
        while (apr_isspace(*endp)) {
            --endp;
        }
        *++endp = '\0';

        /* split key and val */
        val = ap_strchr(key, '=');
        if (val) {
            *val++ = '\0';
        }
        else {
            val = endp;
        }

        err = parse(p, cfg, key, val);
        if (err) {
            return err;
        }

        key = nextp + 1;
    }

    return NULL;
}

static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg,
                                           char *key, char *val)
{
    rewritecond_entry *cfg = _cfg;

    if (   strcasecmp(key, "nocase") == 0
        || strcasecmp(key, "NC") == 0    ) {
        cfg->flags |= CONDFLAG_NOCASE;
    }
    else if (   strcasecmp(key, "ornext") == 0
             || strcasecmp(key, "OR") == 0    ) {
        cfg->flags |= CONDFLAG_ORNEXT;
    }
    else if (   strcasecmp(key, "novary") == 0
             || strcasecmp(key, "NV") == 0    ) {
        cfg->flags |= CONDFLAG_NOVARY;
    }
    else {
        return apr_pstrcat(p, "unknown flag '", key, "'", NULL);
    }
    return NULL;
}

static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
                                   const char *in_str)
{
    rewrite_perdir_conf *dconf = in_dconf;
    char *str = apr_pstrdup(cmd->pool, in_str);
    rewrite_server_conf *sconf;
    rewritecond_entry *newcond;
    ap_regex_t *regexp;
    char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL;
    const char *err;

    sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);

    /*  make a new entry in the internal temporary rewrite rule list */
    if (cmd->path == NULL) {   /* is server command */
        newcond = apr_array_push(sconf->rewriteconds);
    }
    else {                     /* is per-directory command */
        newcond = apr_array_push(dconf->rewriteconds);
    }

    /* parse the argument line ourself
     * a1 .. a3 are substrings of str, which is a fresh copy
     * of the argument line. So we can use a1 .. a3 without
     * copying them again.
     */

    if (parseargline(str, &a1, &a2, &a2_end, &a3)) {
        return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
                           "'", NULL);
    }

    /* arg1: the input string */
    newcond->input = a1;

    /* arg3: optional flags field
     * (this has to be parsed first, because we need to
     *  know if the regex should be compiled with ICASE!)
     */

    newcond->flags = CONDFLAG_NONE;
    if (a3 != NULL) {
        if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
                                      cmd_rewritecond_setflag)) != NULL) {
            return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL);
        }
    }

    /* arg2: the pattern */
    newcond->pattern = a2;
    if (*a2 == '!') {
        newcond->flags |= CONDFLAG_NOTMATCH;
        ++a2;
    }

    /* determine the pattern type */
    newcond->ptype = CONDPAT_REGEX;
    if (strcasecmp(a1, "expr") == 0) {
        newcond->ptype = CONDPAT_AP_EXPR;
    }
    else if (*a2 && a2[1]) {
        if (*a2 == '-') {
            if (!a2[2]) {
                switch (a2[1]) {
                case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break;
                case 's': newcond->ptype = CONDPAT_FILE_SIZE;   break;
                case 'd': newcond->ptype = CONDPAT_FILE_DIR;    break;
                case 'x': newcond->ptype = CONDPAT_FILE_XBIT;   break;
                case 'h': newcond->ptype = CONDPAT_FILE_LINK;   break;
                case 'L': newcond->ptype = CONDPAT_FILE_LINK;   break;
                case 'l': newcond->ptype = CONDPAT_FILE_LINK;   break;
                case 'U': newcond->ptype = CONDPAT_LU_URL;      break;
                case 'F': newcond->ptype = CONDPAT_LU_FILE;     break;
                }
            }
            else if (a2[3]) {
                switch (a2[1]) {
                case 'l':
                    if (a2[2] == 't') {
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_LT;
                    }
                    else if (a2[2] == 'e') {
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_LE;
                    }
                    break;

                case 'g':
                    if (a2[2] == 't') {
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_GT;
                    }
                    else if (a2[2] == 'e') {
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_GE;
                    }
                    break;

                case 'e':
                    if (a2[2] == 'q') {
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_EQ;
                    }
                    break;

                case 'n':
                    if (a2[2] == 'e') {
                        /* Inversion, ensure !-ne == -eq */
                        a2 += 3;
                        newcond->ptype = CONDPAT_INT_EQ;
                        newcond->flags ^= CONDFLAG_NOTMATCH;
                    }
                    break;
                }
            }
        }
        else {
            switch (*a2) {
            case '>'if (*++a2 == '=')
                          ++a2, newcond->ptype = CONDPAT_STR_GE;
                      else
                          newcond->ptype = CONDPAT_STR_GT;
                      break;

            case '<'if (*++a2 == '=')
                          ++a2, newcond->ptype = CONDPAT_STR_LE;
                      else
                          newcond->ptype = CONDPAT_STR_LT;
                      break;

            case '=': newcond->ptype = CONDPAT_STR_EQ;
                      /* "" represents an empty string */
                      if (*++a2 == '"' && a2[1] == '"' && !a2[2])
                          a2 += 2;
                      break;
            }
        }
    }

    if ((newcond->ptype != CONDPAT_REGEX) &&
        (newcond->ptype < CONDPAT_STR_LT || newcond->ptype > CONDPAT_STR_GE) &&
        (newcond->flags & CONDFLAG_NOCASE)) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00665)
                     "RewriteCond: NoCase option for non-regex pattern '%s' "
                     "is not supported and will be ignored. (%s:%d)", a2,
                     cmd->directive->filename, cmd->directive->line_num);
        newcond->flags &= ~CONDFLAG_NOCASE;
    }

    newcond->pskip = a2 - newcond->pattern;
    newcond->pattern += newcond->pskip;

    if (newcond->ptype == CONDPAT_REGEX) {
        regexp = ap_pregcomp(cmd->pool, a2,
                             AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE)
                                             ? AP_REG_ICASE : 0));
        if (!regexp) {
            return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular "
                               "expression '", a2, "'", NULL);
        }

        newcond->regexp  = regexp;
    }
    else if (newcond->ptype == CONDPAT_AP_EXPR) {
        unsigned int flags = newcond->flags & CONDFLAG_NOVARY ?
                             AP_EXPR_FLAG_DONT_VARY : 0;
        newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL);
        if (err)
            return apr_psprintf(cmd->pool, "RewriteCond: cannot compile "
                                "expression \"%s\": %s", a2, err);
    }

    return NULL;
}

static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg,
                                           char *key, char *val)
{
    rewriterule_entry *cfg = _cfg;
    int error = 0;

    switch (*key++) {
    case 'b':
    case 'B':
        if (!*key || !strcasecmp(key, "ackrefescaping")) {
            cfg->flags |= RULEFLAG_ESCAPEBACKREF;
            if (val && *val) {
                cfg->escapes = val;
            }
        }
        else if (!strcasecmp(key, "NE")) {
            if (val && *val) {
                cfg->noescapes = val;
            }
            else {
                return "flag 'BNE' wants a list of characters (i.e. [BNE=...])";
            }
        }
        else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) { 
            cfg->flags |= RULEFLAG_ESCAPENOPLUS;
        }
        else if (!strcasecmp(key, "CTLS")) {
            cfg->flags |= RULEFLAG_ESCAPECTLS|RULEFLAG_ESCAPEBACKREF;
        }
        else {
            ++error;
        }
        break;
    case 'c':
    case 'C':
        if (!*key || !strcasecmp(key, "hain")) {           /* chain */
            cfg->flags |= RULEFLAG_CHAIN;
        }
        else if (((*key == 'O' || *key == 'o') && !key[1])
                 || !strcasecmp(key, "ookie")) {           /* cookie */
            data_item *cp = cfg->cookie;

            if (!cp) {
                cp = cfg->cookie = apr_palloc(p, sizeof(*cp));
            }
            else {
                while (cp->next) {
                    cp = cp->next;
                }
                cp->next = apr_palloc(p, sizeof(*cp));
                cp = cp->next;
            }

            cp->next = NULL;
            cp->data = val;
        }
        else {
            ++error;
        }
        break;
    case 'd':
    case 'D':
        if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) {
            cfg->flags |= (RULEFLAG_DISCARDPATHINFO);
        }
        break;
    case 'e':
    case 'E':
        if (!*key || !strcasecmp(key, "nv")) {             /* env */
            data_item *cp = cfg->env;

            if (!cp) {
                cp = cfg->env = apr_palloc(p, sizeof(*cp));
            }
            else {
                while (cp->next) {
                    cp = cp->next;
                }
                cp->next = apr_palloc(p, sizeof(*cp));
                cp = cp->next;
            }

            cp->next = NULL;
            cp->data = val;
        }
        else if (!strcasecmp(key, "nd")) {                /* end */
            cfg->flags |= RULEFLAG_END;
        }
        else {
            ++error;
        }
        break;

    case 'f':
    case 'F':
        if (!*key || !strcasecmp(key, "orbidden")) {       /* forbidden */
            cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
            cfg->forced_responsecode = HTTP_FORBIDDEN;
        }
        else {
            ++error;
        }
        break;

    case 'g':
    case 'G':
        if (!*key || !strcasecmp(key, "one")) {            /* gone */
            cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
            cfg->forced_responsecode = HTTP_GONE;
        }
        else {
            ++error;
        }
        break;

    case 'h':
    case 'H':
        if (!*key || !strcasecmp(key, "andler")) {         /* handler */
            cfg->forced_handler = val;
        }
        else {
            ++error;
        }
        break;
    case 'l':
    case 'L':
        if (!*key || !strcasecmp(key, "ast")) {            /* last */
            cfg->flags |= RULEFLAG_LASTRULE;
        }
        else {
            ++error;
        }
        break;

    case 'n':
    case 'N':
        if (((*key == 'E' || *key == 'e') && !key[1])
            || !strcasecmp(key, "oescape")) {              /* noescape */
            cfg->flags |= RULEFLAG_NOESCAPE;
        }
        else if (!*key || !strcasecmp(key, "ext")) {       /* next */
            cfg->flags |= RULEFLAG_NEWROUND;
            if (val && *val) { 
                cfg->maxrounds = atoi(val);
            }

        }
        else if (((*key == 'S' || *key == 's') && !key[1])
            || !strcasecmp(key, "osubreq")) {              /* nosubreq */
            cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
        }
        else if (((*key == 'C' || *key == 'c') && !key[1])
            || !strcasecmp(key, "ocase")) {                /* nocase */
            cfg->flags |= RULEFLAG_NOCASE;
        }
        else {
            ++error;
        }
        break;

    case 'p':
    case 'P':
        if (!*key || !strcasecmp(key, "roxy")) {           /* proxy */
            cfg->flags |= RULEFLAG_PROXY;
        }
        else if (((*key == 'T' || *key == 't') && !key[1])
            || !strcasecmp(key, "assthrough")) {           /* passthrough */
            cfg->flags |= RULEFLAG_PASSTHROUGH;
        }
        else {
            ++error;
        }
        break;

    case 'q':
    case 'Q':
        if (   !strcasecmp(key, "SA")
            || !strcasecmp(key, "sappend")) {              /* qsappend */
            cfg->flags |= RULEFLAG_QSAPPEND;
        } else if ( !strcasecmp(key, "SD")
                || !strcasecmp(key, "sdiscard") ) {       /* qsdiscard */
            cfg->flags |= RULEFLAG_QSDISCARD;
        } else if ( !strcasecmp(key, "SL")
                || !strcasecmp(key, "slast") ) {          /* qslast */
            cfg->flags |= RULEFLAG_QSLAST;
        }
        else {
            ++error;
        }
        break;

    case 'r':
    case 'R':
        if (!*key || !strcasecmp(key, "edirect")) {        /* redirect */
            int status = 0;

            cfg->flags |= RULEFLAG_FORCEREDIRECT;
            if (*val) {
                if (strcasecmp(val, "permanent") == 0) {
                    status = HTTP_MOVED_PERMANENTLY;
                }
                else if (strcasecmp(val, "temp") == 0) {
                    status = HTTP_MOVED_TEMPORARILY;
                }
                else if (strcasecmp(val, "seeother") == 0) {
                    status = HTTP_SEE_OTHER;
                }
                else if (apr_isdigit(*val)) {
                    status = atoi(val);
                    if (status != HTTP_INTERNAL_SERVER_ERROR) {
                        int idx =
                            ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);

                        if (ap_index_of_response(status) == idx) {
                            return apr_psprintf(p, "invalid HTTP "
                                                   "response code '%s' for "
                                                   "flag 'R'",
                                                val);
                        }
                    }
                    if (!ap_is_HTTP_REDIRECT(status)) {
                        cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
                    }
                }
                cfg->forced_responsecode = status;
            }
        }
        else {
            ++error;
        }
        break;

    case 's':
    case 'S':
        if (!*key || !strcasecmp(key, "kip")) {            /* skip */
            cfg->skip = atoi(val);
        }
        else {
            ++error;
        }
        break;

    case 't':
    case 'T':
        if (!*key || !strcasecmp(key, "ype")) {            /* type */
            cfg->forced_mimetype = val;
        }
        else {
            ++error;
        }
        break;
    case 'u':
    case 'U':
        if (!strcasecmp(key, "nsafePrefixStat")){
            cfg->flags |= (RULEFLAG_UNSAFE_PREFIX_STAT);
        }
        else if(!strcasecmp(key, "nsafeAllow3F")) {
            cfg->flags |= RULEFLAG_UNSAFE_ALLOW3F;
        }
        else if(!strcasecmp(key, "NC")) {
            cfg->flags |= RULEFLAG_UNC;
        }
        else {
            ++error;
        }
        break;
    default:
        ++error;
        break;
    }

    if (error) {
        return apr_pstrcat(p, "unknown flag '", --key, "'", NULL);
    }

    return NULL;
}

static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
                                   const char *in_str)
{
    rewrite_perdir_conf *dconf = in_dconf;
    char *str = apr_pstrdup(cmd->pool, in_str);
    rewrite_server_conf *sconf;
    rewriterule_entry *newrule;
    ap_regex_t *regexp;
    char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL;
    const char *err;

    sconf = ap_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 */
        newrule = apr_array_push(sconf->rewriterules);
    }
    else {                     /* is per-directory command */
        newrule = apr_array_push(dconf->rewriterules);
    }

    /*  parse the argument line ourself */
    if (parseargline(str, &a1, &a2, &a2_end, &a3)) {
        return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
                           "'", NULL);
    }

    newrule->forced_mimetype     = NULL;
    newrule->forced_handler      = NULL;
    newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
    newrule->flags  = RULEFLAG_NONE;
    newrule->env = NULL;
    newrule->cookie = NULL;
    newrule->skip   = 0;
    newrule->maxrounds = REWRITE_MAX_ROUNDS;
    newrule->escapes = newrule->noescapes = NULL;

    /* arg3: optional flags field */
    if (a3 != NULL) {
        if ((err = cmd_parseflagfield(cmd->pool, newrule, a3,
                                      cmd_rewriterule_setflag)) != NULL) {
            return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL);
        }
    }

    /* arg1: the pattern
     * try to compile the regexp to test if is ok
     */

    if (*a1 == '!') {
        newrule->flags |= RULEFLAG_NOTMATCH;
        ++a1;
    }

    regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED |
                                        ((newrule->flags & RULEFLAG_NOCASE)
                                         ? AP_REG_ICASE : 0));
    if (!regexp) {
        return apr_pstrcat(cmd->pool,
                           "RewriteRule: cannot compile regular expression '",
                           a1, "'", NULL);
    }

    newrule->pattern = a1;
    newrule->regexp  = regexp;

    /* arg2: the output string */
    newrule->output = a2;
    if (*a2 == '-' && !a2[1]) {
        newrule->flags |= RULEFLAG_NOSUB;
    }

    if (*(a2_end-1) == '?') {
        /* a literal ? at the end of the unsubstituted rewrite rule */
        if (newrule->flags & RULEFLAG_QSAPPEND) {
           /* with QSA, splitout_queryargs will safely handle it if RULEFLAG_QSLAST is set */
           newrule->flags |= RULEFLAG_QSLAST;
        }
        else {
            /* avoid getting a query string via inadvertent capture */
            newrule->flags |= RULEFLAG_QSNONE;
            /* trailing ? has done its job, but splitout_queryargs will not chop it off */
            *(a2_end-1) = '\0';
       }
    }
    else if (newrule->flags & RULEFLAG_QSDISCARD) {
        if (NULL == ap_strchr(newrule->output, '?')) {
            newrule->flags |= RULEFLAG_QSNONE;
        }
    }

    /* now, if the server or per-dir config holds an
     * array of RewriteCond entries, we take it for us
     * and clear the array
     */

    if (cmd->path == NULL) {  /* is server command */
        newrule->rewriteconds   = sconf->rewriteconds;
        sconf->rewriteconds = apr_array_make(cmd->pool, 2,
                                             sizeof(rewritecond_entry));
    }
    else {                    /* is per-directory command */
        newrule->rewriteconds   = dconf->rewriteconds;
        dconf->rewriteconds = apr_array_make(cmd->pool, 2,
                                             sizeof(rewritecond_entry));
    }

    return NULL;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                  the rewriting engine
 * |                                                       |
 * +-------------------------------------------------------+
 */


/* Lexicographic Compare */
static APR_INLINE int compare_lexicography(char *a, char *b)
{
    apr_size_t i, lena, lenb;

    lena = strlen(a);
    lenb = strlen(b);

    if (lena == lenb) {
        for (i = 0; i < lena; ++i) {
            if (a[i] != b[i]) {
                return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1;
            }
        }

        return 0;
    }

    return ((lena > lenb) ? 1 : -1);
}

/*
 * Apply a single rewriteCond
 */

static cond_return_type apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
{
    char *input = NULL;
    apr_finfo_t sb;
    request_rec *rsub, *r = ctx->r;
    ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
    int rc = COND_RC_NOMATCH;
    int basis;

    if (p->ptype != CONDPAT_AP_EXPR)
        input = do_expand(p->input, ctx, NULL, NULL);

    switch (p->ptype) {
    case CONDPAT_FILE_EXISTS:
        if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
            r->status = HTTP_FORBIDDEN;
            rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
            return COND_RC_STATUS_SET;
        }
        if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
            && sb.filetype == APR_REG) {
            rc = COND_RC_MATCH;
        }
        break;

    case CONDPAT_FILE_SIZE:
        if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
            r->status = HTTP_FORBIDDEN;
            rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
            return COND_RC_STATUS_SET;
        }
        if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
            && sb.filetype == APR_REG && sb.size > 0) {
            rc = COND_RC_MATCH;
        }
        break;

    case CONDPAT_FILE_LINK:
        if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
            r->status = HTTP_FORBIDDEN;
            rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
            return COND_RC_STATUS_SET;
        }
#if !defined(OS2)
        if (   apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK,
                        r->pool) == APR_SUCCESS
            && sb.filetype == APR_LNK) {
            rc = COND_RC_MATCH;
        }
#endif
        break;

    case CONDPAT_FILE_DIR:
        if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
            r->status = HTTP_FORBIDDEN;
            rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
            return COND_RC_STATUS_SET;
        }
        if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
            && sb.filetype == APR_DIR) {
            rc = COND_RC_MATCH;
        }
        break;

    case CONDPAT_FILE_XBIT:
        if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
            r->status = HTTP_FORBIDDEN;
            rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
            return COND_RC_STATUS_SET;
        }
        if (   apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS
            && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
            rc = COND_RC_MATCH;
        }
        break;

    case CONDPAT_LU_URL:
        if (*input && subreq_ok(r)) {
            rsub = ap_sub_req_lookup_uri(input, r, NULL);
            if (rsub->status < 400) {
                rc = COND_RC_MATCH;
            }
            rewritelog(r, 5, NULL, "RewriteCond URI (-U check: "
                        "path=%s -> status=%d", input, rsub->status);
            ap_destroy_sub_req(rsub);
        }
        break;

    case CONDPAT_LU_FILE:
        if (*input && subreq_ok(r)) {
            if (APR_SUCCESS != ap_stat_check(input, r->pool)) {
                r->status = HTTP_FORBIDDEN;
                rewritelog(r, 4, ctx->perdir, "RewriteCond: refusing to stat input='%s'", input);
                return COND_RC_STATUS_SET;
            }
            rsub = ap_sub_req_lookup_file(input, r, NULL);
            if (rsub->status < 300 &&
                /* double-check that file exists since default result is 200 */
                apr_stat(&sb, rsub->filename, APR_FINFO_MIN,
                         r->pool) == APR_SUCCESS) {
                rc = COND_RC_MATCH;
            }
            rewritelog(r, 5, NULL, "RewriteCond file (-F check: path=%s "
                        "-> file=%s status=%d", input, rsub->filename,
                        rsub->status);
            ap_destroy_sub_req(rsub);
        }
        break;

    case CONDPAT_STR_GE:
        basis = 0;
        goto test_str_g;
    case CONDPAT_STR_GT:
        basis = 1;
test_str_g:
        if (p->flags & CONDFLAG_NOCASE) {
            rc = (strcasecmp(input, p->pattern) >= basis) ? COND_RC_MATCH : COND_RC_NOMATCH;
        }
        else {
            rc = (compare_lexicography(input, p->pattern) >= basis) ? COND_RC_MATCH : COND_RC_NOMATCH;
        }
        break;

    case CONDPAT_STR_LE:
        basis = 0;
        goto test_str_l;
    case CONDPAT_STR_LT:
        basis = -1;
test_str_l:
        if (p->flags & CONDFLAG_NOCASE) {
            rc = (strcasecmp(input, p->pattern) <= basis) ? COND_RC_MATCH : COND_RC_NOMATCH;
        }
        else {
            rc = (compare_lexicography(input, p->pattern) <= basis) ? COND_RC_MATCH : COND_RC_NOMATCH;
        }
        break;

    case CONDPAT_STR_EQ:
        /* Note: the only type where the operator is dropped from p->pattern */
        if (p->flags & CONDFLAG_NOCASE) {
            rc = !strcasecmp(input, p->pattern);
        }
        else {
            rc = !strcmp(input, p->pattern);
        }
        break;

    case CONDPAT_INT_GE: rc = (atoi(input) >= atoi(p->pattern)); break;
    case CONDPAT_INT_GT: rc = (atoi(input) > atoi(p->pattern));  break;

    case CONDPAT_INT_LE: rc = (atoi(input) <= atoi(p->pattern)); break;
    case CONDPAT_INT_LT: rc = (atoi(input) < atoi(p->pattern));  break;

    case CONDPAT_INT_EQ: rc = (atoi(input) == atoi(p->pattern)); break;

    case CONDPAT_AP_EXPR:
        {
            const char *err, *source;
            rc = ap_expr_exec_re(r, p->expr, AP_MAX_REG_MATCH, regmatch,
                                 &source, &err);
            if (rc < 0 || err) {
                rewritelog(r, 1, ctx->perdir,
                            "RewriteCond: expr='%s' evaluation failed: %s",
                            p->pattern - p->pskip, err);
                rc = COND_RC_NOMATCH;
            }
            else {
                rc = (rc > 0) ? COND_RC_MATCH : COND_RC_NOMATCH;
            }

            /* update briRC backref info */
            if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
                ctx->briRC.source = source;
                memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
            }
        }
        break;
    default:
        /* it is really a regexp pattern, so apply it */
        rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0);

        /* update briRC backref info */
        if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
            ctx->briRC.source = input;
            memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
        }
        break;
    }

    if (p->flags & CONDFLAG_NOTMATCH && rc <= COND_RC_MATCH) {
        rc = !rc;
    }

    rewritelog(r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s'%s "
               "=> %s", input, p->pattern - p->pskip,
               (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "",
               rc ? "matched" : "not-matched");

    return rc;
}

/* check for forced type and handler */
static APR_INLINE void force_type_handler(rewriterule_entry *p,
                                          rewrite_ctx *ctx)
{
    char *expanded;

    if (p->forced_mimetype) {
        expanded = do_expand(p->forced_mimetype, ctx, p, NULL);

        if (*expanded) {
            ap_str_tolower(expanded);

            rewritelog(ctx->r, 2, ctx->perdir, "remember %s to have MIME-type "
                        "'%s'", ctx->r->filename, expanded);

            apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
                           expanded);
        }
    }

    if (p->forced_handler) {
        expanded = do_expand(p->forced_handler, ctx, p, NULL);

        if (*expanded) {
            ap_str_tolower(expanded);

            rewritelog(ctx->r, 2, ctx->perdir, "remember %s to have "
                        "Content-handler '%s'", ctx->r->filename, expanded);

            apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR,
                           expanded);
        }
    }
}

/*
 * Apply a single RewriteRule
 */

static rule_return_type apply_rewrite_rule(rewriterule_entry *p,
                                           rewrite_ctx *ctx)
{
    ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
    apr_array_header_t *rewriteconds;
    rewritecond_entry *conds;
    int i, rc;
    char *newuri = NULL;
    request_rec *r = ctx->r;
    int is_proxyreq = 0;
    int prefix_added = 0;

    ctx->uri = r->filename;

    if (ctx->perdir) {
        apr_size_t dirlen = strlen(ctx->perdir);

        /*
         * Proxy request?
         */

        is_proxyreq = (   r->proxyreq && r->filename
                       && !strncmp(r->filename, "proxy:", 6));

        /* Since we want to match against the (so called) full URL, we have
         * to re-add the PATH_INFO postfix
         */

        if (r->path_info && *r->path_info) {
            rewritelog(r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
                       ctx->uri, ctx->uri, r->path_info);
            ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
        }

        /* Additionally we strip the physical path from the url to match
         * it independent from the underlying filesystem.
         */

        if (!is_proxyreq && strlen(ctx->uri) >= dirlen &&
            !strncmp(ctx->uri, ctx->perdir, dirlen)) {

            rewritelog(r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s",
                       ctx->uri, ctx->uri + dirlen);
            ctx->uri = ctx->uri + dirlen;
        }
    }

    /* Try to match the URI against the RewriteRule pattern
     * and exit immediately if it didn't apply.
     */

    rewritelog(r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
                p->pattern, ctx->uri);

    rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0);
    if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
           (!rc &&  (p->flags & RULEFLAG_NOTMATCH))   ) ) {
        return RULE_RC_NOMATCH;
    }

    /* It matched, wow! Now it's time to prepare the context structure for
     * further processing
     */

    ctx->vary_this = NULL;
    ctx->briRC.source = NULL;

    if (p->flags & RULEFLAG_NOTMATCH) {
        ctx->briRR.source = NULL;
    }
    else {
        ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri);
        memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch));
    }

    /* Ok, we already know the pattern has matched, but we now
     * additionally have to check for all existing preconditions
     * (RewriteCond) which have to be also true. We do this at
     * this very late stage to avoid unnecessary checks which
     * would slow down the rewriting engine.
     */

    rewriteconds = p->rewriteconds;
    conds = (rewritecond_entry *)rewriteconds->elts;

    for (i = 0; i < rewriteconds->nelts; ++i) {
        rewritecond_entry *c = &conds[i];

        rc = apply_rewrite_cond(c, ctx);

        /* Error while evaluating cond, r->status set */
        if (COND_RC_STATUS_SET == rc) {
            return RULE_RC_STATUS_SET;
        }

        /*
         * Reset vary_this if the novary flag is set for this condition.
         */

        if (c->flags & CONDFLAG_NOVARY) {
            ctx->vary_this = NULL;
        }
        if (c->flags & CONDFLAG_ORNEXT) {
            if (!rc) {
                /* One condition is false, but another can be still true. */
                ctx->vary_this = NULL;
                continue;
            }
            else {
                /* skip the rest of the chained OR conditions */
                while (   i < rewriteconds->nelts
                       && c->flags & CONDFLAG_ORNEXT) {
                    c = &conds[++i];
                }
            }
        }
        else if (!rc) {
            return RULE_RC_NOMATCH;
        }

        /* If some HTTP header was involved in the condition, remember it
         * for later use
         */

        if (ctx->vary_this) {
            ctx->vary = ctx->vary
                        ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
                                      NULL)
                        : ctx->vary_this;
            ctx->vary_this = NULL;
        }
    }

    /* expand the result */
    if (!(p->flags & RULEFLAG_NOSUB)) {
        int unsafe_qmark = -1;

        if (p->flags & RULEFLAG_UNSAFE_ALLOW3F) {
            newuri = do_expand(p->output, ctx, p, NULL);
        }
        else {
            newuri = do_expand(p->output, ctx, p, &unsafe_qmark);
        }
        rewritelog(r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
                   newuri);

        if (unsafe_qmark > 0) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10508)
                    "Unsafe URL with %%3f URL rewritten without "
                    "UnsafeAllow3F");
            r->status = HTTP_FORBIDDEN;
            return RULE_RC_STATUS_SET;
        }
    }

    /* expand [E=var:val] and [CO=<cookie>] */
    do_expand_env(p->env, ctx);
    do_expand_cookie(p->cookie, ctx);

    /* non-substitution rules ('RewriteRule <pat> -') end here. */
    if (p->flags & RULEFLAG_NOSUB) {
        force_type_handler(p, ctx);

        if (p->flags & RULEFLAG_STATUS) {
            rewritelog(r, 2, ctx->perdir, "forcing responsecode %d for %s",
                       p->forced_responsecode, r->filename);

            r->status = p->forced_responsecode;
        }

        return RULE_RC_NOSUB;
    }

    /* Add the previously stripped per-directory location prefix, unless
     * (1) it's an absolute URL path and
     * (2) it's a full qualified URL
     */

    if (!is_proxyreq
        && !is_absolute_path(newuri)
        && !AP_IS_SLASH(*newuri)
        && !is_absolute_uri(newuri, NULL)) {
        if (ctx->perdir) {
            rewritelog(r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
                       newuri, ctx->perdir, newuri);

            newuri = apr_pstrcat(r->pool, ctx->perdir, newuri, NULL);
            prefix_added = 1;
        }
        else if (!(p->flags & (RULEFLAG_PROXY | RULEFLAG_FORCEREDIRECT))) {
            /* Not an absolute URI-path and the scheme (if any) is unknown,
             * and it won't be passed to fully_qualify_uri() below either,
             * so add an implicit '/' prefix. This avoids potentially a common
             * rule like "RewriteRule ^/some/path(.*) $1" that is given a path
             * like "/some/pathscheme:..." to produce the fully qualified URL
             * "scheme:..." which could be misinterpreted later.
             */

            rewritelog(r, 3, ctx->perdir, "add root prefix: %s -> /%s",
                       newuri, newuri);

            newuri = apr_pstrcat(r->pool, "/", newuri, NULL);
            prefix_added = 1;
        }
    }

    /* Now adjust API's knowledge about r->filename and r->args */
    r->filename = newuri;

    if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) {
        r->path_info = NULL;
    }

    splitout_queryargs(r, p->flags);

    /* If this rule is forced for proxy throughput
     * (`RewriteRule ... ... [P]') then emulate mod_proxy's
     * URL-to-filename handler to be sure mod_proxy is triggered
     * for this URL later in the Apache API. But make sure it is
     * a fully-qualified URL. (If not it is qualified with
     * ourself).
     */

    if (p->flags & RULEFLAG_PROXY) {
        fully_qualify_uri(r);

        rewritelog(r, 2, ctx->perdir, "forcing proxy-throughput with %s",
                   r->filename);

        r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
        return RULE_RC_MATCH;
    }

    /* If this rule is explicitly forced for HTTP redirection
     * (`RewriteRule .. .. [R]') then force an external HTTP
     * redirect. But make sure it is a fully-qualified URL. (If
     * not it is qualified with ourself).
     */

    if (p->flags & RULEFLAG_FORCEREDIRECT) {
        fully_qualify_uri(r);

        rewritelog(r, 2, ctx->perdir, "explicitly forcing redirect with %s",
                   r->filename);

        r->status = p->forced_responsecode;
        return RULE_RC_MATCH;
    }

    /* Special Rewriting Feature: Self-Reduction
     * We reduce the URL by stripping a possible
     * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
     * corresponds to ourself. This is to simplify rewrite maps
     * and to avoid recursion, etc. When this prefix is not a
     * coincidence then the user has to use [R] explicitly (see
     * above).
     */

    reduce_uri(r);

    /* If this rule is still implicitly forced for HTTP
     * redirection (`RewriteRule .. <scheme>://...') then
     * directly force an external HTTP redirect.
     */

    if (is_absolute_uri(r->filename, NULL)) {
        rewritelog(r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d "
                   "with %s", p->forced_responsecode, r->filename);

        r->status = p->forced_responsecode;
        return RULE_RC_MATCH;
    }

    if (!((p->flags & RULEFLAG_UNC) || prefix_added)) {
        /* merge leading slashes, unless they were literals in the sub */
        if (!AP_IS_SLASH(p->output[0]) || !AP_IS_SLASH(p->output[1])) {
            while (AP_IS_SLASH(r->filename[0]) &&
                   AP_IS_SLASH(r->filename[1])) {
                r->filename++;
            }
        }
    }

    /* Finally remember the forced mime-type */
    force_type_handler(p, ctx);

    /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
     * But now we're done for this particular rule.
     */

    return RULE_RC_MATCH;
}

/*
 * Apply a complete rule set,
 * i.e. a list of rewrite rules
 */

static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
                              char *perdir, rewriterule_entry **lastsub)
{
    rewriterule_entry *entries;
    rewriterule_entry *p;
    int i;
    int changed;
    rule_return_type rc;
    int s;
    rewrite_ctx *ctx;
    int round = 1;

    ctx = apr_palloc(r->pool, sizeof(*ctx));
    ctx->perdir = perdir;
    ctx->r = r;
    *lastsub = NULL;

    /*
     *  Iterate over all existing rules
     */

    entries = (rewriterule_entry *)rewriterules->elts;
    changed = 0;
    loop:
    for (i = 0; i < rewriterules->nelts; i++) {
        p = &entries[i];

        /*
         *  Ignore this rule on subrequests if we are explicitly
         *  asked to do so or this is a proxy-throughput or a
         *  forced redirect rule.
         */

        if (r->main != NULL &&
            (p->flags & RULEFLAG_IGNOREONSUBREQ ||
             p->flags & RULEFLAG_FORCEREDIRECT    )) {
            continue;
        }

        /*
         *  Apply the current rule.
         */

        ctx->vary = NULL;
        rc = apply_rewrite_rule(p, ctx);

        if (rc != RULE_RC_NOMATCH) {

            if (!(p->flags & RULEFLAG_NOSUB)) {
                rewritelog(r, 2, perdir, "setting lastsub to rule with output %s", p->output);
                *lastsub = p;
            }

            /* Catch looping rules with pathinfo growing unbounded */
            if ( strlen( r->filename ) > 2*r->server->limit_req_line ) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                              "RewriteRule '%s' and URI '%s' "
                              "exceeded maximum length (%d)"
                              p->pattern, r->uri, 2*r->server->limit_req_line );
                r->status = HTTP_INTERNAL_SERVER_ERROR;
                return ACTION_STATUS;
            }

            /* Regardless of what we do next, we've found a match. Check to see
             * if any of the request header fields were involved, and add them
             * to the Vary field of the response.
             */

            if (ctx->vary) {
                apr_table_merge(r->headers_out, "Vary", ctx->vary);
            }


            /* Error while evaluating rule, r->status set */
            if (RULE_RC_STATUS_SET == rc) {
                return ACTION_STATUS_SET;
            }

            /*
             * The rule sets the response code (implies match-only)
             */

            if (p->flags & RULEFLAG_STATUS) {
                return ACTION_STATUS;
            }

            /*
             * Indicate a change if this was not a match-only rule.
             */

            if (rc != RULE_RC_NOSUB) {
                changed = ((p->flags & RULEFLAG_NOESCAPE)
                           ? ACTION_NOESCAPE : ACTION_NORMAL);
            }

            /*
             *  Pass-Through Feature (`RewriteRule .. .. [PT]'):
             *  Because the Apache 1.x API is very limited we
             *  need this hack to pass the rewritten URL to other
             *  modules like mod_alias, mod_userdir, etc.
             */

            if (p->flags & RULEFLAG_PASSTHROUGH) {
                rewritelog(r, 2, perdir, "forcing '%s' to get passed through "
                           "to next API URI-to-filename handler", r->filename);
                r->filename = apr_pstrcat(r->pool, "passthrough:",
                                         r->filename, NULL);
                changed = ACTION_NORMAL;
                break;
            }

            if (p->flags & RULEFLAG_END) {
                rewritelog(r, 8, perdir, "Rule has END flag, no further rewriting for this request");
                apr_pool_userdata_set("1", really_last_key, apr_pool_cleanup_null, r->pool);
                break;
            }
            /*
             *  Stop processing also on proxy pass-through and
             *  last-rule and new-round flags.
             */

            if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) {
                break;
            }

            /*
             *  On "new-round" flag we just start from the top of
             *  the rewriting ruleset again.
             */

            if (p->flags & RULEFLAG_NEWROUND) {
                if (++round >= p->maxrounds) { 
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02596)
                                  "RewriteRule '%s' and URI '%s' exceeded "
                                  "maximum number of rounds (%d) via the [N] flag"
                                  p->pattern, r->uri, p->maxrounds);

                    r->status = HTTP_INTERNAL_SERVER_ERROR;
                    return ACTION_STATUS; 
                }
                goto loop;
            }

            /*
             *  If we are forced to skip N next rules, do it now.
             */

            if (p->skip > 0) {
                s = p->skip;
                while (   i < rewriterules->nelts
                       && s > 0) {
                    i++;
                    s--;
                }
            }
        }
        else {
            /*
             *  If current rule is chained with next rule(s),
             *  skip all this next rule(s)
             */

            while (   i < rewriterules->nelts
                   && p->flags & RULEFLAG_CHAIN) {
                i++;
                p = &entries[i];
            }
        }
    }
    return changed;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |             Module Initialization Hooks
 * |                                                       |
 * +-------------------------------------------------------+
 */


static int pre_config(apr_pool_t *pconf,
                      apr_pool_t *plog,
                      apr_pool_t *ptemp)
{
    APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;

    rewrite_lock_needed = 0; 
    ap_mutex_register(pconf, rewritemap_mutex_type, NULL, APR_LOCK_DEFAULT, 0);

    /* register int: rewritemap handlers */
    map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
    if (map_pfn_register) {
        map_pfn_register("tolower", rewrite_mapfunc_tolower);
        map_pfn_register("toupper", rewrite_mapfunc_toupper);
        map_pfn_register("escape", rewrite_mapfunc_escape);
        map_pfn_register("unescape", rewrite_mapfunc_unescape);
    }
    dbd_acquire = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
    dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
    return OK;
}

static int post_config(apr_pool_t *p,
                       apr_pool_t *plog,
                       apr_pool_t *ptemp,
                       server_rec *s)
{
    apr_status_t rv;

    /* check if proxy module is available */
    proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);

    if (rewrite_lock_needed) {
        rv = rewritelock_create(s, p);
        if (rv != APR_SUCCESS) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }

        apr_pool_cleanup_register(p, (void *)s, rewritelock_remove,
                apr_pool_cleanup_null);
    }

    /* if we are not doing the initial config, step through the servers and
     * open the RewriteMap prg:xxx programs,
     */

    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) {
        for (; s; s = s->next) {
            if (run_rewritemap_programs(s, p) != APR_SUCCESS) {
                return HTTP_INTERNAL_SERVER_ERROR;
            }
        }
    }

    return OK;
}

static void init_child(apr_pool_t *p, server_rec *s)
{
    apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */

    if (rewrite_mapr_lock_acquire) {
        rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
                 apr_global_mutex_lockfile(rewrite_mapr_lock_acquire), p);
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00666)
                         "mod_rewrite: could not init rewrite_mapr_lock_acquire"
                         " in child");
        }
    }

    /* create the lookup cache */
    if (!init_cache(p)) {
        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00667)
                     "mod_rewrite: could not init map cache in child");
    }
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                     runtime hooks
 * |                                                       |
 * +-------------------------------------------------------+
 */


/*
 * URI-to-filename hook
 * [deals with RewriteRules in server context]
 */

static int hook_uri2file(request_rec *r)
{
    rewrite_perdir_conf *dconf;
    rewrite_server_conf *conf;
    const char *saved_rulestatus;
    const char *var;
    const char *thisserver;
    char *thisport;
    const char *thisurl;
    unsigned int port;
    int rulestatus;
    void *skipdata;
    const char *oargs;
    rewriterule_entry *lastsub = NULL;

    /*
     *  retrieve the config structures
     */

    conf = ap_get_module_config(r->server->module_config, &rewrite_module);

    dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
                                                        &rewrite_module);

    /*
     *  only do something under runtime if the engine is really enabled,
     *  else return immediately!
     */

    if (!dconf || dconf->state == ENGINE_DISABLED) {
        return DECLINED;
    }

    /*
     *  check for the ugly API case of a virtual host section where no
     *  mod_rewrite directives exists. In this situation we became no chance
     *  by the API to setup our default per-server config so we have to
     *  on-the-fly assume we have the default config. But because the default
     *  config has a disabled rewriting engine we are lucky because can
     *  just stop operating now.
     */

    if (conf->server != r->server) {
        return DECLINED;
    }

    /* END flag was used as a RewriteRule flag on this request */
    apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
    if (skipdata != NULL) {
        rewritelog(r, 8, NULL, "Declining, no further rewriting due to END flag");
        return DECLINED;
    }

    /* Unless the anyuri option is set, ensure that the input to the
     * first rule really is a URL-path, avoiding security issues with
     * poorly configured rules.  See CVE-2011-3368, CVE-2011-4317. */

    if ((dconf->options & OPTION_ANYURI) == 0
        && ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
            || !r->uri || r->uri[0] != '/')) {
        rewritelog(r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. "
                    "Consult the manual entry for the RewriteOptions directive "
                    "for options and caveats about matching other strings.",
                   r->uri);
        return DECLINED;
    }

    /*
     *  remember the original query string for later check, since we don't
     *  want to apply URL-escaping when no substitution has changed it.
     */

    oargs = r->args;

    /*
     *  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 = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL);
         if (var == NULL) {
             apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
         }
         else {
             apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
         }
    }
    else {
         var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
         apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
    }

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


    /* add the canonical URI of this URL */
    thisserver = ap_get_server_name_for_url(r);
    port = ap_get_server_port(r);
    if (ap_is_default_port(port, r)) {
        thisport = "";
    }
    else {
        thisport = apr_psprintf(r->pool, ":%u", port);
    }
    thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);

    /* set the variable */
    var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport,
                      thisurl, NULL);
    apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);

    if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) {
        /* if filename was not initially set,
         * we start with the requested URI
         */

        if (r->filename == NULL) {
            r->filename = apr_pstrdup(r->pool, r->uri);
            rewritelog(r, 2, NULL, "init rewrite engine with requested uri %s",
                        r->filename);
        }
        else {
            rewritelog(r, 2, NULL, "init rewrite engine with passed filename "
                        "%s. Original uri = %s", r->filename, r->uri);
        }

        /*
         *  now apply the rules ...
         */

        rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL, &lastsub);
        apr_table_setn(r->notes, "mod_rewrite_rewritten",
                       apr_psprintf(r->pool,"%d",rulestatus));
    }
    else {
        rewritelog(r, 2, NULL, "uri already rewritten. Status %s, Uri %s, "
                   "r->filename %s", saved_rulestatus, r->uri, r->filename);

        rulestatus = atoi(saved_rulestatus);
    }

    if (rulestatus) {
        apr_size_t flen =  r->filename ? strlen(r->filename) : 0;
        unsigned skip_absolute = flen ? is_absolute_uri(r->filename, NULL) : 0;
        int to_proxyreq = (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0);
        int will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE);

        if (r->args
                && !will_escape
                && *(ap_scan_vchar_obstext(r->args))) {
            /*
             * We have a raw control character or a ' ' in r->args.
             * Correct encoding was missed.
             * Correct encoding was missed and we're not going to escape
             * it before returning.
             */

            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410)
                          "Rewritten query string contains control "
                          "characters or spaces");
            return HTTP_FORBIDDEN;
        }

        if (ACTION_STATUS == rulestatus) {
            int n = r->status;

            r->status = HTTP_OK;
            return n;
        }
        else if (ACTION_STATUS_SET == rulestatus) {
            return r->status;
        }

        if (to_proxyreq) {
            /* it should be go on as an internal proxy request */

            /* check if the proxy module is enabled, so
             * we can actually use it!
             */

            if (!proxy_available) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00669)
                              "attempt to make remote request from mod_rewrite "
                              "without proxy enabled: %s", r->filename);
                return HTTP_FORBIDDEN;
            }

            if (rulestatus == ACTION_NOESCAPE) {
                apr_table_setn(r->notes, "proxy-nocanon""1");
            }

            /* make sure the QUERY_STRING and
             * PATH_INFO parts get incorporated
             */

            if (r->path_info != NULL) {
                r->filename = apr_pstrcat(r->pool, r->filename,
                                          r->path_info, NULL);
            }
            if ((r->args != NULL)
                && ((r->proxyreq == PROXYREQ_PROXY)
                    || apr_table_get(r->notes, "proxy-nocanon"))) {
                /* see proxy_http:proxy_http_canon() */
                r->filename = apr_pstrcat(r->pool, r->filename,
                                          "?", r->args, NULL);
            }

            /* now make sure the request gets handled by the proxy handler */
            if (PROXYREQ_NONE == r->proxyreq) {
                r->proxyreq = PROXYREQ_REVERSE;
            }
            r->handler  = "proxy-server";

            rewritelog(r, 1, NULL, "go-ahead with proxy request %s [OK]",
                       r->filename);
            return OK;
        }
        else if (skip_absolute > 0) {
            int n;

            /* it was finally rewritten to a remote URL */

            if (rulestatus != ACTION_NOESCAPE) {
                rewritelog(r, 1, NULL, "escaping %s for redirect",
                           r->filename);
                r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute);
            }

            /* append the QUERY_STRING part */
            if (r->args) {
                char *escaped_args = NULL;
                int noescape = (rulestatus == ACTION_NOESCAPE ||
                                (oargs && !strcmp(r->args, oargs)));

                r->filename = apr_pstrcat(r->pool, r->filename, "?",
                                          noescape
                                            ? r->args
                                            : (escaped_args =
                                               ap_escape_uri(r->pool, r->args)),
                                          NULL);

                rewritelog(r, 1, NULL, "%s %s to query string for redirect %s",
                           noescape ? "copying" : "escaping",
                           r->args ,
                           noescape ? "" : escaped_args);
            }

            /* determine HTTP redirect response code */
            if (ap_is_HTTP_REDIRECT(r->status)) {
                n = r->status;
                r->status = HTTP_OK; /* make Apache kernel happy */
            }
            else {
                n = HTTP_MOVED_TEMPORARILY;
            }

            /* now do the redirection */
            apr_table_setn(r->headers_out, "Location", r->filename);
            rewritelog(r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
                       n);

            return n;
        }
        else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
            /*
             * Hack because of underpowered API: passing the current
             * rewritten filename through to other URL-to-filename handlers
             * just as it were the requested URL. This is to enable
             * post-processing by mod_alias, etc.  which always act on
             * r->uri! The difference here is: We do not try to
             * add the document root
             */

            r->uri = apr_pstrdup(r->pool, r->filename+12);
            return DECLINED;
        }
        else {
            /* it was finally rewritten to a local path */
            const char *uri_reduced = NULL;

            /* expand "/~user" prefix */
#if APR_HAS_USER
            r->filename = expand_tildepaths(r, r->filename);
#endif
            rewritelog(r, 2, NULL, "local path result: %s", r->filename);

            /* the filename must be either an absolute local path or an
             * absolute local URL.
             */

            if (   *r->filename != '/'
                && !ap_os_is_path_absolute(r->pool, r->filename)) {
                return HTTP_BAD_REQUEST;
            }

            /* We have r->filename as a path in a server-context rewrite without
             * the PT flag. The historical behavior is to treat it as a verbatim
             * filesystem path iff the first component of the path exists and is
             * readable by httpd. Otherwise, it is interpreted as DocumentRoot
             * relative.
             *
             * NOTICE:
             * We cannot leave out the prefix_stat because
             * - If we always prefix with document_root
             *   then no absolute path can could ever be used in
             *   a substitution. e.g. emulating an Alias.
             * - If we never prefix with document_root
             *   then the files under document_root have to
             *   be references directly and document_root
             *   gets never used and will be a dummy parameter -
             *   this is also bad.
             *   - Later addition: This part is questionable.
             *     If we had never prefixed, users would just 
             *     need %{DOCUMENT_ROOT} in substitutions or the 
             *     [PT] flag.
             *
             * BUT:
             * Under real Unix systems this is no perf problem,
             * because we only do stat() on the first directory
             * and this gets cached by the kernel for along time!
             */


            if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
                uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced");
            }

            if (!prefix_stat(r, r->filename, r->pool,
                             conf->options & OPTION_UNSAFE_PREFIX_STAT ? NULL : lastsub)
                || uri_reduced != NULL) {
                int res;
                char *tmp = r->uri;

                r->uri = r->filename;
                res = ap_core_translate(r);
                r->uri = tmp;

                if (res != OK) {
                    rewritelog(r, 1, NULL, "prefixing with document_root of %s"
                               " FAILED", r->filename);

                    return res;
                }

                rewritelog(r, 2, NULL, "prefixed with document_root to %s",
                           r->filename);
            }

            rewritelog(r, 1, NULL, "go-ahead with %s [OK]", r->filename);
            return OK;
        }
    }
    else {
        rewritelog(r, 1, NULL, "pass through %s", r->filename);
        return DECLINED;
    }
}

/*
 * Fixup hook
 * [RewriteRules in directory context]
 */

static int hook_fixup(request_rec *r)
{
    rewrite_perdir_conf *dconf;
    char *cp;
    char *cp2;
    const char *ccp;
    apr_size_t l;
    int rulestatus;
    int n;
    char *ofilename, *oargs;
    int is_proxyreq;
    void *skipdata;
    rewriterule_entry *lastsub;

    dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
                                                        &rewrite_module);

    /* if there is no per-dir config we return immediately */
    if (dconf == 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;
    }

    /* 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;
    }

    /*
     * Proxy request?
     */

    is_proxyreq = (   r->proxyreq && r->filename
                   && !strncmp(r->filename, "proxy:", 6));

    /*
     *  .htaccess file is called before really entering the directory, i.e.:
     *  URL: http://localhost/foo  and .htaccess is located in foo directory
     *  Ignore such attempts, allowing mod_dir to direct the client to the
     *  canonical URL. This can be controlled with the AllowNoSlash option.
     */

    if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) {
        l = strlen(dconf->directory) - 1;
        if (r->filename && strlen(r->filename) == l &&
            (dconf->directory)[l] == '/' &&
            !strncmp(r->filename, dconf->directory, l)) {
            return DECLINED;
        }
    }

    /* END flag was used as a RewriteRule flag on this request */
    apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
    if (skipdata != NULL) {
        rewritelog(r, 8, dconf->directory, "Declining, no further rewriting due to END flag");
        return DECLINED;
    }

    /*
     *  Do the Options check after engine check, so
     *  the user is able to explicitly turn RewriteEngine Off.
     */

    if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
        /* FollowSymLinks is mandatory! */
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670)
                     "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, "
                     "so the RewriteRule directive is also forbidden "
                     "due to its similar ability to circumvent directory restrictions : "
                     "%s", r->filename);
        return HTTP_FORBIDDEN;
    }

    /*
     *  remember the current filename before rewriting for later check
     *  to prevent deadlooping because of internal redirects
     *  on final URL/filename which can be equal to the initial one.
     *  also, we'll restore original r->filename if we decline this
     *  request
     */

    ofilename = r->filename;
    oargs = r->args;

    if (r->filename == NULL) {
        r->filename = apr_pstrdup(r->pool, r->uri);
        rewritelog(r, 2, dconf->directory, "init rewrite engine with"
                   " requested uri %s", r->filename);
    }

    /*
     *  now apply the rules ...
     */

    rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory, &lastsub);
    if (rulestatus) {
        unsigned skip_absolute = is_absolute_uri(r->filename, NULL);
        int to_proxyreq = 0;
        int will_escape = 0;

        l = strlen(r->filename);
        to_proxyreq = l > 6 && strncmp(r->filename, "proxy:", 6) == 0;
        will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE);

        if (r->args
               && !will_escape
               &&  *(ap_scan_vchar_obstext(r->args))) {
            /*
             * We have a raw control character or a ' ' in r->args.
             * Correct encoding was missed.
             */

            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411)
                          "Rewritten query string contains control "
                          "characters or spaces");
            return HTTP_FORBIDDEN;
        }

        if (ACTION_STATUS == rulestatus) {
            int n = r->status;

            r->status = HTTP_OK;
            return n;
        }
        else if (ACTION_STATUS_SET == rulestatus) {
            return r->status;
        }

        if (to_proxyreq) {
            /* it should go on as an internal proxy request */

            /* check if the proxy module is enabled, so
             * we can actually use it!
             */

            if (!proxy_available) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10160)
                              "attempt to make remote request from mod_rewrite "
                              "without proxy enabled: %s", r->filename);
                return HTTP_FORBIDDEN;
            }

            if (rulestatus == ACTION_NOESCAPE) {
                apr_table_setn(r->notes, "proxy-nocanon""1");
            }

            /* make sure the QUERY_STRING gets incorporated in the case
             * [NE] was specified on the Proxy rule. We are preventing
             * mod_proxy canon handler from incorporating r->args as well
             * as escaping the URL.
             * (r->path_info was already appended by the
             * rewriting engine because of the per-dir context!)
             */

            if ((r->args != NULL) && apr_table_get(r->notes, "proxy-nocanon")) {
                r->filename = apr_pstrcat(r->pool, r->filename,
                                          "?", r->args, NULL);
            }

            /* now make sure the request gets handled by the proxy handler */
            if (PROXYREQ_NONE == r->proxyreq) {
                r->proxyreq = PROXYREQ_REVERSE;
            }
            r->handler  = "proxy-server";

            rewritelog(r, 1, dconf->directory, "go-ahead with proxy request "
                       "%s [OK]", r->filename);
            return OK;
        }
        else if (skip_absolute > 0) {
            /* it was finally rewritten to a remote URL */

            /* because we are in a per-dir context
             * first try to replace the directory with its base-URL
             * if there is a base-URL available
             */

            if (dconf->baseurl != NULL) {
                /* skip 'scheme://' */
                cp = r->filename + skip_absolute;

                if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
                    rewritelog(r, 2, dconf->directory,
                               "trying to replace prefix %s with %s",
                               dconf->directory, dconf->baseurl);

                    /* I think, that hack needs an explanation:
                     * well, here is it:
                     * mod_rewrite was written for unix systems, were
                     * absolute file-system paths start with a slash.
                     * URL-paths _also_ start with slashes, so they
                     * can be easily compared with system paths.
                     *
                     * the following assumes, that the actual url-path
                     * may be prefixed by the current directory path and
                     * tries to replace the system path with the RewriteBase
                     * URL.
                     * That assumption is true if we use a RewriteRule like
                     *
                     * RewriteRule ^foo bar [R]
                     *
                     * (see apply_rewrite_rule function)
                     * However on systems that don't have a / as system
                     * root this will never match, so we skip the / after the
                     * hostname and compare/substitute only the stuff after it.
                     *
                     * (note that cp was already increased to the right value)
                     */

                    cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
                                                   ? dconf->directory + 1
                                                   : dconf->directory,
                                            dconf->baseurl + 1);
                    if (strcmp(cp2, cp) != 0) {
                        *cp = '\0';
                        r->filename = apr_pstrcat(r->pool, r->filename,
                                                  cp2, NULL);
                    }
                }
            }

            /* now prepare the redirect... */
            if (rulestatus != ACTION_NOESCAPE) {
                rewritelog(r, 1, dconf->directory, "escaping %s for redirect",
                           r->filename);
                r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute);
            }

            /* append the QUERY_STRING part */
            if (r->args) {
                char *escaped_args = NULL;
                int noescape = (rulestatus == ACTION_NOESCAPE ||
                                (oargs && !strcmp(r->args, oargs)));

                r->filename = apr_pstrcat(r->pool, r->filename, "?",
                                          noescape
                                            ? r->args
                                            : (escaped_args = ap_escape_uri(r->pool, r->args)),
                                          NULL);

                rewritelog(r, 1, dconf->directory, "%s %s to query string for redirect %s",
                           noescape ? "copying" : "escaping",
                           r->args ,
                           noescape ? "" : escaped_args);
            }

            /* determine HTTP redirect response code */
            if (ap_is_HTTP_REDIRECT(r->status)) {
                n = r->status;
                r->status = HTTP_OK; /* make Apache kernel happy */
            }
            else {
                n = HTTP_MOVED_TEMPORARILY;
            }

            /* now do the redirection */
            apr_table_setn(r->headers_out, "Location", r->filename);
            rewritelog(r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
                       r->filename, n);
            return n;
        }
        else {
            const char *tmpfilename = NULL;
            /* it was finally rewritten to a local path */

            /* if someone used the PASSTHROUGH flag in per-dir
             * context we just ignore it. It is only useful
             * in per-server context
             */

            if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
                r->filename = apr_pstrdup(r->pool, r->filename+12);
            }

            /* the filename must be either an absolute local path or an
             * absolute local URL.
             */

            if (   *r->filename != '/'
                && !ap_os_is_path_absolute(r->pool, r->filename)) {
                return HTTP_BAD_REQUEST;
            }

            /* Check for deadlooping:
             * At this point we KNOW that at least one rewriting
             * rule was applied, but when the resulting URL is
             * the same as the initial URL, we are not allowed to
             * use the following internal redirection stuff because
             * this would lead to a deadloop.
             */

            if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) {
                rewritelog(r, 1, dconf->directory, "initial URL equal rewritten"
                           " URL: %s [IGNORING REWRITE]", r->filename);
                return OK;
            }

            tmpfilename = r->filename;

            /* if there is a valid base-URL then substitute
             * the per-dir prefix with this base-URL if the
             * current filename still is inside this per-dir
             * context. If not then treat the result as a
             * plain URL
             */

            if (dconf->baseurl != NULL) {
                rewritelog(r, 2, dconf->directory, "trying to replace prefix "
                           "%s with %s", dconf->directory, dconf->baseurl);

                r->filename = subst_prefix_path(r, r->filename,
                                                dconf->directory,
                                                dconf->baseurl);
            }
            else {
                /* if no explicit base-URL exists we assume
                 * that the directory prefix is also a valid URL
                 * for this webserver and only try to remove the
                 * document_root if it is prefix
                 */

                if ((ccp = ap_document_root(r)) != NULL) {
                    /* strip trailing slash */
                    l = strlen(ccp);
                    if (ccp[l-1] == '/') {
                        --l;
                    }
                    if (!strncmp(r->filename, ccp, l) &&
                        r->filename[l] == '/') {
                        rewritelog(r, 2,dconf->directory, "strip document_root"
                                   " prefix: %s -> %s", r->filename,
                                   r->filename+l);

                        r->filename = apr_pstrdup(r->pool, r->filename+l);
                    }
                }
            }

            /* No base URL, or r->filename wasn't still under dconf->directory
             * or, r->filename wasn't still under the document root. 
             * If there's a context document root AND a context prefix, and 
             * the context document root is a prefix of r->filename, replace.
             * This allows a relative substitution on a path found by mod_userdir 
             * or mod_alias without baking in a RewriteBase.
             */

            if (tmpfilename == r->filename && 
                !(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) { 
                if ((ccp = ap_context_document_root(r)) != NULL) { 
                    const char *prefix = ap_context_prefix(r);
                    if (prefix != NULL) { 
                        rewritelog(r, 2, dconf->directory, "trying to replace "
                                    "context docroot %s with context prefix %s",
                                   ccp, prefix);
                        r->filename = subst_prefix_path(r, r->filename,
                                ccp, prefix);
                    }
                }
            }

            apr_table_setn(r->notes, "redirect-keeps-vary""");

            /* now initiate the internal redirect */
            rewritelog(r, 1, dconf->directory, "internal redirect with %s "
                       "[INTERNAL REDIRECT]", r->filename);
            r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
            r->handler = REWRITE_REDIRECT_HANDLER_NAME;
            return OK;
        }
    }
    else {
        rewritelog(r, 1, dconf->directory, "pass through %s", r->filename);
        r->filename = ofilename;
        return DECLINED;
    }
}

/*
 * MIME-type hook
 * [T=...,H=...] execution
 */

static int hook_mimetype(request_rec *r)
{
    const char *t;

    /* type */
    t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
    if (t && *t) {
        rewritelog(r, 1, NULL, "force filename %s to have MIME-type '%s'",
                   r->filename, t);

        ap_set_content_type_ex(r, t, 1);
    }

    /* handler */
    t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR);
    if (t && *t) {
        rewritelog(r, 1, NULL, "force filename %s to have the "
                   "Content-handler '%s'", r->filename, t);

        r->handler = t;
    }

    return OK;
}


/*
 * "content" handler for internal redirects
 */

static int handler_redirect(request_rec *r)
{
    if (strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) {
        return DECLINED;
    }

    /* just make sure that we are really meant! */
    if (strncmp(r->filename, "redirect:", 9) != 0) {
        return DECLINED;
    }

    /* now do the internal redirect */
    ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
                                     r->args ? "?" : NULL, r->args, NULL), r);

    /* and return gracefully */
    return OK;
}


/*
 * +-------------------------------------------------------+
 * |                                                       |
 * |                Module paraphernalia
 * |                                                       |
 * +-------------------------------------------------------+
 */


static const command_rec command_table[] = {
    AP_INIT_FLAG(    "RewriteEngine",   cmd_rewriteengine,  NULL, OR_FILEINFO,
                     "On or Off to enable or disable (default) the whole "
                     "rewriting engine"),
    AP_INIT_ITERATE( "RewriteOptions",  cmd_rewriteoptions,  NULL, OR_FILEINFO,
                     "List of option strings to set"),
    AP_INIT_TAKE1(   "RewriteBase",     cmd_rewritebase,     NULL, OR_FILEINFO,
                     "the base URL of the per-directory context"),
    AP_INIT_RAW_ARGS("RewriteCond",     cmd_rewritecond,     NULL, OR_FILEINFO,
                     "an input string and a to be applied regexp-pattern"),
    AP_INIT_RAW_ARGS("RewriteRule",     cmd_rewriterule,     NULL, OR_FILEINFO,
                     "an URL-applied regexp-pattern and a substitution URL"),
    AP_INIT_TAKE23(   "RewriteMap",      cmd_rewritemap,      NULL, RSRC_CONF,
                     "a mapname and a filename and options"),
    { NULL }
};

static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func)
{
    apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func);
}

static void register_hooks(apr_pool_t *p)
{
    static const char * const aszModProxy[] = { "mod_proxy.c", NULL };

    /* make the hashtable before registering the function, so that
     * other modules are prevented from accessing uninitialized memory.
     */

    mapfunc_hash = apr_hash_make(p);
    APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);

    ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
    
    /* allow to change the uri before mod_proxy takes over it */
    ap_hook_translate_name(hook_uri2file, NULL, aszModProxy, APR_HOOK_FIRST);
    /* fixup before mod_proxy so that a [P] URL gets fixed up there */
    ap_hook_fixups(hook_fixup, NULL, aszModProxy, APR_HOOK_FIRST);
    ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
}

    /* the main config structure */
AP_DECLARE_MODULE(rewrite) = {
   STANDARD20_MODULE_STUFF,
   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       */
   register_hooks               /* register hooks                      */
};

/*EOF*/

Messung V0.5 in Prozent
C=94 H=94 G=93

¤ Dauer der Verarbeitung: 0.154 Sekunden  (vorverarbeitet am  2026-04-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.