upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / filters / mod_include.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * http_include.c: Handles the server-parsed HTML documents
19  * 
20  * Original by Rob McCool; substantial fixups by David Robinson;
21  * incorporated into the Apache module framework by rst.
22  * 
23  */
24
25 #include "apr.h"
26 #include "apr_strings.h"
27 #include "apr_thread_proc.h"
28 #include "apr_hash.h"
29 #include "apr_user.h"
30 #include "apr_lib.h"
31 #include "apr_optional.h"
32
33 #define APR_WANT_STRFUNC
34 #define APR_WANT_MEMFUNC
35 #include "apr_want.h"
36
37 #define CORE_PRIVATE
38
39 #include "ap_config.h"
40 #include "util_filter.h"
41 #include "httpd.h"
42 #include "http_config.h"
43 #include "http_core.h"
44 #include "http_request.h"
45 #include "http_core.h"
46 #include "http_protocol.h"
47 #include "http_log.h"
48 #include "http_main.h"
49 #include "util_script.h"
50 #include "http_core.h"
51
52 #define MOD_INCLUDE_REDESIGN
53 #include "mod_include.h"
54 #include "util_ebcdic.h"
55
56 module AP_MODULE_DECLARE_DATA include_module;
57 static apr_hash_t *include_hash;
58 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
59
60 /*****************************************************************
61  *
62  * XBITHACK.  Sigh...  NB it's configurable per-directory; the compile-time
63  * option only changes the default.
64  */
65
66 enum xbithack {
67     xbithack_off, xbithack_on, xbithack_full
68 };
69
70 struct bndm_t {
71     unsigned int T[256];
72     unsigned int x;
73 } ;
74
75 typedef struct {
76     char *default_error_msg;
77     char *default_time_fmt;
78     enum xbithack *xbithack;
79 } include_dir_config;
80
81 typedef struct {
82     char *default_start_tag;
83     char *default_end_tag;
84     int  start_tag_len;
85     bndm_t start_seq_pat;
86     char *undefinedEcho;
87     int  undefinedEchoLen;
88 } include_server_config;
89
90 /* main parser states */
91 typedef enum {
92     PARSE_PRE_HEAD,
93     PARSE_HEAD,
94     PARSE_DIRECTIVE,
95     PARSE_DIRECTIVE_POSTNAME,
96     PARSE_DIRECTIVE_TAIL,
97     PARSE_DIRECTIVE_POSTTAIL,
98     PARSE_PRE_ARG,
99     PARSE_ARG,
100     PARSE_ARG_NAME,
101     PARSE_ARG_POSTNAME,
102     PARSE_ARG_EQ,
103     PARSE_ARG_PREVAL,
104     PARSE_ARG_VAL,
105     PARSE_ARG_VAL_ESC,
106     PARSE_ARG_POSTVAL,
107     PARSE_TAIL,
108     PARSE_TAIL_SEQ,
109     PARSE_EXECUTE
110 } parse_state_t;
111
112 typedef struct ssi_arg_item {
113     struct ssi_arg_item *next;
114     char                *name;
115     apr_size_t           name_len;
116     char                *value;
117     apr_size_t           value_len;
118 } ssi_arg_item_t;
119
120 typedef struct {
121     parse_state_t state;
122     int           seen_eos;
123     int           error;
124     char          quote;         /* quote character value (or \0) */
125
126     apr_bucket_brigade *tmp_bb;
127
128     apr_size_t    end_seq_len;
129     char         *directive;     /* name of the current directive */
130
131     unsigned        argc;        /* argument counter (of the current
132                                   * directive)
133                                   */
134     ssi_arg_item_t *argv;        /* all arguments */
135     ssi_arg_item_t *current_arg; /* currently parsed argument */
136     request_rec    *r;
137     include_ctx_t  *ctx;         /* public part of the context structure */
138
139     apr_pool_t     *dpool;
140 } ssi_ctx_t;
141
142 #ifdef XBITHACK
143 #define DEFAULT_XBITHACK xbithack_full
144 #else
145 #define DEFAULT_XBITHACK xbithack_off
146 #endif
147
148 #define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
149
150 #define SSI_CREATE_ERROR_BUCKET(ctx, f, bb) APR_BRIGADE_INSERT_TAIL((bb),   \
151     apr_bucket_pool_create(apr_pstrdup((ctx)->pool, (ctx)->error_str),  \
152                            strlen((ctx)->error_str), (ctx)->pool,       \
153                            (f)->c->bucket_alloc))
154
155 /* ------------------------ Environment function -------------------------- */
156
157 /* Sentinel value to store in subprocess_env for items that
158  * shouldn't be evaluated until/unless they're actually used
159  */
160 static const char lazy_eval_sentinel;
161 #define LAZY_VALUE (&lazy_eval_sentinel)
162
163 static void add_include_vars(request_rec *r, char *timefmt)
164 {
165     apr_table_t *e = r->subprocess_env;
166     char *t;
167
168     apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
169     apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
170     apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
171     apr_table_setn(e, "DOCUMENT_URI", r->uri);
172     if (r->path_info && *r->path_info) {
173         apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
174     }
175     apr_table_setn(e, "USER_NAME", LAZY_VALUE);
176     if (r->filename && (t = strrchr(r->filename, '/'))) {
177         apr_table_setn(e, "DOCUMENT_NAME", ++t);
178     }
179     else {
180         apr_table_setn(e, "DOCUMENT_NAME", r->uri);
181     }
182     if (r->args) {
183         char *arg_copy = apr_pstrdup(r->pool, r->args);
184
185         ap_unescape_url(arg_copy);
186         apr_table_setn(e, "QUERY_STRING_UNESCAPED",
187                   ap_escape_shell_cmd(r->pool, arg_copy));
188     }
189 }
190
191 static const char *add_include_vars_lazy(request_rec *r, const char *var)
192 {
193     char *val;
194     if (!strcasecmp(var, "DATE_LOCAL")) {
195         include_dir_config *conf =
196             (include_dir_config *)ap_get_module_config(r->per_dir_config,
197                                                        &include_module);
198         val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
199     }
200     else if (!strcasecmp(var, "DATE_GMT")) {
201         include_dir_config *conf =
202             (include_dir_config *)ap_get_module_config(r->per_dir_config,
203                                                        &include_module);
204         val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
205     }
206     else if (!strcasecmp(var, "LAST_MODIFIED")) {
207         include_dir_config *conf =
208             (include_dir_config *)ap_get_module_config(r->per_dir_config,
209                                                        &include_module);
210         val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
211     }
212     else if (!strcasecmp(var, "USER_NAME")) {
213         if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
214             val = "<unknown>";
215         }
216     }
217     else {
218         val = NULL;
219     }
220
221     if (val) {
222         apr_table_setn(r->subprocess_env, var, val);
223     }
224     return val;
225 }
226
227 static const char *get_include_var(request_rec *r, include_ctx_t *ctx, 
228                                    const char *var)
229 {
230     const char *val;
231     if (apr_isdigit(*var) && !var[1]) {
232         /* Handle $0 .. $9 from the last regex evaluated.
233          * The choice of returning NULL strings on not-found,
234          * v.s. empty strings on an empty match is deliberate.
235          */
236         if (!ctx->re_result || !ctx->re_string) {
237             return NULL;
238         }
239         else {
240             int idx = atoi(var);
241             apr_size_t len = (*ctx->re_result)[idx].rm_eo
242                            - (*ctx->re_result)[idx].rm_so;
243             if (    (*ctx->re_result)[idx].rm_so < 0
244                  || (*ctx->re_result)[idx].rm_eo < 0) {
245                 return NULL;
246             }
247             val = apr_pstrmemdup(r->pool, ctx->re_string 
248                                         + (*ctx->re_result)[idx].rm_so, len);
249         }
250     }
251     else {
252         val = apr_table_get(r->subprocess_env, var);
253
254         if (val == LAZY_VALUE)
255             val = add_include_vars_lazy(r, var);
256     }
257     return val;
258 }
259
260 /* --------------------------- Parser functions --------------------------- */
261
262 /* This is an implementation of the BNDM search algorithm.
263  *
264  * Fast and Flexible String Matching by Combining Bit-parallelism and 
265  * Suffix Automata (2001) 
266  * Gonzalo Navarro, Mathieu Raffinot
267  *
268  * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
269  *
270  * Initial code submitted by Sascha Schumann.
271  */
272    
273 /* Precompile the bndm_t data structure. */
274 static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl)
275 {
276     unsigned int x;
277     const char *ne = n + nl;
278
279     memset(t->T, 0, sizeof(unsigned int) * 256);
280     
281     for (x = 1; n < ne; x <<= 1)
282         t->T[(unsigned char) *n++] |= x;
283
284     t->x = x - 1;
285 }
286
287 /* Implements the BNDM search algorithm (as described above).
288  *
289  * n  - the pattern to search for
290  * nl - length of the pattern to search for
291  * h  - the string to look in
292  * hl - length of the string to look for
293  * t  - precompiled bndm structure against the pattern 
294  *
295  * Returns the count of character that is the first match or hl if no
296  * match is found.
297  */
298 static apr_size_t bndm(const char *n, apr_size_t nl, const char *h, 
299                        apr_size_t hl, bndm_t *t)
300 {
301     const char *skip;
302     const char *he, *p, *pi;
303     unsigned int *T, x, d;
304
305     he = h + hl;
306
307     T = t->T;
308     x = t->x;
309
310     pi = h - 1; /* pi: p initial */
311     p = pi + nl; /* compare window right to left. point to the first char */
312
313     while (p < he) {
314         skip = p;
315         d = x;
316         do {
317             d &= T[(unsigned char) *p--];
318             if (!d) {
319                 break;
320             }
321             if ((d & 1)) {
322                 if (p != pi)
323                     skip = p;
324                 else
325                     return p - h + 1;
326             }
327             d >>= 1;
328         } while (d);
329
330         pi = skip;
331         p = pi + nl;
332     }
333
334     return hl;
335 }
336
337 /*
338  * decodes a string containing html entities or numeric character references.
339  * 's' is overwritten with the decoded string.
340  * If 's' is syntatically incorrect, then the followed fixups will be made:
341  *   unknown entities will be left undecoded;
342  *   references to unused numeric characters will be deleted.
343  *   In particular, &#00; will not be decoded, but will be deleted.
344  *
345  * drtr
346  */
347
348 /* maximum length of any ISO-LATIN-1 HTML entity name. */
349 #define MAXENTLEN (6)
350
351 /* The following is a shrinking transformation, therefore safe. */
352
353 static void decodehtml(char *s)
354 {
355     int val, i, j;
356     char *p;
357     const char *ents;
358     static const char * const entlist[MAXENTLEN + 1] =
359     {
360         NULL,                   /* 0 */
361         NULL,                   /* 1 */
362         "lt\074gt\076",         /* 2 */
363         "amp\046ETH\320eth\360",        /* 3 */
364         "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
365 iuml\357ouml\366uuml\374yuml\377",      /* 4 */
366         "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
367 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
368 ucirc\373thorn\376",            /* 5 */
369         "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
370 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
371 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
372 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
373 otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
374     };
375
376     /* Do a fast scan through the string until we find anything
377      * that needs more complicated handling
378      */
379     for (; *s != '&'; s++) {
380         if (*s == '\0') {
381             return;
382         }
383     }
384
385     for (p = s; *s != '\0'; s++, p++) {
386         if (*s != '&') {
387             *p = *s;
388             continue;
389         }
390         /* find end of entity */
391         for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
392             continue;
393         }
394
395         if (s[i] == '\0') {     /* treat as normal data */
396             *p = *s;
397             continue;
398         }
399
400         /* is it numeric ? */
401         if (s[1] == '#') {
402             for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
403                 val = val * 10 + s[j] - '0';
404             }
405             s += i;
406             if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
407                 (val >= 127 && val <= 160) || val >= 256) {
408                 p--;            /* no data to output */
409             }
410             else {
411                 *p = RAW_ASCII_CHAR(val);
412             }
413         }
414         else {
415             j = i - 1;
416             if (j > MAXENTLEN || entlist[j] == NULL) {
417                 /* wrong length */
418                 *p = '&';
419                 continue;       /* skip it */
420             }
421             for (ents = entlist[j]; *ents != '\0'; ents += i) {
422                 if (strncmp(s + 1, ents, j) == 0) {
423                     break;
424                 }
425             }
426
427             if (*ents == '\0') {
428                 *p = '&';       /* unknown */
429             }
430             else {
431                 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
432                 s += i;
433             }
434         }
435     }
436
437     *p = '\0';
438 }
439
440 /*
441  * Extract the next tag name and value.
442  * If there are no more tags, set the tag name to NULL.
443  * The tag value is html decoded if dodecode is non-zero.
444  * The tag value may be NULL if there is no tag value..
445  *    format:
446  *        [WS]<Tag>[WS]=[WS]['|"|`]<Value>[['|"|`|]|WS]
447  */
448
449 #define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
450
451 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
452                                      char **tag_val, int dodecode)
453 {
454     *tag_val = NULL;
455     if (ctx->curr_tag_pos >= ctx->combined_tag + ctx->tag_length) {
456         *tag = NULL;
457         return;
458     }
459
460     *tag = ctx->curr_tag_pos;
461     if (!**tag) {
462         *tag = NULL;
463         /* finitio */
464         ctx->curr_tag_pos = ctx->combined_tag + ctx->tag_length;
465         return;
466     }
467
468     *tag_val = ap_strchr(*tag, '=');
469     if (!*tag_val) {
470         ctx->curr_tag_pos = ctx->combined_tag + ctx->tag_length;
471         return;
472     }
473
474     /* if it starts with '=' there was no tag name, just a value */
475     if (*tag_val == *tag) {
476         *tag = NULL;
477     }
478
479     *(*tag_val)++ = '\0';
480     ctx->curr_tag_pos = *tag_val + strlen(*tag_val) + 1; /* skip \0 byte */
481
482     if (dodecode) {
483         decodehtml(*tag_val);
484     }
485
486     return;
487 }
488
489 /* initial buffer size for power-of-two allocator in ap_ssi_parse_string */
490 #define PARSE_STRING_INITIAL_SIZE 64
491
492 /*
493  * Do variable substitution on strings
494  * (Note: If out==NULL, this function allocs a buffer for the resulting
495  * string from r->pool.  The return value is the parsed string)
496  */
497 static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx, 
498                                  const char *in, char *out,
499                                  apr_size_t length, int leave_name)
500 {
501     char ch;
502     char *next;
503     char *end_out;
504     apr_size_t out_size;
505
506     /* allocate an output buffer if needed */
507     if (!out) {
508         out_size = PARSE_STRING_INITIAL_SIZE;
509         if (out_size > length) {
510             out_size = length;
511         }
512         out = apr_palloc(r->pool, out_size);
513     }
514     else {
515         out_size = length;
516     }
517
518     /* leave room for nul terminator */
519     end_out = out + out_size - 1;
520
521     next = out;
522     while ((ch = *in++) != '\0') {
523         switch (ch) {
524         case '\\':
525             if (next == end_out) {
526                 if (out_size < length) {
527                     /* double the buffer size */
528                     apr_size_t new_out_size = out_size * 2;
529                     apr_size_t current_length = next - out;
530                     char *new_out;
531                     if (new_out_size > length) {
532                         new_out_size = length;
533                     }
534                     new_out = apr_palloc(r->pool, new_out_size);
535                     memcpy(new_out, out, current_length);
536                     out = new_out;
537                     out_size = new_out_size;
538                     end_out = out + out_size - 1;
539                     next = out + current_length;
540                 }
541                 else {
542                     /* truncated */
543                     *next = '\0';
544                     return out;
545                 }
546             }
547             if (*in == '$') {
548                 *next++ = *in++;
549             }
550             else {
551                 *next++ = ch;
552             }
553             break;
554         case '$':
555             {
556                 const char *start_of_var_name;
557                 char *end_of_var_name;        /* end of var name + 1 */
558                 const char *expansion, *temp_end, *val;
559                 char        tmp_store;
560                 apr_size_t l;
561
562                 /* guess that the expansion won't happen */
563                 expansion = in - 1;
564                 if (*in == '{') {
565                     ++in;
566                     start_of_var_name = in;
567                     in = ap_strchr_c(in, '}');
568                     if (in == NULL) {
569                         ap_log_rerror(APLOG_MARK, APLOG_ERR,
570                                       0, r, "Missing '}' on variable \"%s\"",
571                                       expansion);
572                         *next = '\0';
573                         return out;
574                     }
575                     temp_end = in;
576                     end_of_var_name = (char *)temp_end;
577                     ++in;
578                 }
579                 else {
580                     start_of_var_name = in;
581                     while (apr_isalnum(*in) || *in == '_') {
582                         ++in;
583                     }
584                     temp_end = in;
585                     end_of_var_name = (char *)temp_end;
586                 }
587                 /* what a pain, too bad there's no table_getn where you can
588                  * pass a non-nul terminated string */
589                 l = end_of_var_name - start_of_var_name;
590                 if (l != 0) {
591                     tmp_store        = *end_of_var_name;
592                     *end_of_var_name = '\0';
593                     val = get_include_var(r, ctx, start_of_var_name);
594                     *end_of_var_name = tmp_store;
595
596                     if (val) {
597                         expansion = val;
598                         l = strlen(expansion);
599                     }
600                     else if (leave_name) {
601                         l = in - expansion;
602                     }
603                     else {
604                         /* no expansion to be done */
605                         break;
606                     }
607                 }
608                 else {
609                     /* zero-length variable name causes just the $ to be 
610                      * copied */
611                     l = 1;
612                 }
613                 if ((next + l > end_out) && (out_size < length)) {
614                     /* increase the buffer size to accommodate l more chars */
615                     apr_size_t new_out_size = out_size;
616                     apr_size_t current_length = next - out;
617                     char *new_out;
618                     do {
619                         new_out_size *= 2;
620                     } while (new_out_size < current_length + l + 1); /* +1 for NUL */
621                     if (new_out_size > length) {
622                         new_out_size = length;
623                     }
624                     new_out = apr_palloc(r->pool, new_out_size);
625                     memcpy(new_out, out, current_length);
626                     out = new_out;
627                     out_size = new_out_size;
628                     end_out = out + out_size - 1;
629                     next = out + current_length;
630                 }
631                 l = ((int)l > end_out - next) ? (end_out - next) : l;
632                 memcpy(next, expansion, l);
633                 next += l;
634                 break;
635             }
636         default:
637             if (next == end_out) {
638                 if (out_size < length) {
639                     /* double the buffer size */
640                     apr_size_t new_out_size = out_size * 2;
641                     apr_size_t current_length = next - out;
642                     char *new_out;
643                     if (new_out_size > length) {
644                         new_out_size = length;
645                     }
646                     new_out = apr_palloc(r->pool, new_out_size);
647                     memcpy(new_out, out, current_length);
648                     out = new_out;
649                     out_size = new_out_size;
650                     end_out = out + out_size - 1;
651                     next = out + current_length;
652                 }
653                 else {
654                     /* truncated */
655                     *next = '\0';
656                     return out;
657                 }
658             }
659             *next++ = ch;
660             break;
661         }
662     }
663     *next = '\0';
664     return out;
665 }
666
667 /* --------------------------- Action handlers ---------------------------- */
668
669 /* ensure that path is relative, and does not contain ".." elements
670  * ensentially ensure that it does not match the regex:
671  * (^/|(^|/)\.\.(/|$))
672  * XXX: Simply replace with apr_filepath_merge                    
673  */
674 static int is_only_below(const char *path)
675 {
676 #ifdef HAVE_DRIVE_LETTERS
677     if (path[1] == ':') 
678         return 0;
679 #endif
680 #ifdef NETWARE
681     if (ap_strchr_c(path, ':'))
682         return 0;
683 #endif
684     if (path[0] == '/') {
685         return 0;
686     }
687     while (*path) {
688         int dots = 0;
689         while (path[dots] == '.')
690             ++dots;
691 #if defined(WIN32) 
692         /* If the name is canonical this is redundant
693          * but in security, redundancy is worthwhile.
694          * Does OS2 belong here (accepts ... for ..)?
695          */
696         if (dots > 1 && (!path[dots] || path[dots] == '/'))
697             return 0;
698 #else
699         if (dots == 2 && (!path[dots] || path[dots] == '/'))
700             return 0;
701 #endif
702         path += dots;
703         /* Advance to either the null byte at the end of the
704          * string or the character right after the next slash,
705          * whichever comes first
706          */
707         while (*path && (*path++ != '/')) {
708             continue;
709         }
710     }
711     return 1;
712 }
713
714 static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, 
715                          request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
716                          apr_bucket **inserted_head)
717 {
718     char *tag     = NULL;
719     char *tag_val = NULL;
720     apr_bucket  *tmp_buck;
721     char *parsed_string;
722     int loglevel = APLOG_ERR;
723
724     *inserted_head = NULL;
725     if (ctx->flags & FLAG_PRINTING) {
726         while (1) {
727             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
728             if (tag_val == NULL) {
729                 if (tag == NULL) {
730                     return (0);
731                 }
732                 else {
733                     return (1);
734                 }
735             }
736             if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) {
737                 request_rec *rr = NULL;
738                 char *error_fmt = NULL;
739                 apr_status_t rc = APR_SUCCESS;
740
741                 SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rc);
742                 if (rc != APR_SUCCESS) {
743                     return rc;
744                 }
745  
746                 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
747                                                     MAX_STRING_LEN, 0);
748                 if (tag[0] == 'f') {
749                     /* XXX: Port to apr_filepath_merge
750                      * be safe; only files in this directory or below allowed 
751                      */
752                     if (!is_only_below(parsed_string)) {
753                         error_fmt = "unable to include file \"%s\" "
754                                     "in parsed file %s";
755                     }
756                     else {
757                         rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
758                     }
759                 }
760                 else {
761                     rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
762                 }
763
764                 if (!error_fmt && rr->status != HTTP_OK) {
765                     error_fmt = "unable to include \"%s\" in parsed file %s";
766                 }
767
768                 if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) && 
769                     rr->content_type && 
770                     (strncmp(rr->content_type, "text/", 5))) {
771                     error_fmt = "unable to include potential exec \"%s\" "
772                         "in parsed file %s";
773                 }
774
775                 /* See the Kludge in send_parsed_file for why */
776                 /* Basically, it puts a bread crumb in here, then looks */
777                 /*   for the crumb later to see if its been here.       */
778                 if (rr) 
779                     ap_set_module_config(rr->request_config, 
780                                          &include_module, r);
781
782                 if (!error_fmt && ap_run_sub_req(rr)) {
783                     error_fmt = "unable to include \"%s\" in parsed file %s";
784                 }
785                 if (error_fmt) {
786                     ap_log_rerror(APLOG_MARK, loglevel,
787                                   0, r, error_fmt, tag_val, r->filename);
788                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
789                                         *inserted_head);
790                 }
791                 
792                 /* Do *not* destroy the subrequest here; it may have allocated
793                  * variables in this r->subprocess_env in the subrequest's
794                  * r->pool, so that pool must survive as long as this request.
795                  * Yes, this is a memory leak. */
796             }
797             else {
798                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
799                             "unknown parameter \"%s\" to tag include in %s",
800                             tag, r->filename);
801                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
802                 return 1;
803             }
804         }
805     }
806     return 0;
807 }
808
809
810 static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, 
811                        request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
812                        apr_bucket **inserted_head)
813 {
814     char       *tag       = NULL;
815     char       *tag_val   = NULL;
816     const char *echo_text = NULL;
817     apr_bucket  *tmp_buck;
818     apr_size_t e_len;
819     enum {E_NONE, E_URL, E_ENTITY} encode;
820
821     encode = E_ENTITY;
822
823     *inserted_head = NULL;
824     if (ctx->flags & FLAG_PRINTING) {
825         while (1) {
826             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
827             if (tag_val == NULL) {
828                 if (tag != NULL) {
829                     return 1;
830                 }
831                 else {
832                     return 0;
833                 }
834             }
835             if (!strcmp(tag, "var")) {
836                 conn_rec *c = r->connection;
837                 const char *val =
838                     get_include_var(r, ctx,
839                                     ap_ssi_parse_string(r, ctx, tag_val, NULL,
840                                                         MAX_STRING_LEN, 0));
841                 if (val) {
842                     switch(encode) {
843                     case E_NONE:   
844                         echo_text = val;
845                         break;
846                     case E_URL:
847                         echo_text = ap_escape_uri(r->pool, val);  
848                         break;
849                     case E_ENTITY: 
850                         echo_text = ap_escape_html(r->pool, val); 
851                         break;
852                     }
853
854                     e_len = strlen(echo_text);
855                     tmp_buck = apr_bucket_pool_create(echo_text, e_len,
856                                                       r->pool, c->bucket_alloc);
857                 }
858                 else {
859                     include_server_config *sconf= 
860                         ap_get_module_config(r->server->module_config,
861                                              &include_module);
862                     tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho, 
863                                                       sconf->undefinedEchoLen,
864                                                       r->pool, c->bucket_alloc);
865                 }
866                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
867                 if (*inserted_head == NULL) {
868                     *inserted_head = tmp_buck;
869                 }
870             }
871             else if (!strcmp(tag, "encoding")) {
872                 if (!strcasecmp(tag_val, "none")) encode = E_NONE;
873                 else if (!strcasecmp(tag_val, "url")) encode = E_URL;
874                 else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
875                 else {
876                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
877                            "unknown value \"%s\" to parameter \"encoding\" of "
878                            "tag echo in %s", tag_val, r->filename);
879                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
880                                         *inserted_head);
881                     return 1;
882                 }
883             }
884             else {
885                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
886                             "unknown parameter \"%s\" in tag echo of %s",
887                             tag, r->filename);
888                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
889                 return 1;
890             }
891
892         }
893     }
894     return 0;
895 }
896
897 /* error and tf must point to a string with room for at 
898  * least MAX_STRING_LEN characters 
899  */
900 static int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb, 
901                          request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
902                          apr_bucket **inserted_head)
903 {
904     char *tag     = NULL;
905     char *tag_val = NULL;
906     char *parsed_string;
907     apr_table_t *env = r->subprocess_env;
908
909     *inserted_head = NULL;
910     if (ctx->flags & FLAG_PRINTING) {
911         while (1) {
912             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
913             if (tag_val == NULL) {
914                 if (tag == NULL) {
915                     return 0;  /* Reached the end of the string. */
916                 }
917                 else {
918                     return 1;  /* tags must have values. */
919                 }
920             }
921             if (!strcmp(tag, "errmsg")) {
922                 if (ctx->error_str_override == NULL) {
923                     ctx->error_str_override = (char *)apr_palloc(ctx->pool,
924                                                               MAX_STRING_LEN);
925                     ctx->error_str = ctx->error_str_override;
926                 }
927                 ap_ssi_parse_string(r, ctx, tag_val, ctx->error_str_override,
928                                     MAX_STRING_LEN, 0);
929             }
930             else if (!strcmp(tag, "timefmt")) {
931                 apr_time_t date = r->request_time;
932                 if (ctx->time_str_override == NULL) {
933                     ctx->time_str_override = (char *)apr_palloc(ctx->pool,
934                                                               MAX_STRING_LEN);
935                     ctx->time_str = ctx->time_str_override;
936                 }
937                 ap_ssi_parse_string(r, ctx, tag_val, ctx->time_str_override,
938                                     MAX_STRING_LEN, 0);
939                 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, 
940                                ctx->time_str, 0));
941                 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, 
942                                ctx->time_str, 1));
943                 apr_table_setn(env, "LAST_MODIFIED",
944                                ap_ht_time(r->pool, r->finfo.mtime, 
945                                ctx->time_str, 0));
946             }
947             else if (!strcmp(tag, "sizefmt")) {
948                 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
949                                                     MAX_STRING_LEN, 0);
950                 decodehtml(parsed_string);
951                 if (!strcmp(parsed_string, "bytes")) {
952                     ctx->flags |= FLAG_SIZE_IN_BYTES;
953                 }
954                 else if (!strcmp(parsed_string, "abbrev")) {
955                     ctx->flags &= FLAG_SIZE_ABBREV;
956                 }
957             }
958             else {
959                 apr_bucket *tmp_buck;
960
961                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
962                               "unknown parameter \"%s\" to tag config in %s",
963                               tag, r->filename);
964                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
965                 return 1;
966             }
967         }
968     }
969     return 0;
970 }
971
972
973 static int find_file(request_rec *r, const char *directive, const char *tag,
974                      char *tag_val, apr_finfo_t *finfo)
975 {
976     char *to_send = tag_val;
977     request_rec *rr = NULL;
978     int ret=0;
979     char *error_fmt = NULL;
980     apr_status_t rv = APR_SUCCESS;
981
982     if (!strcmp(tag, "file")) {
983         /* XXX: Port to apr_filepath_merge
984          * be safe; only files in this directory or below allowed 
985          */
986         if (!is_only_below(tag_val)) {
987             error_fmt = "unable to access file \"%s\" "
988                         "in parsed file %s";
989         }
990         else {
991             ap_getparents(tag_val);    /* get rid of any nasties */
992
993             /* note: it is okay to pass NULL for the "next filter" since
994                we never attempt to "run" this sub request. */
995             rr = ap_sub_req_lookup_file(tag_val, r, NULL);
996
997             if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
998                 to_send = rr->filename;
999                 if ((rv = apr_stat(finfo, to_send, 
1000                     APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1001                     && rv != APR_INCOMPLETE) {
1002                     error_fmt = "unable to get information about \"%s\" "
1003                         "in parsed file %s";
1004                 }
1005             }
1006             else {
1007                 error_fmt = "unable to lookup information about \"%s\" "
1008                             "in parsed file %s";
1009             }
1010         }
1011
1012         if (error_fmt) {
1013             ret = -1;
1014             ap_log_rerror(APLOG_MARK, APLOG_ERR,
1015                           rv, r, error_fmt, to_send, r->filename);
1016         }
1017
1018         if (rr) ap_destroy_sub_req(rr);
1019         
1020         return ret;
1021     }
1022     else if (!strcmp(tag, "virtual")) {
1023         /* note: it is okay to pass NULL for the "next filter" since
1024            we never attempt to "run" this sub request. */
1025         rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1026
1027         if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1028             memcpy((char *) finfo, (const char *) &rr->finfo,
1029                    sizeof(rr->finfo));
1030             ap_destroy_sub_req(rr);
1031             return 0;
1032         }
1033         else {
1034             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1035                         "unable to get information about \"%s\" "
1036                         "in parsed file %s",
1037                         tag_val, r->filename);
1038             ap_destroy_sub_req(rr);
1039             return -1;
1040         }
1041     }
1042     else {
1043         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1044                     "unknown parameter \"%s\" to tag %s in %s",
1045                     tag, directive, r->filename);
1046         return -1;
1047     }
1048 }
1049
1050 static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, 
1051                         request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
1052                         apr_bucket **inserted_head)
1053 {
1054     char *tag     = NULL;
1055     char *tag_val = NULL;
1056     apr_finfo_t  finfo;
1057     apr_size_t  s_len;
1058     apr_bucket   *tmp_buck;
1059     char *parsed_string;
1060
1061     *inserted_head = NULL;
1062     if (ctx->flags & FLAG_PRINTING) {
1063         while (1) {
1064             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1065             if (tag_val == NULL) {
1066                 if (tag == NULL) {
1067                     return 0;
1068                 }
1069                 else {
1070                     return 1;
1071                 }
1072             }
1073             else {
1074                 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
1075                                                     MAX_STRING_LEN, 0);
1076                 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1077                     /* XXX: if we *know* we're going to have to copy the
1078                      * thing off of the stack anyway, why not palloc buff
1079                      * instead of sticking it on the stack; then we can just
1080                      * use a pool bucket and skip the copy
1081                      */
1082                     char buff[50];
1083
1084                     if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) {
1085                         apr_strfsize(finfo.size, buff);
1086                         s_len = strlen (buff);
1087                     }
1088                     else {
1089                         int l, x, pos = 0;
1090                         char tmp_buff[50];
1091
1092                         apr_snprintf(tmp_buff, sizeof(tmp_buff), 
1093                                      "%" APR_OFF_T_FMT, finfo.size);
1094                         l = strlen(tmp_buff);    /* grrr */
1095                         for (x = 0; x < l; x++) {
1096                             if (x && (!((l - x) % 3))) {
1097                                 buff[pos++] = ',';
1098                             }
1099                             buff[pos++] = tmp_buff[x];
1100                         }
1101                         buff[pos] = '\0';
1102                         s_len = pos;
1103                     }
1104
1105                     tmp_buck = apr_bucket_heap_create(buff, s_len, NULL,
1106                                                   r->connection->bucket_alloc);
1107                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1108                     if (*inserted_head == NULL) {
1109                         *inserted_head = tmp_buck;
1110                     }
1111                 }
1112                 else {
1113                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
1114                                         *inserted_head);
1115                     return 1;
1116                 }
1117             }
1118         }
1119     }
1120     return 0;
1121 }
1122
1123 static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb, 
1124                            request_rec *r, ap_filter_t *f, 
1125                            apr_bucket *head_ptr, apr_bucket **inserted_head)
1126 {
1127     char *tag     = NULL;
1128     char *tag_val = NULL;
1129     apr_finfo_t  finfo;
1130     apr_size_t  t_len;
1131     apr_bucket   *tmp_buck;
1132     char *parsed_string;
1133
1134     *inserted_head = NULL;
1135     if (ctx->flags & FLAG_PRINTING) {
1136         while (1) {
1137             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1138             if (tag_val == NULL) {
1139                 if (tag == NULL) {
1140                     return 0;
1141                 }
1142                 else {
1143                     return 1;
1144                 }
1145             }
1146             else {
1147                 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
1148                                                     MAX_STRING_LEN, 0);
1149                 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1150                     char *t_val;
1151
1152                     t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
1153                     t_len = strlen(t_val);
1154
1155                     tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool,
1156                                                   r->connection->bucket_alloc);
1157                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1158                     if (*inserted_head == NULL) {
1159                         *inserted_head = tmp_buck;
1160                     }
1161                 }
1162                 else {
1163                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
1164                                         *inserted_head);
1165                     return 1;
1166                 }
1167             }
1168         }
1169     }
1170     return 0;
1171 }
1172
1173 static int re_check(request_rec *r, include_ctx_t *ctx, 
1174                     char *string, char *rexp)
1175 {
1176     regex_t *compiled;
1177     const apr_size_t nres = sizeof(*ctx->re_result) / sizeof(regmatch_t);
1178     int regex_error;
1179
1180     compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
1181     if (compiled == NULL) {
1182         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1183                       "unable to compile pattern \"%s\"", rexp);
1184         return -1;
1185     }
1186     if (!ctx->re_result) {
1187         ctx->re_result = apr_pcalloc(r->pool, sizeof(*ctx->re_result));
1188     }
1189     ctx->re_string = string;
1190     regex_error = ap_regexec(compiled, string, nres, *ctx->re_result, 0);
1191     ap_pregfree(r->pool, compiled);
1192     return (!regex_error);
1193 }
1194
1195 enum token_type {
1196     token_string, token_re,
1197     token_and, token_or, token_not, token_eq, token_ne,
1198     token_rbrace, token_lbrace, token_group,
1199     token_ge, token_le, token_gt, token_lt
1200 };
1201 struct token {
1202     enum token_type type;
1203     char* value;
1204 };
1205
1206 static const char *get_ptoken(request_rec *r, const char *string, 
1207                               struct token *token, int *unmatched)
1208 {
1209     char ch;
1210     int next = 0;
1211     char qs = 0;
1212     int tkn_fnd = 0;
1213
1214     token->value = NULL;
1215
1216     /* Skip leading white space */
1217     if (string == (char *) NULL) {
1218         return (char *) NULL;
1219     }
1220     while ((ch = *string++)) {
1221         if (!apr_isspace(ch)) {
1222             break;
1223         }
1224     }
1225     if (ch == '\0') {
1226         return (char *) NULL;
1227     }
1228
1229     token->type = token_string; /* the default type */
1230     switch (ch) {
1231     case '(':
1232         token->type = token_lbrace;
1233         return (string);
1234     case ')':
1235         token->type = token_rbrace;
1236         return (string);
1237     case '=':
1238         token->type = token_eq;
1239         return (string);
1240     case '!':
1241         if (*string == '=') {
1242             token->type = token_ne;
1243             return (string + 1);
1244         }
1245         else {
1246             token->type = token_not;
1247             return (string);
1248         }
1249     case '\'':
1250         /* already token->type == token_string */
1251         qs = '\'';
1252         break;
1253     case '/':
1254         token->type = token_re;
1255         qs = '/';
1256         break;
1257     case '|':
1258         if (*string == '|') {
1259             token->type = token_or;
1260             return (string + 1);
1261         }
1262         break;
1263     case '&':
1264         if (*string == '&') {
1265             token->type = token_and;
1266             return (string + 1);
1267         }
1268         break;
1269     case '>':
1270         if (*string == '=') {
1271             token->type = token_ge;
1272             return (string + 1);
1273         }
1274         else {
1275             token->type = token_gt;
1276             return (string);
1277         }
1278     case '<':
1279         if (*string == '=') {
1280             token->type = token_le;
1281             return (string + 1);
1282         }
1283         else {
1284             token->type = token_lt;
1285             return (string);
1286         }
1287     default:
1288         /* already token->type == token_string */
1289         break;
1290     }
1291     /* We should only be here if we are in a string */
1292     token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
1293                                                                trailing null */
1294     if (!qs) {
1295         --string;
1296     }
1297
1298     /* 
1299      * I used the ++string throughout this section so that string
1300      * ends up pointing to the next token and I can just return it
1301      */
1302     for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
1303         if (ch == '\\') {
1304             if ((ch = *++string) == '\0') {
1305                 tkn_fnd = 1;
1306             }
1307             else {
1308                 token->value[next++] = ch;
1309             }
1310         }
1311         else {
1312             if (!qs) {
1313                 if (apr_isspace(ch)) {
1314                     tkn_fnd = 1;
1315                 }
1316                 else {
1317                     switch (ch) {
1318                     case '(':
1319                     case ')':
1320                     case '=':
1321                     case '!':
1322                     case '<':
1323                     case '>':
1324                         tkn_fnd = 1;
1325                         break;
1326                     case '|':
1327                         if (*(string + 1) == '|') {
1328                             tkn_fnd = 1;
1329                         }
1330                         break;
1331                     case '&':
1332                         if (*(string + 1) == '&') {
1333                             tkn_fnd = 1;
1334                         }
1335                         break;
1336                     }
1337                     if (!tkn_fnd) {
1338                         token->value[next++] = ch;
1339                     }
1340                 }
1341             }
1342             else {
1343                 if (ch == qs) {
1344                     qs = 0;
1345                     tkn_fnd = 1;
1346                     string++;
1347                 }
1348                 else {
1349                     token->value[next++] = ch;
1350                 }
1351             }
1352         }
1353         if (tkn_fnd) {
1354             break;
1355         }
1356     }
1357
1358     /* If qs is still set, we have an unmatched quote */
1359     if (qs) {
1360         *unmatched = 1;
1361         next = 0;
1362     }
1363     token->value[next] = '\0';
1364
1365     return (string);
1366 }
1367
1368
1369 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
1370  * characters long...
1371  */
1372 static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
1373                       int *was_error, int *was_unmatched, char *debug)
1374 {
1375     struct parse_node {
1376         struct parse_node *left, *right, *parent;
1377         struct token token;
1378         int value, done;
1379     } *root, *current, *new;
1380     const char *parse;
1381     char* buffer;
1382     int retval = 0;
1383     apr_size_t debug_pos = 0;
1384
1385     debug[debug_pos] = '\0';
1386     *was_error       = 0;
1387     *was_unmatched   = 0;
1388     if ((parse = expr) == (char *) NULL) {
1389         return (0);
1390     }
1391     root = current = (struct parse_node *) NULL;
1392
1393     /* Create Parse Tree */
1394     while (1) {
1395         new = (struct parse_node *) apr_palloc(r->pool,
1396                                            sizeof(struct parse_node));
1397         new->parent = new->left = new->right = (struct parse_node *) NULL;
1398         new->done = 0;
1399         if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == 
1400             (char *) NULL) {
1401             break;
1402         }
1403         switch (new->token.type) {
1404
1405         case token_string:
1406 #ifdef DEBUG_INCLUDE
1407             debug_pos += sprintf (&debug[debug_pos], 
1408                                   "     Token: string (%s)\n", 
1409                                   new->token.value);
1410 #endif
1411             if (current == (struct parse_node *) NULL) {
1412                 root = current = new;
1413                 break;
1414             }
1415             switch (current->token.type) {
1416             case token_string:
1417                 current->token.value = apr_pstrcat(r->pool,
1418                                                    current->token.value,
1419                                                    current->token.value[0] ? " " : "",
1420                                                    new->token.value,
1421                                                    NULL);
1422                                                    
1423                 break;
1424             case token_eq:
1425             case token_ne:
1426             case token_and:
1427             case token_or:
1428             case token_lbrace:
1429             case token_not:
1430             case token_ge:
1431             case token_gt:
1432             case token_le:
1433             case token_lt:
1434                 new->parent = current;
1435                 current = current->right = new;
1436                 break;
1437             default:
1438                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1439                             "Invalid expression \"%s\" in file %s",
1440                             expr, r->filename);
1441                 *was_error = 1;
1442                 return retval;
1443             }
1444             break;
1445
1446         case token_re:
1447 #ifdef DEBUG_INCLUDE
1448             debug_pos += sprintf (&debug[debug_pos], 
1449                                   "     Token: regex (%s)\n", 
1450                                   new->token.value);
1451 #endif
1452             if (current == (struct parse_node *) NULL) {
1453                 root = current = new;
1454                 break;
1455             }
1456             switch (current->token.type) {
1457             case token_eq:
1458             case token_ne:
1459             case token_and:
1460             case token_or:
1461             case token_lbrace:
1462             case token_not:
1463                 new->parent = current;
1464                 current = current->right = new;
1465                 break;
1466             default:
1467                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1468                             "Invalid expression \"%s\" in file %s",
1469                             expr, r->filename);
1470                 *was_error = 1;
1471                 return retval;
1472             }
1473             break;
1474
1475         case token_and:
1476         case token_or:
1477 #ifdef DEBUG_INCLUDE
1478             memcpy (&debug[debug_pos], "     Token: and/or\n",
1479                     sizeof ("     Token: and/or\n"));
1480             debug_pos += sizeof ("     Token: and/or\n");
1481 #endif
1482             if (current == (struct parse_node *) NULL) {
1483                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1484                             "Invalid expression \"%s\" in file %s",
1485                             expr, r->filename);
1486                 *was_error = 1;
1487                 return retval;
1488             }
1489             /* Percolate upwards */
1490             while (current != (struct parse_node *) NULL) {
1491                 switch (current->token.type) {
1492                 case token_string:
1493                 case token_re:
1494                 case token_group:
1495                 case token_not:
1496                 case token_eq:
1497                 case token_ne:
1498                 case token_and:
1499                 case token_or:
1500                 case token_ge:
1501                 case token_gt:
1502                 case token_le:
1503                 case token_lt:
1504                     current = current->parent;
1505                     continue;
1506                 case token_lbrace:
1507                     break;
1508                 default:
1509                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1510                                 "Invalid expression \"%s\" in file %s",
1511                                 expr, r->filename);
1512                     *was_error = 1;
1513                     return retval;
1514                 }
1515                 break;
1516             }
1517             if (current == (struct parse_node *) NULL) {
1518                 new->left = root;
1519                 new->left->parent = new;
1520                 new->parent = (struct parse_node *) NULL;
1521                 root = new;
1522             }
1523             else {
1524                 new->left = current->right;
1525                 new->left->parent = new;
1526                 current->right = new;
1527                 new->parent = current;
1528             }
1529             current = new;
1530             break;
1531
1532         case token_not:
1533 #ifdef DEBUG_INCLUDE
1534             memcpy(&debug[debug_pos], "     Token: not\n",
1535                     sizeof("     Token: not\n"));
1536             debug_pos += sizeof("     Token: not\n");
1537 #endif
1538             if (current == (struct parse_node *) NULL) {
1539                 root = current = new;
1540                 break;
1541             }
1542             /* Percolate upwards */
1543             if (current != (struct parse_node *) NULL) {
1544                 switch (current->token.type) {
1545                 case token_not:
1546                 case token_eq:
1547                 case token_ne:
1548                 case token_and:
1549                 case token_or:
1550                 case token_lbrace:
1551                 case token_ge:
1552                 case token_gt:
1553                 case token_le:
1554                 case token_lt:
1555                     break;
1556                 default:
1557                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1558                                   "Invalid expression \"%s\" in file %s",
1559                                   expr, r->filename);
1560                     *was_error = 1;
1561                     return retval;
1562                 }
1563             }
1564             if (current == (struct parse_node *) NULL) {
1565                 new->left = root;
1566                 new->left->parent = new;
1567                 new->parent = (struct parse_node *) NULL;
1568                 root = new;
1569             }
1570             else {
1571                 new->left = current->right;
1572                 current->right = new;
1573                 new->parent = current;
1574             }
1575             current = new;
1576             break;
1577
1578         case token_eq:
1579         case token_ne:
1580         case token_ge:
1581         case token_gt:
1582         case token_le:
1583         case token_lt:
1584 #ifdef DEBUG_INCLUDE
1585             memcpy(&debug[debug_pos], "     Token: eq/ne/ge/gt/le/lt\n",
1586                     sizeof("     Token: eq/ne/ge/gt/le/lt\n"));
1587             debug_pos += sizeof("     Token: eq/ne/ge/gt/le/lt\n");
1588 #endif
1589             if (current == (struct parse_node *) NULL) {
1590                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1591                               "Invalid expression \"%s\" in file %s",
1592                               expr, r->filename);
1593                 *was_error = 1;
1594                 return retval;
1595             }
1596             /* Percolate upwards */
1597             while (current != (struct parse_node *) NULL) {
1598                 switch (current->token.type) {
1599                 case token_string:
1600                 case token_re:
1601                 case token_group:
1602                     current = current->parent;
1603                     continue;
1604                 case token_lbrace:
1605                 case token_and:
1606                 case token_or:
1607                     break;
1608                 case token_not:
1609                 case token_eq:
1610                 case token_ne:
1611                 case token_ge:
1612                 case token_gt:
1613                 case token_le:
1614                 case token_lt:
1615                 default:
1616                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1617                                 "Invalid expression \"%s\" in file %s",
1618                                 expr, r->filename);
1619                     *was_error = 1;
1620                     return retval;
1621                 }
1622                 break;
1623             }
1624             if (current == (struct parse_node *) NULL) {
1625                 new->left = root;
1626                 new->left->parent = new;
1627                 new->parent = (struct parse_node *) NULL;
1628                 root = new;
1629             }
1630             else {
1631                 new->left = current->right;
1632                 new->left->parent = new;
1633                 current->right = new;
1634                 new->parent = current;
1635             }
1636             current = new;
1637             break;
1638
1639         case token_rbrace:
1640 #ifdef DEBUG_INCLUDE
1641             memcpy (&debug[debug_pos], "     Token: rbrace\n",
1642                     sizeof ("     Token: rbrace\n"));
1643             debug_pos += sizeof ("     Token: rbrace\n");
1644 #endif
1645             while (current != (struct parse_node *) NULL) {
1646                 if (current->token.type == token_lbrace) {
1647                     current->token.type = token_group;
1648                     break;
1649                 }
1650                 current = current->parent;
1651             }
1652             if (current == (struct parse_node *) NULL) {
1653                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1654                             "Unmatched ')' in \"%s\" in file %s",
1655                             expr, r->filename);
1656                 *was_error = 1;
1657                 return retval;
1658             }
1659             break;
1660
1661         case token_lbrace:
1662 #ifdef DEBUG_INCLUDE
1663             memcpy (&debug[debug_pos], "     Token: lbrace\n",
1664                     sizeof ("     Token: lbrace\n"));
1665             debug_pos += sizeof ("     Token: lbrace\n");
1666 #endif
1667             if (current == (struct parse_node *) NULL) {
1668                 root = current = new;
1669                 break;
1670             }
1671             /* Percolate upwards */
1672             if (current != (struct parse_node *) NULL) {
1673                 switch (current->token.type) {
1674                 case token_not:
1675                 case token_eq:
1676                 case token_ne:
1677                 case token_and:
1678                 case token_or:
1679                 case token_lbrace:
1680                 case token_ge:
1681                 case token_gt:
1682                 case token_le:
1683                 case token_lt:
1684                     break;
1685                 case token_string:
1686                 case token_re:
1687                 case token_group:
1688                 default:
1689                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1690                                 "Invalid expression \"%s\" in file %s",
1691                                 expr, r->filename);
1692                     *was_error = 1;
1693                     return retval;
1694                 }
1695             }
1696             if (current == (struct parse_node *) NULL) {
1697                 new->left = root;
1698                 new->left->parent = new;
1699                 new->parent = (struct parse_node *) NULL;
1700                 root = new;
1701             }
1702             else {
1703                 new->left = current->right;
1704                 current->right = new;
1705                 new->parent = current;
1706             }
1707             current = new;
1708             break;
1709         default:
1710             break;
1711         }
1712     }
1713
1714     /* Evaluate Parse Tree */
1715     current = root;
1716     while (current != (struct parse_node *) NULL) {
1717         switch (current->token.type) {
1718         case token_string:
1719 #ifdef DEBUG_INCLUDE
1720             memcpy (&debug[debug_pos], "     Evaluate string\n",
1721                     sizeof ("     Evaluate string\n"));
1722             debug_pos += sizeof ("     Evaluate string\n");
1723 #endif
1724             buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL, 
1725                                          MAX_STRING_LEN, 0);
1726             current->token.value = buffer;
1727             current->value = (current->token.value[0] != '\0');
1728             current->done = 1;
1729             current = current->parent;
1730             break;
1731
1732         case token_re:
1733             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1734                           "No operator before regex of expr \"%s\" in file %s",
1735                           expr, r->filename);
1736             *was_error = 1;
1737             return retval;
1738
1739         case token_and:
1740         case token_or:
1741 #ifdef DEBUG_INCLUDE
1742             memcpy(&debug[debug_pos], "     Evaluate and/or\n",
1743                     sizeof("     Evaluate and/or\n"));
1744             debug_pos += sizeof("     Evaluate and/or\n");
1745 #endif
1746             if (current->left  == (struct parse_node *) NULL ||
1747                 current->right == (struct parse_node *) NULL) {
1748                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1749                               "Invalid expression \"%s\" in file %s",
1750                               expr, r->filename);
1751                 *was_error = 1;
1752                 return retval;
1753             }
1754             if (!current->left->done) {
1755                 switch (current->left->token.type) {
1756                 case token_string:
1757                     buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1758                                                  NULL, MAX_STRING_LEN, 0);
1759                     current->left->token.value = buffer;
1760                     current->left->value = 
1761                                        (current->left->token.value[0] != '\0');
1762                     current->left->done = 1;
1763                     break;
1764                 default:
1765                     current = current->left;
1766                     continue;
1767                 }
1768             }
1769             if (!current->right->done) {
1770                 switch (current->right->token.type) {
1771                 case token_string:
1772                     buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1773                                                  NULL, MAX_STRING_LEN, 0);
1774                     current->right->token.value = buffer;
1775                     current->right->value = 
1776                                       (current->right->token.value[0] != '\0');
1777                     current->right->done = 1;
1778                     break;
1779                 default:
1780                     current = current->right;
1781                     continue;
1782                 }
1783             }
1784 #ifdef DEBUG_INCLUDE
1785             debug_pos += sprintf (&debug[debug_pos], "     Left: %c\n",
1786                                   current->left->value ? '1' : '0');
1787             debug_pos += sprintf (&debug[debug_pos], "     Right: %c\n",
1788                                   current->right->value ? '1' : '0');
1789 #endif
1790             if (current->token.type == token_and) {
1791                 current->value = current->left->value && current->right->value;
1792             }
1793             else {
1794                 current->value = current->left->value || current->right->value;
1795             }
1796 #ifdef DEBUG_INCLUDE
1797             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1798                                   current->value ? '1' : '0');
1799 #endif
1800             current->done = 1;
1801             current = current->parent;
1802             break;
1803
1804         case token_eq:
1805         case token_ne:
1806 #ifdef DEBUG_INCLUDE
1807             memcpy (&debug[debug_pos], "     Evaluate eq/ne\n",
1808                     sizeof ("     Evaluate eq/ne\n"));
1809             debug_pos += sizeof ("     Evaluate eq/ne\n");
1810 #endif
1811             if ((current->left == (struct parse_node *) NULL) ||
1812                 (current->right == (struct parse_node *) NULL) ||
1813                 (current->left->token.type != token_string) ||
1814                 ((current->right->token.type != token_string) &&
1815                  (current->right->token.type != token_re))) {
1816                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1817                             "Invalid expression \"%s\" in file %s",
1818                             expr, r->filename);
1819                 *was_error = 1;
1820                 return retval;
1821             }
1822             buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1823                                          NULL, MAX_STRING_LEN, 0);
1824             current->left->token.value = buffer;
1825             buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1826                                          NULL, MAX_STRING_LEN, 0);
1827             current->right->token.value = buffer;
1828             if (current->right->token.type == token_re) {
1829 #ifdef DEBUG_INCLUDE
1830                 debug_pos += sprintf (&debug[debug_pos],
1831                                       "     Re Compare (%s) with /%s/\n",
1832                                       current->left->token.value,
1833                                       current->right->token.value);
1834 #endif
1835                 current->value =
1836                     re_check(r, ctx, current->left->token.value,
1837                              current->right->token.value);
1838             }
1839             else {
1840 #ifdef DEBUG_INCLUDE
1841                 debug_pos += sprintf (&debug[debug_pos],
1842                                       "     Compare (%s) with (%s)\n",
1843                                       current->left->token.value,
1844                                       current->right->token.value);
1845 #endif
1846                 current->value =
1847                     (strcmp(current->left->token.value,
1848                             current->right->token.value) == 0);
1849             }
1850             if (current->token.type == token_ne) {
1851                 current->value = !current->value;
1852             }
1853 #ifdef DEBUG_INCLUDE
1854             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1855                                   current->value ? '1' : '0');
1856 #endif
1857             current->done = 1;
1858             current = current->parent;
1859             break;
1860         case token_ge:
1861         case token_gt:
1862         case token_le:
1863         case token_lt:
1864 #ifdef DEBUG_INCLUDE
1865             memcpy (&debug[debug_pos], "     Evaluate ge/gt/le/lt\n",
1866                     sizeof ("     Evaluate ge/gt/le/lt\n"));
1867             debug_pos += sizeof ("     Evaluate ge/gt/le/lt\n");
1868 #endif
1869             if ((current->left == (struct parse_node *) NULL) ||
1870                 (current->right == (struct parse_node *) NULL) ||
1871                 (current->left->token.type != token_string) ||
1872                 (current->right->token.type != token_string)) {
1873                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1874                             "Invalid expression \"%s\" in file %s",
1875                             expr, r->filename);
1876                 *was_error = 1;
1877                 return retval;
1878             }
1879             buffer = ap_ssi_parse_string(r, ctx, current->left->token.value,
1880                                          NULL, MAX_STRING_LEN, 0);
1881             current->left->token.value = buffer;
1882             buffer = ap_ssi_parse_string(r, ctx, current->right->token.value,
1883                                          NULL, MAX_STRING_LEN, 0);
1884             current->right->token.value = buffer;
1885 #ifdef DEBUG_INCLUDE
1886             debug_pos += sprintf (&debug[debug_pos],
1887                                   "     Compare (%s) with (%s)\n",
1888                                   current->left->token.value,
1889                                   current->right->token.value);
1890 #endif
1891             current->value =
1892                 strcmp(current->left->token.value,
1893                        current->right->token.value);
1894             if (current->token.type == token_ge) {
1895                 current->value = current->value >= 0;
1896             }
1897             else if (current->token.type == token_gt) {
1898                 current->value = current->value > 0;
1899             }
1900             else if (current->token.type == token_le) {
1901                 current->value = current->value <= 0;
1902             }
1903             else if (current->token.type == token_lt) {
1904                 current->value = current->value < 0;
1905             }
1906             else {
1907                 current->value = 0;     /* Don't return -1 if unknown token */
1908             }
1909 #ifdef DEBUG_INCLUDE
1910             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1911                                   current->value ? '1' : '0');
1912 #endif
1913             current->done = 1;
1914             current = current->parent;
1915             break;
1916
1917         case token_not:
1918             if (current->right != (struct parse_node *) NULL) {
1919                 if (!current->right->done) {
1920                     current = current->right;
1921                     continue;
1922                 }
1923                 current->value = !current->right->value;
1924             }
1925             else {
1926                 current->value = 0;
1927             }
1928 #ifdef DEBUG_INCLUDE
1929             debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
1930                                   current->value ? '1' : '0');
1931 #endif
1932             current->done = 1;
1933             current = current->parent;
1934             break;
1935
1936         case token_group:
1937             if (current->right != (struct parse_node *) NULL) {
1938                 if (!current->right->done) {
1939                     current = current->right;
1940                     continue;
1941                 }
1942                 current->value = current->right->value;
1943             }
1944             else {
1945                 current->value = 1;
1946             }
1947 #ifdef DEBUG_INCLUDE
1948             debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
1949                                   current->value ? '1' : '0');
1950 #endif
1951             current->done = 1;
1952             current = current->parent;
1953             break;
1954
1955         case token_lbrace:
1956             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1957                         "Unmatched '(' in \"%s\" in file %s",
1958                         expr, r->filename);
1959             *was_error = 1;
1960             return retval;
1961
1962         case token_rbrace:
1963             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1964                         "Unmatched ')' in \"%s\" in file %s",
1965                         expr, r->filename);
1966             *was_error = 1;
1967             return retval;
1968
1969         default:
1970             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1971                           "bad token type");
1972             *was_error = 1;
1973             return retval;
1974         }
1975     }
1976
1977     retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
1978     return (retval);
1979 }
1980
1981 /*-------------------------------------------------------------------------*/
1982 #ifdef DEBUG_INCLUDE
1983
1984 #define MAX_DEBUG_SIZE MAX_STRING_LEN
1985 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)           \
1986 {                                                                          \
1987     char cond_txt[] = "**** X     conditional_status=\"0\"\n";             \
1988                                                                            \
1989     if (cntx->flags & FLAG_COND_TRUE) {                                    \
1990         cond_txt[31] = '1';                                                \
1991     }                                                                      \
1992     memcpy(&cond_txt[5], tag_text, sizeof(tag_text)-1);                    \
1993     t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt)-1,          \
1994                                     NULL, h_ptr->list);                    \
1995     APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                               \
1996                                                                            \
1997     if (ins_head == NULL) {                                                \
1998         ins_head = t_buck;                                                 \
1999     }                                                                      \
2000 }
2001 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)            \
2002 {                                                                        \
2003     if (d_buf[0] != '\0') {                                              \
2004         t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf),            \
2005                                         NULL, h_ptr->list);              \
2006         APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                         \
2007                                                                          \
2008         if (ins_head == NULL) {                                          \
2009             ins_head = t_buck;                                           \
2010         }                                                                \
2011     }                                                                    \
2012 }
2013 #else
2014
2015 #define MAX_DEBUG_SIZE 10
2016 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)
2017 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)
2018
2019 #endif
2020 /*-------------------------------------------------------------------------*/
2021
2022 /* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
2023 static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2024                      request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
2025                      apr_bucket **inserted_head)
2026 {
2027     char *tag     = NULL;
2028     char *tag_val = NULL;
2029     char *expr    = NULL;
2030     int   expr_ret, was_error, was_unmatched;
2031     apr_bucket *tmp_buck;
2032     char debug_buf[MAX_DEBUG_SIZE];
2033
2034     *inserted_head = NULL;
2035     if (!(ctx->flags & FLAG_PRINTING)) {
2036         ctx->if_nesting_level++;
2037     }
2038     else {
2039         while (1) {
2040             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2041             if (tag == NULL) {
2042                 if (expr == NULL) {
2043                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2044                                   "missing expr in if statement: %s", 
2045                                   r->filename);
2046                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
2047                                         *inserted_head);
2048                     return 1;
2049                 }
2050                 expr_ret = parse_expr(r, ctx, expr, &was_error, 
2051                                       &was_unmatched, debug_buf);
2052                 if (was_error) {
2053                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
2054                                         *inserted_head);
2055                     return 1;
2056                 }
2057                 if (was_unmatched) {
2058                     DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, 
2059                                           "\nUnmatched '\n", *inserted_head);
2060                 }
2061                 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, 
2062                                       *inserted_head);
2063                 
2064                 if (expr_ret) {
2065                     ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2066                 }
2067                 else {
2068                     ctx->flags &= FLAG_CLEAR_PRINT_COND;
2069                 }
2070                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, 
2071                                 "   if");
2072                 ctx->if_nesting_level = 0;
2073                 return 0;
2074             }
2075             else if (!strcmp(tag, "expr")) {
2076                 expr = tag_val;
2077 #ifdef DEBUG_INCLUDE
2078                 if (1) {
2079                     apr_size_t d_len = 0;
2080                     d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
2081                     tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2082                                                   r->connection->bucket_alloc);
2083                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2084
2085                     if (*inserted_head == NULL) {
2086                         *inserted_head = tmp_buck;
2087                     }
2088                 }
2089 #endif
2090             }
2091             else {
2092                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2093                             "unknown parameter \"%s\" to tag if in %s", tag, 
2094                             r->filename);
2095                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2096                 return 1;
2097             }
2098
2099         }
2100     }
2101     return 0;
2102 }
2103
2104 static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2105                        request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
2106                        apr_bucket **inserted_head)
2107 {
2108     char *tag     = NULL;
2109     char *tag_val = NULL;
2110     char *expr    = NULL;
2111     int   expr_ret, was_error, was_unmatched;
2112     apr_bucket *tmp_buck;
2113     char debug_buf[MAX_DEBUG_SIZE];
2114
2115     *inserted_head = NULL;
2116     if (!ctx->if_nesting_level) {
2117         while (1) {
2118             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2119             if (tag == '\0') {
2120                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, 
2121                                 " elif");
2122                 
2123                 if (ctx->flags & FLAG_COND_TRUE) {
2124                     ctx->flags &= FLAG_CLEAR_PRINTING;
2125                     return (0);
2126                 }
2127                 if (expr == NULL) {
2128                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2129                                   "missing expr in elif statement: %s", 
2130                                   r->filename);
2131                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
2132                                         *inserted_head);
2133                     return 1;
2134                 }
2135                 expr_ret = parse_expr(r, ctx, expr, &was_error, 
2136                                       &was_unmatched, debug_buf);
2137                 if (was_error) {
2138                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
2139                                         *inserted_head);
2140                     return 1;
2141                 }
2142                 if (was_unmatched) {
2143                     DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, 
2144                                           "\nUnmatched '\n", *inserted_head);
2145                 }
2146                 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, 
2147                                       *inserted_head);
2148                 
2149                 if (expr_ret) {
2150                     ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2151                 }
2152                 else {
2153                     ctx->flags &= FLAG_CLEAR_PRINT_COND;
2154                 }
2155                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, 
2156                                 " elif");
2157                 return (0);
2158             }
2159             else if (!strcmp(tag, "expr")) {
2160                 expr = tag_val;
2161 #ifdef DEBUG_INCLUDE
2162                 if (1) {
2163                     apr_size_t d_len = 0;
2164                     d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
2165                     tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL,
2166                                                   r->connection->bucket_alloc);
2167                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2168
2169                     if (*inserted_head == NULL) {
2170                         *inserted_head = tmp_buck;
2171                     }
2172                 }
2173 #endif
2174             }
2175             else {
2176                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2177                                "unknown parameter \"%s\" to tag if in %s", tag, 
2178                                r->filename);
2179                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2180                 return 1;
2181             }
2182         }
2183     }
2184     return 0;
2185 }
2186
2187 static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2188                        request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
2189                        apr_bucket **inserted_head)
2190 {
2191     char *tag = NULL;
2192     char *tag_val = NULL;
2193     apr_bucket *tmp_buck;
2194
2195     *inserted_head = NULL;
2196     if (!ctx->if_nesting_level) {
2197         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2198         if ((tag != NULL) || (tag_val != NULL)) {
2199             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2200                         "else directive does not take tags in %s", r->filename);
2201             if (ctx->flags & FLAG_PRINTING) {
2202                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2203             }
2204             return -1;
2205         }
2206         else {
2207             LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else");
2208             
2209             if (ctx->flags & FLAG_COND_TRUE) {
2210                 ctx->flags &= FLAG_CLEAR_PRINTING;
2211             }
2212             else {
2213                 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2214             }
2215             return 0;
2216         }
2217     }
2218     return 0;
2219 }
2220
2221 static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2222                         request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
2223                         apr_bucket **inserted_head)
2224 {
2225     char *tag     = NULL;
2226     char *tag_val = NULL;
2227     apr_bucket *tmp_buck;
2228
2229     *inserted_head = NULL;
2230     if (!ctx->if_nesting_level) {
2231         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2232         if ((tag != NULL) || (tag_val != NULL)) {
2233             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2234                        "endif directive does not take tags in %s", r->filename);
2235             CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2236             return -1;
2237         }
2238         else {
2239             LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif");
2240             ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2241             return 0;
2242         }
2243     }
2244     else {
2245         ctx->if_nesting_level--;
2246         return 0;
2247     }
2248 }
2249
2250 static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2251                       request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, 
2252                       apr_bucket **inserted_head)
2253 {
2254     char *tag     = NULL;
2255     char *tag_val = NULL;
2256     char *var     = NULL;
2257     apr_bucket *tmp_buck;
2258     char *parsed_string;
2259     request_rec *sub = r->main;
2260     apr_pool_t *p = r->pool;
2261
2262     /* we need to use the 'main' request pool to set notes as that is 
2263      * a notes lifetime
2264      */
2265     while (sub) {
2266         p = sub->pool;
2267         sub = sub->main;
2268     }
2269
2270     *inserted_head = NULL;
2271     if (ctx->flags & FLAG_PRINTING) {
2272         while (1) {
2273             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2274             if ((tag == NULL) && (tag_val == NULL)) {
2275                 return 0;
2276             }
2277             else if (tag_val == NULL) {
2278                 return 1;
2279             }
2280             else if (!strcmp(tag, "var")) {
2281                 var = ap_ssi_parse_string(r, ctx, tag_val, NULL,
2282                                           MAX_STRING_LEN, 0);
2283             }
2284             else if (!strcmp(tag, "value")) {
2285                 if (var == (char *) NULL) {
2286                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2287                            "variable must precede value in set directive in %s",
2288                            r->filename);
2289                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, 
2290                                         *inserted_head);
2291                     return (-1);
2292                 }
2293                 parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, 
2294                                                     MAX_STRING_LEN, 0);
2295                 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2296                                apr_pstrdup(p, parsed_string));
2297             }
2298             else {
2299                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2300                             "Invalid tag for set directive in %s", r->filename);
2301                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2302                 return -1;
2303             }
2304         }
2305     }
2306     return 0;
2307 }
2308
2309 static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb, 
2310                            request_rec *r, ap_filter_t *f, 
2311                            apr_bucket *head_ptr, apr_bucket **inserted_head)
2312 {
2313     char *tag     = NULL;
2314     char *tag_val = NULL;
2315     apr_bucket *tmp_buck;
2316
2317     if (ctx->flags & FLAG_PRINTING) {
2318         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2319         if ((tag == NULL) && (tag_val == NULL)) {
2320             const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
2321             const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
2322             int i;
2323             const char *key_text, *val_text;
2324             char *key_val, *next;
2325             apr_size_t   k_len, v_len, kv_length;
2326
2327             *inserted_head = NULL;
2328             for (i = 0; i < arr->nelts; ++i) {
2329                 key_text = ap_escape_html(r->pool, elts[i].key);
2330                 val_text = elts[i].val;
2331                 if (val_text == LAZY_VALUE) {
2332                     val_text = add_include_vars_lazy(r, elts[i].key);
2333                 }
2334                 val_text = ap_escape_html(r->pool, elts[i].val);
2335                 k_len = strlen(key_text);
2336                 v_len = strlen(val_text);
2337                 kv_length = k_len + v_len + sizeof("=\n");
2338                 key_val = apr_palloc(r->pool, kv_length);
2339                 next = key_val;
2340                 memcpy(next, key_text, k_len);
2341                 next += k_len;
2342                 *next++ = '=';
2343                 memcpy(next, val_text, v_len);
2344                 next += v_len;
2345                 *next++ = '\n';
2346                 *next = 0;
2347                 tmp_buck = apr_bucket_pool_create(key_val, kv_length - 1,
2348                                                   r->pool,
2349                                                   r->connection->bucket_alloc);
2350                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2351                 if (*inserted_head == NULL) {
2352                     *inserted_head = tmp_buck;
2353                 }
2354             }
2355             return 0;
2356         }
2357         else {
2358             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2359                         "printenv directive does not take tags in %s", 
2360                         r->filename);
2361             CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2362             return -1;
2363         }
2364     }
2365     return 0;
2366 }
2367
2368 /* -------------------------- The main function --------------------------- */
2369
2370 /*
2371  * returns the index position of the first byte of start_seq (or the len of
2372  * the buffer as non-match)
2373  */
2374 static apr_size_t find_start_sequence(ssi_ctx_t *ctx, const char *data,
2375                                       apr_size_t len)
2376 {
2377     apr_size_t slen = ctx->ctx->start_seq_len;
2378     apr_size_t index;
2379     const char *p, *ep;
2380
2381     if (len < slen) {
2382         p = data; /* try partial match at the end of the buffer (below) */
2383     }
2384     else {
2385         /* try fast bndm search over the buffer
2386          * (hopefully the whole start sequence can be found in this buffer)
2387          */
2388         index = bndm(ctx->ctx->start_seq, ctx->ctx->start_seq_len, data, len,
2389                      ctx->ctx->start_seq_pat);
2390
2391         /* wow, found it. ready. */
2392         if (index < len) {
2393             ctx->state = PARSE_DIRECTIVE;
2394             return index;
2395         }
2396         else {
2397             /* ok, the pattern can't be found as whole in the buffer,
2398              * check the end for a partial match
2399              */
2400             p = data + len - slen + 1;
2401         }
2402     }
2403
2404     ep = data + len;
2405     do {
2406         while (p < ep && *p != *ctx->ctx->start_seq) {
2407             ++p;
2408         }
2409
2410         index = p - data;
2411
2412         /* found a possible start_seq start */
2413         if (p < ep) {
2414             apr_size_t pos = 1;
2415
2416             ++p;
2417             while (p < ep && *p == ctx->ctx->start_seq[pos]) {
2418                 ++p;
2419                 ++pos;
2420             }
2421
2422             /* partial match found. Store the info for the next round */
2423             if (p == ep) {
2424                 ctx->state = PARSE_HEAD;
2425                 ctx->ctx->parse_pos = pos;
2426                 return index;
2427             }
2428         }
2429
2430         /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2431          * and a string data of "--.-" and the end of the buffer
2432          */
2433         p = data + index + 1;
2434     } while (p < ep);
2435
2436     /* no match */
2437     return len;
2438 }
2439
2440 /*
2441  * returns the first byte *after* the partial (or final) match.
2442  *
2443  * If we had to trick with the start_seq start, 'release' returns the
2444  * number of chars of the start_seq which appeared not to be part of a
2445  * full tag and may have to be passed down the filter chain.
2446  */
2447 static apr_size_t find_partial_start_sequence(ssi_ctx_t *ctx,
2448                                               const char *data,
2449                                               apr_size_t len,
2450                                               apr_size_t *release)
2451 {
2452     apr_size_t pos, spos = 0;
2453     apr_size_t slen = ctx->ctx->start_seq_len;
2454     const char *p, *ep;
2455
2456     pos = ctx->ctx->parse_pos;
2457     ep = data + len;
2458     *release = 0;
2459
2460     do {
2461         p = data;
2462
2463         while (p < ep && pos < slen && *p == ctx->ctx->start_seq[pos]) {
2464             ++p;
2465             ++pos;
2466         }
2467
2468         /* full match */
2469         if (pos == slen) {
2470             ctx->state = PARSE_DIRECTIVE;
2471             return (p - data);
2472         }
2473
2474         /* the whole buffer is a partial match */
2475         if (p == ep) {
2476             ctx->ctx->parse_pos = pos;
2477             return (p - data);
2478         }
2479
2480         /* No match so far, but again:
2481          * We must try all combinations, since the start_seq is a random
2482          * user supplied string
2483          *
2484          * So: look if the first char of start_seq appears somewhere within
2485          * the current partial match. If it does, try to start a match that
2486          * begins with this offset. (This can happen, if a strange
2487          * start_seq like "---->" spans buffers)
2488          */
2489         if (spos < ctx->ctx->parse_pos) {
2490             do {
2491                 ++spos;
2492                 ++*release;
2493                 p = ctx->ctx->start_seq + spos;
2494                 pos = ctx->ctx->parse_pos - spos;
2495
2496                 while (pos && *p != *ctx->ctx->start_seq) {
2497                     ++p;
2498                     ++spos;
2499                     ++*release;
2500                     --pos;
2501                 }
2502
2503                 /* if a matching beginning char was found, try to match the
2504                  * remainder of the old buffer.
2505                  */
2506                 if (pos > 1) {
2507                     apr_size_t t = 1;
2508
2509                     ++p;
2510                     while (t < pos && *p == ctx->ctx->start_seq[t]) {
2511                         ++p;
2512                         ++t;
2513                     }
2514
2515                     if (t == pos) {
2516                         /* yeah, another partial match found in the *old*
2517                          * buffer, now test the *current* buffer for
2518                          * continuing match
2519                          */
2520                         break;
2521                     }
2522                 }
2523             } while (pos > 1);
2524
2525             if (pos) {
2526                 continue;
2527             }
2528         }
2529
2530         break;
2531     } while (1); /* work hard to find a match ;-) */
2532
2533     /* no match at all, release all (wrongly) matched chars so far */
2534     *release = ctx->ctx->parse_pos;
2535     ctx->state = PARSE_PRE_HEAD;
2536     return 0;
2537 }
2538
2539 /*
2540  * returns the position after the directive
2541  */
2542 static apr_size_t find_directive(ssi_ctx_t *ctx, const char *data,
2543                                  apr_size_t len, char ***store,
2544                                  apr_size_t **store_len)
2545 {
2546     const char *p = data;
2547     const char *ep = data + len;
2548     apr_size_t pos;
2549
2550     switch (ctx->state) {
2551     case PARSE_DIRECTIVE:
2552         while (p < ep && !apr_isspace(*p)) {
2553             /* we have to consider the case of missing space between directive
2554              * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2555              */
2556             if (*p == *ctx->ctx->end_seq) {
2557                 ctx->state = PARSE_DIRECTIVE_TAIL;
2558                 ctx->ctx->parse_pos = 1;
2559                 ++p;
2560                 return (p - data);
2561             }
2562             ++p;
2563         }
2564
2565         if (p < ep) { /* found delimiter whitespace */
2566             ctx->state = PARSE_DIRECTIVE_POSTNAME;
2567             *store = &ctx->directive;
2568             *store_len = &ctx->ctx->directive_length;
2569         }
2570
2571         break;
2572
2573     case PARSE_DIRECTIVE_TAIL:
2574         pos = ctx->ctx->parse_pos;
2575
2576         while (p < ep && pos < ctx->end_seq_len &&
2577                *p == ctx->ctx->end_seq[pos]) {
2578             ++p;
2579             ++pos;
2580         }
2581
2582         /* full match, we're done */
2583         if (pos == ctx->end_seq_len) {
2584             ctx->state = PARSE_DIRECTIVE_POSTTAIL;
2585             *store = &ctx->directive;
2586             *store_len = &ctx->ctx->directive_length;
2587             break;
2588         }
2589
2590         /* partial match, the buffer is too small to match fully */
2591         if (p == ep) {
2592             ctx->ctx->parse_pos = pos;
2593             break;
2594         }
2595
2596         /* no match. continue normal parsing */
2597         ctx->state = PARSE_DIRECTIVE;
2598         return 0;
2599
2600     case PARSE_DIRECTIVE_POSTTAIL:
2601         ctx->state = PARSE_EXECUTE;
2602         ctx->ctx->directive_length -= ctx->end_seq_len;
2603         /* continue immediately with the next state */
2604
2605     case PARSE_DIRECTIVE_POSTNAME:
2606         if (PARSE_DIRECTIVE_POSTNAME == ctx->state) {
2607             ctx->state = PARSE_PRE_ARG;
2608         }
2609         ctx->argc = 0;
2610         ctx->argv = NULL;
2611
2612         if (!ctx->ctx->directive_length) {
2613             ctx->error = 1;
2614             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing directive "
2615                           "name in parsed document %s", ctx->r->filename);
2616         }
2617         else {
2618             char *sp = ctx->directive;
2619             char *sep = ctx->directive + ctx->ctx->directive_length;
2620
2621             /* normalize directive name */
2622             for (; sp < sep; ++sp) {
2623                 *sp = apr_tolower(*sp);
2624             }
2625         }
2626
2627         return 0;
2628
2629     default:
2630         /* get a rid of a gcc warning about unhandled enumerations */
2631         break;
2632     }
2633
2634     return (p - data);
2635 }
2636
2637 /*
2638  * find out whether the next token is (a possible) end_seq or an argument
2639  */
2640 static apr_size_t find_arg_or_tail(ssi_ctx_t *ctx, const char *data,
2641                                    apr_size_t len)
2642 {
2643     const char *p = data;
2644     const char *ep = data + len;
2645
2646     /* skip leading WS */
2647     while (p < ep && apr_isspace(*p)) {
2648         ++p;
2649     }
2650
2651     /* buffer doesn't consist of whitespaces only */
2652     if (p < ep) {
2653         ctx->state = (*p == *ctx->ctx->end_seq) ? PARSE_TAIL : PARSE_ARG;
2654     }
2655
2656     return (p - data);
2657 }
2658
2659 /*
2660  * test the stream for end_seq. If it doesn't match at all, it must be an
2661  * argument
2662  */
2663 static apr_size_t find_tail(ssi_ctx_t *ctx, const char *data,
2664                             apr_size_t len)
2665 {
2666     const char *p = data;
2667     const char *ep = data + len;
2668     apr_size_t pos = ctx->ctx->parse_pos;
2669
2670     if (PARSE_TAIL == ctx->state) {
2671         ctx->state = PARSE_TAIL_SEQ;
2672         pos = ctx->ctx->parse_pos = 0;
2673     }
2674
2675     while (p < ep && pos < ctx->end_seq_len && *p == ctx->ctx->end_seq[pos]) {
2676         ++p;
2677         ++pos;
2678     }
2679
2680     /* bingo, full match */
2681     if (pos == ctx->end_seq_len) {
2682         ctx->state = PARSE_EXECUTE;
2683         return (p - data);
2684     }
2685
2686     /* partial match, the buffer is too small to match fully */
2687     if (p == ep) {
2688         ctx->ctx->parse_pos = pos;
2689         return (p - data);
2690     }
2691
2692     /* no match. It must be an argument string then */
2693     ctx->state = PARSE_ARG;
2694     return 0;
2695 }
2696
2697 /*
2698  * extract name=value from the buffer
2699  * A pcre-pattern could look (similar to):
2700  * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2701  */
2702 static apr_size_t find_argument(ssi_ctx_t *ctx, const char *data,
2703                                 apr_size_t len, char ***store,
2704                                 apr_size_t **store_len)
2705 {
2706     const char *p = data;
2707     const char *ep = data + len;
2708
2709     switch (ctx->state) {
2710     case PARSE_ARG:
2711         /*
2712          * create argument structure and append it to the current list
2713          */
2714         ctx->current_arg = apr_palloc(ctx->dpool,
2715                                       sizeof(*ctx->current_arg));
2716         ctx->current_arg->next = NULL;
2717
2718         ++(ctx->argc);
2719         if (!ctx->argv) {
2720             ctx->argv = ctx->current_arg;
2721         }
2722         else {
2723             ssi_arg_item_t *newarg = ctx->argv;
2724
2725             while (newarg->next) {
2726                 newarg = newarg->next;
2727             }
2728             newarg->next = ctx->current_arg;
2729         }
2730
2731         /* check whether it's a valid one. If it begins with a quote, we
2732          * can safely assume, someone forgot the name of the argument
2733          */
2734         switch (*p) {
2735         case '"': case '\'': case '`':
2736             *store = NULL;
2737
2738             ctx->state = PARSE_ARG_VAL;
2739             ctx->quote = *p++;
2740             ctx->current_arg->name = NULL;
2741             ctx->current_arg->name_len = 0;
2742             ctx->error = 1;
2743
2744             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing argument "
2745                           "name for value to tag %s in %s",
2746                           apr_pstrmemdup(ctx->r->pool, ctx->directive,
2747                                          ctx->ctx->directive_length),
2748                                          ctx->r->filename);
2749
2750             return (p - data);
2751
2752         default:
2753             ctx->state = PARSE_ARG_NAME;
2754         }
2755         /* continue immediately with next state */
2756
2757     case PARSE_ARG_NAME:
2758         while (p < ep && !apr_isspace(*p) && *p != '=') {
2759             ++p;
2760         }
2761
2762         if (p < ep) {
2763             ctx->state = PARSE_ARG_POSTNAME;
2764             *store = &ctx->current_arg->name;
2765             *store_len = &ctx->current_arg->name_len;
2766             return (p - data);
2767         }
2768         break;
2769
2770     case PARSE_ARG_POSTNAME:
2771         ctx->current_arg->name = apr_pstrmemdup(ctx->dpool,
2772                                                 ctx->current_arg->name,
2773                                                 ctx->current_arg->name_len);
2774         if (!ctx->current_arg->name_len) {
2775             ctx->error = 1;
2776             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "missing argument "
2777                           "name for value to tag %s in %s",
2778                           apr_pstrmemdup(ctx->r->pool, ctx->directive,
2779                                          ctx->ctx->directive_length),
2780                                          ctx->r->filename);
2781         }
2782         else {
2783             char *sp = ctx->current_arg->name;
2784
2785             /* normalize the name */
2786             while (*sp) {
2787                 *sp = apr_tolower(*sp);
2788                 ++sp;
2789             }
2790         }
2791
2792         ctx->state = PARSE_ARG_EQ;
2793         /* continue with next state immediately */
2794
2795     case PARSE_ARG_EQ:
2796         *store = NULL;
2797
2798         while (p < ep && apr_isspace(*p)) {
2799             ++p;
2800         }
2801
2802         if (p < ep) {
2803             if (*p == '=') {
2804                 ctx->state = PARSE_ARG_PREVAL;
2805                 ++p;
2806             }
2807             else { /* no value */
2808                 ctx->current_arg->value = NULL;
2809                 ctx->state = PARSE_PRE_ARG;
2810             }
2811
2812             return (p - data);
2813         }
2814         break;
2815
2816     case PARSE_ARG_PREVAL:
2817         *store = NULL;
2818
2819         while (p < ep && apr_isspace(*p)) {
2820             ++p;
2821         }
2822
2823         /* buffer doesn't consist of whitespaces only */
2824         if (p < ep) {
2825             ctx->state = PARSE_ARG_VAL;
2826             switch (*p) {
2827             case '"': case '\'': case '`':
2828                 ctx->quote = *p++;
2829                 break;
2830             default:
2831                 ctx->quote = '\0';
2832                 break;
2833             }
2834
2835             return (p - data);
2836         }
2837         break;
2838
2839     case PARSE_ARG_VAL_ESC:
2840         if (*p == ctx->quote) {
2841             ++p;
2842         }
2843         ctx->state = PARSE_ARG_VAL;
2844         /* continue with next state immediately */
2845
2846     case PARSE_ARG_VAL:
2847         for (; p < ep; ++p) {
2848             if (ctx->quote && *p == '\\') {
2849                 ++p;
2850                 if (p == ep) {
2851                     ctx->state = PARSE_ARG_VAL_ESC;
2852                     break;
2853                 }
2854
2855                 if (*p != ctx->quote) {
2856                     --p;
2857                 }
2858             }
2859             else if (ctx->quote && *p == ctx->quote) {
2860                 ++p;
2861                 *store = &ctx->current_arg->value;
2862                 *store_len = &ctx->current_arg->value_len;
2863                 ctx->state = PARSE_ARG_POSTVAL;
2864                 break;
2865             }
2866             else if (!ctx->quote && apr_isspace(*p)) {
2867                 ++p;
2868                 *store = &ctx->current_arg->value;
2869                 *store_len = &ctx->current_arg->value_len;
2870                 ctx->state = PARSE_ARG_POSTVAL;
2871                 break;
2872             }
2873         }
2874
2875         return (p - data);
2876
2877     case PARSE_ARG_POSTVAL:
2878         /*
2879          * The value is still the raw input string. Finally clean it up.
2880          */
2881         --(ctx->current_arg->value_len);
2882
2883         /* strip quote escaping \ from the string */
2884         if (ctx->quote) {
2885             apr_size_t shift = 0;
2886             char *sp;
2887
2888             sp = ctx->current_arg->value;
2889             ep = ctx->current_arg->value + ctx->current_arg->value_len;
2890             while (sp < ep && *sp != '\\') {
2891                 ++sp;
2892             }
2893             for (; sp < ep; ++sp) {
2894                 if (*sp == '\\' && sp[1] == ctx->quote) {
2895                     ++sp;
2896                     ++shift;
2897                 }
2898                 if (shift) {
2899                     *(sp-shift) = *sp;
2900                 }
2901             }
2902
2903             ctx->current_arg->value_len -= shift;
2904         }
2905
2906         ctx->current_arg->value[ctx->current_arg->value_len] = '\0';
2907         ctx->state = PARSE_PRE_ARG;
2908
2909         return 0;
2910
2911     default:
2912         /* get a rid of a gcc warning about unhandled enumerations */
2913         break;
2914     }
2915
2916     return len; /* partial match of something */
2917 }
2918
2919 /*
2920  * This is the main loop over the current bucket brigade.
2921  */
2922 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
2923 {
2924     ssi_ctx_t *ctx = f->ctx;
2925     request_rec *r = f->r;
2926     apr_bucket *b = APR_BRIGADE_FIRST(bb);
2927     apr_bucket_brigade *pass_bb;
2928     apr_status_t rv = APR_SUCCESS;
2929     char *magic; /* magic pointer for sentinel use */
2930
2931     /* fast exit */
2932     if (APR_BRIGADE_EMPTY(bb)) {
2933         return APR_SUCCESS;
2934     }
2935
2936     /* we may crash, since already cleaned up; hand over the responsibility
2937      * to the next filter;-)
2938      */
2939     if (ctx->seen_eos) {
2940         return ap_pass_brigade(f->next, bb);
2941     }
2942
2943     /* All stuff passed along has to be put into that brigade */
2944     pass_bb = apr_brigade_create(ctx->ctx->pool, f->c->bucket_alloc);
2945     ctx->ctx->bytes_parsed = 0;
2946     ctx->ctx->output_now = 0;
2947     ctx->error = 0;
2948
2949     /* loop over the current bucket brigade */
2950     while (b != APR_BRIGADE_SENTINEL(bb)) {
2951         const char *data = NULL;
2952         apr_size_t len, index, release;
2953         apr_bucket *newb = NULL;
2954         char **store = &magic;
2955         apr_size_t *store_len;
2956
2957         /* handle meta buckets before reading any data */
2958         if (APR_BUCKET_IS_METADATA(b)) {
2959             newb = APR_BUCKET_NEXT(b);
2960
2961             APR_BUCKET_REMOVE(b);
2962
2963             if (APR_BUCKET_IS_EOS(b)) {
2964                 ctx->seen_eos = 1;
2965
2966                 /* Hit end of stream, time for cleanup ... But wait!
2967                  * Perhaps we're not ready yet. We may have to loop one or
2968                  * two times again to finish our work. In that case, we
2969                  * just re-insert the EOS bucket to allow for an extra loop.
2970                  *
2971                  * PARSE_EXECUTE means, we've hit a directive just before the
2972                  *    EOS, which is now waiting for execution.
2973                  *
2974                  * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
2975                  *    no argument and no space between directive and end_seq
2976                  *    just before the EOS. (consider <!--#printenv--> as last
2977                  *    or only string within the stream). This state, however,
2978                  *    just cleans up and turns itself to PARSE_EXECUTE, which
2979                  *    will be passed through within the next (and actually
2980                  *    last) round.
2981                  */
2982                 if (PARSE_EXECUTE            == ctx->state ||
2983                     PARSE_DIRECTIVE_POSTTAIL == ctx->state) {
2984                     APR_BUCKET_INSERT_BEFORE(newb, b);
2985                 }
2986                 else {
2987                     break; /* END OF STREAM */
2988                 }
2989             }
2990             else {
2991                 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
2992
2993                 if (APR_BUCKET_IS_FLUSH(b)) {
2994                     ctx->ctx->output_now = 1;
2995                 }
2996
2997                 b = newb;
2998                 continue;
2999             }
3000         }
3001
3002         /* enough is enough ... */
3003         if (ctx->ctx->output_now ||
3004             ctx->ctx->bytes_parsed > AP_MIN_BYTES_TO_WRITE) {
3005
3006             if (!APR_BRIGADE_EMPTY(pass_bb)) {
3007                 rv = ap_pass_brigade(f->next, pass_bb);
3008                 if (!APR_STATUS_IS_SUCCESS(rv)) {
3009                     apr_brigade_destroy(pass_bb);
3010                     return rv;
3011                 }
3012             }
3013
3014             ctx->ctx->output_now = 0;
3015             ctx->ctx->bytes_parsed = 0;
3016         }
3017
3018         /* read the current bucket data */
3019         len = 0;
3020         if (!ctx->seen_eos) {
3021             if (ctx->ctx->bytes_parsed > 0) {
3022                 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3023                 if (APR_STATUS_IS_EAGAIN(rv)) {
3024                     ctx->ctx->output_now = 1;
3025                     continue;
3026                 }
3027             }
3028
3029             if (!len || !APR_STATUS_IS_SUCCESS(rv)) {
3030                 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3031             }
3032
3033             if (!APR_STATUS_IS_SUCCESS(rv)) {
3034                 apr_brigade_destroy(pass_bb);
3035                 return rv;
3036             }
3037
3038             ctx->ctx->bytes_parsed += len;
3039         }
3040
3041         /* zero length bucket, fetch next one */
3042         if (!len && !ctx->seen_eos) {
3043             b = APR_BUCKET_NEXT(b);
3044             continue;
3045         }
3046
3047         /*
3048          * it's actually a data containing bucket, start/continue parsing
3049          */
3050
3051         switch (ctx->state) {
3052         /* no current tag; search for start sequence */
3053         case PARSE_PRE_HEAD:
3054             index = find_start_sequence(ctx, data, len);
3055
3056             if (index < len) {
3057                 apr_bucket_split(b, index);
3058             }
3059
3060             newb = APR_BUCKET_NEXT(b);
3061             if (ctx->ctx->flags & FLAG_PRINTING) {
3062                 APR_BUCKET_REMOVE(b);
3063                 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3064             }
3065             else {
3066                 apr_bucket_delete(b);
3067             }
3068
3069             if (index < len) {
3070                 /* now delete the start_seq stuff from the remaining bucket */
3071                 if (PARSE_DIRECTIVE == ctx->state) { /* full match */
3072                     apr_bucket_split(newb, ctx->ctx->start_seq_len);
3073                     ctx->ctx->output_now = 1; /* pass pre-tag stuff */
3074                 }
3075
3076                 b = APR_BUCKET_NEXT(newb);
3077                 apr_bucket_delete(newb);
3078             }
3079             else {
3080                 b = newb;
3081             }
3082
3083             break;
3084
3085         /* we're currently looking for the end of the start sequence */
3086         case PARSE_HEAD:
3087             index = find_partial_start_sequence(ctx, data, len, &release);
3088
3089             /* check if we mismatched earlier and have to release some chars */
3090             if (release && (ctx->ctx->flags & FLAG_PRINTING)) {
3091                 char *to_release = apr_palloc(ctx->ctx->pool, release);
3092
3093                 memcpy(to_release, ctx->ctx->start_seq, release);
3094                 newb = apr_bucket_pool_create(to_release, release,
3095                                               ctx->ctx->pool,
3096                                               f->c->bucket_alloc);
3097                 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3098             }
3099
3100             if (index) { /* any match */
3101                 /* now delete the start_seq stuff from the remaining bucket */
3102                 if (PARSE_DIRECTIVE == ctx->state) { /* final match */
3103                     apr_bucket_split(b, index);
3104                     ctx->ctx->output_now = 1; /* pass pre-tag stuff */
3105                 }
3106                 newb = APR_BUCKET_NEXT(b);
3107                 apr_bucket_delete(b);
3108                 b = newb;
3109             }
3110
3111             break;
3112
3113         /* we're currently grabbing the directive name */
3114         case PARSE_DIRECTIVE:
3115         case PARSE_DIRECTIVE_POSTNAME:
3116         case PARSE_DIRECTIVE_TAIL:
3117         case PARSE_DIRECTIVE_POSTTAIL:
3118             index = find_directive(ctx, data, len, &store, &store_len);
3119
3120             if (index) {
3121                 apr_bucket_split(b, index);
3122                 newb = APR_BUCKET_NEXT(b);
3123             }
3124
3125             if (store) {
3126                 if (index) {
3127                     APR_BUCKET_REMOVE(b);
3128                     APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3129                     b = newb;
3130                 }
3131
3132                 /* time for cleanup? */
3133                 if (store != &magic) {
3134                     apr_brigade_pflatten(ctx->tmp_bb, store, store_len,
3135                                          ctx->dpool);
3136                     apr_brigade_cleanup(ctx->tmp_bb);
3137                 }
3138             }
3139             else if (index) {
3140                 apr_bucket_delete(b);
3141                 b = newb;
3142             }
3143
3144             break;
3145
3146         /* skip WS and find out what comes next (arg or end_seq) */
3147         case PARSE_PRE_ARG:
3148             index = find_arg_or_tail(ctx, data, len);
3149
3150             if (index) { /* skipped whitespaces */
3151                 if (index < len) {
3152                     apr_bucket_split(b, index);
3153                 }
3154                 newb = APR_BUCKET_NEXT(b);
3155                 apr_bucket_delete(b);
3156                 b = newb;
3157             }
3158
3159             break;
3160
3161         /* currently parsing name[=val] */
3162         case PARSE_ARG:
3163         case PARSE_ARG_NAME:
3164         case PARSE_ARG_POSTNAME:
3165         case PARSE_ARG_EQ:
3166         case PARSE_ARG_PREVAL:
3167         case PARSE_ARG_VAL:
3168         case PARSE_ARG_VAL_ESC:
3169         case PARSE_ARG_POSTVAL:
3170             index = find_argument(ctx, data, len, &store, &store_len);
3171
3172             if (index) {
3173                 apr_bucket_split(b, index);
3174                 newb = APR_BUCKET_NEXT(b);
3175             }
3176
3177             if (store) {
3178                 if (index) {
3179                     APR_BUCKET_REMOVE(b);
3180                     APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3181                     b = newb;
3182                 }
3183
3184                 /* time for cleanup? */
3185                 if (store != &magic) {
3186                     apr_brigade_pflatten(ctx->tmp_bb, store, store_len,
3187                                          ctx->dpool);
3188                     apr_brigade_cleanup(ctx->tmp_bb);
3189                 }
3190             }
3191             else if (index) {
3192                 apr_bucket_delete(b);
3193                 b = newb;
3194             }
3195
3196             break;
3197
3198         /* try to match end_seq at current pos. */
3199         case PARSE_TAIL:
3200         case PARSE_TAIL_SEQ:
3201             index = find_tail(ctx, data, len);
3202
3203             switch (ctx->state) {
3204             case PARSE_EXECUTE:  /* full match */
3205                 apr_bucket_split(b, index);
3206                 newb = APR_BUCKET_NEXT(b);
3207                 apr_bucket_delete(b);
3208                 b = newb;
3209                 break;
3210
3211             case PARSE_ARG:      /* no match */
3212                 /* PARSE_ARG must reparse at the beginning */
3213                 APR_BRIGADE_PREPEND(bb, ctx->tmp_bb);
3214                 b = APR_BRIGADE_FIRST(bb);
3215                 break;
3216
3217             default:             /* partial match */
3218                 newb = APR_BUCKET_NEXT(b);
3219                 APR_BUCKET_REMOVE(b);
3220                 APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, b);
3221                 b = newb;
3222                 break;
3223             }
3224
3225             break;
3226
3227         /* now execute the parsed directive, cleanup the space and
3228          * start again with PARSE_PRE_HEAD
3229          */
3230         case PARSE_EXECUTE:
3231             /* if there was an error, it was already logged; just stop here */
3232             if (ctx->error) {
3233                 if (ctx->ctx->flags & FLAG_PRINTING) {
3234                     SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3235                     ctx->error = 0;
3236                 }
3237             }
3238             else {
3239                 include_handler_fn_t *handle_func;
3240
3241                 handle_func =
3242                     (include_handler_fn_t *) apr_hash_get(include_hash,
3243                                                     ctx->directive,
3244                                                     ctx->ctx->directive_length);
3245                 if (handle_func) {
3246                     apr_bucket *dummy;
3247                     char *tag;
3248                     apr_size_t tag_len = 0;
3249                     ssi_arg_item_t *carg = ctx->argv;
3250
3251                     /* legacy wrapper code */
3252                     while (carg) {
3253                         /* +1 \0 byte (either after tag or value)
3254                          * +1 =  byte (before value)
3255                          */
3256                         tag_len += (carg->name  ? carg->name_len      : 0) +
3257                                    (carg->value ? carg->value_len + 1 : 0) + 1;
3258                         carg = carg->next;
3259                     }
3260
3261                     tag = ctx->ctx->combined_tag = ctx->ctx->curr_tag_pos =
3262                         apr_palloc(ctx->dpool, tag_len);
3263
3264                     carg = ctx->argv;
3265                     while (carg) {
3266                         if (carg->name) {
3267                             memcpy(tag, carg->name, carg->name_len);
3268                             tag += carg->name_len;
3269                         }
3270                         if (carg->value) {
3271                             *tag++ = '=';
3272                             memcpy(tag, carg->value, carg->value_len);
3273                             tag += carg->value_len;
3274                         }
3275                         *tag++ = '\0';
3276                         carg = carg->next;
3277                     }
3278                     ctx->ctx->tag_length = tag_len;
3279
3280                     /* create dummy buckets for backards compat */
3281                     ctx->ctx->head_start_bucket =
3282                         apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3283                                                            ctx->ctx->start_seq,
3284                                                        ctx->ctx->start_seq_len),
3285                                                ctx->ctx->start_seq_len,
3286                                                ctx->ctx->pool,
3287                                                f->c->bucket_alloc);
3288                     APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3289                                             ctx->ctx->head_start_bucket);
3290                     ctx->ctx->tag_start_bucket =
3291                         apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3292                                                          ctx->ctx->combined_tag,
3293                                                          ctx->ctx->tag_length),
3294                                                ctx->ctx->tag_length,
3295                                                ctx->ctx->pool,
3296                                                f->c->bucket_alloc);
3297                     APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3298                                             ctx->ctx->tag_start_bucket);
3299                     ctx->ctx->tail_start_bucket =
3300                         apr_bucket_pool_create(apr_pmemdup(ctx->ctx->pool,
3301                                                            ctx->ctx->end_seq,
3302                                                            ctx->end_seq_len),
3303                                                ctx->end_seq_len,
3304                                                ctx->ctx->pool,
3305                                                f->c->bucket_alloc);
3306                     APR_BRIGADE_INSERT_TAIL(ctx->ctx->ssi_tag_brigade,
3307                                             ctx->ctx->tail_start_bucket);
3308
3309                     rv = handle_func(ctx->ctx, &bb, r, f, b, &dummy);
3310
3311                     apr_brigade_cleanup(ctx->ctx->ssi_tag_brigade);
3312
3313                     if (rv != 0 && rv != 1 && rv != -1) {
3314                         apr_brigade_destroy(pass_bb);
3315                         return rv;
3316                     }
3317
3318                     if (dummy) {
3319                         apr_bucket_brigade *remain;
3320
3321                         remain = apr_brigade_split(bb, b);
3322                         APR_BRIGADE_CONCAT(pass_bb, bb);
3323                         bb = remain;
3324                     }
3325                 }
3326                 else {
3327                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3328                                   "unknown directive \"%s\" in parsed doc %s",
3329                                   apr_pstrmemdup(r->pool, ctx->directive,
3330                                                  ctx->ctx->directive_length),
3331                                                  r->filename);
3332                     if (ctx->ctx->flags & FLAG_PRINTING) {
3333                         SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3334                     }
3335                 }
3336             }
3337
3338             /* cleanup */
3339             apr_pool_clear(ctx->dpool);
3340             apr_brigade_cleanup(ctx->tmp_bb);
3341
3342             /* Oooof. Done here, start next round */
3343             ctx->state = PARSE_PRE_HEAD;
3344             break;
3345         }
3346
3347     } /* while (brigade) */
3348
3349     /* End of stream. Final cleanup */
3350     if (ctx->seen_eos) {
3351         if (PARSE_HEAD == ctx->state) {
3352             if (ctx->ctx->flags & FLAG_PRINTING) {
3353                 char *to_release = apr_palloc(ctx->ctx->pool,
3354                                               ctx->ctx->parse_pos);
3355
3356                 memcpy(to_release, ctx->ctx->start_seq, ctx->ctx->parse_pos);
3357                 APR_BRIGADE_INSERT_TAIL(pass_bb,
3358                                         apr_bucket_pool_create(to_release,
3359                                         ctx->ctx->parse_pos, ctx->ctx->pool,
3360                                         f->c->bucket_alloc));
3361             }
3362         }
3363         else if (PARSE_PRE_HEAD != ctx->state) {
3364             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3365                           "SSI directive was not properly finished at the end "
3366                           "of parsed document %s", r->filename);
3367             if (ctx->ctx->flags & FLAG_PRINTING) {
3368                 SSI_CREATE_ERROR_BUCKET(ctx->ctx, f, pass_bb);
3369             }
3370         }
3371
3372         if (!(ctx->ctx->flags & FLAG_PRINTING)) {
3373             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3374                           "missing closing endif directive in parsed document"
3375                           " %s", r->filename);
3376         }
3377
3378         /* cleanup our temporary memory */
3379         apr_brigade_destroy(ctx->tmp_bb);
3380         apr_pool_destroy(ctx->dpool);
3381
3382         /* don't forget to finally insert the EOS bucket */
3383         APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3384     }
3385
3386     /* if something's left over, pass it along */
3387     if (!APR_BRIGADE_EMPTY(pass_bb)) {
3388         rv = ap_pass_brigade(f->next, pass_bb);
3389     }
3390     else {
3391         rv = APR_SUCCESS;
3392     }
3393
3394     apr_brigade_destroy(pass_bb);
3395     return rv;
3396 }
3397
3398 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3399 {
3400     include_dir_config *result =
3401         (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
3402     enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
3403     *xbh = DEFAULT_XBITHACK;
3404     result->default_error_msg = DEFAULT_ERROR_MSG;
3405     result->default_time_fmt = DEFAULT_TIME_FORMAT;
3406     result->xbithack = xbh;
3407     return result;
3408 }
3409
3410 static void *create_includes_server_config(apr_pool_t*p, server_rec *server)
3411 {
3412     include_server_config *result =
3413         (include_server_config *)apr_palloc(p, sizeof(include_server_config));
3414     result->default_end_tag = ENDING_SEQUENCE;
3415     result->default_start_tag =STARTING_SEQUENCE;
3416     result->start_tag_len = sizeof(STARTING_SEQUENCE)-1;
3417     /* compile the pattern used by find_start_sequence */
3418     bndm_compile(&result->start_seq_pat, result->default_start_tag, 
3419                  result->start_tag_len); 
3420
3421     result->undefinedEcho = apr_pstrdup(p,"(none)");
3422     result->undefinedEchoLen = strlen( result->undefinedEcho);
3423     return result; 
3424 }
3425 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
3426 {
3427     include_dir_config *conf = (include_dir_config *)xbp;
3428
3429     if (!strcasecmp(arg, "off")) {
3430         *conf->xbithack = xbithack_off;
3431     }
3432     else if (!strcasecmp(arg, "on")) {
3433         *conf->xbithack = xbithack_on;
3434     }
3435     else if (!strcasecmp(arg, "full")) {
3436         *conf->xbithack = xbithack_full;
3437     }
3438     else {
3439         return "XBitHack must be set to Off, On, or Full";
3440     }
3441
3442     return NULL;
3443 }
3444
3445 static int includes_setup(ap_filter_t *f)
3446 {
3447     include_dir_config *conf = 
3448                (include_dir_config *)ap_get_module_config(f->r->per_dir_config,
3449                                                           &include_module);
3450
3451     /* When our xbithack value isn't set to full or our platform isn't
3452      * providing group-level protection bits or our group-level bits do not
3453      * have group-execite on, we will set the no_local_copy value to 1 so
3454      * that we will not send 304s.
3455      */
3456     if ((*conf->xbithack != xbithack_full)
3457         || !(f->r->finfo.valid & APR_FINFO_GPROT)
3458         || !(f->r->finfo.protection & APR_GEXECUTE)) {
3459         f->r->no_local_copy = 1;
3460     }
3461
3462     /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3463      * We don't know if we are going to be including a file or executing
3464      * a program - in either case a strong ETag header will likely be invalid.
3465      */
3466     apr_table_setn(f->r->notes, "no-etag", "");
3467
3468     return OK;
3469 }
3470
3471 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3472 {
3473     request_rec *r = f->r;
3474     ssi_ctx_t *ctx = f->ctx;
3475     request_rec *parent;
3476     include_dir_config *conf = 
3477                    (include_dir_config *)ap_get_module_config(r->per_dir_config,
3478                                                               &include_module);
3479
3480     include_server_config *sconf= ap_get_module_config(r->server->module_config,
3481                                                               &include_module);
3482
3483     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3484         ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3485                       "mod_include: Options +Includes (or IncludesNoExec) "
3486                       "wasn't set, INCLUDES filter removed");
3487         ap_remove_output_filter(f);
3488         return ap_pass_brigade(f->next, b);
3489     }
3490
3491     if (!f->ctx) {
3492         /* create context for this filter */
3493         f->ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx));
3494         ctx->ctx = apr_pcalloc(f->c->pool, sizeof(*ctx->ctx));
3495         ctx->ctx->pool = f->r->pool;
3496         apr_pool_create(&ctx->dpool, ctx->ctx->pool);
3497
3498         /* configuration data */
3499         ctx->end_seq_len = strlen(sconf->default_end_tag);
3500         ctx->r = f->r;
3501
3502         /* runtime data */
3503         ctx->tmp_bb = apr_brigade_create(ctx->ctx->pool, f->c->bucket_alloc);
3504         ctx->seen_eos = 0;
3505         ctx->state = PARSE_PRE_HEAD;
3506         ctx->ctx->flags = (FLAG_PRINTING | FLAG_COND_TRUE);
3507         if (ap_allow_options(f->r) & OPT_INCNOEXEC) {
3508             ctx->ctx->flags |= FLAG_NO_EXEC;
3509         }
3510         ctx->ctx->if_nesting_level = 0;
3511         ctx->ctx->re_string = NULL;
3512         ctx->ctx->error_str_override = NULL;
3513         ctx->ctx->time_str_override = NULL;
3514
3515         ctx->ctx->error_str = conf->default_error_msg;
3516         ctx->ctx->time_str = conf->default_time_fmt;
3517         ctx->ctx->start_seq_pat = &sconf->start_seq_pat;
3518         ctx->ctx->start_seq  = sconf->default_start_tag;
3519         ctx->ctx->start_seq_len = sconf->start_tag_len;
3520         ctx->ctx->end_seq = sconf->default_end_tag;
3521
3522         /* legacy compat stuff */
3523         ctx->ctx->state = PARSED; /* dummy */
3524         ctx->ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool,
3525                                                        f->c->bucket_alloc);
3526         ctx->ctx->status = APR_SUCCESS;
3527         ctx->ctx->head_start_index = 0;
3528         ctx->ctx->tag_start_index = 0;
3529         ctx->ctx->tail_start_index = 0;
3530     }
3531     else {
3532         ctx->ctx->bytes_parsed = 0;
3533     }
3534
3535     if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3536         /* Kludge --- for nested includes, we want to keep the subprocess
3537          * environment of the base document (for compatibility); that means
3538          * torquing our own last_modified date as well so that the
3539          * LAST_MODIFIED variable gets reset to the proper value if the
3540          * nested document resets <!--#config timefmt -->.
3541          */
3542         r->subprocess_env = r->main->subprocess_env;
3543         apr_pool_join(r->main->pool, r->pool);
3544         r->finfo.mtime = r->main->finfo.mtime;
3545     }
3546     else {
3547         /* we're not a nested include, so we create an initial
3548          * environment */
3549         ap_add_common_vars(r);
3550         ap_add_cgi_vars(r);
3551         add_include_vars(r, conf->default_time_fmt);
3552     }
3553     /* Always unset the content-length.  There is no way to know if
3554      * the content will be modified at some point by send_parsed_content.
3555      * It is very possible for us to not find any content in the first
3556      * 9k of the file, but still have to modify the content of the file.
3557      * If we are going to pass the file through send_parsed_content, then
3558      * the content-length should just be unset.
3559      */
3560     apr_table_unset(f->r->headers_out, "Content-Length");
3561
3562     /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3563      * We don't know if we are going to be including a file or executing
3564      * a program which may change the Last-Modified header or make the 
3565      * content completely dynamic.  Therefore, we can't support these
3566      * headers.
3567      * Exception: XBitHack full means we *should* set the Last-Modified field.
3568      */
3569
3570     /* Assure the platform supports Group protections */
3571     if ((*conf->xbithack == xbithack_full)
3572         && (r->finfo.valid & APR_FINFO_GPROT)
3573         && (r->finfo.protection & APR_GEXECUTE)) {
3574         ap_update_mtime(r, r->finfo.mtime);
3575         ap_set_last_modified(r);
3576     }
3577     else {
3578         apr_table_unset(f->r->headers_out, "Last-Modified");
3579     }
3580
3581     /* add QUERY stuff to env cause it ain't yet */
3582     if (r->args) {
3583         char *arg_copy = apr_pstrdup(r->pool, r->args);
3584
3585         apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3586         ap_unescape_url(arg_copy);
3587         apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3588                   ap_escape_shell_cmd(r->pool, arg_copy));
3589     }
3590
3591     return send_parsed_content(f, b);
3592 }
3593
3594 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3595 {
3596     apr_hash_set(include_hash, tag, strlen(tag), (const void *)func);
3597 }
3598
3599 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3600                                 apr_pool_t *ptemp, server_rec *s)
3601 {
3602     include_hash = apr_hash_make(p);
3603     
3604     ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3605
3606     if(ssi_pfn_register) {
3607         ssi_pfn_register("if", handle_if);
3608         ssi_pfn_register("set", handle_set);
3609         ssi_pfn_register("else", handle_else);
3610         ssi_pfn_register("elif", handle_elif);
3611         ssi_pfn_register("echo", handle_echo);
3612         ssi_pfn_register("endif", handle_endif);
3613         ssi_pfn_register("fsize", handle_fsize);
3614         ssi_pfn_register("config", handle_config);
3615         ssi_pfn_register("include", handle_include);
3616         ssi_pfn_register("flastmod", handle_flastmod);
3617         ssi_pfn_register("printenv", handle_printenv);
3618     }
3619     return OK;
3620 }
3621
3622 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
3623 {
3624     include_dir_config *conf = (include_dir_config *)mconfig;
3625     conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
3626     return NULL;
3627 }
3628
3629 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3630 {
3631     include_server_config *conf;
3632     conf= ap_get_module_config(cmd->server->module_config , &include_module);
3633     conf->default_start_tag = apr_pstrdup(cmd->pool, msg);
3634     conf->start_tag_len = strlen(conf->default_start_tag );
3635     bndm_compile(&conf->start_seq_pat, conf->default_start_tag, 
3636                  conf->start_tag_len); 
3637
3638     return NULL;
3639 }
3640 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig, const char *msg)
3641 {
3642     include_server_config *conf;
3643     conf = ap_get_module_config(cmd->server->module_config, &include_module);
3644     conf->undefinedEcho = apr_pstrdup(cmd->pool, msg);
3645     conf->undefinedEchoLen = strlen(msg);
3646
3647     return NULL;
3648 }
3649
3650
3651 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig, const char *msg)
3652 {
3653     include_server_config *conf;
3654     conf= ap_get_module_config(cmd->server->module_config , &include_module);
3655     conf->default_end_tag = apr_pstrdup(cmd->pool, msg);
3656
3657     return NULL;
3658 }
3659
3660 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
3661 {
3662     include_dir_config *conf = (include_dir_config *)mconfig;
3663     conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
3664     return NULL;
3665 }
3666
3667 /*
3668  * Module definition and configuration data structs...
3669  */
3670 static const command_rec includes_cmds[] =
3671 {
3672     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
3673                   "Off, On, or Full"),
3674     AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 
3675                   "a string"),
3676     AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3677                   "a strftime(3) formatted string"),
3678     AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3679                   "SSI Start String Tag"),
3680     AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3681                   "SSI End String Tag"),
3682     AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, RSRC_CONF,
3683                   "SSI Start String Tag"),
3684
3685     {NULL}
3686 };
3687
3688 static int include_fixup(request_rec *r)
3689 {
3690     include_dir_config *conf;
3691  
3692     conf = (include_dir_config *) ap_get_module_config(r->per_dir_config,
3693                                                 &include_module);
3694  
3695     if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) 
3696     {
3697         if (!r->content_type || !*r->content_type) {
3698             ap_set_content_type(r, "text/html");
3699         }
3700         r->handler = "default-handler";
3701     }
3702     else 
3703 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3704     /* These OS's don't support xbithack. This is being worked on. */
3705     {
3706         return DECLINED;
3707     }
3708 #else
3709     {
3710         if (*conf->xbithack == xbithack_off) {
3711             return DECLINED;
3712         }
3713
3714         if (!(r->finfo.protection & APR_UEXECUTE)) {
3715             return DECLINED;
3716         }
3717
3718         if (!r->content_type || strcmp(r->content_type, "text/html")) {
3719             return DECLINED;
3720         }
3721     }
3722 #endif
3723
3724     /* We always return declined, because the default handler actually
3725      * serves the file.  All we have to do is add the filter.
3726      */
3727     ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3728     return DECLINED;
3729 }
3730
3731 static void register_hooks(apr_pool_t *p)
3732 {
3733     APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3734     APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3735     APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3736     ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3737     ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3738     ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3739                               AP_FTYPE_RESOURCE);
3740 }
3741
3742 module AP_MODULE_DECLARE_DATA include_module =
3743 {
3744     STANDARD20_MODULE_STUFF,
3745     create_includes_dir_config,   /* dir config creater */
3746     NULL,                         /* dir merger --- default is to override */
3747     create_includes_server_config,/* server config */
3748     NULL,                         /* merge server config */
3749     includes_cmds,                /* command apr_table_t */
3750     register_hooks                /* register hooks */
3751 };