]> granicus.if.org Git - apache/blob - modules/filters/mod_include.c
Cleaning up a _Security_ concern - Please Review Carefully
[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  * sub key may be anything a Perl*Handler can be:
68  * subroutine name, package name (defaults to package::handler),
69  * Class->method call or anoymous sub {}
70  *
71  * Child <!--#perl sub="sub {print $$}" --> accessed
72  * <!--#perl sub="sub {print ++$Access::Cnt }" --> times. <br>
73  *
74  * <!--#perl arg="one" sub="mymod::includer" -->
75  *
76  * -Doug MacEachern
77  */
78
79 #define CORE_PRIVATE
80
81 #ifdef USE_PERL_SSI
82 #include "config.h"
83 #undef VOIDUSED
84 #ifdef USE_SFIO
85 #undef USE_SFIO
86 #define USE_STDIO
87 #endif
88 #include "modules/perl/mod_perl.h"
89 #else
90 #include "apr_strings.h"
91 #include "ap_config.h"
92 #include "util_filter.h"
93 #include "httpd.h"
94 #include "http_config.h"
95 #include "http_request.h"
96 #include "http_core.h"
97 #include "http_protocol.h"
98 #include "http_log.h"
99 #include "http_main.h"
100 #include "util_script.h"
101 #include "http_core.h"
102 #ifdef HAVE_STRING_H
103 #include <string.h>
104 #endif
105 #ifdef HAVE_STRINGS_H
106 #include <strings.h>
107 #endif
108 #ifdef HAVE_PWD_H
109 #include <pwd.h>
110 #endif
111 #endif
112 #include "util_ebcdic.h"
113
114 #define STARTING_SEQUENCE "<!--#"
115 #define ENDING_SEQUENCE "-->"
116
117 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
118 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
119 #define SIZEFMT_BYTES 0
120 #define SIZEFMT_KMG 1
121 #ifdef CHARSET_EBCDIC
122 #define RAW_ASCII_CHAR(ch)  apr_xlate_conv_byte(ap_hdrs_from_ascii, (unsigned char)ch)
123 #else /*CHARSET_EBCDIC*/
124 #define RAW_ASCII_CHAR(ch)  (ch)
125 #endif /*CHARSET_EBCDIC*/
126
127 module MODULE_VAR_EXPORT includes_module;
128
129 /* just need some arbitrary non-NULL pointer which can't also be a request_rec */
130 #define NESTED_INCLUDE_MAGIC    (&includes_module)
131
132 /* TODO: changing directory should be handled by CreateProcess */
133 #define ap_chdir_file(x) do {} while(0)
134
135 /* ------------------------ Environment function -------------------------- */
136
137 /* XXX: could use ap_table_overlap here */
138 static void add_include_vars(request_rec *r, char *timefmt)
139 {
140 #ifndef WIN32
141     struct passwd *pw;
142 #endif /* ndef WIN32 */
143     apr_table_t *e = r->subprocess_env;
144     char *t;
145     apr_time_t date = r->request_time;
146
147     apr_table_setn(e, "DATE_LOCAL", ap_ht_time(r->pool, date, timefmt, 0));
148     apr_table_setn(e, "DATE_GMT", ap_ht_time(r->pool, date, timefmt, 1));
149     apr_table_setn(e, "LAST_MODIFIED",
150               ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0));
151     apr_table_setn(e, "DOCUMENT_URI", r->uri);
152     apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
153 #ifndef WIN32
154     pw = getpwuid(r->finfo.user);
155     if (pw) {
156         apr_table_setn(e, "USER_NAME", apr_pstrdup(r->pool, pw->pw_name));
157     }
158     else {
159         apr_table_setn(e, "USER_NAME", apr_psprintf(r->pool, "user#%lu",
160                     (unsigned long) r->finfo.user));
161     }
162 #endif /* ndef WIN32 */
163
164     if ((t = strrchr(r->filename, '/'))) {
165         apr_table_setn(e, "DOCUMENT_NAME", ++t);
166     }
167     else {
168         apr_table_setn(e, "DOCUMENT_NAME", r->uri);
169     }
170     if (r->args) {
171         char *arg_copy = apr_pstrdup(r->pool, r->args);
172
173         ap_unescape_url(arg_copy);
174         apr_table_setn(e, "QUERY_STRING_UNESCAPED",
175                   ap_escape_shell_cmd(r->pool, arg_copy));
176     }
177 }
178
179
180
181 /* --------------------------- Parser functions --------------------------- */
182
183 #define OUTBUFSIZE 4096
184
185 static ap_bucket *find_string(ap_bucket *dptr, const char *str, ap_bucket *end)
186 {
187     apr_ssize_t len;
188     const char *c;
189     const char *buf;
190     int state = 0;
191
192     do {
193         if (AP_BUCKET_IS_EOS(dptr)) {
194             break;
195         }
196         ap_bucket_read(dptr, &buf, &len, 0);
197         /* XXX handle retcodes */
198         if (len == 0) { /* end of pipe? */
199             break;
200         }
201         c = buf;
202         while (c - buf != len) {
203             if (*c == str[state]) {
204                 state++;
205             }
206             else {
207                 if (str[state] == '\0') {
208                     /* We want to split the bucket at the '<' and '>' 
209                      * respectively.  That means adjusting where we split based
210                      * on what we are searching for.
211                      */
212                     if (str[0] == '<') {
213                         ap_bucket_split(dptr, c - buf - strlen(str));
214                     }
215                     else {
216                         ap_bucket_split(dptr, c - buf);
217                     }
218                     return AP_BUCKET_NEXT(dptr);
219                 }
220                 else {
221                     state = 0;
222                     /* The reason for this, is that we need to make sure 
223                      * that we catch cases like <<--#.  This makes the 
224                      * second check after the original check fails.
225                      */
226                      if (*c == buf[state]) {
227                          state++;
228                      }
229                 }
230             }
231             c++;
232         }
233         dptr = AP_BUCKET_NEXT(dptr);
234     } while (AP_BUCKET_PREV(dptr) != end);
235     return NULL;
236 }
237
238 /*
239  * decodes a string containing html entities or numeric character references.
240  * 's' is overwritten with the decoded string.
241  * If 's' is syntatically incorrect, then the followed fixups will be made:
242  *   unknown entities will be left undecoded;
243  *   references to unused numeric characters will be deleted.
244  *   In particular, &#00; will not be decoded, but will be deleted.
245  *
246  * drtr
247  */
248
249 /* maximum length of any ISO-LATIN-1 HTML entity name. */
250 #define MAXENTLEN (6)
251
252 /* The following is a shrinking transformation, therefore safe. */
253
254 static void decodehtml(char *s)
255 {
256     int val, i, j;
257     char *p = s;
258     const char *ents;
259     static const char * const entlist[MAXENTLEN + 1] =
260     {
261         NULL,                   /* 0 */
262         NULL,                   /* 1 */
263         "lt\074gt\076",         /* 2 */
264         "amp\046ETH\320eth\360",        /* 3 */
265         "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
266 iuml\357ouml\366uuml\374yuml\377",      /* 4 */
267         "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
268 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
269 ucirc\373thorn\376",            /* 5 */
270         "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
271 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
272 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
273 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
274 otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
275     };
276
277     for (; *s != '\0'; s++, p++) {
278         if (*s != '&') {
279             *p = *s;
280             continue;
281         }
282         /* find end of entity */
283         for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
284             continue;
285         }
286
287         if (s[i] == '\0') {     /* treat as normal data */
288             *p = *s;
289             continue;
290         }
291
292         /* is it numeric ? */
293         if (s[1] == '#') {
294             for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
295                 val = val * 10 + s[j] - '0';
296             }
297             s += i;
298             if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
299                 (val >= 127 && val <= 160) || val >= 256) {
300                 p--;            /* no data to output */
301             }
302             else {
303                 *p = RAW_ASCII_CHAR(val);
304             }
305         }
306         else {
307             j = i - 1;
308             if (j > MAXENTLEN || entlist[j] == NULL) {
309                 /* wrong length */
310                 *p = '&';
311                 continue;       /* skip it */
312             }
313             for (ents = entlist[j]; *ents != '\0'; ents += i) {
314                 if (strncmp(s + 1, ents, j) == 0) {
315                     break;
316                 }
317             }
318
319             if (*ents == '\0') {
320                 *p = '&';       /* unknown */
321             }
322             else {
323                 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
324                 s += i;
325             }
326         }
327     }
328
329     *p = '\0';
330 }
331
332 /*
333  * extract the next tag name and value.
334  * if there are no more tags, set the tag name to 'done'
335  * the tag value is html decoded if dodecode is non-zero
336  */
337
338 static char *get_tag(apr_pool_t *p, ap_bucket *in, char *tag, int tagbuf_len, int dodecode, apr_off_t *offset)
339 {
340     ap_bucket *dptr = in;
341     const char *c;
342     const char *str;
343     apr_ssize_t length; 
344     char *t = tag, *tag_val, term;
345
346     /* makes code below a little less cluttered */
347     --tagbuf_len;
348
349     /* Remove all whitespace */
350     while (dptr) { 
351         ap_bucket_read(dptr, &str, &length, 0);
352         c = str + *offset;
353         while (c - str < length) {
354             if (!apr_isspace(*c)) {
355                 break;
356             }
357         }
358         if (!apr_isspace(*c)) {
359             break;
360         }
361         dptr = AP_BUCKET_NEXT(dptr);
362     }
363
364     /* tags can't start with - */
365     if (*c == '-') {
366         c++;
367         if (c == '\0') {
368             ap_bucket_read(dptr, &str, &length, 0);
369             c = str;
370         }
371         if (*c == '-') {
372             do {
373                 c++;
374                 if (c == '\0') {
375                     ap_bucket_read(dptr, &str, &length, 0);
376                     c = str;
377                 }
378             } while (apr_isspace(*c));
379             if (*c == '>') {
380                 apr_cpystrn(tag, "done", tagbuf_len);
381                 *offset = c - str;
382                 return tag;
383             }
384         }
385         return NULL;            /* failed */
386     }
387
388     /* find end of tag name */
389     while (1) {
390         if (t - tag == tagbuf_len) {
391             *t = '\0';
392             return NULL;
393         }
394         if (*c == '=' || apr_isspace(*c)) {
395             break;
396         }
397         *(t++) = apr_tolower(*c);
398         c++;
399         if (c == '\0') {
400             ap_bucket_read(dptr, &str, &length, 0);
401             c = str;
402         }
403     }
404
405     *t++ = '\0';
406     tag_val = t;
407
408     while (apr_isspace(*c)) {
409         c++;
410         if (c == '\0') {
411             ap_bucket_read(dptr, &str, &length, 0);
412             c = str;
413         }
414     }
415     if (*c != '=') {
416         /* XXX may need to ungetc() here (see pre-bucketized code) */
417         return NULL;
418     }
419
420     do {
421         c++;
422         if (c == '\0') {
423             ap_bucket_read(dptr, &str, &length, 0);
424             c = str;
425         }
426     } while (apr_isspace(*c));
427
428     /* we should allow a 'name' as a value */
429
430     if (*c != '"' && *c != '\'') {
431         return NULL;
432     }
433     term = *c;
434     while (1) {
435         c++;
436         if (c == '\0') {
437             ap_bucket_read(dptr, &str, &length, 0);
438             c = str;
439         }
440         if (t - tag == tagbuf_len) {
441             *t = '\0';
442             return NULL;
443         }
444 /* Want to accept \" as a valid character within a string. */
445         if (*c == '\\') {
446             *(t++) = *c;         /* Add backslash */
447             c++;
448             if (c == '\0') {
449                 ap_bucket_read(dptr, &str, &length, 0);
450                 c = str;
451             }
452             if (*c == term) {    /* Only if */
453                 *(--t) = *c;     /* Replace backslash ONLY for terminator */
454             }
455         }
456         else if (*c == term) {
457             break;
458         }
459         *(t++) = *c;
460     }
461     *t = '\0';
462     if (dodecode) {
463         decodehtml(tag_val);
464     }
465     *offset = c - str;
466     return apr_pstrdup(p, tag_val);
467 }
468
469 static int get_directive(ap_bucket *in, char *dest, size_t len, apr_pool_t *p)
470 {
471     ap_bucket *dptr = in;
472     char *d = dest;
473     const char *c;
474     const char *str;
475     apr_ssize_t length; 
476
477     /* make room for nul terminator */
478     --len;
479
480     while (dptr) {
481         ap_bucket_read(dptr, &str, &length, 0);
482         /* need to start past the <!--#
483          */
484         c = str + strlen(STARTING_SEQUENCE);
485         while (c - str < length) {
486             if (!apr_isspace(*c)) {
487                 break;
488             }
489         }
490         if (!apr_isspace(*c)) {
491             break;
492         }
493         dptr = AP_BUCKET_NEXT(dptr);
494     }
495
496     /* now get directive */
497     while (dptr) {
498         if (c - str >= length) {
499             ap_bucket_read(dptr, &str, &length, 0);
500         }
501         while (c - str < length) {
502             if (d - dest == (int)len) {
503                 return 1;
504             }
505             *d++ = apr_tolower(*c);
506             c++;
507             if (apr_isspace(*c)) {
508                 break;
509             }
510         }
511         if (apr_isspace(*c)) {
512             break;
513         }
514         dptr = AP_BUCKET_NEXT(dptr);
515     }
516     *d = '\0';
517     return 0;
518 }
519
520 /*
521  * Do variable substitution on strings
522  */
523 static void parse_string(request_rec *r, const char *in, char *out,
524                         size_t length, int leave_name)
525 {
526     char ch;
527     char *next = out;
528     char *end_out;
529
530     /* leave room for nul terminator */
531     end_out = out + length - 1;
532
533     while ((ch = *in++) != '\0') {
534         switch (ch) {
535         case '\\':
536             if (next == end_out) {
537                 /* truncated */
538                 *next = '\0';
539                 return;
540             }
541             if (*in == '$') {
542                 *next++ = *in++;
543             }
544             else {
545                 *next++ = ch;
546             }
547             break;
548         case '$':
549             {
550                 char var[MAX_STRING_LEN];
551                 const char *start_of_var_name;
552                 const char *end_of_var_name;    /* end of var name + 1 */
553                 const char *expansion;
554                 const char *val;
555                 size_t l;
556
557                 /* guess that the expansion won't happen */
558                 expansion = in - 1;
559                 if (*in == '{') {
560                     ++in;
561                     start_of_var_name = in;
562                     in = ap_strchr_c(in, '}');
563                     if (in == NULL) {
564                         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
565                                     0, r, "Missing '}' on variable \"%s\"",
566                                     expansion);
567                         *next = '\0';
568                         return;
569                     }
570                     end_of_var_name = in;
571                     ++in;
572                 }
573                 else {
574                     start_of_var_name = in;
575                     while (apr_isalnum(*in) || *in == '_') {
576                         ++in;
577                     }
578                     end_of_var_name = in;
579                 }
580                 /* what a pain, too bad there's no table_getn where you can
581                  * pass a non-nul terminated string */
582                 l = end_of_var_name - start_of_var_name;
583                 if (l != 0) {
584                     l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l;
585                     memcpy(var, start_of_var_name, l);
586                     var[l] = '\0';
587
588                     val = apr_table_get(r->subprocess_env, var);
589                     if (val) {
590                         expansion = val;
591                         l = strlen(expansion);
592                     }
593                     else if (leave_name) {
594                         l = in - expansion;
595                     }
596                     else {
597                         break;  /* no expansion to be done */
598                     }
599                 }
600                 else {
601                     /* zero-length variable name causes just the $ to be copied */
602                     l = 1;
603                 }
604                 l = ((int)l > end_out - next) ? (end_out - next) : l;
605                 memcpy(next, expansion, l);
606                 next += l;
607                 break;
608             }
609         default:
610             if (next == end_out) {
611                 /* truncated */
612                 *next = '\0';
613                 return;
614             }
615             *next++ = ch;
616             break;
617         }
618     }
619     *next = '\0';
620     return;
621 }
622
623 /* --------------------------- Action handlers ---------------------------- */
624
625 static int include_cgi(char *s, request_rec *r, ap_filter_t *next)
626 {
627     request_rec *rr = ap_sub_req_lookup_uri(s, r);
628     int rr_status;
629
630     if (rr->status != HTTP_OK) {
631         return -1;
632     }
633
634     /* No hardwired path info or query allowed */
635
636     if ((rr->path_info && rr->path_info[0]) || rr->args) {
637         return -1;
638     }
639     if (rr->finfo.protection == 0) {
640         return -1;
641     }
642
643     /* Script gets parameters of the *document*, for back compatibility */
644
645     rr->path_info = r->path_info;       /* hard to get right; see mod_cgi.c */
646     rr->args = r->args;
647
648     /* Force sub_req to be treated as a CGI request, even if ordinary
649      * typing rules would have called it something else.
650      */
651
652     rr->content_type = CGI_MAGIC_TYPE;
653
654     /* The subrequest should inherit the remaining filters from this request. */
655     rr->output_filters = next;
656
657     /* Run it. */
658
659     rr_status = ap_run_sub_req(rr);
660     if (ap_is_HTTP_REDIRECT(rr_status)) {
661         const char *location = apr_table_get(rr->headers_out, "Location");
662         location = ap_escape_html(rr->pool, location);
663         ap_rvputs(r, "<A HREF=\"", location, "\">", location, "</A>", NULL);
664     }
665
666     ap_destroy_sub_req(rr);
667     ap_chdir_file(r->filename);
668
669     return 0;
670 }
671
672 /* ensure that path is relative, and does not contain ".." elements
673  * ensentially ensure that it does not match the regex:
674  * (^/|(^|/)\.\.(/|$))
675  * XXX: Needs to become apr_is_path_relative() test
676  */
677 static int is_only_below(const char *path)
678 {
679 #ifdef HAVE_DRIVE_LETTERS
680     if (path[1] == ':') 
681         return 0;
682 #endif
683 #ifdef NETWARE
684     if (strchr(path, ':')
685         return 0;
686 #endif
687     if (path[0] == '/') {
688         return 0;
689     }
690     while (*path) {
691         int dots = 0;
692         while (path[dots] == '.')
693             ++dots;
694 #if defined(WIN32) 
695         /* If the name is canonical this is redundant
696          * but in security, redundancy is worthwhile.
697          * Does OS2 belong here (accepts ... for ..)?
698          */
699         if (dots > 1 && (!path[dots] || path[dots] == '/'))
700             return 0;
701 #else
702         if (dots == 2 && (!path[dots] || path[dots] == '/'))
703             return 0;
704 #endif
705         path += dots;
706         while (*path && *(path++) != '/')
707             ++path;
708     }
709     return 1;
710 }
711
712 static int handle_include(ap_bucket *in, request_rec *r, ap_filter_t *next,
713                           const char *error, int noexec)
714 {
715     char tag[MAX_STRING_LEN];
716     char parsed_string[MAX_STRING_LEN];
717     char *tag_val;
718     apr_off_t offset = strlen("include ") + strlen(STARTING_SEQUENCE);
719
720     while (1) {
721         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
722             return 1;
723         }
724         if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
725             request_rec *rr = NULL;
726             char *error_fmt = NULL;
727
728             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
729             if (tag[0] == 'f') {
730                 /* be safe; only files in this directory or below allowed */
731                 if (!is_only_below(parsed_string)) {
732                     error_fmt = "unable to include file \"%s\" "
733                                 "in parsed file %s";
734                 }
735                 else {
736                     rr = ap_sub_req_lookup_file(parsed_string, r);
737                 }
738             }
739             else {
740                 rr = ap_sub_req_lookup_uri(parsed_string, r);
741             }
742
743             if (!error_fmt && rr->status != HTTP_OK) {
744                 error_fmt = "unable to include \"%s\" in parsed file %s";
745             }
746
747             if (!error_fmt && noexec && rr->content_type
748                 && (strncmp(rr->content_type, "text/", 5))) {
749                 error_fmt = "unable to include potential exec \"%s\" "
750                     "in parsed file %s";
751             }
752             if (error_fmt == NULL) {
753                 /* try to avoid recursive includes.  We do this by walking
754                  * up the r->main list of subrequests, and at each level
755                  * walking back through any internal redirects.  At each
756                  * step, we compare the filenames and the URIs.  
757                  *
758                  * The filename comparison catches a recursive include
759                  * with an ever-changing URL, eg.
760                  * <!--#include virtual=
761                  *      "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x"-->
762                  * which, although they would eventually be caught because
763                  * we have a limit on the length of files, etc., can 
764                  * recurse for a while.
765                  *
766                  * The URI comparison catches the case where the filename
767                  * is changed while processing the request, so the 
768                  * current name is never the same as any previous one.
769                  * This can happen with "DocumentRoot /foo" when you
770                  * request "/" on the server and it includes "/".
771                  * This only applies to modules such as mod_dir that 
772                  * (somewhat improperly) mess with r->filename outside 
773                  * of a filename translation phase.
774                  */
775                 int founddupe = 0;
776                 request_rec *p;
777                 for (p = r; p != NULL && !founddupe; p = p->main) {
778                     request_rec *q;
779                     for (q = p; q != NULL; q = q->prev) {
780                         if ( (strcmp(q->filename, rr->filename) == 0) ||
781                              (strcmp(q->uri, rr->uri) == 0) ){
782                             founddupe = 1;
783                             break;
784                         }
785                     }
786                 }
787
788                 if (p != NULL) {
789                     error_fmt = "Recursive include of \"%s\" "
790                         "in parsed file %s";
791                 }
792             }
793
794             /* see the Kludge in send_parsed_file for why */
795             if (rr) 
796                 ap_set_module_config(rr->request_config, &includes_module, r);
797
798             if (!error_fmt) {
799                 /* The subrequest should inherit the remaining filters from 
800                  * this request. */
801                 rr->output_filters = next;
802                 if (ap_run_sub_req(rr)) {
803                     error_fmt = "unable to include \"%s\" in parsed file %s";
804                 }
805             }
806             ap_chdir_file(r->filename);
807             if (error_fmt) {
808                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
809                             0, r, error_fmt, tag_val, r->filename);
810                 ap_rputs(error, r);
811             }
812
813             /* destroy the sub request if it's not a nested include */
814             if (rr != NULL
815                 && ap_get_module_config(rr->request_config, &includes_module)
816                     != NESTED_INCLUDE_MAGIC) {
817                 ap_destroy_sub_req(rr);
818             }
819         }
820         else if (!strcmp(tag, "done")) {
821             return 0;
822         }
823         else {
824             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
825                         "unknown parameter \"%s\" to tag include in %s",
826                         tag, r->filename);
827             ap_rputs(error, r);
828         }
829     }
830 }
831
832 typedef struct {
833 #ifdef TPF
834     TPF_FORK_CHILD t;
835 #endif
836     request_rec *r;
837     char *s;
838 } include_cmd_arg;
839
840
841
842 static apr_status_t build_argv_list(char ***argv, request_rec *r, apr_pool_t *p)
843 {
844     int numwords, x, idx;
845     char *w;
846     const char *args = r->args;
847
848     if (!args || !args[0] || ap_strchr_c(args, '=')) {
849        numwords = 1;
850     }
851     else {
852         /* count the number of keywords */
853         for (x = 0, numwords = 1; args[x]; x++) {
854             if (args[x] == '+') {
855                 ++numwords;
856             }
857         }
858     }
859     /* Everything is - 1 to account for the first parameter which is the
860      * program name.  We didn't used to have to do this, but APR wants it.
861      */
862     if (numwords > APACHE_ARG_MAX - 1) {
863         numwords = APACHE_ARG_MAX - 1;  /* Truncate args to prevent overrun */
864     }
865     *argv = (char **) apr_palloc(p, (numwords + 2) * sizeof(char *));
866  
867     for (x = 1, idx = 1; x < numwords; x++) {
868         w = ap_getword_nulls(p, &args, '+');
869         ap_unescape_url(w);
870         (*argv)[idx++] = ap_escape_shell_cmd(p, w);
871     }
872     (*argv)[idx] = NULL;
873
874     return APR_SUCCESS;
875 }
876
877
878
879 static int include_cmd(char *s, request_rec *r, ap_filter_t *next)
880 {
881     include_cmd_arg arg;
882     apr_procattr_t *procattr;
883     apr_proc_t *procnew;
884     apr_status_t rc;
885     apr_table_t *env = r->subprocess_env;
886     char **argv;
887     apr_file_t *file = NULL;
888 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
889     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
890     core_dir_config *conf; 
891     conf = (core_dir_config *) ap_get_module_config(r->per_dir_config,
892                                                     &core_module);
893 #endif
894
895     arg.r = r;
896     arg.s = s;
897 #ifdef TPF
898     arg.t.filename = r->filename;
899     arg.t.subprocess_env = r->subprocess_env;
900     arg.t.prog_type = FORK_FILE;
901 #endif
902
903     if (r->path_info && r->path_info[0] != '\0') {
904         request_rec *pa_req;
905
906         apr_table_setn(env, "PATH_INFO", ap_escape_shell_cmd(r->pool, r->path_info));
907
908         pa_req = ap_sub_req_lookup_uri(ap_escape_uri(r->pool, r->path_info), r);
909         if (pa_req->filename) {
910             apr_table_setn(env, "PATH_TRANSLATED",
911                       apr_pstrcat(r->pool, pa_req->filename, pa_req->path_info,
912                               NULL));
913         }
914     }
915
916     if (r->args) {
917         char *arg_copy = apr_pstrdup(r->pool, r->args);
918
919         apr_table_setn(env, "QUERY_STRING", r->args);
920         ap_unescape_url(arg_copy);
921         apr_table_setn(env, "QUERY_STRING_UNESCAPED",
922                   ap_escape_shell_cmd(r->pool, arg_copy));
923     }
924
925     if ((apr_createprocattr_init(&procattr, r->pool) != APR_SUCCESS) ||
926         (apr_setprocattr_io(procattr, APR_NO_PIPE, 
927                            APR_FULL_BLOCK, APR_NO_PIPE) != APR_SUCCESS) ||
928         (apr_setprocattr_dir(procattr, ap_make_dirstr_parent(r->pool, r->filename)) != APR_SUCCESS) ||
929 #ifdef RLIMIT_CPU
930         ((rc = apr_setprocattr_limit(procattr, APR_LIMIT_CPU, conf->limit_cpu)) != APR_SUCCESS) ||
931 #endif
932 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
933         ((rc = apr_setprocattr_limit(procattr, APR_LIMIT_MEM, conf->limit_mem)) != APR_SUCCESS) ||
934 #endif
935 #ifdef RLIMIT_NPROC
936         ((rc = apr_setprocattr_limit(procattr, APR_LIMIT_NPROC, conf->limit_nproc)) != APR_SUCCESS) ||
937 #endif
938         (apr_setprocattr_cmdtype(procattr, APR_SHELLCMD) != APR_SUCCESS)) {
939         /* Something bad happened, tell the world. */
940         ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
941             "couldn't initialize proc attributes: %s %s", r->filename, s);
942         rc = !APR_SUCCESS;
943     }
944     else {
945         build_argv_list(&argv, r, r->pool);
946         argv[0] = apr_pstrdup(r->pool, s);
947         procnew = apr_pcalloc(r->pool, sizeof(*procnew));
948         rc = apr_create_process(procnew, s, argv, ap_create_environment(r->pool, env), procattr, r->pool);
949
950         if (rc != APR_SUCCESS) {
951             /* Bad things happened. Everyone should have cleaned up. */
952             ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
953                         "couldn't create child process: %d: %s", rc, s);
954         }
955         else {
956             ap_bucket_brigade *bcgi;
957             ap_bucket *b;
958
959             apr_note_subprocess(r->pool, procnew, kill_after_timeout);
960             /* Fill in BUFF structure for parents pipe to child's stdout */
961             file = procnew->out;
962             if (!file)
963                 return APR_EBADF;
964             bcgi = ap_brigade_create(r->pool);
965             b = ap_bucket_create_pipe(file);
966             AP_BRIGADE_INSERT_TAIL(bcgi, b);
967             ap_pass_brigade(next, bcgi);
968         
969             /* We can't close the pipe here, because we may return before the
970              * full CGI has been sent to the network.  That's okay though,
971              * because we can rely on the pool to close the pipe for us.
972              */
973         }
974     }
975
976     return 0;
977 }
978
979 static int handle_exec(ap_bucket *in, request_rec *r, const char *error,
980                        ap_filter_t *next)
981 {
982     char tag[MAX_STRING_LEN];
983     char *tag_val;
984     char *file = r->filename;
985     char parsed_string[MAX_STRING_LEN];
986     apr_off_t offset = strlen("exec ") + strlen(STARTING_SEQUENCE);
987
988     while (1) {
989         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
990             return 1;
991         }
992         if (!strcmp(tag, "cmd")) {
993             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
994             if (include_cmd(parsed_string, r, next) == -1) {
995                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
996                             "execution failure for parameter \"%s\" "
997                             "to tag exec in file %s",
998                             tag, r->filename);
999                 ap_rputs(error, r);
1000             }
1001             /* just in case some stooge changed directories */
1002             ap_chdir_file(r->filename);
1003         }
1004         else if (!strcmp(tag, "cgi")) {
1005             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1006             if (include_cgi(parsed_string, r, next) == -1) {
1007                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1008                             "invalid CGI ref \"%s\" in %s", tag_val, file);
1009                 ap_rputs(error, r);
1010             }
1011             ap_chdir_file(r->filename);
1012         }
1013         else if (!strcmp(tag, "done")) {
1014             return 0;
1015         }
1016         else {
1017             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1018                         "unknown parameter \"%s\" to tag exec in %s",
1019                         tag, file);
1020             ap_rputs(error, r);
1021         }
1022     }
1023
1024 }
1025
1026 static int handle_echo(ap_bucket *in, request_rec *r, const char *error)
1027 {
1028     char tag[MAX_STRING_LEN];
1029     char *tag_val;
1030     enum {E_NONE, E_URL, E_ENTITY} encode;
1031     apr_off_t offset = strlen("echo ") + strlen(STARTING_SEQUENCE);
1032
1033     encode = E_ENTITY;
1034
1035     while (1) {
1036         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
1037             return 1;
1038         }
1039         if (!strcmp(tag, "var")) {
1040             const char *val = apr_table_get(r->subprocess_env, tag_val);
1041
1042             if (val) {
1043                 if (encode == E_NONE) {
1044                     ap_rputs(val, r);
1045                 }
1046                 else if (encode == E_URL) {
1047                     ap_rputs(ap_escape_uri(r->pool, val), r);
1048                 }
1049                 else if (encode == E_ENTITY) {
1050                     ap_rputs(ap_escape_html(r->pool, val), r);
1051                 }
1052             }
1053             else {
1054                 ap_rputs("(none)", r);
1055             }
1056         }
1057         else if (!strcmp(tag, "done")) {
1058             return 0;
1059         }
1060         else if (!strcmp(tag, "encoding")) {
1061             if (!strcasecmp(tag_val, "none")) encode = E_NONE;
1062             else if (!strcasecmp(tag_val, "url")) encode = E_URL;
1063             else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY;
1064             else {
1065                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1066                             "unknown value \"%s\" to parameter \"encoding\" of "
1067                             "tag echo in %s",
1068                             tag_val, r->filename);
1069                 ap_rputs(error, r);
1070             }
1071         }
1072
1073         else {
1074             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1075                         "unknown parameter \"%s\" to tag echo in %s",
1076                         tag, r->filename);
1077             ap_rputs(error, r);
1078         }
1079     }
1080 }
1081
1082 #ifdef USE_PERL_SSI
1083 static int handle_perl(ap_bucket *in, request_rec *r, const char *error)
1084 {
1085     char tag[MAX_STRING_LEN];
1086     char parsed_string[MAX_STRING_LEN];
1087     char *tag_val;
1088     SV *sub = Nullsv;
1089     AV *av = newAV();
1090     apr_off_t offset = strlen("perl ") + strlen(STARTING_SEQUENCE);
1091
1092     if (ap_allow_options(r) & OPT_INCNOEXEC) {
1093         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1094                       "#perl SSI disallowed by IncludesNoExec in %s",
1095                       r->filename);
1096         return DECLINED;
1097     }
1098     while (1) {
1099         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
1100             break;
1101         }
1102         if (strnEQ(tag, "sub", 3)) {
1103             sub = newSVpv(tag_val, 0);
1104         }
1105         else if (strnEQ(tag, "arg", 3)) {
1106             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1107             av_push(av, newSVpv(parsed_string, 0));
1108         }
1109         else if (strnEQ(tag, "done", 4)) {
1110             break;
1111         }
1112     }
1113     perl_stdout2client(r);
1114     perl_setup_env(r);
1115     perl_call_handler(sub, r, av);
1116     return OK;
1117 }
1118 #endif
1119
1120 /* error and tf must point to a string with room for at 
1121  * least MAX_STRING_LEN characters 
1122  */
1123 static int handle_config(ap_bucket *in, request_rec *r, char *error, char *tf,
1124                          int *sizefmt)
1125 {
1126     char tag[MAX_STRING_LEN];
1127     char *tag_val;
1128     char parsed_string[MAX_STRING_LEN];
1129     apr_table_t *env = r->subprocess_env;
1130     apr_off_t offset = strlen("config ") + strlen(STARTING_SEQUENCE);
1131
1132     while (1) {
1133         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset))) {
1134             return 1;
1135         }
1136         if (!strcmp(tag, "errmsg")) {
1137             parse_string(r, tag_val, error, MAX_STRING_LEN, 0);
1138         }
1139         else if (!strcmp(tag, "timefmt")) {
1140             apr_time_t date = r->request_time;
1141
1142             parse_string(r, tag_val, tf, MAX_STRING_LEN, 0);
1143             apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, tf, 0));
1144             apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, tf, 1));
1145             apr_table_setn(env, "LAST_MODIFIED",
1146                       ap_ht_time(r->pool, r->finfo.mtime, tf, 0));
1147         }
1148         else if (!strcmp(tag, "sizefmt")) {
1149             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1150             decodehtml(parsed_string);
1151             if (!strcmp(parsed_string, "bytes")) {
1152                 *sizefmt = SIZEFMT_BYTES;
1153             }
1154             else if (!strcmp(parsed_string, "abbrev")) {
1155                 *sizefmt = SIZEFMT_KMG;
1156             }
1157         }
1158         else if (!strcmp(tag, "done")) {
1159             return 0;
1160         }
1161         else {
1162             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1163                         "unknown parameter \"%s\" to tag config in %s",
1164                         tag, r->filename);
1165             ap_rputs(error, r);
1166         }
1167     }
1168 }
1169
1170
1171 static int find_file(request_rec *r, const char *directive, const char *tag,
1172                      char *tag_val, apr_finfo_t *finfo, const char *error)
1173 {
1174     char *to_send = tag_val;
1175     request_rec *rr = NULL;
1176     int ret=0;
1177     char *error_fmt = NULL;
1178
1179     if (!strcmp(tag, "file")) {
1180         /* be safe; only files in this directory or below allowed */
1181         if (!is_only_below(tag_val)) {
1182             error_fmt = "unable to access file \"%s\" "
1183                         "in parsed file %s";
1184         }
1185         else {
1186             ap_getparents(tag_val);    /* get rid of any nasties */
1187             rr = ap_sub_req_lookup_file(tag_val, r);
1188
1189             if (rr->status == HTTP_OK && rr->finfo.protection != 0) {
1190                 to_send = rr->filename;
1191                 if (apr_stat(finfo, to_send, rr->pool) != APR_SUCCESS) {
1192                     error_fmt = "unable to get information about \"%s\" "
1193                         "in parsed file %s";
1194                 }
1195             }
1196             else {
1197                 error_fmt = "unable to lookup information about \"%s\" "
1198                             "in parsed file %s";
1199             }
1200         }
1201
1202         if (error_fmt) {
1203             ret = -1;
1204             /* TODO: pass APLOG_NOERRNO if no apr_stat() failure; pass rv from apr_stat()
1205              * otherwise
1206              */
1207             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, to_send, r->filename);
1208             ap_rputs(error, r);
1209         }
1210
1211         if (rr) ap_destroy_sub_req(rr);
1212         
1213         return ret;
1214     }
1215     else if (!strcmp(tag, "virtual")) {
1216         rr = ap_sub_req_lookup_uri(tag_val, r);
1217
1218         if (rr->status == HTTP_OK && rr->finfo.protection != 0) {
1219             memcpy((char *) finfo, (const char *) &rr->finfo,
1220                    sizeof(rr->finfo));
1221             ap_destroy_sub_req(rr);
1222             return 0;
1223         }
1224         else {
1225             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1226                         "unable to get information about \"%s\" "
1227                         "in parsed file %s",
1228                         tag_val, r->filename);
1229             ap_rputs(error, r);
1230             ap_destroy_sub_req(rr);
1231             return -1;
1232         }
1233     }
1234     else {
1235         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1236                     "unknown parameter \"%s\" to tag %s in %s",
1237                     tag, directive, r->filename);
1238         ap_rputs(error, r);
1239         return -1;
1240     }
1241 }
1242
1243
1244 static int handle_fsize(ap_bucket *in, request_rec *r, const char *error, int sizefmt)
1245 {
1246     char tag[MAX_STRING_LEN];
1247     char *tag_val;
1248     apr_finfo_t finfo;
1249     char parsed_string[MAX_STRING_LEN];
1250     apr_off_t offset = strlen("fsize ") + strlen(STARTING_SEQUENCE);
1251
1252     while (1) {
1253         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
1254             return 1;
1255         }
1256         else if (!strcmp(tag, "done")) {
1257             return 0;
1258         }
1259         else {
1260             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1261             if (!find_file(r, "fsize", tag, parsed_string, &finfo, error)) {
1262                 if (sizefmt == SIZEFMT_KMG) {
1263                     ap_send_size(finfo.size, r);
1264                 }
1265                 else {
1266                     int l, x;
1267                     apr_snprintf(tag, sizeof(tag), "%" APR_OFF_T_FMT, finfo.size);
1268                     l = strlen(tag);    /* grrr */
1269                     for (x = 0; x < l; x++) {
1270                         if (x && (!((l - x) % 3))) {
1271                             ap_rputc(',', r);
1272                         }
1273                         ap_rputc(tag[x], r);
1274                     }
1275                 }
1276             }
1277         }
1278     }
1279 }
1280
1281 static int handle_flastmod(ap_bucket *in, request_rec *r, const char *error, const char *tf)
1282 {
1283     char tag[MAX_STRING_LEN];
1284     char *tag_val;
1285     apr_finfo_t finfo;
1286     char parsed_string[MAX_STRING_LEN];
1287     apr_off_t offset = strlen("flastmod ") + strlen(STARTING_SEQUENCE);
1288
1289     while (1) {
1290         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
1291             return 1;
1292         }
1293         else if (!strcmp(tag, "done")) {
1294             return 0;
1295         }
1296         else {
1297             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
1298             if (!find_file(r, "flastmod", tag, parsed_string, &finfo, error)) {
1299                 ap_rputs(ap_ht_time(r->pool, finfo.mtime, tf, 0), r);
1300             }
1301         }
1302     }
1303 }
1304
1305 static int re_check(request_rec *r, char *string, char *rexp)
1306 {
1307     regex_t *compiled;
1308     int regex_error;
1309
1310     compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
1311     if (compiled == NULL) {
1312         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1313                     "unable to compile pattern \"%s\"", rexp);
1314         return -1;
1315     }
1316     regex_error = ap_regexec(compiled, string, 0, (regmatch_t *) NULL, 0);
1317     ap_pregfree(r->pool, compiled);
1318     return (!regex_error);
1319 }
1320
1321 enum token_type {
1322     token_string,
1323     token_and, token_or, token_not, token_eq, token_ne,
1324     token_rbrace, token_lbrace, token_group,
1325     token_ge, token_le, token_gt, token_lt
1326 };
1327 struct token {
1328     enum token_type type;
1329     char value[MAX_STRING_LEN];
1330 };
1331
1332 /* there is an implicit assumption here that string is at most MAX_STRING_LEN-1
1333  * characters long...
1334  */
1335 static const char *get_ptoken(request_rec *r, const char *string, struct token *token)
1336 {
1337     char ch;
1338     int next = 0;
1339     int qs = 0;
1340
1341     /* Skip leading white space */
1342     if (string == (char *) NULL) {
1343         return (char *) NULL;
1344     }
1345     while ((ch = *string++)) {
1346         if (!apr_isspace(ch)) {
1347             break;
1348         }
1349     }
1350     if (ch == '\0') {
1351         return (char *) NULL;
1352     }
1353
1354     token->type = token_string; /* the default type */
1355     switch (ch) {
1356     case '(':
1357         token->type = token_lbrace;
1358         return (string);
1359     case ')':
1360         token->type = token_rbrace;
1361         return (string);
1362     case '=':
1363         token->type = token_eq;
1364         return (string);
1365     case '!':
1366         if (*string == '=') {
1367             token->type = token_ne;
1368             return (string + 1);
1369         }
1370         else {
1371             token->type = token_not;
1372             return (string);
1373         }
1374     case '\'':
1375         token->type = token_string;
1376         qs = 1;
1377         break;
1378     case '|':
1379         if (*string == '|') {
1380             token->type = token_or;
1381             return (string + 1);
1382         }
1383         break;
1384     case '&':
1385         if (*string == '&') {
1386             token->type = token_and;
1387             return (string + 1);
1388         }
1389         break;
1390     case '>':
1391         if (*string == '=') {
1392             token->type = token_ge;
1393             return (string + 1);
1394         }
1395         else {
1396             token->type = token_gt;
1397             return (string);
1398         }
1399     case '<':
1400         if (*string == '=') {
1401             token->type = token_le;
1402             return (string + 1);
1403         }
1404         else {
1405             token->type = token_lt;
1406             return (string);
1407         }
1408     default:
1409         token->type = token_string;
1410         break;
1411     }
1412     /* We should only be here if we are in a string */
1413     if (!qs) {
1414         token->value[next++] = ch;
1415     }
1416
1417     /* 
1418      * Yes I know that goto's are BAD.  But, c doesn't allow me to
1419      * exit a loop from a switch statement.  Yes, I could use a flag,
1420      * but that is (IMHO) even less readable/maintainable than the goto.
1421      */
1422     /* 
1423      * I used the ++string throughout this section so that string
1424      * ends up pointing to the next token and I can just return it
1425      */
1426     for (ch = *string; ch != '\0'; ch = *++string) {
1427         if (ch == '\\') {
1428             if ((ch = *++string) == '\0') {
1429                 goto TOKEN_DONE;
1430             }
1431             token->value[next++] = ch;
1432             continue;
1433         }
1434         if (!qs) {
1435             if (apr_isspace(ch)) {
1436                 goto TOKEN_DONE;
1437             }
1438             switch (ch) {
1439             case '(':
1440                 goto TOKEN_DONE;
1441             case ')':
1442                 goto TOKEN_DONE;
1443             case '=':
1444                 goto TOKEN_DONE;
1445             case '!':
1446                 goto TOKEN_DONE;
1447             case '|':
1448                 if (*(string + 1) == '|') {
1449                     goto TOKEN_DONE;
1450                 }
1451                 break;
1452             case '&':
1453                 if (*(string + 1) == '&') {
1454                     goto TOKEN_DONE;
1455                 }
1456                 break;
1457             case '<':
1458                 goto TOKEN_DONE;
1459             case '>':
1460                 goto TOKEN_DONE;
1461             }
1462             token->value[next++] = ch;
1463         }
1464         else {
1465             if (ch == '\'') {
1466                 qs = 0;
1467                 ++string;
1468                 goto TOKEN_DONE;
1469             }
1470             token->value[next++] = ch;
1471         }
1472     }
1473   TOKEN_DONE:
1474     /* If qs is still set, I have an unmatched ' */
1475     if (qs) {
1476         ap_rputs("\nUnmatched '\n", r);
1477         next = 0;
1478     }
1479     token->value[next] = '\0';
1480     return (string);
1481 }
1482
1483
1484 /*
1485  * Hey I still know that goto's are BAD.  I don't think that I've ever
1486  * used two in the same project, let alone the same file before.  But,
1487  * I absolutely want to make sure that I clean up the memory in all
1488  * cases.  And, without rewriting this completely, the easiest way
1489  * is to just branch to the return code which cleans it up.
1490  */
1491 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
1492  * characters long...
1493  */
1494 static int parse_expr(request_rec *r, const char *expr, const char *error)
1495 {
1496     struct parse_node {
1497         struct parse_node *left, *right, *parent;
1498         struct token token;
1499         int value, done;
1500     }         *root, *current, *new;
1501     const char *parse;
1502     char buffer[MAX_STRING_LEN];
1503     apr_pool_t *expr_pool;
1504     int retval = 0;
1505
1506     if ((parse = expr) == (char *) NULL) {
1507         return (0);
1508     }
1509     root = current = (struct parse_node *) NULL;
1510     if (apr_create_pool(&expr_pool, r->pool) != APR_SUCCESS)
1511                 return 0;
1512
1513     /* Create Parse Tree */
1514     while (1) {
1515         new = (struct parse_node *) apr_palloc(expr_pool,
1516                                            sizeof(struct parse_node));
1517         new->parent = new->left = new->right = (struct parse_node *) NULL;
1518         new->done = 0;
1519         if ((parse = get_ptoken(r, parse, &new->token)) == (char *) NULL) {
1520             break;
1521         }
1522         switch (new->token.type) {
1523
1524         case token_string:
1525 #ifdef DEBUG_INCLUDE
1526             ap_rvputs(r, "     Token: string (", new->token.value, ")\n", NULL);
1527 #endif
1528             if (current == (struct parse_node *) NULL) {
1529                 root = current = new;
1530                 break;
1531             }
1532             switch (current->token.type) {
1533             case token_string:
1534                 if (current->token.value[0] != '\0') {
1535                     strncat(current->token.value, " ",
1536                          sizeof(current->token.value)
1537                             - strlen(current->token.value) - 1);
1538                 }
1539                 strncat(current->token.value, new->token.value,
1540                          sizeof(current->token.value)
1541                             - strlen(current->token.value) - 1);
1542                 current->token.value[sizeof(current->token.value) - 1] = '\0';
1543                 break;
1544             case token_eq:
1545             case token_ne:
1546             case token_and:
1547             case token_or:
1548             case token_lbrace:
1549             case token_not:
1550             case token_ge:
1551             case token_gt:
1552             case token_le:
1553             case token_lt:
1554                 new->parent = current;
1555                 current = current->right = new;
1556                 break;
1557             default:
1558                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1559                             "Invalid expression \"%s\" in file %s",
1560                             expr, r->filename);
1561                 ap_rputs(error, r);
1562                 goto RETURN;
1563             }
1564             break;
1565
1566         case token_and:
1567         case token_or:
1568 #ifdef DEBUG_INCLUDE
1569             ap_rputs("     Token: and/or\n", r);
1570 #endif
1571             if (current == (struct parse_node *) NULL) {
1572                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1573                             "Invalid expression \"%s\" in file %s",
1574                             expr, r->filename);
1575                 ap_rputs(error, r);
1576                 goto RETURN;
1577             }
1578             /* Percolate upwards */
1579             while (current != (struct parse_node *) NULL) {
1580                 switch (current->token.type) {
1581                 case token_string:
1582                 case token_group:
1583                 case token_not:
1584                 case token_eq:
1585                 case token_ne:
1586                 case token_and:
1587                 case token_or:
1588                 case token_ge:
1589                 case token_gt:
1590                 case token_le:
1591                 case token_lt:
1592                     current = current->parent;
1593                     continue;
1594                 case token_lbrace:
1595                     break;
1596                 default:
1597                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1598                                 "Invalid expression \"%s\" in file %s",
1599                                 expr, r->filename);
1600                     ap_rputs(error, r);
1601                     goto RETURN;
1602                 }
1603                 break;
1604             }
1605             if (current == (struct parse_node *) NULL) {
1606                 new->left = root;
1607                 new->left->parent = new;
1608                 new->parent = (struct parse_node *) NULL;
1609                 root = new;
1610             }
1611             else {
1612                 new->left = current->right;
1613                 current->right = new;
1614                 new->parent = current;
1615             }
1616             current = new;
1617             break;
1618
1619         case token_not:
1620 #ifdef DEBUG_INCLUDE
1621             ap_rputs("     Token: not\n", r);
1622 #endif
1623             if (current == (struct parse_node *) NULL) {
1624                 root = current = new;
1625                 break;
1626             }
1627             /* Percolate upwards */
1628             while (current != (struct parse_node *) NULL) {
1629                 switch (current->token.type) {
1630                 case token_not:
1631                 case token_eq:
1632                 case token_ne:
1633                 case token_and:
1634                 case token_or:
1635                 case token_lbrace:
1636                 case token_ge:
1637                 case token_gt:
1638                 case token_le:
1639                 case token_lt:
1640                     break;
1641                 default:
1642                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1643                                 "Invalid expression \"%s\" in file %s",
1644                                 expr, r->filename);
1645                     ap_rputs(error, r);
1646                     goto RETURN;
1647                 }
1648                 break;
1649             }
1650             if (current == (struct parse_node *) NULL) {
1651                 new->left = root;
1652                 new->left->parent = new;
1653                 new->parent = (struct parse_node *) NULL;
1654                 root = new;
1655             }
1656             else {
1657                 new->left = current->right;
1658                 current->right = new;
1659                 new->parent = current;
1660             }
1661             current = new;
1662             break;
1663
1664         case token_eq:
1665         case token_ne:
1666         case token_ge:
1667         case token_gt:
1668         case token_le:
1669         case token_lt:
1670 #ifdef DEBUG_INCLUDE
1671             ap_rputs("     Token: eq/ne/ge/gt/le/lt\n", r);
1672 #endif
1673             if (current == (struct parse_node *) NULL) {
1674                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1675                             "Invalid expression \"%s\" in file %s",
1676                             expr, r->filename);
1677                 ap_rputs(error, r);
1678                 goto RETURN;
1679             }
1680             /* Percolate upwards */
1681             while (current != (struct parse_node *) NULL) {
1682                 switch (current->token.type) {
1683                 case token_string:
1684                 case token_group:
1685                     current = current->parent;
1686                     continue;
1687                 case token_lbrace:
1688                 case token_and:
1689                 case token_or:
1690                     break;
1691                 case token_not:
1692                 case token_eq:
1693                 case token_ne:
1694                 case token_ge:
1695                 case token_gt:
1696                 case token_le:
1697                 case token_lt:
1698                 default:
1699                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1700                                 "Invalid expression \"%s\" in file %s",
1701                                 expr, r->filename);
1702                     ap_rputs(error, r);
1703                     goto RETURN;
1704                 }
1705                 break;
1706             }
1707             if (current == (struct parse_node *) NULL) {
1708                 new->left = root;
1709                 new->left->parent = new;
1710                 new->parent = (struct parse_node *) NULL;
1711                 root = new;
1712             }
1713             else {
1714                 new->left = current->right;
1715                 current->right = new;
1716                 new->parent = current;
1717             }
1718             current = new;
1719             break;
1720
1721         case token_rbrace:
1722 #ifdef DEBUG_INCLUDE
1723             ap_rputs("     Token: rbrace\n", r);
1724 #endif
1725             while (current != (struct parse_node *) NULL) {
1726                 if (current->token.type == token_lbrace) {
1727                     current->token.type = token_group;
1728                     break;
1729                 }
1730                 current = current->parent;
1731             }
1732             if (current == (struct parse_node *) NULL) {
1733                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1734                             "Unmatched ')' in \"%s\" in file %s",
1735                             expr, r->filename);
1736                 ap_rputs(error, r);
1737                 goto RETURN;
1738             }
1739             break;
1740
1741         case token_lbrace:
1742 #ifdef DEBUG_INCLUDE
1743             ap_rputs("     Token: lbrace\n", r);
1744 #endif
1745             if (current == (struct parse_node *) NULL) {
1746                 root = current = new;
1747                 break;
1748             }
1749             /* Percolate upwards */
1750             while (current != (struct parse_node *) NULL) {
1751                 switch (current->token.type) {
1752                 case token_not:
1753                 case token_eq:
1754                 case token_ne:
1755                 case token_and:
1756                 case token_or:
1757                 case token_lbrace:
1758                 case token_ge:
1759                 case token_gt:
1760                 case token_le:
1761                 case token_lt:
1762                     break;
1763                 case token_string:
1764                 case token_group:
1765                 default:
1766                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1767                                 "Invalid expression \"%s\" in file %s",
1768                                 expr, r->filename);
1769                     ap_rputs(error, r);
1770                     goto RETURN;
1771                 }
1772                 break;
1773             }
1774             if (current == (struct parse_node *) NULL) {
1775                 new->left = root;
1776                 new->left->parent = new;
1777                 new->parent = (struct parse_node *) NULL;
1778                 root = new;
1779             }
1780             else {
1781                 new->left = current->right;
1782                 current->right = new;
1783                 new->parent = current;
1784             }
1785             current = new;
1786             break;
1787         default:
1788             break;
1789         }
1790     }
1791
1792     /* Evaluate Parse Tree */
1793     current = root;
1794     while (current != (struct parse_node *) NULL) {
1795         switch (current->token.type) {
1796         case token_string:
1797 #ifdef DEBUG_INCLUDE
1798             ap_rputs("     Evaluate string\n", r);
1799 #endif
1800             parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
1801             apr_cpystrn(current->token.value, buffer, sizeof(current->token.value));
1802             current->value = (current->token.value[0] != '\0');
1803             current->done = 1;
1804             current = current->parent;
1805             break;
1806
1807         case token_and:
1808         case token_or:
1809 #ifdef DEBUG_INCLUDE
1810             ap_rputs("     Evaluate and/or\n", r);
1811 #endif
1812             if (current->left == (struct parse_node *) NULL ||
1813                 current->right == (struct parse_node *) NULL) {
1814                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1815                             "Invalid expression \"%s\" in file %s",
1816                             expr, r->filename);
1817                 ap_rputs(error, r);
1818                 goto RETURN;
1819             }
1820             if (!current->left->done) {
1821                 switch (current->left->token.type) {
1822                 case token_string:
1823                     parse_string(r, current->left->token.value,
1824                                  buffer, sizeof(buffer), 0);
1825                     apr_cpystrn(current->left->token.value, buffer,
1826                             sizeof(current->left->token.value));
1827                     current->left->value = (current->left->token.value[0] != '\0');
1828                     current->left->done = 1;
1829                     break;
1830                 default:
1831                     current = current->left;
1832                     continue;
1833                 }
1834             }
1835             if (!current->right->done) {
1836                 switch (current->right->token.type) {
1837                 case token_string:
1838                     parse_string(r, current->right->token.value,
1839                                  buffer, sizeof(buffer), 0);
1840                     apr_cpystrn(current->right->token.value, buffer,
1841                             sizeof(current->right->token.value));
1842                     current->right->value = (current->right->token.value[0] != '\0');
1843                     current->right->done = 1;
1844                     break;
1845                 default:
1846                     current = current->right;
1847                     continue;
1848                 }
1849             }
1850 #ifdef DEBUG_INCLUDE
1851             ap_rvputs(r, "     Left: ", current->left->value ? "1" : "0",
1852                    "\n", NULL);
1853             ap_rvputs(r, "     Right: ", current->right->value ? "1" : "0",
1854                    "\n", NULL);
1855 #endif
1856             if (current->token.type == token_and) {
1857                 current->value = current->left->value && current->right->value;
1858             }
1859             else {
1860                 current->value = current->left->value || current->right->value;
1861             }
1862 #ifdef DEBUG_INCLUDE
1863             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
1864                    "\n", NULL);
1865 #endif
1866             current->done = 1;
1867             current = current->parent;
1868             break;
1869
1870         case token_eq:
1871         case token_ne:
1872 #ifdef DEBUG_INCLUDE
1873             ap_rputs("     Evaluate eq/ne\n", r);
1874 #endif
1875             if ((current->left == (struct parse_node *) NULL) ||
1876                 (current->right == (struct parse_node *) NULL) ||
1877                 (current->left->token.type != token_string) ||
1878                 (current->right->token.type != token_string)) {
1879                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1880                             "Invalid expression \"%s\" in file %s",
1881                             expr, r->filename);
1882                 ap_rputs(error, r);
1883                 goto RETURN;
1884             }
1885             parse_string(r, current->left->token.value,
1886                          buffer, sizeof(buffer), 0);
1887             apr_cpystrn(current->left->token.value, buffer,
1888                         sizeof(current->left->token.value));
1889             parse_string(r, current->right->token.value,
1890                          buffer, sizeof(buffer), 0);
1891             apr_cpystrn(current->right->token.value, buffer,
1892                         sizeof(current->right->token.value));
1893             if (current->right->token.value[0] == '/') {
1894                 int len;
1895                 len = strlen(current->right->token.value);
1896                 if (current->right->token.value[len - 1] == '/') {
1897                     current->right->token.value[len - 1] = '\0';
1898                 }
1899                 else {
1900                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1901                                 "Invalid rexp \"%s\" in file %s",
1902                                 current->right->token.value, r->filename);
1903                     ap_rputs(error, r);
1904                     goto RETURN;
1905                 }
1906 #ifdef DEBUG_INCLUDE
1907                 ap_rvputs(r, "     Re Compare (", current->left->token.value,
1908                   ") with /", &current->right->token.value[1], "/\n", NULL);
1909 #endif
1910                 current->value =
1911                     re_check(r, current->left->token.value,
1912                              &current->right->token.value[1]);
1913             }
1914             else {
1915 #ifdef DEBUG_INCLUDE
1916                 ap_rvputs(r, "     Compare (", current->left->token.value,
1917                        ") with (", current->right->token.value, ")\n", NULL);
1918 #endif
1919                 current->value =
1920                     (strcmp(current->left->token.value,
1921                             current->right->token.value) == 0);
1922             }
1923             if (current->token.type == token_ne) {
1924                 current->value = !current->value;
1925             }
1926 #ifdef DEBUG_INCLUDE
1927             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
1928                    "\n", NULL);
1929 #endif
1930             current->done = 1;
1931             current = current->parent;
1932             break;
1933         case token_ge:
1934         case token_gt:
1935         case token_le:
1936         case token_lt:
1937 #ifdef DEBUG_INCLUDE
1938             ap_rputs("     Evaluate ge/gt/le/lt\n", r);
1939 #endif
1940             if ((current->left == (struct parse_node *) NULL) ||
1941                 (current->right == (struct parse_node *) NULL) ||
1942                 (current->left->token.type != token_string) ||
1943                 (current->right->token.type != token_string)) {
1944                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1945                             "Invalid expression \"%s\" in file %s",
1946                             expr, r->filename);
1947                 ap_rputs(error, r);
1948                 goto RETURN;
1949             }
1950             parse_string(r, current->left->token.value,
1951                          buffer, sizeof(buffer), 0);
1952             apr_cpystrn(current->left->token.value, buffer,
1953                         sizeof(current->left->token.value));
1954             parse_string(r, current->right->token.value,
1955                          buffer, sizeof(buffer), 0);
1956             apr_cpystrn(current->right->token.value, buffer,
1957                         sizeof(current->right->token.value));
1958 #ifdef DEBUG_INCLUDE
1959             ap_rvputs(r, "     Compare (", current->left->token.value,
1960                    ") with (", current->right->token.value, ")\n", NULL);
1961 #endif
1962             current->value =
1963                 strcmp(current->left->token.value,
1964                        current->right->token.value);
1965             if (current->token.type == token_ge) {
1966                 current->value = current->value >= 0;
1967             }
1968             else if (current->token.type == token_gt) {
1969                 current->value = current->value > 0;
1970             }
1971             else if (current->token.type == token_le) {
1972                 current->value = current->value <= 0;
1973             }
1974             else if (current->token.type == token_lt) {
1975                 current->value = current->value < 0;
1976             }
1977             else {
1978                 current->value = 0;     /* Don't return -1 if unknown token */
1979             }
1980 #ifdef DEBUG_INCLUDE
1981             ap_rvputs(r, "     Returning ", current->value ? "1" : "0",
1982                    "\n", NULL);
1983 #endif
1984             current->done = 1;
1985             current = current->parent;
1986             break;
1987
1988         case token_not:
1989             if (current->right != (struct parse_node *) NULL) {
1990                 if (!current->right->done) {
1991                     current = current->right;
1992                     continue;
1993                 }
1994                 current->value = !current->right->value;
1995             }
1996             else {
1997                 current->value = 0;
1998             }
1999 #ifdef DEBUG_INCLUDE
2000             ap_rvputs(r, "     Evaluate !: ", current->value ? "1" : "0",
2001                    "\n", NULL);
2002 #endif
2003             current->done = 1;
2004             current = current->parent;
2005             break;
2006
2007         case token_group:
2008             if (current->right != (struct parse_node *) NULL) {
2009                 if (!current->right->done) {
2010                     current = current->right;
2011                     continue;
2012                 }
2013                 current->value = current->right->value;
2014             }
2015             else {
2016                 current->value = 1;
2017             }
2018 #ifdef DEBUG_INCLUDE
2019             ap_rvputs(r, "     Evaluate (): ", current->value ? "1" : "0",
2020                    "\n", NULL);
2021 #endif
2022             current->done = 1;
2023             current = current->parent;
2024             break;
2025
2026         case token_lbrace:
2027             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2028                         "Unmatched '(' in \"%s\" in file %s",
2029                         expr, r->filename);
2030             ap_rputs(error, r);
2031             goto RETURN;
2032
2033         case token_rbrace:
2034             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2035                         "Unmatched ')' in \"%s\" in file %s",
2036                         expr, r->filename);
2037             ap_rputs(error, r);
2038             goto RETURN;
2039
2040         default:
2041             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2042                         "bad token type");
2043             ap_rputs(error, r);
2044             goto RETURN;
2045         }
2046     }
2047
2048     retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
2049   RETURN:
2050     apr_destroy_pool(expr_pool);
2051     return (retval);
2052 }
2053
2054 static int handle_if(ap_bucket *in, request_rec *r, const char *error,
2055                      int *conditional_status, int *printing)
2056 {
2057     char tag[MAX_STRING_LEN];
2058     char *tag_val;
2059     char *expr;
2060     apr_off_t offset = strlen("if ") + strlen(STARTING_SEQUENCE);
2061
2062     expr = NULL;
2063     while (1) {
2064         tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset);
2065         if (*tag == '\0') {
2066             return 1;
2067         }
2068         else if (!strcmp(tag, "done")) {
2069             if (expr == NULL) {
2070                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2071                             "missing expr in if statement: %s",
2072                             r->filename);
2073                 ap_rputs(error, r);
2074                 return 1;
2075             }
2076             *printing = *conditional_status = parse_expr(r, expr, error);
2077 #ifdef DEBUG_INCLUDE
2078             ap_rvputs(r, "**** if conditional_status=\"",
2079                    *conditional_status ? "1" : "0", "\"\n", NULL);
2080 #endif
2081             return 0;
2082         }
2083         else if (!strcmp(tag, "expr")) {
2084             expr = tag_val;
2085 #ifdef DEBUG_INCLUDE
2086             ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
2087 #endif
2088         }
2089         else {
2090             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2091                         "unknown parameter \"%s\" to tag if in %s",
2092                         tag, r->filename);
2093             ap_rputs(error, r);
2094         }
2095     }
2096 }
2097
2098 static int handle_elif(ap_bucket *in, request_rec *r, const char *error,
2099                        int *conditional_status, int *printing)
2100 {
2101     char tag[MAX_STRING_LEN];
2102     char *tag_val;
2103     char *expr;
2104     apr_off_t offset = strlen("elif ") + strlen(STARTING_SEQUENCE);
2105
2106     expr = NULL;
2107     while (1) {
2108         tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0, &offset);
2109         if (*tag == '\0') {
2110             return 1;
2111         }
2112         else if (!strcmp(tag, "done")) {
2113 #ifdef DEBUG_INCLUDE
2114             ap_rvputs(r, "**** elif conditional_status=\"",
2115                    *conditional_status ? "1" : "0", "\"\n", NULL);
2116 #endif
2117             if (*conditional_status) {
2118                 *printing = 0;
2119                 return (0);
2120             }
2121             if (expr == NULL) {
2122                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2123                             "missing expr in elif statement: %s",
2124                             r->filename);
2125                 ap_rputs(error, r);
2126                 return 1;
2127             }
2128             *printing = *conditional_status = parse_expr(r, expr, error);
2129 #ifdef DEBUG_INCLUDE
2130             ap_rvputs(r, "**** elif conditional_status=\"",
2131                    *conditional_status ? "1" : "0", "\"\n", NULL);
2132 #endif
2133             return 0;
2134         }
2135         else if (!strcmp(tag, "expr")) {
2136             expr = tag_val;
2137 #ifdef DEBUG_INCLUDE
2138             ap_rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
2139 #endif
2140         }
2141         else {
2142             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2143                         "unknown parameter \"%s\" to tag if in %s",
2144                         tag, r->filename);
2145             ap_rputs(error, r);
2146         }
2147     }
2148 }
2149
2150 static int handle_else(ap_bucket *in, request_rec *r, const char *error,
2151                        int *conditional_status, int *printing)
2152 {
2153     char tag[MAX_STRING_LEN];
2154     apr_off_t offset = strlen("else ") + strlen(STARTING_SEQUENCE);
2155
2156     if (!get_tag(r->pool, in, tag, sizeof(tag), 1, &offset)) {
2157         return 1;
2158     }
2159     else if (!strcmp(tag, "done")) {
2160 #ifdef DEBUG_INCLUDE
2161         ap_rvputs(r, "**** else conditional_status=\"",
2162                *conditional_status ? "1" : "0", "\"\n", NULL);
2163 #endif
2164         *printing = !(*conditional_status);
2165         *conditional_status = 1;
2166         return 0;
2167     }
2168     else {
2169         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2170                     "else directive does not take tags in %s",
2171                     r->filename);
2172         if (*printing) {
2173             ap_rputs(error, r);
2174         }
2175         return -1;
2176     }
2177 }
2178
2179 static int handle_endif(ap_bucket *in, request_rec *r, const char *error,
2180                         int *conditional_status, int *printing)
2181 {
2182     char tag[MAX_STRING_LEN];
2183     apr_off_t offset = strlen("endif ") + strlen(STARTING_SEQUENCE);
2184
2185     if (!get_tag(r->pool, in, tag, sizeof(tag), 1, &offset)) {
2186         return 1;
2187     }
2188     else if (!strcmp(tag, "done")) {
2189 #ifdef DEBUG_INCLUDE
2190         ap_rvputs(r, "**** endif conditional_status=\"",
2191                *conditional_status ? "1" : "0", "\"\n", NULL);
2192 #endif
2193         *printing = 1;
2194         *conditional_status = 1;
2195         return 0;
2196     }
2197     else {
2198         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2199                     "endif directive does not take tags in %s",
2200                     r->filename);
2201         ap_rputs(error, r);
2202         return -1;
2203     }
2204 }
2205
2206 static int handle_set(ap_bucket *in, request_rec *r, const char *error)
2207 {
2208     char tag[MAX_STRING_LEN];
2209     char parsed_string[MAX_STRING_LEN];
2210     char *tag_val;
2211     char *var;
2212     apr_off_t offset = strlen("set ") + strlen(STARTING_SEQUENCE);
2213
2214     var = (char *) NULL;
2215     while (1) {
2216         if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
2217             return 1;
2218         }
2219         else if (!strcmp(tag, "done")) {
2220             return 0;
2221         }
2222         else if (!strcmp(tag, "var")) {
2223             var = tag_val;
2224         }
2225         else if (!strcmp(tag, "value")) {
2226             if (var == (char *) NULL) {
2227                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2228                             "variable must precede value in set directive in %s",
2229                             r->filename);
2230                 ap_rputs(error, r);
2231                 return -1;
2232             }
2233             parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
2234             apr_table_setn(r->subprocess_env, var, apr_pstrdup(r->pool, parsed_string));
2235         }
2236         else {
2237             ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2238                         "Invalid tag for set directive in %s", r->filename);
2239             ap_rputs(error, r);
2240             return -1;
2241         }
2242     }
2243 }
2244
2245 static int handle_printenv(ap_bucket *in, request_rec *r, const char *error)
2246 {
2247     char tag[MAX_STRING_LEN];
2248     char *tag_val;
2249     apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
2250     apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
2251     int i;
2252     apr_off_t offset = strlen("printenv ") + strlen(STARTING_SEQUENCE);
2253
2254     if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1, &offset))) {
2255         return 1;
2256     }
2257     else if (!strcmp(tag, "done")) {
2258         for (i = 0; i < arr->nelts; ++i) {
2259             ap_rvputs(r, ap_escape_html(r->pool, elts[i].key), "=", 
2260                 ap_escape_html(r->pool, elts[i].val), "\n", NULL);
2261         }
2262         return 0;
2263     }
2264     else {
2265         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2266                     "printenv directive does not take tags in %s",
2267                     r->filename);
2268         ap_rputs(error, r);
2269         return -1;
2270     }
2271 }
2272
2273
2274
2275 /* -------------------------- The main function --------------------------- */
2276
2277 /* This is a stub which parses a file descriptor. */
2278
2279 static void send_parsed_content(ap_bucket_brigade **bb, request_rec *r, 
2280                                 ap_filter_t *f)
2281 {
2282     char directive[MAX_STRING_LEN], error[MAX_STRING_LEN];
2283     char timefmt[MAX_STRING_LEN];
2284     int noexec = ap_allow_options(r) & OPT_INCNOEXEC;
2285     int sizefmt;
2286     int if_nesting;
2287     int printing;
2288     int conditional_status;
2289     ap_bucket *dptr = AP_BRIGADE_FIRST(*bb);
2290     ap_bucket *tagbuck, *dptr2;
2291     ap_bucket *endsec;
2292     ap_bucket_brigade *tag_and_after;
2293     int ret;
2294
2295     apr_cpystrn(error, DEFAULT_ERROR_MSG, sizeof(error));
2296     apr_cpystrn(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt));
2297     sizefmt = SIZEFMT_KMG;
2298
2299 /*  Turn printing on */
2300     printing = conditional_status = 1;
2301     if_nesting = 0;
2302
2303     ap_chdir_file(r->filename);
2304     if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
2305         char *arg_copy = apr_pstrdup(r->pool, r->args);
2306
2307         apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
2308         ap_unescape_url(arg_copy);
2309         apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
2310                   ap_escape_shell_cmd(r->pool, arg_copy));
2311     }
2312
2313     AP_BRIGADE_FOREACH(dptr, *bb) {
2314         if ((tagbuck = find_string(dptr, STARTING_SEQUENCE, AP_BRIGADE_LAST(*bb))) != NULL) {
2315             dptr2 = tagbuck;
2316             dptr = tagbuck;
2317             endsec = find_string(dptr2, ENDING_SEQUENCE, AP_BRIGADE_LAST(*bb));
2318             if (endsec == NULL) {
2319                 /** XXX No ending tag, needs to become an error bucket
2320                  ** Tag could come in the next brigade (unless we've 
2321                  ** received eos in this brigade).
2322                  **
2323                  ** We're about to segfault.
2324                  **/
2325             }
2326              
2327             /* At this point, everything between tagbuck and endsec is an SSI
2328              * directive, we just have to deal with it now.
2329              */
2330             if (get_directive(tagbuck, directive, sizeof(directive), r->pool)) {
2331                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2332                             "mod_include: error reading directive in %s",
2333                             r->filename);
2334                 ap_rputs(error, r);
2335                 return;
2336             }
2337             tag_and_after = ap_brigade_split(*bb, dptr);
2338             ap_pass_brigade(f->next, *bb); /* process what came before the tag */
2339             *bb = tag_and_after;
2340             if (!strcmp(directive, "if")) {
2341                 if (!printing) {
2342                     if_nesting++;
2343                 }
2344                 else {
2345                     ret = handle_if(tagbuck, r, error, &conditional_status,
2346                                     &printing);
2347                     if_nesting = 0;
2348                 }
2349                 continue;
2350             }
2351             else if (!strcmp(directive, "else")) {
2352                 if (!if_nesting) {
2353                     ret = handle_else(tagbuck, r, error, &conditional_status,
2354                                       &printing);
2355                 }
2356                 continue;
2357             }
2358             else if (!strcmp(directive, "elif")) {
2359                 if (!if_nesting) {
2360                     ret = handle_elif(tagbuck, r, error, &conditional_status,
2361                                       &printing);
2362                 }
2363                 continue;
2364             }
2365             else if (!strcmp(directive, "endif")) {
2366                 if (!if_nesting) {
2367                     ret = handle_endif(tagbuck, r, error, &conditional_status,
2368                                        &printing);
2369                 }
2370                 else {
2371                     if_nesting--;
2372                 }
2373                 continue;
2374             }
2375             if (!printing) {
2376                 continue;
2377             }
2378             if (!strcmp(directive, "exec")) {
2379                 if (noexec) {
2380                     ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2381                                   "exec used but not allowed in %s",
2382                                   r->filename);
2383                     if (printing) {
2384                         ap_rputs(error, r);
2385                     }
2386                 }
2387                 else {
2388                     ret = handle_exec(tagbuck, r, error, f->next);
2389                 }
2390             }
2391             else if (!strcmp(directive, "config")) {
2392                 ret = handle_config(tagbuck, r, error, timefmt, &sizefmt);
2393             }
2394             else if (!strcmp(directive, "set")) {
2395                 ret = handle_set(tagbuck, r, error);
2396             }
2397             else if (!strcmp(directive, "include")) {
2398                 ret = handle_include(tagbuck, r, f->next, error, noexec);
2399             }
2400             else if (!strcmp(directive, "echo")) {
2401                 ret = handle_echo(tagbuck, r, error);
2402             }
2403             else if (!strcmp(directive, "fsize")) {
2404                 ret = handle_fsize(tagbuck, r, error, sizefmt);
2405             }
2406             else if (!strcmp(directive, "flastmod")) {
2407                 ret = handle_flastmod(tagbuck, r, error, timefmt);
2408             }
2409             else if (!strcmp(directive, "printenv")) {
2410                 ret = handle_printenv(tagbuck, r, error);
2411             }
2412 #ifdef USE_PERL_SSI
2413             else if (!strcmp(directive, "perl")) {
2414                 ret = handle_perl(tagbuck, r, error);
2415             }
2416 #endif
2417             else {
2418                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
2419                               "unknown directive \"%s\" "
2420                               "in parsed doc %s",
2421                               directive, r->filename);
2422                 if (printing) {
2423                     ap_rputs(error, r);
2424                 }
2425             }
2426 /*            AP_BRIGADE_UNSPLICE(dptr, AP_BUCKET_PREV(endsec)); */
2427             dptr = AP_BUCKET_PREV(endsec);
2428         }
2429         else {
2430             return;
2431         }
2432     }
2433 }
2434
2435 /*****************************************************************
2436  *
2437  * XBITHACK.  Sigh...  NB it's configurable per-directory; the compile-time
2438  * option only changes the default.
2439  */
2440
2441 module includes_module;
2442 enum xbithack {
2443     xbithack_off, xbithack_on, xbithack_full
2444 };
2445
2446 #ifdef XBITHACK
2447 #define DEFAULT_XBITHACK xbithack_full
2448 #else
2449 #define DEFAULT_XBITHACK xbithack_off
2450 #endif
2451
2452 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
2453 {
2454     enum xbithack *result = (enum xbithack *) apr_palloc(p, sizeof(enum xbithack));
2455     *result = DEFAULT_XBITHACK;
2456     return result;
2457 }
2458
2459 static const char *set_xbithack(cmd_parms *cmd, void *xbp, const char *arg)
2460 {
2461     enum xbithack *state = (enum xbithack *) xbp;
2462
2463     if (!strcasecmp(arg, "off")) {
2464         *state = xbithack_off;
2465     }
2466     else if (!strcasecmp(arg, "on")) {
2467         *state = xbithack_on;
2468     }
2469     else if (!strcasecmp(arg, "full")) {
2470         *state = xbithack_full;
2471     }
2472     else {
2473         return "XBitHack must be set to Off, On, or Full";
2474     }
2475
2476     return NULL;
2477 }
2478
2479 static int includes_filter(ap_filter_t *f, ap_bucket_brigade *b)
2480 {
2481     request_rec *r = f->r;
2482     enum xbithack *state =
2483     (enum xbithack *) ap_get_module_config(r->per_dir_config, &includes_module);
2484     request_rec *parent;
2485
2486     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
2487         return ap_pass_brigade(f->next, b);
2488     }
2489     r->allowed |= (1 << M_GET);
2490     if (r->method_number != M_GET) {
2491         return ap_pass_brigade(f->next, b);
2492     }
2493
2494     if ((*state == xbithack_full)
2495 #if !defined(OS2) && !defined(WIN32)
2496     /*  OS/2 dosen't support Groups. */
2497         && (r->finfo.protection & APR_GEXECUTE)
2498 #endif
2499         ) {
2500         ap_update_mtime(r, r->finfo.mtime);
2501         ap_set_last_modified(r);
2502     }
2503
2504     if ((parent = ap_get_module_config(r->request_config, &includes_module))) {
2505         /* Kludge --- for nested includes, we want to keep the subprocess
2506          * environment of the base document (for compatibility); that means
2507          * torquing our own last_modified date as well so that the
2508          * LAST_MODIFIED variable gets reset to the proper value if the
2509          * nested document resets <!--#config timefmt-->.
2510          * We also insist that the memory for this subrequest not be
2511          * destroyed, that's dealt with in handle_include().
2512          */
2513         r->subprocess_env = parent->subprocess_env;
2514         apr_pool_join(parent->pool, r->pool);
2515         r->finfo.mtime = parent->finfo.mtime;
2516     }
2517     else {
2518         /* we're not a nested include, so we create an initial
2519          * environment */
2520         ap_add_common_vars(r);
2521         ap_add_cgi_vars(r);
2522         add_include_vars(r, DEFAULT_TIME_FORMAT);
2523     }
2524     /* XXX: this is bogus, at some point we're going to do a subrequest,
2525      * and when we do it we're going to be subjecting code that doesn't
2526      * expect to be signal-ready to SIGALRM.  There is no clean way to
2527      * fix this, except to put alarm support into BUFF. -djg
2528      */
2529
2530     send_parsed_content(&b, r, f);
2531     ap_pass_brigade(f->next, b);
2532
2533     if (parent) {
2534         /* signify that the sub request should not be killed */
2535         ap_set_module_config(r->request_config, &includes_module,
2536             NESTED_INCLUDE_MAGIC);
2537     }
2538
2539     return OK;
2540 }
2541
2542 static const command_rec includes_cmds[] =
2543 {
2544     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
2545                   "Off, On, or Full"),
2546     {NULL}
2547 };
2548
2549 static void register_hooks(void)
2550 {
2551     ap_register_output_filter("INCLUDES", includes_filter, AP_FTYPE_CONTENT);
2552 }
2553
2554 module MODULE_VAR_EXPORT includes_module =
2555 {
2556     STANDARD20_MODULE_STUFF,
2557     create_includes_dir_config, /* dir config creater */
2558     NULL,                       /* dir merger --- default is to override */
2559     NULL,                       /* server config */
2560     NULL,                       /* merge server config */
2561     includes_cmds,              /* command apr_table_t */
2562 #if 0
2563     includes_handlers,          /* handlers */
2564 #else
2565     NULL,                       /* handlers */
2566 #endif
2567     register_hooks              /* register hooks */
2568 };