]> granicus.if.org Git - apache/blobdiff - modules/filters/mod_include.c
Fix a segfault in mod_include when the original request has no
[apache] / modules / filters / mod_include.c
index 95283bee4cece13a2aedcd24231e9c2db57124a1..b65a50ba10d1c576b664610ebcdf4c55fc63d4f0 100644 (file)
@@ -1,7 +1,7 @@
 /* ====================================================================
  * The Apache Software License, Version 1.1
  *
- * Copyright (c) 2000 The Apache Software Foundation.  All rights
+ * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
  * reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * incorporated into the Apache module framework by rst.
  * 
  */
-/* 
- * sub key may be anything a Perl*Handler can be:
- * subroutine name, package name (defaults to package::handler),
- * Class->method call or anoymous sub {}
- *
- * Child <!--#perl sub="sub {print $$}" --> accessed
- * <!--#perl sub="sub {print ++$Access::Cnt }" --> times. <br>
- *
- * <!--#perl arg="one" sub="mymod::includer" -->
- *
- * -Doug MacEachern
- */
+
+#include "apr.h"
+#include "apr_strings.h"
+#include "apr_thread_proc.h"
+#include "apr_hash.h"
+#include "apr_user.h"
+#include "apr_lib.h"
+#include "apr_optional.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
 
 #define CORE_PRIVATE
 
-#ifdef USE_PERL_SSI
-#include "config.h"
-#undef VOIDUSED
-#ifdef USE_SFIO
-#undef USE_SFIO
-#define USE_STDIO
-#endif
-#include "modules/perl/mod_perl.h"
-#else
 #include "ap_config.h"
+#include "util_filter.h"
 #include "httpd.h"
 #include "http_config.h"
+#include "http_core.h"
 #include "http_request.h"
 #include "http_core.h"
 #include "http_protocol.h"
 #include "http_main.h"
 #include "util_script.h"
 #include "http_core.h"
-#ifdef HAVE_STRING_H
-#include <string.h>
-#endif
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#ifdef HAVE_PWD_H
-#include <pwd.h>
-#endif
-#endif
+#include "mod_include.h"
 #include "util_ebcdic.h"
 
-#define STARTING_SEQUENCE "<!--#"
-#define ENDING_SEQUENCE "-->"
-#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
-#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
-#define SIZEFMT_BYTES 0
-#define SIZEFMT_KMG 1
-#ifdef CHARSET_EBCDIC
-#define RAW_ASCII_CHAR(ch)  ap_xlate_conv_byte(ap_hdrs_from_ascii, (unsigned char)ch)
-#else /*CHARSET_EBCDIC*/
-#define RAW_ASCII_CHAR(ch)  (ch)
-#endif /*CHARSET_EBCDIC*/
-
-module MODULE_VAR_EXPORT includes_module;
+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;
 
-/* just need some arbitrary non-NULL pointer which can't also be a request_rec */
-#define NESTED_INCLUDE_MAGIC   (&includes_module)
-
-/* TODO: changing directory should be handled by CreateProcess */
-#define ap_chdir_file(x) do {} while(0)
+#define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
 
 /* ------------------------ Environment function -------------------------- */
 
 /* XXX: could use ap_table_overlap here */
 static void add_include_vars(request_rec *r, char *timefmt)
 {
-#ifndef WIN32
-    struct passwd *pw;
-#endif /* ndef WIN32 */
-    ap_table_t *e = r->subprocess_env;
+    char *pwname;
+    apr_table_t *e = r->subprocess_env;
     char *t;
-    ap_time_t date = r->request_time;
+    apr_time_t date = r->request_time;
 
-    ap_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0));
-    ap_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1));
-    ap_table_setn(e, "LAST_MODIFIED",
+    apr_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0));
+    apr_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1));
+    apr_table_setn(e, "LAST_MODIFIED",
               ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0));
-    ap_table_setn(e, "DOCUMENT_URI", r->uri);
-    ap_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
-#ifndef WIN32
-    pw = getpwuid(r->finfo.user);
-    if (pw) {
-        ap_table_setn(e, "USER_NAME", ap_pstrdup(r->pool, pw->pw_name));
+    apr_table_setn(e, "DOCUMENT_URI", r->uri);
+    apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
+    if (apr_get_username(&pwname, r->finfo.user, r->pool) == APR_SUCCESS) {
+        apr_table_setn(e, "USER_NAME", pwname);
     }
     else {
-        ap_table_setn(e, "USER_NAME", ap_psprintf(r->pool, "user#%lu",
-                    (unsigned long) r->finfo.user));
+        apr_table_setn(e, "USER_NAME", "<unknown>");
     }
-#endif /* ndef WIN32 */
-
     if ((t = strrchr(r->filename, '/'))) {
-        ap_table_setn(e, "DOCUMENT_NAME", ++t);
+        apr_table_setn(e, "DOCUMENT_NAME", ++t);
     }
     else {
-        ap_table_setn(e, "DOCUMENT_NAME", r->uri);
+        apr_table_setn(e, "DOCUMENT_NAME", r->uri);
     }
     if (r->args) {
-        char *arg_copy = ap_pstrdup(r->pool, r->args);
+        char *arg_copy = apr_pstrdup(r->pool, r->args);
 
         ap_unescape_url(arg_copy);
-        ap_table_setn(e, "QUERY_STRING_UNESCAPED",
+        apr_table_setn(e, "QUERY_STRING_UNESCAPED",
                   ap_escape_shell_cmd(r->pool, arg_copy));
     }
 }
@@ -177,100 +139,296 @@ static void add_include_vars(request_rec *r, char *timefmt)
 
 /* --------------------------- Parser functions --------------------------- */
 
-#define OUTBUFSIZE 4096
-/* PUT_CHAR and FLUSH_BUF currently only work within the scope of 
- * find_string(); they are hacks to avoid calling rputc for each and
- * every character output.  A common set of buffering calls for this 
- * type of output SHOULD be implemented.
+/* This function returns either a pointer to the split bucket containing the
+ * first byte of the BEGINNING_SEQUENCE (after finding a complete match) or it
+ * returns NULL if no match found.
  */
-#define PUT_CHAR(c,r) \
- { \
-    outbuf[outind++] = c; \
-    if (outind == OUTBUFSIZE) { \
-        FLUSH_BUF(r) \
-    }; \
- }
-
-/* there SHOULD be some error checking on the return value of
- * rwrite, however it is unclear what the API for rwrite returning
- * errors is and little can really be done to help the error in 
- * any case.
- */
-#define FLUSH_BUF(r) \
- { \
-   ap_rwrite(outbuf, outind, r); \
-   outind = 0; \
- }
-
-/*
- * f: file handle being read from
- * c: character to read into
- * ret: return value to use if input fails
- * r: current request_rec
- *
- * This macro is redefined after find_string() for historical reasons
- * to avoid too many code changes.  This is one of the many things
- * that should be fixed.
- */
-#define GET_CHAR(f,c,ret,r) \
- { \
-   ap_status_t status = ap_getc(&c, f); \
-   if (status != APR_SUCCESS) { /* either EOF or error -- needs error handling if latter */ \
-       if (status != APR_EOF) { \
-           ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, \
-                        "encountered error in GET_CHAR macro, " \
-                        "mod_include."); \
-       } \
-       FLUSH_BUF(r); \
-       ap_close(f); \
-       return ret; \
-   } \
- }
-
-static int find_string(ap_file_t *in, const char *str, request_rec *r, int printing)
+static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
+                                      apr_bucket_brigade *bb, int *do_cleanup)
 {
-    int x, l = strlen(str), p;
-    char outbuf[OUTBUFSIZE];
-    int outind = 0;
-    char c;
+    apr_size_t len;
+    const char *c;
+    const char *buf;
+    const char *str = STARTING_SEQUENCE;
+    apr_bucket *tmp_bkt;
+    apr_size_t  start_index;
 
-    p = 0;
-    while (1) {
-        GET_CHAR(in, c, 1, r);
-        if (c == str[p]) {
-            if ((++p) == l) {
-                FLUSH_BUF(r);
-                return 0;
+    *do_cleanup = 0;
+
+    do {
+        if (APR_BUCKET_IS_EOS(dptr)) {
+            break;
+        }
+        apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
+        /* XXX handle retcodes */
+        if (len == 0) { /* end of pipe? */
+            break;
+        }
+        c = buf;
+        while (c < buf + len) {
+            if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                apr_bucket *start_bucket;
+
+                if (ctx->head_start_index > 0) {
+                    start_index  = ctx->head_start_index;
+                    start_bucket = ctx->head_start_bucket;
+                }
+                else {
+                    start_index  = (c - buf);
+                    start_bucket = dptr;
+                }
+                apr_bucket_split(start_bucket, start_index);
+                tmp_bkt = APR_BUCKET_NEXT(start_bucket);
+                if (ctx->head_start_index > 0) {
+                    ctx->head_start_index  = 0;
+                    ctx->head_start_bucket = tmp_bkt;
+                    ctx->parse_pos = 0;
+                    ctx->state = PRE_HEAD;
+                }
+
+                return tmp_bkt;
+            }
+
+            if (*c == str[ctx->parse_pos]) {
+                if (ctx->state == PRE_HEAD) {
+                    ctx->state             = PARSE_HEAD;
+                    ctx->head_start_bucket = dptr;
+                    ctx->head_start_index  = c - buf;
+                }
+                ctx->parse_pos++;
+            }
+            else {
+                if (str[ctx->parse_pos] == '\0') {
+                    /* We want to split the bucket at the '<'. */
+                    ctx->bytes_parsed++;
+                    ctx->state            = PARSE_DIRECTIVE;
+                    ctx->tag_length       = 0;
+                    ctx->parse_pos        = 0;
+                    ctx->tag_start_bucket = dptr;
+                    ctx->tag_start_index  = c - buf;
+                    if (ctx->head_start_index > 0) {
+                        start_index = (c - buf) - ctx->head_start_index;
+                        apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
+                        tmp_bkt = APR_BUCKET_NEXT(ctx->head_start_bucket);
+                        if (dptr == ctx->head_start_bucket) {
+                            ctx->tag_start_bucket = tmp_bkt;
+                            ctx->tag_start_index  = start_index;
+                        }
+                        ctx->head_start_bucket = tmp_bkt;
+                        ctx->head_start_index  = 0;
+                    }
+                    return ctx->head_start_bucket;
+                }
+                else if (ctx->parse_pos != 0) {
+                    /* The reason for this, is that we need to make sure 
+                     * that we catch cases like <<!--#.  This makes the 
+                     * second check after the original check fails.
+                     * If parse_pos was already 0 then we already checked this.
+                     */
+                    *do_cleanup = 1;
+                    if (*c == str[0]) {
+                        ctx->parse_pos         = 1;
+                        ctx->state             = PARSE_HEAD;
+                        ctx->head_start_bucket = dptr;
+                        ctx->head_start_index  = c - buf;
+                    }
+                    else {
+                        ctx->parse_pos         = 0;
+                        ctx->state             = PRE_HEAD;
+                        ctx->head_start_bucket = NULL;
+                        ctx->head_start_index  = 0;
+                    }
+                }
             }
+            c++;
+            ctx->bytes_parsed++;
+        }
+        dptr = APR_BUCKET_NEXT(dptr);
+    } while (dptr != APR_BRIGADE_SENTINEL(bb));
+    return NULL;
+}
+
+static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_bucket_brigade *bb)
+{
+    apr_size_t len;
+    const char *c;
+    const char *buf;
+    const char *str = ENDING_SEQUENCE;
+
+    do {
+        if (APR_BUCKET_IS_EOS(dptr)) {
+            break;
+        }
+        apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
+        /* XXX handle retcodes */
+        if (len == 0) { /* end of pipe? */
+            break;
+        }
+        if (dptr == ctx->tag_start_bucket) {
+            c = buf + ctx->tag_start_index;
         }
         else {
-            if (printing) {
-                for (x = 0; x < p; x++) {
-                    PUT_CHAR(str[x], r);
+            c = buf;
+        }
+        while (c < buf + len) {
+            if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                if (ctx->state == PARSE_DIRECTIVE) {
+                    /* gonna start over parsing the directive next time through */
+                    ctx->directive_length = 0;
+                    ctx->tag_length       = 0;
+                }
+                return dptr;
+            }
+
+            if (*c == str[ctx->parse_pos]) {
+                if (ctx->state != PARSE_TAIL) {
+                    ctx->state             = PARSE_TAIL;
+                    ctx->tail_start_bucket = dptr;
+                    ctx->tail_start_index  = c - buf;
+                }
+                ctx->parse_pos++;
+            }
+            else {
+                if (ctx->state == PARSE_DIRECTIVE) {
+                    if (ctx->tag_length == 0) {
+                        if (!apr_isspace(*c)) {
+                            ctx->tag_start_bucket = dptr;
+                            ctx->tag_start_index  = c - buf;
+                            ctx->tag_length       = 1;
+                            ctx->directive_length = 1;
+                        }
+                    }
+                    else {
+                        if (!apr_isspace(*c)) {
+                            ctx->directive_length++;
+                        }
+                        else {
+                            ctx->state = PARSE_TAG;
+                        }
+                        ctx->tag_length++;
+                    }
+                }
+                else if (ctx->state == PARSE_TAG) {
+                    ctx->tag_length++;
+                }
+                else {
+                    if (str[ctx->parse_pos] == '\0') {
+                        apr_bucket *tmp_buck = dptr;
+
+                        /* We want to split the bucket at the '>'. The
+                         * end of the END_SEQUENCE is in the current bucket.
+                         * The beginning might be in a previous bucket.
+                         */
+                        ctx->bytes_parsed++;
+                        ctx->state = PARSED;
+                        if ((c - buf) > 0) {
+                            apr_bucket_split(dptr, c - buf);
+                            tmp_buck = APR_BUCKET_NEXT(dptr);
+                        }
+                        return (tmp_buck);
+                    }
+                    else if (ctx->parse_pos != 0) {
+                        /* The reason for this, is that we need to make sure 
+                         * that we catch cases like --->.  This makes the 
+                         * second check after the original check fails.
+                         * If parse_pos was already 0 then we already checked this.
+                         */
+                         ctx->tag_length += ctx->parse_pos;
+
+                         if (*c == str[0]) {
+                             ctx->state             = PARSE_TAIL;
+                             ctx->tail_start_bucket = dptr;
+                             ctx->tail_start_index  = c - buf;
+                             ctx->tag_length       += ctx->parse_pos;
+                             ctx->parse_pos         = 1;
+                         }
+                         else {
+                             if (ctx->tag_length > ctx->directive_length) {
+                                 ctx->state = PARSE_TAG;
+                             }
+                             else {
+                                 ctx->state = PARSE_DIRECTIVE;
+                                 ctx->directive_length += ctx->parse_pos;
+                             }
+                             ctx->tail_start_bucket = NULL;
+                             ctx->tail_start_index  = 0;
+                             ctx->tag_length       += ctx->parse_pos;
+                             ctx->parse_pos         = 0;
+                         }
+                    }
                 }
-                PUT_CHAR(c, r);
             }
-            p = 0;
+            c++;
+            ctx->bytes_parsed++;
         }
-    }
+        dptr = APR_BUCKET_NEXT(dptr);
+    } while (dptr != APR_BRIGADE_SENTINEL(bb));
+    return NULL;
 }
 
-#undef FLUSH_BUF
-#undef PUT_CHAR
-#undef GET_CHAR
-#define GET_CHAR(f,c,r,p) \
- { \
-   ap_status_t status = ap_getc(&c, f); \
-   if (status != APR_SUCCESS) { /* either EOF or error -- needs error handling if latter */ \
-       if (status != APR_EOF) { \
-           ap_log_error(APLOG_MARK, APLOG_STARTUP | APLOG_NOERRNO, 0, NULL, \
-                        "encountered error in GET_CHAR macro, " \
-                        "mod_include."); \
-       } \
-       ap_close(f); \
-       return r; \
-   } \
- }
+/* This function culls through the buckets that have been set aside in the 
+ * ssi_tag_brigade and copies just the directive part of the SSI tag (none
+ * of the start and end delimiter bytes are copied).
+ */
+static apr_status_t get_combined_directive (include_ctx_t *ctx,
+                                            request_rec *r,
+                                            apr_bucket_brigade *bb,
+                                            char *tmp_buf, 
+                                            apr_size_t tmp_buf_size)
+{
+    int        done = 0;
+    apr_bucket *dptr;
+    const char *tmp_from;
+    apr_size_t tmp_from_len;
+
+    /* If the tag length is longer than the tmp buffer, allocate space. */
+    if (ctx->tag_length > tmp_buf_size-1) {
+        if ((ctx->combined_tag = apr_pcalloc(r->pool, ctx->tag_length + 1)) == NULL) {
+            return (APR_ENOMEM);
+        }
+    }     /* Else, just use the temp buffer. */
+    else {
+        ctx->combined_tag = tmp_buf;
+    }
+
+    /* Prime the pump. Start at the beginning of the tag... */
+    dptr = ctx->tag_start_bucket;
+    apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);  /* Read the bucket... */
+
+    /* Adjust the pointer to start at the tag within the bucket... */
+    if (dptr == ctx->tail_start_bucket) {
+        tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
+    }
+    tmp_from          = &tmp_from[ctx->tag_start_index];
+    tmp_from_len     -= ctx->tag_start_index;
+    ctx->curr_tag_pos = ctx->combined_tag;
+
+    /* Loop through the buckets from the tag_start_bucket until before
+     * the tail_start_bucket copying the contents into the buffer.
+     */
+    do {
+        memcpy (ctx->curr_tag_pos, tmp_from, tmp_from_len);
+        ctx->curr_tag_pos += tmp_from_len;
+
+        if (dptr == ctx->tail_start_bucket) {
+            done = 1;
+        }
+        else {
+            dptr = APR_BUCKET_NEXT (dptr);
+            apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);
+            /* Adjust the count to stop at the beginning of the tail. */
+            if (dptr == ctx->tail_start_bucket) {
+                tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
+            }
+        }
+    } while ((!done) &&
+             (ctx->curr_tag_pos < ctx->combined_tag + ctx->tag_length));
+
+    ctx->combined_tag[ctx->tag_length] = '\0';
+    ctx->curr_tag_pos = ctx->combined_tag;
+
+    return (APR_SUCCESS);
+}
 
 /*
  * decodes a string containing html entities or numeric character references.
@@ -328,7 +486,7 @@ otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
 
         /* is it numeric ? */
         if (s[1] == '#') {
-            for (j = 2, val = 0; j < i && ap_isdigit(s[j]); j++) {
+            for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
                 val = val * 10 + s[j] - '0';
             }
             s += i;
@@ -367,132 +525,96 @@ otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
 }
 
 /*
- * extract the next tag name and value.
- * if there are no more tags, set the tag name to 'done'
- * the tag value is html decoded if dodecode is non-zero
+ * 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..
+ *    format:
+ *        [WS]<Tag>[WS]=[WS]['|"]<Value>['|"|WS]
  */
 
-static char *get_tag(ap_pool_t *p, ap_file_t *in, char *tag, int tagbuf_len, int dodecode)
-{
-    char *t = tag, *tag_val, c, term;
+#define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
 
-    /* makes code below a little less cluttered */
-    --tagbuf_len;
+static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
+                              char **tag_val, int dodecode)
+{
+    char *c = ctx->curr_tag_pos;
+    int   shift_val = 0; 
+    char  term = '\0';
 
-    do {                        /* skip whitespace */
-        GET_CHAR(in, c, NULL, p);
-    } while (ap_isspace(c));
+    *tag_val = NULL;
+    SKIP_TAG_WHITESPACE(c);
+    *tag = c;             /* First non-whitespace character (could be NULL). */
 
-    /* tags can't start with - */
-    if (c == '-') {
-        GET_CHAR(in, c, NULL, p);
-        if (c == '-') {
-            do {
-                GET_CHAR(in, c, NULL, p);
-            } while (ap_isspace(c));
-            if (c == '>') {
-                ap_cpystrn(tag, "done", tagbuf_len);
-                return tag;
-            }
-        }
-        return NULL;            /* failed */
+    while ((*c != '\0') && (*c != '=') && (!apr_isspace(*c))) {
+        *c = apr_tolower(*c);    /* find end of tag, lowercasing as we go... */
+        c++;
     }
 
-    /* find end of tag name */
-    while (1) {
-        if (t - tag == tagbuf_len) {
-            *t = '\0';
-            return NULL;
-        }
-        if (c == '=' || ap_isspace(c)) {
-            break;
+    if ((*c == '\0') || (**tag == '=')) {
+        if ((**tag == '\0') || (**tag == '=')) {
+            *tag = NULL;
         }
-        *(t++) = ap_tolower(c);
-        GET_CHAR(in, c, NULL, p);
-    }
-
-    *t++ = '\0';
-    tag_val = t;
+        ctx->curr_tag_pos = c;
+        return;      /* We have found the end of the buffer. */
+    }                /* We might have a tag, but definitely no value. */
 
-    while (ap_isspace(c)) {
-        GET_CHAR(in, c, NULL, p);       /* space before = */
+    if (*c == '=') {
+        *c++ = '\0';     /* Overwrite the '=' with a terminating byte after tag. */
     }
-    if (c != '=') {
-        ap_ungetc(c, in);
-        return NULL;
+    else {               /* Try skipping WS to find the '='. */
+        *c++ = '\0';     /* Terminate the tag... */
+        SKIP_TAG_WHITESPACE(c);
+        
+        if (*c != '=') {     /* There needs to be an equal sign if there's a value. */
+            ctx->curr_tag_pos = c;
+            return;       /* There apparently was no value. */
+        }
+        else {
+            c++; /* Skip the equals sign. */
+        }
     }
 
-    do {
-        GET_CHAR(in, c, NULL, p);       /* space after = */
-    } while (ap_isspace(c));
-
-    /* we should allow a 'name' as a value */
-
-    if (c != '"' && c != '\'') {
-        return NULL;
+    SKIP_TAG_WHITESPACE(c);
+    if (*c == '"' || *c == '\'') {    /* Allow quoted values for space inclusion. */
+        term = *c++;     /* NOTE: This does not pass the quotes on return. */
     }
-    term = c;
-    while (1) {
-        GET_CHAR(in, c, NULL, p);
-        if (t - tag == tagbuf_len) {
-            *t = '\0';
-            return NULL;
-        }
-/* Want to accept \" as a valid character within a string. */
-        if (c == '\\') {
-            *(t++) = c;         /* Add backslash */
-            GET_CHAR(in, c, NULL, p);
-            if (c == term) {    /* Only if */
-                *(--t) = c;     /* Replace backslash ONLY for terminator */
+    
+    *tag_val = c;
+    while ((*c != '\0') &&
+           (((term != '\0') && (*c != term)) ||
+            ((term == '\0') && (!apr_isspace(*c))))) {
+        if (*c == '\\') {  /* Accept \" and \' as valid char in string. */
+            c++;
+            if (*c == term) { /* Overwrite the "\" during the embedded  */
+                shift_val++;  /* escape sequence of '\"' or "\'". Shift */
+            }                 /* bytes from here to next delimiter.     */
+            if (shift_val > 0) {
+                *(c-shift_val) = *c;
             }
         }
-        else if (c == term) {
-            break;
+
+        c++;
+        if (shift_val > 0) {
+            *(c-shift_val) = *c;
         }
-        *(t++) = c;
     }
-    *t = '\0';
+    
+    *(c-shift_val) = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
+    ctx->curr_tag_pos = ++c;
     if (dodecode) {
-        decodehtml(tag_val);
+        decodehtml(*tag_val);
     }
-    return ap_pstrdup(p, tag_val);
-}
-
-static int get_directive(ap_file_t *in, char *dest, size_t len, ap_pool_t *p)
-{
-    char *d = dest;
-    char c;
 
-    /* make room for nul terminator */
-    --len;
-
-    /* skip initial whitespace */
-    while (1) {
-        GET_CHAR(in, c, 1, p);
-        if (!ap_isspace(c)) {
-            break;
-        }
-    }
-    /* now get directive */
-    while (1) {
-       if (d - dest == (int)len) {
-           return 1;
-       }
-        *d++ = ap_tolower(c);
-        GET_CHAR(in, c, 1, p);
-        if (ap_isspace(c)) {
-            break;
-        }
-    }
-    *d = '\0';
-    return 0;
+    return;
 }
 
+
 /*
  * Do variable substitution on strings
  */
-static void parse_string(request_rec *r, const char *in, char *out,
-                       size_t length, int leave_name)
+static void ap_ssi_parse_string(request_rec *r, const char *in, char *out,
+                         size_t length, int leave_name)
 {
     char ch;
     char *next = out;
@@ -518,11 +640,10 @@ static void parse_string(request_rec *r, const char *in, char *out,
             break;
         case '$':
             {
-               char var[MAX_STRING_LEN];
                const char *start_of_var_name;
-               const char *end_of_var_name;    /* end of var name + 1 */
-               const char *expansion;
-               const char *val;
+               char *end_of_var_name;  /* end of var name + 1 */
+               const char *expansion, *temp_end, *val;
+                char        tmp_store;
                size_t l;
 
                /* guess that the expansion won't happen */
@@ -538,25 +659,27 @@ static void parse_string(request_rec *r, const char *in, char *out,
                         *next = '\0';
                         return;
                     }
-                   end_of_var_name = in;
+                   temp_end = in;
+                    end_of_var_name = (char *)temp_end;
                    ++in;
                }
                else {
                    start_of_var_name = in;
-                   while (ap_isalnum(*in) || *in == '_') {
+                   while (apr_isalnum(*in) || *in == '_') {
                        ++in;
                    }
-                   end_of_var_name = in;
+                    temp_end = in;
+                   end_of_var_name = (char *)temp_end;
                }
                /* what a pain, too bad there's no table_getn where you can
                 * pass a non-nul terminated string */
                l = end_of_var_name - start_of_var_name;
                if (l != 0) {
-                   l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l;
-                   memcpy(var, start_of_var_name, l);
-                   var[l] = '\0';
+                    tmp_store        = *end_of_var_name;
+                    *end_of_var_name = '\0';
+                    val = apr_table_get(r->subprocess_env, start_of_var_name);
+                    *end_of_var_name = tmp_store;
 
-                   val = ap_table_get(r->subprocess_env, var);
                    if (val) {
                        expansion = val;
                        l = strlen(expansion);
@@ -593,526 +716,310 @@ static void parse_string(request_rec *r, const char *in, char *out,
 
 /* --------------------------- Action handlers ---------------------------- */
 
-static int include_cgi(char *s, request_rec *r)
-{
-    request_rec *rr = ap_sub_req_lookup_uri(s, r);
-    int rr_status;
-
-    if (rr->status != HTTP_OK) {
-        return -1;
-    }
-
-    /* No hardwired path info or query allowed */
-
-    if ((rr->path_info && rr->path_info[0]) || rr->args) {
-        return -1;
-    }
-    if (rr->finfo.protection == 0) {
-        return -1;
-    }
-
-    /* Script gets parameters of the *document*, for back compatibility */
-
-    rr->path_info = r->path_info;       /* hard to get right; see mod_cgi.c */
-    rr->args = r->args;
-
-    /* Force sub_req to be treated as a CGI request, even if ordinary
-     * typing rules would have called it something else.
-     */
-
-    rr->content_type = CGI_MAGIC_TYPE;
-
-    /* Run it. */
-
-    rr_status = ap_run_sub_req(rr);
-    if (ap_is_HTTP_REDIRECT(rr_status)) {
-        const char *location = ap_table_get(rr->headers_out, "Location");
-        location = ap_escape_html(rr->pool, location);
-        ap_rvputs(r, "<A HREF=\"", location, "\">", location, "</A>", NULL);
-    }
-
-    ap_destroy_sub_req(rr);
-    ap_chdir_file(r->filename);
-
-    return 0;
-}
-
 /* ensure that path is relative, and does not contain ".." elements
  * ensentially ensure that it does not match the regex:
  * (^/|(^|/)\.\.(/|$))
- * XXX: this needs os abstraction... consider c:..\foo in win32
+ * XXX: Needs to become apr_is_path_relative() test
  */
 static int is_only_below(const char *path)
 {
 #ifdef HAVE_DRIVE_LETTERS
-    if (path[1] == ':')
+    if (path[1] == ':') 
        return 0;
 #endif
-    if (path[0] == '/') {
+#ifdef NETWARE
+    if (strchr(path, ':')
        return 0;
-    }
-    if (path[0] == '.' && path[1] == '.'
-       && (path[2] == '\0' || path[2] == '/')) {
+#endif
+    if (path[0] == '/') {
        return 0;
     }
     while (*path) {
-       if (*path == '/' && path[1] == '.' && path[2] == '.'
-           && (path[3] == '\0' || path[3] == '/')) {
-           return 0;
-       }
-       ++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;
+        while (*path && *(path++) != '/')
+            ++path;
     }
     return 1;
 }
 
-static int handle_include(ap_file_t *in, request_rec *r, const char *error, int noexec)
+static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                          ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_bucket  *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
-
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
-            request_rec *rr = NULL;
-            char *error_fmt = NULL;
 
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            if (tag[0] == 'f') {
-                /* 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";
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return (0);
                 }
                 else {
-                    rr = ap_sub_req_lookup_file(parsed_string, r);
+                    return (1);
                 }
             }
-            else {
-                rr = ap_sub_req_lookup_uri(parsed_string, r);
-            }
-
-            if (!error_fmt && rr->status != HTTP_OK) {
-                error_fmt = "unable to include \"%s\" in parsed file %s";
-            }
-
-            if (!error_fmt && noexec && 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 ( (strcmp(q->filename, rr->filename) == 0) ||
-                            (strcmp(q->uri, rr->uri) == 0) ){
-                           founddupe = 1;
-                           break;
-                       }
-                   }
-               }
+            if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
+                request_rec *rr = NULL;
+                char *error_fmt = NULL;
+
+                ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+                if (tag[0] == 'f') {
+                    /* 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_file(parsed_string, r, f->next);
+                    }
+                }
+                else {
+                    rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
+                }
+
+                if (!error_fmt && rr->status != HTTP_OK) {
+                    error_fmt = "unable to include \"%s\" in parsed file %s";
+                }
 
-                if (p != NULL) {
-                    error_fmt = "Recursive include of \"%s\" "
+                if (!error_fmt && (ctx->flags & 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)) ||
+                            (strcmp(q->uri, rr->uri) == 0)) {
+                           founddupe = 1;
+                           break;
+                       }
+                   }
+               }
+
+                    if (p != NULL) {
+                        error_fmt = "Recursive include of \"%s\" "
+                            "in parsed file %s";
+                    }
+                }
 
-           /* see the Kludge in send_parsed_file for why */
-           if (rr) 
-               ap_set_module_config(rr->request_config, &includes_module, r);
+           /* 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) {
+                    SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
+                    
+                    if (ap_run_sub_req(rr)) {
+                        error_fmt = "unable to include \"%s\" in parsed file %s";
+                    }
+                }
+                if (error_fmt) {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
+                           0, r, error_fmt, tag_val, r->filename);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                }
 
-            if (!error_fmt && ap_run_sub_req(rr)) {
-                error_fmt = "unable to include \"%s\" in parsed file %s";
-            }
-            ap_chdir_file(r->filename);
-            if (error_fmt) {
-                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
-                           0, r, error_fmt, tag_val, r->filename);
-                ap_rputs(error, r);
+           /* destroy the sub request if it's not a nested include (crumb) */
+                if (rr != NULL
+               && ap_get_module_config(rr->request_config, &include_module)
+                   != NESTED_INCLUDE_MAGIC) {
+               ap_destroy_sub_req(rr);
+                }
             }
-
-           /* destroy the sub request if it's not a nested include */
-            if (rr != NULL
-               && ap_get_module_config(rr->request_config, &includes_module)
-                   != NESTED_INCLUDE_MAGIC) {
-               ap_destroy_sub_req(rr);
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" to tag include in %s",
+                            tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
             }
         }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag include in %s",
-                        tag, r->filename);
-            ap_rputs(error, r);
-        }
     }
+    return 0;
 }
 
-typedef struct {
-#ifdef TPF
-    TPF_FORK_CHILD t;
-#endif
-    request_rec *r;
-    char *s;
-} include_cmd_arg;
 
+static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                       ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
+{
+    char       *tag       = NULL;
+    char       *tag_val   = NULL;
+    const char *echo_text = NULL;
+    apr_bucket  *tmp_buck;
+    apr_size_t e_len, e_wrt;
+    enum {E_NONE, E_URL, E_ENTITY} encode;
 
+    encode = E_ENTITY;
 
-static ap_status_t build_argv_list(char ***argv, request_rec *r, ap_pool_t *p)
-{
-    int numwords, x, idx;
-    char *w;
-    const char *args = r->args;
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag != NULL) {
+                    return 1;
+                }
+                else {
+                    return 0;
+                }
+            }
+            if (!strcmp(tag, "var")) {
+                const char *val = apr_table_get(r->subprocess_env, tag_val);
 
-    if (!args || !args[0] || ap_strchr_c(args, '=')) {
-       numwords = 1;
-    }
-    else {
-        /* count the number of keywords */
-        for (x = 0, numwords = 1; args[x]; x++) {
-            if (args[x] == '+') {
-                ++numwords;
+                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_heap_create(echo_text, e_len, 1, &e_wrt);
+                }
+                else {
+                    tmp_buck = apr_bucket_immortal_create("(none)", sizeof("(none)")-1);
+                }
+                APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                if (*inserted_head == NULL) {
+                    *inserted_head = 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 {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                  "unknown value \"%s\" to parameter \"encoding\" of "
+                                  "tag echo in %s", tag_val, r->filename);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                }
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" in tag echo of %s",
+                            tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
             }
+
         }
     }
-    /* Everything is - 1 to account for the first parameter which is the
-     * program name.  We didn't used to have to do this, but APR wants it.
-     */
-    if (numwords > APACHE_ARG_MAX - 1) {
-        numwords = APACHE_ARG_MAX - 1; /* Truncate args to prevent overrun */
-    }
-    *argv = (char **) ap_palloc(p, (numwords + 2) * sizeof(char *));
-    for (x = 1, idx = 1; x < numwords; x++) {
-        w = ap_getword_nulls(p, &args, '+');
-        ap_unescape_url(w);
-        (*argv)[idx++] = ap_escape_shell_cmd(p, w);
-    }
-    (*argv)[idx] = NULL;
-
-    return APR_SUCCESS;
+    return 0;
 }
 
-
-
-static int include_cmd(char *s, request_rec *r)
+/* error and tf must point to a string with room for at 
+ * least MAX_STRING_LEN characters 
+ */
+static int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                         ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    include_cmd_arg arg;
-    BUFF *script_in;
-    ap_procattr_t *procattr;
-    ap_proc_t procnew;
-    ap_status_t rc;
-    ap_table_t *env = r->subprocess_env;
-    char **argv;
-    ap_file_t *file = NULL;
-    ap_iol *iol;
-#if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
-    defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
-    core_dir_config *conf; 
-    conf = (core_dir_config *) ap_get_module_config(r->per_dir_config,
-                                                    &core_module);
-#endif
-
-    arg.r = r;
-    arg.s = s;
-#ifdef TPF
-    arg.t.filename = r->filename;
-    arg.t.subprocess_env = r->subprocess_env;
-    arg.t.prog_type = FORK_FILE;
-#endif
-
-    if (r->path_info && r->path_info[0] != '\0') {
-        request_rec *pa_req;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char parsed_string[MAX_STRING_LEN];
+    apr_table_t *env = r->subprocess_env;
+
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return 0;  /* Reached the end of the string. */
+                }
+                else {
+                    return 1;  /* tags must have values. */
+                }
+            }
+            if (!strcmp(tag, "errmsg")) {
+                ap_ssi_parse_string(r, tag_val, ctx->error_str, MAX_STRING_LEN, 0);
+                ctx->error_length = strlen(ctx->error_str);
+            }
+            else if (!strcmp(tag, "timefmt")) {
+                apr_time_t date = r->request_time;
 
-        ap_table_setn(env, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
+                ap_ssi_parse_string(r, tag_val, ctx->time_str, MAX_STRING_LEN, 0);
+                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 (!strcmp(tag, "sizefmt")) {
+                ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+                decodehtml(parsed_string);
+                if (!strcmp(parsed_string, "bytes")) {
+                    ctx->flags |= FLAG_SIZE_IN_BYTES;
+                }
+                else if (!strcmp(parsed_string, "abbrev")) {
+                    ctx->flags &= FLAG_SIZE_ABBREV;
+                }
+            }
+            else {
+                apr_bucket  *tmp_buck;
 
-        pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r);
-        if (pa_req->filename) {
-            ap_table_setn(env, "PATH_TRANSLATED",
-                      ap_pstrcat(r->pool, pa_req->filename, pa_req->path_info,
-                              NULL));
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" to tag config in %s",
+                            tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            }
         }
     }
-
-    if (r->args) {
-        char *arg_copy = ap_pstrdup(r->pool, r->args);
-
-        ap_table_setn(env, "QUERY_STRING", r->args);
-        ap_unescape_url(arg_copy);
-        ap_table_setn(env, "QUERY_STRING_UNESCAPED",
-                  ap_escape_shell_cmd(r->pool, arg_copy));
-    }
-
-    if ((ap_createprocattr_init(&procattr, r->pool) != APR_SUCCESS) ||
-        (ap_setprocattr_io(procattr, APR_NO_PIPE, 
-                           APR_FULL_BLOCK, APR_NO_PIPE) != APR_SUCCESS) ||
-        (ap_setprocattr_dir(procattr, ap_make_dirstr_parent(r->pool, r->filename)) != APR_SUCCESS) ||
-#ifdef RLIMIT_CPU
-        ((rc = ap_setprocattr_limit(procattr, APR_LIMIT_CPU, conf->limit_cpu)) != APR_SUCCESS) ||
-#endif
-#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
-        ((rc = ap_setprocattr_limit(procattr, APR_LIMIT_MEM, conf->limit_mem)) != APR_SUCCESS) ||
-#endif
-#ifdef RLIMIT_NPROC
-        ((rc = ap_setprocattr_limit(procattr, APR_LIMIT_NPROC, conf->limit_nproc)) != APR_SUCCESS) ||
-#endif
-        (ap_setprocattr_cmdtype(procattr, APR_SHELLCMD) != APR_SUCCESS)) {
-        /* Something bad happened, tell the world. */
-       ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
-            "couldn't initialize proc attributes: %s %s", r->filename, s);
-        rc = !APR_SUCCESS;
-    }
-    else {
-        build_argv_list(&argv, r, r->pool);
-        argv[0] = ap_pstrdup(r->pool, s);
-        rc = ap_create_process(&procnew, s, argv, ap_create_environment(r->pool, env), procattr, r->pool);
-
-        if (rc != APR_SUCCESS) {
-            /* Bad things happened. Everyone should have cleaned up. */
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
-                        "couldn't create child process: %d: %s", rc, s);
-        }
-        else {
-            ap_note_subprocess(r->pool, &procnew, kill_after_timeout);
-            /* Fill in BUFF structure for parents pipe to child's stdout */
-            file = procnew.out;
-            iol = ap_create_file_iol(file);
-            if (!iol)
-                return APR_EBADF;
-            script_in = ap_bcreate(r->pool, B_RD);
-            ap_bpush_iol(script_in, iol);
-            ap_send_fb(script_in, r);
-            ap_bclose(script_in);
-        }
-    }
-
-    return 0;
-}
-
-static int handle_exec(ap_file_t *in, request_rec *r, const char *error)
-{
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char *file = r->filename;
-    char parsed_string[MAX_STRING_LEN];
-
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        if (!strcmp(tag, "cmd")) {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
-            if (include_cmd(parsed_string, r) == -1) {
-                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                            "execution failure for parameter \"%s\" "
-                            "to tag exec in file %s",
-                            tag, r->filename);
-                ap_rputs(error, r);
-            }
-            /* just in case some stooge changed directories */
-            ap_chdir_file(r->filename);
-        }
-        else if (!strcmp(tag, "cgi")) {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            if (include_cgi(parsed_string, r) == -1) {
-                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                            "invalid CGI ref \"%s\" in %s", tag_val, file);
-                ap_rputs(error, r);
-            }
-            ap_chdir_file(r->filename);
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag exec in %s",
-                        tag, file);
-            ap_rputs(error, r);
-        }
-    }
-
-}
-
-static int handle_echo(ap_file_t *in, request_rec *r, const char *error)
-{
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    enum {E_NONE, E_URL, E_ENTITY} encode;
-
-    encode = E_ENTITY;
-
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        if (!strcmp(tag, "var")) {
-            const char *val = ap_table_get(r->subprocess_env, tag_val);
-
-            if (val) {
-               if (encode == E_NONE) {
-                   ap_rputs(val, r);
-               }
-               else if (encode == E_URL) {
-                   ap_rputs(ap_escape_uri(r->pool, val), r);
-               }
-               else if (encode == E_ENTITY) {
-                   ap_rputs(ap_escape_html(r->pool, val), r);
-               }
-            }
-            else {
-                ap_rputs("(none)", r);
-            }
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-       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_NOERRNO|APLOG_ERR, 0, r,
-                           "unknown value \"%s\" to parameter \"encoding\" of "
-                           "tag echo in %s",
-                           tag_val, r->filename);
-               ap_rputs(error, r);
-           }
-       }
-
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag echo in %s",
-                        tag, r->filename);
-            ap_rputs(error, r);
-        }
-    }
-}
-
-#ifdef USE_PERL_SSI
-static int handle_perl(ap_file_t *in, request_rec *r, const char *error)
-{
-    char tag[MAX_STRING_LEN];
-    char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
-    SV *sub = Nullsv;
-    AV *av = newAV();
-
-    if (ap_allow_options(r) & OPT_INCNOEXEC) {
-        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                     "#perl SSI disallowed by IncludesNoExec in %s",
-                     r->filename);
-        return DECLINED;
-    }
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            break;
-        }
-        if (strnEQ(tag, "sub", 3)) {
-            sub = newSVpv(tag_val, 0);
-        }
-        else if (strnEQ(tag, "arg", 3)) {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            av_push(av, newSVpv(parsed_string, 0));
-        }
-        else if (strnEQ(tag, "done", 4)) {
-            break;
-        }
-    }
-    perl_stdout2client(r);
-    perl_setup_env(r);
-    perl_call_handler(sub, r, av);
-    return OK;
-}
-#endif
-
-/* error and tf must point to a string with room for at 
- * least MAX_STRING_LEN characters 
- */
-static int handle_config(ap_file_t *in, request_rec *r, char *error, char *tf,
-                         int *sizefmt)
-{
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char parsed_string[MAX_STRING_LEN];
-    ap_table_t *env = r->subprocess_env;
-
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0))) {
-            return 1;
-        }
-        if (!strcmp(tag, "errmsg")) {
-            parse_string(r, tag_val, error, MAX_STRING_LEN, 0);
-        }
-        else if (!strcmp(tag, "timefmt")) {
-            ap_time_t date = r->request_time;
-
-            parse_string(r, tag_val, tf, MAX_STRING_LEN, 0);
-            ap_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, tf, 0));
-            ap_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, tf, 1));
-            ap_table_setn(env, "LAST_MODIFIED",
-                      ap_ht_time(r->pool, r->finfo.mtime, tf, 0));
-        }
-        else if (!strcmp(tag, "sizefmt")) {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            decodehtml(parsed_string);
-            if (!strcmp(parsed_string, "bytes")) {
-                *sizefmt = SIZEFMT_BYTES;
-            }
-            else if (!strcmp(parsed_string, "abbrev")) {
-                *sizefmt = SIZEFMT_KMG;
-            }
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag config in %s",
-                        tag, r->filename);
-            ap_rputs(error, r);
-        }
-    }
-}
+    return 0;
+}
 
 
 static int find_file(request_rec *r, const char *directive, const char *tag,
-                     char *tag_val, ap_finfo_t *finfo, const char *error)
+                     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")) {
         /* be safe; only files in this directory or below allowed */
@@ -1122,11 +1029,16 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
         }
         else {
             ap_getparents(tag_val);    /* get rid of any nasties */
-            rr = ap_sub_req_lookup_file(tag_val, r);
 
-            if (rr->status == HTTP_OK && rr->finfo.protection != 0) {
+            /* 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 (ap_stat(finfo, to_send, rr->pool) != APR_SUCCESS) {
+                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";
                 }
@@ -1139,8 +1051,8 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
 
         if (error_fmt) {
             ret = -1;
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, to_send, r->filename);
-            ap_rputs(error, r);
+            ap_log_rerror(APLOG_MARK, APLOG_ERR | (rv ? 0 : APLOG_NOERRNO),
+                          rv, r, error_fmt, to_send, r->filename);
         }
 
         if (rr) ap_destroy_sub_req(rr);
@@ -1148,9 +1060,11 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
         return ret;
     }
     else if (!strcmp(tag, "virtual")) {
-        rr = ap_sub_req_lookup_uri(tag_val, r);
+        /* 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.protection != 0) {
+        if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
             memcpy((char *) finfo, (const char *) &rr->finfo,
                    sizeof(rr->finfo));
             ap_destroy_sub_req(rr);
@@ -1161,7 +1075,6 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
                         "unable to get information about \"%s\" "
                         "in parsed file %s",
                         tag_val, r->filename);
-            ap_rputs(error, r);
             ap_destroy_sub_req(rr);
             return -1;
         }
@@ -1170,69 +1083,115 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                     "unknown parameter \"%s\" to tag %s in %s",
                     tag, directive, r->filename);
-        ap_rputs(error, r);
         return -1;
     }
 }
 
-
-static int handle_fsize(ap_file_t *in, request_rec *r, const char *error, int sizefmt)
+static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                        ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    ap_finfo_t finfo;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_size_t  s_len, s_wrt;
+    apr_bucket   *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            if (!find_file(r, "fsize", tag, parsed_string, &finfo, error)) {
-                if (sizefmt == SIZEFMT_KMG) {
-                    ap_send_size(finfo.size, r);
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return 0;
                 }
                 else {
-                    int l, x;
-                    ap_snprintf(tag, sizeof(tag), "%" APR_OFF_T_FMT, finfo.size);
-                    l = strlen(tag);    /* grrr */
-                    for (x = 0; x < l; x++) {
-                        if (x && (!((l - x) % 3))) {
-                            ap_rputc(',', r);
+                    return 1;
+                }
+            }
+            else {
+                ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+                if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
+                    char buff[50];
+
+                    if (!(ctx->flags & 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];
                         }
-                        ap_rputc(tag[x], r);
+                        buff[pos] = '\0';
+                        s_len = pos;
+                    }
+
+                    tmp_buck = apr_bucket_heap_create(buff, s_len, 1, &s_wrt);
+                    APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                    if (*inserted_head == NULL) {
+                        *inserted_head = tmp_buck;
                     }
                 }
+                else {
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                }
             }
         }
     }
+    return 0;
 }
 
-static int handle_flastmod(ap_file_t *in, request_rec *r, const char *error, const char *tf)
+static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                           ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    ap_finfo_t finfo;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_size_t  t_len, t_wrt;
+    apr_bucket   *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            if (!find_file(r, "flastmod", tag, parsed_string, &finfo, error)) {
-                ap_rputs(ap_ht_time(r->pool, finfo.mtime, tf, 0), r);
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return 0;
+                }
+                else {
+                    return 1;
+                }
+            }
+            else {
+                ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+                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_heap_create(t_val, t_len, 1, &t_wrt);
+                    APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                    if (*inserted_head == NULL) {
+                        *inserted_head = tmp_buck;
+                    }
+                }
+                else {
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                }
             }
         }
     }
+    return 0;
 }
 
 static int re_check(request_rec *r, char *string, char *rexp)
@@ -1265,18 +1224,20 @@ struct token {
 /* there is an implicit assumption here that string is at most MAX_STRING_LEN-1
  * characters long...
  */
-static const char *get_ptoken(request_rec *r, const char *string, struct token *token)
+static const char *get_ptoken(request_rec *r, const char *string, struct token *token,
+                              int *unmatched)
 {
     char ch;
     int next = 0;
     int qs = 0;
+    int tkn_fnd = 0;
 
     /* Skip leading white space */
     if (string == (char *) NULL) {
         return (char *) NULL;
     }
     while ((ch = *string++)) {
-        if (!ap_isspace(ch)) {
+        if (!apr_isspace(ch)) {
             break;
         }
     }
@@ -1356,57 +1317,62 @@ static const char *get_ptoken(request_rec *r, const char *string, struct token *
      * 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'; ch = *++string) {
+    for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
         if (ch == '\\') {
             if ((ch = *++string) == '\0') {
-                goto TOKEN_DONE;
+                tkn_fnd = 1;
+            }
+            else {
+                token->value[next++] = ch;
             }
-            token->value[next++] = ch;
-            continue;
         }
-        if (!qs) {
-            if (ap_isspace(ch)) {
-                goto TOKEN_DONE;
-            }
-            switch (ch) {
-            case '(':
-                goto TOKEN_DONE;
-            case ')':
-                goto TOKEN_DONE;
-            case '=':
-                goto TOKEN_DONE;
-            case '!':
-                goto TOKEN_DONE;
-            case '|':
-                if (*(string + 1) == '|') {
-                    goto TOKEN_DONE;
+        else {
+            if (!qs) {
+                if (apr_isspace(ch)) {
+                    tkn_fnd = 1;
                 }
-                break;
-            case '&':
-                if (*(string + 1) == '&') {
-                    goto TOKEN_DONE;
+                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;
+                    }
                 }
-                break;
-            case '<':
-                goto TOKEN_DONE;
-            case '>':
-                goto TOKEN_DONE;
             }
-            token->value[next++] = ch;
-        }
-        else {
-            if (ch == '\'') {
-                qs = 0;
-                ++string;
-                goto TOKEN_DONE;
+            else {
+                if (ch == '\'') {
+                    qs = 0;
+                    ++string;
+                    tkn_fnd = 1;
+                }
+                else {
+                    token->value[next++] = ch;
+                }
             }
-            token->value[next++] = ch;
         }
     }
-  TOKEN_DONE:
+
     /* If qs is still set, I have an unmatched ' */
     if (qs) {
-        ap_rputs("\nUnmatched '\n", r);
+        *unmatched = 1;
         next = 0;
     }
     token->value[next] = '\0';
@@ -1424,7 +1390,8 @@ static const char *get_ptoken(request_rec *r, const char *string, struct token *
 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
  * characters long...
  */
-static int parse_expr(request_rec *r, const char *expr, const char *error)
+static int parse_expr(request_rec *r, const char *expr, int *was_error, 
+                      int *was_unmatched, char *debug)
 {
     struct parse_node {
         struct parse_node *left, *right, *parent;
@@ -1433,30 +1400,35 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
     }         *root, *current, *new;
     const char *parse;
     char buffer[MAX_STRING_LEN];
-    ap_pool_t *expr_pool;
+    apr_pool_t *expr_pool;
     int retval = 0;
+    apr_size_t debug_pos = 0;
 
+    debug[debug_pos] = '\0';
+    *was_error       = 0;
+    *was_unmatched   = 0;
     if ((parse = expr) == (char *) NULL) {
         return (0);
     }
     root = current = (struct parse_node *) NULL;
-    if (ap_create_pool(&expr_pool, r->pool) != APR_SUCCESS)
+    if (apr_pool_create(&expr_pool, r->pool) != APR_SUCCESS)
                return 0;
 
     /* Create Parse Tree */
     while (1) {
-        new = (struct parse_node *) ap_palloc(expr_pool,
+        new = (struct parse_node *) apr_palloc(expr_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)) == (char *) NULL) {
+        if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == (char *) NULL) {
             break;
         }
         switch (new->token.type) {
 
         case token_string:
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Token: string (", new->token.value, ")\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Token: string (%s)\n",
+                                  new->token.value);
 #endif
             if (current == (struct parse_node *) NULL) {
                 root = current = new;
@@ -1491,7 +1463,7 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
             break;
@@ -1499,13 +1471,15 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         case token_and:
         case token_or:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Token: and/or\n", r);
+            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_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
             /* Percolate upwards */
@@ -1530,7 +1504,7 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                                 "Invalid expression \"%s\" in file %s",
                                 expr, r->filename);
-                    ap_rputs(error, r);
+                    *was_error = 1;
                     goto RETURN;
                 }
                 break;
@@ -1551,7 +1525,9 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
 
         case token_not:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Token: not\n", r);
+            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;
@@ -1575,7 +1551,7 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                                 "Invalid expression \"%s\" in file %s",
                                 expr, r->filename);
-                    ap_rputs(error, r);
+                    *was_error = 1;
                     goto RETURN;
                 }
                 break;
@@ -1601,13 +1577,15 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         case token_le:
         case token_lt:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Token: eq/ne/ge/gt/le/lt\n", r);
+            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_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
             /* Percolate upwards */
@@ -1632,7 +1610,7 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                                 "Invalid expression \"%s\" in file %s",
                                 expr, r->filename);
-                    ap_rputs(error, r);
+                    *was_error = 1;
                     goto RETURN;
                 }
                 break;
@@ -1653,7 +1631,9 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
 
         case token_rbrace:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Token: rbrace\n", r);
+            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) {
@@ -1666,14 +1646,16 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                             "Unmatched ')' in \"%s\" in file %s",
                            expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
             break;
 
         case token_lbrace:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Token: lbrace\n", r);
+            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;
@@ -1699,7 +1681,7 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                                 "Invalid expression \"%s\" in file %s",
                                 expr, r->filename);
-                    ap_rputs(error, r);
+                    *was_error = 1;
                     goto RETURN;
                 }
                 break;
@@ -1728,10 +1710,12 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         switch (current->token.type) {
         case token_string:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Evaluate string\n", r);
+            memcpy (&debug[debug_pos], "     Evaluate string\n",
+                    sizeof ("     Evaluate string\n"));
+            debug_pos += sizeof ("     Evaluate string\n");
 #endif
-            parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
-           ap_cpystrn(current->token.value, buffer, sizeof(current->token.value));
+            ap_ssi_parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
+           apr_cpystrn(current->token.value, buffer, sizeof(current->token.value));
             current->value = (current->token.value[0] != '\0');
             current->done = 1;
             current = current->parent;
@@ -1740,22 +1724,24 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         case token_and:
         case token_or:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Evaluate and/or\n", r);
+            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_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
             if (!current->left->done) {
                 switch (current->left->token.type) {
                 case token_string:
-                    parse_string(r, current->left->token.value,
+                    ap_ssi_parse_string(r, current->left->token.value,
                                  buffer, sizeof(buffer), 0);
-                    ap_cpystrn(current->left->token.value, buffer,
+                    apr_cpystrn(current->left->token.value, buffer,
                             sizeof(current->left->token.value));
                    current->left->value = (current->left->token.value[0] != '\0');
                     current->left->done = 1;
@@ -1768,9 +1754,9 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
             if (!current->right->done) {
                 switch (current->right->token.type) {
                 case token_string:
-                    parse_string(r, current->right->token.value,
+                    ap_ssi_parse_string(r, current->right->token.value,
                                  buffer, sizeof(buffer), 0);
-                    ap_cpystrn(current->right->token.value, buffer,
+                    apr_cpystrn(current->right->token.value, buffer,
                             sizeof(current->right->token.value));
                    current->right->value = (current->right->token.value[0] != '\0');
                     current->right->done = 1;
@@ -1781,10 +1767,10 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 }
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Left: ", current->left->value ? "1" : "0",
-                   "\n", NULL);
-            ap_rvputs(r, "     Right: ", current->right->value ? "1" : "0",
-                   "\n", NULL);
+            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;
@@ -1793,8 +1779,8 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 current->value = current->left->value || current->right->value;
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
-                   "\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
 #endif
             current->done = 1;
             current = current->parent;
@@ -1803,7 +1789,9 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         case token_eq:
         case token_ne:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Evaluate eq/ne\n", r);
+            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) ||
@@ -1812,16 +1800,16 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
-            parse_string(r, current->left->token.value,
+            ap_ssi_parse_string(r, current->left->token.value,
                          buffer, sizeof(buffer), 0);
-            ap_cpystrn(current->left->token.value, buffer,
+            apr_cpystrn(current->left->token.value, buffer,
                        sizeof(current->left->token.value));
-            parse_string(r, current->right->token.value,
+            ap_ssi_parse_string(r, current->right->token.value,
                          buffer, sizeof(buffer), 0);
-            ap_cpystrn(current->right->token.value, buffer,
+            apr_cpystrn(current->right->token.value, buffer,
                        sizeof(current->right->token.value));
             if (current->right->token.value[0] == '/') {
                 int len;
@@ -1833,12 +1821,14 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                                 "Invalid rexp \"%s\" in file %s",
                                 current->right->token.value, r->filename);
-                    ap_rputs(error, r);
+                    *was_error = 1;
                     goto RETURN;
                 }
 #ifdef DEBUG_INCLUDE
-                ap_rvputs(r, "     Re Compare (", current->left->token.value,
-                  ") with /", &current->right->token.value[1], "/\n", NULL);
+                debug_pos += sprintf (&debug[debug_pos],
+                                      "     Re Compare (%s) with /%s/\n",
+                                      current->left->token.value,
+                                      &current->right->token.value[1]);
 #endif
                 current->value =
                     re_check(r, current->left->token.value,
@@ -1846,8 +1836,10 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
             }
             else {
 #ifdef DEBUG_INCLUDE
-                ap_rvputs(r, "     Compare (", current->left->token.value,
-                       ") with (", current->right->token.value, ")\n", NULL);
+                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,
@@ -1857,8 +1849,8 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 current->value = !current->value;
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
-                   "\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
 #endif
             current->done = 1;
             current = current->parent;
@@ -1868,7 +1860,9 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
         case token_le:
         case token_lt:
 #ifdef DEBUG_INCLUDE
-            ap_rputs("     Evaluate ge/gt/le/lt\n", r);
+            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) ||
@@ -1877,20 +1871,22 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                             "Invalid expression \"%s\" in file %s",
                             expr, r->filename);
-                ap_rputs(error, r);
+                *was_error = 1;
                 goto RETURN;
             }
-            parse_string(r, current->left->token.value,
+            ap_ssi_parse_string(r, current->left->token.value,
                          buffer, sizeof(buffer), 0);
-            ap_cpystrn(current->left->token.value, buffer,
+            apr_cpystrn(current->left->token.value, buffer,
                        sizeof(current->left->token.value));
-            parse_string(r, current->right->token.value,
+            ap_ssi_parse_string(r, current->right->token.value,
                          buffer, sizeof(buffer), 0);
-            ap_cpystrn(current->right->token.value, buffer,
+            apr_cpystrn(current->right->token.value, buffer,
                        sizeof(current->right->token.value));
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Compare (", current->left->token.value,
-                   ") with (", current->right->token.value, ")\n", NULL);
+            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,
@@ -1911,8 +1907,8 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 current->value = 0;     /* Don't return -1 if unknown token */
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
-                   "\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
+                                  current->value ? '1' : '0');
 #endif
             current->done = 1;
             current = current->parent;
@@ -1930,8 +1926,8 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 current->value = 0;
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Evaluate !: ", current->value ? "1" : "0",
-                   "\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
+                                  current->value ? '1' : '0');
 #endif
             current->done = 1;
             current = current->parent;
@@ -1949,8 +1945,8 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                 current->value = 1;
             }
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "     Evaluate (): ", current->value ? "1" : "0",
-                   "\n", NULL);
+            debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
+                                  current->value ? '1' : '0');
 #endif
             current->done = 1;
             current = current->parent;
@@ -1960,385 +1956,667 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                         "Unmatched '(' in \"%s\" in file %s",
                         expr, r->filename);
-            ap_rputs(error, r);
+            *was_error = 1;
             goto RETURN;
 
         case token_rbrace:
             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
                         "Unmatched ')' in \"%s\" in file %s",
                         expr, r->filename);
-            ap_rputs(error, r);
+            *was_error = 1;
             goto RETURN;
 
         default:
             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                       "bad token type");
-            ap_rputs(error, r);
+                          "bad token type");
+            *was_error = 1;
             goto RETURN;
         }
     }
 
     retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
   RETURN:
-    ap_destroy_pool(expr_pool);
+    apr_pool_destroy(expr_pool);
     return (retval);
 }
 
-static int handle_if(ap_file_t *in, request_rec *r, const char *error,
-                     int *conditional_status, int *printing)
-{
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char *expr;
-
-    expr = NULL;
-    while (1) {
-        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
-        if (*tag == '\0') {
-            return 1;
-        }
-        else if (!strcmp(tag, "done")) {
-           if (expr == NULL) {
-               ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                           "missing expr in if statement: %s",
-                           r->filename);
-               ap_rputs(error, r);
-               return 1;
-           }
-            *printing = *conditional_status = parse_expr(r, expr, error);
+/*-------------------------------------------------------------------------*/
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** if conditional_status=\"",
-                   *conditional_status ? "1" : "0", "\"\n", NULL);
+
+/* XXX overlaying the static string pointed to by cond_txt isn't cool */
+
+#define MAX_DEBUG_SIZE MAX_STRING_LEN
+#define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)           \
+{                                                                          \
+    char *cond_txt = "**** X     conditional_status=\"0\"\n";              \
+    apr_size_t c_wrt;                                                      \
+                                                                           \
+    if (cntx->flags & FLAG_COND_TRUE) {                                    \
+        cond_txt[31] = '1';                                                \
+    }                                                                      \
+    memcpy(&cond_txt[5], tag_text, sizeof(tag_text));                      \
+    t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt), 1, &c_wrt); \
+    APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                                \
+                                                                           \
+    if (ins_head == NULL) {                                                \
+        ins_head = t_buck;                                                 \
+    }                                                                      \
+}
+#define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)            \
+{                                                                        \
+    apr_size_t b_wrt;                                                    \
+    if (d_buf[0] != '\0') {                                              \
+        t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf), 1, &b_wrt); \
+        APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                          \
+                                                                         \
+        if (ins_head == NULL) {                                          \
+            ins_head = t_buck;                                           \
+        }                                                                \
+    }                                                                    \
+}
+#else
+
+#define MAX_DEBUG_SIZE 10
+#define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)
+#define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)
+
 #endif
-            return 0;
-        }
-        else if (!strcmp(tag, "expr")) {
-            expr = tag_val;
+/*-------------------------------------------------------------------------*/
+
+/* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
+static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                     ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
+{
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *expr    = NULL;
+    int   expr_ret, was_error, was_unmatched;
+    apr_bucket *tmp_buck;
+    char debug_buf[MAX_DEBUG_SIZE];
+
+    *inserted_head = NULL;
+    if (!ctx->flags & FLAG_PRINTING) {
+        ctx->if_nesting_level++;
+    }
+    else {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
+            if (tag == NULL) {
+                if (expr == NULL) {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                  "missing expr in if statement: %s", r->filename);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    return 1;
+                }
+                expr_ret = parse_expr(r, expr, &was_error, &was_unmatched, debug_buf);
+                if (was_error) {
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    return 1;
+                }
+                if (was_unmatched) {
+                    DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, "\nUnmatched '\n",
+                                          *inserted_head);
+                }
+                DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, *inserted_head);
+                
+                if (expr_ret) {
+                    ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
+                }
+                else {
+                    ctx->flags &= FLAG_CLEAR_PRINT_COND;
+                }
+                LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "   if");
+                ctx->if_nesting_level = 0;
+                return 0;
+            }
+            else if (!strcmp(tag, "expr")) {
+                expr = tag_val;
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
+                if (1) {
+                    apr_size_t d_len = 0, d_wrt = 0;
+                    d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
+                    tmp_buck = apr_bucket_heap_create(debug_buf, d_len, 1, &d_wrt);
+                    APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+
+                    if (*inserted_head == NULL) {
+                        *inserted_head = tmp_buck;
+                    }
+                }
 #endif
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag if in %s",
-                        tag, r->filename);
-            ap_rputs(error, r);
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" to tag if in %s", tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            }
+
         }
     }
+    return 0;
 }
 
-static int handle_elif(ap_file_t *in, request_rec *r, const char *error,
-                       int *conditional_status, int *printing)
+static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                       ap_filter_t *f,  apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char *expr;
-
-    expr = NULL;
-    while (1) {
-        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
-        if (*tag == '\0') {
-            return 1;
-        }
-        else if (!strcmp(tag, "done")) {
-#ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** elif conditional_status=\"",
-                   *conditional_status ? "1" : "0", "\"\n", NULL);
-#endif
-            if (*conditional_status) {
-                *printing = 0;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *expr    = NULL;
+    int   expr_ret, was_error, was_unmatched;
+    apr_bucket *tmp_buck;
+    char debug_buf[MAX_DEBUG_SIZE];
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
+            if (tag == '\0') {
+                LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " elif");
+                
+                if (ctx->flags & FLAG_COND_TRUE) {
+                    ctx->flags &= FLAG_CLEAR_PRINTING;
+                    return (0);
+                }
+                if (expr == NULL) {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                  "missing expr in elif statement: %s", r->filename);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    return (1);
+                }
+                expr_ret = parse_expr(r, expr, &was_error, &was_unmatched, debug_buf);
+                if (was_error) {
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    return 1;
+                }
+                if (was_unmatched) {
+                    DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, "\nUnmatched '\n",
+                                          *inserted_head);
+                }
+                DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, *inserted_head);
+                
+                if (expr_ret) {
+                    ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
+                }
+                else {
+                    ctx->flags &= FLAG_CLEAR_PRINT_COND;
+                }
+                LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " elif");
                 return (0);
             }
-           if (expr == NULL) {
-               ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                           "missing expr in elif statement: %s",
-                           r->filename);
-               ap_rputs(error, r);
-               return 1;
-           }
-            *printing = *conditional_status = parse_expr(r, expr, error);
+            else if (!strcmp(tag, "expr")) {
+                expr = tag_val;
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** elif conditional_status=\"",
-                   *conditional_status ? "1" : "0", "\"\n", NULL);
-#endif
-            return 0;
-        }
-        else if (!strcmp(tag, "expr")) {
-            expr = tag_val;
-#ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
+                if (1) {
+                    apr_size_t d_len = 0, d_wrt = 0;
+                    d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
+                    tmp_buck = apr_bucket_heap_create(debug_buf, d_len, 1, &d_wrt);
+                    APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+
+                    if (*inserted_head == NULL) {
+                        *inserted_head = tmp_buck;
+                    }
+                }
 #endif
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "unknown parameter \"%s\" to tag if in %s",
-                        tag, r->filename);
-            ap_rputs(error, r);
+            }
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                            "unknown parameter \"%s\" to tag if in %s", tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            }
         }
     }
+    return 0;
 }
 
-static int handle_else(ap_file_t *in, request_rec *r, const char *error,
-                       int *conditional_status, int *printing)
+static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                       ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-
-    if (!get_tag(r->pool, in, tag, sizeof(tag), 1)) {
-        return 1;
-    }
-    else if (!strcmp(tag, "done")) {
-#ifdef DEBUG_INCLUDE
-        ap_rvputs(r, "**** else conditional_status=\"",
-               *conditional_status ? "1" : "0", "\"\n", NULL);
-#endif
-        *printing = !(*conditional_status);
-        *conditional_status = 1;
-        return 0;
-    }
-    else {
-        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                    "else directive does not take tags in %s",
-                   r->filename);
-        if (*printing) {
-            ap_rputs(error, r);
+    char *tag = NULL;
+    char *tag_val = NULL;
+    apr_bucket *tmp_buck;
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+        if ((tag != NULL) || (tag_val != NULL)) {
+            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                        "else directive does not take tags in %s", r->filename);
+            if (ctx->flags & FLAG_PRINTING) {
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            }
+            return -1;
+        }
+        else {
+            LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else");
+            
+            if (ctx->flags & FLAG_COND_TRUE) {
+                ctx->flags &= FLAG_CLEAR_PRINTING;
+            }
+            else {
+                ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
+            }
+            return 0;
         }
-        return -1;
     }
+    return 0;
 }
 
-static int handle_endif(ap_file_t *in, request_rec *r, const char *error,
-                        int *conditional_status, int *printing)
+static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                        ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-
-    if (!get_tag(r->pool, in, tag, sizeof(tag), 1)) {
-        return 1;
-    }
-    else if (!strcmp(tag, "done")) {
-#ifdef DEBUG_INCLUDE
-        ap_rvputs(r, "**** endif conditional_status=\"",
-               *conditional_status ? "1" : "0", "\"\n", NULL);
-#endif
-        *printing = 1;
-        *conditional_status = 1;
-        return 0;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_bucket *tmp_buck;
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+        if ((tag != NULL) || (tag_val != NULL)) {
+            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                        "endif directive does not take tags in %s", r->filename);
+            CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            return -1;
+        }
+        else {
+            LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif");
+            ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
+            return 0;
+        }
     }
     else {
-        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                    "endif directive does not take tags in %s",
-                   r->filename);
-        ap_rputs(error, r);
-        return -1;
+        ctx->if_nesting_level--;
+        return 0;
     }
 }
 
-static int handle_set(ap_file_t *in, request_rec *r, const char *error)
+static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                      ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *var     = NULL;
+    apr_bucket *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
-    char *var;
 
-    var = (char *) NULL;
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-            return 1;
-        }
-        else if (!strcmp(tag, "done")) {
-            return 0;
-        }
-        else if (!strcmp(tag, "var")) {
-            var = tag_val;
-        }
-        else if (!strcmp(tag, "value")) {
-            if (var == (char *) NULL) {
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if ((tag == NULL) && (tag_val == NULL)) {
+                return 0;
+            }
+            else if (tag_val == NULL) {
+                return 1;
+            }
+            else if (!strcmp(tag, "var")) {
+                var = tag_val;
+            }
+            else if (!strcmp(tag, "value")) {
+                if (var == (char *) NULL) {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                "variable must precede value in set directive in %s",
+                           r->filename);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    return (-1);
+                }
+                ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+                apr_table_setn(r->subprocess_env, apr_pstrdup(r->pool, var),
+                               apr_pstrdup(r->pool, parsed_string));
+            }
+            else {
                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                            "variable must precede value in set directive in %s",
-                           r->filename);
-                ap_rputs(error, r);
+                            "Invalid tag for set directive in %s", r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
                 return -1;
             }
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
-            ap_table_setn(r->subprocess_env, var, ap_pstrdup(r->pool, parsed_string));
-        }
-        else {
-            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                        "Invalid tag for set directive in %s", r->filename);
-            ap_rputs(error, r);
-            return -1;
         }
     }
+    return 0;
 }
 
-static int handle_printenv(ap_file_t *in, request_rec *r, const char *error)
+static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
+                           ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    ap_array_header_t *arr = ap_table_elts(r->subprocess_env);
-    ap_table_entry_t *elts = (ap_table_entry_t *)arr->elts;
-    int i;
-
-    if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
-        return 1;
-    }
-    else if (!strcmp(tag, "done")) {
-        for (i = 0; i < arr->nelts; ++i) {
-            ap_rvputs(r, ap_escape_html(r->pool, elts[i].key), "=", 
-               ap_escape_html(r->pool, elts[i].val), "\n", NULL);
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_bucket *tmp_buck;
+
+    if (ctx->flags & FLAG_PRINTING) {
+        ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
+        if ((tag == NULL) && (tag_val == NULL)) {
+            apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+            apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
+            int i;
+            char *key_text, *val_text;
+            apr_size_t   k_len, v_len, t_wrt;
+
+            *inserted_head = NULL;
+            for (i = 0; i < arr->nelts; ++i) {
+                key_text = ap_escape_html(r->pool, elts[i].key);
+                val_text = ap_escape_html(r->pool, elts[i].val);
+                k_len = strlen(key_text);
+                v_len = strlen(val_text);
+
+                /*  Key_text                                               */
+                tmp_buck = apr_bucket_heap_create(key_text, k_len, 1, &t_wrt);
+                APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                if (*inserted_head == NULL) {
+                    *inserted_head = tmp_buck;
+                }
+                /*            =                                            */
+                tmp_buck = apr_bucket_immortal_create("=", 1);
+                APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                /*              Value_text                                 */
+                tmp_buck = apr_bucket_heap_create(val_text, v_len, 1, &t_wrt);
+                APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                /*                        newline...                       */
+                tmp_buck = apr_bucket_immortal_create("\n", 1);
+                APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+            }
+            return 0;
+        }
+        else {
+            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                        "printenv directive does not take tags in %s", r->filename);
+            CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+            return -1;
         }
-        return 0;
-    }
-    else {
-        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                    "printenv directive does not take tags in %s",
-                   r->filename);
-        ap_rputs(error, r);
-        return -1;
     }
+    return 0;
 }
 
-
-
 /* -------------------------- The main function --------------------------- */
 
-/* This is a stub which parses a file descriptor. */
-
-static void send_parsed_content(ap_file_t *f, request_rec *r)
+static apr_status_t send_parsed_content(apr_bucket_brigade **bb, 
+                                        request_rec *r, ap_filter_t *f)
 {
-    char directive[MAX_STRING_LEN], error[MAX_STRING_LEN];
-    char timefmt[MAX_STRING_LEN];
-    int noexec = ap_allow_options(r) & OPT_INCNOEXEC;
-    int ret, sizefmt;
-    int if_nesting;
-    int printing;
-    int conditional_status;
-
-    ap_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error));
-    ap_cpystrn(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt));
-    sizefmt = SIZEFMT_KMG;
-
-/*  Turn printing on */
-    printing = conditional_status = 1;
-    if_nesting = 0;
-
-    ap_chdir_file(r->filename);
+    include_ctx_t *ctx = f->ctx;
+    apr_bucket *dptr = APR_BRIGADE_FIRST(*bb);
+    apr_bucket *tmp_dptr;
+    apr_bucket_brigade *tag_and_after;
+    int ret;
+    apr_status_t rv;
+
     if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
-        char *arg_copy = ap_pstrdup(r->pool, r->args);
+        char *arg_copy = apr_pstrdup(r->pool, r->args);
 
-        ap_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
+        apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
         ap_unescape_url(arg_copy);
-        ap_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
+        apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
                   ap_escape_shell_cmd(r->pool, arg_copy));
     }
 
-    while (1) {
-        if (!find_string(f, STARTING_SEQUENCE, r, printing)) {
-            if (get_directive(f, directive, sizeof(directive), r->pool)) {
-               ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                           "mod_include: error reading directive in %s",
-                           r->filename);
-               ap_rputs(error, r);
-                return;
+    while (dptr != APR_BRIGADE_SENTINEL(*bb)) {
+        /* State to check for the STARTING_SEQUENCE. */
+        if ((ctx->state == PRE_HEAD) || (ctx->state == PARSE_HEAD)) {
+            int do_cleanup = 0;
+            apr_size_t cleanup_bytes = ctx->parse_pos;
+
+            tmp_dptr = find_start_sequence(dptr, ctx, *bb, &do_cleanup);
+
+            /* The few bytes stored in the ssi_tag_brigade turned out not to
+             * be a tag after all. This can only happen if the starting
+             * tag actually spans brigades. This should be very rare.
+             */
+            if ((do_cleanup) && (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
+                apr_bucket *tmp_bkt;
+
+                tmp_bkt = apr_bucket_immortal_create(STARTING_SEQUENCE,
+                                                     cleanup_bytes);
+                APR_BRIGADE_INSERT_HEAD(*bb, tmp_bkt);
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
-            if (!strcmp(directive, "if")) {
-                if (!printing) {
-                    if_nesting++;
-                }
-                else {
-                    ret = handle_if(f, r, error, &conditional_status,
-                                    &printing);
-                    if_nesting = 0;
+
+            /* If I am inside a conditional (if, elif, else) that is false
+             *   then I need to throw away anything contained in it.
+             */
+            if ((!(ctx->flags & FLAG_PRINTING)) && (tmp_dptr != NULL) &&
+                (dptr != APR_BRIGADE_SENTINEL(*bb))) {
+                while ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
+                       (dptr != tmp_dptr)) {
+                    apr_bucket *free_bucket = dptr;
+
+                    dptr = APR_BUCKET_NEXT (dptr);
+                    apr_bucket_delete(free_bucket);
                 }
-                continue;
             }
-            else if (!strcmp(directive, "else")) {
-                if (!if_nesting) {
-                    ret = handle_else(f, r, error, &conditional_status,
-                                      &printing);
+
+            /* Adjust the current bucket position based on what was found... */
+            if ((tmp_dptr != NULL) && (ctx->state == PARSE_DIRECTIVE)) {
+                if (ctx->tag_start_bucket != NULL) {
+                    dptr = ctx->tag_start_bucket;
                 }
-                continue;
-            }
-            else if (!strcmp(directive, "elif")) {
-                if (!if_nesting) {
-                    ret = handle_elif(f, r, error, &conditional_status,
-                                      &printing);
+                else {
+                    dptr = APR_BRIGADE_SENTINEL(*bb);
                 }
-                continue;
             }
-            else if (!strcmp(directive, "endif")) {
-                if (!if_nesting) {
-                    ret = handle_endif(f, r, error, &conditional_status,
-                                       &printing);
+            else if ((tmp_dptr != NULL) && (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD)) {
+                               /* Send the large chunk of pre-tag bytes...  */
+                tag_and_after = apr_brigade_split(*bb, tmp_dptr);
+                rv = ap_pass_brigade(f->next, *bb);
+                if (rv != APR_SUCCESS) {
+                    return rv;
                 }
-                else {
-                    if_nesting--;
+                *bb  = tag_and_after;
+                dptr = tmp_dptr;
+                ctx->bytes_parsed = 0;
+            }
+            else if (tmp_dptr == NULL) { /* There was no possible SSI tag in the */
+                dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
+            }
+        }
+
+        /* State to check for the ENDING_SEQUENCE. */
+        if (((ctx->state == PARSE_DIRECTIVE) ||
+             (ctx->state == PARSE_TAG)       ||
+             (ctx->state == PARSE_TAIL))       &&
+            (dptr != APR_BRIGADE_SENTINEL(*bb))) {
+            tmp_dptr = find_end_sequence(dptr, ctx, *bb);
+
+            if (tmp_dptr != NULL) {
+                dptr = tmp_dptr;  /* Adjust bucket pos... */
+                
+                /* If some of the tag has already been set aside then set
+                 * aside remainder of tag. Now the full tag is in ssi_tag_brigade.
+                 * If none has yet been set aside, then leave it all where it is.
+                 * In any event after this the entire set of tag buckets will be
+                 * in one place or another.
+                 */
+                if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    tag_and_after = apr_brigade_split(*bb, dptr);
+                    APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
+                    *bb = tag_and_after;
+                }
+                else if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
+                    SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
                 }
-                continue;
             }
-            if (!printing) {
-                continue;
+            else {
+                dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
             }
-            if (!strcmp(directive, "exec")) {
-                if (noexec) {
-                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                                 "exec used but not allowed in %s",
-                                 r->filename);
-                    if (printing) {
-                        ap_rputs(error, r);
-                    }
-                    ret = find_string(f, ENDING_SEQUENCE, r, 0);
+        }
+
+        /* State to processed the directive... */
+        if (ctx->state == PARSED) {
+            apr_bucket    *content_head = NULL, *tmp_bkt;
+            apr_size_t    tmp_i;
+            char          tmp_buf[TMP_BUF_SIZE];
+            int (*handle_func)(include_ctx_t *, apr_bucket_brigade **, request_rec *,
+                           ap_filter_t *, apr_bucket *, apr_bucket **);
+
+            /* By now the full tag (all buckets) should either be set aside into
+             *  ssi_tag_brigade or contained within the current bb. All tag
+             *  processing from here on can assume that.
+             */
+
+            /* At this point, everything between ctx->head_start_bucket and
+             * ctx->tail_start_bucket is an SSI
+             * directive, we just have to deal with it now.
+             */
+            if (get_combined_directive(ctx, r, *bb, tmp_buf,
+                                        TMP_BUF_SIZE) != APR_SUCCESS) {
+               ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                           "mod_include: error copying directive in %s",
+                           r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
+
+                /* DO CLEANUP HERE!!!!! */
+                tmp_dptr = ctx->head_start_bucket;
+                if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    apr_brigade_cleanup(ctx->ssi_tag_brigade);
                 }
                 else {
-                    ret = handle_exec(f, r, error);
+                    do {
+                        tmp_bkt  = tmp_dptr;
+                        tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
+                        apr_bucket_delete(tmp_bkt);
+                    } while ((tmp_dptr != dptr) &&
+                             (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
                 }
+
+                return APR_SUCCESS;
             }
-            else if (!strcmp(directive, "config")) {
-                ret = handle_config(f, r, error, timefmt, &sizefmt);
-            }
-            else if (!strcmp(directive, "set")) {
-                ret = handle_set(f, r, error);
+
+            /* Can't destroy the tag buckets until I'm done processing
+             *  because the combined_tag might just be pointing to
+             *  the contents of a single bucket!
+             */
+
+            /* Retrieve the handler function to be called for this directive from the
+             *  functions registered in the hash table.
+             * Need to lower case the directive for proper matching. Also need to have
+             *  it NULL terminated (and include the NULL in the length) for proper
+             *  hash matching.
+             */
+            for (tmp_i = 0; tmp_i < ctx->directive_length; tmp_i++) {
+                ctx->combined_tag[tmp_i] = apr_tolower(ctx->combined_tag[tmp_i]);
             }
-            else if (!strcmp(directive, "include")) {
-                ret = handle_include(f, r, error, noexec);
+            ctx->combined_tag[ctx->directive_length] = '\0';
+            ctx->curr_tag_pos = &ctx->combined_tag[ctx->directive_length+1];
+
+            handle_func = 
+                (include_handler_fn_t *)apr_hash_get(include_hash, 
+                                                     ctx->combined_tag, 
+                                                     ctx->directive_length+1);
+            if (handle_func != NULL) {
+                ret = (*handle_func)(ctx, bb, r, f, dptr, &content_head);
             }
-            else if (!strcmp(directive, "echo")) {
-                ret = handle_echo(f, r, error);
+            else {
+                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                              "unknown directive \"%s\" in parsed doc %s",
+                              ctx->combined_tag, r->filename);
+                CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
+            }
+
+            /* This chunk of code starts at the first bucket in the chain
+             * of tag buckets (assuming that by this point the bucket for
+             * the STARTING_SEQUENCE has been split) and loops through to
+             * the end of the tag buckets freeing them all.
+             *
+             * Remember that some part of this may have been set aside
+             * into the ssi_tag_brigade and the remainder (possibly as
+             * little as one byte) will be in the current brigade.
+             *
+             * The value of dptr should have been set during the
+             * PARSE_TAIL state to the first bucket after the
+             * ENDING_SEQUENCE.
+             *
+             * The value of content_head may have been set during processing
+             * of the directive. If so, the content was inserted in front
+             * of the dptr bucket. The inserted buckets should not be thrown
+             * away here, but they should also not be parsed later.
+             */
+            if (content_head == NULL) {
+                content_head = dptr;
+            }
+            tmp_dptr = ctx->head_start_bucket;
+            if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
-            else if (!strcmp(directive, "fsize")) {
-                ret = handle_fsize(f, r, error, sizefmt);
+            else {
+                do {
+                    tmp_bkt  = tmp_dptr;
+                    tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
+                    apr_bucket_delete(tmp_bkt);
+                } while ((tmp_dptr != content_head) &&
+                         (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
             }
-            else if (!strcmp(directive, "flastmod")) {
-                ret = handle_flastmod(f, r, error, timefmt);
+            if (ctx->combined_tag == tmp_buf) {
+                memset (ctx->combined_tag, '\0', ctx->tag_length);
+                ctx->combined_tag = NULL;
             }
-            else if (!strcmp(directive, "printenv")) {
-                ret = handle_printenv(f, r, error);
+
+            /* Don't reset the flags or the nesting level!!! */
+            ctx->parse_pos         = 0;
+            ctx->head_start_bucket = NULL;
+            ctx->head_start_index  = 0;
+            ctx->tag_start_bucket  = NULL;
+            ctx->tag_start_index   = 0;
+            ctx->tail_start_bucket = NULL;
+            ctx->tail_start_index  = 0;
+            ctx->curr_tag_pos      = NULL;
+            ctx->tag_length        = 0;
+            ctx->directive_length  = 0;
+
+            if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                apr_brigade_cleanup(ctx->ssi_tag_brigade);
             }
-#ifdef USE_PERL_SSI
-            else if (!strcmp(directive, "perl")) {
-                ret = handle_perl(f, r, error);
+
+            ctx->state     = PRE_HEAD;
+        }
+    }
+
+    /* If I am in the middle of parsing an SSI tag then I need to set aside
+     *   the pertinent trailing buckets and pass on the initial part of the
+     *   brigade. The pertinent parts of the next brigades will be added to
+     *   these set aside buckets to form the whole tag and will be processed
+     *   once the whole tag has been found.
+     */
+    if (ctx->state == PRE_HEAD) {
+        /* Inside a false conditional (if, elif, else), so toss it all... */
+        if ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
+            (!(ctx->flags & FLAG_PRINTING))) {
+            apr_bucket *free_bucket;
+            do {
+                free_bucket = dptr;
+                dptr = APR_BUCKET_NEXT (dptr);
+                apr_bucket_delete(free_bucket);
+            } while (dptr != APR_BRIGADE_SENTINEL(*bb));
+        }
+        else { /* Otherwise pass it along... */
+            rv = ap_pass_brigade(f->next, *bb);  /* No SSI tags in this brigade... */
+            if (rv != APR_SUCCESS) {
+                return rv;
             }
-#endif
-            else {
-                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                             "unknown directive \"%s\" "
-                             "in parsed doc %s",
-                             directive, r->filename);
-                if (printing) {
-                    ap_rputs(error, r);
-                }
-                ret = find_string(f, ENDING_SEQUENCE, r, 0);
+            ctx->bytes_parsed = 0;
+        }
+    }
+    else if (ctx->state == PARSED) {     /* Invalid internal condition... */
+        apr_bucket *content_head = NULL, *tmp_bkt;
+        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                      "Invalid mod_include state during file %s", r->filename);
+        CREATE_ERROR_BUCKET(ctx, tmp_bkt, APR_BRIGADE_FIRST(*bb), content_head);
+    }
+    else {                 /* Entire brigade is middle chunk of SSI tag... */
+        if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+            APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
+        }
+        else {             /* End of brigade contains part of SSI tag... */
+            if (ctx->head_start_index > 0) {
+                apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
+                ctx->head_start_bucket = APR_BUCKET_NEXT(ctx->head_start_bucket);
+                ctx->head_start_index  = 0;
             }
-            if (ret) {
-                ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                             "premature EOF in parsed file %s",
-                             r->filename);
-                return;
+                           /* Set aside tag, pass pre-tag... */
+            tag_and_after = apr_brigade_split(*bb, ctx->head_start_bucket);
+            ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after, r->pool);
+            rv = ap_pass_brigade(f->next, *bb);
+            if (rv != APR_SUCCESS) {
+                return rv;
             }
-        }
-        else {
-            return;
+            ctx->bytes_parsed = 0;
         }
     }
+    return APR_SUCCESS;
 }
 
 /*****************************************************************
@@ -2347,36 +2625,47 @@ static void send_parsed_content(ap_file_t *f, request_rec *r)
  * option only changes the default.
  */
 
-module includes_module;
+module include_module;
 enum xbithack {
     xbithack_off, xbithack_on, xbithack_full
 };
 
+typedef struct {
+    char *default_error_msg;
+    char *default_time_fmt;
+    enum xbithack *xbithack;
+} include_dir_config;
+
 #ifdef XBITHACK
 #define DEFAULT_XBITHACK xbithack_full
 #else
 #define DEFAULT_XBITHACK xbithack_off
 #endif
 
-static void *create_includes_dir_config(ap_pool_t *p, char *dummy)
+static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
 {
-    enum xbithack *result = (enum xbithack *) ap_palloc(p, sizeof(enum xbithack));
-    *result = DEFAULT_XBITHACK;
+    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 const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
 {
-    enum xbithack *state = (enum xbithack *) xbp;
+    include_dir_config *conf = (include_dir_config *)xbp;
 
     if (!strcasecmp(arg, "off")) {
-        *state = xbithack_off;
+        *conf->xbithack = xbithack_off;
     }
     else if (!strcasecmp(arg, "on")) {
-        *state = xbithack_on;
+        *conf->xbithack = xbithack_on;
     }
     else if (!strcasecmp(arg, "full")) {
-        *state = xbithack_full;
+        *conf->xbithack = xbithack_full;
     }
     else {
         return "XBitHack must be set to Off, On, or Full";
@@ -2385,59 +2674,55 @@ static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
     return NULL;
 }
 
-static int send_parsed_file(request_rec *r)
+static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
 {
-    ap_file_t *f = NULL;
-    enum xbithack *state =
-    (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module);
-    int errstatus;
+    request_rec *r = f->r;
+    include_ctx_t *ctx = f->ctx;
     request_rec *parent;
+    apr_status_t rv;
+    include_dir_config *conf = 
+                   (include_dir_config *)ap_get_module_config(r->per_dir_config,
+                                                              &include_module);
 
     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
-        return DECLINED;
+        return ap_pass_brigade(f->next, b);
     }
-    r->allowed |= (1 << M_GET);
+    r->allowed |= (AP_METHOD_BIT << M_GET);
     if (r->method_number != M_GET) {
-        return DECLINED;
-    }
-    if (r->finfo.protection == 0) {
-        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
-                   "File does not exist: %s",
-                    (r->path_info
-                     ? ap_pstrcat(r->pool, r->filename, r->path_info, NULL)
-                     : r->filename));
-        return HTTP_NOT_FOUND;
+        return ap_pass_brigade(f->next, b);
     }
 
-    errstatus = ap_open(&f, r->filename, APR_READ|APR_BUFFERED, 0, r->pool);
+    if (!f->ctx) {
+        f->ctx    = ctx      = apr_pcalloc(f->c->pool, sizeof(*ctx));
+        if (ctx != NULL) {
+            ctx->state           = PRE_HEAD;
+            ctx->flags           = (FLAG_PRINTING | FLAG_COND_TRUE);
+            if (ap_allow_options(r) & OPT_INCNOEXEC) {
+                ctx->flags |= FLAG_NO_EXEC;
+            }
+            ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool);
 
-    if (errstatus != APR_SUCCESS) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, errstatus, r,
-                    "file permissions deny server access: %s", r->filename);
-        return HTTP_FORBIDDEN;
+            apr_cpystrn(ctx->error_str, conf->default_error_msg, sizeof(ctx->error_str));
+            apr_cpystrn(ctx->time_str, conf->default_time_fmt, sizeof(ctx->time_str));
+            ctx->error_length = strlen(ctx->error_str);
+        }
+        else {
+            return ap_pass_brigade(f->next, b);
+        }
+    }
+    else {
+        ctx->bytes_parsed = 0;
     }
 
-    if ((*state == xbithack_full)
-#if !defined(OS2) && !defined(WIN32)
-    /*  OS/2 dosen't support Groups. */
-        && (r->finfo.protection & APR_GEXECUTE)
-#endif
-        ) {
+    /* Assure the platform supports Group protections */
+    if ((*conf->xbithack == xbithack_full)
+        && (r->finfo.valid & APR_FINFO_GPROT)
+        && (r->finfo.protection & APR_GEXECUTE)) {
         ap_update_mtime(r, r->finfo.mtime);
         ap_set_last_modified(r);
     }
-    if ((errstatus = ap_meets_conditions(r)) != OK) {
-        return errstatus;
-    }
-
-    ap_send_http_header(r);
 
-    if (r->header_only) {
-        ap_close(f);
-        return OK;
-    }
-
-    if ((parent = ap_get_module_config(r->request_config, &includes_module))) {
+    if ((parent = ap_get_module_config(r->request_config, &include_module))) {
        /* Kludge --- for nested includes, we want to keep the subprocess
         * environment of the base document (for compatibility); that means
         * torquing our own last_modified date as well so that the
@@ -2446,89 +2731,145 @@ static int send_parsed_file(request_rec *r)
         * We also insist that the memory for this subrequest not be
         * destroyed, that's dealt with in handle_include().
         */
-       r->subprocess_env = parent->subprocess_env;
-       r->finfo.mtime = parent->finfo.mtime;
+        r->subprocess_env = r->main->subprocess_env;
+        apr_pool_join(r->main->pool, r->pool);
+        r->finfo.mtime = r->main->finfo.mtime;
     }
     else {
        /* we're not a nested include, so we create an initial
         * environment */
         ap_add_common_vars(r);
         ap_add_cgi_vars(r);
-        add_include_vars(r, DEFAULT_TIME_FORMAT);
+        add_include_vars(r, conf->default_time_fmt);
     }
     /* XXX: this is bogus, at some point we're going to do a subrequest,
      * and when we do it we're going to be subjecting code that doesn't
      * expect to be signal-ready to SIGALRM.  There is no clean way to
      * fix this, except to put alarm support into BUFF. -djg
      */
-#ifdef CHARSET_EBCDIC
-    /* XXX:@@@ Is the generated/included output ALWAYS in text/ebcdic format? */
-    ap_bsetopt(r->connection->client, BO_WXLATE, &ap_hdrs_to_ascii);
-#endif
 
-    send_parsed_content(f, r);
+
+    /* Always unset the content-length.  There is no way to know if
+     * the content will be modified at some point by send_parsed_content.
+     * It is very possible for us to not find any content in the first
+     * 9k of the file, but still have to modify the content of the file.
+     * If we are going to pass the file through send_parsed_content, then
+     * the content-length should just be unset.
+     */
+    apr_table_unset(f->r->headers_out, "Content-Length");
+
+    rv = send_parsed_content(&b, r, f);
 
     if (parent) {
        /* signify that the sub request should not be killed */
-       ap_set_module_config(r->request_config, &includes_module,
+       ap_set_module_config(r->request_config, &include_module,
            NESTED_INCLUDE_MAGIC);
     }
 
-    return OK;
+    return rv;
+}
+
+static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
+{
+    apr_hash_set(include_hash, tag, strlen(tag) + 1, (const void *)func);
+}
+
+static void include_post_config(apr_pool_t *p, apr_pool_t *plog,
+                                apr_pool_t *ptemp, server_rec *s)
+{
+    include_hash = 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);
+    }
+}
+
+static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
+{
+    include_dir_config *conf = (include_dir_config *)mconfig;
+    conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
+    return NULL;
 }
 
-static int send_shtml_file(request_rec *r)
+static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
 {
-    r->content_type = "text/html";
-    return send_parsed_file(r);
+    include_dir_config *conf = (include_dir_config *)mconfig;
+    conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
+    return NULL;
 }
 
+/*
+ * Module definition and configuration data structs...
+ */
+static const command_rec includes_cmds[] =
+{
+    AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
+                  "Off, On, or Full"),
+    AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 
+                  "a string"),
+    AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
+                  "a strftime(3) formatted string"),
+    {NULL}
+};
+
 static int xbithack_handler(request_rec *r)
 {
-#if defined(OS2) || defined(WIN32)
+#if defined(OS2) || defined(WIN32) || defined(NETWARE)
     /* OS/2 dosen't currently support the xbithack. This is being worked on. */
     return DECLINED;
 #else
     enum xbithack *state;
-
+    if (ap_strcmp_match(r->handler, "text/html")) {
+        return DECLINED;
+    }
     if (!(r->finfo.protection & APR_UEXECUTE)) {
         return DECLINED;
     }
-
     state = (enum xbithack *) ap_get_module_config(r->per_dir_config,
-                                                &includes_module);
-
+                                                &include_module);
     if (*state == xbithack_off) {
         return DECLINED;
     }
-    return send_parsed_file(r);
+    /* We always return declined, because the default handler will actually
+     * serve the file.  All we have to do is add the filter.
+     */
+    ap_add_output_filter("INCLUDES", NULL, r, r->connection);
+    return DECLINED;
 #endif
 }
 
-static const command_rec includes_cmds[] =
-{
-    AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
-                  "Off, On, or Full"),
-    {NULL}
-};
-
-static const handler_rec includes_handlers[] =
+static void register_hooks(apr_pool_t *p)
 {
-    {INCLUDES_MAGIC_TYPE, send_shtml_file},
-    {INCLUDES_MAGIC_TYPE3, send_shtml_file},
-    {"server-parsed", send_parsed_file},
-    {"text/html", xbithack_handler},
-    {NULL}
-};
+    APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
+    APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
+    APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
+    ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
+    ap_hook_handler(xbithack_handler, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_register_output_filter("INCLUDES", includes_filter, AP_FTYPE_CONTENT);
+}
 
-module MODULE_VAR_EXPORT includes_module =
+module AP_MODULE_DECLARE_DATA include_module =
 {
     STANDARD20_MODULE_STUFF,
     create_includes_dir_config, /* dir config creater */
     NULL,                       /* dir merger --- default is to override */
     NULL,                       /* server config */
     NULL,                       /* merge server config */
-    includes_cmds,              /* command ap_table_t */
-    includes_handlers,          /* handlers */
-    NULL                       /* register hooks */
+    includes_cmds,              /* command apr_table_t */
+    register_hooks             /* register hooks */
 };