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