]> granicus.if.org Git - apache/commitdiff
before working further, bring some kind of system into the stuff
authorAndré Malo <nd@apache.org>
Fri, 22 Aug 2003 00:15:28 +0000 (00:15 +0000)
committerAndré Malo <nd@apache.org>
Fri, 22 Aug 2003 00:15:28 +0000 (00:15 +0000)
and (re-)order the code. That should finally improve readability...

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@101049 13f79535-47bb-0310-9956-ffa450edef68

modules/filters/mod_include.c

index b90a75a9885ef70de5f55741a8466e6f95d0e597..33b7957400799182e5abe215cdd8afb4bbdbfdb1 100644 (file)
 #include "util_ebcdic.h"
 #define RAW_ASCII_CHAR(ch)  apr_xlate_conv_byte(ap_hdrs_from_ascii, \
                                                 (unsigned char)ch)
-#else /*APR_CHARSET_EBCDIC*/
+#else /* APR_CHARSET_EBCDIC */
 #define RAW_ASCII_CHAR(ch)  (ch)
-#endif /*APR_CHARSET_EBCDIC*/
+#endif /* !APR_CHARSET_EBCDIC */
+
+
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                  Debugging Macros
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
 
 #ifdef DEBUG_INCLUDE
 
@@ -126,14 +135,13 @@ do {                                                                        \
 
 #endif
 
-module AP_MODULE_DECLARE_DATA include_module;
-static apr_hash_t *include_hash;
-static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
 
-/*****************************************************************
- *
- * XBITHACK.  Sigh...  NB it's configurable per-directory; the compile-time
- * option only changes the default.
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                 Types and Structures
+ * |                                                       |
+ * +-------------------------------------------------------+
  */
 
 enum xbithack {
@@ -218,7 +226,31 @@ struct ssi_internal_ctx {
     regmatch_t   (*re_result)[10];
 };
 
-/* some defaults */
+
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                 Static Module Data
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
+
+/* global module structure */
+module AP_MODULE_DECLARE_DATA include_module;
+
+/* function handlers for include directives */
+static apr_hash_t *include_handlers;
+
+/* forward declaration of handler registry */
+static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
+
+/* Sentinel value to store in subprocess_env for items that
+ * shouldn't be evaluated until/unless they're actually used
+ */
+static const char lazy_eval_sentinel;
+#define LAZY_VALUE (&lazy_eval_sentinel)
+
+/* default values */
 #define DEFAULT_START_SEQUENCE "<!--#"
 #define DEFAULT_END_SEQUENCE "-->"
 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
@@ -231,187 +263,14 @@ struct ssi_internal_ctx {
 #define DEFAULT_XBITHACK xbithack_off
 #endif
 
-/* ------------------------ Environment function -------------------------- */
-
-/* Sentinel value to store in subprocess_env for items that
- * shouldn't be evaluated until/unless they're actually used
- */
-static const char lazy_eval_sentinel;
-#define LAZY_VALUE (&lazy_eval_sentinel)
-
-static void add_include_vars(request_rec *r, char *timefmt)
-{
-    apr_table_t *e = r->subprocess_env;
-    char *t;
-
-    apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
-    apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
-    apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
-    apr_table_setn(e, "DOCUMENT_URI", r->uri);
-    if (r->path_info && *r->path_info) {
-        apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
-    }
-    apr_table_setn(e, "USER_NAME", LAZY_VALUE);
-    if ((t = strrchr(r->filename, '/'))) {
-        apr_table_setn(e, "DOCUMENT_NAME", ++t);
-    }
-    else {
-        apr_table_setn(e, "DOCUMENT_NAME", r->uri);
-    }
-    if (r->args) {
-        char *arg_copy = apr_pstrdup(r->pool, r->args);
-
-        ap_unescape_url(arg_copy);
-        apr_table_setn(e, "QUERY_STRING_UNESCAPED",
-                  ap_escape_shell_cmd(r->pool, arg_copy));
-    }
-}
-
-static const char *add_include_vars_lazy(request_rec *r, const char *var)
-{
-    char *val;
-    if (!strcasecmp(var, "DATE_LOCAL")) {
-        include_dir_config *conf =
-            (include_dir_config *)ap_get_module_config(r->per_dir_config,
-                                                       &include_module);
-        val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
-    }
-    else if (!strcasecmp(var, "DATE_GMT")) {
-        include_dir_config *conf =
-            (include_dir_config *)ap_get_module_config(r->per_dir_config,
-                                                       &include_module);
-        val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
-    }
-    else if (!strcasecmp(var, "LAST_MODIFIED")) {
-        include_dir_config *conf =
-            (include_dir_config *)ap_get_module_config(r->per_dir_config,
-                                                       &include_module);
-        val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
-    }
-    else if (!strcasecmp(var, "USER_NAME")) {
-        if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
-            val = "<unknown>";
-        }
-    }
-    else {
-        val = NULL;
-    }
-
-    if (val) {
-        apr_table_setn(r->subprocess_env, var, val);
-    }
-    return val;
-}
-
-static const char *get_include_var(request_rec *r, include_ctx_t *ctx, 
-                                   const char *var)
-{
-    const char *val;
-    if (apr_isdigit(*var) && !var[1]) {
-        /* Handle $0 .. $9 from the last regex evaluated.
-         * The choice of returning NULL strings on not-found,
-         * v.s. empty strings on an empty match is deliberate.
-         */
-        if (!ctx->intern->re_result || !ctx->intern->re_string) {
-            return NULL;
-        }
-        else {
-            int idx = atoi(var);
-            apr_size_t len = (*ctx->intern->re_result)[idx].rm_eo
-                           - (*ctx->intern->re_result)[idx].rm_so;
-            if (    (*ctx->intern->re_result)[idx].rm_so < 0
-                 || (*ctx->intern->re_result)[idx].rm_eo < 0) {
-                return NULL;
-            }
-            val = apr_pstrmemdup(r->pool, ctx->intern->re_string
-                                 + (*ctx->intern->re_result)[idx].rm_so, len);
-        }
-    }
-    else {
-        val = apr_table_get(r->subprocess_env, var);
-
-        if (val == LAZY_VALUE)
-            val = add_include_vars_lazy(r, var);
-    }
-    return val;
-}
-
-/* --------------------------- Parser functions --------------------------- */
-
-/* This is an implementation of the BNDM search algorithm.
- *
- * Fast and Flexible String Matching by Combining Bit-parallelism and 
- * Suffix Automata (2001) 
- * Gonzalo Navarro, Mathieu Raffinot
- *
- * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
- *
- * Initial code submitted by Sascha Schumann.
- */
-   
-/* Precompile the bndm_t data structure. */
-static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl)
-{
-    unsigned int x;
-    const char *ne = n + nl;
 
-    memset(t->T, 0, sizeof(unsigned int) * 256);
-    
-    for (x = 1; n < ne; x <<= 1)
-        t->T[(unsigned char) *n++] |= x;
-
-    t->x = x - 1;
-}
-
-/* Implements the BNDM search algorithm (as described above).
- *
- * n  - the pattern to search for
- * nl - length of the pattern to search for
- * h  - the string to look in
- * hl - length of the string to look for
- * t  - precompiled bndm structure against the pattern 
- *
- * Returns the count of character that is the first match or hl if no
- * match is found.
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |            Environment/Expansion Functions
+ * |                                                       |
+ * +-------------------------------------------------------+
  */
-static apr_size_t bndm(const char *n, apr_size_t nl, const char *h, 
-                       apr_size_t hl, bndm_t *t)
-{
-    const char *skip;
-    const char *he, *p, *pi;
-    unsigned int *T, x, d;
-
-    he = h + hl;
-
-    T = t->T;
-    x = t->x;
-
-    pi = h - 1; /* pi: p initial */
-    p = pi + nl; /* compare window right to left. point to the first char */
-
-    while (p < he) {
-        skip = p;
-        d = x;
-        do {
-            d &= T[(unsigned char) *p--];
-            if (!d) {
-                break;
-            }
-            if ((d & 1)) {
-                if (p != pi)
-                    skip = p;
-                else
-                    return p - h + 1;
-            }
-            d >>= 1;
-        } while (d);
-
-        pi = skip;
-        p = pi + nl;
-    }
-
-    return hl;
-}
 
 /*
  * decodes a string containing html entities or numeric character references.
@@ -420,8 +279,6 @@ static apr_size_t bndm(const char *n, apr_size_t nl, const char *h,
  *   unknown entities will be left undecoded;
  *   references to unused numeric characters will be deleted.
  *   In particular, &#00; will not be decoded, but will be deleted.
- *
- * drtr
  */
 
 /* maximum length of any ISO-LATIN-1 HTML entity name. */
@@ -516,45 +373,114 @@ otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
     *p = '\0';
 }
 
-/*
- * Extract the next tag name and value.
- * If there are no more tags, set the tag name to NULL.
- * The tag value is html decoded if dodecode is non-zero.
- * The tag value may be NULL if there is no tag value..
- */
-static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
-                                     char **tag_val, int dodecode)
+static void add_include_vars(request_rec *r, char *timefmt)
 {
-    if (!ctx->intern->argv) {
-        *tag = NULL;
-        *tag_val = NULL;
+    apr_table_t *e = r->subprocess_env;
+    char *t;
 
-        return;
+    apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
+    apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
+    apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
+    apr_table_setn(e, "DOCUMENT_URI", r->uri);
+    if (r->path_info && *r->path_info) {
+        apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
     }
-
-    *tag_val = ctx->intern->argv->value;
-    *tag = ctx->intern->argv->name;
-
-    ctx->intern->argv = ctx->intern->argv->next;
-
-    if (dodecode && *tag_val) {
-        decodehtml(*tag_val);
+    apr_table_setn(e, "USER_NAME", LAZY_VALUE);
+    if ((t = strrchr(r->filename, '/'))) {
+        apr_table_setn(e, "DOCUMENT_NAME", ++t);
+    }
+    else {
+        apr_table_setn(e, "DOCUMENT_NAME", r->uri);
     }
+    if (r->args) {
+        char *arg_copy = apr_pstrdup(r->pool, r->args);
 
-    return;
+        ap_unescape_url(arg_copy);
+        apr_table_setn(e, "QUERY_STRING_UNESCAPED",
+                  ap_escape_shell_cmd(r->pool, arg_copy));
+    }
 }
 
-/* initial buffer size for power-of-two allocator in ap_ssi_parse_string */
-#define PARSE_STRING_INITIAL_SIZE 64
-
-/*
- * Do variable substitution on strings
- * (Note: If out==NULL, this function allocs a buffer for the resulting
- * string from r->pool.  The return value is the parsed string)
- */
-static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx, 
-                                 const char *in, char *out,
-                                 apr_size_t length, int leave_name)
+static const char *add_include_vars_lazy(request_rec *r, const char *var)
+{
+    char *val;
+    if (!strcasecmp(var, "DATE_LOCAL")) {
+        include_dir_config *conf =
+            (include_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                       &include_module);
+        val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
+    }
+    else if (!strcasecmp(var, "DATE_GMT")) {
+        include_dir_config *conf =
+            (include_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                       &include_module);
+        val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
+    }
+    else if (!strcasecmp(var, "LAST_MODIFIED")) {
+        include_dir_config *conf =
+            (include_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                       &include_module);
+        val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
+    }
+    else if (!strcasecmp(var, "USER_NAME")) {
+        if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
+            val = "<unknown>";
+        }
+    }
+    else {
+        val = NULL;
+    }
+
+    if (val) {
+        apr_table_setn(r->subprocess_env, var, val);
+    }
+    return val;
+}
+
+static const char *get_include_var(request_rec *r, include_ctx_t *ctx, 
+                                   const char *var)
+{
+    const char *val;
+    if (apr_isdigit(*var) && !var[1]) {
+        /* Handle $0 .. $9 from the last regex evaluated.
+         * The choice of returning NULL strings on not-found,
+         * v.s. empty strings on an empty match is deliberate.
+         */
+        if (!ctx->intern->re_result || !ctx->intern->re_string) {
+            return NULL;
+        }
+        else {
+            int idx = atoi(var);
+            apr_size_t len = (*ctx->intern->re_result)[idx].rm_eo
+                           - (*ctx->intern->re_result)[idx].rm_so;
+            if (    (*ctx->intern->re_result)[idx].rm_so < 0
+                 || (*ctx->intern->re_result)[idx].rm_eo < 0) {
+                return NULL;
+            }
+            val = apr_pstrmemdup(r->pool, ctx->intern->re_string
+                                 + (*ctx->intern->re_result)[idx].rm_so, len);
+        }
+    }
+    else {
+        val = apr_table_get(r->subprocess_env, var);
+
+        if (val == LAZY_VALUE)
+            val = add_include_vars_lazy(r, var);
+    }
+    return val;
+}
+
+/* initial buffer size for power-of-two allocator in ap_ssi_parse_string */
+#define PARSE_STRING_INITIAL_SIZE 64
+
+/*
+ * Do variable substitution on strings
+ * (Note: If out==NULL, this function allocs a buffer for the resulting
+ * string from r->pool.  The return value is the parsed string)
+ */
+static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx, 
+                                 const char *in, char *out,
+                                 apr_size_t length, int leave_name)
 {
     char ch;
     char *next;
@@ -722,1318 +648,1360 @@ static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx,
     return out;
 }
 
-/* --------------------------- Action handlers ---------------------------- */
 
-/* ensure that path is relative, and does not contain ".." elements
- * ensentially ensure that it does not match the regex:
- * (^/|(^|/)\.\.(/|$))
- * XXX: Simply replace with apr_filepath_merge                    
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |              Conditional Expression Parser
+ * |                                                       |
+ * +-------------------------------------------------------+
  */
-static int is_only_below(const char *path)
+
+static int re_check(request_rec *r, include_ctx_t *ctx, 
+                    char *string, char *rexp)
 {
-#ifdef HAVE_DRIVE_LETTERS
-    if (path[1] == ':') 
-        return 0;
-#endif
-#ifdef NETWARE
-    if (ap_strchr_c(path, ':'))
-        return 0;
-#endif
-    if (path[0] == '/') {
-        return 0;
+    regex_t *compiled;
+    const apr_size_t nres = sizeof(*ctx->intern->re_result) / sizeof(regmatch_t);
+    int regex_error;
+
+    compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
+    if (compiled == NULL) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "unable to compile pattern \"%s\"", rexp);
+        return -1;
     }
-    while (*path) {
-        int dots = 0;
-        while (path[dots] == '.')
-            ++dots;
-#if defined(WIN32) 
-        /* If the name is canonical this is redundant
-         * but in security, redundancy is worthwhile.
-         * Does OS2 belong here (accepts ... for ..)?
-         */
-        if (dots > 1 && (!path[dots] || path[dots] == '/'))
-            return 0;
-#else
-        if (dots == 2 && (!path[dots] || path[dots] == '/'))
-            return 0;
-#endif
-        path += dots;
-        /* Advance to either the null byte at the end of the
-         * string or the character right after the next slash,
-         * whichever comes first
-         */
-        while (*path && (*path++ != '/')) {
-            continue;
-        }
+    if (!ctx->intern->re_result) {
+        ctx->intern->re_result = apr_pcalloc(r->pool, sizeof(*ctx->intern->re_result));
     }
-    return 1;
+    ctx->intern->re_string = string;
+    regex_error = ap_regexec(compiled, string, nres, *ctx->intern->re_result, 0);
+    ap_pregfree(r->pool, compiled);
+    return (!regex_error);
 }
 
-static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
-                                   apr_bucket_brigade *bb)
-{
-    char *tag     = NULL;
-    char *tag_val = NULL;
-    char *parsed_string;
-    int loglevel = APLOG_ERR;
-    request_rec *r = f->r;
-
-    if (ctx->flags & SSI_FLAG_PRINTING) {
-        while (1) {
-            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
-            if (!tag || !tag_val) {
-                return APR_SUCCESS;
-            }
-
-            if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) {
-                request_rec *rr = NULL;
-                char *error_fmt = NULL;
+enum token_type {
+    token_string, token_re,
+    token_and, token_or, token_not, token_eq, token_ne,
+    token_rbrace, token_lbrace, token_group,
+    token_ge, token_le, token_gt, token_lt
+};
+struct token {
+    enum token_type type;
+    char* value;
+};
 
-                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
-                                                    MAX_STRING_LEN,
-                                                    SSI_EXPAND_DROP_NAME);
-                if (tag[0] == 'f') {
-                    /* XXX: Port to apr_filepath_merge
-                     * be safe; only files in this directory or below allowed 
-                     */
-                    if (!is_only_below(parsed_string)) {
-                        error_fmt = "unable to include file \"%s\" "
-                                    "in parsed file %s";
-                    }
-                    else {
-                        rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
-                    }
-                }
-                else {
-                    rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
-                }
+static const char *get_ptoken(request_rec *r, const char *string, 
+                              struct token *token, int *unmatched)
+{
+    char ch;
+    int next = 0;
+    char qs = 0;
+    int tkn_fnd = 0;
 
-                if (!error_fmt && rr->status != HTTP_OK) {
-                    error_fmt = "unable to include \"%s\" in parsed file %s";
-                }
+    token->value = NULL;
 
-                if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) && 
-                    rr->content_type && 
-                    (strncmp(rr->content_type, "text/", 5))) {
-                    error_fmt = "unable to include potential exec \"%s\" "
-                        "in parsed file %s";
-                }
-                if (error_fmt == NULL) {
-                    /* try to avoid recursive includes.  We do this by walking
-                     * up the r->main list of subrequests, and at each level
-                     * walking back through any internal redirects.  At each
-                     * step, we compare the filenames and the URIs.  
-                     *
-                     * The filename comparison catches a recursive include
-                     * with an ever-changing URL, eg.
-                     * <!--#include virtual=
-                     *      "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
-                     * which, although they would eventually be caught because
-                     * we have a limit on the length of files, etc., can 
-                     * recurse for a while.
-                     *
-                     * The URI comparison catches the case where the filename
-                     * is changed while processing the request, so the 
-                     * current name is never the same as any previous one.
-                     * This can happen with "DocumentRoot /foo" when you
-                     * request "/" on the server and it includes "/".
-                     * This only applies to modules such as mod_dir that 
-                     * (somewhat improperly) mess with r->filename outside 
-                     * of a filename translation phase.
-                     */
-                    int founddupe = 0;
-                    request_rec *p;
-                    for (p = r; p != NULL && !founddupe; p = p->main) {
-                        request_rec *q;
-                        for (q = p; q != NULL; q = q->prev) {
-                            if ((q->filename && rr->filename && 
-                                (strcmp(q->filename, rr->filename) == 0)) ||
-                                ((*q->uri == '/') && 
-                                 (strcmp(q->uri, rr->uri) == 0)))
-                            {
-                                founddupe = 1;
-                                break;
-                            }
-                        }
-                    }
+    /* Skip leading white space */
+    if (string == (char *) NULL) {
+        return (char *) NULL;
+    }
+    while ((ch = *string++)) {
+        if (!apr_isspace(ch)) {
+            break;
+        }
+    }
+    if (ch == '\0') {
+        return (char *) NULL;
+    }
 
-                    if (p != NULL) {
-                        error_fmt = "Recursive include of \"%s\" "
-                            "in parsed file %s";
-                    }
-                }
-
-                /* See the Kludge in send_parsed_file for why */
-                /* Basically, it puts a bread crumb in here, then looks */
-                /*   for the crumb later to see if its been here.       */
-                if (rr) 
-                    ap_set_module_config(rr->request_config, 
-                                         &include_module, r);
-
-                if (!error_fmt && ap_run_sub_req(rr)) {
-                    error_fmt = "unable to include \"%s\" in parsed file %s";
-                }
-                if (error_fmt) {
-                    ap_log_rerror(APLOG_MARK, loglevel,
-                                  0, r, error_fmt, tag_val, r->filename);
-                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
-                }
+    token->type = token_string; /* the default type */
+    switch (ch) {
+    case '(':
+        token->type = token_lbrace;
+        return (string);
+    case ')':
+        token->type = token_rbrace;
+        return (string);
+    case '=':
+        token->type = token_eq;
+        return (string);
+    case '!':
+        if (*string == '=') {
+            token->type = token_ne;
+            return (string + 1);
+        }
+        else {
+            token->type = token_not;
+            return (string);
+        }
+    case '\'':
+        /* already token->type == token_string */
+        qs = '\'';
+        break;
+    case '/':
+        token->type = token_re;
+        qs = '/';
+        break;
+    case '|':
+        if (*string == '|') {
+            token->type = token_or;
+            return (string + 1);
+        }
+        break;
+    case '&':
+        if (*string == '&') {
+            token->type = token_and;
+            return (string + 1);
+        }
+        break;
+    case '>':
+        if (*string == '=') {
+            token->type = token_ge;
+            return (string + 1);
+        }
+        else {
+            token->type = token_gt;
+            return (string);
+        }
+    case '<':
+        if (*string == '=') {
+            token->type = token_le;
+            return (string + 1);
+        }
+        else {
+            token->type = token_lt;
+            return (string);
+        }
+    default:
+        /* already token->type == token_string */
+        break;
+    }
+    /* We should only be here if we are in a string */
+    token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
+                                                               trailing null */
+    if (!qs) {
+        token->value[next++] = ch;
+    }
 
-                /* destroy the sub request */
-                if (rr != NULL) {
-                    ap_destroy_sub_req(rr);
-                }
+    /* 
+     * I used the ++string throughout this section so that string
+     * ends up pointing to the next token and I can just return it
+     */
+    for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
+        if (ch == '\\') {
+            if ((ch = *++string) == '\0') {
+                tkn_fnd = 1;
             }
             else {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "unknown parameter \"%s\" to tag include in %s",
-                            tag, r->filename);
-                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                token->value[next++] = ch;
             }
         }
-    }
-
-    return APR_SUCCESS;
-}
-
-
-static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
-                                apr_bucket_brigade *bb)
-{
-    char       *tag       = NULL;
-    char       *tag_val   = NULL;
-    const char *echo_text = NULL;
-    apr_bucket  *tmp_buck;
-    apr_size_t e_len;
-    enum {E_NONE, E_URL, E_ENTITY} encode;
-    request_rec *r = f->r;
-
-    encode = E_ENTITY;
-
-    if (ctx->flags & SSI_FLAG_PRINTING) {
-        while (1) {
-            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
-            if (!tag || !tag_val) {
-                return APR_SUCCESS;
-            }
-
-            if (!strcmp(tag, "var")) {
-                conn_rec *c = r->connection;
-                const char *val =
-                    get_include_var(r, ctx,
-                                    ap_ssi_parse_string(r, ctx, tag_val, NULL,
-                                                        MAX_STRING_LEN,
-                                                        SSI_EXPAND_DROP_NAME));
-                if (val) {
-                    switch(encode) {
-                    case E_NONE:   
-                        echo_text = val;
+        else {
+            if (!qs) {
+                if (apr_isspace(ch)) {
+                    tkn_fnd = 1;
+                }
+                else {
+                    switch (ch) {
+                    case '(':
+                    case ')':
+                    case '=':
+                    case '!':
+                    case '<':
+                    case '>':
+                        tkn_fnd = 1;
                         break;
-                    case E_URL:
-                        echo_text = ap_escape_uri(r->pool, val);  
+                    case '|':
+                        if (*(string + 1) == '|') {
+                            tkn_fnd = 1;
+                        }
                         break;
-                    case E_ENTITY: 
-                        echo_text = ap_escape_html(r->pool, val); 
+                    case '&':
+                        if (*(string + 1) == '&') {
+                            tkn_fnd = 1;
+                        }
                         break;
                     }
-
-                    e_len = strlen(echo_text);
-                    tmp_buck = apr_bucket_pool_create(echo_text, e_len,
-                                                      r->pool, c->bucket_alloc);
-                }
-                else {
-                    include_server_config *sconf= 
-                        ap_get_module_config(r->server->module_config,
-                                             &include_module);
-                    tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho, 
-                                                      sconf->undefinedEchoLen,
-                                                      r->pool, c->bucket_alloc);
+                    if (!tkn_fnd) {
+                        token->value[next++] = ch;
+                    }
                 }
-                APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
             }
-            else if (!strcmp(tag, "encoding")) {
-                if (!strcasecmp(tag_val, "none")) encode = E_NONE;
-                else if (!strcasecmp(tag_val, "url")) encode = E_URL;
-                else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
+            else {
+                if (ch == qs) {
+                    qs = 0;
+                    tkn_fnd = 1;
+                    string++;
+                }
                 else {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                           "unknown value \"%s\" to parameter \"encoding\" of "
-                           "tag echo in %s", tag_val, r->filename);
-                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                    token->value[next++] = ch;
                 }
             }
-            else {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "unknown parameter \"%s\" in tag echo of %s",
-                            tag, r->filename);
-                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
-            }
-
+        }
+        if (tkn_fnd) {
+            break;
         }
     }
 
-    return APR_SUCCESS;
+    /* If qs is still set, we have an unmatched quote */
+    if (qs) {
+        *unmatched = 1;
+        next = 0;
+    }
+    token->value[next] = '\0';
+
+    return (string);
 }
 
-/* error and tf must point to a string with room for at 
- * least MAX_STRING_LEN characters 
+
+/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
+ * characters long...
  */
-static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
-                                  apr_bucket_brigade *bb)
+static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
+                      int *was_error, int *was_unmatched, char *debug)
 {
-    char *tag     = NULL;
-    char *tag_val = NULL;
-    char *parsed_string;
-    request_rec *r = f->r;
-    apr_table_t *env = r->subprocess_env;
+    struct parse_node {
+        struct parse_node *left, *right, *parent;
+        struct token token;
+        int value, done;
+    } *root, *current, *new;
+    const char *parse;
+    char* buffer;
+    int retval = 0;
+    apr_size_t debug_pos = 0;
 
-    if (ctx->flags & SSI_FLAG_PRINTING) {
-        while (1) {
-            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
-            if (! tag || !tag_val) {
-                return APR_SUCCESS;
-            }
+    debug[debug_pos] = '\0';
+    *was_error       = 0;
+    *was_unmatched   = 0;
+    if ((parse = expr) == (char *) NULL) {
+        return (0);
+    }
+    root = current = (struct parse_node *) NULL;
 
-            if (!strcmp(tag, "errmsg")) {
-                if (ctx->intern->error_str_override == NULL) {
-                    ctx->intern->error_str_override = apr_palloc(ctx->pool,
-                                                                MAX_STRING_LEN);
-                    ctx->error_str = ctx->intern->error_str_override;
-                }
-                ap_ssi_parse_string(r, ctx, tag_val,
-                                    ctx->intern->error_str_override,
-                                    MAX_STRING_LEN, SSI_EXPAND_DROP_NAME);
+    /* Create Parse Tree */
+    while (1) {
+        new = (struct parse_node *) apr_palloc(r->pool,
+                                           sizeof(struct parse_node));
+        new->parent = new->left = new->right = (struct parse_node *) NULL;
+        new->done = 0;
+        if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == 
+            (char *) NULL) {
+            break;
+        }
+        switch (new->token.type) {
+
+        case token_string:
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], 
+                                  "     Token: string (%s)\n", 
+                                  new->token.value);
+#endif
+            if (current == (struct parse_node *) NULL) {
+                root = current = new;
+                break;
             }
-            else if (!strcmp(tag, "timefmt")) {
-                apr_time_t date = r->request_time;
-                if (ctx->intern->time_str_override == NULL) {
-                    ctx->intern->time_str_override = apr_palloc(ctx->pool,
-                                                                MAX_STRING_LEN);
-                    ctx->time_str = ctx->intern->time_str_override;
-                }
-                ap_ssi_parse_string(r, ctx, tag_val,
-                                    ctx->intern->time_str_override,
-                                    MAX_STRING_LEN, SSI_EXPAND_DROP_NAME);
+            switch (current->token.type) {
+            case token_string:
+                current->token.value = apr_pstrcat(r->pool,
+                                                   current->token.value,
+                                                   current->token.value[0] ? " " : "",
+                                                   new->token.value,
+                                                   NULL);
+                                                   
+                break;
+            case token_eq:
+            case token_ne:
+            case token_and:
+            case token_or:
+            case token_lbrace:
+            case token_not:
+            case token_ge:
+            case token_gt:
+            case token_le:
+            case token_lt:
+                new->parent = current;
+                current = current->right = new;
+                break;
+            default:
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            break;
 
-                apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, 
-                               ctx->time_str, 0));
-                apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, 
-                               ctx->time_str, 1));
-                apr_table_setn(env, "LAST_MODIFIED",
-                               ap_ht_time(r->pool, r->finfo.mtime, 
-                               ctx->time_str, 0));
+        case token_re:
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], 
+                                  "     Token: regex (%s)\n", 
+                                  new->token.value);
+#endif
+            if (current == (struct parse_node *) NULL) {
+                root = current = new;
+                break;
             }
-            else if (!strcmp(tag, "sizefmt")) {
-                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
-                                                    MAX_STRING_LEN,
-                                                    SSI_EXPAND_DROP_NAME);
-                decodehtml(parsed_string);
-                if (!strcmp(parsed_string, "bytes")) {
-                    ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
-                }
-                else if (!strcmp(parsed_string, "abbrev")) {
-                    ctx->flags &= SSI_FLAG_SIZE_ABBREV;
+            switch (current->token.type) {
+            case token_eq:
+            case token_ne:
+            case token_and:
+            case token_or:
+            case token_lbrace:
+            case token_not:
+                new->parent = current;
+                current = current->right = new;
+                break;
+            default:
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            break;
+
+        case token_and:
+        case token_or:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Token: and/or\n",
+                    sizeof ("     Token: and/or\n"));
+            debug_pos += sizeof ("     Token: and/or\n");
+#endif
+            if (current == (struct parse_node *) NULL) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            /* Percolate upwards */
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_string:
+                case token_re:
+                case token_group:
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
+                    current = current->parent;
+                    continue;
+                case token_lbrace:
+                    break;
+                default:
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
+                    *was_error = 1;
+                    return retval;
                 }
+                break;
+            }
+            if (current == (struct parse_node *) NULL) {
+                new->left = root;
+                new->left->parent = new;
+                new->parent = (struct parse_node *) NULL;
+                root = new;
             }
             else {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "unknown parameter \"%s\" to tag config in %s",
-                              tag, r->filename);
-                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                new->left = current->right;
+                current->right = new;
+                new->parent = current;
             }
-        }
-    }
-
-    return APR_SUCCESS;
-}
-
-
-static int find_file(request_rec *r, const char *directive, const char *tag,
-                     char *tag_val, apr_finfo_t *finfo)
-{
-    char *to_send = tag_val;
-    request_rec *rr = NULL;
-    int ret=0;
-    char *error_fmt = NULL;
-    apr_status_t rv = APR_SUCCESS;
-
-    if (!strcmp(tag, "file")) {
-        /* XXX: Port to apr_filepath_merge
-         * be safe; only files in this directory or below allowed 
-         */
-        if (!is_only_below(tag_val)) {
-            error_fmt = "unable to access file \"%s\" "
-                        "in parsed file %s";
-        }
-        else {
-            ap_getparents(tag_val);    /* get rid of any nasties */
-
-            /* note: it is okay to pass NULL for the "next filter" since
-               we never attempt to "run" this sub request. */
-            rr = ap_sub_req_lookup_file(tag_val, r, NULL);
+            current = new;
+            break;
 
-            if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
-                to_send = rr->filename;
-                if ((rv = apr_stat(finfo, to_send, 
-                    APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
-                    && rv != APR_INCOMPLETE) {
-                    error_fmt = "unable to get information about \"%s\" "
-                        "in parsed file %s";
+        case token_not:
+#ifdef DEBUG_INCLUDE
+            memcpy(&debug[debug_pos], "     Token: not\n",
+                    sizeof("     Token: not\n"));
+            debug_pos += sizeof("     Token: not\n");
+#endif
+            if (current == (struct parse_node *) NULL) {
+                root = current = new;
+                break;
+            }
+            /* Percolate upwards */
+            if (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_lbrace:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
+                    break;
+                default:
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                  "Invalid expression \"%s\" in file %s",
+                                  expr, r->filename);
+                    *was_error = 1;
+                    return retval;
                 }
             }
+            if (current == (struct parse_node *) NULL) {
+                new->left = root;
+                new->left->parent = new;
+                new->parent = (struct parse_node *) NULL;
+                root = new;
+            }
             else {
-                error_fmt = "unable to lookup information about \"%s\" "
-                            "in parsed file %s";
+                new->left = current->right;
+                current->right = new;
+                new->parent = current;
             }
-        }
-
-        if (error_fmt) {
-            ret = -1;
-            ap_log_rerror(APLOG_MARK, APLOG_ERR,
-                          rv, r, error_fmt, to_send, r->filename);
-        }
+            current = new;
+            break;
 
-        if (rr) ap_destroy_sub_req(rr);
-        
-        return ret;
-    }
-    else if (!strcmp(tag, "virtual")) {
-        /* note: it is okay to pass NULL for the "next filter" since
-           we never attempt to "run" this sub request. */
-        rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
-
-        if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
-            memcpy((char *) finfo, (const char *) &rr->finfo,
-                   sizeof(rr->finfo));
-            ap_destroy_sub_req(rr);
-            return 0;
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                        "unable to get information about \"%s\" "
-                        "in parsed file %s",
-                        tag_val, r->filename);
-            ap_destroy_sub_req(rr);
-            return -1;
-        }
-    }
-    else {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                    "unknown parameter \"%s\" to tag %s in %s",
-                    tag, directive, r->filename);
-        return -1;
-    }
-}
-
-static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
-                                 apr_bucket_brigade *bb)
-{
-    char *tag     = NULL;
-    char *tag_val = NULL;
-    apr_finfo_t  finfo;
-    apr_size_t  s_len;
-    apr_bucket   *tmp_buck;
-    char *parsed_string;
-    request_rec *r = f->r;
-
-    if (ctx->flags & SSI_FLAG_PRINTING) {
-        while (1) {
-            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
-            if (!tag || !tag_val) {
-                return APR_SUCCESS;
+        case token_eq:
+        case token_ne:
+        case token_ge:
+        case token_gt:
+        case token_le:
+        case token_lt:
+#ifdef DEBUG_INCLUDE
+            memcpy(&debug[debug_pos], "     Token: eq/ne/ge/gt/le/lt\n",
+                    sizeof("     Token: eq/ne/ge/gt/le/lt\n"));
+            debug_pos += sizeof("     Token: eq/ne/ge/gt/le/lt\n");
+#endif
+            if (current == (struct parse_node *) NULL) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "Invalid expression \"%s\" in file %s",
+                              expr, r->filename);
+                *was_error = 1;
+                return retval;
             }
-            else {
-                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
-                                                    MAX_STRING_LEN,
-                                                    SSI_EXPAND_DROP_NAME);
-                if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
-                    /* XXX: if we *know* we're going to have to copy the
-                     * thing off of the stack anyway, why not palloc buff
-                     * instead of sticking it on the stack; then we can just
-                     * use a pool bucket and skip the copy
-                     */
-                    char buff[50];
-
-                    if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
-                        apr_strfsize(finfo.size, buff);
-                        s_len = strlen (buff);
-                    }
-                    else {
-                        int l, x, pos = 0;
-                        char tmp_buff[50];
-
-                        apr_snprintf(tmp_buff, sizeof(tmp_buff), 
-                                     "%" APR_OFF_T_FMT, finfo.size);
-                        l = strlen(tmp_buff);    /* grrr */
-                        for (x = 0; x < l; x++) {
-                            if (x && (!((l - x) % 3))) {
-                                buff[pos++] = ',';
-                            }
-                            buff[pos++] = tmp_buff[x];
-                        }
-                        buff[pos] = '\0';
-                        s_len = pos;
-                    }
-
-                    tmp_buck = apr_bucket_heap_create(buff, s_len, NULL,
-                                                  r->connection->bucket_alloc);
-                    APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
-                }
-                else {
-                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+            /* Percolate upwards */
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_string:
+                case token_re:
+                case token_group:
+                    current = current->parent;
+                    continue;
+                case token_lbrace:
+                case token_and:
+                case token_or:
+                    break;
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
+                default:
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
+                    *was_error = 1;
+                    return retval;
                 }
+                break;
             }
-        }
-    }
-
-    return APR_SUCCESS;
-}
-
-static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
-                                    apr_bucket_brigade *bb)
-{
-    char *tag     = NULL;
-    char *tag_val = NULL;
-    apr_finfo_t  finfo;
-    apr_size_t  t_len;
-    apr_bucket   *tmp_buck;
-    char *parsed_string;
-    request_rec *r = f->r;
-
-    if (ctx->flags & SSI_FLAG_PRINTING) {
-        while (1) {
-            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
-            if (!tag || !tag_val) {
-                return APR_SUCCESS;
+            if (current == (struct parse_node *) NULL) {
+                new->left = root;
+                new->left->parent = new;
+                new->parent = (struct parse_node *) NULL;
+                root = new;
             }
             else {
-                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
-                                                    MAX_STRING_LEN,
-                                                    SSI_EXPAND_DROP_NAME);
-                if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
-                    char *t_val;
-
-                    t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
-                    t_len = strlen(t_val);
+                new->left = current->right;
+                current->right = new;
+                new->parent = current;
+            }
+            current = new;
+            break;
 
-                    tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool,
-                                                  r->connection->bucket_alloc);
-                    APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
+        case token_rbrace:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Token: rbrace\n",
+                    sizeof ("     Token: rbrace\n"));
+            debug_pos += sizeof ("     Token: rbrace\n");
+#endif
+            while (current != (struct parse_node *) NULL) {
+                if (current->token.type == token_lbrace) {
+                    current->token.type = token_group;
+                    break;
                 }
-                else {
-                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                current = current->parent;
+            }
+            if (current == (struct parse_node *) NULL) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Unmatched ')' in \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            break;
+
+        case token_lbrace:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Token: lbrace\n",
+                    sizeof ("     Token: lbrace\n"));
+            debug_pos += sizeof ("     Token: lbrace\n");
+#endif
+            if (current == (struct parse_node *) NULL) {
+                root = current = new;
+                break;
+            }
+            /* Percolate upwards */
+            if (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_lbrace:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
+                    break;
+                case token_string:
+                case token_re:
+                case token_group:
+                default:
+                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
+                    *was_error = 1;
+                    return retval;
                 }
             }
+            if (current == (struct parse_node *) NULL) {
+                new->left = root;
+                new->left->parent = new;
+                new->parent = (struct parse_node *) NULL;
+                root = new;
+            }
+            else {
+                new->left = current->right;
+                current->right = new;
+                new->parent = current;
+            }
+            current = new;
+            break;
+        default:
+            break;
         }
     }
 
-    return APR_SUCCESS;
-}
-
-static int re_check(request_rec *r, include_ctx_t *ctx, 
-                    char *string, char *rexp)
-{
-    regex_t *compiled;
-    const apr_size_t nres = sizeof(*ctx->intern->re_result) / sizeof(regmatch_t);
-    int regex_error;
-
-    compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
-    if (compiled == NULL) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                      "unable to compile pattern \"%s\"", rexp);
-        return -1;
-    }
-    if (!ctx->intern->re_result) {
-        ctx->intern->re_result = apr_pcalloc(r->pool, sizeof(*ctx->intern->re_result));
-    }
-    ctx->intern->re_string = string;
-    regex_error = ap_regexec(compiled, string, nres, *ctx->intern->re_result, 0);
-    ap_pregfree(r->pool, compiled);
-    return (!regex_error);
-}
+    /* Evaluate Parse Tree */
+    current = root;
+    while (current != (struct parse_node *) NULL) {
+        switch (current->token.type) {
+        case token_string:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Evaluate string\n",
+                    sizeof ("     Evaluate string\n"));
+            debug_pos += sizeof ("     Evaluate string\n");
+#endif
+            buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL, 
+                                         MAX_STRING_LEN, 0);
+            current->token.value = buffer;
+            current->value = (current->token.value[0] != '\0');
+            current->done = 1;
+            current = current->parent;
+            break;
 
-enum token_type {
-    token_string, token_re,
-    token_and, token_or, token_not, token_eq, token_ne,
-    token_rbrace, token_lbrace, token_group,
-    token_ge, token_le, token_gt, token_lt
-};
-struct token {
-    enum token_type type;
-    char* value;
-};
+        case token_re:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                          "No operator before regex of expr \"%s\" in file %s",
+                          expr, r->filename);
+            *was_error = 1;
+            return retval;
 
-static const char *get_ptoken(request_rec *r, const char *string, 
-                              struct token *token, int *unmatched)
-{
-    char ch;
-    int next = 0;
-    char qs = 0;
-    int tkn_fnd = 0;
+        case token_and:
+        case token_or:
+#ifdef DEBUG_INCLUDE
+            memcpy(&debug[debug_pos], "     Evaluate and/or\n",
+                    sizeof("     Evaluate and/or\n"));
+            debug_pos += sizeof("     Evaluate and/or\n");
+#endif
+            if (current->left  == (struct parse_node *) NULL ||
+                current->right == (struct parse_node *) NULL) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "Invalid expression \"%s\" in file %s",
+                              expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            if (!current->left->done) {
+                switch (current->left->token.type) {
+                case token_string:
+                    buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
+                                                 NULL, MAX_STRING_LEN, 0);
+                    current->left->token.value = buffer;
+                    current->left->value = 
+                                       (current->left->token.value[0] != '\0');
+                    current->left->done = 1;
+                    break;
+                default:
+                    current = current->left;
+                    continue;
+                }
+            }
+            if (!current->right->done) {
+                switch (current->right->token.type) {
+                case token_string:
+                    buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
+                                                 NULL, MAX_STRING_LEN, 0);
+                    current->right->token.value = buffer;
+                    current->right->value = 
+                                      (current->right->token.value[0] != '\0');
+                    current->right->done = 1;
+                    break;
+                default:
+                    current = current->right;
+                    continue;
+                }
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Left: %c\n",
+                                  current->left->value ? '1' : '0');
+            debug_pos += sprintf (&debug[debug_pos], "     Right: %c\n",
+                                  current->right->value ? '1' : '0');
+#endif
+            if (current->token.type == token_and) {
+                current->value = current->left->value && current->right->value;
+            }
+            else {
+                current->value = current->left->value || current->right->value;
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
+#endif
+            current->done = 1;
+            current = current->parent;
+            break;
 
-    token->value = NULL;
+        case token_eq:
+        case token_ne:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Evaluate eq/ne\n",
+                    sizeof ("     Evaluate eq/ne\n"));
+            debug_pos += sizeof ("     Evaluate eq/ne\n");
+#endif
+            if ((current->left == (struct parse_node *) NULL) ||
+                (current->right == (struct parse_node *) NULL) ||
+                (current->left->token.type != token_string) ||
+                ((current->right->token.type != token_string) &&
+                 (current->right->token.type != token_re))) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
+                                         NULL, MAX_STRING_LEN, 0);
+            current->left->token.value = buffer;
+            buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
+                                         NULL, MAX_STRING_LEN, 0);
+            current->right->token.value = buffer;
+            if (current->right->token.type == token_re) {
+#ifdef DEBUG_INCLUDE
+                debug_pos += sprintf (&debug[debug_pos],
+                                      "     Re Compare (%s) with /%s/\n",
+                                      current->left->token.value,
+                                      current->right->token.value);
+#endif
+                current->value =
+                    re_check(r, ctx, current->left->token.value,
+                             current->right->token.value);
+            }
+            else {
+#ifdef DEBUG_INCLUDE
+                debug_pos += sprintf (&debug[debug_pos],
+                                      "     Compare (%s) with (%s)\n",
+                                      current->left->token.value,
+                                      current->right->token.value);
+#endif
+                current->value =
+                    (strcmp(current->left->token.value,
+                            current->right->token.value) == 0);
+            }
+            if (current->token.type == token_ne) {
+                current->value = !current->value;
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
+#endif
+            current->done = 1;
+            current = current->parent;
+            break;
+        case token_ge:
+        case token_gt:
+        case token_le:
+        case token_lt:
+#ifdef DEBUG_INCLUDE
+            memcpy (&debug[debug_pos], "     Evaluate ge/gt/le/lt\n",
+                    sizeof ("     Evaluate ge/gt/le/lt\n"));
+            debug_pos += sizeof ("     Evaluate ge/gt/le/lt\n");
+#endif
+            if ((current->left == (struct parse_node *) NULL) ||
+                (current->right == (struct parse_node *) NULL) ||
+                (current->left->token.type != token_string) ||
+                (current->right->token.type != token_string)) {
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                *was_error = 1;
+                return retval;
+            }
+            buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
+                                         NULL, MAX_STRING_LEN, 0);
+            current->left->token.value = buffer;
+            buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
+                                         NULL, MAX_STRING_LEN, 0);
+            current->right->token.value = buffer;
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos],
+                                  "     Compare (%s) with (%s)\n",
+                                  current->left->token.value,
+                                  current->right->token.value);
+#endif
+            current->value =
+                strcmp(current->left->token.value,
+                       current->right->token.value);
+            if (current->token.type == token_ge) {
+                current->value = current->value >= 0;
+            }
+            else if (current->token.type == token_gt) {
+                current->value = current->value > 0;
+            }
+            else if (current->token.type == token_le) {
+                current->value = current->value <= 0;
+            }
+            else if (current->token.type == token_lt) {
+                current->value = current->value < 0;
+            }
+            else {
+                current->value = 0;     /* Don't return -1 if unknown token */
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
+#endif
+            current->done = 1;
+            current = current->parent;
+            break;
 
-    /* Skip leading white space */
-    if (string == (char *) NULL) {
-        return (char *) NULL;
-    }
-    while ((ch = *string++)) {
-        if (!apr_isspace(ch)) {
+        case token_not:
+            if (current->right != (struct parse_node *) NULL) {
+                if (!current->right->done) {
+                    current = current->right;
+                    continue;
+                }
+                current->value = !current->right->value;
+            }
+            else {
+                current->value = 0;
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
+                                  current->value ? '1' : '0');
+#endif
+            current->done = 1;
+            current = current->parent;
             break;
+
+        case token_group:
+            if (current->right != (struct parse_node *) NULL) {
+                if (!current->right->done) {
+                    current = current->right;
+                    continue;
+                }
+                current->value = current->right->value;
+            }
+            else {
+                current->value = 1;
+            }
+#ifdef DEBUG_INCLUDE
+            debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
+                                  current->value ? '1' : '0');
+#endif
+            current->done = 1;
+            current = current->parent;
+            break;
+
+        case token_lbrace:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                        "Unmatched '(' in \"%s\" in file %s",
+                        expr, r->filename);
+            *was_error = 1;
+            return retval;
+
+        case token_rbrace:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                        "Unmatched ')' in \"%s\" in file %s",
+                        expr, r->filename);
+            *was_error = 1;
+            return retval;
+
+        default:
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                          "bad token type");
+            *was_error = 1;
+            return retval;
         }
     }
-    if (ch == '\0') {
-        return (char *) NULL;
-    }
 
-    token->type = token_string; /* the default type */
-    switch (ch) {
-    case '(':
-        token->type = token_lbrace;
-        return (string);
-    case ')':
-        token->type = token_rbrace;
-        return (string);
-    case '=':
-        token->type = token_eq;
-        return (string);
-    case '!':
-        if (*string == '=') {
-            token->type = token_ne;
-            return (string + 1);
-        }
-        else {
-            token->type = token_not;
-            return (string);
-        }
-    case '\'':
-        /* already token->type == token_string */
-        qs = '\'';
-        break;
-    case '/':
-        token->type = token_re;
-        qs = '/';
-        break;
-    case '|':
-        if (*string == '|') {
-            token->type = token_or;
-            return (string + 1);
-        }
-        break;
-    case '&':
-        if (*string == '&') {
-            token->type = token_and;
-            return (string + 1);
-        }
-        break;
-    case '>':
-        if (*string == '=') {
-            token->type = token_ge;
-            return (string + 1);
-        }
-        else {
-            token->type = token_gt;
-            return (string);
-        }
-    case '<':
-        if (*string == '=') {
-            token->type = token_le;
-            return (string + 1);
-        }
-        else {
-            token->type = token_lt;
-            return (string);
-        }
-    default:
-        /* already token->type == token_string */
-        break;
+    retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
+    return (retval);
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                    Action Handlers
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
+
+/*
+ * Extract the next tag name and value.
+ * If there are no more tags, set the tag name to NULL.
+ * The tag value is html decoded if dodecode is non-zero.
+ * The tag value may be NULL if there is no tag value..
+ */
+static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
+                                     char **tag_val, int dodecode)
+{
+    if (!ctx->intern->argv) {
+        *tag = NULL;
+        *tag_val = NULL;
+
+        return;
     }
-    /* We should only be here if we are in a string */
-    token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
-                                                               trailing null */
-    if (!qs) {
-        token->value[next++] = ch;
+
+    *tag_val = ctx->intern->argv->value;
+    *tag = ctx->intern->argv->name;
+
+    ctx->intern->argv = ctx->intern->argv->next;
+
+    if (dodecode && *tag_val) {
+        decodehtml(*tag_val);
     }
 
-    /* 
-     * I used the ++string throughout this section so that string
-     * ends up pointing to the next token and I can just return it
-     */
-    for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
-        if (ch == '\\') {
-            if ((ch = *++string) == '\0') {
-                tkn_fnd = 1;
-            }
-            else {
-                token->value[next++] = ch;
-            }
+    return;
+}
+
+/* ensure that path is relative, and does not contain ".." elements
+ * ensentially ensure that it does not match the regex:
+ * (^/|(^|/)\.\.(/|$))
+ * XXX: Simply replace with apr_filepath_merge                    
+ */
+static int is_only_below(const char *path)
+{
+#ifdef HAVE_DRIVE_LETTERS
+    if (path[1] == ':') 
+        return 0;
+#endif
+#ifdef NETWARE
+    if (ap_strchr_c(path, ':'))
+        return 0;
+#endif
+    if (path[0] == '/') {
+        return 0;
+    }
+    while (*path) {
+        int dots = 0;
+        while (path[dots] == '.')
+            ++dots;
+#if defined(WIN32) 
+        /* If the name is canonical this is redundant
+         * but in security, redundancy is worthwhile.
+         * Does OS2 belong here (accepts ... for ..)?
+         */
+        if (dots > 1 && (!path[dots] || path[dots] == '/'))
+            return 0;
+#else
+        if (dots == 2 && (!path[dots] || path[dots] == '/'))
+            return 0;
+#endif
+        path += dots;
+        /* Advance to either the null byte at the end of the
+         * string or the character right after the next slash,
+         * whichever comes first
+         */
+        while (*path && (*path++ != '/')) {
+            continue;
         }
-        else {
-            if (!qs) {
-                if (apr_isspace(ch)) {
-                    tkn_fnd = 1;
-                }
-                else {
-                    switch (ch) {
-                    case '(':
-                    case ')':
-                    case '=':
-                    case '!':
-                    case '<':
-                    case '>':
-                        tkn_fnd = 1;
-                        break;
-                    case '|':
-                        if (*(string + 1) == '|') {
-                            tkn_fnd = 1;
-                        }
-                        break;
-                    case '&':
-                        if (*(string + 1) == '&') {
-                            tkn_fnd = 1;
-                        }
-                        break;
-                    }
-                    if (!tkn_fnd) {
-                        token->value[next++] = ch;
-                    }
+    }
+    return 1;
+}
+
+static int find_file(request_rec *r, const char *directive, const char *tag,
+                     char *tag_val, apr_finfo_t *finfo)
+{
+    char *to_send = tag_val;
+    request_rec *rr = NULL;
+    int ret=0;
+    char *error_fmt = NULL;
+    apr_status_t rv = APR_SUCCESS;
+
+    if (!strcmp(tag, "file")) {
+        /* XXX: Port to apr_filepath_merge
+         * be safe; only files in this directory or below allowed 
+         */
+        if (!is_only_below(tag_val)) {
+            error_fmt = "unable to access file \"%s\" "
+                        "in parsed file %s";
+        }
+        else {
+            ap_getparents(tag_val);    /* get rid of any nasties */
+
+            /* note: it is okay to pass NULL for the "next filter" since
+               we never attempt to "run" this sub request. */
+            rr = ap_sub_req_lookup_file(tag_val, r, NULL);
+
+            if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
+                to_send = rr->filename;
+                if ((rv = apr_stat(finfo, to_send, 
+                    APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
+                    && rv != APR_INCOMPLETE) {
+                    error_fmt = "unable to get information about \"%s\" "
+                        "in parsed file %s";
                 }
             }
             else {
-                if (ch == qs) {
-                    qs = 0;
-                    tkn_fnd = 1;
-                    string++;
-                }
-                else {
-                    token->value[next++] = ch;
-                }
+                error_fmt = "unable to lookup information about \"%s\" "
+                            "in parsed file %s";
             }
         }
-        if (tkn_fnd) {
-            break;
+
+        if (error_fmt) {
+            ret = -1;
+            ap_log_rerror(APLOG_MARK, APLOG_ERR,
+                          rv, r, error_fmt, to_send, r->filename);
         }
-    }
 
-    /* If qs is still set, we have an unmatched quote */
-    if (qs) {
-        *unmatched = 1;
-        next = 0;
+        if (rr) ap_destroy_sub_req(rr);
+        
+        return ret;
     }
-    token->value[next] = '\0';
+    else if (!strcmp(tag, "virtual")) {
+        /* note: it is okay to pass NULL for the "next filter" since
+           we never attempt to "run" this sub request. */
+        rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
 
-    return (string);
+        if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
+            memcpy((char *) finfo, (const char *) &rr->finfo,
+                   sizeof(rr->finfo));
+            ap_destroy_sub_req(rr);
+            return 0;
+        }
+        else {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                        "unable to get information about \"%s\" "
+                        "in parsed file %s",
+                        tag_val, r->filename);
+            ap_destroy_sub_req(rr);
+            return -1;
+        }
+    }
+    else {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                    "unknown parameter \"%s\" to tag %s in %s",
+                    tag, directive, r->filename);
+        return -1;
+    }
 }
 
-
-/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
- * characters long...
- */
-static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
-                      int *was_error, int *was_unmatched, char *debug)
+static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
+                                   apr_bucket_brigade *bb)
 {
-    struct parse_node {
-        struct parse_node *left, *right, *parent;
-        struct token token;
-        int value, done;
-    } *root, *current, *new;
-    const char *parse;
-    char* buffer;
-    int retval = 0;
-    apr_size_t debug_pos = 0;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *parsed_string;
+    int loglevel = APLOG_ERR;
+    request_rec *r = f->r;
 
-    debug[debug_pos] = '\0';
-    *was_error       = 0;
-    *was_unmatched   = 0;
-    if ((parse = expr) == (char *) NULL) {
-        return (0);
-    }
-    root = current = (struct parse_node *) NULL;
+    if (ctx->flags & SSI_FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+            if (!tag || !tag_val) {
+                return APR_SUCCESS;
+            }
 
-    /* Create Parse Tree */
-    while (1) {
-        new = (struct parse_node *) apr_palloc(r->pool,
-                                           sizeof(struct parse_node));
-        new->parent = new->left = new->right = (struct parse_node *) NULL;
-        new->done = 0;
-        if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == 
-            (char *) NULL) {
-            break;
-        }
-        switch (new->token.type) {
+            if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) {
+                request_rec *rr = NULL;
+                char *error_fmt = NULL;
 
-        case token_string:
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], 
-                                  "     Token: string (%s)\n", 
-                                  new->token.value);
-#endif
-            if (current == (struct parse_node *) NULL) {
-                root = current = new;
-                break;
-            }
-            switch (current->token.type) {
-            case token_string:
-                current->token.value = apr_pstrcat(r->pool,
-                                                   current->token.value,
-                                                   current->token.value[0] ? " " : "",
-                                                   new->token.value,
-                                                   NULL);
-                                                   
-                break;
-            case token_eq:
-            case token_ne:
-            case token_and:
-            case token_or:
-            case token_lbrace:
-            case token_not:
-            case token_ge:
-            case token_gt:
-            case token_le:
-            case token_lt:
-                new->parent = current;
-                current = current->right = new;
-                break;
-            default:
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Invalid expression \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            break;
+                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
+                                                    MAX_STRING_LEN,
+                                                    SSI_EXPAND_DROP_NAME);
+                if (tag[0] == 'f') {
+                    /* XXX: Port to apr_filepath_merge
+                     * be safe; only files in this directory or below allowed 
+                     */
+                    if (!is_only_below(parsed_string)) {
+                        error_fmt = "unable to include file \"%s\" "
+                                    "in parsed file %s";
+                    }
+                    else {
+                        rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+                    }
+                }
+                else {
+                    rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+                }
 
-        case token_re:
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], 
-                                  "     Token: regex (%s)\n", 
-                                  new->token.value);
-#endif
-            if (current == (struct parse_node *) NULL) {
-                root = current = new;
-                break;
-            }
-            switch (current->token.type) {
-            case token_eq:
-            case token_ne:
-            case token_and:
-            case token_or:
-            case token_lbrace:
-            case token_not:
-                new->parent = current;
-                current = current->right = new;
-                break;
-            default:
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Invalid expression \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            break;
+                if (!error_fmt && rr->status != HTTP_OK) {
+                    error_fmt = "unable to include \"%s\" in parsed file %s";
+                }
 
-        case token_and:
-        case token_or:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Token: and/or\n",
-                    sizeof ("     Token: and/or\n"));
-            debug_pos += sizeof ("     Token: and/or\n");
-#endif
-            if (current == (struct parse_node *) NULL) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Invalid expression \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            /* Percolate upwards */
-            while (current != (struct parse_node *) NULL) {
-                switch (current->token.type) {
-                case token_string:
-                case token_re:
-                case token_group:
-                case token_not:
-                case token_eq:
-                case token_ne:
-                case token_and:
-                case token_or:
-                case token_ge:
-                case token_gt:
-                case token_le:
-                case token_lt:
-                    current = current->parent;
-                    continue;
-                case token_lbrace:
-                    break;
-                default:
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                                "Invalid expression \"%s\" in file %s",
-                                expr, r->filename);
-                    *was_error = 1;
-                    return retval;
+                if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) && 
+                    rr->content_type && 
+                    (strncmp(rr->content_type, "text/", 5))) {
+                    error_fmt = "unable to include potential exec \"%s\" "
+                        "in parsed file %s";
+                }
+                if (error_fmt == NULL) {
+                    /* try to avoid recursive includes.  We do this by walking
+                     * up the r->main list of subrequests, and at each level
+                     * walking back through any internal redirects.  At each
+                     * step, we compare the filenames and the URIs.  
+                     *
+                     * The filename comparison catches a recursive include
+                     * with an ever-changing URL, eg.
+                     * <!--#include virtual=
+                     *      "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
+                     * which, although they would eventually be caught because
+                     * we have a limit on the length of files, etc., can 
+                     * recurse for a while.
+                     *
+                     * The URI comparison catches the case where the filename
+                     * is changed while processing the request, so the 
+                     * current name is never the same as any previous one.
+                     * This can happen with "DocumentRoot /foo" when you
+                     * request "/" on the server and it includes "/".
+                     * This only applies to modules such as mod_dir that 
+                     * (somewhat improperly) mess with r->filename outside 
+                     * of a filename translation phase.
+                     */
+                    int founddupe = 0;
+                    request_rec *p;
+                    for (p = r; p != NULL && !founddupe; p = p->main) {
+                        request_rec *q;
+                        for (q = p; q != NULL; q = q->prev) {
+                            if ((q->filename && rr->filename && 
+                                (strcmp(q->filename, rr->filename) == 0)) ||
+                                ((*q->uri == '/') && 
+                                 (strcmp(q->uri, rr->uri) == 0)))
+                            {
+                                founddupe = 1;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (p != NULL) {
+                        error_fmt = "Recursive include of \"%s\" "
+                            "in parsed file %s";
+                    }
                 }
-                break;
-            }
-            if (current == (struct parse_node *) NULL) {
-                new->left = root;
-                new->left->parent = new;
-                new->parent = (struct parse_node *) NULL;
-                root = new;
-            }
-            else {
-                new->left = current->right;
-                current->right = new;
-                new->parent = current;
-            }
-            current = new;
-            break;
 
-        case token_not:
-#ifdef DEBUG_INCLUDE
-            memcpy(&debug[debug_pos], "     Token: not\n",
-                    sizeof("     Token: not\n"));
-            debug_pos += sizeof("     Token: not\n");
-#endif
-            if (current == (struct parse_node *) NULL) {
-                root = current = new;
-                break;
-            }
-            /* Percolate upwards */
-            if (current != (struct parse_node *) NULL) {
-                switch (current->token.type) {
-                case token_not:
-                case token_eq:
-                case token_ne:
-                case token_and:
-                case token_or:
-                case token_lbrace:
-                case token_ge:
-                case token_gt:
-                case token_le:
-                case token_lt:
-                    break;
-                default:
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                                  "Invalid expression \"%s\" in file %s",
-                                  expr, r->filename);
-                    *was_error = 1;
-                    return retval;
+                /* See the Kludge in send_parsed_file for why */
+                /* Basically, it puts a bread crumb in here, then looks */
+                /*   for the crumb later to see if its been here.       */
+                if (rr) 
+                    ap_set_module_config(rr->request_config, 
+                                         &include_module, r);
+
+                if (!error_fmt && ap_run_sub_req(rr)) {
+                    error_fmt = "unable to include \"%s\" in parsed file %s";
+                }
+                if (error_fmt) {
+                    ap_log_rerror(APLOG_MARK, loglevel,
+                                  0, r, error_fmt, tag_val, r->filename);
+                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
                 }
-            }
-            if (current == (struct parse_node *) NULL) {
-                new->left = root;
-                new->left->parent = new;
-                new->parent = (struct parse_node *) NULL;
-                root = new;
-            }
-            else {
-                new->left = current->right;
-                current->right = new;
-                new->parent = current;
-            }
-            current = new;
-            break;
 
-        case token_eq:
-        case token_ne:
-        case token_ge:
-        case token_gt:
-        case token_le:
-        case token_lt:
-#ifdef DEBUG_INCLUDE
-            memcpy(&debug[debug_pos], "     Token: eq/ne/ge/gt/le/lt\n",
-                    sizeof("     Token: eq/ne/ge/gt/le/lt\n"));
-            debug_pos += sizeof("     Token: eq/ne/ge/gt/le/lt\n");
-#endif
-            if (current == (struct parse_node *) NULL) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "Invalid expression \"%s\" in file %s",
-                              expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            /* Percolate upwards */
-            while (current != (struct parse_node *) NULL) {
-                switch (current->token.type) {
-                case token_string:
-                case token_re:
-                case token_group:
-                    current = current->parent;
-                    continue;
-                case token_lbrace:
-                case token_and:
-                case token_or:
-                    break;
-                case token_not:
-                case token_eq:
-                case token_ne:
-                case token_ge:
-                case token_gt:
-                case token_le:
-                case token_lt:
-                default:
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                                "Invalid expression \"%s\" in file %s",
-                                expr, r->filename);
-                    *was_error = 1;
-                    return retval;
+                /* destroy the sub request */
+                if (rr != NULL) {
+                    ap_destroy_sub_req(rr);
                 }
-                break;
-            }
-            if (current == (struct parse_node *) NULL) {
-                new->left = root;
-                new->left->parent = new;
-                new->parent = (struct parse_node *) NULL;
-                root = new;
             }
             else {
-                new->left = current->right;
-                current->right = new;
-                new->parent = current;
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" to tag include in %s",
+                            tag, r->filename);
+                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
             }
-            current = new;
-            break;
+        }
+    }
 
-        case token_rbrace:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Token: rbrace\n",
-                    sizeof ("     Token: rbrace\n"));
-            debug_pos += sizeof ("     Token: rbrace\n");
-#endif
-            while (current != (struct parse_node *) NULL) {
-                if (current->token.type == token_lbrace) {
-                    current->token.type = token_group;
-                    break;
-                }
-                current = current->parent;
-            }
-            if (current == (struct parse_node *) NULL) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Unmatched ')' in \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
+    return APR_SUCCESS;
+}
+
+static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
+                                apr_bucket_brigade *bb)
+{
+    char       *tag       = NULL;
+    char       *tag_val   = NULL;
+    const char *echo_text = NULL;
+    apr_bucket  *tmp_buck;
+    apr_size_t e_len;
+    enum {E_NONE, E_URL, E_ENTITY} encode;
+    request_rec *r = f->r;
+
+    encode = E_ENTITY;
+
+    if (ctx->flags & SSI_FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+            if (!tag || !tag_val) {
+                return APR_SUCCESS;
             }
-            break;
 
-        case token_lbrace:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Token: lbrace\n",
-                    sizeof ("     Token: lbrace\n"));
-            debug_pos += sizeof ("     Token: lbrace\n");
-#endif
-            if (current == (struct parse_node *) NULL) {
-                root = current = new;
-                break;
+            if (!strcmp(tag, "var")) {
+                conn_rec *c = r->connection;
+                const char *val =
+                    get_include_var(r, ctx,
+                                    ap_ssi_parse_string(r, ctx, tag_val, NULL,
+                                                        MAX_STRING_LEN,
+                                                        SSI_EXPAND_DROP_NAME));
+                if (val) {
+                    switch(encode) {
+                    case E_NONE:   
+                        echo_text = val;
+                        break;
+                    case E_URL:
+                        echo_text = ap_escape_uri(r->pool, val);  
+                        break;
+                    case E_ENTITY: 
+                        echo_text = ap_escape_html(r->pool, val); 
+                        break;
+                    }
+
+                    e_len = strlen(echo_text);
+                    tmp_buck = apr_bucket_pool_create(echo_text, e_len,
+                                                      r->pool, c->bucket_alloc);
+                }
+                else {
+                    include_server_config *sconf= 
+                        ap_get_module_config(r->server->module_config,
+                                             &include_module);
+                    tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho, 
+                                                      sconf->undefinedEchoLen,
+                                                      r->pool, c->bucket_alloc);
+                }
+                APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
             }
-            /* Percolate upwards */
-            if (current != (struct parse_node *) NULL) {
-                switch (current->token.type) {
-                case token_not:
-                case token_eq:
-                case token_ne:
-                case token_and:
-                case token_or:
-                case token_lbrace:
-                case token_ge:
-                case token_gt:
-                case token_le:
-                case token_lt:
-                    break;
-                case token_string:
-                case token_re:
-                case token_group:
-                default:
+            else if (!strcmp(tag, "encoding")) {
+                if (!strcasecmp(tag_val, "none")) encode = E_NONE;
+                else if (!strcasecmp(tag_val, "url")) encode = E_URL;
+                else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
+                else {
                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                                "Invalid expression \"%s\" in file %s",
-                                expr, r->filename);
-                    *was_error = 1;
-                    return retval;
+                           "unknown value \"%s\" to parameter \"encoding\" of "
+                           "tag echo in %s", tag_val, r->filename);
+                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
                 }
             }
-            if (current == (struct parse_node *) NULL) {
-                new->left = root;
-                new->left->parent = new;
-                new->parent = (struct parse_node *) NULL;
-                root = new;
-            }
             else {
-                new->left = current->right;
-                current->right = new;
-                new->parent = current;
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" in tag echo of %s",
+                            tag, r->filename);
+                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
             }
-            current = new;
-            break;
-        default:
-            break;
+
         }
     }
 
-    /* Evaluate Parse Tree */
-    current = root;
-    while (current != (struct parse_node *) NULL) {
-        switch (current->token.type) {
-        case token_string:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Evaluate string\n",
-                    sizeof ("     Evaluate string\n"));
-            debug_pos += sizeof ("     Evaluate string\n");
-#endif
-            buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL, 
-                                         MAX_STRING_LEN, 0);
-            current->token.value = buffer;
-            current->value = (current->token.value[0] != '\0');
-            current->done = 1;
-            current = current->parent;
-            break;
+    return APR_SUCCESS;
+}
 
-        case token_re:
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                          "No operator before regex of expr \"%s\" in file %s",
-                          expr, r->filename);
-            *was_error = 1;
-            return retval;
+/* error and tf must point to a string with room for at 
+ * least MAX_STRING_LEN characters 
+ */
+static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
+                                  apr_bucket_brigade *bb)
+{
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *parsed_string;
+    request_rec *r = f->r;
+    apr_table_t *env = r->subprocess_env;
 
-        case token_and:
-        case token_or:
-#ifdef DEBUG_INCLUDE
-            memcpy(&debug[debug_pos], "     Evaluate and/or\n",
-                    sizeof("     Evaluate and/or\n"));
-            debug_pos += sizeof("     Evaluate and/or\n");
-#endif
-            if (current->left  == (struct parse_node *) NULL ||
-                current->right == (struct parse_node *) NULL) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "Invalid expression \"%s\" in file %s",
-                              expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            if (!current->left->done) {
-                switch (current->left->token.type) {
-                case token_string:
-                    buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
-                                                 NULL, MAX_STRING_LEN, 0);
-                    current->left->token.value = buffer;
-                    current->left->value = 
-                                       (current->left->token.value[0] != '\0');
-                    current->left->done = 1;
-                    break;
-                default:
-                    current = current->left;
-                    continue;
-                }
-            }
-            if (!current->right->done) {
-                switch (current->right->token.type) {
-                case token_string:
-                    buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
-                                                 NULL, MAX_STRING_LEN, 0);
-                    current->right->token.value = buffer;
-                    current->right->value = 
-                                      (current->right->token.value[0] != '\0');
-                    current->right->done = 1;
-                    break;
-                default:
-                    current = current->right;
-                    continue;
-                }
-            }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Left: %c\n",
-                                  current->left->value ? '1' : '0');
-            debug_pos += sprintf (&debug[debug_pos], "     Right: %c\n",
-                                  current->right->value ? '1' : '0');
-#endif
-            if (current->token.type == token_and) {
-                current->value = current->left->value && current->right->value;
-            }
-            else {
-                current->value = current->left->value || current->right->value;
+    if (ctx->flags & SSI_FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
+            if (! tag || !tag_val) {
+                return APR_SUCCESS;
             }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
-                                  current->value ? '1' : '0');
-#endif
-            current->done = 1;
-            current = current->parent;
-            break;
 
-        case token_eq:
-        case token_ne:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Evaluate eq/ne\n",
-                    sizeof ("     Evaluate eq/ne\n"));
-            debug_pos += sizeof ("     Evaluate eq/ne\n");
-#endif
-            if ((current->left == (struct parse_node *) NULL) ||
-                (current->right == (struct parse_node *) NULL) ||
-                (current->left->token.type != token_string) ||
-                ((current->right->token.type != token_string) &&
-                 (current->right->token.type != token_re))) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Invalid expression \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
-                                         NULL, MAX_STRING_LEN, 0);
-            current->left->token.value = buffer;
-            buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
-                                         NULL, MAX_STRING_LEN, 0);
-            current->right->token.value = buffer;
-            if (current->right->token.type == token_re) {
-#ifdef DEBUG_INCLUDE
-                debug_pos += sprintf (&debug[debug_pos],
-                                      "     Re Compare (%s) with /%s/\n",
-                                      current->left->token.value,
-                                      current->right->token.value);
-#endif
-                current->value =
-                    re_check(r, ctx, current->left->token.value,
-                             current->right->token.value);
-            }
-            else {
-#ifdef DEBUG_INCLUDE
-                debug_pos += sprintf (&debug[debug_pos],
-                                      "     Compare (%s) with (%s)\n",
-                                      current->left->token.value,
-                                      current->right->token.value);
-#endif
-                current->value =
-                    (strcmp(current->left->token.value,
-                            current->right->token.value) == 0);
-            }
-            if (current->token.type == token_ne) {
-                current->value = !current->value;
-            }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
-                                  current->value ? '1' : '0');
-#endif
-            current->done = 1;
-            current = current->parent;
-            break;
-        case token_ge:
-        case token_gt:
-        case token_le:
-        case token_lt:
-#ifdef DEBUG_INCLUDE
-            memcpy (&debug[debug_pos], "     Evaluate ge/gt/le/lt\n",
-                    sizeof ("     Evaluate ge/gt/le/lt\n"));
-            debug_pos += sizeof ("     Evaluate ge/gt/le/lt\n");
-#endif
-            if ((current->left == (struct parse_node *) NULL) ||
-                (current->right == (struct parse_node *) NULL) ||
-                (current->left->token.type != token_string) ||
-                (current->right->token.type != token_string)) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                            "Invalid expression \"%s\" in file %s",
-                            expr, r->filename);
-                *was_error = 1;
-                return retval;
-            }
-            buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
-                                         NULL, MAX_STRING_LEN, 0);
-            current->left->token.value = buffer;
-            buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
-                                         NULL, MAX_STRING_LEN, 0);
-            current->right->token.value = buffer;
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos],
-                                  "     Compare (%s) with (%s)\n",
-                                  current->left->token.value,
-                                  current->right->token.value);
-#endif
-            current->value =
-                strcmp(current->left->token.value,
-                       current->right->token.value);
-            if (current->token.type == token_ge) {
-                current->value = current->value >= 0;
-            }
-            else if (current->token.type == token_gt) {
-                current->value = current->value > 0;
+            if (!strcmp(tag, "errmsg")) {
+                if (ctx->intern->error_str_override == NULL) {
+                    ctx->intern->error_str_override = apr_palloc(ctx->pool,
+                                                                MAX_STRING_LEN);
+                    ctx->error_str = ctx->intern->error_str_override;
+                }
+                ap_ssi_parse_string(r, ctx, tag_val,
+                                    ctx->intern->error_str_override,
+                                    MAX_STRING_LEN, SSI_EXPAND_DROP_NAME);
             }
-            else if (current->token.type == token_le) {
-                current->value = current->value <= 0;
+            else if (!strcmp(tag, "timefmt")) {
+                apr_time_t date = r->request_time;
+                if (ctx->intern->time_str_override == NULL) {
+                    ctx->intern->time_str_override = apr_palloc(ctx->pool,
+                                                                MAX_STRING_LEN);
+                    ctx->time_str = ctx->intern->time_str_override;
+                }
+                ap_ssi_parse_string(r, ctx, tag_val,
+                                    ctx->intern->time_str_override,
+                                    MAX_STRING_LEN, SSI_EXPAND_DROP_NAME);
+
+                apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, 
+                               ctx->time_str, 0));
+                apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, 
+                               ctx->time_str, 1));
+                apr_table_setn(env, "LAST_MODIFIED",
+                               ap_ht_time(r->pool, r->finfo.mtime, 
+                               ctx->time_str, 0));
             }
-            else if (current->token.type == token_lt) {
-                current->value = current->value < 0;
+            else if (!strcmp(tag, "sizefmt")) {
+                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
+                                                    MAX_STRING_LEN,
+                                                    SSI_EXPAND_DROP_NAME);
+                decodehtml(parsed_string);
+                if (!strcmp(parsed_string, "bytes")) {
+                    ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
+                }
+                else if (!strcmp(parsed_string, "abbrev")) {
+                    ctx->flags &= SSI_FLAG_SIZE_ABBREV;
+                }
             }
             else {
-                current->value = 0;     /* Don't return -1 if unknown token */
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                              "unknown parameter \"%s\" to tag config in %s",
+                              tag, r->filename);
+                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
             }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
-                                  current->value ? '1' : '0');
-#endif
-            current->done = 1;
-            current = current->parent;
-            break;
+        }
+    }
 
-        case token_not:
-            if (current->right != (struct parse_node *) NULL) {
-                if (!current->right->done) {
-                    current = current->right;
-                    continue;
-                }
-                current->value = !current->right->value;
+    return APR_SUCCESS;
+}
+
+static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
+                                 apr_bucket_brigade *bb)
+{
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_size_t  s_len;
+    apr_bucket   *tmp_buck;
+    char *parsed_string;
+    request_rec *r = f->r;
+
+    if (ctx->flags & SSI_FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+            if (!tag || !tag_val) {
+                return APR_SUCCESS;
             }
             else {
-                current->value = 0;
-            }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
-                                  current->value ? '1' : '0');
-#endif
-            current->done = 1;
-            current = current->parent;
-            break;
+                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
+                                                    MAX_STRING_LEN,
+                                                    SSI_EXPAND_DROP_NAME);
+                if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
+                    /* XXX: if we *know* we're going to have to copy the
+                     * thing off of the stack anyway, why not palloc buff
+                     * instead of sticking it on the stack; then we can just
+                     * use a pool bucket and skip the copy
+                     */
+                    char buff[50];
 
-        case token_group:
-            if (current->right != (struct parse_node *) NULL) {
-                if (!current->right->done) {
-                    current = current->right;
-                    continue;
+                    if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
+                        apr_strfsize(finfo.size, buff);
+                        s_len = strlen (buff);
+                    }
+                    else {
+                        int l, x, pos = 0;
+                        char tmp_buff[50];
+
+                        apr_snprintf(tmp_buff, sizeof(tmp_buff), 
+                                     "%" APR_OFF_T_FMT, finfo.size);
+                        l = strlen(tmp_buff);    /* grrr */
+                        for (x = 0; x < l; x++) {
+                            if (x && (!((l - x) % 3))) {
+                                buff[pos++] = ',';
+                            }
+                            buff[pos++] = tmp_buff[x];
+                        }
+                        buff[pos] = '\0';
+                        s_len = pos;
+                    }
+
+                    tmp_buck = apr_bucket_heap_create(buff, s_len, NULL,
+                                                  r->connection->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
+                }
+                else {
+                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
                 }
-                current->value = current->right->value;
-            }
-            else {
-                current->value = 1;
             }
-#ifdef DEBUG_INCLUDE
-            debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
-                                  current->value ? '1' : '0');
-#endif
-            current->done = 1;
-            current = current->parent;
-            break;
+        }
+    }
 
-        case token_lbrace:
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                        "Unmatched '(' in \"%s\" in file %s",
-                        expr, r->filename);
-            *was_error = 1;
-            return retval;
+    return APR_SUCCESS;
+}
 
-        case token_rbrace:
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                        "Unmatched ')' in \"%s\" in file %s",
-                        expr, r->filename);
-            *was_error = 1;
-            return retval;
+static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
+                                    apr_bucket_brigade *bb)
+{
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_size_t  t_len;
+    apr_bucket   *tmp_buck;
+    char *parsed_string;
+    request_rec *r = f->r;
 
-        default:
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                          "bad token type");
-            *was_error = 1;
-            return retval;
+    if (ctx->flags & SSI_FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
+            if (!tag || !tag_val) {
+                return APR_SUCCESS;
+            }
+            else {
+                parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
+                                                    MAX_STRING_LEN,
+                                                    SSI_EXPAND_DROP_NAME);
+                if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
+                    char *t_val;
+
+                    t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
+                    t_len = strlen(t_val);
+
+                    tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool,
+                                                  r->connection->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
+                }
+                else {
+                    SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+                }
+            }
         }
     }
 
-    retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
-    return (retval);
+    return APR_SUCCESS;
 }
 
 /* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
@@ -2340,22 +2308,104 @@ static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
                                                   r->connection->bucket_alloc);
                 APR_BRIGADE_INSERT_TAIL(bb, tmp_buck);
             }
-            return APR_SUCCESS;
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                        "printenv directive does not take tags in %s", 
-                        r->filename);
-            SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
-            return APR_SUCCESS;
-        }
+            return APR_SUCCESS;
+        }
+        else {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                        "printenv directive does not take tags in %s", 
+                        r->filename);
+            SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
+            return APR_SUCCESS;
+        }
+    }
+
+    return APR_SUCCESS;
+}
+
+
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |               Main Includes-Filter Engine
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
+
+/* This is an implementation of the BNDM search algorithm.
+ *
+ * Fast and Flexible String Matching by Combining Bit-parallelism and 
+ * Suffix Automata (2001) 
+ * Gonzalo Navarro, Mathieu Raffinot
+ *
+ * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
+ *
+ * Initial code submitted by Sascha Schumann.
+ */
+   
+/* Precompile the bndm_t data structure. */
+static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl)
+{
+    unsigned int x;
+    const char *ne = n + nl;
+
+    memset(t->T, 0, sizeof(unsigned int) * 256);
+    
+    for (x = 1; n < ne; x <<= 1)
+        t->T[(unsigned char) *n++] |= x;
+
+    t->x = x - 1;
+}
+
+/* Implements the BNDM search algorithm (as described above).
+ *
+ * n  - the pattern to search for
+ * nl - length of the pattern to search for
+ * h  - the string to look in
+ * hl - length of the string to look for
+ * t  - precompiled bndm structure against the pattern 
+ *
+ * Returns the count of character that is the first match or hl if no
+ * match is found.
+ */
+static apr_size_t bndm(const char *n, apr_size_t nl, const char *h, 
+                       apr_size_t hl, bndm_t *t)
+{
+    const char *skip;
+    const char *he, *p, *pi;
+    unsigned int *T, x, d;
+
+    he = h + hl;
+
+    T = t->T;
+    x = t->x;
+
+    pi = h - 1; /* pi: p initial */
+    p = pi + nl; /* compare window right to left. point to the first char */
+
+    while (p < he) {
+        skip = p;
+        d = x;
+        do {
+            d &= T[(unsigned char) *p--];
+            if (!d) {
+                break;
+            }
+            if ((d & 1)) {
+                if (p != pi)
+                    skip = p;
+                else
+                    return p - h + 1;
+            }
+            d >>= 1;
+        } while (d);
+
+        pi = skip;
+        p = pi + nl;
     }
 
-    return APR_SUCCESS;
+    return hl;
 }
 
-/* -------------------------- The main function --------------------------- */
-
 /*
  * returns the index position of the first byte of start_seq (or the len of
  * the buffer as non-match)
@@ -3240,7 +3290,7 @@ static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
             else {
                 include_handler_fn_t *handle_func;
 
-                handle_func = apr_hash_get(include_hash, intern->directive, 
+                handle_func = apr_hash_get(include_handlers, intern->directive,
                                            intern->directive_len);
 
                 if (handle_func) {
@@ -3322,52 +3372,14 @@ static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
     return rv;
 }
 
-static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
-{
-    include_dir_config *result =
-        (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
-    enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
-    *xbh = DEFAULT_XBITHACK;
-    result->default_error_msg = DEFAULT_ERROR_MSG;
-    result->default_time_fmt = DEFAULT_TIME_FORMAT;
-    result->xbithack = xbh;
-    return result;
-}
-
-static void *create_includes_server_config(apr_pool_t*p, server_rec *server)
-{
-    include_server_config *result =
-        (include_server_config *)apr_palloc(p, sizeof(include_server_config));
-    result->default_end_tag = DEFAULT_END_SEQUENCE;
-    result->default_start_tag = DEFAULT_START_SEQUENCE;
-    result->start_tag_len = sizeof(DEFAULT_START_SEQUENCE)-1;
-    /* compile the pattern used by find_start_sequence */
-    bndm_compile(&result->start_seq_pat, result->default_start_tag, 
-                 result->start_tag_len); 
-
-    result->undefinedEcho = apr_pstrdup(p,"(none)");
-    result->undefinedEchoLen = strlen( result->undefinedEcho);
-    return result; 
-}
-static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
-{
-    include_dir_config *conf = (include_dir_config *)xbp;
-
-    if (!strcasecmp(arg, "off")) {
-        *conf->xbithack = xbithack_off;
-    }
-    else if (!strcasecmp(arg, "on")) {
-        *conf->xbithack = xbithack_on;
-    }
-    else if (!strcasecmp(arg, "full")) {
-        *conf->xbithack = xbithack_full;
-    }
-    else {
-        return "XBitHack must be set to Off, On, or Full";
-    }
 
-    return NULL;
-}
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                     Runtime Hooks
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
 
 static int includes_setup(ap_filter_t *f)
 {
@@ -3499,32 +3511,103 @@ static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
     return send_parsed_content(f, b);
 }
 
-static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
+static int include_fixup(request_rec *r)
 {
-    apr_hash_set(include_hash, tag, strlen(tag), (const void *)func);
+    include_dir_config *conf;
+    conf = (include_dir_config *) ap_get_module_config(r->per_dir_config,
+                                                &include_module);
+    if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) 
+    {
+        if (!r->content_type || !*r->content_type) {
+            ap_set_content_type(r, "text/html");
+        }
+        r->handler = "default-handler";
+    }
+    else 
+#if defined(OS2) || defined(WIN32) || defined(NETWARE)
+    /* These OS's don't support xbithack. This is being worked on. */
+    {
+        return DECLINED;
+    }
+#else
+    {
+        if (*conf->xbithack == xbithack_off) {
+            return DECLINED;
+        }
+
+        if (!(r->finfo.protection & APR_UEXECUTE)) {
+            return DECLINED;
+        }
+
+        if (!r->content_type || strcmp(r->content_type, "text/html")) {
+            return DECLINED;
+        }
+    }
+#endif
+
+    /* We always return declined, because the default handler actually
+     * serves the file.  All we have to do is add the filter.
+     */
+    ap_add_output_filter("INCLUDES", NULL, r, r->connection);
+    return DECLINED;
 }
 
-static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
-                                apr_pool_t *ptemp, server_rec *s)
+
+/*
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |                Configuration Handling
+ * |                                                       |
+ * +-------------------------------------------------------+
+ */
+
+static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
 {
-    include_hash = apr_hash_make(p);
-    
-    ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
+    include_dir_config *result =
+        (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
+    enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
+    *xbh = DEFAULT_XBITHACK;
+    result->default_error_msg = DEFAULT_ERROR_MSG;
+    result->default_time_fmt = DEFAULT_TIME_FORMAT;
+    result->xbithack = xbh;
+    return result;
+}
 
-    if(ssi_pfn_register) {
-        ssi_pfn_register("if", handle_if);
-        ssi_pfn_register("set", handle_set);
-        ssi_pfn_register("else", handle_else);
-        ssi_pfn_register("elif", handle_elif);
-        ssi_pfn_register("echo", handle_echo);
-        ssi_pfn_register("endif", handle_endif);
-        ssi_pfn_register("fsize", handle_fsize);
-        ssi_pfn_register("config", handle_config);
-        ssi_pfn_register("include", handle_include);
-        ssi_pfn_register("flastmod", handle_flastmod);
-        ssi_pfn_register("printenv", handle_printenv);
+static void *create_includes_server_config(apr_pool_t*p, server_rec *server)
+{
+    include_server_config *result =
+        (include_server_config *)apr_palloc(p, sizeof(include_server_config));
+    result->default_end_tag = DEFAULT_END_SEQUENCE;
+    result->default_start_tag = DEFAULT_START_SEQUENCE;
+    result->start_tag_len = sizeof(DEFAULT_START_SEQUENCE)-1;
+    /* compile the pattern used by find_start_sequence */
+    bndm_compile(&result->start_seq_pat, result->default_start_tag, 
+                 result->start_tag_len); 
+
+    result->undefinedEcho = apr_pstrdup(p,"(none)");
+    result->undefinedEchoLen = strlen( result->undefinedEcho);
+    return result; 
+}
+static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
+{
+    include_dir_config *conf = (include_dir_config *)xbp;
+
+    if (!strcasecmp(arg, "off")) {
+        *conf->xbithack = xbithack_off;
     }
-    return OK;
+    else if (!strcasecmp(arg, "on")) {
+        *conf->xbithack = xbithack_on;
+    }
+    else if (!strcasecmp(arg, "full")) {
+        *conf->xbithack = xbithack_full;
+    }
+    else {
+        return "XBitHack must be set to Off, On, or Full";
+    }
+
+    return NULL;
 }
 
 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
@@ -3592,9 +3675,39 @@ static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const cha
     return NULL;
 }
 
+
 /*
- * Module definition and configuration data structs...
+ * +-------------------------------------------------------+
+ * |                                                       |
+ * |        Module Initialization and Configuration
+ * |                                                       |
+ * +-------------------------------------------------------+
  */
+
+static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
+                                apr_pool_t *ptemp, server_rec *s)
+{
+    include_handlers = apr_hash_make(p);
+
+    ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
+
+    if(ssi_pfn_register) {
+        ssi_pfn_register("if", handle_if);
+        ssi_pfn_register("set", handle_set);
+        ssi_pfn_register("else", handle_else);
+        ssi_pfn_register("elif", handle_elif);
+        ssi_pfn_register("echo", handle_echo);
+        ssi_pfn_register("endif", handle_endif);
+        ssi_pfn_register("fsize", handle_fsize);
+        ssi_pfn_register("config", handle_config);
+        ssi_pfn_register("include", handle_include);
+        ssi_pfn_register("flastmod", handle_flastmod);
+        ssi_pfn_register("printenv", handle_printenv);
+    }
+
+    return OK;
+}
+
 static const command_rec includes_cmds[] =
 {
     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
@@ -3612,47 +3725,9 @@ static const command_rec includes_cmds[] =
     {NULL}
 };
 
-static int include_fixup(request_rec *r)
+static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
 {
-    include_dir_config *conf;
-    conf = (include_dir_config *) ap_get_module_config(r->per_dir_config,
-                                                &include_module);
-    if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) 
-    {
-        if (!r->content_type || !*r->content_type) {
-            ap_set_content_type(r, "text/html");
-        }
-        r->handler = "default-handler";
-    }
-    else 
-#if defined(OS2) || defined(WIN32) || defined(NETWARE)
-    /* These OS's don't support xbithack. This is being worked on. */
-    {
-        return DECLINED;
-    }
-#else
-    {
-        if (*conf->xbithack == xbithack_off) {
-            return DECLINED;
-        }
-
-        if (!(r->finfo.protection & APR_UEXECUTE)) {
-            return DECLINED;
-        }
-
-        if (!r->content_type || strcmp(r->content_type, "text/html")) {
-            return DECLINED;
-        }
-    }
-#endif
-
-    /* We always return declined, because the default handler actually
-     * serves the file.  All we have to do is add the filter.
-     */
-    ap_add_output_filter("INCLUDES", NULL, r, r->connection);
-    return DECLINED;
+    apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
 }
 
 static void register_hooks(apr_pool_t *p)