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