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