httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Paul J. Reder" <rede...@raleigh.ibm.com>
Subject [Patch]: mod_include rewrite: take two.
Date Wed, 22 Nov 2000 08:45:48 GMT
The following patch is an update of my previous mod_include patch. This one
addresses all of Ryan's and Greg's comments except the hash/hook changes.

There was one problem that I encountered that could use some explanation.
Ryan suggested that I remove the rr->output_filters = f->next; statements.
When I did this I got core dumps due to what appeared to be a reentrancy
problem. With these lines in the code seems to work fine. I assume that I
am doing something else wrong that is being masked by this but don't have
the filter/brigade background to understand what.

I am going on vacation today (Wednesday) until Saturday night. I will try
to check my mail before I leave (around 8:00am). Hopefully things are
stable enough with this version that we can move forward, and I can go
on vacation with a clear conscience.

-- 
Paul J. Reder
-----------------------------------------------------------
"The strength of the Constitution lies entirely in the determination of each
citizen to defend it.  Only if every single citizen feels duty bound to do
his share in this defense are the constitutional rights secure."
-- Albert Einstein





standard/mod_include.h
=========================================================================

/* ====================================================================
 * 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_H */







Index: standard/mod_include.c
===================================================================
RCS file: /home/cvspublic/apache-2.0/src/modules/standard/mod_include.c,v
retrieving revision 1.74
diff -r1.74 mod_include.c
83,87d82
< #undef VOIDUSED
< #ifdef USE_SFIO
< #undef USE_SFIO
< #define USE_STDIO
< #endif
102c97
< #include "ap_mpm.h"
---
> #include "mod_include.h"
115,135d109
< #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)
< 
184,186c158,163
< #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)
188c165
<     apr_size_t len;
---
>     apr_ssize_t len;
191c168,170
<     int state = 0;
---
>     const char *str = STARTING_SEQUENCE;
> 
>     *do_cleanup = 0;
204,205c183,189
<             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++;
208,211c192,219
<                 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.
213,214c221,226
<                     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;
217c229,284
<                         ap_bucket_split(dptr, c - buf);
---
>                         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 {
>                         ctx->tag_length++;
219d285
<                     return AP_BUCKET_NEXT(dptr);
222,229c288,322
<                     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;
>                          }
>                     }
235c328
<     } while (AP_BUCKET_PREV(dptr) != end);
---
>     } while (dptr != AP_BRIGADE_SENTINEL(bb));
238a332,394
> /* 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);
> }
> 
334,336c490,495
<  * 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]
339,345c498
< static char *get_tag(apr_pool_t *p, ap_bucket *in, char *tag, int tagbuf_len, int dodecode, apr_off_t *offset)
< {
<     ap_bucket *dptr = in;
<     const char *c;
<     const char *str;
<     apr_size_t length; 
<     char *t = tag, *tag_val, term;
---
> #define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
347,348c500,505
<     /* makes code below a little less cluttered */
<     --tagbuf_len;
---
> static void get_tag_and_value(include_ctx_t *ctx, char **tag,
>                               char **tag_val, int dodecode)
> {
>     char *c = ctx->curr_tag_pos;
>     int   shift_val = 0; 
>     char  term = '\0';
350,365c507,509
<     /* 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);
---
>     *tag_val = NULL;
>     SKIP_TAG_WHITESPACE(c);
>     *tag = c;             /* First non-whitespace character (could be NULL). */
367,368c511,512
<     /* 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... */
370,388d513
<         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 */
391,404c516,533
<     /* 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;
>         }
>         ctx->curr_tag_pos = c;
>         return;      /* We have found the end of the buffer. */
>     }                /* We might have a tag, but definitely no value. */
> 
>     if (*c == '=') {
>         *c++ = '\0';     /* Overwrite the '=' with a terminating byte after tag. */
>     }
>     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. */
406,415c535,536
<     }
< 
<     *t++ = '\0';
<     tag_val = t;
< 
<     while (apr_isspace(*c)) {
<         c++;
<         if (c == '\0') {
<             ap_bucket_read(dptr, &str, &length, 0);
<             c = str;
---
>         else {
>             c++; /* Skip the equals sign. */
418,431d538
<     if (*c != '=') {
<         /* XXX may need to ungetc() here (see pre-bucketized code) */
<         return NULL;
<     }
< 
<     do {
<         c++;
<         if (c == '\0') {
<             ap_bucket_read(dptr, &str, &length, 0);
<             c = str;
<         }
<     } while (apr_isspace(*c));
< 
<     /* we should allow a 'name' as a value */
433,434c540,542
<     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. */
436,449c544,549
<     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. */
451,456c551,555
<             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;
459,460c558,561
<         else if (*c == term) {
<             break;
---
> 
>         c++;
>         if (shift_val > 0) {
>             *(c-shift_val) = *c;
462d562
<         *(t++) = *c;
464c564,566
<     *t = '\0';
---
>     
>     *c++ = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
>     ctx->curr_tag_pos = c;
466c568
<         decodehtml(tag_val);
---
>         decodehtml(*tag_val);
468,469c570,571
<     *offset = c - str + 1;
<     return apr_pstrdup(p, tag_val);
---
> 
>     return;
472c574
< 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)
474,478c576,578
<     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;
480,481c580
<     /* make room for nul terminator */
<     --len;
---
>     SKIP_TAG_WHITESPACE(c);
483,496c582,587
<     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++;
497a589,591
>     
>     *c++ = '\0';
>     ctx->curr_tag_pos = c;
499,514c593,615
<     /* 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;
516,517d616
<         }
<         dptr = AP_BUCKET_NEXT(dptr);
519,520c618,619
<     *d = '\0';
<     return 0;
---
> 
>     return (dest);
553c652
< 		char var[MAX_STRING_LEN];
---
> /* pjr hack     char var[MAX_STRING_LEN]; */
555c654
< 		const char *end_of_var_name;	/* end of var name + 1 */
---
> 		char *end_of_var_name;	/* end of var name + 1 */
557a657
>                 char        tmp_store;
573c673
< 		    end_of_var_name = in;
---
> 		    (const char *)end_of_var_name = in;
581c681
< 		    end_of_var_name = in;
---
> 		    (const char *)end_of_var_name = in;
587,589c687,697
< 		    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;
591d698
< 		    val = apr_table_get(r->subprocess_env, var);
628c735,736
< 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)
631a740
>     ap_bucket  *tmp_buck, *tmp2_buck;
657d765
<     /* The subrequest should inherit the remaining filters from this request. */
659d766
< 
663a771
>         apr_ssize_t len_loc, h_wrt;
664a773
> 
666c775,790
<         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;
>         }
715,718c839,845
< static int handle_include(ap_bucket *in, request_rec *r, ap_filter_t *next,
<                           const char *error, int noexec)
< {
<     char tag[MAX_STRING_LEN];
---
> 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     = NULL;
>     char *tag_val = NULL;
>     ap_bucket  *tmp_buck;
720,721d846
<     char *tag_val;
<     apr_off_t offset = strlen("include ") + strlen(STARTING_SEQUENCE);
723,736c848,854
<     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);
739c857
<                     rr = ap_sub_req_lookup_file(parsed_string, r);
---
>                     return (1);
742,744c860,877
<             else {
<                 rr = ap_sub_req_lookup_uri(parsed_string, r);
<             }
---
>             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";
>                     }
>                     else {
>                         rr = ap_sub_req_lookup_file(parsed_string, r);
>                     }
>                 }
>                 else {
>                     rr = ap_sub_req_lookup_uri(parsed_string, r);
>                 }
746,789c879,881
<             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 (!error_fmt && rr->status != HTTP_OK) {
>                     error_fmt = "unable to include \"%s\" in parsed file %s";
>                 }
791,792c883,885
<                 if (p != NULL) {
<                     error_fmt = "Recursive include of \"%s\" "
---
>                 if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) && rr->content_type
>                     && (strncmp(rr->content_type, "text/", 5))) {
>                     error_fmt = "unable to include potential exec \"%s\" "
795c888,922
<             }
---
>                 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;
>     			}
>     		    }
>     		}
797,806c924,927
< 	    /* see the Kludge in send_parsed_file for why */
< 	    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 (p != NULL) {
>                         error_fmt = "Recursive include of \"%s\" "
>                             "in parsed file %s";
>                     }
808,814d928
<             }
<             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);
<             }
816,820c930,961
< 	    /* 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);
---
>     	    /* 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) {
>                     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);
>                 }
> 
>     	    /* 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);
822,830d962
<         }
<         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);
832a965
>     return 0;
882c1015,1016
< 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)
947a1082
>         SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx);
951c1086
<         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);
970c1105
<             ap_pass_brigade(next, bcgi);
---
>             ap_pass_brigade(f->next, bcgi);
982,983c1117,1119
< 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)
985,986c1121,1122
<     char tag[MAX_STRING_LEN];
<     char *tag_val;
---
>     char *tag     = NULL;
>     char *tag_val = NULL;
987a1124
>     ap_bucket  *tmp_buck;
989d1125
<     apr_off_t offset = strlen("exec ") + strlen(STARTING_SEQUENCE);
991,1017c1127,1132
<     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);
1020,1023c1135,1171
<             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);
>                 }
>             }
1026c1174
< 
---
>     return 0;
1029c1177,1178
< 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)
1031,1032c1180,1184
<     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;
1034d1185
<     apr_off_t offset = strlen("echo ") + strlen(STARTING_SEQUENCE);
1038,1043c1189,1210
<     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) {
>                     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;
>             	}
1045,1054c1212,1232
<             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);
< 		}
---
>                     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);
>                 }
1057c1235,1238
<                 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);
1059,1074d1239
<         }
<         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);
< 	    }
< 	}
1076,1080d1240
<         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);
1082a1243
>     return 0;
1086c1247
< static int handle_perl(ap_bucket *in, request_rec *r, const char *error)
---
> static int handle_perl(include_ctx_t *ctx, request_rec *r)
1088c1249,1250
<     char tag[MAX_STRING_LEN];
---
>     char *tag     = NULL;
>     char *tag_val = NULL;
1090d1251
<     char *tag_val;
1093d1253
<     apr_off_t offset = strlen("perl ") + strlen(STARTING_SEQUENCE);
1095,1110c1255,1260
<     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);
1112,1113c1262,1274
<         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));
>             }
1114a1276,1279
> 
>         perl_stdout2client(r);
>         perl_setup_env(r);
>         perl_call_handler(sub, r, av);
1116,1119c1281,1282
<     perl_stdout2client(r);
<     perl_setup_env(r);
<     perl_call_handler(sub, r, av);
<     return OK;
---
> 
>     return (OK);
1126,1127c1289,1290
< 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)
1129,1130c1292,1293
<     char tag[MAX_STRING_LEN];
<     char *tag_val;
---
>     char *tag     = NULL;
>     char *tag_val = NULL;
1133,1143d1295
<     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;
1145,1155c1297,1330
<             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;
---
>     *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, ctx->time_str, MAX_STRING_LEN, 0);
>                 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, ctx->time_str, 0));
>                 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, ctx->time_str, 1));
>                 apr_table_setn(env, "LAST_MODIFIED",
>                                ap_ht_time(r->pool, r->finfo.mtime, ctx->time_str, 0));
>             }
>             else if (!strcmp(tag, "sizefmt")) {
>                 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;
>                 }
1157,1158c1332,1338
<             else if (!strcmp(parsed_string, "abbrev")) {
<                 *sizefmt = SIZEFMT_KMG;
---
>             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);
1161,1169d1340
<         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);
<         }
1170a1342
>     return 0;
1175c1347
<                      char *tag_val, apr_finfo_t *finfo, const char *error)
---
>                      char *tag_val, apr_finfo_t *finfo)
1211d1382
<             ap_rputs(error, r);
1232d1402
<             ap_rputs(error, r);
1241d1410
<         ap_rputs(error, r);
1245a1415,1417
> #define NEG_SIGN  "    -"
> #define ZERO_K    "   0k"
> #define ONE_K     "   1k"
1247c1419
< static int handle_fsize(ap_bucket *in, request_rec *r, const char *error, int sizefmt)
---
> static void generate_size(apr_ssize_t size, char *buff, apr_ssize_t buff_size)
1249,1251c1421,1449
<     char tag[MAX_STRING_LEN];
<     char *tag_val;
<     apr_finfo_t finfo;
---
>     /* 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(include_ctx_t *ctx, request_rec *r, ap_bucket *head_ptr,
>                         ap_bucket **inserted_head)
> {
>     char *tag     = NULL;
>     char *tag_val = NULL;
>     apr_finfo_t  finfo;
>     apr_ssize_t  s_len, s_wrt;
>     ap_bucket   *tmp_buck;
1253d1450
<     apr_off_t offset = strlen("fsize ") + strlen(STARTING_SEQUENCE);
1255,1266c1452,1493
<     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 {
>                     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];
>                         }
>                         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;
>                     }
1269,1277c1496
<                     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);
<                         }
<                         ap_rputc(tag[x], r);
<                     }
---
>                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1281a1501
>     return 0;
1284c1504,1505
< 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)
1286,1288c1507,1511
<     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;
1290d1512
<     apr_off_t offset = strlen("flastmod ") + strlen(STARTING_SEQUENCE);
1292,1302c1514,1542
<     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);
>                 }
1305a1546
>     return 0;
1338c1579,1580
< 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)
1342a1585
>     int tkn_fnd = 0;
1429c1672
<     for (ch = *string; ch != '\0'; ch = *++string) {
---
>     for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
1432c1675
<                 goto TOKEN_DONE;
---
>                 tkn_fnd = 1;
1434,1463c1677,1678
<             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;
<                 }
<                 break;
<             case '&':
<                 if (*(string + 1) == '&') {
<                     goto TOKEN_DONE;
<                 }
<                 break;
<             case '<':
<                 goto TOKEN_DONE;
<             case '>':
<                 goto TOKEN_DONE;
---
>             else {
>                 token->value[next++] = ch;
1465d1679
<             token->value[next++] = ch;
1468,1471c1682,1720
<             if (ch == '\'') {
<                 qs = 0;
<                 ++string;
<                 goto TOKEN_DONE;
---
>             if (!qs) {
>                 if (apr_isspace(ch)) {
>                     tkn_fnd = 1;
>                 }
>                 else {
>                     switch (ch) {
>                     case '(':
>                     case ')':
>                     case '=':
>                     case '!':
>                     case '<':
>                     case '>':
>                         tkn_fnd = 1;
>                         break;
>                     case '|':
>                         if (*(string + 1) == '|') {
>                             tkn_fnd = 1;
>                         }
>                         break;
>                     case '&':
>                         if (*(string + 1) == '&') {
>                             tkn_fnd = 1;
>                         }
>                         break;
>                     }
>                     if (!tkn_fnd) {
>                         token->value[next++] = ch;
>                     }
>                 }
>             }
>             else {
>                 if (ch == '\'') {
>                     qs = 0;
>                     ++string;
>                     tkn_fnd = 1;
>                 }
>                 else {
>                     token->value[next++] = ch;
>                 }
1473d1721
<             token->value[next++] = ch;
1476c1724
<   TOKEN_DONE:
---
> 
1479c1727
<         ap_rputs("\nUnmatched '\n", r);
---
>         *unmatched = 1;
1497c1745,1746
< 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)
1507a1757
>     apr_ssize_t debug_pos = 0;
1508a1759,1761
>     debug[debug_pos] = '\0';
>     *was_error       = 0;
>     *was_unmatched   = 0;
1522c1775
<         if ((parse = get_ptoken(r, parse, &new->token)) == (char *) NULL) {
---
>         if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == (char *) NULL) {
1529c1782,1783
<             ap_rvputs(r, "     Token: string (", new->token.value, ")\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Token: string (%s)\n",
>                                   new->token.value);
1564c1818
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1572c1826,1828
<             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");
1578c1834
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1603c1859
<                     ap_rputs(error, r);
---
>                     *was_error = 1;
1624c1880,1882
<             ap_rputs("     Token: not\n", r);
---
>             memcpy (&debug[debug_pos], "     Token: not\n",
>                     sizeof ("     Token: not\n"));
>             debug_pos += sizeof ("     Token: not\n");
1648c1906
<                     ap_rputs(error, r);
---
>                     *was_error = 1;
1674c1932,1934
<             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");
1680c1940
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1705c1965
<                     ap_rputs(error, r);
---
>                     *was_error = 1;
1726c1986,1988
<             ap_rputs("     Token: rbrace\n", r);
---
>             memcpy (&debug[debug_pos], "     Token: rbrace\n",
>                     sizeof ("     Token: rbrace\n"));
>             debug_pos += sizeof ("     Token: rbrace\n");
1739c2001
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1746c2008,2010
<             ap_rputs("     Token: lbrace\n", r);
---
>             memcpy (&debug[debug_pos], "     Token: lbrace\n",
>                     sizeof ("     Token: lbrace\n"));
>             debug_pos += sizeof ("     Token: lbrace\n");
1772c2036
<                     ap_rputs(error, r);
---
>                     *was_error = 1;
1801c2065,2067
<             ap_rputs("     Evaluate string\n", r);
---
>             memcpy (&debug[debug_pos], "     Evaluate string\n",
>                     sizeof ("     Evaluate string\n"));
>             debug_pos += sizeof ("     Evaluate string\n");
1813c2079,2081
<             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");
1820c2088
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1854,1857c2122,2125
<             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');
1866,1867c2134,2135
<             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
<                    "\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
>                                   current->value ? '1' : '0');
1876c2144,2146
<             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");
1885c2155
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1906c2176
<                     ap_rputs(error, r);
---
>                     *was_error = 1;
1910,1911c2180,2183
<                 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]);
1919,1920c2191,2194
<                 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);
1930,1931c2204,2205
<             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
<                    "\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
>                                   current->value ? '1' : '0');
1941c2215,2217
<             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");
1950c2226
<                 ap_rputs(error, r);
---
>                 *was_error = 1;
1962,1963c2238,2241
<             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);
1984,1985c2262,2263
<             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
<                    "\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
>                                   current->value ? '1' : '0');
2003,2004c2281,2282
<             ap_rvputs(r, "     Evaluate !: ", current->value ? "1" : "0",
<                    "\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
>                                   current->value ? '1' : '0');
2022,2023c2300,2301
<             ap_rvputs(r, "     Evaluate (): ", current->value ? "1" : "0",
<                    "\n", NULL);
---
>             debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
>                                   current->value ? '1' : '0');
2033c2311
<             ap_rputs(error, r);
---
>             *was_error = 1;
2040c2318
<             ap_rputs(error, r);
---
>             *was_error = 1;
2045,2046c2323,2324
< 			"bad token type");
<             ap_rputs(error, r);
---
>                           "bad token type");
>             *was_error = 1;
2057,2087c2335
< 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);
< #endif
<             return 0;
<         }
<         else if (!strcmp(tag, "expr")) {
<             expr = tag_val;
---
> /*-------------------------------------------------------------------------*/
2089c2337,2434
<             ap_rvputs(r, "**** if expr=\"", expr, "\"\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
> /*-------------------------------------------------------------------------*/
> 
> /* 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
>                 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;
>                     }
>                 }
2091,2096c2436,2442
<         }
<         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);
>             }
> 
2098a2445
>     return 0;
2101,2102c2448,2449
< 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)
2104,2121c2451,2492
<     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");
2124,2139c2495,2496
< 	    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;
2141c2498,2507
<             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;
>                     }
>                 }
2143,2148c2509,2514
<         }
<         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);
>             }
2150a2517
>     return 0;
2153,2154c2520,2521
< 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)
2156,2176c2523,2547
<     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;
2178d2548
<         return -1;
2179a2550
>     return 0;
2182,2183c2553,2554
< 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)
2185,2198c2556,2573
<     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;
>         }
2201,2205c2576,2577
<         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;
2209c2581,2582
< 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)
2211c2584,2587
<     char tag[MAX_STRING_LEN];
---
>     char *tag     = NULL;
>     char *tag_val = NULL;
>     char *var     = NULL;
>     ap_bucket *tmp_buck;
2213,2215d2588
<     char *tag_val;
<     char *var;
<     apr_off_t offset = strlen("set ") + strlen(STARTING_SEQUENCE);
2217,2229c2590,2615
<     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 {
2231,2233c2617,2618
<                             "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);
2236,2243d2620
<             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;
2245a2623
>     return 0;
2248c2626,2627
< 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)
2250,2263c2629,2671
<     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;
2265,2272d2672
<         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;
2273a2674
>     return 0;
2277d2677
< 
2280,2284d2679
< /* This is a stub which parses a file descriptor. */
< 
< typedef struct include_ctx {
<     ap_bucket_brigade *bb;
< } include_ctx;
2288,2294c2683
<     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;
2296,2297c2685
<     ap_bucket *tagbuck, *dptr2;
<     ap_bucket *endsec;
---
>     ap_bucket *tmp_dptr;
2299d2686
<     include_ctx *ctx;
2302,2309d2688
<     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;
< 
2320,2327c2699,2712
<     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;
> 
>             tmp_dptr = find_start_sequence(dptr, ctx, *bb, &do_cleanup);
> 
>             /* The few bytes stored in the ssi_tag_brigade turned out not to
>              * be a tag after all. This can only happen if the starting
>              * tag actually spans brigades. This should be very rare.
>              */
>             if ((do_cleanup) && (!AP_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
>                 ap_bucket *tmp_bkt;
2329,2335c2714,2721
<     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);
---
>                 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);
>                 }
2337,2339c2723,2725
<              
<             /* At this point, everything between tagbuck and endsec is an SSI
<              * directive, we just have to deal with it now.
---
> 
>             /* If I am inside a conditional (if, elif, else) that is false
>              *   then I need to throw away anything contained in it.
2341,2346c2727,2736
<             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;
---
>             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);
>                 }
2348,2353c2738,2742
<             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++;
---
> 
>             /* 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;
2356,2379c2745
<                     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);
<                 }
<                 continue;
<             }
<             else if (!strcmp(directive, "elif")) {
<                 if (!if_nesting) {
<                     ret = handle_elif(tagbuck, r, error, &conditional_status,
<                                       &printing);
<                 }
<                 continue;
<             }
<             else if (!strcmp(directive, "endif")) {
<                 if (!if_nesting) {
<                     ret = handle_endif(tagbuck, r, error, &conditional_status,
<                                        &printing);
---
>                     dptr = AP_BRIGADE_SENTINEL(*bb);
2381,2382c2747,2770
<                 else {
<                     if_nesting--;
---
>             }
>             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;
2384d2771
<                 continue;
2386,2387c2773,2774
<             if (!printing) {
<                 continue;
---
>             else {
>                 dptr = AP_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
2389,2395c2776,2807
<             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);
2399c2811,2817
<                     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)));
2400a2819,2820
> 
>                 return;
2402,2421c2822,2907
<             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);
---
> 
>             /* 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
>             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_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;
2423,2425c2909,2915
< #ifdef USE_PERL_SSI
<             else if (!strcmp(directive, "perl")) {
<                 ret = handle_perl(tagbuck, r, error);
---
>             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);
>                 }
2427d2916
< #endif
2429,2434c2918,2946
<                 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);
---
>                 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);
2437,2438c2949,2950
<             *bb = ap_brigade_split(tag_and_after, endsec); 
<             dptr = AP_BUCKET_PREV(endsec);
---
> 
>             ctx->state     = PRE_HEAD;
2440,2441c2952,2995
<         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);
2492a3047
>     include_ctx_t *ctx = f->ctx;
2496a3052,3057
> #ifdef FREDDY_KRUGER_VEGAMATIC_MODE
>     ap_bucket_brigade *rest;
>     ap_bucket *tmp_buck;
>     int count = 0;
> #endif
> 
2504a3066,3085
>     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;
>         }
>     }
> 
2540a3122,3145
> 
> #ifdef FREDDY_KRUGER_VEGAMATIC_MODE
>     tmp_buck = AP_BRIGADE_FIRST(b);
>     while (!AP_BUCKET_IS_EOS(tmp_buck)) {
>         const char *buf;
>         apr_ssize_t len;
> 
>         ap_bucket_read(tmp_buck, &buf, &len, 0);
> 
>         if (len > 2) {
>             ap_bucket_split(tmp_buck, 2);
>         }
>         tmp_buck = AP_BUCKET_NEXT(tmp_buck);
>         rest = ap_brigade_split(b, tmp_buck);
> 
>         count++;
>         send_parsed_content(&b, r, f);
> 
>         b = rest;
>     }
>     if (AP_BUCKET_IS_EOS(tmp_buck)) {
>         send_parsed_content(&b, r, f);
>     }
> #else
2542c3147
<     ap_pass_brigade(f->next, b);
---
> #endif

Mime
View raw message