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