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