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