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