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