]> granicus.if.org Git - apache/blob - modules/filters/mod_include.c
Fixed an error in ap_ssi_get_tag_and_value(). It was not placing the
[apache] / modules / filters / mod_include.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2001 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 #define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE
100
101 /* ------------------------ Environment function -------------------------- */
102
103 /* XXX: could use ap_table_overlap here */
104 static void add_include_vars(request_rec *r, char *timefmt)
105 {
106     char *pwname;
107     apr_table_t *e = r->subprocess_env;
108     char *t;
109     apr_time_t date = r->request_time;
110
111     apr_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0));
112     apr_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1));
113     apr_table_setn(e, "LAST_MODIFIED",
114               ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0));
115     apr_table_setn(e, "DOCUMENT_URI", r->uri);
116     apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
117     if (apr_get_username(&pwname, r->finfo.user, r->pool) == APR_SUCCESS) {
118         apr_table_setn(e, "USER_NAME", pwname);
119     }
120     else {
121         apr_table_setn(e, "USER_NAME", "<unknown>");
122     }
123     if ((t = strrchr(r->filename, '/'))) {
124         apr_table_setn(e, "DOCUMENT_NAME", ++t);
125     }
126     else {
127         apr_table_setn(e, "DOCUMENT_NAME", r->uri);
128     }
129     if (r->args) {
130         char *arg_copy = apr_pstrdup(r->pool, r->args);
131
132         ap_unescape_url(arg_copy);
133         apr_table_setn(e, "QUERY_STRING_UNESCAPED",
134                   ap_escape_shell_cmd(r->pool, arg_copy));
135     }
136 }
137
138
139
140 /* --------------------------- Parser functions --------------------------- */
141
142 /* This function returns either a pointer to the split bucket containing the
143  * first byte of the BEGINNING_SEQUENCE (after finding a complete match) or it
144  * returns NULL if no match found.
145  */
146 static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx,
147                                       apr_bucket_brigade *bb, int *do_cleanup)
148 {
149     apr_size_t len;
150     const char *c;
151     const char *buf;
152     const char *str = STARTING_SEQUENCE;
153     apr_bucket *tmp_bkt;
154     apr_size_t  start_index;
155
156     *do_cleanup = 0;
157
158     do {
159         if (APR_BUCKET_IS_EOS(dptr)) {
160             break;
161         }
162         apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
163         /* XXX handle retcodes */
164         if (len == 0) { /* end of pipe? */
165             break;
166         }
167         c = buf;
168         while (c < buf + len) {
169             if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
170                 apr_bucket *start_bucket;
171
172                 if (ctx->head_start_index > 0) {
173                     start_index  = ctx->head_start_index;
174                     start_bucket = ctx->head_start_bucket;
175                 }
176                 else {
177                     start_index  = (c - buf);
178                     start_bucket = dptr;
179                 }
180                 apr_bucket_split(start_bucket, start_index);
181                 tmp_bkt = APR_BUCKET_NEXT(start_bucket);
182                 if (ctx->head_start_index > 0) {
183                     ctx->head_start_index  = 0;
184                     ctx->head_start_bucket = tmp_bkt;
185                 }
186
187                 return tmp_bkt;
188             }
189
190             if (*c == str[ctx->parse_pos]) {
191                 if (ctx->state == PRE_HEAD) {
192                     ctx->state             = PARSE_HEAD;
193                     ctx->head_start_bucket = dptr;
194                     ctx->head_start_index  = c - buf;
195                 }
196                 ctx->parse_pos++;
197             }
198             else {
199                 if (str[ctx->parse_pos] == '\0') {
200                     /* We want to split the bucket at the '<'. */
201                     ctx->bytes_parsed++;
202                     ctx->state            = PARSE_DIRECTIVE;
203                     ctx->tag_length       = 0;
204                     ctx->parse_pos        = 0;
205                     ctx->tag_start_bucket = dptr;
206                     ctx->tag_start_index  = c - buf;
207                     if (ctx->head_start_index > 0) {
208                         start_index = (c - buf) - ctx->head_start_index;
209                         apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
210                         tmp_bkt = APR_BUCKET_NEXT(ctx->head_start_bucket);
211                         if (dptr == ctx->head_start_bucket) {
212                             ctx->tag_start_bucket = tmp_bkt;
213                             ctx->tag_start_index  = start_index;
214                         }
215                         ctx->head_start_bucket = tmp_bkt;
216                         ctx->head_start_index  = 0;
217                     }
218                     return ctx->head_start_bucket;
219                 }
220                 else if (ctx->parse_pos != 0) {
221                     /* The reason for this, is that we need to make sure 
222                      * that we catch cases like <<!--#.  This makes the 
223                      * second check after the original check fails.
224                      * If parse_pos was already 0 then we already checked this.
225                      */
226                     *do_cleanup = 1;
227                     if (*c == str[0]) {
228                         ctx->parse_pos         = 1;
229                         ctx->state             = PARSE_HEAD;
230                         ctx->head_start_bucket = dptr;
231                         ctx->head_start_index  = c - buf;
232                     }
233                     else {
234                         ctx->parse_pos         = 0;
235                         ctx->state             = PRE_HEAD;
236                         ctx->head_start_bucket = NULL;
237                         ctx->head_start_index  = 0;
238                     }
239                 }
240             }
241             c++;
242             ctx->bytes_parsed++;
243         }
244         dptr = APR_BUCKET_NEXT(dptr);
245     } while (dptr != APR_BRIGADE_SENTINEL(bb));
246     return NULL;
247 }
248
249 static apr_bucket *find_end_sequence(apr_bucket *dptr, include_ctx_t *ctx, apr_bucket_brigade *bb)
250 {
251     apr_size_t len;
252     const char *c;
253     const char *buf;
254     const char *str = ENDING_SEQUENCE;
255
256     do {
257         if (APR_BUCKET_IS_EOS(dptr)) {
258             break;
259         }
260         apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ);
261         /* XXX handle retcodes */
262         if (len == 0) { /* end of pipe? */
263             break;
264         }
265         if (dptr == ctx->tag_start_bucket) {
266             c = buf + ctx->tag_start_index;
267         }
268         else {
269             c = buf;
270         }
271         while (c < buf + len) {
272             if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
273                 return dptr;
274             }
275
276             if (*c == str[ctx->parse_pos]) {
277                 if (ctx->state != PARSE_TAIL) {
278                     ctx->state             = PARSE_TAIL;
279                     ctx->tail_start_bucket = dptr;
280                     ctx->tail_start_index  = c - buf;
281                 }
282                 ctx->parse_pos++;
283             }
284             else {
285                 if (ctx->state == PARSE_DIRECTIVE) {
286                     if (ctx->tag_length == 0) {
287                         if (!apr_isspace(*c)) {
288                             ctx->tag_start_bucket = dptr;
289                             ctx->tag_start_index  = c - buf;
290                             ctx->tag_length       = 1;
291                             ctx->directive_length = 1;
292                         }
293                     }
294                     else {
295                         if (!apr_isspace(*c)) {
296                             ctx->directive_length++;
297                         }
298                         else {
299                             ctx->state = PARSE_TAG;
300                         }
301                         ctx->tag_length++;
302                     }
303                 }
304                 else if (ctx->state == PARSE_TAG) {
305                     ctx->tag_length++;
306                 }
307                 else {
308                     if (str[ctx->parse_pos] == '\0') {
309                         apr_bucket *tmp_buck = dptr;
310
311                         /* We want to split the bucket at the '>'. The
312                          * end of the END_SEQUENCE is in the current bucket.
313                          * The beginning might be in a previous bucket.
314                          */
315                         ctx->bytes_parsed++;
316                         ctx->state = PARSED;
317                         if ((c - buf) > 0) {
318                             apr_bucket_split(dptr, c - buf);
319                             tmp_buck = APR_BUCKET_NEXT(dptr);
320                         }
321                         return (tmp_buck);
322                     }
323                     else if (ctx->parse_pos != 0) {
324                         /* The reason for this, is that we need to make sure 
325                          * that we catch cases like --->.  This makes the 
326                          * second check after the original check fails.
327                          * If parse_pos was already 0 then we already checked this.
328                          */
329                          ctx->tag_length += ctx->parse_pos;
330
331                          if (*c == str[0]) {
332                              ctx->state             = PARSE_TAIL;
333                              ctx->tail_start_bucket = dptr;
334                              ctx->tail_start_index  = c - buf;
335                              ctx->tag_length       += ctx->parse_pos;
336                              ctx->parse_pos         = 1;
337                          }
338                          else {
339                              if (ctx->tag_length > ctx->directive_length) {
340                                  ctx->state = PARSE_TAG;
341                              }
342                              else {
343                                  ctx->state = PARSE_DIRECTIVE;
344                                  ctx->directive_length += ctx->parse_pos;
345                              }
346                              ctx->tail_start_bucket = NULL;
347                              ctx->tail_start_index  = 0;
348                              ctx->tag_length       += ctx->parse_pos;
349                              ctx->parse_pos         = 0;
350                          }
351                     }
352                 }
353             }
354             c++;
355             ctx->bytes_parsed++;
356         }
357         dptr = APR_BUCKET_NEXT(dptr);
358     } while (dptr != APR_BRIGADE_SENTINEL(bb));
359     return NULL;
360 }
361
362 /* This function culls through the buckets that have been set aside in the 
363  * ssi_tag_brigade and copies just the directive part of the SSI tag (none
364  * of the start and end delimiter bytes are copied).
365  */
366 static apr_status_t get_combined_directive (include_ctx_t *ctx,
367                                             request_rec *r,
368                                             apr_bucket_brigade *bb,
369                                             char *tmp_buf, 
370                                             apr_size_t tmp_buf_size)
371 {
372     int        done = 0;
373     apr_bucket *dptr;
374     const char *tmp_from;
375     apr_size_t tmp_from_len;
376
377     /* If the tag length is longer than the tmp buffer, allocate space. */
378     if (ctx->tag_length > tmp_buf_size-1) {
379         if ((ctx->combined_tag = apr_pcalloc(r->pool, ctx->tag_length + 1)) == NULL) {
380             return (APR_ENOMEM);
381         }
382     }     /* Else, just use the temp buffer. */
383     else {
384         ctx->combined_tag = tmp_buf;
385     }
386
387     /* Prime the pump. Start at the beginning of the tag... */
388     dptr = ctx->tag_start_bucket;
389     apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);  /* Read the bucket... */
390
391     /* Adjust the pointer to start at the tag within the bucket... */
392     if (dptr == ctx->tail_start_bucket) {
393         tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
394     }
395     tmp_from          = &tmp_from[ctx->tag_start_index];
396     tmp_from_len     -= ctx->tag_start_index;
397     ctx->curr_tag_pos = ctx->combined_tag;
398
399     /* Loop through the buckets from the tag_start_bucket until before
400      * the tail_start_bucket copying the contents into the buffer.
401      */
402     do {
403         memcpy (ctx->curr_tag_pos, tmp_from, tmp_from_len);
404         ctx->curr_tag_pos += tmp_from_len;
405
406         if (dptr == ctx->tail_start_bucket) {
407             done = 1;
408         }
409         else {
410             dptr = APR_BUCKET_NEXT (dptr);
411             apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0);
412             /* Adjust the count to stop at the beginning of the tail. */
413             if (dptr == ctx->tail_start_bucket) {
414                 tmp_from_len -= (tmp_from_len - ctx->tail_start_index);
415             }
416         }
417     } while ((!done) &&
418              (ctx->curr_tag_pos < ctx->combined_tag + ctx->tag_length));
419
420     ctx->combined_tag[ctx->tag_length] = '\0';
421     ctx->curr_tag_pos = ctx->combined_tag;
422
423     return (APR_SUCCESS);
424 }
425
426 /*
427  * decodes a string containing html entities or numeric character references.
428  * 's' is overwritten with the decoded string.
429  * If 's' is syntatically incorrect, then the followed fixups will be made:
430  *   unknown entities will be left undecoded;
431  *   references to unused numeric characters will be deleted.
432  *   In particular, &#00; will not be decoded, but will be deleted.
433  *
434  * drtr
435  */
436
437 /* maximum length of any ISO-LATIN-1 HTML entity name. */
438 #define MAXENTLEN (6)
439
440 /* The following is a shrinking transformation, therefore safe. */
441
442 static void decodehtml(char *s)
443 {
444     int val, i, j;
445     char *p = s;
446     const char *ents;
447     static const char * const entlist[MAXENTLEN + 1] =
448     {
449         NULL,                   /* 0 */
450         NULL,                   /* 1 */
451         "lt\074gt\076",         /* 2 */
452         "amp\046ETH\320eth\360",        /* 3 */
453         "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
454 iuml\357ouml\366uuml\374yuml\377",      /* 4 */
455         "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
456 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
457 ucirc\373thorn\376",            /* 5 */
458         "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
459 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
460 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
461 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
462 otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
463     };
464
465     for (; *s != '\0'; s++, p++) {
466         if (*s != '&') {
467             *p = *s;
468             continue;
469         }
470         /* find end of entity */
471         for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
472             continue;
473         }
474
475         if (s[i] == '\0') {     /* treat as normal data */
476             *p = *s;
477             continue;
478         }
479
480         /* is it numeric ? */
481         if (s[1] == '#') {
482             for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
483                 val = val * 10 + s[j] - '0';
484             }
485             s += i;
486             if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
487                 (val >= 127 && val <= 160) || val >= 256) {
488                 p--;            /* no data to output */
489             }
490             else {
491                 *p = RAW_ASCII_CHAR(val);
492             }
493         }
494         else {
495             j = i - 1;
496             if (j > MAXENTLEN || entlist[j] == NULL) {
497                 /* wrong length */
498                 *p = '&';
499                 continue;       /* skip it */
500             }
501             for (ents = entlist[j]; *ents != '\0'; ents += i) {
502                 if (strncmp(s + 1, ents, j) == 0) {
503                     break;
504                 }
505             }
506
507             if (*ents == '\0') {
508                 *p = '&';       /* unknown */
509             }
510             else {
511                 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
512                 s += i;
513             }
514         }
515     }
516
517     *p = '\0';
518 }
519
520 /*
521  * Extract the next tag name and value.
522  * If there are no more tags, set the tag name to NULL.
523  * The tag value is html decoded if dodecode is non-zero.
524  * The tag value may be NULL if there is no tag value..
525  *    format:
526  *        [WS]<Tag>[WS]=[WS]['|"]<Value>['|"|WS]
527  */
528
529 #define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++
530
531 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
532                               char **tag_val, int dodecode)
533 {
534     char *c = ctx->curr_tag_pos;
535     int   shift_val = 0; 
536     char  term = '\0';
537
538     *tag_val = NULL;
539     SKIP_TAG_WHITESPACE(c);
540     *tag = c;             /* First non-whitespace character (could be NULL). */
541
542     while ((*c != '\0') && (*c != '=') && (!apr_isspace(*c))) {
543         *c = apr_tolower(*c);    /* find end of tag, lowercasing as we go... */
544         c++;
545     }
546
547     if ((*c == '\0') || (**tag == '=')) {
548         if ((**tag == '\0') || (**tag == '=')) {
549             *tag = NULL;
550         }
551         ctx->curr_tag_pos = c;
552         return;      /* We have found the end of the buffer. */
553     }                /* We might have a tag, but definitely no value. */
554
555     if (*c == '=') {
556         *c++ = '\0';     /* Overwrite the '=' with a terminating byte after tag. */
557     }
558     else {               /* Try skipping WS to find the '='. */
559         *c++ = '\0';     /* Terminate the tag... */
560         SKIP_TAG_WHITESPACE(c);
561         
562         if (*c != '=') {     /* There needs to be an equal sign if there's a value. */
563             ctx->curr_tag_pos = c;
564             return;       /* There apparently was no value. */
565         }
566         else {
567             c++; /* Skip the equals sign. */
568         }
569     }
570
571     SKIP_TAG_WHITESPACE(c);
572     if (*c == '"' || *c == '\'') {    /* Allow quoted values for space inclusion. */
573         term = *c++;     /* NOTE: This does not pass the quotes on return. */
574     }
575     
576     *tag_val = c;
577     while ((*c != '\0') &&
578            (((term != '\0') && (*c != term)) ||
579             ((term == '\0') && (!apr_isspace(*c))))) {
580         if (*c == '\\') {  /* Accept \" and \' as valid char in string. */
581             c++;
582             if (*c == term) { /* Overwrite the "\" during the embedded  */
583                 shift_val++;  /* escape sequence of '\"' or "\'". Shift */
584             }                 /* bytes from here to next delimiter.     */
585             if (shift_val > 0) {
586                 *(c-shift_val) = *c;
587             }
588         }
589
590         c++;
591         if (shift_val > 0) {
592             *(c-shift_val) = *c;
593         }
594     }
595     
596     *(c-shift_val) = '\0'; /* Overwrites delimiter (term or WS) with NULL. */
597     ctx->curr_tag_pos = ++c;
598     if (dodecode) {
599         decodehtml(*tag_val);
600     }
601
602     return;
603 }
604
605
606 /*
607  * Do variable substitution on strings
608  */
609 static void ap_ssi_parse_string(request_rec *r, const char *in, char *out,
610                          size_t length, int leave_name)
611 {
612     char ch;
613     char *next = out;
614     char *end_out;
615
616     /* leave room for nul terminator */
617     end_out = out + length - 1;
618
619     while ((ch = *in++) != '\0') {
620         switch (ch) {
621         case '\\':
622             if (next == end_out) {
623                 /* truncated */
624                 *next = '\0';
625                 return;
626             }
627             if (*in == '$') {
628                 *next++ = *in++;
629             }
630             else {
631                 *next++ = ch;
632             }
633             break;
634         case '$':
635             {
636                 const char *start_of_var_name;
637                 char *end_of_var_name;  /* end of var name + 1 */
638                 const char *expansion, *temp_end, *val;
639                 char        tmp_store;
640                 size_t l;
641
642                 /* guess that the expansion won't happen */
643                 expansion = in - 1;
644                 if (*in == '{') {
645                     ++in;
646                     start_of_var_name = in;
647                     in = ap_strchr_c(in, '}');
648                     if (in == NULL) {
649                         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
650                                     0, r, "Missing '}' on variable \"%s\"",
651                                     expansion);
652                         *next = '\0';
653                         return;
654                     }
655                     temp_end = in;
656                     end_of_var_name = (char *)temp_end;
657                     ++in;
658                 }
659                 else {
660                     start_of_var_name = in;
661                     while (apr_isalnum(*in) || *in == '_') {
662                         ++in;
663                     }
664                     temp_end = in;
665                     end_of_var_name = (char *)temp_end;
666                 }
667                 /* what a pain, too bad there's no table_getn where you can
668                  * pass a non-nul terminated string */
669                 l = end_of_var_name - start_of_var_name;
670                 if (l != 0) {
671                     tmp_store        = *end_of_var_name;
672                     *end_of_var_name = '\0';
673                     val = apr_table_get(r->subprocess_env, start_of_var_name);
674                     *end_of_var_name = tmp_store;
675
676                     if (val) {
677                         expansion = val;
678                         l = strlen(expansion);
679                     }
680                     else if (leave_name) {
681                         l = in - expansion;
682                     }
683                     else {
684                         break;  /* no expansion to be done */
685                     }
686                 }
687                 else {
688                     /* zero-length variable name causes just the $ to be copied */
689                     l = 1;
690                 }
691                 l = ((int)l > end_out - next) ? (end_out - next) : l;
692                 memcpy(next, expansion, l);
693                 next += l;
694                 break;
695             }
696         default:
697             if (next == end_out) {
698                 /* truncated */
699                 *next = '\0';
700                 return;
701             }
702             *next++ = ch;
703             break;
704         }
705     }
706     *next = '\0';
707     return;
708 }
709
710 /* --------------------------- Action handlers ---------------------------- */
711
712 /* ensure that path is relative, and does not contain ".." elements
713  * ensentially ensure that it does not match the regex:
714  * (^/|(^|/)\.\.(/|$))
715  * XXX: Needs to become apr_is_path_relative() test
716  */
717 static int is_only_below(const char *path)
718 {
719 #ifdef HAVE_DRIVE_LETTERS
720     if (path[1] == ':') 
721         return 0;
722 #endif
723 #ifdef NETWARE
724     if (strchr(path, ':')
725         return 0;
726 #endif
727     if (path[0] == '/') {
728         return 0;
729     }
730     while (*path) {
731         int dots = 0;
732         while (path[dots] == '.')
733             ++dots;
734 #if defined(WIN32) 
735         /* If the name is canonical this is redundant
736          * but in security, redundancy is worthwhile.
737          * Does OS2 belong here (accepts ... for ..)?
738          */
739         if (dots > 1 && (!path[dots] || path[dots] == '/'))
740             return 0;
741 #else
742         if (dots == 2 && (!path[dots] || path[dots] == '/'))
743             return 0;
744 #endif
745         path += dots;
746         while (*path && *(path++) != '/')
747             ++path;
748     }
749     return 1;
750 }
751
752 static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
753                           ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
754 {
755     char *tag     = NULL;
756     char *tag_val = NULL;
757     apr_bucket  *tmp_buck;
758     char parsed_string[MAX_STRING_LEN];
759
760     *inserted_head = NULL;
761     if (ctx->flags & FLAG_PRINTING) {
762         while (1) {
763             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
764             if (tag_val == NULL) {
765                 if (tag == NULL) {
766                     return (0);
767                 }
768                 else {
769                     return (1);
770                 }
771             }
772             if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
773                 request_rec *rr = NULL;
774                 char *error_fmt = NULL;
775
776                 ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
777                 if (tag[0] == 'f') {
778                     /* be safe; only files in this directory or below allowed */
779                 if (!is_only_below(parsed_string)) {
780                         error_fmt = "unable to include file \"%s\" "
781                                     "in parsed file %s";
782                     }
783                     else {
784                         rr = ap_sub_req_lookup_file(parsed_string, r, f->next);
785                     }
786                 }
787                 else {
788                     rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
789                 }
790
791                 if (!error_fmt && rr->status != HTTP_OK) {
792                     error_fmt = "unable to include \"%s\" in parsed file %s";
793                 }
794
795                 if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) && rr->content_type
796                     && (strncmp(rr->content_type, "text/", 5))) {
797                     error_fmt = "unable to include potential exec \"%s\" "
798                         "in parsed file %s";
799                 }
800                 if (error_fmt == NULL) {
801                 /* try to avoid recursive includes.  We do this by walking
802                  * up the r->main list of subrequests, and at each level
803                  * walking back through any internal redirects.  At each
804                  * step, we compare the filenames and the URIs.  
805                  *
806                  * The filename comparison catches a recursive include
807                  * with an ever-changing URL, eg.
808                  * <!--#include virtual=
809                  *      "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x"-->
810                  * which, although they would eventually be caught because
811                  * we have a limit on the length of files, etc., can 
812                  * recurse for a while.
813                  *
814                  * The URI comparison catches the case where the filename
815                  * is changed while processing the request, so the 
816                  * current name is never the same as any previous one.
817                  * This can happen with "DocumentRoot /foo" when you
818                  * request "/" on the server and it includes "/".
819                  * This only applies to modules such as mod_dir that 
820                  * (somewhat improperly) mess with r->filename outside 
821                  * of a filename translation phase.
822                  */
823                 int founddupe = 0;
824                     request_rec *p;
825                     for (p = r; p != NULL && !founddupe; p = p->main) {
826                     request_rec *q;
827                     for (q = p; q != NULL; q = q->prev) {
828                         if ( (strcmp(q->filename, rr->filename) == 0) ||
829                              (strcmp(q->uri, rr->uri) == 0) ){
830                             founddupe = 1;
831                             break;
832                         }
833                     }
834                 }
835
836                     if (p != NULL) {
837                         error_fmt = "Recursive include of \"%s\" "
838                             "in parsed file %s";
839                     }
840                 }
841
842             /* See the Kludge in send_parsed_file for why */
843                 /* Basically, it puts a bread crumb in here, then looks */
844                 /*   for the crumb later to see if its been here.       */
845             if (rr) 
846                 ap_set_module_config(rr->request_config, &include_module, r);
847
848                 if (!error_fmt) {
849                     SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
850                     
851                     if (ap_run_sub_req(rr)) {
852                         error_fmt = "unable to include \"%s\" in parsed file %s";
853                     }
854                 }
855                 if (error_fmt) {
856                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
857                             0, r, error_fmt, tag_val, r->filename);
858                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
859                 }
860
861             /* destroy the sub request if it's not a nested include (crumb) */
862                 if (rr != NULL
863                 && ap_get_module_config(rr->request_config, &include_module)
864                     != NESTED_INCLUDE_MAGIC) {
865                 ap_destroy_sub_req(rr);
866                 }
867             }
868             else {
869                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
870                             "unknown parameter \"%s\" to tag include in %s",
871                             tag, r->filename);
872                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
873             }
874         }
875     }
876     return 0;
877 }
878
879
880 static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
881                        ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
882 {
883     char       *tag       = NULL;
884     char       *tag_val   = NULL;
885     const char *echo_text = NULL;
886     apr_bucket  *tmp_buck;
887     apr_size_t e_len, e_wrt;
888     enum {E_NONE, E_URL, E_ENTITY} encode;
889
890     encode = E_ENTITY;
891
892     *inserted_head = NULL;
893     if (ctx->flags & FLAG_PRINTING) {
894         while (1) {
895             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
896             if (tag_val == NULL) {
897                 if (tag != NULL) {
898                     return 1;
899                 }
900                 else {
901                     return 0;
902                 }
903             }
904             if (!strcmp(tag, "var")) {
905                 const char *val = apr_table_get(r->subprocess_env, tag_val);
906
907                 if (val) {
908                     switch(encode) {
909                     case E_NONE:   echo_text = val;                          break;
910                     case E_URL:    echo_text = ap_escape_uri(r->pool, val);  break;
911                     case E_ENTITY: echo_text = ap_escape_html(r->pool, val); break;
912                 }
913
914                     e_len = strlen(echo_text);
915                     tmp_buck = apr_bucket_heap_create(echo_text, e_len, 1, &e_wrt);
916                 }
917                 else {
918                     tmp_buck = apr_bucket_immortal_create("(none)", sizeof("(none)")-1);
919                 }
920                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
921                 if (*inserted_head == NULL) {
922                     *inserted_head = tmp_buck;
923                 }
924             }
925             else if (!strcmp(tag, "encoding")) {
926                 if (!strcasecmp(tag_val, "none")) encode = E_NONE;
927                 else if (!strcasecmp(tag_val, "url")) encode = E_URL;
928                 else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
929                 else {
930                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
931                                   "unknown value \"%s\" to parameter \"encoding\" of "
932                                   "tag echo in %s", tag_val, r->filename);
933                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
934                 }
935             }
936             else {
937                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
938                             "unknown parameter \"%s\" in tag echo of %s",
939                             tag, r->filename);
940                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
941             }
942
943         }
944     }
945     return 0;
946 }
947
948 /* error and tf must point to a string with room for at 
949  * least MAX_STRING_LEN characters 
950  */
951 static int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
952                          ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
953 {
954     char *tag     = NULL;
955     char *tag_val = NULL;
956     char parsed_string[MAX_STRING_LEN];
957     apr_table_t *env = r->subprocess_env;
958
959     *inserted_head = NULL;
960     if (ctx->flags & FLAG_PRINTING) {
961         while (1) {
962             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
963             if (tag_val == NULL) {
964                 if (tag == NULL) {
965                     return 0;  /* Reached the end of the string. */
966                 }
967                 else {
968                     return 1;  /* tags must have values. */
969                 }
970             }
971             if (!strcmp(tag, "errmsg")) {
972                 ap_ssi_parse_string(r, tag_val, ctx->error_str, MAX_STRING_LEN, 0);
973                 ctx->error_length = strlen(ctx->error_str);
974             }
975             else if (!strcmp(tag, "timefmt")) {
976                 apr_time_t date = r->request_time;
977
978                 ap_ssi_parse_string(r, tag_val, ctx->time_str, MAX_STRING_LEN, 0);
979                 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, ctx->time_str, 0));
980                 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, ctx->time_str, 1));
981                 apr_table_setn(env, "LAST_MODIFIED",
982                                ap_ht_time(r->pool, r->finfo.mtime, ctx->time_str, 0));
983             }
984             else if (!strcmp(tag, "sizefmt")) {
985                 ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
986                 decodehtml(parsed_string);
987                 if (!strcmp(parsed_string, "bytes")) {
988                     ctx->flags |= FLAG_SIZE_IN_BYTES;
989                 }
990                 else if (!strcmp(parsed_string, "abbrev")) {
991                     ctx->flags &= FLAG_SIZE_ABBREV;
992                 }
993             }
994             else {
995                 apr_bucket  *tmp_buck;
996
997                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
998                             "unknown parameter \"%s\" to tag config in %s",
999                             tag, r->filename);
1000                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1001             }
1002         }
1003     }
1004     return 0;
1005 }
1006
1007
1008 static int find_file(request_rec *r, const char *directive, const char *tag,
1009                      char *tag_val, apr_finfo_t *finfo)
1010 {
1011     char *to_send = tag_val;
1012     request_rec *rr = NULL;
1013     int ret=0;
1014     char *error_fmt = NULL;
1015     apr_status_t rv = APR_SUCCESS;
1016
1017     if (!strcmp(tag, "file")) {
1018         /* be safe; only files in this directory or below allowed */
1019         if (!is_only_below(tag_val)) {
1020             error_fmt = "unable to access file \"%s\" "
1021                         "in parsed file %s";
1022         }
1023         else {
1024             ap_getparents(tag_val);    /* get rid of any nasties */
1025
1026             /* note: it is okay to pass NULL for the "next filter" since
1027                we never attempt to "run" this sub request. */
1028             rr = ap_sub_req_lookup_file(tag_val, r, NULL);
1029
1030             if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1031                 to_send = rr->filename;
1032                 if ((rv = apr_stat(finfo, to_send, APR_FINFO_GPROT 
1033                                 | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1034                                                      && rv != APR_INCOMPLETE) {
1035                     error_fmt = "unable to get information about \"%s\" "
1036                         "in parsed file %s";
1037                 }
1038             }
1039             else {
1040                 error_fmt = "unable to lookup information about \"%s\" "
1041                             "in parsed file %s";
1042             }
1043         }
1044
1045         if (error_fmt) {
1046             ret = -1;
1047             ap_log_rerror(APLOG_MARK, APLOG_ERR | (rv ? 0 : APLOG_NOERRNO),
1048                           rv, r, error_fmt, to_send, r->filename);
1049         }
1050
1051         if (rr) ap_destroy_sub_req(rr);
1052         
1053         return ret;
1054     }
1055     else if (!strcmp(tag, "virtual")) {
1056         /* note: it is okay to pass NULL for the "next filter" since
1057            we never attempt to "run" this sub request. */
1058         rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1059
1060         if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1061             memcpy((char *) finfo, (const char *) &rr->finfo,
1062                    sizeof(rr->finfo));
1063             ap_destroy_sub_req(rr);
1064             return 0;
1065         }
1066         else {
1067             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1068                         "unable to get information about \"%s\" "
1069                         "in parsed file %s",
1070                         tag_val, r->filename);
1071             ap_destroy_sub_req(rr);
1072             return -1;
1073         }
1074     }
1075     else {
1076         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1077                     "unknown parameter \"%s\" to tag %s in %s",
1078                     tag, directive, r->filename);
1079         return -1;
1080     }
1081 }
1082
1083 static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
1084                         ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
1085 {
1086     char *tag     = NULL;
1087     char *tag_val = NULL;
1088     apr_finfo_t  finfo;
1089     apr_size_t  s_len, s_wrt;
1090     apr_bucket   *tmp_buck;
1091     char parsed_string[MAX_STRING_LEN];
1092
1093     *inserted_head = NULL;
1094     if (ctx->flags & FLAG_PRINTING) {
1095         while (1) {
1096             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1097             if (tag_val == NULL) {
1098                 if (tag == NULL) {
1099                     return 0;
1100                 }
1101                 else {
1102                     return 1;
1103                 }
1104             }
1105             else {
1106                 ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1107                 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
1108                     char buff[50];
1109
1110                     if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) {
1111                         apr_strfsize(finfo.size, buff);
1112                         s_len = strlen (buff);
1113                     }
1114                     else {
1115                         int l, x, pos = 0;
1116                         char tmp_buff[50];
1117
1118                         apr_snprintf(tmp_buff, sizeof(tmp_buff), "%" APR_OFF_T_FMT, finfo.size);
1119                         l = strlen(tmp_buff);    /* grrr */
1120                         for (x = 0; x < l; x++) {
1121                             if (x && (!((l - x) % 3))) {
1122                                 buff[pos++] = ',';
1123                             }
1124                             buff[pos++] = tmp_buff[x];
1125                         }
1126                         buff[pos] = '\0';
1127                         s_len = pos;
1128                     }
1129
1130                     tmp_buck = apr_bucket_heap_create(buff, s_len, 1, &s_wrt);
1131                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1132                     if (*inserted_head == NULL) {
1133                         *inserted_head = tmp_buck;
1134                     }
1135                 }
1136                 else {
1137                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1138                 }
1139             }
1140         }
1141     }
1142     return 0;
1143 }
1144
1145 static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
1146                            ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
1147 {
1148     char *tag     = NULL;
1149     char *tag_val = NULL;
1150     apr_finfo_t  finfo;
1151     apr_size_t  t_len, t_wrt;
1152     apr_bucket   *tmp_buck;
1153     char parsed_string[MAX_STRING_LEN];
1154
1155     *inserted_head = NULL;
1156     if (ctx->flags & FLAG_PRINTING) {
1157         while (1) {
1158             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
1159             if (tag_val == NULL) {
1160                 if (tag == NULL) {
1161                     return 0;
1162                 }
1163                 else {
1164                     return 1;
1165                 }
1166             }
1167             else {
1168                 ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1169                 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
1170                     char *t_val;
1171
1172                     t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0);
1173                     t_len = strlen(t_val);
1174
1175                     tmp_buck = apr_bucket_heap_create(t_val, t_len, 1, &t_wrt);
1176                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
1177                     if (*inserted_head == NULL) {
1178                         *inserted_head = tmp_buck;
1179                     }
1180                 }
1181                 else {
1182                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
1183                 }
1184             }
1185         }
1186     }
1187     return 0;
1188 }
1189
1190 static int re_check(request_rec *r, char *string, char *rexp)
1191 {
1192     regex_t *compiled;
1193     int regex_error;
1194
1195     compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
1196     if (compiled == NULL) {
1197         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1198                     "unable to compile pattern \"%s\"", rexp);
1199         return -1;
1200     }
1201     regex_error = ap_regexec(compiled, string, 0, (regmatch_t *) NULL, 0);
1202     ap_pregfree(r->pool, compiled);
1203     return (!regex_error);
1204 }
1205
1206 enum token_type {
1207     token_string,
1208     token_and, token_or, token_not, token_eq, token_ne,
1209     token_rbrace, token_lbrace, token_group,
1210     token_ge, token_le, token_gt, token_lt
1211 };
1212 struct token {
1213     enum token_type type;
1214     char value[MAX_STRING_LEN];
1215 };
1216
1217 /* there is an implicit assumption here that string is at most MAX_STRING_LEN-1
1218  * characters long...
1219  */
1220 static const char *get_ptoken(request_rec *r, const char *string, struct token *token,
1221                               int *unmatched)
1222 {
1223     char ch;
1224     int next = 0;
1225     int qs = 0;
1226     int tkn_fnd = 0;
1227
1228     /* Skip leading white space */
1229     if (string == (char *) NULL) {
1230         return (char *) NULL;
1231     }
1232     while ((ch = *string++)) {
1233         if (!apr_isspace(ch)) {
1234             break;
1235         }
1236     }
1237     if (ch == '\0') {
1238         return (char *) NULL;
1239     }
1240
1241     token->type = token_string; /* the default type */
1242     switch (ch) {
1243     case '(':
1244         token->type = token_lbrace;
1245         return (string);
1246     case ')':
1247         token->type = token_rbrace;
1248         return (string);
1249     case '=':
1250         token->type = token_eq;
1251         return (string);
1252     case '!':
1253         if (*string == '=') {
1254             token->type = token_ne;
1255             return (string + 1);
1256         }
1257         else {
1258             token->type = token_not;
1259             return (string);
1260         }
1261     case '\'':
1262         token->type = token_string;
1263         qs = 1;
1264         break;
1265     case '|':
1266         if (*string == '|') {
1267             token->type = token_or;
1268             return (string + 1);
1269         }
1270         break;
1271     case '&':
1272         if (*string == '&') {
1273             token->type = token_and;
1274             return (string + 1);
1275         }
1276         break;
1277     case '>':
1278         if (*string == '=') {
1279             token->type = token_ge;
1280             return (string + 1);
1281         }
1282         else {
1283             token->type = token_gt;
1284             return (string);
1285         }
1286     case '<':
1287         if (*string == '=') {
1288             token->type = token_le;
1289             return (string + 1);
1290         }
1291         else {
1292             token->type = token_lt;
1293             return (string);
1294         }
1295     default:
1296         token->type = token_string;
1297         break;
1298     }
1299     /* We should only be here if we are in a string */
1300     if (!qs) {
1301         token->value[next++] = ch;
1302     }
1303
1304     /* 
1305      * Yes I know that goto's are BAD.  But, c doesn't allow me to
1306      * exit a loop from a switch statement.  Yes, I could use a flag,
1307      * but that is (IMHO) even less readable/maintainable than the goto.
1308      */
1309     /* 
1310      * I used the ++string throughout this section so that string
1311      * ends up pointing to the next token and I can just return it
1312      */
1313     for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
1314         if (ch == '\\') {
1315             if ((ch = *++string) == '\0') {
1316                 tkn_fnd = 1;
1317             }
1318             else {
1319                 token->value[next++] = ch;
1320             }
1321         }
1322         else {
1323             if (!qs) {
1324                 if (apr_isspace(ch)) {
1325                     tkn_fnd = 1;
1326                 }
1327                 else {
1328                     switch (ch) {
1329                     case '(':
1330                     case ')':
1331                     case '=':
1332                     case '!':
1333                     case '<':
1334                     case '>':
1335                         tkn_fnd = 1;
1336                         break;
1337                     case '|':
1338                         if (*(string + 1) == '|') {
1339                             tkn_fnd = 1;
1340                         }
1341                         break;
1342                     case '&':
1343                         if (*(string + 1) == '&') {
1344                             tkn_fnd = 1;
1345                         }
1346                         break;
1347                     }
1348                     if (!tkn_fnd) {
1349                         token->value[next++] = ch;
1350                     }
1351                 }
1352             }
1353             else {
1354                 if (ch == '\'') {
1355                     qs = 0;
1356                     ++string;
1357                     tkn_fnd = 1;
1358                 }
1359                 else {
1360                     token->value[next++] = ch;
1361                 }
1362             }
1363         }
1364     }
1365
1366     /* If qs is still set, I have an unmatched ' */
1367     if (qs) {
1368         *unmatched = 1;
1369         next = 0;
1370     }
1371     token->value[next] = '\0';
1372     return (string);
1373 }
1374
1375
1376 /*
1377  * Hey I still know that goto's are BAD.  I don't think that I've ever
1378  * used two in the same project, let alone the same file before.  But,
1379  * I absolutely want to make sure that I clean up the memory in all
1380  * cases.  And, without rewriting this completely, the easiest way
1381  * is to just branch to the return code which cleans it up.
1382  */
1383 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
1384  * characters long...
1385  */
1386 static int parse_expr(request_rec *r, const char *expr, int *was_error, 
1387                       int *was_unmatched, char *debug)
1388 {
1389     struct parse_node {
1390         struct parse_node *left, *right, *parent;
1391         struct token token;
1392         int value, done;
1393     }         *root, *current, *new;
1394     const char *parse;
1395     char buffer[MAX_STRING_LEN];
1396     apr_pool_t *expr_pool;
1397     int retval = 0;
1398     apr_size_t debug_pos = 0;
1399
1400     debug[debug_pos] = '\0';
1401     *was_error       = 0;
1402     *was_unmatched   = 0;
1403     if ((parse = expr) == (char *) NULL) {
1404         return (0);
1405     }
1406     root = current = (struct parse_node *) NULL;
1407     if (apr_pool_create(&expr_pool, r->pool) != APR_SUCCESS)
1408                 return 0;
1409
1410     /* Create Parse Tree */
1411     while (1) {
1412         new = (struct parse_node *) apr_palloc(expr_pool,
1413                                            sizeof(struct parse_node));
1414         new->parent = new->left = new->right = (struct parse_node *) NULL;
1415         new->done = 0;
1416         if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == (char *) NULL) {
1417             break;
1418         }
1419         switch (new->token.type) {
1420
1421         case token_string:
1422 #ifdef DEBUG_INCLUDE
1423             debug_pos += sprintf (&debug[debug_pos], "     Token: string (%s)\n",
1424                                   new->token.value);
1425 #endif
1426             if (current == (struct parse_node *) NULL) {
1427                 root = current = new;
1428                 break;
1429             }
1430             switch (current->token.type) {
1431             case token_string:
1432                 if (current->token.value[0] != '\0') {
1433                     strncat(current->token.value, " ",
1434                          sizeof(current->token.value)
1435                             - strlen(current->token.value) - 1);
1436                 }
1437                 strncat(current->token.value, new->token.value,
1438                          sizeof(current->token.value)
1439                             - strlen(current->token.value) - 1);
1440                 current->token.value[sizeof(current->token.value) - 1] = '\0';
1441                 break;
1442             case token_eq:
1443             case token_ne:
1444             case token_and:
1445             case token_or:
1446             case token_lbrace:
1447             case token_not:
1448             case token_ge:
1449             case token_gt:
1450             case token_le:
1451             case token_lt:
1452                 new->parent = current;
1453                 current = current->right = new;
1454                 break;
1455             default:
1456                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1457                             "Invalid expression \"%s\" in file %s",
1458                             expr, r->filename);
1459                 *was_error = 1;
1460                 goto RETURN;
1461             }
1462             break;
1463
1464         case token_and:
1465         case token_or:
1466 #ifdef DEBUG_INCLUDE
1467             memcpy (&debug[debug_pos], "     Token: and/or\n",
1468                     sizeof ("     Token: and/or\n"));
1469             debug_pos += sizeof ("     Token: and/or\n");
1470 #endif
1471             if (current == (struct parse_node *) NULL) {
1472                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1473                             "Invalid expression \"%s\" in file %s",
1474                             expr, r->filename);
1475                 *was_error = 1;
1476                 goto RETURN;
1477             }
1478             /* Percolate upwards */
1479             while (current != (struct parse_node *) NULL) {
1480                 switch (current->token.type) {
1481                 case token_string:
1482                 case token_group:
1483                 case token_not:
1484                 case token_eq:
1485                 case token_ne:
1486                 case token_and:
1487                 case token_or:
1488                 case token_ge:
1489                 case token_gt:
1490                 case token_le:
1491                 case token_lt:
1492                     current = current->parent;
1493                     continue;
1494                 case token_lbrace:
1495                     break;
1496                 default:
1497                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1498                                 "Invalid expression \"%s\" in file %s",
1499                                 expr, r->filename);
1500                     *was_error = 1;
1501                     goto RETURN;
1502                 }
1503                 break;
1504             }
1505             if (current == (struct parse_node *) NULL) {
1506                 new->left = root;
1507                 new->left->parent = new;
1508                 new->parent = (struct parse_node *) NULL;
1509                 root = new;
1510             }
1511             else {
1512                 new->left = current->right;
1513                 current->right = new;
1514                 new->parent = current;
1515             }
1516             current = new;
1517             break;
1518
1519         case token_not:
1520 #ifdef DEBUG_INCLUDE
1521             memcpy (&debug[debug_pos], "     Token: not\n",
1522                     sizeof ("     Token: not\n"));
1523             debug_pos += sizeof ("     Token: not\n");
1524 #endif
1525             if (current == (struct parse_node *) NULL) {
1526                 root = current = new;
1527                 break;
1528             }
1529             /* Percolate upwards */
1530             while (current != (struct parse_node *) NULL) {
1531                 switch (current->token.type) {
1532                 case token_not:
1533                 case token_eq:
1534                 case token_ne:
1535                 case token_and:
1536                 case token_or:
1537                 case token_lbrace:
1538                 case token_ge:
1539                 case token_gt:
1540                 case token_le:
1541                 case token_lt:
1542                     break;
1543                 default:
1544                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1545                                 "Invalid expression \"%s\" in file %s",
1546                                 expr, r->filename);
1547                     *was_error = 1;
1548                     goto RETURN;
1549                 }
1550                 break;
1551             }
1552             if (current == (struct parse_node *) NULL) {
1553                 new->left = root;
1554                 new->left->parent = new;
1555                 new->parent = (struct parse_node *) NULL;
1556                 root = new;
1557             }
1558             else {
1559                 new->left = current->right;
1560                 current->right = new;
1561                 new->parent = current;
1562             }
1563             current = new;
1564             break;
1565
1566         case token_eq:
1567         case token_ne:
1568         case token_ge:
1569         case token_gt:
1570         case token_le:
1571         case token_lt:
1572 #ifdef DEBUG_INCLUDE
1573             memcpy (&debug[debug_pos], "     Token: eq/ne/ge/gt/le/lt\n",
1574                     sizeof ("     Token: eq/ne/ge/gt/le/lt\n"));
1575             debug_pos += sizeof ("     Token: eq/ne/ge/gt/le/lt\n");
1576 #endif
1577             if (current == (struct parse_node *) NULL) {
1578                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1579                             "Invalid expression \"%s\" in file %s",
1580                             expr, r->filename);
1581                 *was_error = 1;
1582                 goto RETURN;
1583             }
1584             /* Percolate upwards */
1585             while (current != (struct parse_node *) NULL) {
1586                 switch (current->token.type) {
1587                 case token_string:
1588                 case token_group:
1589                     current = current->parent;
1590                     continue;
1591                 case token_lbrace:
1592                 case token_and:
1593                 case token_or:
1594                     break;
1595                 case token_not:
1596                 case token_eq:
1597                 case token_ne:
1598                 case token_ge:
1599                 case token_gt:
1600                 case token_le:
1601                 case token_lt:
1602                 default:
1603                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1604                                 "Invalid expression \"%s\" in file %s",
1605                                 expr, r->filename);
1606                     *was_error = 1;
1607                     goto RETURN;
1608                 }
1609                 break;
1610             }
1611             if (current == (struct parse_node *) NULL) {
1612                 new->left = root;
1613                 new->left->parent = new;
1614                 new->parent = (struct parse_node *) NULL;
1615                 root = new;
1616             }
1617             else {
1618                 new->left = current->right;
1619                 current->right = new;
1620                 new->parent = current;
1621             }
1622             current = new;
1623             break;
1624
1625         case token_rbrace:
1626 #ifdef DEBUG_INCLUDE
1627             memcpy (&debug[debug_pos], "     Token: rbrace\n",
1628                     sizeof ("     Token: rbrace\n"));
1629             debug_pos += sizeof ("     Token: rbrace\n");
1630 #endif
1631             while (current != (struct parse_node *) NULL) {
1632                 if (current->token.type == token_lbrace) {
1633                     current->token.type = token_group;
1634                     break;
1635                 }
1636                 current = current->parent;
1637             }
1638             if (current == (struct parse_node *) NULL) {
1639                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1640                             "Unmatched ')' in \"%s\" in file %s",
1641                             expr, r->filename);
1642                 *was_error = 1;
1643                 goto RETURN;
1644             }
1645             break;
1646
1647         case token_lbrace:
1648 #ifdef DEBUG_INCLUDE
1649             memcpy (&debug[debug_pos], "     Token: lbrace\n",
1650                     sizeof ("     Token: lbrace\n"));
1651             debug_pos += sizeof ("     Token: lbrace\n");
1652 #endif
1653             if (current == (struct parse_node *) NULL) {
1654                 root = current = new;
1655                 break;
1656             }
1657             /* Percolate upwards */
1658             while (current != (struct parse_node *) NULL) {
1659                 switch (current->token.type) {
1660                 case token_not:
1661                 case token_eq:
1662                 case token_ne:
1663                 case token_and:
1664                 case token_or:
1665                 case token_lbrace:
1666                 case token_ge:
1667                 case token_gt:
1668                 case token_le:
1669                 case token_lt:
1670                     break;
1671                 case token_string:
1672                 case token_group:
1673                 default:
1674                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1675                                 "Invalid expression \"%s\" in file %s",
1676                                 expr, r->filename);
1677                     *was_error = 1;
1678                     goto RETURN;
1679                 }
1680                 break;
1681             }
1682             if (current == (struct parse_node *) NULL) {
1683                 new->left = root;
1684                 new->left->parent = new;
1685                 new->parent = (struct parse_node *) NULL;
1686                 root = new;
1687             }
1688             else {
1689                 new->left = current->right;
1690                 current->right = new;
1691                 new->parent = current;
1692             }
1693             current = new;
1694             break;
1695         default:
1696             break;
1697         }
1698     }
1699
1700     /* Evaluate Parse Tree */
1701     current = root;
1702     while (current != (struct parse_node *) NULL) {
1703         switch (current->token.type) {
1704         case token_string:
1705 #ifdef DEBUG_INCLUDE
1706             memcpy (&debug[debug_pos], "     Evaluate string\n",
1707                     sizeof ("     Evaluate string\n"));
1708             debug_pos += sizeof ("     Evaluate string\n");
1709 #endif
1710             ap_ssi_parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
1711             apr_cpystrn(current->token.value, buffer, sizeof(current->token.value));
1712             current->value = (current->token.value[0] != '\0');
1713             current->done = 1;
1714             current = current->parent;
1715             break;
1716
1717         case token_and:
1718         case token_or:
1719 #ifdef DEBUG_INCLUDE
1720             memcpy (&debug[debug_pos], "     Evaluate and/or\n",
1721                     sizeof ("     Evaluate and/or\n"));
1722             debug_pos += sizeof ("     Evaluate and/or\n");
1723 #endif
1724             if (current->left == (struct parse_node *) NULL ||
1725                 current->right == (struct parse_node *) NULL) {
1726                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1727                             "Invalid expression \"%s\" in file %s",
1728                             expr, r->filename);
1729                 *was_error = 1;
1730                 goto RETURN;
1731             }
1732             if (!current->left->done) {
1733                 switch (current->left->token.type) {
1734                 case token_string:
1735                     ap_ssi_parse_string(r, current->left->token.value,
1736                                  buffer, sizeof(buffer), 0);
1737                     apr_cpystrn(current->left->token.value, buffer,
1738                             sizeof(current->left->token.value));
1739                     current->left->value = (current->left->token.value[0] != '\0');
1740                     current->left->done = 1;
1741                     break;
1742                 default:
1743                     current = current->left;
1744                     continue;
1745                 }
1746             }
1747             if (!current->right->done) {
1748                 switch (current->right->token.type) {
1749                 case token_string:
1750                     ap_ssi_parse_string(r, current->right->token.value,
1751                                  buffer, sizeof(buffer), 0);
1752                     apr_cpystrn(current->right->token.value, buffer,
1753                             sizeof(current->right->token.value));
1754                     current->right->value = (current->right->token.value[0] != '\0');
1755                     current->right->done = 1;
1756                     break;
1757                 default:
1758                     current = current->right;
1759                     continue;
1760                 }
1761             }
1762 #ifdef DEBUG_INCLUDE
1763             debug_pos += sprintf (&debug[debug_pos], "     Left: %c\n",
1764                                   current->left->value ? '1' : '0');
1765             debug_pos += sprintf (&debug[debug_pos], "     Right: %c\n",
1766                                   current->right->value ? '1' : '0');
1767 #endif
1768             if (current->token.type == token_and) {
1769                 current->value = current->left->value && current->right->value;
1770             }
1771             else {
1772                 current->value = current->left->value || current->right->value;
1773             }
1774 #ifdef DEBUG_INCLUDE
1775             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1776                                   current->value ? '1' : '0');
1777 #endif
1778             current->done = 1;
1779             current = current->parent;
1780             break;
1781
1782         case token_eq:
1783         case token_ne:
1784 #ifdef DEBUG_INCLUDE
1785             memcpy (&debug[debug_pos], "     Evaluate eq/ne\n",
1786                     sizeof ("     Evaluate eq/ne\n"));
1787             debug_pos += sizeof ("     Evaluate eq/ne\n");
1788 #endif
1789             if ((current->left == (struct parse_node *) NULL) ||
1790                 (current->right == (struct parse_node *) NULL) ||
1791                 (current->left->token.type != token_string) ||
1792                 (current->right->token.type != token_string)) {
1793                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1794                             "Invalid expression \"%s\" in file %s",
1795                             expr, r->filename);
1796                 *was_error = 1;
1797                 goto RETURN;
1798             }
1799             ap_ssi_parse_string(r, current->left->token.value,
1800                          buffer, sizeof(buffer), 0);
1801             apr_cpystrn(current->left->token.value, buffer,
1802                         sizeof(current->left->token.value));
1803             ap_ssi_parse_string(r, current->right->token.value,
1804                          buffer, sizeof(buffer), 0);
1805             apr_cpystrn(current->right->token.value, buffer,
1806                         sizeof(current->right->token.value));
1807             if (current->right->token.value[0] == '/') {
1808                 int len;
1809                 len = strlen(current->right->token.value);
1810                 if (current->right->token.value[len - 1] == '/') {
1811                     current->right->token.value[len - 1] = '\0';
1812                 }
1813                 else {
1814                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1815                                 "Invalid rexp \"%s\" in file %s",
1816                                 current->right->token.value, r->filename);
1817                     *was_error = 1;
1818                     goto RETURN;
1819                 }
1820 #ifdef DEBUG_INCLUDE
1821                 debug_pos += sprintf (&debug[debug_pos],
1822                                       "     Re Compare (%s) with /%s/\n",
1823                                       current->left->token.value,
1824                                       &current->right->token.value[1]);
1825 #endif
1826                 current->value =
1827                     re_check(r, current->left->token.value,
1828                              &current->right->token.value[1]);
1829             }
1830             else {
1831 #ifdef DEBUG_INCLUDE
1832                 debug_pos += sprintf (&debug[debug_pos],
1833                                       "     Compare (%s) with (%s)\n",
1834                                       current->left->token.value,
1835                                       current->right->token.value);
1836 #endif
1837                 current->value =
1838                     (strcmp(current->left->token.value,
1839                             current->right->token.value) == 0);
1840             }
1841             if (current->token.type == token_ne) {
1842                 current->value = !current->value;
1843             }
1844 #ifdef DEBUG_INCLUDE
1845             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1846                                   current->value ? '1' : '0');
1847 #endif
1848             current->done = 1;
1849             current = current->parent;
1850             break;
1851         case token_ge:
1852         case token_gt:
1853         case token_le:
1854         case token_lt:
1855 #ifdef DEBUG_INCLUDE
1856             memcpy (&debug[debug_pos], "     Evaluate ge/gt/le/lt\n",
1857                     sizeof ("     Evaluate ge/gt/le/lt\n"));
1858             debug_pos += sizeof ("     Evaluate ge/gt/le/lt\n");
1859 #endif
1860             if ((current->left == (struct parse_node *) NULL) ||
1861                 (current->right == (struct parse_node *) NULL) ||
1862                 (current->left->token.type != token_string) ||
1863                 (current->right->token.type != token_string)) {
1864                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1865                             "Invalid expression \"%s\" in file %s",
1866                             expr, r->filename);
1867                 *was_error = 1;
1868                 goto RETURN;
1869             }
1870             ap_ssi_parse_string(r, current->left->token.value,
1871                          buffer, sizeof(buffer), 0);
1872             apr_cpystrn(current->left->token.value, buffer,
1873                         sizeof(current->left->token.value));
1874             ap_ssi_parse_string(r, current->right->token.value,
1875                          buffer, sizeof(buffer), 0);
1876             apr_cpystrn(current->right->token.value, buffer,
1877                         sizeof(current->right->token.value));
1878 #ifdef DEBUG_INCLUDE
1879             debug_pos += sprintf (&debug[debug_pos],
1880                                   "     Compare (%s) with (%s)\n",
1881                                   current->left->token.value,
1882                                   current->right->token.value);
1883 #endif
1884             current->value =
1885                 strcmp(current->left->token.value,
1886                        current->right->token.value);
1887             if (current->token.type == token_ge) {
1888                 current->value = current->value >= 0;
1889             }
1890             else if (current->token.type == token_gt) {
1891                 current->value = current->value > 0;
1892             }
1893             else if (current->token.type == token_le) {
1894                 current->value = current->value <= 0;
1895             }
1896             else if (current->token.type == token_lt) {
1897                 current->value = current->value < 0;
1898             }
1899             else {
1900                 current->value = 0;     /* Don't return -1 if unknown token */
1901             }
1902 #ifdef DEBUG_INCLUDE
1903             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1904                                   current->value ? '1' : '0');
1905 #endif
1906             current->done = 1;
1907             current = current->parent;
1908             break;
1909
1910         case token_not:
1911             if (current->right != (struct parse_node *) NULL) {
1912                 if (!current->right->done) {
1913                     current = current->right;
1914                     continue;
1915                 }
1916                 current->value = !current->right->value;
1917             }
1918             else {
1919                 current->value = 0;
1920             }
1921 #ifdef DEBUG_INCLUDE
1922             debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
1923                                   current->value ? '1' : '0');
1924 #endif
1925             current->done = 1;
1926             current = current->parent;
1927             break;
1928
1929         case token_group:
1930             if (current->right != (struct parse_node *) NULL) {
1931                 if (!current->right->done) {
1932                     current = current->right;
1933                     continue;
1934                 }
1935                 current->value = current->right->value;
1936             }
1937             else {
1938                 current->value = 1;
1939             }
1940 #ifdef DEBUG_INCLUDE
1941             debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
1942                                   current->value ? '1' : '0');
1943 #endif
1944             current->done = 1;
1945             current = current->parent;
1946             break;
1947
1948         case token_lbrace:
1949             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1950                         "Unmatched '(' in \"%s\" in file %s",
1951                         expr, r->filename);
1952             *was_error = 1;
1953             goto RETURN;
1954
1955         case token_rbrace:
1956             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1957                         "Unmatched ')' in \"%s\" in file %s",
1958                         expr, r->filename);
1959             *was_error = 1;
1960             goto RETURN;
1961
1962         default:
1963             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1964                           "bad token type");
1965             *was_error = 1;
1966             goto RETURN;
1967         }
1968     }
1969
1970     retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
1971   RETURN:
1972     apr_pool_destroy(expr_pool);
1973     return (retval);
1974 }
1975
1976 /*-------------------------------------------------------------------------*/
1977 #ifdef DEBUG_INCLUDE
1978
1979 /* XXX overlaying the static string pointed to by cond_txt isn't cool */
1980
1981 #define MAX_DEBUG_SIZE MAX_STRING_LEN
1982 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)           \
1983 {                                                                          \
1984     char *cond_txt = "**** X     conditional_status=\"0\"\n";              \
1985     apr_size_t c_wrt;                                                      \
1986                                                                            \
1987     if (cntx->flags & FLAG_COND_TRUE) {                                    \
1988         cond_txt[31] = '1';                                                \
1989     }                                                                      \
1990     memcpy(&cond_txt[5], tag_text, sizeof(tag_text));                      \
1991     t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt), 1, &c_wrt); \
1992     APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                                \
1993                                                                            \
1994     if (ins_head == NULL) {                                                \
1995         ins_head = t_buck;                                                 \
1996     }                                                                      \
1997 }
1998 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)            \
1999 {                                                                        \
2000     apr_size_t b_wrt;                                                    \
2001     if (d_buf[0] != '\0') {                                              \
2002         t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf), 1, &b_wrt); \
2003         APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck);                          \
2004                                                                          \
2005         if (ins_head == NULL) {                                          \
2006             ins_head = t_buck;                                           \
2007         }                                                                \
2008     }                                                                    \
2009 }
2010 #else
2011
2012 #define MAX_DEBUG_SIZE 10
2013 #define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text)
2014 #define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head)
2015
2016 #endif
2017 /*-------------------------------------------------------------------------*/
2018
2019 /* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */
2020 static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2021                      ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
2022 {
2023     char *tag     = NULL;
2024     char *tag_val = NULL;
2025     char *expr    = NULL;
2026     int   expr_ret, was_error, was_unmatched;
2027     apr_bucket *tmp_buck;
2028     char debug_buf[MAX_DEBUG_SIZE];
2029
2030     *inserted_head = NULL;
2031     if (!ctx->flags & FLAG_PRINTING) {
2032         ctx->if_nesting_level++;
2033     }
2034     else {
2035         while (1) {
2036             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2037             if (tag == NULL) {
2038                 if (expr == NULL) {
2039                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2040                                   "missing expr in if statement: %s", r->filename);
2041                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2042                     return 1;
2043                 }
2044                 expr_ret = parse_expr(r, expr, &was_error, &was_unmatched, debug_buf);
2045                 if (was_error) {
2046                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2047                     return 1;
2048                 }
2049                 if (was_unmatched) {
2050                     DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, "\nUnmatched '\n",
2051                                           *inserted_head);
2052                 }
2053                 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, *inserted_head);
2054                 
2055                 if (expr_ret) {
2056                     ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2057                 }
2058                 else {
2059                     ctx->flags &= FLAG_CLEAR_PRINT_COND;
2060                 }
2061                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "   if");
2062                 ctx->if_nesting_level = 0;
2063                 return 0;
2064             }
2065             else if (!strcmp(tag, "expr")) {
2066                 expr = tag_val;
2067 #ifdef DEBUG_INCLUDE
2068                 if (1) {
2069                     apr_size_t d_len = 0, d_wrt = 0;
2070                     d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
2071                     tmp_buck = apr_bucket_heap_create(debug_buf, d_len, 1, &d_wrt);
2072                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2073
2074                     if (*inserted_head == NULL) {
2075                         *inserted_head = tmp_buck;
2076                     }
2077                 }
2078 #endif
2079             }
2080             else {
2081                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2082                             "unknown parameter \"%s\" to tag if in %s", tag, r->filename);
2083                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2084             }
2085
2086         }
2087     }
2088     return 0;
2089 }
2090
2091 static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2092                        ap_filter_t *f,  apr_bucket *head_ptr, apr_bucket **inserted_head)
2093 {
2094     char *tag     = NULL;
2095     char *tag_val = NULL;
2096     char *expr    = NULL;
2097     int   expr_ret, was_error, was_unmatched;
2098     apr_bucket *tmp_buck;
2099     char debug_buf[MAX_DEBUG_SIZE];
2100
2101     *inserted_head = NULL;
2102     if (!ctx->if_nesting_level) {
2103         while (1) {
2104             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0);
2105             if (tag == '\0') {
2106                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " elif");
2107                 
2108                 if (ctx->flags & FLAG_COND_TRUE) {
2109                     ctx->flags &= FLAG_CLEAR_PRINTING;
2110                     return (0);
2111                 }
2112                 if (expr == NULL) {
2113                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2114                                   "missing expr in elif statement: %s", r->filename);
2115                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2116                     return (1);
2117                 }
2118                 expr_ret = parse_expr(r, expr, &was_error, &was_unmatched, debug_buf);
2119                 if (was_error) {
2120                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2121                     return 1;
2122                 }
2123                 if (was_unmatched) {
2124                     DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, "\nUnmatched '\n",
2125                                           *inserted_head);
2126                 }
2127                 DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, *inserted_head);
2128                 
2129                 if (expr_ret) {
2130                     ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2131                 }
2132                 else {
2133                     ctx->flags &= FLAG_CLEAR_PRINT_COND;
2134                 }
2135                 LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " elif");
2136                 return (0);
2137             }
2138             else if (!strcmp(tag, "expr")) {
2139                 expr = tag_val;
2140 #ifdef DEBUG_INCLUDE
2141                 if (1) {
2142                     apr_size_t d_len = 0, d_wrt = 0;
2143                     d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
2144                     tmp_buck = apr_bucket_heap_create(debug_buf, d_len, 1, &d_wrt);
2145                     APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2146
2147                     if (*inserted_head == NULL) {
2148                         *inserted_head = tmp_buck;
2149                     }
2150                 }
2151 #endif
2152             }
2153             else {
2154                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2155                             "unknown parameter \"%s\" to tag if in %s", tag, r->filename);
2156                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2157             }
2158         }
2159     }
2160     return 0;
2161 }
2162
2163 static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2164                        ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
2165 {
2166     char *tag = NULL;
2167     char *tag_val = NULL;
2168     apr_bucket *tmp_buck;
2169
2170     *inserted_head = NULL;
2171     if (!ctx->if_nesting_level) {
2172         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2173         if ((tag != NULL) || (tag_val != NULL)) {
2174             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2175                         "else directive does not take tags in %s", r->filename);
2176             if (ctx->flags & FLAG_PRINTING) {
2177                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2178             }
2179             return -1;
2180         }
2181         else {
2182             LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else");
2183             
2184             if (ctx->flags & FLAG_COND_TRUE) {
2185                 ctx->flags &= FLAG_CLEAR_PRINTING;
2186             }
2187             else {
2188                 ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2189             }
2190             return 0;
2191         }
2192     }
2193     return 0;
2194 }
2195
2196 static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2197                         ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
2198 {
2199     char *tag     = NULL;
2200     char *tag_val = NULL;
2201     apr_bucket *tmp_buck;
2202
2203     *inserted_head = NULL;
2204     if (!ctx->if_nesting_level) {
2205         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2206         if ((tag != NULL) || (tag_val != NULL)) {
2207             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2208                         "endif directive does not take tags in %s", r->filename);
2209             CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2210             return -1;
2211         }
2212         else {
2213             LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif");
2214             ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE);
2215             return 0;
2216         }
2217     }
2218     else {
2219         ctx->if_nesting_level--;
2220         return 0;
2221     }
2222 }
2223
2224 static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2225                       ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
2226 {
2227     char *tag     = NULL;
2228     char *tag_val = NULL;
2229     char *var     = NULL;
2230     apr_bucket *tmp_buck;
2231     char parsed_string[MAX_STRING_LEN];
2232
2233     *inserted_head = NULL;
2234     if (ctx->flags & FLAG_PRINTING) {
2235         while (1) {
2236             ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2237             if ((tag == NULL) && (tag_val == NULL)) {
2238                 return 0;
2239             }
2240             else if (tag_val == NULL) {
2241                 return 1;
2242             }
2243             else if (!strcmp(tag, "var")) {
2244                 var = tag_val;
2245             }
2246             else if (!strcmp(tag, "value")) {
2247                 if (var == (char *) NULL) {
2248                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2249                                 "variable must precede value in set directive in %s",
2250                             r->filename);
2251                     CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2252                     return (-1);
2253                 }
2254                 ap_ssi_parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
2255                 apr_table_setn(r->subprocess_env, apr_pstrdup(r->pool, var),
2256                                apr_pstrdup(r->pool, parsed_string));
2257             }
2258             else {
2259                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2260                             "Invalid tag for set directive in %s", r->filename);
2261                 CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2262                 return -1;
2263             }
2264         }
2265     }
2266     return 0;
2267 }
2268
2269 static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb, request_rec *r,
2270                            ap_filter_t *f, apr_bucket *head_ptr, apr_bucket **inserted_head)
2271 {
2272     char *tag     = NULL;
2273     char *tag_val = NULL;
2274     apr_bucket *tmp_buck;
2275
2276     if (ctx->flags & FLAG_PRINTING) {
2277         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);
2278         if ((tag == NULL) && (tag_val == NULL)) {
2279             apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
2280             apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
2281             int i;
2282             char *key_text, *val_text;
2283             apr_size_t   k_len, v_len, t_wrt;
2284
2285             *inserted_head = NULL;
2286             for (i = 0; i < arr->nelts; ++i) {
2287                 key_text = ap_escape_html(r->pool, elts[i].key);
2288                 val_text = ap_escape_html(r->pool, elts[i].val);
2289                 k_len = strlen(key_text);
2290                 v_len = strlen(val_text);
2291
2292                 /*  Key_text                                               */
2293                 tmp_buck = apr_bucket_heap_create(key_text, k_len, 1, &t_wrt);
2294                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2295                 if (*inserted_head == NULL) {
2296                     *inserted_head = tmp_buck;
2297                 }
2298                 /*            =                                            */
2299                 tmp_buck = apr_bucket_immortal_create("=", 1);
2300                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2301                 /*              Value_text                                 */
2302                 tmp_buck = apr_bucket_heap_create(val_text, v_len, 1, &t_wrt);
2303                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2304                 /*                        newline...                       */
2305                 tmp_buck = apr_bucket_immortal_create("\n", 1);
2306                 APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck);
2307             }
2308             return 0;
2309         }
2310         else {
2311             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2312                         "printenv directive does not take tags in %s", r->filename);
2313             CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
2314             return -1;
2315         }
2316     }
2317     return 0;
2318 }
2319
2320 /* -------------------------- The main function --------------------------- */
2321
2322 static apr_status_t send_parsed_content(apr_bucket_brigade **bb, 
2323                                         request_rec *r, ap_filter_t *f)
2324 {
2325     include_ctx_t *ctx = f->ctx;
2326     apr_bucket *dptr = APR_BRIGADE_FIRST(*bb);
2327     apr_bucket *tmp_dptr;
2328     apr_bucket_brigade *tag_and_after;
2329     int ret;
2330     apr_status_t rv;
2331
2332     if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
2333         char *arg_copy = apr_pstrdup(r->pool, r->args);
2334
2335         apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
2336         ap_unescape_url(arg_copy);
2337         apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
2338                   ap_escape_shell_cmd(r->pool, arg_copy));
2339     }
2340
2341     while (dptr != APR_BRIGADE_SENTINEL(*bb)) {
2342         /* State to check for the STARTING_SEQUENCE. */
2343         if ((ctx->state == PRE_HEAD) || (ctx->state == PARSE_HEAD)) {
2344             int do_cleanup = 0;
2345             apr_size_t cleanup_bytes = ctx->parse_pos;
2346
2347             tmp_dptr = find_start_sequence(dptr, ctx, *bb, &do_cleanup);
2348
2349             /* The few bytes stored in the ssi_tag_brigade turned out not to
2350              * be a tag after all. This can only happen if the starting
2351              * tag actually spans brigades. This should be very rare.
2352              */
2353             if ((do_cleanup) && (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) {
2354                 apr_bucket *tmp_bkt;
2355
2356                 tmp_bkt = apr_bucket_immortal_create(STARTING_SEQUENCE,
2357                                                      cleanup_bytes);
2358                 APR_BRIGADE_INSERT_HEAD(*bb, tmp_bkt);
2359                 apr_brigade_cleanup(ctx->ssi_tag_brigade);
2360             }
2361
2362             /* If I am inside a conditional (if, elif, else) that is false
2363              *   then I need to throw away anything contained in it.
2364              */
2365             if ((!(ctx->flags & FLAG_PRINTING)) && (tmp_dptr != NULL) &&
2366                 (dptr != APR_BRIGADE_SENTINEL(*bb))) {
2367                 while ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
2368                        (dptr != tmp_dptr)) {
2369                     apr_bucket *free_bucket = dptr;
2370
2371                     dptr = APR_BUCKET_NEXT (dptr);
2372                     apr_bucket_delete(free_bucket);
2373                 }
2374             }
2375
2376             /* Adjust the current bucket position based on what was found... */
2377             if ((tmp_dptr != NULL) && (ctx->state == PARSE_DIRECTIVE)) {
2378                 if (ctx->tag_start_bucket != NULL) {
2379                     dptr = ctx->tag_start_bucket;
2380                 }
2381                 else {
2382                     dptr = APR_BRIGADE_SENTINEL(*bb);
2383                 }
2384             }
2385             else if ((tmp_dptr != NULL) && (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD)) {
2386                                /* Send the large chunk of pre-tag bytes...  */
2387                 tag_and_after = apr_brigade_split(*bb, tmp_dptr);
2388                 rv = ap_pass_brigade(f->next, *bb);
2389                 if (rv != APR_SUCCESS) {
2390                     return rv;
2391                 }
2392                 *bb  = tag_and_after;
2393                 dptr = tmp_dptr;
2394                 ctx->bytes_parsed = 0;
2395             }
2396             else if (tmp_dptr == NULL) { /* There was no possible SSI tag in the */
2397                 dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
2398             }
2399         }
2400
2401         /* State to check for the ENDING_SEQUENCE. */
2402         if (((ctx->state == PARSE_DIRECTIVE) ||
2403              (ctx->state == PARSE_TAG)       ||
2404              (ctx->state == PARSE_TAIL))       &&
2405             (dptr != APR_BRIGADE_SENTINEL(*bb))) {
2406             tmp_dptr = find_end_sequence(dptr, ctx, *bb);
2407
2408             if (tmp_dptr != NULL) {
2409                 dptr = tmp_dptr;  /* Adjust bucket pos... */
2410                 
2411                 /* If some of the tag has already been set aside then set
2412                  * aside remainder of tag. Now the full tag is in ssi_tag_brigade.
2413                  * If none has yet been set aside, then leave it all where it is.
2414                  * In any event after this the entire set of tag buckets will be
2415                  * in one place or another.
2416                  */
2417                 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
2418                     tag_and_after = apr_brigade_split(*bb, dptr);
2419                     APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
2420                     *bb = tag_and_after;
2421                 }
2422                 else if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) {
2423                     SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next);
2424                 }
2425             }
2426             else {
2427                 dptr = APR_BRIGADE_SENTINEL(*bb);  /* remainder of this brigade...    */
2428             }
2429         }
2430
2431         /* State to processed the directive... */
2432         if (ctx->state == PARSED) {
2433             apr_bucket    *content_head = NULL, *tmp_bkt;
2434             apr_size_t    tmp_i;
2435             char          tmp_buf[TMP_BUF_SIZE];
2436             int (*handle_func)(include_ctx_t *, apr_bucket_brigade **, request_rec *,
2437                            ap_filter_t *, apr_bucket *, apr_bucket **);
2438
2439             /* By now the full tag (all buckets) should either be set aside into
2440              *  ssi_tag_brigade or contained within the current bb. All tag
2441              *  processing from here on can assume that.
2442              */
2443
2444             /* At this point, everything between ctx->head_start_bucket and
2445              * ctx->tail_start_bucket is an SSI
2446              * directive, we just have to deal with it now.
2447              */
2448             if (get_combined_directive(ctx, r, *bb, tmp_buf,
2449                                         TMP_BUF_SIZE) != APR_SUCCESS) {
2450                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2451                             "mod_include: error copying directive in %s",
2452                             r->filename);
2453                 CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
2454
2455                 /* DO CLEANUP HERE!!!!! */
2456                 tmp_dptr = ctx->head_start_bucket;
2457                 if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
2458                     apr_brigade_cleanup(ctx->ssi_tag_brigade);
2459                 }
2460                 else {
2461                     do {
2462                         tmp_bkt  = tmp_dptr;
2463                         tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
2464                         apr_bucket_delete(tmp_bkt);
2465                     } while ((tmp_dptr != dptr) &&
2466                              (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
2467                 }
2468
2469                 return APR_SUCCESS;
2470             }
2471
2472             /* Can't destroy the tag buckets until I'm done processing
2473              *  because the combined_tag might just be pointing to
2474              *  the contents of a single bucket!
2475              */
2476
2477             /* Retrieve the handler function to be called for this directive from the
2478              *  functions registered in the hash table.
2479              * Need to lower case the directive for proper matching. Also need to have
2480              *  it NULL terminated (and include the NULL in the length) for proper
2481              *  hash matching.
2482              */
2483             for (tmp_i = 0; tmp_i < ctx->directive_length; tmp_i++) {
2484                 ctx->combined_tag[tmp_i] = apr_tolower(ctx->combined_tag[tmp_i]);
2485             }
2486             ctx->combined_tag[ctx->directive_length] = '\0';
2487             ctx->curr_tag_pos = &ctx->combined_tag[ctx->directive_length+1];
2488
2489             handle_func = 
2490                 (include_handler_fn_t *)apr_hash_get(include_hash, 
2491                                                      ctx->combined_tag, 
2492                                                      ctx->directive_length+1);
2493             if (handle_func != NULL) {
2494                 ret = (*handle_func)(ctx, bb, r, f, dptr, &content_head);
2495             }
2496             else {
2497                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2498                               "unknown directive \"%s\" in parsed doc %s",
2499                               ctx->combined_tag, r->filename);
2500                 CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head);
2501             }
2502
2503             /* This chunk of code starts at the first bucket in the chain
2504              * of tag buckets (assuming that by this point the bucket for
2505              * the STARTING_SEQUENCE has been split) and loops through to
2506              * the end of the tag buckets freeing them all.
2507              *
2508              * Remember that some part of this may have been set aside
2509              * into the ssi_tag_brigade and the remainder (possibly as
2510              * little as one byte) will be in the current brigade.
2511              *
2512              * The value of dptr should have been set during the
2513              * PARSE_TAIL state to the first bucket after the
2514              * ENDING_SEQUENCE.
2515              *
2516              * The value of content_head may have been set during processing
2517              * of the directive. If so, the content was inserted in front
2518              * of the dptr bucket. The inserted buckets should not be thrown
2519              * away here, but they should also not be parsed later.
2520              */
2521             if (content_head == NULL) {
2522                 content_head = dptr;
2523             }
2524             tmp_dptr = ctx->head_start_bucket;
2525             if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
2526                 apr_brigade_cleanup(ctx->ssi_tag_brigade);
2527             }
2528             else {
2529                 do {
2530                     tmp_bkt  = tmp_dptr;
2531                     tmp_dptr = APR_BUCKET_NEXT (tmp_dptr);
2532                     apr_bucket_delete(tmp_bkt);
2533                 } while ((tmp_dptr != content_head) &&
2534                          (tmp_dptr != APR_BRIGADE_SENTINEL(*bb)));
2535             }
2536             if (ctx->combined_tag == tmp_buf) {
2537                 memset (ctx->combined_tag, '\0', ctx->tag_length);
2538                 ctx->combined_tag = NULL;
2539             }
2540
2541             /* Don't reset the flags or the nesting level!!! */
2542             ctx->parse_pos         = 0;
2543             ctx->head_start_bucket = NULL;
2544             ctx->head_start_index  = 0;
2545             ctx->tag_start_bucket  = NULL;
2546             ctx->tag_start_index   = 0;
2547             ctx->tail_start_bucket = NULL;
2548             ctx->tail_start_index  = 0;
2549             ctx->curr_tag_pos      = NULL;
2550             ctx->tag_length        = 0;
2551             ctx->directive_length  = 0;
2552
2553             if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
2554                 apr_brigade_cleanup(ctx->ssi_tag_brigade);
2555             }
2556
2557             ctx->state     = PRE_HEAD;
2558         }
2559     }
2560
2561     /* If I am in the middle of parsing an SSI tag then I need to set aside
2562      *   the pertinent trailing buckets and pass on the initial part of the
2563      *   brigade. The pertinent parts of the next brigades will be added to
2564      *   these set aside buckets to form the whole tag and will be processed
2565      *   once the whole tag has been found.
2566      */
2567     if (ctx->state == PRE_HEAD) {
2568         /* Inside a false conditional (if, elif, else), so toss it all... */
2569         if ((dptr != APR_BRIGADE_SENTINEL(*bb)) &&
2570             (!(ctx->flags & FLAG_PRINTING))) {
2571             apr_bucket *free_bucket;
2572             do {
2573                 free_bucket = dptr;
2574                 dptr = APR_BUCKET_NEXT (dptr);
2575                 apr_bucket_delete(free_bucket);
2576             } while (dptr != APR_BRIGADE_SENTINEL(*bb));
2577         }
2578         else { /* Otherwise pass it along... */
2579             rv = ap_pass_brigade(f->next, *bb);  /* No SSI tags in this brigade... */
2580             if (rv != APR_SUCCESS) {
2581                 return rv;
2582             }
2583             ctx->bytes_parsed = 0;
2584         }
2585     }
2586     else if (ctx->state == PARSED) {     /* Invalid internal condition... */
2587         apr_bucket *content_head = NULL, *tmp_bkt;
2588         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2589                       "Invalid mod_include state during file %s", r->filename);
2590         CREATE_ERROR_BUCKET(ctx, tmp_bkt, APR_BRIGADE_FIRST(*bb), content_head);
2591     }
2592     else {                 /* Entire brigade is middle chunk of SSI tag... */
2593         if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) {
2594             APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb);
2595         }
2596         else {             /* End of brigade contains part of SSI tag... */
2597             if (ctx->head_start_index > 0) {
2598                 apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index);
2599                 ctx->head_start_bucket = APR_BUCKET_NEXT(ctx->head_start_bucket);
2600                 ctx->head_start_index  = 0;
2601             }
2602                            /* Set aside tag, pass pre-tag... */
2603             tag_and_after = apr_brigade_split(*bb, ctx->head_start_bucket);
2604             ap_save_brigade(f, &ctx->ssi_tag_brigade, &tag_and_after, r->pool);
2605             rv = ap_pass_brigade(f->next, *bb);
2606             if (rv != APR_SUCCESS) {
2607                 return rv;
2608             }
2609             ctx->bytes_parsed = 0;
2610         }
2611     }
2612     return APR_SUCCESS;
2613 }
2614
2615 /*****************************************************************
2616  *
2617  * XBITHACK.  Sigh...  NB it's configurable per-directory; the compile-time
2618  * option only changes the default.
2619  */
2620
2621 module include_module;
2622 enum xbithack {
2623     xbithack_off, xbithack_on, xbithack_full
2624 };
2625
2626 typedef struct {
2627     char *default_error_msg;
2628     char *default_time_fmt;
2629     enum xbithack *xbithack;
2630 } include_dir_config;
2631
2632 #ifdef XBITHACK
2633 #define DEFAULT_XBITHACK xbithack_full
2634 #else
2635 #define DEFAULT_XBITHACK xbithack_off
2636 #endif
2637
2638 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
2639 {
2640     include_dir_config *result =
2641         (include_dir_config *)apr_palloc(p, sizeof(include_dir_config));
2642     enum xbithack *xbh = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
2643     *xbh = DEFAULT_XBITHACK;
2644     result->default_error_msg = DEFAULT_ERROR_MSG;
2645     result->default_time_fmt = DEFAULT_TIME_FORMAT;
2646     result->xbithack = xbh;
2647     return result;
2648 }
2649
2650 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
2651 {
2652     include_dir_config *conf = (include_dir_config *)xbp;
2653
2654     if (!strcasecmp(arg, "off")) {
2655         *conf->xbithack = xbithack_off;
2656     }
2657     else if (!strcasecmp(arg, "on")) {
2658         *conf->xbithack = xbithack_on;
2659     }
2660     else if (!strcasecmp(arg, "full")) {
2661         *conf->xbithack = xbithack_full;
2662     }
2663     else {
2664         return "XBitHack must be set to Off, On, or Full";
2665     }
2666
2667     return NULL;
2668 }
2669
2670 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
2671 {
2672     request_rec *r = f->r;
2673     include_ctx_t *ctx = f->ctx;
2674     request_rec *parent;
2675     apr_status_t rv;
2676     include_dir_config *conf = 
2677                    (include_dir_config *)ap_get_module_config(r->per_dir_config,
2678                                                               &include_module);
2679
2680     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
2681         return ap_pass_brigade(f->next, b);
2682     }
2683     r->allowed |= (1 << M_GET);
2684     if (r->method_number != M_GET) {
2685         return ap_pass_brigade(f->next, b);
2686     }
2687
2688     if (!f->ctx) {
2689         f->ctx    = ctx      = apr_pcalloc(f->c->pool, sizeof(*ctx));
2690         if (ctx != NULL) {
2691             ctx->state           = PRE_HEAD;
2692             ctx->flags           = (FLAG_PRINTING | FLAG_COND_TRUE);
2693             if (ap_allow_options(r) & OPT_INCNOEXEC) {
2694                 ctx->flags |= FLAG_NO_EXEC;
2695             }
2696             ctx->ssi_tag_brigade = apr_brigade_create(f->c->pool);
2697
2698             apr_cpystrn(ctx->error_str, conf->default_error_msg, sizeof(ctx->error_str));
2699             apr_cpystrn(ctx->time_str, conf->default_time_fmt, sizeof(ctx->time_str));
2700             ctx->error_length = strlen(ctx->error_str);
2701         }
2702         else {
2703             return ap_pass_brigade(f->next, b);
2704         }
2705     }
2706     else {
2707         ctx->bytes_parsed = 0;
2708     }
2709
2710     /* Assure the platform supports Group protections */
2711     if ((*conf->xbithack == xbithack_full)
2712         && (r->finfo.valid & APR_FINFO_GPROT)
2713         && (r->finfo.protection & APR_GEXECUTE)) {
2714         ap_update_mtime(r, r->finfo.mtime);
2715         ap_set_last_modified(r);
2716     }
2717
2718     if ((parent = ap_get_module_config(r->request_config, &include_module))) {
2719         /* Kludge --- for nested includes, we want to keep the subprocess
2720          * environment of the base document (for compatibility); that means
2721          * torquing our own last_modified date as well so that the
2722          * LAST_MODIFIED variable gets reset to the proper value if the
2723          * nested document resets <!--#config timefmt-->.
2724          * We also insist that the memory for this subrequest not be
2725          * destroyed, that's dealt with in handle_include().
2726          */
2727         r->subprocess_env = r->main->subprocess_env;
2728         apr_pool_join(r->main->pool, r->pool);
2729         r->finfo.mtime = r->main->finfo.mtime;
2730     }
2731     else {
2732         /* we're not a nested include, so we create an initial
2733          * environment */
2734         ap_add_common_vars(r);
2735         ap_add_cgi_vars(r);
2736         add_include_vars(r, conf->default_time_fmt);
2737     }
2738     /* XXX: this is bogus, at some point we're going to do a subrequest,
2739      * and when we do it we're going to be subjecting code that doesn't
2740      * expect to be signal-ready to SIGALRM.  There is no clean way to
2741      * fix this, except to put alarm support into BUFF. -djg
2742      */
2743
2744
2745     /* Always unset the content-length.  There is no way to know if
2746      * the content will be modified at some point by send_parsed_content.
2747      * It is very possible for us to not find any content in the first
2748      * 9k of the file, but still have to modify the content of the file.
2749      * If we are going to pass the file through send_parsed_content, then
2750      * the content-length should just be unset.
2751      */
2752     apr_table_unset(f->r->headers_out, "Content-Length");
2753
2754     rv = send_parsed_content(&b, r, f);
2755
2756     if (parent) {
2757         /* signify that the sub request should not be killed */
2758         ap_set_module_config(r->request_config, &include_module,
2759             NESTED_INCLUDE_MAGIC);
2760     }
2761
2762     return rv;
2763 }
2764
2765 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
2766 {
2767     apr_hash_set(include_hash, tag, strlen(tag) + 1, (const void *)func);
2768 }
2769
2770 static void include_post_config(apr_pool_t *p, apr_pool_t *plog,
2771                                 apr_pool_t *ptemp, server_rec *s)
2772 {
2773     include_hash = apr_hash_make(p);
2774
2775     ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
2776
2777     if(ssi_pfn_register) {
2778         ssi_pfn_register("if", handle_if);
2779         ssi_pfn_register("set", handle_set);
2780         ssi_pfn_register("else", handle_else);
2781         ssi_pfn_register("elif", handle_elif);
2782         ssi_pfn_register("echo", handle_echo);
2783         ssi_pfn_register("endif", handle_endif);
2784         ssi_pfn_register("fsize", handle_fsize);
2785         ssi_pfn_register("config", handle_config);
2786         ssi_pfn_register("include", handle_include);
2787         ssi_pfn_register("flastmod", handle_flastmod);
2788         ssi_pfn_register("printenv", handle_printenv);
2789     }
2790 }
2791
2792 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg)
2793 {
2794     include_dir_config *conf = (include_dir_config *)mconfig;
2795     conf->default_error_msg = apr_pstrdup(cmd->pool, msg);
2796     return NULL;
2797 }
2798
2799 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt)
2800 {
2801     include_dir_config *conf = (include_dir_config *)mconfig;
2802     conf->default_time_fmt = apr_pstrdup(cmd->pool, fmt);
2803     return NULL;
2804 }
2805
2806 /*
2807  * Module definition and configuration data structs...
2808  */
2809 static const command_rec includes_cmds[] =
2810 {
2811     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
2812                   "Off, On, or Full"),
2813     AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 
2814                   "a string"),
2815     AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
2816                   "a strftime(3) formatted string"),
2817     {NULL}
2818 };
2819
2820 static int xbithack_handler(request_rec *r)
2821 {
2822 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
2823     /* OS/2 dosen't currently support the xbithack. This is being worked on. */
2824     return DECLINED;
2825 #else
2826     enum xbithack *state;
2827  
2828     if (ap_strcmp_match(r->handler, "text/html")) {
2829         return DECLINED;
2830     }
2831     if (!(r->finfo.protection & APR_UEXECUTE)) {
2832         return DECLINED;
2833     }
2834  
2835     state = (enum xbithack *) ap_get_module_config(r->per_dir_config,
2836                                                 &include_module);
2837  
2838     if (*state == xbithack_off) {
2839         return DECLINED;
2840     }
2841     /* We always return declined, because the default handler will actually
2842      * serve the file.  All we have to do is add the filter.
2843      */
2844     ap_add_output_filter("INCLUDES", NULL, r, r->connection);
2845     return DECLINED;
2846 #endif
2847 }
2848
2849 static void register_hooks(apr_pool_t *p)
2850 {
2851     APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
2852     APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
2853     APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
2854     ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
2855     ap_hook_handler(xbithack_handler, NULL, NULL, APR_HOOK_MIDDLE);
2856     ap_register_output_filter("INCLUDES", includes_filter, AP_FTYPE_CONTENT);
2857 }
2858
2859 module AP_MODULE_DECLARE_DATA include_module =
2860 {
2861     STANDARD20_MODULE_STUFF,
2862     create_includes_dir_config, /* dir config creater */
2863     NULL,                       /* dir merger --- default is to override */
2864     NULL,                       /* server config */
2865     NULL,                       /* merge server config */
2866     includes_cmds,              /* command apr_table_t */
2867     register_hooks              /* register hooks */
2868 };