]> granicus.if.org Git - apache/commitdiff
A complete re-write of mod_include. This has served the entire manual
authorRyan Bloom <rbb@apache.org>
Wed, 22 Nov 2000 19:49:20 +0000 (19:49 +0000)
committerRyan Bloom <rbb@apache.org>
Wed, 22 Nov 2000 19:49:20 +0000 (19:49 +0000)
now.  This makes mod_include a filter that uses buckets directly.
Submitted by: Paul J. Reder <rederpj@raleigh.ibm.com>
Reviewed by: Ryan Bloom

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

modules/filters/mod_include.c
modules/filters/mod_include.h [new file with mode: 0644]

index 2c65dc92b571228565799d919cf799354fa314c4..8731d65602bfb7a5200d0fe655e501c1536b154a 100644 (file)
 
 #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 "apr_strings.h"
@@ -99,7 +94,7 @@
 #include "http_main.h"
 #include "util_script.h"
 #include "http_core.h"
-#include "ap_mpm.h"
+#include "mod_include.h"
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
 #endif
 #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)  apr_xlate_conv_byte(ap_hdrs_from_ascii, (unsigned char)ch)
-#else /*CHARSET_EBCDIC*/
-#define RAW_ASCII_CHAR(ch)  (ch)
-#endif /*CHARSET_EBCDIC*/
-
-module AP_MODULE_DECLARE_DATA includes_module;
-
-/* 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)
-
 /* ------------------------ Environment function -------------------------- */
 
 /* XXX: could use ap_table_overlap here */
@@ -181,14 +155,19 @@ static void add_include_vars(request_rec *r, char *timefmt)
 
 /* --------------------------- Parser functions --------------------------- */
 
-#define OUTBUFSIZE 4096
-
-static ap_bucket *find_string(ap_bucket *dptr, const char *str, ap_bucket *end)
+/* 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.
+ */
+static ap_bucket *find_start_sequence(ap_bucket *dptr, include_ctx_t *ctx,
+                                      ap_bucket_brigade *bb, int *do_cleanup)
 {
-    apr_size_t len;
+    apr_ssize_t len;
     const char *c;
     const char *buf;
-    int state = 0;
+    const char *str = STARTING_SEQUENCE;
+
+    *do_cleanup = 0;
 
     do {
         if (AP_BUCKET_IS_EOS(dptr)) {
@@ -201,41 +180,218 @@ static ap_bucket *find_string(ap_bucket *dptr, const char *str, ap_bucket *end)
         }
         c = buf;
         while (c - buf != len) {
-            if (*c == str[state]) {
-                state++;
+            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[state] == '\0') {
-                    /* We want to split the bucket at the '<' and '>' 
-                     * respectively.  That means adjusting where we split based
-                     * on what we are searching for.
+                if (str[ctx->parse_pos] == '\0') {
+                    ap_bucket   *tmp_bkt;
+                    apr_ssize_t  start_index;
+
+                    /* We want to split the bucket at the '<'. */
+                    ctx->state            = PARSE_TAG;
+                    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;
+                        ap_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
+                        tmp_bkt = AP_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.
                      */
-                    if (str[0] == '<') {
-                        ap_bucket_split(dptr, c - buf - strlen(str));
+                    *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++;
+        }
+        dptr = AP_BUCKET_NEXT(dptr);
+    } while (dptr != AP_BRIGADE_SENTINEL(bb));
+    return NULL;
+}
+
+static ap_bucket *find_end_sequence(ap_bucket *dptr, include_ctx_t *ctx, ap_bucket_brigade *bb)
+{
+    apr_ssize_t len;
+    const char *c;
+    const char *buf;
+    const char *str = ENDING_SEQUENCE;
+
+    do {
+        if (AP_BUCKET_IS_EOS(dptr)) {
+            break;
+        }
+        ap_bucket_read(dptr, &buf, &len, 0);
+        /* XXX handle retcodes */
+        if (len == 0) { /* end of pipe? */
+            break;
+        }
+        if (dptr == ctx->tag_start_bucket) {
+            c = buf + ctx->tag_start_index;
+        }
+        else {
+            c = buf;
+        }
+        while (c - buf != len) {
+            if (*c == str[ctx->parse_pos]) {
+                if (ctx->state == PARSE_TAG) {
+                    ctx->state             = PARSE_TAIL;
+                    ctx->tail_start_bucket = dptr;
+                    ctx->tail_start_index  = c - buf;
+                }
+                ctx->parse_pos++;
+            }
+            else {
+                if (ctx->state == PARSE_TAG) {
+                    if (ctx->tag_length == 0) {
+                        if (!apr_isspace(*c)) {
+                            ctx->tag_start_bucket = dptr;
+                            ctx->tag_start_index  = c - buf;
+                            ctx->tag_length       = 1;
+                        }
                     }
                     else {
-                        ap_bucket_split(dptr, c - buf);
+                        ctx->tag_length++;
                     }
-                    return AP_BUCKET_NEXT(dptr);
                 }
                 else {
-                    state = 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 (*c == str[state]) {
-                         state++;
-                     }
+                    if (str[ctx->parse_pos] == '\0') {
+                        ap_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->state = PARSED;
+                        if ((c - buf) > 0) {
+                            ap_bucket_split(dptr, c - buf);
+                            tmp_buck = AP_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->parse_pos         = 1;
+                             ctx->state             = PARSE_TAIL;
+                             ctx->tail_start_bucket = dptr;
+                             ctx->tail_start_index  = c - buf;
+                         }
+                         else {
+                             ctx->parse_pos         = 0;
+                             ctx->state             = PARSE_TAG;
+                             ctx->tail_start_bucket = NULL;
+                             ctx->tail_start_index  = 0;
+                         }
+                    }
                 }
             }
             c++;
         }
         dptr = AP_BUCKET_NEXT(dptr);
-    } while (AP_BUCKET_PREV(dptr) != end);
+    } while (dptr != AP_BRIGADE_SENTINEL(bb));
     return NULL;
 }
 
+/* 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,
+                                            ap_bucket_brigade *bb,
+                                            char *tmp_buf, int tmp_buf_size)
+{
+    int         done = 0;
+    ap_bucket  *dptr;
+    const char *tmp_from;
+    apr_ssize_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;
+    ap_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 = AP_BUCKET_NEXT (dptr);
+            ap_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.
  * 's' is overwritten with the decoded string.
@@ -331,193 +487,136 @@ 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(apr_pool_t *p, ap_bucket *in, char *tag, int tagbuf_len, int dodecode, apr_off_t *offset)
+#define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
+
+static void get_tag_and_value(include_ctx_t *ctx, char **tag,
+                              char **tag_val, int dodecode)
 {
-    ap_bucket *dptr = in;
-    const char *c;
-    const char *str;
-    apr_size_t length; 
-    char *t = tag, *tag_val, term;
+    char *c = ctx->curr_tag_pos;
+    int   shift_val = 0; 
+    char  term = '\0';
 
-    /* makes code below a little less cluttered */
-    --tagbuf_len;
+    *tag_val = NULL;
+    SKIP_TAG_WHITESPACE(c);
+    *tag = c;             /* First non-whitespace character (could be NULL). */
 
-    /* Remove all whitespace */
-    do {
-        ap_bucket_read(dptr, &str, &length, 0);
-        c = str + *offset;
-        *offset = 0;
-        while (c - str < length) {
-            if (!apr_isspace(*c)) {
-                break;
-            }
-            c++;
-        }
-        if (!apr_isspace(*c)) {
-            break;
-        }
-        dptr = AP_BUCKET_NEXT(dptr);
-    } while (dptr);
-
-    /* tags can't start with - */
-    if (*c == '-') {
+    while ((*c != '\0') && (*c != '=') && (!apr_isspace(*c))) {
+        *c = apr_tolower(*c);    /* find end of tag, lowercasing as we go... */
         c++;
-        if (c == '\0') {
-            ap_bucket_read(dptr, &str, &length, 0);
-            c = str;
-        }
-        if (*c == '-') {
-            do {
-                c++;
-                if (c == '\0') {
-                    ap_bucket_read(dptr, &str, &length, 0);
-                    c = str;
-                }
-            } while (apr_isspace(*c));
-            if (*c == '>') {
-                apr_cpystrn(tag, "done", tagbuf_len);
-                *offset = c - str;
-                return tag;
-            }
-        }
-        return NULL;            /* failed */
     }
 
-    /* find end of tag name */
-    while (1) {
-        if (t - tag == tagbuf_len) {
-            *t = '\0';
-            return NULL;
-        }
-        if (*c == '=' || apr_isspace(*c)) {
-            break;
-        }
-        *(t++) = apr_tolower(*c);
-        c++;
-        if (c == '\0') {
-            ap_bucket_read(dptr, &str, &length, 0);
-            c = str;
+    if ((*c == '\0') || (**tag == '=')) {
+        if ((**tag == '\0') || (**tag == '=')) {
+            *tag = NULL;
         }
-    }
-
-    *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 (apr_isspace(*c)) {
-        c++;
-        if (c == '\0') {
-            ap_bucket_read(dptr, &str, &length, 0);
-            c = str;
-        }
-    }
-    if (*c != '=') {
-        /* XXX may need to ungetc() here (see pre-bucketized code) */
-        return NULL;
+    if (*c == '=') {
+        *c++ = '\0';     /* Overwrite the '=' with a terminating byte after tag. */
     }
-
-    do {
-        c++;
-        if (c == '\0') {
-            ap_bucket_read(dptr, &str, &length, 0);
-            c = str;
+    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. */
         }
-    } while (apr_isspace(*c));
-
-    /* we should allow a 'name' as a value */
+        else {
+            c++; /* Skip the equals sign. */
+        }
+    }
 
-    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) {
-        c++;
-        if (c == '\0') {
-            ap_bucket_read(dptr, &str, &length, 0);
-            c = str;
-        }
-        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 */
+    
+    *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 == '\0') {
-                ap_bucket_read(dptr, &str, &length, 0);
-                c = str;
-            }
-            if (*c == term) {    /* Only if */
-                *(--t) = *c;     /* Replace backslash ONLY for terminator */
+            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++ = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
+    ctx->curr_tag_pos = c;
     if (dodecode) {
-        decodehtml(tag_val);
+        decodehtml(*tag_val);
     }
-    *offset = c - str + 1;
-    return apr_pstrdup(p, tag_val);
+
+    return;
 }
 
-static int get_directive(ap_bucket *in, char *dest, size_t len, apr_pool_t *p)
+static char *get_directive(include_ctx_t *ctx, dir_token_id *fnd_token)
 {
-    ap_bucket *dptr = in;
-    char *d = dest;
-    const char *c;
-    const char *str;
-    apr_size_t length; 
+    char *c = ctx->curr_tag_pos;
+    char *dest;
+    int len = 0;
 
-    /* make room for nul terminator */
-    --len;
+    SKIP_TAG_WHITESPACE(c);
 
-    while (dptr) {
-        ap_bucket_read(dptr, &str, &length, 0);
-        /* need to start past the <!--#
-         */
-        c = str + strlen(STARTING_SEQUENCE);
-        while (c - str < length) {
-            if (!apr_isspace(*c)) {
-                break;
-            }
-        }
-        if (!apr_isspace(*c)) {
-            break;
-        }
-        dptr = AP_BUCKET_NEXT(dptr);
+    dest = c;
+    /* now get directive */
+    while ((*c != '\0') && (!apr_isspace(*c))) {
+        *c = apr_tolower(*c);
+        c++;
+        len++;
     }
+    
+    *c++ = '\0';
+    ctx->curr_tag_pos = c;
 
-    /* now get directive */
-    while (dptr) {
-        if (c - str >= length) {
-            ap_bucket_read(dptr, &str, &length, 0);
-        }
-        while (c - str < length) {
-           if (d - dest == (int)len) {
-               return 1;
-           }
-            *d++ = apr_tolower(*c);
-            c++;
-            if (apr_isspace(*c)) {
-                break;
-            }
-        }
-        if (apr_isspace(*c)) {
+    *fnd_token = TOK_UNKNOWN;
+    switch (len) {
+    case 2: if      (!strcmp(dest, "if"))       *fnd_token = TOK_IF;
+            break;
+    case 3: if      (!strcmp(dest, "set"))      *fnd_token = TOK_SET;
+            break;
+    case 4: if      (!strcmp(dest, "else"))     *fnd_token = TOK_ELSE;
+            else if (!strcmp(dest, "elif"))     *fnd_token = TOK_ELIF;
+            else if (!strcmp(dest, "exec"))     *fnd_token = TOK_EXEC;
+            else if (!strcmp(dest, "echo"))     *fnd_token = TOK_ECHO;
+#ifdef USE_PERL_SSI                             
+            else if (!strcmp(dest, "perl"))     *fnd_token = TOK_PERL;
+#endif
+            break;
+    case 5: if      (!strcmp(dest, "endif"))    *fnd_token = TOK_ENDIF;
+            else if (!strcmp(dest, "fsize"))    *fnd_token = TOK_FSIZE;
+            break;
+    case 6: if      (!strcmp(dest, "config"))   *fnd_token = TOK_CONFIG;
+            break;
+    case 7: if      (!strcmp(dest, "include"))  *fnd_token = TOK_INCLUDE;
+            break;
+    case 8: if      (!strcmp(dest, "flastmod")) *fnd_token = TOK_FLASTMOD;
+            else if (!strcmp(dest, "printenv")) *fnd_token = TOK_PRINTENV;
             break;
-        }
-        dptr = AP_BUCKET_NEXT(dptr);
     }
-    *d = '\0';
-    return 0;
+
+    return (dest);
 }
 
 /*
@@ -550,11 +649,12 @@ static void parse_string(request_rec *r, const char *in, char *out,
             break;
         case '$':
             {
-               char var[MAX_STRING_LEN];
+/* pjr hack     char var[MAX_STRING_LEN]; */
                const char *start_of_var_name;
-               const char *end_of_var_name;    /* end of var name + 1 */
+               char *end_of_var_name;  /* end of var name + 1 */
                const char *expansion;
                const char *val;
+                char        tmp_store;
                size_t l;
 
                /* guess that the expansion won't happen */
@@ -570,7 +670,7 @@ static void parse_string(request_rec *r, const char *in, char *out,
                         *next = '\0';
                         return;
                     }
-                   end_of_var_name = in;
+                   (const char *)end_of_var_name = in;
                    ++in;
                }
                else {
@@ -578,17 +678,24 @@ static void parse_string(request_rec *r, const char *in, char *out,
                    while (apr_isalnum(*in) || *in == '_') {
                        ++in;
                    }
-                   end_of_var_name = in;
+                   (const char *)end_of_var_name = in;
                }
                /* 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';
+/* pjr - this is a test hack to avoid a memcpy. Make sure that this works...
+*                  l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l;
+*                  memcpy(var, start_of_var_name, l);
+*                  var[l] = '\0';
+*
+*                  val = apr_table_get(r->subprocess_env, var);
+*/
+/* pjr hack */      tmp_store        = *end_of_var_name;
+/* pjr hack */      *end_of_var_name = '\0';
+/* pjr hack */      val = apr_table_get(r->subprocess_env, start_of_var_name);
+/* pjr hack */      *end_of_var_name = tmp_store;
 
-                   val = apr_table_get(r->subprocess_env, var);
                    if (val) {
                        expansion = val;
                        l = strlen(expansion);
@@ -625,10 +732,12 @@ static void parse_string(request_rec *r, const char *in, char *out,
 
 /* --------------------------- Action handlers ---------------------------- */
 
-static int include_cgi(char *s, request_rec *r, ap_filter_t *next)
+static int include_cgi(char *s, request_rec *r, ap_filter_t *next,
+                       ap_bucket *head_ptr, ap_bucket **inserted_head)
 {
-    request_rec *rr = ap_sub_req_lookup_uri(s, r);
+    request_rec *rr = ap_sub_req_lookup_uri(s, r, next);
     int rr_status;
+    ap_bucket  *tmp_buck, *tmp2_buck;
 
     if (rr->status != HTTP_OK) {
         return -1;
@@ -654,16 +763,30 @@ static int include_cgi(char *s, request_rec *r, ap_filter_t *next)
 
     rr->content_type = CGI_MAGIC_TYPE;
 
-    /* The subrequest should inherit the remaining filters from this request. */
-    rr->output_filters = next;
-
     /* Run it. */
 
     rr_status = ap_run_sub_req(rr);
     if (ap_is_HTTP_REDIRECT(rr_status)) {
+        apr_ssize_t len_loc, h_wrt;
         const char *location = apr_table_get(rr->headers_out, "Location");
+
         location = ap_escape_html(rr->pool, location);
-        ap_rvputs(r, "<A HREF=\"", location, "\">", location, "</A>", NULL);
+        len_loc = strlen(location);
+
+        tmp_buck = ap_bucket_create_immortal("<A HREF=\"", sizeof("<A HREF=\""));
+        AP_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+        tmp2_buck = ap_bucket_create_heap(location, len_loc, 1, &h_wrt);
+        AP_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
+        tmp2_buck = ap_bucket_create_immortal("\">", sizeof("\">"));
+        AP_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
+        tmp2_buck = ap_bucket_create_heap(location, len_loc, 1, &h_wrt);
+        AP_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
+        tmp2_buck = ap_bucket_create_immortal("</A>", sizeof("</A>"));
+        AP_BUCKET_INSERT_BEFORE(head_ptr, tmp2_buck);
+
+        if (*inserted_head == NULL) {
+            *inserted_head = tmp_buck;
+        }
     }
 
     ap_destroy_sub_req(rr);
@@ -712,124 +835,134 @@ static int is_only_below(const char *path)
     return 1;
 }
 
-static int handle_include(ap_bucket *in, request_rec *r, ap_filter_t *next,
-                          const char *error, int noexec)
+static int handle_include(include_ctx_t *ctx, ap_bucket_brigade **bb, request_rec *r,
+                          ap_filter_t *f, ap_bucket *head_ptr,
+                          ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    ap_bucket  *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
-    apr_off_t offset = strlen("include ") + strlen(STARTING_SEQUENCE);
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            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) {
+            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;
 
-                if (p != NULL) {
-                    error_fmt = "Recursive include of \"%s\" "
+                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 (!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 ( (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, &includes_module, r);
 
-            if (!error_fmt) {
-                /* The subrequest should inherit the remaining filters from 
-                 * this request. */
-                rr->output_filters = next;
-                if (ap_run_sub_req(rr)) {
-                    error_fmt = "unable to include \"%s\" in parsed file %s";
+                if (!error_fmt) {
+                    SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx);
+/*
+                    rr->output_filters = f->next;
+*/                    if (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);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
                 }
-            }
-            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 */
-            if (rr != NULL
-               && ap_get_module_config(rr->request_config, &includes_module)
-                   != NESTED_INCLUDE_MAGIC) {
-               ap_destroy_sub_req(rr);
+           /* destroy the sub request if it's not a nested include (crumb) */
+                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 {
@@ -879,7 +1012,8 @@ static apr_status_t build_argv_list(char ***argv, request_rec *r, apr_pool_t *p)
 
 
 
-static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
+static int include_cmd(include_ctx_t *ctx, ap_bucket_brigade **bb, char *s,
+                       request_rec *r, ap_filter_t *f)
 {
     include_cmd_arg arg;
     apr_procattr_t *procattr;
@@ -908,7 +1042,7 @@ static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
 
         apr_table_setn(env, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
 
-        pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r);
+        pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r, f->next);
         if (pa_req->filename) {
             apr_table_setn(env, "PATH_TRANSLATED",
                       apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info,
@@ -945,10 +1079,11 @@ static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
         rc = !APR_SUCCESS;
     }
     else {
+        SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx);
         build_argv_list(&argv, r, r->pool);
         argv[0] = apr_pstrdup(r->pool, s);
         procnew = apr_pcalloc(r->pool, sizeof(*procnew));
-        rc = ap_os_create_privileged_process(r, procnew, s, argv, ap_create_environment(r->pool, env), procattr, r->pool);
+        rc = apr_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. */
@@ -967,7 +1102,7 @@ static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
             bcgi = ap_brigade_create(r->pool);
             b = ap_bucket_create_pipe(file);
             AP_BRIGADE_INSERT_TAIL(bcgi, b);
-            ap_pass_brigade(next, bcgi);
+            ap_pass_brigade(f->next, bcgi);
         
             /* We can't close the pipe here, because we may return before the
              * full CGI has been sent to the network.  That's okay though,
@@ -979,200 +1114,237 @@ static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
     return 0;
 }
 
-static int handle_exec(ap_bucket *in, request_rec *r, const char *error,
-                       ap_filter_t *next)
+static int handle_exec(include_ctx_t *ctx, ap_bucket_brigade **bb, request_rec *r,
+                       ap_filter_t *f, ap_bucket *head_ptr,
+                       ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
+    char *tag     = NULL;
+    char *tag_val = NULL;
     char *file = r->filename;
+    ap_bucket  *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    apr_off_t offset = strlen("exec ") + strlen(STARTING_SEQUENCE);
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            return 1;
-        }
-        if (!strcmp(tag, "cmd")) {
-            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
-            if (include_cmd(parsed_string, r, next) == -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, next) == -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;
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        if (ctx->flags & FLAG_NO_EXEC) {
+            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                      "exec used but not allowed in %s", 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\" to tag exec in %s",
-                        tag, file);
-            ap_rputs(error, r);
+            while (1) {
+                get_tag_and_value(ctx, &tag, &tag_val, 1);
+                if (tag_val == NULL) {
+                    if (tag == NULL) {
+                        return (0);
+                    }
+                    else {
+                        return 1;
+                    }
+                }
+                if (!strcmp(tag, "cmd")) {
+                    parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
+                    if (include_cmd(ctx, bb, parsed_string, r, f) == -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);
+                        CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    }
+                    /* 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);
+                    SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx);
+                    if (include_cgi(parsed_string, r, f->next, head_ptr, inserted_head) == -1) {
+                        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                    "invalid CGI ref \"%s\" in %s", tag_val, file);
+                        CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                    }
+                    ap_chdir_file(r->filename);
+                }
+                else {
+                    ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                                "unknown parameter \"%s\" to tag exec in %s", tag, file);
+                    CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
+                }
+            }
         }
     }
-
+    return 0;
 }
 
-static int handle_echo(ap_bucket *in, request_rec *r, const char *error)
+static int handle_echo(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                           ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
+    char       *tag       = NULL;
+    char       *tag_val   = NULL;
+    const char *echo_text = NULL;
+    ap_bucket  *tmp_buck;
+    apr_ssize_t e_len, e_wrt;
     enum {E_NONE, E_URL, E_ENTITY} encode;
-    apr_off_t offset = strlen("echo ") + strlen(STARTING_SEQUENCE);
 
     encode = E_ENTITY;
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            return 1;
-        }
-        if (!strcmp(tag, "var")) {
-            const char *val = apr_table_get(r->subprocess_env, tag_val);
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            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);
+                int b_copy = 0;
 
-            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);
-               }
+                if (val) {
+                    switch(encode) {
+                    case E_NONE:   echo_text = val;  b_copy = 1;             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 = ap_bucket_create_heap(echo_text, e_len, 1, &e_wrt);
+                }
+                else {
+                    tmp_buck = ap_bucket_create_immortal("(none)", sizeof("none"));
+                }
+                AP_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_rputs("(none)", r);
+                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);
             }
-        }
-        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);
         }
     }
+    return 0;
 }
 
 #ifdef USE_PERL_SSI
-static int handle_perl(ap_bucket *in, request_rec *r, const char *error)
+static int handle_perl(include_ctx_t *ctx, request_rec *r)
 {
-    char tag[MAX_STRING_LEN];
+    char *tag     = NULL;
+    char *tag_val = NULL;
     char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
     SV *sub = Nullsv;
     AV *av = newAV();
-    apr_off_t offset = strlen("perl ") + strlen(STARTING_SEQUENCE);
 
-    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, &offset))) {
-            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));
+    if (ctx->flags & FLAG_PRINTING) {
+        if (ctx->flags & FLAG_NO_EXEC) {
+            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+                     "#perl SSI disallowed by IncludesNoExec in %s",
+                     r->filename);
+            return (DECLINED);
         }
-        else if (strnEQ(tag, "done", 4)) {
-            break;
+
+        while (1) {
+            get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                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));
+            }
         }
+
+        perl_stdout2client(r);
+        perl_setup_env(r);
+        perl_call_handler(sub, r, av);
     }
-    perl_stdout2client(r);
-    perl_setup_env(r);
-    perl_call_handler(sub, r, av);
-    return OK;
+
+    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_bucket *in, request_rec *r, char *error, char *tf,
-                         int *sizefmt)
+static int handle_config(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                           ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
+    char *tag     = NULL;
+    char *tag_val = NULL;
     char parsed_string[MAX_STRING_LEN];
     apr_table_t *env = r->subprocess_env;
-    apr_off_t offset = strlen("config ") + strlen(STARTING_SEQUENCE);
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset))) {
-            return 1;
-        }
-        if (!strcmp(tag, "errmsg")) {
-            parse_string(r, tag_val, error, MAX_STRING_LEN, 0);
-        }
-        else if (!strcmp(tag, "timefmt")) {
-            apr_time_t date = r->request_time;
+    *inserted_head = NULL;
+    if (ctx->flags & FLAG_PRINTING) {
+        while (1) {
+            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")) {
+                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;
 
-            parse_string(r, tag_val, tf, MAX_STRING_LEN, 0);
-            apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, tf, 0));
-            apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, tf, 1));
-            apr_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;
+                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(parsed_string, "abbrev")) {
-                *sizefmt = SIZEFMT_KMG;
+            else if (!strcmp(tag, "sizefmt")) {
+                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 {
+                ap_bucket  *tmp_buck;
+
+                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);
             }
-        }
-        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, apr_finfo_t *finfo, const char *error)
+                     char *tag_val, apr_finfo_t *finfo)
 {
     char *to_send = tag_val;
     request_rec *rr = NULL;
@@ -1187,7 +1359,7 @@ 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);
+            rr = ap_sub_req_lookup_file(tag_val, r, NULL);
 
             if (rr->status == HTTP_OK && rr->finfo.protection != 0) {
                 to_send = rr->filename;
@@ -1208,7 +1380,6 @@ static int find_file(request_rec *r, const char *directive, const char *tag,
              * otherwise
              */
             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, to_send, r->filename);
-            ap_rputs(error, r);
         }
 
         if (rr) ap_destroy_sub_req(rr);
@@ -1216,7 +1387,7 @@ 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);
+        rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
 
         if (rr->status == HTTP_OK && rr->finfo.protection != 0) {
             memcpy((char *) finfo, (const char *) &rr->finfo,
@@ -1229,7 +1400,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;
         }
@@ -1238,71 +1408,142 @@ 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;
     }
 }
 
+#define NEG_SIGN  "    -"
+#define ZERO_K    "   0k"
+#define ONE_K     "   1k"
+
+static void generate_size(apr_ssize_t size, char *buff, apr_ssize_t buff_size)
+{
+    /* XXX: this -1 thing is a gross hack */
+    if (size == (apr_ssize_t)-1) {
+       memcpy (buff, NEG_SIGN, sizeof(NEG_SIGN)+1);
+    }
+    else if (!size) {
+       memcpy (buff, ZERO_K, sizeof(ZERO_K)+1);
+    }
+    else if (size < 1024) {
+       memcpy (buff, ONE_K, sizeof(ONE_K)+1);
+    }
+    else if (size < 1048576) {
+        apr_snprintf(buff, buff_size, "%4" APR_SSIZE_T_FMT "k", (size + 512) / 1024);
+    }
+    else if (size < 103809024) {
+        apr_snprintf(buff, buff_size, "%4.1fM", size / 1048576.0);
+    }
+    else {
+        apr_snprintf(buff, buff_size, "%4" APR_SSIZE_T_FMT "M", (size + 524288) / 1048576);
+    }
+}
 
-static int handle_fsize(ap_bucket *in, request_rec *r, const char *error, int sizefmt)
+static int handle_fsize(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                        ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    apr_finfo_t finfo;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_ssize_t  s_len, s_wrt;
+    ap_bucket   *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    apr_off_t offset = strlen("fsize ") + strlen(STARTING_SEQUENCE);
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            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) {
+            get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return 0;
                 }
                 else {
-                    int l, x;
-                    apr_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 {
+                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)) {
+                        generate_size(finfo.size, buff, sizeof(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 = ap_bucket_create_heap(buff, s_len, 1, &s_wrt);
+                    AP_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_bucket *in, request_rec *r, const char *error, const char *tf)
+static int handle_flastmod(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                           ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    apr_finfo_t finfo;
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    apr_finfo_t  finfo;
+    apr_ssize_t  t_len, t_wrt;
+    ap_bucket   *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    apr_off_t offset = strlen("flastmod ") + strlen(STARTING_SEQUENCE);
 
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            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) {
+            get_tag_and_value(ctx, &tag, &tag_val, 1);
+            if (tag_val == NULL) {
+                if (tag == NULL) {
+                    return 0;
+                }
+                else {
+                    return 1;
+                }
+            }
+            else {
+                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 = ap_bucket_create_heap(t_val, t_len, 1, &t_wrt);
+                    AP_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)
@@ -1335,11 +1576,13 @@ 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) {
@@ -1426,57 +1669,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 (apr_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';
@@ -1494,7 +1742,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;
@@ -1505,7 +1754,11 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
     char buffer[MAX_STRING_LEN];
     apr_pool_t *expr_pool;
     int retval = 0;
+    apr_ssize_t debug_pos = 0;
 
+    debug[debug_pos] = '\0';
+    *was_error       = 0;
+    *was_unmatched   = 0;
     if ((parse = expr) == (char *) NULL) {
         return (0);
     }
@@ -1519,14 +1772,15 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
                                            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;
@@ -1561,7 +1815,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;
@@ -1569,13 +1823,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 */
@@ -1600,7 +1856,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;
@@ -1621,7 +1877,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;
@@ -1645,7 +1903,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;
@@ -1671,13 +1929,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 */
@@ -1702,7 +1962,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;
@@ -1723,7 +1983,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) {
@@ -1736,14 +1998,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;
@@ -1769,7 +2033,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;
@@ -1798,7 +2062,9 @@ 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);
            apr_cpystrn(current->token.value, buffer, sizeof(current->token.value));
@@ -1810,14 +2076,16 @@ 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) {
@@ -1851,10 +2119,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;
@@ -1863,8 +2131,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;
@@ -1873,7 +2141,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) ||
@@ -1882,7 +2152,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;
             }
             parse_string(r, current->left->token.value,
@@ -1903,12 +2173,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,
@@ -1916,8 +2188,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,
@@ -1927,8 +2201,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;
@@ -1938,7 +2212,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) ||
@@ -1947,7 +2223,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;
             }
             parse_string(r, current->left->token.value,
@@ -1959,8 +2235,10 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
             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,
@@ -1981,8 +2259,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;
@@ -2000,8 +2278,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;
@@ -2019,8 +2297,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;
@@ -2030,20 +2308,20 @@ 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;
         }
     }
@@ -2054,259 +2332,360 @@ static int parse_expr(request_rec *r, const char *expr, const char *error)
     return (retval);
 }
 
-static int handle_if(ap_bucket *in, request_rec *r, const char *error,
-                     int *conditional_status, int *printing)
-{
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char *expr;
-    apr_off_t offset = strlen("if ") + strlen(STARTING_SEQUENCE);
-
-    expr = NULL;
-    while (1) {
-        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset);
-        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);
+
+#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_ssize_t c_wrt;                                                     \
+                                                                           \
+    if (cntx->flags & FLAG_COND_TRUE) {                                    \
+        cont_txt[31] = '1';                                                \
+    }                                                                      \
+    memcpy(&cond_txt[5], tag_text, sizeof(tag_text));                      \
+    t_buck = ap_bucket_create_heap(cond_txt, sizeof(cond_txt), 1, &c_wrt); \
+    AP_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_ssize_t b_wrt;                                                   \
+    if (d_buf[0] != '\0') {                                              \
+        t_buck = ap_bucket_create_heap(d_buf, strlen(d_buf), 1, &b_wrt); \
+        AP_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, request_rec *r, ap_bucket *head_ptr,
+                     ap_bucket **inserted_head)
+{
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *expr    = NULL;
+    int   expr_ret, was_error, was_unmatched;
+    ap_bucket *tmp_buck;
+    char debug_buf[MAX_DEBUG_SIZE];
+
+    *inserted_head = NULL;
+    if (!ctx->flags & FLAG_PRINTING) {
+        ctx->if_nesting_level++;
+    }
+    else {
+        while (1) {
+            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_ssize_t d_len = 0, d_wrt = 0;
+                    d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
+                    tmp_buck = ap_bucket_create_heap(debug_buf, d_len, 1, &d_wrt);
+                    AP_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_bucket *in, request_rec *r, const char *error,
-                       int *conditional_status, int *printing)
+static int handle_elif(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                       ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    char *expr;
-    apr_off_t offset = strlen("elif ") + strlen(STARTING_SEQUENCE);
-
-    expr = NULL;
-    while (1) {
-        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset);
-        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;
+    ap_bucket *tmp_buck;
+    char debug_buf[MAX_DEBUG_SIZE];
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        while (1) {
+            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);
-#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;
+            else if (!strcmp(tag, "expr")) {
+                expr = tag_val;
 #ifdef DEBUG_INCLUDE
-            ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
+                if (1) {
+                    apr_ssize_t d_len = 0, d_wrt = 0;
+                    d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
+                    tmp_buck = ap_bucket_create_heap(debug_buf, d_len, 1, &d_wrt);
+                    AP_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_bucket *in, request_rec *r, const char *error,
-                       int *conditional_status, int *printing)
+static int handle_else(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                       ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    apr_off_t offset = strlen("else ") + strlen(STARTING_SEQUENCE);
-
-    if (!get_tag(r->pool, in, tag, sizeof(tag), 1, &offset)) {
-        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;
+    ap_bucket *tmp_buck;
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        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_bucket *in, request_rec *r, const char *error,
-                        int *conditional_status, int *printing)
+static int handle_endif(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                        ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    apr_off_t offset = strlen("endif ") + strlen(STARTING_SEQUENCE);
-
-    if (!get_tag(r->pool, in, tag, sizeof(tag), 1, &offset)) {
-        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;
+    ap_bucket *tmp_buck;
+
+    *inserted_head = NULL;
+    if (!ctx->if_nesting_level) {
+        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_bucket *in, request_rec *r, const char *error)
+static int handle_set(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                        ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
+    char *tag     = NULL;
+    char *tag_val = NULL;
+    char *var     = NULL;
+    ap_bucket *tmp_buck;
     char parsed_string[MAX_STRING_LEN];
-    char *tag_val;
-    char *var;
-    apr_off_t offset = strlen("set ") + strlen(STARTING_SEQUENCE);
 
-    var = (char *) NULL;
-    while (1) {
-        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-            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) {
+            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);
+                }
+                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);
-            apr_table_setn(r->subprocess_env, var, apr_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_bucket *in, request_rec *r, const char *error)
+static int handle_printenv(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
+                           ap_bucket **inserted_head)
 {
-    char tag[MAX_STRING_LEN];
-    char *tag_val;
-    apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
-    apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
-    int i;
-    apr_off_t offset = strlen("printenv ") + strlen(STARTING_SEQUENCE);
-
-    if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
-        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;
+    ap_bucket *tmp_buck;
+
+    if (ctx->flags & FLAG_PRINTING) {
+        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_ssize_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 = ap_bucket_create_heap(key_text, k_len, 1, &t_wrt);
+                AP_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                if (*inserted_head == NULL) {
+                    *inserted_head = tmp_buck;
+                }
+                /*            =                                            */
+                tmp_buck = ap_bucket_create_immortal("=", 1);
+                AP_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                /*              Value_text                                 */
+                tmp_buck = ap_bucket_create_heap(val_text, v_len, 1, &t_wrt);
+                AP_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
+                /*                        newline...                       */
+                tmp_buck = ap_bucket_create_immortal("\n", 1);
+                AP_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. */
-
-typedef struct include_ctx {
-    ap_bucket_brigade *bb;
-} include_ctx;
 static void send_parsed_content(ap_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 sizefmt;
-    int if_nesting;
-    int printing;
-    int conditional_status;
+    include_ctx_t *ctx = f->ctx;
     ap_bucket *dptr = AP_BRIGADE_FIRST(*bb);
-    ap_bucket *tagbuck, *dptr2;
-    ap_bucket *endsec;
+    ap_bucket *tmp_dptr;
     ap_bucket_brigade *tag_and_after;
-    include_ctx *ctx;
     int ret;
 
-    apr_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error));
-    apr_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);
     if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
         char *arg_copy = apr_pstrdup(r->pool, r->args);
@@ -2317,128 +2696,303 @@ static void send_parsed_content(ap_bucket_brigade **bb, request_rec *r,
                   ap_escape_shell_cmd(r->pool, arg_copy));
     }
 
-    if (!f->ctx) {
-        f->ctx = ctx = apr_pcalloc(r->pool, sizeof(f->ctx));
-        ctx->bb = ap_brigade_create(r->pool);
-    }
-    else {
-        ctx = f->ctx;
-        AP_BRIGADE_CONCAT(*bb, ctx->bb);
-    }
+    while (dptr != AP_BRIGADE_SENTINEL(*bb)) {
+        /* State to check for the STARTING_SEQUENCE. */
+        if ((ctx->state == PRE_HEAD) || (ctx->state == PARSE_HEAD)) {
+            int do_cleanup = 0;
+            apr_ssize_t cleanup_bytes = ctx->parse_pos;
 
-    AP_BRIGADE_FOREACH(dptr, *bb) {
-        if ((tagbuck = find_string(dptr, STARTING_SEQUENCE, AP_BRIGADE_LAST(*bb))) != NULL) {
-            dptr2 = tagbuck;
-            dptr = tagbuck;
-            endsec = find_string(dptr2, ENDING_SEQUENCE, AP_BRIGADE_LAST(*bb));
-            if (endsec == NULL) {
-                ap_save_brigade(f, &ctx->bb, bb);
-            }
-             
-            /* At this point, everything between tagbuck and endsec is an SSI
-             * directive, we just have to deal with it now.
+            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 (get_directive(tagbuck, 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;
-            }
-            tag_and_after = ap_brigade_split(*bb, dptr);
-            ap_pass_brigade(f->next, *bb); /* process what came before the tag */
-            *bb = tag_and_after;
-            if (!strcmp(directive, "if")) {
-                if (!printing) {
-                    if_nesting++;
-                }
-                else {
-                    ret = handle_if(tagbuck, r, error, &conditional_status,
-                                    &printing);
-                    if_nesting = 0;
-                }
-                continue;
-            }
-            else if (!strcmp(directive, "else")) {
-                if (!if_nesting) {
-                    ret = handle_else(tagbuck, r, error, &conditional_status,
-                                      &printing);
+            if ((do_cleanup) && (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
+                ap_bucket *tmp_bkt;
+
+                tmp_bkt = ap_bucket_create_immortal(STARTING_SEQUENCE, cleanup_bytes);
+                AP_BRIGADE_INSERT_HEAD(*bb, tmp_bkt);
+
+                while (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    tmp_bkt = AP_BRIGADE_FIRST(ctx->ssi_tag_brigade);
+                    AP_BUCKET_REMOVE(tmp_bkt);
+                    ap_bucket_destroy(tmp_bkt);
                 }
-                continue;
             }
-            else if (!strcmp(directive, "elif")) {
-                if (!if_nesting) {
-                    ret = handle_elif(tagbuck, r, error, &conditional_status,
-                                      &printing);
+
+            /* 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 != AP_BRIGADE_SENTINEL(*bb))) {
+                while ((dptr != AP_BRIGADE_SENTINEL(*bb)) &&
+                       (dptr != tmp_dptr)) {
+                    ap_bucket *free_bucket = dptr;
+
+                    dptr = AP_BUCKET_NEXT (dptr);
+                    AP_BUCKET_REMOVE(free_bucket);
+                    ap_bucket_destroy(free_bucket);
                 }
-                continue;
             }
-            else if (!strcmp(directive, "endif")) {
-                if (!if_nesting) {
-                    ret = handle_endif(tagbuck, r, error, &conditional_status,
-                                       &printing);
+
+            /* Adjust the current bucket position based on what was found... */
+            if ((tmp_dptr != NULL) && (ctx->state == PARSE_TAG)) {
+                if (ctx->tag_start_bucket != NULL) {
+                    dptr = ctx->tag_start_bucket;
                 }
                 else {
-                    if_nesting--;
+                    dptr = AP_BRIGADE_SENTINEL(*bb);
+                }
+            }
+            else if (tmp_dptr == NULL) { /* There was no possible SSI tag in the */
+                dptr = AP_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
+            }
+        }
+
+        /* State to check for the ENDING_SEQUENCE. */
+        if (((ctx->state == PARSE_TAG) || (ctx->state == PARSE_TAIL)) &&
+            (dptr != AP_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 (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    tag_and_after = ap_brigade_split(*bb, dptr);
+                    AP_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
+                    *bb = tag_and_after;
                 }
-                continue;
             }
-            if (!printing) {
-                continue;
+            else {
+                dptr = AP_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);
+        }
+
+        /* State to processed the directive... */
+        if (ctx->state == PARSED) {
+            ap_bucket    *content_head = NULL, *tmp_bkt;
+            char          tmp_buf[TMP_BUF_SIZE];
+            char         *directive_str = NULL;
+            dir_token_id  directive_token;
+
+            /* 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 (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    while (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                        tmp_bkt = AP_BRIGADE_FIRST(ctx->ssi_tag_brigade);
+                        AP_BUCKET_REMOVE(tmp_bkt);
+                        ap_bucket_destroy(tmp_bkt);
                     }
                 }
                 else {
-                    ret = handle_exec(tagbuck, r, error, f->next);
+                    do {
+                        tmp_bkt  = tmp_dptr;
+                        tmp_dptr = AP_BUCKET_NEXT (tmp_dptr);
+                        AP_BUCKET_REMOVE(tmp_bkt);
+                        ap_bucket_destroy(tmp_bkt);
+                    } while ((tmp_dptr != dptr) &&
+                             (tmp_dptr != AP_BRIGADE_SENTINEL(*bb)));
                 }
+
+                return;
             }
-            else if (!strcmp(directive, "config")) {
-                ret = handle_config(tagbuck, r, error, timefmt, &sizefmt);
-            }
-            else if (!strcmp(directive, "set")) {
-                ret = handle_set(tagbuck, r, error);
-            }
-            else if (!strcmp(directive, "include")) {
-                ret = handle_include(tagbuck, r, f->next, error, noexec);
-            }
-            else if (!strcmp(directive, "echo")) {
-                ret = handle_echo(tagbuck, r, error);
-            }
-            else if (!strcmp(directive, "fsize")) {
-                ret = handle_fsize(tagbuck, r, error, sizefmt);
-            }
-            else if (!strcmp(directive, "flastmod")) {
-                ret = handle_flastmod(tagbuck, r, error, timefmt);
-            }
-            else if (!strcmp(directive, "printenv")) {
-                ret = handle_printenv(tagbuck, r, error);
-            }
-#ifdef USE_PERL_SSI
-            else if (!strcmp(directive, "perl")) {
-                ret = handle_perl(tagbuck, r, error);
-            }
+
+            /* Even if I don't generate any content, I know at this point that
+             *   I will at least remove the discovered SSI tag, thereby making
+             *   the content shorter than it was. This is the safest point I can
+             *   find to unset this field.
+             */
+            apr_table_unset(f->r->headers_out, "Content-Length");
+
+            /* 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!
+             */
+            directive_str = get_directive(ctx, &directive_token);
+
+            switch (directive_token) {
+            case TOK_IF:
+                ret = handle_if(ctx, r, dptr, &content_head);
+                break;
+            case TOK_ELSE:
+                ret = handle_else(ctx, r, dptr, &content_head);
+                break;
+            case TOK_ELIF:
+                ret = handle_elif(ctx, r, dptr, &content_head);
+                break;
+            case TOK_ENDIF:
+                ret = handle_endif(ctx, r, dptr, &content_head);
+                break;
+            case TOK_EXEC:
+                ret = handle_exec(ctx, bb, r, f, dptr, &content_head);
+                break;
+            case TOK_INCLUDE:
+                ret = handle_include(ctx, bb, r, f, dptr, &content_head);
+                break;
+#ifdef USE_PERL_SSI  /* Leaving this as is for now... */
+            case TOK_PERL:
+                SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx);
+                ret = handle_perl(ctx, r);
+                break;
 #endif
-            else {
+            case TOK_SET:
+                ret = handle_set(ctx, r, dptr, &content_head);
+                break;
+            case TOK_ECHO:
+                ret = handle_echo(ctx, r, dptr, &content_head);
+                break;
+            case TOK_FSIZE:
+                ret = handle_fsize(ctx, r, dptr, &content_head);
+                break;
+            case TOK_CONFIG:
+                ret = handle_config(ctx, r, dptr, &content_head);
+                break;
+            case TOK_FLASTMOD:
+                ret = handle_flastmod(ctx, r, dptr, &content_head);
+                break;
+            case TOK_PRINTENV:
+                ret = handle_printenv(ctx, r, dptr, &content_head);
+                break;
+
+            case TOK_UNKNOWN:
+            default:
                 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);
+                              "unknown directive \"%s\" in parsed doc %s",
+                              directive_str, 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 (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                while (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    tmp_bkt = AP_BRIGADE_FIRST(ctx->ssi_tag_brigade);
+                    AP_BUCKET_REMOVE(tmp_bkt);
+                    ap_bucket_destroy(tmp_bkt);
+                }
+            }
+            else {
+                do {
+                    tmp_bkt  = tmp_dptr;
+                    tmp_dptr = AP_BUCKET_NEXT (tmp_dptr);
+                    AP_BUCKET_REMOVE(tmp_bkt);
+                    ap_bucket_destroy(tmp_bkt);
+                } while ((tmp_dptr != content_head) &&
+                         (tmp_dptr != AP_BRIGADE_SENTINEL(*bb)));
+            }
+            if (ctx->combined_tag == tmp_buf) {
+                memset (ctx->combined_tag, '\0', ctx->tag_length);
+                ctx->combined_tag = NULL;
+            }
+
+            /* 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;
+
+            if (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                while (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+                    tmp_bkt = AP_BRIGADE_FIRST(ctx->ssi_tag_brigade);
+                    AP_BUCKET_REMOVE(tmp_bkt);
+                    ap_bucket_destroy(tmp_bkt);
                 }
             }
-            *bb = ap_brigade_split(tag_and_after, endsec); 
-            dptr = AP_BUCKET_PREV(endsec);
+
+            ctx->state     = PRE_HEAD;
         }
-        else {
-            return;
+    }
+
+    /* 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 != AP_BRIGADE_SENTINEL(*bb)) &&
+            (!(ctx->flags & FLAG_PRINTING))) {
+            ap_bucket *free_bucket;
+            do {
+                free_bucket = dptr;
+                dptr = AP_BUCKET_NEXT (dptr);
+                AP_BUCKET_REMOVE(free_bucket);
+                ap_bucket_destroy(free_bucket);
+            } while (dptr != AP_BRIGADE_SENTINEL(*bb));
+        }
+        else { /* Otherwise pass it along... */
+            ap_pass_brigade(f->next, *bb);  /* No SSI tags in this brigade... */
+        }
+    }
+    else if (ctx->state == PARSED) {     /* Invalid internal condition... */
+        ap_bucket *content_head, *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, AP_BRIGADE_FIRST(*bb), content_head);
+    }
+    else {                 /* Entire brigade is middle chunk of SSI tag... */
+        if (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
+            AP_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
+        }
+        else {             /* End of brigade contains part of SSI tag... */
+            if (ctx->head_start_index > 0) {
+                ap_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
+                ctx->head_start_bucket = AP_BUCKET_NEXT(ctx->head_start_bucket);
+                ctx->head_start_index  = 0;
+            }
+                           /* Set aside tag, pass pre-tag... */
+            tag_and_after = ap_brigade_split(*bb, ctx->head_start_bucket);
+            ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after);
+            ap_pass_brigade(f->next, *bb);
         }
     }
 }
@@ -2490,6 +3044,7 @@ static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
 static int includes_filter(ap_filter_t *f, ap_bucket_brigade *b)
 {
     request_rec *r = f->r;
+    include_ctx_t *ctx = f->ctx;
     enum xbithack *state =
     (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module);
     request_rec *parent;
@@ -2502,6 +3057,26 @@ static int includes_filter(ap_filter_t *f, ap_bucket_brigade *b)
         return ap_pass_brigade(f->next, b);
     }
 
+    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 = ap_brigade_create(f->c->pool);
+
+            apr_cpystrn(ctx->error_str, DEFAULT_ERROR_MSG,   sizeof(ctx->error_str));
+            apr_cpystrn(ctx->time_str,  DEFAULT_TIME_FORMAT, sizeof(ctx->time_str));
+            ctx->error_length = strlen(ctx->error_str);
+        }
+        else {
+            ap_pass_brigade(f->next, b);
+            return APR_ENOMEM;
+        }
+    }
+
     if ((*state == xbithack_full)
 #if !defined(OS2) && !defined(WIN32)
     /*  OS/2 dosen't support Groups. */
@@ -2538,8 +3113,8 @@ static int includes_filter(ap_filter_t *f, ap_bucket_brigade *b)
      * fix this, except to put alarm support into BUFF. -djg
      */
 
+
     send_parsed_content(&b, r, f);
-    ap_pass_brigade(f->next, b);
 
     if (parent) {
        /* signify that the sub request should not be killed */
@@ -2570,10 +3145,6 @@ module AP_MODULE_DECLARE_DATA includes_module =
     NULL,                       /* server config */
     NULL,                       /* merge server config */
     includes_cmds,              /* command apr_table_t */
-#if 0
-    includes_handlers,          /* handlers */
-#else
     NULL,                       /* handlers */
-#endif
     register_hooks             /* register hooks */
 };
diff --git a/modules/filters/mod_include.h b/modules/filters/mod_include.h
new file mode 100644 (file)
index 0000000..554d999
--- /dev/null
@@ -0,0 +1,206 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ * Portions of this software are based upon public domain software
+ * originally written at the National Center for Supercomputing Applications,
+ * University of Illinois, Urbana-Champaign.
+ */
+
+#ifndef _MOD_INCLUDE_H
+#define _MOD_INCLUDE_H 1
+
+
+
+#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
+#define TMP_BUF_SIZE 1024
+#ifdef CHARSET_EBCDIC
+#define RAW_ASCII_CHAR(ch)  apr_xlate_conv_byte(ap_hdrs_from_ascii, (unsigned char)ch)
+#else /*CHARSET_EBCDIC*/
+#define RAW_ASCII_CHAR(ch)  (ch)
+#endif /*CHARSET_EBCDIC*/
+
+module AP_MODULE_DECLARE_DATA includes_module;
+
+/* 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)
+
+
+/****************************************************************************
+ * Used to keep context information during parsing of a request for SSI tags.
+ * This is especially useful if the tag stretches across multiple buckets or
+ * brigades. This keeps track of which buckets need to be replaced with the
+ * content generated by the SSI tag.
+ *
+ * state: PRE_HEAD - State prior to finding the first character of the 
+ *                   STARTING_SEQUENCE. Next state is PARSE_HEAD.
+ *        PARSE_HEAD - State entered once the first character of the
+ *                     STARTING_SEQUENCE is found and exited when the
+ *                     the full STARTING_SEQUENCE has been matched or
+ *                     a match failure occurs. Next state is PRE_HEAD
+ *                     or PARSE_TAG.
+ *        PARSE_TAG - State entered once the STARTING sequence has been
+ *                    matched. It is exited when the first character in
+ *                    ENDING_SEQUENCE is found. Next state is PARSE_TAIL.
+ *        PARSE_TAIL - State entered from PARSE_TAG state when the first
+ *                     character in ENDING_SEQUENCE is encountered. This
+ *                     state is exited when the ENDING_SEQUENCE has been
+ *                     completely matched, or when a match failure occurs.
+ *                     Next state is PARSE_TAG or PARSED.
+ *        PARSED - State entered from PARSE_TAIL once the complete 
+ *                 ENDING_SEQUENCE has been matched. The SSI tag is
+ *                 processed and the SSI buckets are replaced with the
+ *                 SSI content during this state.
+ * parse_pos: Current matched position within the STARTING_SEQUENCE or
+ *            ENDING_SEQUENCE during the PARSE_HEAD and PARSE_TAIL states.
+ *            This is especially useful when the sequence spans brigades.
+ * X_start_bucket: These point to the buckets containing the first character
+ *                 of the STARTING_SEQUENCE, the first non-whitespace
+ *                 character of the tag, and the first character in the
+ *                 ENDING_SEQUENCE (head_, tag_, and tail_ respectively).
+ *                 The buckets are kept intact until the PARSED state is
+ *                 reached, at which time the tag is consolidated and the
+ *                 buckets are released. The buckets that these point to
+ *                 have all been set aside in the ssi_tag_brigade (along
+ *                 with all of the intervening buckets).
+ * X_start_index: The index points within the specified bucket contents
+ *                where the first character of the STARTING_SEQUENCE,
+ *                the first non-whitespace character of the tag, and the
+ *                first character in the ENDING_SEQUENCE can be found
+ *                (head_, tag_, and tail_ respectively).
+ * combined_tag: Once the PARSED state is reached the tag is collected from
+ *               the bucket(s) in the ssi_tag_brigade into this contiguous
+ *               buffer. The buckets in the ssi_tag_brigade are released
+ *               and the tag is processed.
+ * curr_tag_pos: Ptr to the combined_tag buffer indicating the current
+ *               parse position.
+ * tag_length: The number of bytes in the actual tag (excluding the
+ *             STARTING_SEQUENCE, leading and trailing whitespace,
+ *             and ENDING_SEQUENCE). This length is computed as the
+ *             buckets are parsed and set aside during the PARSE_TAG state.
+ * ssi_tag_brigade: The temporary brigade used by this filter to set aside
+ *                  the buckets containing parts of the ssi tag and headers.
+ */
+typedef enum {PRE_HEAD, PARSE_HEAD, PARSE_TAG, PARSE_TAIL, PARSED} states;
+typedef struct include_filter_ctx {
+    states       state;
+    long         flags;    /* See the FLAG_XXXXX definitions. */
+    int          if_nesting_level;
+    apr_ssize_t  parse_pos;
+    
+    ap_bucket   *head_start_bucket;
+    apr_ssize_t  head_start_index;
+
+    ap_bucket   *tag_start_bucket;
+    apr_ssize_t  tag_start_index;
+
+    ap_bucket   *tail_start_bucket;
+    apr_ssize_t  tail_start_index;
+
+    char        *combined_tag;
+    char        *curr_tag_pos;
+    apr_ssize_t  tag_length;
+
+    apr_ssize_t  error_length;
+    char         error_str[MAX_STRING_LEN];
+    char         time_str[MAX_STRING_LEN];
+
+    ap_bucket_brigade *ssi_tag_brigade;
+} include_ctx_t;
+
+/* These flags are used to set flag bits. */
+#define FLAG_PRINTING         0x00000001  /* Printing conditional lines. */
+#define FLAG_COND_TRUE        0x00000002  /* Conditional eval'd to true. */
+#define FLAG_SIZE_IN_BYTES    0x00000004  /* Sizes displayed in bytes.   */
+#define FLAG_NO_EXEC          0x00000008  /* No Exec in current context. */
+
+/* These flags are used to clear flag bits. */
+#define FLAG_SIZE_ABBREV      0xFFFFFFFB  /* Reset SIZE_IN_BYTES bit.    */
+#define FLAG_CLEAR_PRINT_COND 0xFFFFFFFC  /* Reset PRINTING and COND_TRUE*/
+#define FLAG_CLEAR_PRINTING   0xFFFFFFFE  /* Reset just PRINTING bit.    */
+
+typedef enum {TOK_UNKNOWN, TOK_IF, TOK_SET, TOK_ECHO, TOK_ELIF, TOK_ELSE,
+              TOK_EXEC, TOK_PERL, TOK_ENDIF, TOK_FSIZE, TOK_CONFIG,
+              TOK_INCLUDE, TOK_FLASTMOD, TOK_PRINTENV} dir_token_id;
+
+#define CREATE_ERROR_BUCKET(cntx, t_buck, h_ptr, ins_head)        \
+{                                                                 \
+    apr_ssize_t e_wrt;                                            \
+    t_buck = ap_bucket_create_heap(cntx->error_str,               \
+                                   ctx->error_length, 1, &e_wrt); \
+    AP_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                       \
+                                                                  \
+    if (ins_head == NULL) {                                       \
+        ins_head = t_buck;                                        \
+    }                                                             \
+}
+
+#define SPLIT_AND_PASS_PRETAG_BUCKETS(brgd, cntxt)               \
+if ((AP_BRIGADE_EMPTY(cntxt->ssi_tag_brigade)) &&                \
+    (cntxt->head_start_bucket != NULL)) {                        \
+    ap_bucket_brigade *tag_plus;                                 \
+                                                                 \
+    tag_plus = ap_brigade_split(brgd, cntxt->head_start_bucket); \
+    ap_pass_brigade(f->next, brgd);                              \
+    brgd = tag_plus;                                             \
+}
+
+#endif /* MOD_INCLUDE */