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