]> granicus.if.org Git - apache/blob - modules/filters/mod_include.c
de3996a3ff8288a11f4ffeba3c93452340dfd8c8
[apache] / modules / filters / mod_include.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2003 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 #include "apr.h"
68 #include "apr_strings.h"
69 #include "apr_thread_proc.h"
70 #include "apr_hash.h"
71 #include "apr_user.h"
72 #include "apr_lib.h"
73 #include "apr_optional.h"
74
75 #define APR_WANT_STRFUNC
76 #define APR_WANT_MEMFUNC
77 #include "apr_want.h"
78
79 #include "ap_config.h"
80 #include "util_filter.h"
81 #include "httpd.h"
82 #include "http_config.h"
83 #include "http_core.h"
84 #include "http_request.h"
85 #include "http_core.h"
86 #include "http_protocol.h"
87 #include "http_log.h"
88 #include "http_main.h"
89 #include "util_script.h"
90 #include "http_core.h"
91 #include "mod_include.h"
92
93 /* helper for Latin1 <-> entity encoding */
94 #if APR_CHARSET_EBCDIC
95 #include "util_ebcdic.h"
96 #define RAW_ASCII_CHAR(ch)  apr_xlate_conv_byte(ap_hdrs_from_ascii, \
97                                                 (unsigned char)ch)
98 #else /* APR_CHARSET_EBCDIC */
99 #define RAW_ASCII_CHAR(ch)  (ch)
100 #endif /* !APR_CHARSET_EBCDIC */
101
102
103 /*
104  * +-------------------------------------------------------+
105  * |                                                       |
106  * |                  Debugging Macros
107  * |                                                       |
108  * +-------------------------------------------------------+
109  */
110
111 #ifdef DEBUG_INCLUDE
112
113 #define MAX_DEBUG_SIZE MAX_STRING_LEN
114
115 #define LOG_COND_STATUS(ctx, f, bb, text)                                   \
116 do {                                                                        \
117     char *cond_txt = apr_pstrcat((ctx)->dpool, "**** ", (text),             \
118         " conditional_status=\"", ((ctx)->flags & SSI_FLAG_COND_TRUE)?"1":"0", \
119         "\"\n", NULL);                                                      \
120     APR_BRIGADE_INSERT_TAIL((bb), apr_bucket_heap_create(cond_txt,          \
121                             strlen(cond_txt), NULL, (f)->c->bucket_alloc)); \
122 } while(0)
123
124 #define DUMP_PARSE_EXPR_DEBUG(buf, f, bb)                                   \
125 do {                                                                        \
126     APR_BRIGADE_INSERT_TAIL((bb), apr_bucket_heap_create((buf),             \
127                             strlen((buf)), NULL, (f)->c->bucket_alloc));    \
128 } while(0)
129
130 #else
131
132 #define MAX_DEBUG_SIZE 10
133 #define LOG_COND_STATUS(ctx, f, bb, text)
134 #define DUMP_PARSE_EXPR_DEBUG(buf, f, bb)
135
136 #endif
137
138
139 /*
140  * +-------------------------------------------------------+
141  * |                                                       |
142  * |                 Types and Structures
143  * |                                                       |
144  * +-------------------------------------------------------+
145  */
146
147 /* sll used for string expansion */
148 typedef struct result_item {
149     struct result_item *next;
150     apr_size_t len;
151     const char *string;
152 } result_item_t;
153
154 typedef enum {
155     XBITHACK_OFF,
156     XBITHACK_ON,
157     XBITHACK_FULL
158 } xbithack_t;
159
160 typedef struct {
161     unsigned int T[256];
162     unsigned int x;
163     apr_size_t pattern_len;
164 } bndm_t;
165
166 typedef struct {
167     const char *default_error_msg;
168     const char *default_time_fmt;
169     xbithack_t  xbithack;
170 } include_dir_config;
171
172 typedef struct {
173     const char *default_start_tag;
174     const char *default_end_tag;
175     const char *undefined_echo;
176     apr_size_t  undefined_echo_len;
177 } include_server_config;
178
179 /* main parser states */
180 typedef enum {
181     PARSE_PRE_HEAD,
182     PARSE_HEAD,
183     PARSE_DIRECTIVE,
184     PARSE_DIRECTIVE_POSTNAME,
185     PARSE_DIRECTIVE_TAIL,
186     PARSE_DIRECTIVE_POSTTAIL,
187     PARSE_PRE_ARG,
188     PARSE_ARG,
189     PARSE_ARG_NAME,
190     PARSE_ARG_POSTNAME,
191     PARSE_ARG_EQ,
192     PARSE_ARG_PREVAL,
193     PARSE_ARG_VAL,
194     PARSE_ARG_VAL_ESC,
195     PARSE_ARG_POSTVAL,
196     PARSE_TAIL,
197     PARSE_TAIL_SEQ,
198     PARSE_EXECUTE
199 } parse_state_t;
200
201 typedef struct arg_item {
202     struct arg_item  *next;
203     char             *name;
204     apr_size_t        name_len;
205     char             *value;
206     apr_size_t        value_len;
207 } arg_item_t;
208
209 #define MAX_NMATCH 10
210
211 typedef struct {
212     const char *source;
213     const char *rexp;
214     apr_size_t  nsub;
215     regmatch_t  match[MAX_NMATCH];
216 } backref_t;
217
218 struct ssi_internal_ctx {
219     parse_state_t state;
220     int           seen_eos;
221     int           error;
222     char          quote;         /* quote character value (or \0) */
223     apr_size_t    parse_pos;     /* parse position of partial matches */
224     apr_size_t    bytes_read;
225
226     apr_bucket_brigade *tmp_bb;
227
228     request_rec  *r;
229     const char   *start_seq;
230     bndm_t       *start_seq_pat;
231     const char   *end_seq;
232     apr_size_t    end_seq_len;
233     char         *directive;     /* name of the current directive */
234     apr_size_t    directive_len; /* length of the current directive name */
235
236     arg_item_t   *current_arg;   /* currently parsed argument */
237     arg_item_t   *argv;          /* all arguments */
238
239     backref_t    *re;            /* NULL if there wasn't a regex yet */
240 };
241
242
243 /*
244  * +-------------------------------------------------------+
245  * |                                                       |
246  * |                 Static Module Data
247  * |                                                       |
248  * +-------------------------------------------------------+
249  */
250
251 /* global module structure */
252 module AP_MODULE_DECLARE_DATA include_module;
253
254 /* function handlers for include directives */
255 static apr_hash_t *include_handlers;
256
257 /* forward declaration of handler registry */
258 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
259
260 /* Sentinel value to store in subprocess_env for items that
261  * shouldn't be evaluated until/unless they're actually used
262  */
263 static const char lazy_eval_sentinel;
264 #define LAZY_VALUE (&lazy_eval_sentinel)
265
266 /* default values */
267 #define DEFAULT_START_SEQUENCE "<!--#"
268 #define DEFAULT_END_SEQUENCE "-->"
269 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
270 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
271 #define DEFAULT_UNDEFINED_ECHO "(none)"
272
273 #ifdef XBITHACK
274 #define DEFAULT_XBITHACK XBITHACK_FULL
275 #else
276 #define DEFAULT_XBITHACK XBITHACK_OFF
277 #endif
278
279
280 /*
281  * +-------------------------------------------------------+
282  * |                                                       |
283  * |            Environment/Expansion Functions
284  * |                                                       |
285  * +-------------------------------------------------------+
286  */
287
288 /*
289  * decodes a string containing html entities or numeric character references.
290  * 's' is overwritten with the decoded string.
291  * If 's' is syntatically incorrect, then the followed fixups will be made:
292  *   unknown entities will be left undecoded;
293  *   references to unused numeric characters will be deleted.
294  *   In particular, &#00; will not be decoded, but will be deleted.
295  */
296
297 /* maximum length of any ISO-LATIN-1 HTML entity name. */
298 #define MAXENTLEN (6)
299
300 /* The following is a shrinking transformation, therefore safe. */
301
302 static void decodehtml(char *s)
303 {
304     int val, i, j;
305     char *p;
306     const char *ents;
307     static const char * const entlist[MAXENTLEN + 1] =
308     {
309         NULL,                   /* 0 */
310         NULL,                   /* 1 */
311         "lt\074gt\076",         /* 2 */
312         "amp\046ETH\320eth\360",        /* 3 */
313         "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
314 iuml\357ouml\366uuml\374yuml\377",      /* 4 */
315         "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
316 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
317 ucirc\373thorn\376",            /* 5 */
318         "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
319 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
320 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
321 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
322 otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
323     };
324
325     /* Do a fast scan through the string until we find anything
326      * that needs more complicated handling
327      */
328     for (; *s != '&'; s++) {
329         if (*s == '\0') {
330             return;
331         }
332     }
333
334     for (p = s; *s != '\0'; s++, p++) {
335         if (*s != '&') {
336             *p = *s;
337             continue;
338         }
339         /* find end of entity */
340         for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
341             continue;
342         }
343
344         if (s[i] == '\0') {     /* treat as normal data */
345             *p = *s;
346             continue;
347         }
348
349         /* is it numeric ? */
350         if (s[1] == '#') {
351             for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
352                 val = val * 10 + s[j] - '0';
353             }
354             s += i;
355             if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
356                 (val >= 127 && val <= 160) || val >= 256) {
357                 p--;            /* no data to output */
358             }
359             else {
360                 *p = RAW_ASCII_CHAR(val);
361             }
362         }
363         else {
364             j = i - 1;
365             if (j > MAXENTLEN || entlist[j] == NULL) {
366                 /* wrong length */
367                 *p = '&';
368                 continue;       /* skip it */
369             }
370             for (ents = entlist[j]; *ents != '\0'; ents += i) {
371                 if (strncmp(s + 1, ents, j) == 0) {
372                     break;
373                 }
374             }
375
376             if (*ents == '\0') {
377                 *p = '&';       /* unknown */
378             }
379             else {
380                 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
381                 s += i;
382             }
383         }
384     }
385
386     *p = '\0';
387 }
388
389 static void add_include_vars(request_rec *r, const char *timefmt)
390 {
391     apr_table_t *e = r->subprocess_env;
392     char *t;
393
394     apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
395     apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
396     apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
397     apr_table_setn(e, "DOCUMENT_URI", r->uri);
398     if (r->path_info && *r->path_info) {
399         apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
400     }
401     apr_table_setn(e, "USER_NAME", LAZY_VALUE);
402     if ((t = strrchr(r->filename, '/'))) {
403         apr_table_setn(e, "DOCUMENT_NAME", ++t);
404     }
405     else {
406         apr_table_setn(e, "DOCUMENT_NAME", r->uri);
407     }
408     if (r->args) {
409         char *arg_copy = apr_pstrdup(r->pool, r->args);
410
411         ap_unescape_url(arg_copy);
412         apr_table_setn(e, "QUERY_STRING_UNESCAPED",
413                   ap_escape_shell_cmd(r->pool, arg_copy));
414     }
415 }
416
417 static const char *add_include_vars_lazy(request_rec *r, const char *var)
418 {
419     char *val;
420     if (!strcasecmp(var, "DATE_LOCAL")) {
421         include_dir_config *conf =
422             (include_dir_config *)ap_get_module_config(r->per_dir_config,
423                                                        &include_module);
424         val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
425     }
426     else if (!strcasecmp(var, "DATE_GMT")) {
427         include_dir_config *conf =
428             (include_dir_config *)ap_get_module_config(r->per_dir_config,
429                                                        &include_module);
430         val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
431     }
432     else if (!strcasecmp(var, "LAST_MODIFIED")) {
433         include_dir_config *conf =
434             (include_dir_config *)ap_get_module_config(r->per_dir_config,
435                                                        &include_module);
436         val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
437     }
438     else if (!strcasecmp(var, "USER_NAME")) {
439         if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
440             val = "<unknown>";
441         }
442     }
443     else {
444         val = NULL;
445     }
446
447     if (val) {
448         apr_table_setn(r->subprocess_env, var, val);
449     }
450     return val;
451 }
452
453 static const char *get_include_var(const char *var, include_ctx_t *ctx)
454 {
455     const char *val;
456     request_rec *r = ctx->intern->r;
457
458     if (apr_isdigit(*var) && !var[1]) {
459         int idx = *var - '0';
460         backref_t *re = ctx->intern->re;
461
462         /* Handle $0 .. $9 from the last regex evaluated.
463          * The choice of returning NULL strings on not-found,
464          * v.s. empty strings on an empty match is deliberate.
465          */
466         if (!re) {
467             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "regex capture $%d "
468                           "refers to no regex in %s", idx, r->filename);
469             return NULL;
470         }
471         else {
472             if (re->nsub < idx) {
473                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
474                               "regex capture $%d is out of range (last regex "
475                               "was: '%s') in %s", idx, re->rexp, r->filename);
476                 return NULL;
477             }
478
479             if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
480                 return NULL;
481             }
482
483             val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
484                                  re->match[idx].rm_eo - re->match[idx].rm_so);
485         }
486     }
487     else {
488         val = apr_table_get(r->subprocess_env, var);
489
490         if (val == LAZY_VALUE) {
491             val = add_include_vars_lazy(r, var);
492         }
493     }
494
495     return val;
496 }
497
498 /*
499  * Do variable substitution on strings
500  *
501  * (Note: If out==NULL, this function allocs a buffer for the resulting
502  * string from ctx->pool. The return value is always the parsed string)
503  */
504 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
505                                  apr_size_t length, int leave_name)
506 {
507     request_rec *r = ctx->intern->r;
508     result_item_t *result = NULL, *current = NULL;
509     apr_size_t outlen = 0, inlen, span;
510     char *ret = NULL, *eout = NULL;
511     const char *p;
512
513     if (out) {
514         /* sanity check, out && !length is not supported */
515         ap_assert(out && length);
516
517         ret = out;
518         eout = out + length - 1;
519     }
520
521     span = strcspn(in, "\\$");
522     inlen = strlen(in);
523
524     /* fast exit */
525     if (inlen == span) {
526         if (out) {
527             apr_cpystrn(out, in, length);
528         }
529         else {
530             ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
531                                                 ? length - 1 : inlen);
532         }
533
534         return ret;
535     }
536
537     /* well, actually something to do */
538     p = in + span;
539
540     if (out) {
541         if (span) {
542             memcpy(out, in, (out+span <= eout) ? span : (eout-out));
543             out += span;
544         }
545     }
546     else {
547         current = result = apr_palloc(ctx->dpool, sizeof(*result));
548         current->next = NULL;
549         current->string = in;
550         current->len = span;
551         outlen = span;
552     }
553
554     /* loop for specials */
555     do {
556         if ((out && out >= eout) || (length && outlen >= length)) {
557             break;
558         }
559
560         /* prepare next entry */
561         if (!out && current->len) {
562             current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
563             current = current->next;
564             current->next = NULL;
565             current->len = 0;
566         }
567
568         /*
569          * escaped character
570          */
571         if (*p == '\\') {
572             if (out) {
573                 *out++ = (p[1] == '$') ? *++p : *p;
574                 ++p;
575             }
576             else {
577                 current->len = 1;
578                 current->string = (p[1] == '$') ? ++p : p;
579                 ++p;
580                 ++outlen;
581             }
582         }
583
584         /*
585          * variable expansion
586          */
587         else {       /* *p == '$' */
588             const char *newp = NULL, *ep, *key = NULL;
589
590             if (*++p == '{') {
591                 ep = ap_strchr_c(++p, '}');
592                 if (!ep) {
593                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
594                                   "variable \"%s\" in %s", p, r->filename);
595                     break;
596                 }
597
598                 if (p < ep) {
599                     key = apr_pstrmemdup(ctx->dpool, p, ep - p);
600                     newp = ep + 1;
601                 }
602                 p -= 2;
603             }
604             else {
605                 ep = p;
606                 while (*ep == '_' || apr_isalnum(*ep)) {
607                     ++ep;
608                 }
609
610                 if (p < ep) {
611                     key = apr_pstrmemdup(ctx->dpool, p, ep - p);
612                     newp = ep;
613                 }
614                 --p;
615             }
616
617             /* empty name results in a copy of '$' in the output string */
618             if (!key) {
619                 if (out) {
620                     *out++ = *p++;
621                 }
622                 else {
623                     current->len = 1;
624                     current->string = p++;
625                     ++outlen;
626                 }
627             }
628             else {
629                 const char *val = get_include_var(key, ctx);
630                 apr_size_t len = 0;
631
632                 if (val) {
633                     len = strlen(val);
634                 }
635                 else if (leave_name) {
636                     val = p;
637                     len = ep - p;
638                 }
639
640                 if (val && len) {
641                     if (out) {
642                         memcpy(out, val, (out+len <= eout) ? len : (eout-out));
643                         out += len;
644                     }
645                     else {
646                         current->len = len;
647                         current->string = val;
648                         outlen += len;
649                     }
650                 }
651
652                 p = newp;
653             }
654         }
655
656         if ((out && out >= eout) || (length && outlen >= length)) {
657             break;
658         }
659
660         /* check the remainder */
661         if (*p && (span = strcspn(p, "\\$")) > 0) {
662             if (!out && current->len) {
663                 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
664                 current = current->next;
665                 current->next = NULL;
666             }
667
668             if (out) {
669                 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
670                 out += span;
671             }
672             else {
673                 current->len = span;
674                 current->string = p;
675                 outlen += span;
676             }
677
678             p += span;
679         }
680     } while (p < in+inlen);
681
682     /* assemble result */
683     if (out) {
684         if (out > eout) {
685             *eout = '\0';
686         }
687         else {
688             *out = '\0';
689         }
690     }
691     else {
692         const char *ep;
693
694         if (length && outlen > length) {
695             outlen = length - 1;
696         }
697
698         ret = out = apr_palloc(ctx->pool, outlen + 1);
699         ep = ret + outlen;
700
701         do {
702             if (result->len) {
703                 memcpy(out, result->string, (out+result->len <= ep)
704                                             ? result->len : (ep-out));
705                 out += result->len;
706             }
707             result = result->next;
708         } while (result && out < ep);
709
710         ret[outlen] = '\0';
711     }
712
713     return ret;
714 }
715
716
717 /*
718  * +-------------------------------------------------------+
719  * |                                                       |
720  * |              Conditional Expression Parser
721  * |                                                       |
722  * +-------------------------------------------------------+
723  */
724
725 static APR_INLINE int re_check(include_ctx_t *ctx, char *string, char *rexp)
726 {
727     regex_t *compiled;
728     backref_t *re = ctx->intern->re;
729     int rc;
730
731     compiled = ap_pregcomp(ctx->dpool, rexp, REG_EXTENDED);
732     if (!compiled) {
733         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->intern->r, "unable to "
734                       "compile pattern \"%s\"", rexp);
735         return -1;
736     }
737
738     if (!re) {
739         re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
740     }
741
742     re->source = string;
743     re->rexp = rexp;
744     re->nsub = compiled->re_nsub;
745     rc = !ap_regexec(compiled, string, MAX_NMATCH, re->match, 0);
746
747     ap_pregfree(ctx->dpool, compiled);
748     return rc;
749 }
750
751 enum token_type {
752     token_string, token_re,
753     token_and, token_or, token_not, token_eq, token_ne,
754     token_rbrace, token_lbrace, token_group,
755     token_ge, token_le, token_gt, token_lt
756 };
757 struct token {
758     enum token_type type;
759     char* value;
760 };
761
762 static const char *get_ptoken(request_rec *r, const char *string, 
763                               struct token *token, int *unmatched)
764 {
765     char ch;
766     int next = 0;
767     char qs = 0;
768     int tkn_fnd = 0;
769
770     token->value = NULL;
771
772     /* Skip leading white space */
773     if (string == (char *) NULL) {
774         return (char *) NULL;
775     }
776     while ((ch = *string++)) {
777         if (!apr_isspace(ch)) {
778             break;
779         }
780     }
781     if (ch == '\0') {
782         return (char *) NULL;
783     }
784
785     token->type = token_string; /* the default type */
786     switch (ch) {
787     case '(':
788         token->type = token_lbrace;
789         return (string);
790     case ')':
791         token->type = token_rbrace;
792         return (string);
793     case '=':
794         token->type = token_eq;
795         return (string);
796     case '!':
797         if (*string == '=') {
798             token->type = token_ne;
799             return (string + 1);
800         }
801         else {
802             token->type = token_not;
803             return (string);
804         }
805     case '\'':
806         /* already token->type == token_string */
807         qs = '\'';
808         break;
809     case '/':
810         token->type = token_re;
811         qs = '/';
812         break;
813     case '|':
814         if (*string == '|') {
815             token->type = token_or;
816             return (string + 1);
817         }
818         break;
819     case '&':
820         if (*string == '&') {
821             token->type = token_and;
822             return (string + 1);
823         }
824         break;
825     case '>':
826         if (*string == '=') {
827             token->type = token_ge;
828             return (string + 1);
829         }
830         else {
831             token->type = token_gt;
832             return (string);
833         }
834     case '<':
835         if (*string == '=') {
836             token->type = token_le;
837             return (string + 1);
838         }
839         else {
840             token->type = token_lt;
841             return (string);
842         }
843     default:
844         /* already token->type == token_string */
845         break;
846     }
847     /* We should only be here if we are in a string */
848     token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus
849                                                                trailing null */
850     if (!qs) {
851         token->value[next++] = ch;
852     }
853
854     /* 
855      * I used the ++string throughout this section so that string
856      * ends up pointing to the next token and I can just return it
857      */
858     for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) {
859         if (ch == '\\') {
860             if ((ch = *++string) == '\0') {
861                 tkn_fnd = 1;
862             }
863             else {
864                 token->value[next++] = ch;
865             }
866         }
867         else {
868             if (!qs) {
869                 if (apr_isspace(ch)) {
870                     tkn_fnd = 1;
871                 }
872                 else {
873                     switch (ch) {
874                     case '(':
875                     case ')':
876                     case '=':
877                     case '!':
878                     case '<':
879                     case '>':
880                         tkn_fnd = 1;
881                         break;
882                     case '|':
883                         if (*(string + 1) == '|') {
884                             tkn_fnd = 1;
885                         }
886                         break;
887                     case '&':
888                         if (*(string + 1) == '&') {
889                             tkn_fnd = 1;
890                         }
891                         break;
892                     }
893                     if (!tkn_fnd) {
894                         token->value[next++] = ch;
895                     }
896                 }
897             }
898             else {
899                 if (ch == qs) {
900                     qs = 0;
901                     tkn_fnd = 1;
902                     string++;
903                 }
904                 else {
905                     token->value[next++] = ch;
906                 }
907             }
908         }
909         if (tkn_fnd) {
910             break;
911         }
912     }
913
914     /* If qs is still set, we have an unmatched quote */
915     if (qs) {
916         *unmatched = 1;
917         next = 0;
918     }
919     token->value[next] = '\0';
920
921     return (string);
922 }
923
924
925 /* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
926  * characters long...
927  */
928 static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr,
929                       int *was_error, int *was_unmatched, char *debug)
930 {
931     struct parse_node {
932         struct parse_node *left, *right, *parent;
933         struct token token;
934         int value, done;
935     } *root, *current, *new;
936     const char *parse;
937     char* buffer;
938     int retval = 0;
939     apr_size_t debug_pos = 0;
940
941     debug[debug_pos] = '\0';
942     *was_error       = 0;
943     *was_unmatched   = 0;
944     if ((parse = expr) == (char *) NULL) {
945         return (0);
946     }
947     root = current = (struct parse_node *) NULL;
948
949     /* Create Parse Tree */
950     while (1) {
951         new = (struct parse_node *) apr_palloc(r->pool,
952                                            sizeof(struct parse_node));
953         new->parent = new->left = new->right = (struct parse_node *) NULL;
954         new->done = 0;
955         if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == 
956             (char *) NULL) {
957             break;
958         }
959         switch (new->token.type) {
960
961         case token_string:
962 #ifdef DEBUG_INCLUDE
963             debug_pos += sprintf (&debug[debug_pos], 
964                                   "     Token: string (%s)\n", 
965                                   new->token.value);
966 #endif
967             if (current == (struct parse_node *) NULL) {
968                 root = current = new;
969                 break;
970             }
971             switch (current->token.type) {
972             case token_string:
973                 current->token.value = apr_pstrcat(r->pool,
974                                                    current->token.value,
975                                                    current->token.value[0] ? " " : "",
976                                                    new->token.value,
977                                                    NULL);
978                                                    
979                 break;
980             case token_eq:
981             case token_ne:
982             case token_and:
983             case token_or:
984             case token_lbrace:
985             case token_not:
986             case token_ge:
987             case token_gt:
988             case token_le:
989             case token_lt:
990                 new->parent = current;
991                 current = current->right = new;
992                 break;
993             default:
994                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
995                             "Invalid expression \"%s\" in file %s",
996                             expr, r->filename);
997                 *was_error = 1;
998                 return retval;
999             }
1000             break;
1001
1002         case token_re:
1003 #ifdef DEBUG_INCLUDE
1004             debug_pos += sprintf (&debug[debug_pos], 
1005                                   "     Token: regex (%s)\n", 
1006                                   new->token.value);
1007 #endif
1008             if (current == (struct parse_node *) NULL) {
1009                 root = current = new;
1010                 break;
1011             }
1012             switch (current->token.type) {
1013             case token_eq:
1014             case token_ne:
1015             case token_and:
1016             case token_or:
1017             case token_lbrace:
1018             case token_not:
1019                 new->parent = current;
1020                 current = current->right = new;
1021                 break;
1022             default:
1023                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1024                             "Invalid expression \"%s\" in file %s",
1025                             expr, r->filename);
1026                 *was_error = 1;
1027                 return retval;
1028             }
1029             break;
1030
1031         case token_and:
1032         case token_or:
1033 #ifdef DEBUG_INCLUDE
1034             memcpy (&debug[debug_pos], "     Token: and/or\n",
1035                     sizeof ("     Token: and/or\n"));
1036             debug_pos += sizeof ("     Token: and/or\n");
1037 #endif
1038             if (current == (struct parse_node *) NULL) {
1039                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1040                             "Invalid expression \"%s\" in file %s",
1041                             expr, r->filename);
1042                 *was_error = 1;
1043                 return retval;
1044             }
1045             /* Percolate upwards */
1046             while (current != (struct parse_node *) NULL) {
1047                 switch (current->token.type) {
1048                 case token_string:
1049                 case token_re:
1050                 case token_group:
1051                 case token_not:
1052                 case token_eq:
1053                 case token_ne:
1054                 case token_and:
1055                 case token_or:
1056                 case token_ge:
1057                 case token_gt:
1058                 case token_le:
1059                 case token_lt:
1060                     current = current->parent;
1061                     continue;
1062                 case token_lbrace:
1063                     break;
1064                 default:
1065                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1066                                 "Invalid expression \"%s\" in file %s",
1067                                 expr, r->filename);
1068                     *was_error = 1;
1069                     return retval;
1070                 }
1071                 break;
1072             }
1073             if (current == (struct parse_node *) NULL) {
1074                 new->left = root;
1075                 new->left->parent = new;
1076                 new->parent = (struct parse_node *) NULL;
1077                 root = new;
1078             }
1079             else {
1080                 new->left = current->right;
1081                 current->right = new;
1082                 new->parent = current;
1083             }
1084             current = new;
1085             break;
1086
1087         case token_not:
1088 #ifdef DEBUG_INCLUDE
1089             memcpy(&debug[debug_pos], "     Token: not\n",
1090                     sizeof("     Token: not\n"));
1091             debug_pos += sizeof("     Token: not\n");
1092 #endif
1093             if (current == (struct parse_node *) NULL) {
1094                 root = current = new;
1095                 break;
1096             }
1097             /* Percolate upwards */
1098             if (current != (struct parse_node *) NULL) {
1099                 switch (current->token.type) {
1100                 case token_not:
1101                 case token_eq:
1102                 case token_ne:
1103                 case token_and:
1104                 case token_or:
1105                 case token_lbrace:
1106                 case token_ge:
1107                 case token_gt:
1108                 case token_le:
1109                 case token_lt:
1110                     break;
1111                 default:
1112                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1113                                   "Invalid expression \"%s\" in file %s",
1114                                   expr, r->filename);
1115                     *was_error = 1;
1116                     return retval;
1117                 }
1118             }
1119             if (current == (struct parse_node *) NULL) {
1120                 new->left = root;
1121                 new->left->parent = new;
1122                 new->parent = (struct parse_node *) NULL;
1123                 root = new;
1124             }
1125             else {
1126                 new->left = current->right;
1127                 current->right = new;
1128                 new->parent = current;
1129             }
1130             current = new;
1131             break;
1132
1133         case token_eq:
1134         case token_ne:
1135         case token_ge:
1136         case token_gt:
1137         case token_le:
1138         case token_lt:
1139 #ifdef DEBUG_INCLUDE
1140             memcpy(&debug[debug_pos], "     Token: eq/ne/ge/gt/le/lt\n",
1141                     sizeof("     Token: eq/ne/ge/gt/le/lt\n"));
1142             debug_pos += sizeof("     Token: eq/ne/ge/gt/le/lt\n");
1143 #endif
1144             if (current == (struct parse_node *) NULL) {
1145                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1146                               "Invalid expression \"%s\" in file %s",
1147                               expr, r->filename);
1148                 *was_error = 1;
1149                 return retval;
1150             }
1151             /* Percolate upwards */
1152             while (current != (struct parse_node *) NULL) {
1153                 switch (current->token.type) {
1154                 case token_string:
1155                 case token_re:
1156                 case token_group:
1157                     current = current->parent;
1158                     continue;
1159                 case token_lbrace:
1160                 case token_and:
1161                 case token_or:
1162                     break;
1163                 case token_not:
1164                 case token_eq:
1165                 case token_ne:
1166                 case token_ge:
1167                 case token_gt:
1168                 case token_le:
1169                 case token_lt:
1170                 default:
1171                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1172                                 "Invalid expression \"%s\" in file %s",
1173                                 expr, r->filename);
1174                     *was_error = 1;
1175                     return retval;
1176                 }
1177                 break;
1178             }
1179             if (current == (struct parse_node *) NULL) {
1180                 new->left = root;
1181                 new->left->parent = new;
1182                 new->parent = (struct parse_node *) NULL;
1183                 root = new;
1184             }
1185             else {
1186                 new->left = current->right;
1187                 current->right = new;
1188                 new->parent = current;
1189             }
1190             current = new;
1191             break;
1192
1193         case token_rbrace:
1194 #ifdef DEBUG_INCLUDE
1195             memcpy (&debug[debug_pos], "     Token: rbrace\n",
1196                     sizeof ("     Token: rbrace\n"));
1197             debug_pos += sizeof ("     Token: rbrace\n");
1198 #endif
1199             while (current != (struct parse_node *) NULL) {
1200                 if (current->token.type == token_lbrace) {
1201                     current->token.type = token_group;
1202                     break;
1203                 }
1204                 current = current->parent;
1205             }
1206             if (current == (struct parse_node *) NULL) {
1207                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1208                             "Unmatched ')' in \"%s\" in file %s",
1209                             expr, r->filename);
1210                 *was_error = 1;
1211                 return retval;
1212             }
1213             break;
1214
1215         case token_lbrace:
1216 #ifdef DEBUG_INCLUDE
1217             memcpy (&debug[debug_pos], "     Token: lbrace\n",
1218                     sizeof ("     Token: lbrace\n"));
1219             debug_pos += sizeof ("     Token: lbrace\n");
1220 #endif
1221             if (current == (struct parse_node *) NULL) {
1222                 root = current = new;
1223                 break;
1224             }
1225             /* Percolate upwards */
1226             if (current != (struct parse_node *) NULL) {
1227                 switch (current->token.type) {
1228                 case token_not:
1229                 case token_eq:
1230                 case token_ne:
1231                 case token_and:
1232                 case token_or:
1233                 case token_lbrace:
1234                 case token_ge:
1235                 case token_gt:
1236                 case token_le:
1237                 case token_lt:
1238                     break;
1239                 case token_string:
1240                 case token_re:
1241                 case token_group:
1242                 default:
1243                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1244                                 "Invalid expression \"%s\" in file %s",
1245                                 expr, r->filename);
1246                     *was_error = 1;
1247                     return retval;
1248                 }
1249             }
1250             if (current == (struct parse_node *) NULL) {
1251                 new->left = root;
1252                 new->left->parent = new;
1253                 new->parent = (struct parse_node *) NULL;
1254                 root = new;
1255             }
1256             else {
1257                 new->left = current->right;
1258                 current->right = new;
1259                 new->parent = current;
1260             }
1261             current = new;
1262             break;
1263         default:
1264             break;
1265         }
1266     }
1267
1268     /* Evaluate Parse Tree */
1269     current = root;
1270     while (current != (struct parse_node *) NULL) {
1271         switch (current->token.type) {
1272         case token_string:
1273 #ifdef DEBUG_INCLUDE
1274             memcpy (&debug[debug_pos], "     Evaluate string\n",
1275                     sizeof ("     Evaluate string\n"));
1276             debug_pos += sizeof ("     Evaluate string\n");
1277 #endif
1278             buffer = ap_ssi_parse_string(ctx, current->token.value, NULL, 
1279                                          MAX_STRING_LEN, 0);
1280             current->token.value = buffer;
1281             current->value = (current->token.value[0] != '\0');
1282             current->done = 1;
1283             current = current->parent;
1284             break;
1285
1286         case token_re:
1287             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1288                           "No operator before regex of expr \"%s\" in file %s",
1289                           expr, r->filename);
1290             *was_error = 1;
1291             return retval;
1292
1293         case token_and:
1294         case token_or:
1295 #ifdef DEBUG_INCLUDE
1296             memcpy(&debug[debug_pos], "     Evaluate and/or\n",
1297                     sizeof("     Evaluate and/or\n"));
1298             debug_pos += sizeof("     Evaluate and/or\n");
1299 #endif
1300             if (current->left  == (struct parse_node *) NULL ||
1301                 current->right == (struct parse_node *) NULL) {
1302                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1303                               "Invalid expression \"%s\" in file %s",
1304                               expr, r->filename);
1305                 *was_error = 1;
1306                 return retval;
1307             }
1308             if (!current->left->done) {
1309                 switch (current->left->token.type) {
1310                 case token_string:
1311                     buffer = ap_ssi_parse_string(ctx, current->left->token.value,
1312                                                  NULL, MAX_STRING_LEN, 0);
1313                     current->left->token.value = buffer;
1314                     current->left->value = 
1315                                        (current->left->token.value[0] != '\0');
1316                     current->left->done = 1;
1317                     break;
1318                 default:
1319                     current = current->left;
1320                     continue;
1321                 }
1322             }
1323             if (!current->right->done) {
1324                 switch (current->right->token.type) {
1325                 case token_string:
1326                     buffer = ap_ssi_parse_string(ctx, current->right->token.value,
1327                                                  NULL, MAX_STRING_LEN, 0);
1328                     current->right->token.value = buffer;
1329                     current->right->value = 
1330                                       (current->right->token.value[0] != '\0');
1331                     current->right->done = 1;
1332                     break;
1333                 default:
1334                     current = current->right;
1335                     continue;
1336                 }
1337             }
1338 #ifdef DEBUG_INCLUDE
1339             debug_pos += sprintf (&debug[debug_pos], "     Left: %c\n",
1340                                   current->left->value ? '1' : '0');
1341             debug_pos += sprintf (&debug[debug_pos], "     Right: %c\n",
1342                                   current->right->value ? '1' : '0');
1343 #endif
1344             if (current->token.type == token_and) {
1345                 current->value = current->left->value && current->right->value;
1346             }
1347             else {
1348                 current->value = current->left->value || current->right->value;
1349             }
1350 #ifdef DEBUG_INCLUDE
1351             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1352                                   current->value ? '1' : '0');
1353 #endif
1354             current->done = 1;
1355             current = current->parent;
1356             break;
1357
1358         case token_eq:
1359         case token_ne:
1360 #ifdef DEBUG_INCLUDE
1361             memcpy (&debug[debug_pos], "     Evaluate eq/ne\n",
1362                     sizeof ("     Evaluate eq/ne\n"));
1363             debug_pos += sizeof ("     Evaluate eq/ne\n");
1364 #endif
1365             if ((current->left == (struct parse_node *) NULL) ||
1366                 (current->right == (struct parse_node *) NULL) ||
1367                 (current->left->token.type != token_string) ||
1368                 ((current->right->token.type != token_string) &&
1369                  (current->right->token.type != token_re))) {
1370                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1371                             "Invalid expression \"%s\" in file %s",
1372                             expr, r->filename);
1373                 *was_error = 1;
1374                 return retval;
1375             }
1376             buffer = ap_ssi_parse_string(ctx, current->left->token.value,
1377                                          NULL, MAX_STRING_LEN, 0);
1378             current->left->token.value = buffer;
1379             buffer = ap_ssi_parse_string(ctx, current->right->token.value,
1380                                          NULL, MAX_STRING_LEN, 0);
1381             current->right->token.value = buffer;
1382             if (current->right->token.type == token_re) {
1383 #ifdef DEBUG_INCLUDE
1384                 debug_pos += sprintf (&debug[debug_pos],
1385                                       "     Re Compare (%s) with /%s/\n",
1386                                       current->left->token.value,
1387                                       current->right->token.value);
1388 #endif
1389                 current->value =
1390                     re_check(ctx, current->left->token.value,
1391                              current->right->token.value);
1392             }
1393             else {
1394 #ifdef DEBUG_INCLUDE
1395                 debug_pos += sprintf (&debug[debug_pos],
1396                                       "     Compare (%s) with (%s)\n",
1397                                       current->left->token.value,
1398                                       current->right->token.value);
1399 #endif
1400                 current->value =
1401                     (strcmp(current->left->token.value,
1402                             current->right->token.value) == 0);
1403             }
1404             if (current->token.type == token_ne) {
1405                 current->value = !current->value;
1406             }
1407 #ifdef DEBUG_INCLUDE
1408             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1409                                   current->value ? '1' : '0');
1410 #endif
1411             current->done = 1;
1412             current = current->parent;
1413             break;
1414         case token_ge:
1415         case token_gt:
1416         case token_le:
1417         case token_lt:
1418 #ifdef DEBUG_INCLUDE
1419             memcpy (&debug[debug_pos], "     Evaluate ge/gt/le/lt\n",
1420                     sizeof ("     Evaluate ge/gt/le/lt\n"));
1421             debug_pos += sizeof ("     Evaluate ge/gt/le/lt\n");
1422 #endif
1423             if ((current->left == (struct parse_node *) NULL) ||
1424                 (current->right == (struct parse_node *) NULL) ||
1425                 (current->left->token.type != token_string) ||
1426                 (current->right->token.type != token_string)) {
1427                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1428                             "Invalid expression \"%s\" in file %s",
1429                             expr, r->filename);
1430                 *was_error = 1;
1431                 return retval;
1432             }
1433             buffer = ap_ssi_parse_string(ctx, current->left->token.value, NULL,
1434                                          MAX_STRING_LEN, 0);
1435             current->left->token.value = buffer;
1436             buffer = ap_ssi_parse_string(ctx, current->right->token.value, NULL,
1437                                          MAX_STRING_LEN, 0);
1438             current->right->token.value = buffer;
1439 #ifdef DEBUG_INCLUDE
1440             debug_pos += sprintf (&debug[debug_pos],
1441                                   "     Compare (%s) with (%s)\n",
1442                                   current->left->token.value,
1443                                   current->right->token.value);
1444 #endif
1445             current->value =
1446                 strcmp(current->left->token.value,
1447                        current->right->token.value);
1448             if (current->token.type == token_ge) {
1449                 current->value = current->value >= 0;
1450             }
1451             else if (current->token.type == token_gt) {
1452                 current->value = current->value > 0;
1453             }
1454             else if (current->token.type == token_le) {
1455                 current->value = current->value <= 0;
1456             }
1457             else if (current->token.type == token_lt) {
1458                 current->value = current->value < 0;
1459             }
1460             else {
1461                 current->value = 0;     /* Don't return -1 if unknown token */
1462             }
1463 #ifdef DEBUG_INCLUDE
1464             debug_pos += sprintf (&debug[debug_pos], "     Returning %c\n",
1465                                   current->value ? '1' : '0');
1466 #endif
1467             current->done = 1;
1468             current = current->parent;
1469             break;
1470
1471         case token_not:
1472             if (current->right != (struct parse_node *) NULL) {
1473                 if (!current->right->done) {
1474                     current = current->right;
1475                     continue;
1476                 }
1477                 current->value = !current->right->value;
1478             }
1479             else {
1480                 current->value = 0;
1481             }
1482 #ifdef DEBUG_INCLUDE
1483             debug_pos += sprintf (&debug[debug_pos], "     Evaluate !: %c\n",
1484                                   current->value ? '1' : '0');
1485 #endif
1486             current->done = 1;
1487             current = current->parent;
1488             break;
1489
1490         case token_group:
1491             if (current->right != (struct parse_node *) NULL) {
1492                 if (!current->right->done) {
1493                     current = current->right;
1494                     continue;
1495                 }
1496                 current->value = current->right->value;
1497             }
1498             else {
1499                 current->value = 1;
1500             }
1501 #ifdef DEBUG_INCLUDE
1502             debug_pos += sprintf (&debug[debug_pos], "     Evaluate (): %c\n",
1503                                   current->value ? '1' : '0');
1504 #endif
1505             current->done = 1;
1506             current = current->parent;
1507             break;
1508
1509         case token_lbrace:
1510             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1511                         "Unmatched '(' in \"%s\" in file %s",
1512                         expr, r->filename);
1513             *was_error = 1;
1514             return retval;
1515
1516         case token_rbrace:
1517             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1518                         "Unmatched ')' in \"%s\" in file %s",
1519                         expr, r->filename);
1520             *was_error = 1;
1521             return retval;
1522
1523         default:
1524             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1525                           "bad token type");
1526             *was_error = 1;
1527             return retval;
1528         }
1529     }
1530
1531     retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
1532     return (retval);
1533 }
1534
1535
1536 /*
1537  * +-------------------------------------------------------+
1538  * |                                                       |
1539  * |                    Action Handlers
1540  * |                                                       |
1541  * +-------------------------------------------------------+
1542  */
1543
1544 /*
1545  * Extract the next tag name and value.
1546  * If there are no more tags, set the tag name to NULL.
1547  * The tag value is html decoded if dodecode is non-zero.
1548  * The tag value may be NULL if there is no tag value..
1549  */
1550 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1551                                      char **tag_val, int dodecode)
1552 {
1553     if (!ctx->intern->argv) {
1554         *tag = NULL;
1555         *tag_val = NULL;
1556
1557         return;
1558     }
1559
1560     *tag_val = ctx->intern->argv->value;
1561     *tag = ctx->intern->argv->name;
1562
1563     ctx->intern->argv = ctx->intern->argv->next;
1564
1565     if (dodecode && *tag_val) {
1566         decodehtml(*tag_val);
1567     }
1568
1569     return;
1570 }
1571
1572 /* ensure that path is relative, and does not contain ".." elements
1573  * ensentially ensure that it does not match the regex:
1574  * (^/|(^|/)\.\.(/|$))
1575  * XXX: Simply replace with apr_filepath_merge                    
1576  */
1577 static int is_only_below(const char *path)
1578 {
1579 #ifdef HAVE_DRIVE_LETTERS
1580     if (path[1] == ':') 
1581         return 0;
1582 #endif
1583 #ifdef NETWARE
1584     if (ap_strchr_c(path, ':'))
1585         return 0;
1586 #endif
1587     if (path[0] == '/') {
1588         return 0;
1589     }
1590     while (*path) {
1591         int dots = 0;
1592         while (path[dots] == '.')
1593             ++dots;
1594 #if defined(WIN32) 
1595         /* If the name is canonical this is redundant
1596          * but in security, redundancy is worthwhile.
1597          * Does OS2 belong here (accepts ... for ..)?
1598          */
1599         if (dots > 1 && (!path[dots] || path[dots] == '/'))
1600             return 0;
1601 #else
1602         if (dots == 2 && (!path[dots] || path[dots] == '/'))
1603             return 0;
1604 #endif
1605         path += dots;
1606         /* Advance to either the null byte at the end of the
1607          * string or the character right after the next slash,
1608          * whichever comes first
1609          */
1610         while (*path && (*path++ != '/')) {
1611             continue;
1612         }
1613     }
1614     return 1;
1615 }
1616
1617 static int find_file(request_rec *r, const char *directive, const char *tag,
1618                      char *tag_val, apr_finfo_t *finfo)
1619 {
1620     char *to_send = tag_val;
1621     request_rec *rr = NULL;
1622     int ret=0;
1623     char *error_fmt = NULL;
1624     apr_status_t rv = APR_SUCCESS;
1625
1626     if (!strcmp(tag, "file")) {
1627         /* XXX: Port to apr_filepath_merge
1628          * be safe; only files in this directory or below allowed 
1629          */
1630         if (!is_only_below(tag_val)) {
1631             error_fmt = "unable to access file \"%s\" "
1632                         "in parsed file %s";
1633         }
1634         else {
1635             ap_getparents(tag_val);    /* get rid of any nasties */
1636
1637             /* note: it is okay to pass NULL for the "next filter" since
1638                we never attempt to "run" this sub request. */
1639             rr = ap_sub_req_lookup_file(tag_val, r, NULL);
1640
1641             if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1642                 to_send = rr->filename;
1643                 if ((rv = apr_stat(finfo, to_send, 
1644                     APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1645                     && rv != APR_INCOMPLETE) {
1646                     error_fmt = "unable to get information about \"%s\" "
1647                         "in parsed file %s";
1648                 }
1649             }
1650             else {
1651                 error_fmt = "unable to lookup information about \"%s\" "
1652                             "in parsed file %s";
1653             }
1654         }
1655
1656         if (error_fmt) {
1657             ret = -1;
1658             ap_log_rerror(APLOG_MARK, APLOG_ERR,
1659                           rv, r, error_fmt, to_send, r->filename);
1660         }
1661
1662         if (rr) ap_destroy_sub_req(rr);
1663         
1664         return ret;
1665     }
1666     else if (!strcmp(tag, "virtual")) {
1667         /* note: it is okay to pass NULL for the "next filter" since
1668            we never attempt to "run" this sub request. */
1669         rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1670
1671         if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1672             memcpy((char *) finfo, (const char *) &rr->finfo,
1673                    sizeof(rr->finfo));
1674             ap_destroy_sub_req(rr);
1675             return 0;
1676         }
1677         else {
1678             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1679                         "unable to get information about \"%s\" "
1680                         "in parsed file %s",
1681                         tag_val, r->filename);
1682             ap_destroy_sub_req(rr);
1683             return -1;
1684         }
1685     }
1686     else {
1687         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1688                     "unknown parameter \"%s\" to tag %s in %s",
1689                     tag, directive, r->filename);
1690         return -1;
1691     }
1692 }
1693
1694 /*
1695  * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1696  */
1697 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1698                                    apr_bucket_brigade *bb)
1699 {
1700     request_rec *r = f->r;
1701
1702     if (!ctx->argc) {
1703         ap_log_rerror(APLOG_MARK,
1704                       (ctx->flags & SSI_FLAG_PRINTING)
1705                           ? APLOG_ERR : APLOG_WARNING,
1706                       0, r, "missing argument for include element in %s",
1707                       r->filename);
1708     }
1709
1710     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1711         return APR_SUCCESS;
1712     }
1713
1714     if (!ctx->argc) {
1715         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1716         return APR_SUCCESS;
1717     }
1718
1719     while (1) {
1720         char *tag     = NULL;
1721         char *tag_val = NULL;
1722         request_rec *rr = NULL;
1723         char *error_fmt = NULL;
1724         char *parsed_string;
1725
1726         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1727         if (!tag || !tag_val) {
1728             break;
1729         }
1730
1731         if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1732             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1733                           "\"%s\" to tag include in %s", tag, r->filename);
1734             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1735             break;
1736         }
1737
1738         parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, MAX_STRING_LEN,
1739                                             SSI_EXPAND_DROP_NAME);
1740         if (tag[0] == 'f') {
1741             /* XXX: Port to apr_filepath_merge
1742              * be safe; only files in this directory or below allowed 
1743              */
1744             if (!is_only_below(parsed_string)) {
1745                 error_fmt = "unable to include file \"%s\" in parsed file %s";
1746             }
1747             else {
1748                 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1749             }
1750         }
1751         else {
1752             rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1753         }
1754
1755         if (!error_fmt && rr->status != HTTP_OK) {
1756             error_fmt = "unable to include \"%s\" in parsed file %s";
1757         }
1758
1759         if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1760             rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1761
1762             error_fmt = "unable to include potential exec \"%s\" in parsed "
1763                         "file %s";
1764         }
1765
1766         if (!error_fmt) {
1767             int founddupe = 0;
1768             request_rec *p, *q;
1769
1770             /* try to avoid recursive includes.  We do this by walking
1771              * up the r->main list of subrequests, and at each level
1772              * walking back through any internal redirects.  At each
1773              * step, we compare the filenames and the URIs.  
1774              *
1775              * The filename comparison catches a recursive include
1776              * with an ever-changing URL, eg.
1777              * <!--#include virtual=
1778              *      "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
1779              * which, although they would eventually be caught because
1780              * we have a limit on the length of files, etc., can 
1781              * recurse for a while.
1782              *
1783              * The URI comparison catches the case where the filename
1784              * is changed while processing the request, so the 
1785              * current name is never the same as any previous one.
1786              * This can happen with "DocumentRoot /foo" when you
1787              * request "/" on the server and it includes "/".
1788              * This only applies to modules such as mod_dir that 
1789              * (somewhat improperly) mess with r->filename outside 
1790              * of a filename translation phase.
1791              */
1792              for (p = r; p && !founddupe; p = p->main) {
1793                 for (q = p; q; q = q->prev) {
1794                     if ((q->filename && rr->filename && 
1795                         (strcmp(q->filename, rr->filename) == 0)) ||
1796                         ((*q->uri == '/') && 
1797                         (strcmp(q->uri, rr->uri) == 0))) {
1798
1799                         founddupe = 1;
1800                         break;
1801                     }
1802                 }
1803             }
1804
1805             if (p) {
1806                 error_fmt = "Recursive include of \"%s\" in parsed file %s";
1807             }
1808         }
1809
1810         /* See the Kludge in includes_filter for why.
1811          * Basically, it puts a bread crumb in here, then looks
1812          * for the crumb later to see if its been here.
1813          */
1814         if (rr) {
1815             ap_set_module_config(rr->request_config, &include_module, r);
1816         }
1817
1818         if (!error_fmt && ap_run_sub_req(rr)) {
1819             error_fmt = "unable to include \"%s\" in parsed file %s";
1820         }
1821
1822         if (error_fmt) {
1823             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1824                           r->filename);
1825             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1826         }
1827
1828         /* destroy the sub request */
1829         if (rr) {
1830             ap_destroy_sub_req(rr);
1831         }
1832
1833         if (error_fmt) {
1834             break;
1835         }
1836     }
1837
1838     return APR_SUCCESS;
1839 }
1840
1841 /*
1842  * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1843  */
1844 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1845                                 apr_bucket_brigade *bb)
1846 {
1847     enum {E_NONE, E_URL, E_ENTITY} encode;
1848     request_rec *r = f->r;
1849
1850     if (!ctx->argc) {
1851         ap_log_rerror(APLOG_MARK,
1852                       (ctx->flags & SSI_FLAG_PRINTING)
1853                           ? APLOG_ERR : APLOG_WARNING,
1854                       0, r, "missing argument for echo element in %s",
1855                       r->filename);
1856     }
1857
1858     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1859         return APR_SUCCESS;
1860     }
1861
1862     if (!ctx->argc) {
1863         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1864         return APR_SUCCESS;
1865     }
1866
1867     encode = E_ENTITY;
1868
1869     while (1) {
1870         char *tag = NULL;
1871         char *tag_val = NULL;
1872
1873         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1874         if (!tag || !tag_val) {
1875             break;
1876         }
1877
1878         if (!strcmp(tag, "var")) {
1879             const char *val;
1880             const char *echo_text = NULL;
1881             apr_size_t e_len;
1882
1883             val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1884                                                       MAX_STRING_LEN,
1885                                                       SSI_EXPAND_DROP_NAME),
1886                                   ctx);
1887
1888             if (val) {
1889                 switch(encode) {
1890                 case E_NONE:
1891                     echo_text = val;
1892                     break;
1893                 case E_URL:
1894                     echo_text = ap_escape_uri(ctx->dpool, val);
1895                     break;
1896                 case E_ENTITY:
1897                     echo_text = ap_escape_html(ctx->dpool, val);
1898                     break;
1899                 }
1900
1901                 e_len = strlen(echo_text);
1902             }
1903             else {
1904                 include_server_config *sconf;
1905
1906                 sconf = ap_get_module_config(r->server->module_config,
1907                                              &include_module);
1908                 echo_text = sconf->undefined_echo;
1909                 e_len = sconf->undefined_echo_len;
1910             }
1911
1912             APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1913                                     apr_pstrmemdup(ctx->pool, echo_text, e_len),
1914                                     e_len, ctx->pool, f->c->bucket_alloc));
1915         }
1916         else if (!strcmp(tag, "encoding")) {
1917             if (!strcasecmp(tag_val, "none")) {
1918                 encode = E_NONE;
1919             }
1920             else if (!strcasecmp(tag_val, "url")) {
1921                 encode = E_URL;
1922             }
1923             else if (!strcasecmp(tag_val, "entity")) {
1924                 encode = E_ENTITY;
1925             }
1926             else {
1927                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1928                               "\"%s\" to parameter \"encoding\" of tag echo in "
1929                               "%s", tag_val, r->filename);
1930                 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1931                 break;
1932             }
1933         }
1934         else {
1935             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1936                           "\"%s\" in tag echo of %s", tag, r->filename);
1937             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1938             break;
1939         }
1940     }
1941
1942     return APR_SUCCESS;
1943 }
1944
1945 /*
1946  * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."] -->
1947  */
1948 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1949                                   apr_bucket_brigade *bb)
1950 {
1951     request_rec *r = f->r;
1952     apr_table_t *env = r->subprocess_env;
1953
1954     if (!ctx->argc) {
1955         ap_log_rerror(APLOG_MARK,
1956                       (ctx->flags & SSI_FLAG_PRINTING)
1957                           ? APLOG_ERR : APLOG_WARNING,
1958                       0, r, "missing argument for config element in %s",
1959                       r->filename);
1960     }
1961
1962     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1963         return APR_SUCCESS;
1964     }
1965
1966     if (!ctx->argc) {
1967         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1968         return APR_SUCCESS;
1969     }
1970
1971     while (1) {
1972         char *tag     = NULL;
1973         char *tag_val = NULL;
1974         char *parsed_string;
1975
1976         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1977         if (!tag || !tag_val) {
1978             break;
1979         }
1980
1981         if (!strcmp(tag, "errmsg")) {
1982             ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1983                                                  SSI_EXPAND_DROP_NAME);
1984         }
1985         else if (!strcmp(tag, "timefmt")) {
1986             apr_time_t date = r->request_time;
1987
1988             ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1989                                                 SSI_EXPAND_DROP_NAME);
1990
1991             apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, 
1992                            ctx->time_str, 0));
1993             apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, 
1994                            ctx->time_str, 1));
1995             apr_table_setn(env, "LAST_MODIFIED",
1996                            ap_ht_time(r->pool, r->finfo.mtime, 
1997                            ctx->time_str, 0));
1998         }
1999         else if (!strcmp(tag, "sizefmt")) {
2000             parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL,
2001                                                 MAX_STRING_LEN,
2002                                                 SSI_EXPAND_DROP_NAME);
2003             if (!strcmp(parsed_string, "bytes")) {
2004                 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
2005             }
2006             else if (!strcmp(parsed_string, "abbrev")) {
2007                 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
2008             }
2009             else {
2010                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
2011                               "\"%s\" to parameter \"sizefmt\" of tag config "
2012                               "in %s", parsed_string, r->filename);
2013                 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2014                 break;
2015             }
2016         }
2017         else {
2018             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
2019                           "\"%s\" to tag config in %s", tag, r->filename);
2020             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2021             break;
2022         }
2023     }
2024
2025     return APR_SUCCESS;
2026 }
2027
2028 /*
2029  * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
2030  */
2031 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
2032                                  apr_bucket_brigade *bb)
2033 {
2034     request_rec *r = f->r;
2035
2036     if (!ctx->argc) {
2037         ap_log_rerror(APLOG_MARK,
2038                       (ctx->flags & SSI_FLAG_PRINTING)
2039                           ? APLOG_ERR : APLOG_WARNING,
2040                       0, r, "missing argument for fsize element in %s",
2041                       r->filename);
2042     }
2043
2044     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2045         return APR_SUCCESS;
2046     }
2047
2048     if (!ctx->argc) {
2049         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2050         return APR_SUCCESS;
2051     }
2052
2053     while (1) {
2054         char *tag     = NULL;
2055         char *tag_val = NULL;
2056         apr_finfo_t finfo;
2057         char *parsed_string;
2058
2059         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2060         if (!tag || !tag_val) {
2061             break;
2062         }
2063
2064         parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, MAX_STRING_LEN,
2065                                             SSI_EXPAND_DROP_NAME);
2066
2067         if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
2068             char *buf;
2069             apr_size_t len;
2070
2071             if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
2072                 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
2073                 len = 4; /* omit the \0 terminator */
2074             }
2075             else {
2076                 apr_size_t l, x, pos;
2077                 char *tmp;
2078
2079                 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
2080                 len = l = strlen(tmp);
2081
2082                 for (x = 0; x < l; ++x) {
2083                     if (x && !((l - x) % 3)) {
2084                         ++len;
2085                     }
2086                 }
2087
2088                 if (len == l) {
2089                     buf = apr_pstrmemdup(ctx->pool, tmp, len);
2090                 }
2091                 else {
2092                     buf = apr_palloc(ctx->pool, len);
2093
2094                     for (pos = x = 0; x < l; ++x) {
2095                         if (x && !((l - x) % 3)) {
2096                             buf[pos++] = ',';
2097                         }
2098                         buf[pos++] = tmp[x];
2099                     }
2100                 }
2101             }
2102
2103             APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
2104                                     ctx->pool, f->c->bucket_alloc));
2105         }
2106         else {
2107             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2108             break;
2109         }
2110     }
2111
2112     return APR_SUCCESS;
2113 }
2114
2115 /*
2116  * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
2117  */
2118 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
2119                                     apr_bucket_brigade *bb)
2120 {
2121     request_rec *r = f->r;
2122
2123     if (!ctx->argc) {
2124         ap_log_rerror(APLOG_MARK,
2125                       (ctx->flags & SSI_FLAG_PRINTING)
2126                           ? APLOG_ERR : APLOG_WARNING,
2127                       0, r, "missing argument for flastmod element in %s",
2128                       r->filename);
2129     }
2130
2131     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2132         return APR_SUCCESS;
2133     }
2134
2135     if (!ctx->argc) {
2136         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2137         return APR_SUCCESS;
2138     }
2139
2140     while (1) {
2141         char *tag     = NULL;
2142         char *tag_val = NULL;
2143         apr_finfo_t  finfo;
2144         char *parsed_string;
2145
2146         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2147         if (!tag || !tag_val) {
2148             break;
2149         }
2150
2151         parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, MAX_STRING_LEN,
2152                                             SSI_EXPAND_DROP_NAME);
2153
2154         if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2155             char *t_val;
2156             apr_size_t t_len;
2157
2158             t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2159             t_len = strlen(t_val);
2160
2161             APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2162                                     ctx->pool, f->c->bucket_alloc));
2163         }
2164         else {
2165             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2166             break;
2167         }
2168     }
2169
2170     return APR_SUCCESS;
2171 }
2172
2173 /*
2174  * <!--#if expr="..." -->
2175  */
2176 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2177                               apr_bucket_brigade *bb)
2178 {
2179     char *tag = NULL;
2180     char *expr = NULL;
2181     char debug_buf[MAX_DEBUG_SIZE];
2182     request_rec *r = f->r;
2183     int expr_ret, was_error, was_unmatched;
2184
2185     if (ctx->argc != 1) {
2186         ap_log_rerror(APLOG_MARK,
2187                       (ctx->flags & SSI_FLAG_PRINTING)
2188                           ? APLOG_ERR : APLOG_WARNING,
2189                       0, r, (ctx->argc)
2190                                 ? "too many arguments for if element in %s"
2191                                 : "missing expr argument for if element in %s",
2192                       r->filename);
2193     }
2194
2195     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2196         ++(ctx->if_nesting_level);
2197         return APR_SUCCESS;
2198     }
2199
2200     if (ctx->argc != 1) {
2201         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2202         return APR_SUCCESS;
2203     }
2204
2205     ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2206
2207     if (strcmp(tag, "expr")) {
2208         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2209                       "to tag if in %s", tag, r->filename);
2210         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2211         return APR_SUCCESS;
2212     }
2213
2214     if (!expr) {
2215         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
2216                       "element in %s", r->filename);
2217         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2218         return APR_SUCCESS;
2219     }
2220 #ifdef DEBUG_INCLUDE
2221     do {
2222         apr_size_t d_len = 0;
2223         d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr);
2224         APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_heap_create(debug_buf, d_len,
2225                                 NULL, f->c->bucket_alloc));
2226
2227     } while (0);
2228 #endif
2229
2230     expr_ret = parse_expr(r, ctx, expr, &was_error, &was_unmatched, debug_buf);
2231
2232     if (was_error) {
2233         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2234         return APR_SUCCESS;
2235     }
2236
2237     if (was_unmatched) {
2238         DUMP_PARSE_EXPR_DEBUG("\nUnmatched '\n", f, bb);
2239     }
2240     DUMP_PARSE_EXPR_DEBUG(debug_buf, f, bb);
2241
2242     if (expr_ret) {
2243         ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2244     }
2245     else {
2246         ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2247     }
2248
2249     LOG_COND_STATUS(ctx, f, bb, "   if");
2250     ctx->if_nesting_level = 0;
2251
2252     return APR_SUCCESS;
2253 }
2254
2255 /*
2256  * <!--#elif expr="..." -->
2257  */
2258 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2259                                 apr_bucket_brigade *bb)
2260 {
2261     char *tag = NULL;
2262     char *expr = NULL;
2263     request_rec *r = f->r;
2264     char debug_buf[MAX_DEBUG_SIZE];
2265     int expr_ret, was_error, was_unmatched;
2266
2267     if (ctx->argc != 1) {
2268         ap_log_rerror(APLOG_MARK,
2269                       (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2270                       0, r, (ctx->argc)
2271                                 ? "too many arguments for if element in %s"
2272                                 : "missing expr argument for if element in %s",
2273                       r->filename);
2274     }
2275
2276     if (ctx->if_nesting_level) {
2277         return APR_SUCCESS;
2278     }
2279
2280     if (ctx->argc != 1) {
2281         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2282         return APR_SUCCESS;
2283     }
2284
2285     ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2286
2287     if (strcmp(tag, "expr")) {
2288         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2289                       "to tag if in %s", tag, r->filename);
2290         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2291         return APR_SUCCESS;
2292     }
2293
2294     if (!expr) {
2295         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
2296                       "statement: %s", r->filename);
2297         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2298         return APR_SUCCESS;
2299     }
2300 #ifdef DEBUG_INCLUDE
2301     do {
2302         apr_size_t d_len = 0;
2303         d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr);
2304         APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_heap_create(debug_buf, d_len,
2305                                 NULL, f->c->bucket_alloc));
2306     } while (0);
2307 #endif
2308
2309     LOG_COND_STATUS(ctx, f, bb, " elif");
2310
2311     if (ctx->flags & SSI_FLAG_COND_TRUE) {
2312         ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2313         return APR_SUCCESS;
2314     }
2315
2316     expr_ret = parse_expr(r, ctx, expr, &was_error, &was_unmatched, debug_buf);
2317
2318     if (was_error) {
2319         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2320         return APR_SUCCESS;
2321     }
2322
2323     if (was_unmatched) {
2324         DUMP_PARSE_EXPR_DEBUG("\nUnmatched '\n", f, bb);
2325     }
2326     DUMP_PARSE_EXPR_DEBUG(debug_buf, f, bb);
2327
2328     if (expr_ret) {
2329         ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2330     }
2331     else {
2332         ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2333     }
2334
2335     LOG_COND_STATUS(ctx, f, bb, " elif");
2336
2337     return APR_SUCCESS;
2338 }
2339
2340 /*
2341  * <!--#else -->
2342  */
2343 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2344                                 apr_bucket_brigade *bb)
2345 {
2346     request_rec *r = f->r;
2347
2348     if (ctx->argc) {
2349         ap_log_rerror(APLOG_MARK,
2350                       (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2351                       0, r, "else directive does not take tags in %s",
2352                       r->filename);
2353     }
2354
2355     if (ctx->if_nesting_level) {
2356         return APR_SUCCESS;
2357     }
2358
2359     if (ctx->argc) {
2360         if (ctx->flags & SSI_FLAG_PRINTING) {
2361             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2362         }
2363
2364         return APR_SUCCESS;
2365     }
2366
2367     LOG_COND_STATUS(ctx, f, bb, " else");
2368             
2369     if (ctx->flags & SSI_FLAG_COND_TRUE) {
2370         ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2371     }
2372     else {
2373         ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2374     }
2375
2376     return APR_SUCCESS;
2377 }
2378
2379 /*
2380  * <!--#endif -->
2381  */
2382 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2383                                  apr_bucket_brigade *bb)
2384 {
2385     request_rec *r = f->r;
2386
2387     if (ctx->argc) {
2388         ap_log_rerror(APLOG_MARK,
2389                       (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2390                       0, r, "endif directive does not take tags in %s",
2391                       r->filename);
2392     }
2393
2394     if (ctx->if_nesting_level) {
2395         --(ctx->if_nesting_level);
2396         return APR_SUCCESS;
2397     }
2398
2399     if (ctx->argc) {
2400         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2401         return APR_SUCCESS;
2402     }
2403
2404     LOG_COND_STATUS(ctx, f, bb, "endif");
2405     ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2406
2407     return APR_SUCCESS;
2408 }
2409
2410 /*
2411  * <!--#set var="..." value="..." ... -->
2412  */
2413 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2414                                apr_bucket_brigade *bb)
2415 {
2416     char *var = NULL;
2417     request_rec *r = f->r;
2418     request_rec *sub = r->main;
2419     apr_pool_t *p = r->pool;
2420
2421     if (ctx->argc < 2) {
2422         ap_log_rerror(APLOG_MARK,
2423                       (ctx->flags & SSI_FLAG_PRINTING)
2424                           ? APLOG_ERR : APLOG_WARNING,
2425                       0, r, "missing argument for set element in %s",
2426                       r->filename);
2427     }
2428
2429     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2430         return APR_SUCCESS;
2431     }
2432
2433     if (ctx->argc < 2) {
2434         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2435         return APR_SUCCESS;
2436     }
2437
2438     /* we need to use the 'main' request pool to set notes as that is 
2439      * a notes lifetime
2440      */
2441     while (sub) {
2442         p = sub->pool;
2443         sub = sub->main;
2444     }
2445
2446     while (1) {
2447         char *tag = NULL;
2448         char *tag_val = NULL;
2449
2450         ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2451
2452         if (!tag || !tag_val) {
2453             break;
2454         }
2455
2456         if (!strcmp(tag, "var")) {
2457             var = ap_ssi_parse_string(ctx, tag_val, NULL, MAX_STRING_LEN,
2458                                       SSI_EXPAND_DROP_NAME);
2459         }
2460         else if (!strcmp(tag, "value")) {
2461             char *parsed_string;
2462
2463             if (!var) {
2464                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
2465                               "precede value in set directive in %s",
2466                               r->filename);
2467                 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2468                 break;
2469             }
2470
2471             parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL,
2472                                                 MAX_STRING_LEN,
2473                                                 SSI_EXPAND_DROP_NAME);
2474             apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2475                            apr_pstrdup(p, parsed_string));
2476         }
2477         else {
2478             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
2479                           "directive in %s", r->filename);
2480             SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2481             break;
2482         }
2483     }
2484
2485     return APR_SUCCESS;
2486 }
2487
2488 /*
2489  * <!--#printenv -->
2490  */
2491 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2492                                     apr_bucket_brigade *bb)
2493 {
2494     request_rec *r = f->r;
2495     const apr_array_header_t *arr;
2496     const apr_table_entry_t *elts;
2497     int i;
2498
2499     if (ctx->argc) {
2500         ap_log_rerror(APLOG_MARK,
2501                       (ctx->flags & SSI_FLAG_PRINTING)
2502                           ? APLOG_ERR : APLOG_WARNING,
2503                       0, r, "printenv directive does not take tags in %s",
2504                       r->filename);
2505     }
2506
2507     if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2508         return APR_SUCCESS;
2509     }
2510
2511     if (ctx->argc) {
2512         SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2513         return APR_SUCCESS;
2514     }
2515
2516     arr = apr_table_elts(r->subprocess_env);
2517     elts = (apr_table_entry_t *)arr->elts;
2518
2519     for (i = 0; i < arr->nelts; ++i) {
2520         const char *key_text, *val_text;
2521         char *key_val, *next;
2522         apr_size_t k_len, v_len, kv_length;
2523
2524         /* get key */
2525         key_text = ap_escape_html(ctx->dpool, elts[i].key);
2526         k_len = strlen(key_text);
2527
2528         /* get value */
2529         val_text = elts[i].val;
2530         if (val_text == LAZY_VALUE) {
2531             val_text = add_include_vars_lazy(r, elts[i].key);
2532         }
2533         val_text = ap_escape_html(ctx->dpool, elts[i].val);
2534         v_len = strlen(val_text);
2535
2536         /* assemble result */
2537         kv_length = k_len + v_len + sizeof("=\n");
2538         key_val = apr_palloc(ctx->pool, kv_length);
2539         next = key_val;
2540
2541         memcpy(next, key_text, k_len);
2542         next += k_len;
2543         *next++ = '=';
2544         memcpy(next, val_text, v_len);
2545         next += v_len;
2546         *next++ = '\n';
2547         *next = 0;
2548
2549         APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
2550                                 ctx->pool, f->c->bucket_alloc));
2551     }
2552
2553     ctx->flush_now = 1;
2554     return APR_SUCCESS;
2555 }
2556
2557
2558 /*
2559  * +-------------------------------------------------------+
2560  * |                                                       |
2561  * |               Main Includes-Filter Engine
2562  * |                                                       |
2563  * +-------------------------------------------------------+
2564  */
2565
2566 /* This is an implementation of the BNDM search algorithm.
2567  *
2568  * Fast and Flexible String Matching by Combining Bit-parallelism and 
2569  * Suffix Automata (2001) 
2570  * Gonzalo Navarro, Mathieu Raffinot
2571  *
2572  * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2573  *
2574  * Initial code submitted by Sascha Schumann.
2575  */
2576    
2577 /* Precompile the bndm_t data structure. */
2578 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2579 {
2580     unsigned int x;
2581     const char *ne = n + nl;
2582     bndm_t *t = apr_palloc(pool, sizeof(*t));
2583
2584     memset(t->T, 0, sizeof(unsigned int) * 256);
2585     t->pattern_len = nl;
2586
2587     for (x = 1; n < ne; x <<= 1) {
2588         t->T[(unsigned char) *n++] |= x;
2589     }
2590
2591     t->x = x - 1;
2592
2593     return t;
2594 }
2595
2596 /* Implements the BNDM search algorithm (as described above).
2597  *
2598  * h  - the string to look in
2599  * hl - length of the string to look for
2600  * t  - precompiled bndm structure against the pattern 
2601  *
2602  * Returns the count of character that is the first match or hl if no
2603  * match is found.
2604  */
2605 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2606 {
2607     const char *skip;
2608     const char *he, *p, *pi;
2609     unsigned int *T, x, d;
2610     apr_size_t nl;
2611
2612     he = h + hl;
2613
2614     T = t->T;
2615     x = t->x;
2616     nl = t->pattern_len;
2617
2618     pi = h - 1; /* pi: p initial */
2619     p = pi + nl; /* compare window right to left. point to the first char */
2620
2621     while (p < he) {
2622         skip = p;
2623         d = x;
2624         do {
2625             d &= T[(unsigned char) *p--];
2626             if (!d) {
2627                 break;
2628             }
2629             if ((d & 1)) {
2630                 if (p != pi) {
2631                     skip = p;
2632                 }
2633                 else {
2634                     return p - h + 1;
2635                 }
2636             }
2637             d >>= 1;
2638         } while (d);
2639
2640         pi = skip;
2641         p = pi + nl;
2642     }
2643
2644     return hl;
2645 }
2646
2647 /*
2648  * returns the index position of the first byte of start_seq (or the len of
2649  * the buffer as non-match)
2650  */
2651 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2652                                       apr_size_t len)
2653 {
2654     struct ssi_internal_ctx *intern = ctx->intern;
2655     apr_size_t slen = intern->start_seq_pat->pattern_len;
2656     apr_size_t index;
2657     const char *p, *ep;
2658
2659     if (len < slen) {
2660         p = data; /* try partial match at the end of the buffer (below) */
2661     }
2662     else {
2663         /* try fast bndm search over the buffer
2664          * (hopefully the whole start sequence can be found in this buffer)
2665          */
2666         index = bndm(intern->start_seq_pat, data, len);
2667
2668         /* wow, found it. ready. */
2669         if (index < len) {
2670             intern->state = PARSE_DIRECTIVE;
2671             return index;
2672         }
2673         else {
2674             /* ok, the pattern can't be found as whole in the buffer,
2675              * check the end for a partial match
2676              */
2677             p = data + len - slen + 1;
2678         }
2679     }
2680
2681     ep = data + len;
2682     do {
2683         while (p < ep && *p != *intern->start_seq) {
2684             ++p;
2685         }
2686
2687         index = p - data;
2688
2689         /* found a possible start_seq start */
2690         if (p < ep) {
2691             apr_size_t pos = 1;
2692
2693             ++p;
2694             while (p < ep && *p == intern->start_seq[pos]) {
2695                 ++p;
2696                 ++pos;
2697             }
2698
2699             /* partial match found. Store the info for the next round */
2700             if (p == ep) {
2701                 intern->state = PARSE_HEAD;
2702                 intern->parse_pos = pos;
2703                 return index;
2704             }
2705         }
2706
2707         /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2708          * and a string data of "--.-" and the end of the buffer
2709          */
2710         p = data + index + 1;
2711     } while (p < ep);
2712
2713     /* no match */
2714     return len;
2715 }
2716
2717 /*
2718  * returns the first byte *after* the partial (or final) match.
2719  *
2720  * If we had to trick with the start_seq start, 'release' returns the
2721  * number of chars of the start_seq which appeared not to be part of a
2722  * full tag and may have to be passed down the filter chain.
2723  */
2724 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2725                                               const char *data,
2726                                               apr_size_t len,
2727                                               apr_size_t *release)
2728 {
2729     struct ssi_internal_ctx *intern = ctx->intern;
2730     apr_size_t pos, spos = 0;
2731     apr_size_t slen = intern->start_seq_pat->pattern_len;
2732     const char *p, *ep;
2733
2734     pos = intern->parse_pos;
2735     ep = data + len;
2736     *release = 0;
2737
2738     do {
2739         p = data;
2740
2741         while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2742             ++p;
2743             ++pos;
2744         }
2745
2746         /* full match */
2747         if (pos == slen) {
2748             intern->state = PARSE_DIRECTIVE;
2749             return (p - data);
2750         }
2751
2752         /* the whole buffer is a partial match */
2753         if (p == ep) {
2754             intern->parse_pos = pos;
2755             return (p - data);
2756         }
2757
2758         /* No match so far, but again:
2759          * We must try all combinations, since the start_seq is a random
2760          * user supplied string
2761          *
2762          * So: look if the first char of start_seq appears somewhere within
2763          * the current partial match. If it does, try to start a match that
2764          * begins with this offset. (This can happen, if a strange
2765          * start_seq like "---->" spans buffers)
2766          */
2767         if (spos < intern->parse_pos) {
2768             do {
2769                 ++spos;
2770                 ++*release;
2771                 p = intern->start_seq + spos;
2772                 pos = intern->parse_pos - spos;
2773
2774                 while (pos && *p != *intern->start_seq) {
2775                     ++p;
2776                     ++spos;
2777                     ++*release;
2778                     --pos;
2779                 }
2780
2781                 /* if a matching beginning char was found, try to match the
2782                  * remainder of the old buffer.
2783                  */
2784                 if (pos > 1) {
2785                     apr_size_t t = 1;
2786
2787                     ++p;
2788                     while (t < pos && *p == intern->start_seq[t]) {
2789                         ++p;
2790                         ++t;
2791                     }
2792
2793                     if (t == pos) {
2794                         /* yeah, another partial match found in the *old*
2795                          * buffer, now test the *current* buffer for
2796                          * continuing match
2797                          */
2798                         break;
2799                     }
2800                 }
2801             } while (pos > 1);
2802
2803             if (pos) {
2804                 continue;
2805             }
2806         }
2807
2808         break;
2809     } while (1); /* work hard to find a match ;-) */
2810
2811     /* no match at all, release all (wrongly) matched chars so far */
2812     *release = intern->parse_pos;
2813     intern->state = PARSE_PRE_HEAD;
2814     return 0;
2815 }
2816
2817 /*
2818  * returns the position after the directive
2819  */
2820 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2821                                  apr_size_t len, char ***store,
2822                                  apr_size_t **store_len)
2823 {
2824     struct ssi_internal_ctx *intern = ctx->intern;
2825     const char *p = data;
2826     const char *ep = data + len;
2827     apr_size_t pos;
2828
2829     switch (intern->state) {
2830     case PARSE_DIRECTIVE:
2831         while (p < ep && !apr_isspace(*p)) {
2832             /* we have to consider the case of missing space between directive
2833              * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2834              */
2835             if (*p == *intern->end_seq) {
2836                 intern->state = PARSE_DIRECTIVE_TAIL;
2837                 intern->parse_pos = 1;
2838                 ++p;
2839                 return (p - data);
2840             }
2841             ++p;
2842         }
2843
2844         if (p < ep) { /* found delimiter whitespace */
2845             intern->state = PARSE_DIRECTIVE_POSTNAME;
2846             *store = &intern->directive;
2847             *store_len = &intern->directive_len;
2848         }
2849
2850         break;
2851
2852     case PARSE_DIRECTIVE_TAIL:
2853         pos = intern->parse_pos;
2854
2855         while (p < ep && pos < intern->end_seq_len &&
2856                *p == intern->end_seq[pos]) {
2857             ++p;
2858             ++pos;
2859         }
2860
2861         /* full match, we're done */
2862         if (pos == intern->end_seq_len) {
2863             intern->state = PARSE_DIRECTIVE_POSTTAIL;
2864             *store = &intern->directive;
2865             *store_len = &intern->directive_len;
2866             break;
2867         }
2868
2869         /* partial match, the buffer is too small to match fully */
2870         if (p == ep) {
2871             intern->parse_pos = pos;
2872             break;
2873         }
2874
2875         /* no match. continue normal parsing */
2876         intern->state = PARSE_DIRECTIVE;
2877         return 0;
2878
2879     case PARSE_DIRECTIVE_POSTTAIL:
2880         intern->state = PARSE_EXECUTE;
2881         intern->directive_len -= intern->end_seq_len;
2882         /* continue immediately with the next state */
2883
2884     case PARSE_DIRECTIVE_POSTNAME:
2885         if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2886             intern->state = PARSE_PRE_ARG;
2887         }
2888         ctx->argc = 0;
2889         intern->argv = NULL;
2890
2891         if (!intern->directive_len) {
2892             intern->error = 1;
2893             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2894                           "directive name in parsed document %s",
2895                           intern->r->filename);
2896         }
2897         else {
2898             char *sp = intern->directive;
2899             char *sep = intern->directive + intern->directive_len;
2900
2901             /* normalize directive name */
2902             for (; sp < sep; ++sp) {
2903                 *sp = apr_tolower(*sp);
2904             }
2905         }
2906
2907         return 0;
2908
2909     default:
2910         /* get a rid of a gcc warning about unhandled enumerations */
2911         break;
2912     }
2913
2914     return (p - data);
2915 }
2916
2917 /*
2918  * find out whether the next token is (a possible) end_seq or an argument
2919  */
2920 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2921                                    apr_size_t len)
2922 {
2923     struct ssi_internal_ctx *intern = ctx->intern;
2924     const char *p = data;
2925     const char *ep = data + len;
2926
2927     /* skip leading WS */
2928     while (p < ep && apr_isspace(*p)) {
2929         ++p;
2930     }
2931
2932     /* buffer doesn't consist of whitespaces only */
2933     if (p < ep) {
2934         intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2935     }
2936
2937     return (p - data);
2938 }
2939
2940 /*
2941  * test the stream for end_seq. If it doesn't match at all, it must be an
2942  * argument
2943  */
2944 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2945                             apr_size_t len)
2946 {
2947     struct ssi_internal_ctx *intern = ctx->intern;
2948     const char *p = data;
2949     const char *ep = data + len;
2950     apr_size_t pos = intern->parse_pos;
2951
2952     if (PARSE_TAIL == intern->state) {
2953         intern->state = PARSE_TAIL_SEQ;
2954         pos = intern->parse_pos = 0;
2955     }
2956
2957     while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2958         ++p;
2959         ++pos;
2960     }
2961
2962     /* bingo, full match */
2963     if (pos == intern->end_seq_len) {
2964         intern->state = PARSE_EXECUTE;
2965         return (p - data);
2966     }
2967
2968     /* partial match, the buffer is too small to match fully */
2969     if (p == ep) {
2970         intern->parse_pos = pos;
2971         return (p - data);
2972     }
2973
2974     /* no match. It must be an argument string then
2975      * The caller should cleanup and rewind to the reparse point
2976      */
2977     intern->state = PARSE_ARG;
2978     return 0;
2979 }
2980
2981 /*
2982  * extract name=value from the buffer
2983  * A pcre-pattern could look (similar to):
2984  * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2985  */
2986 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2987                                 apr_size_t len, char ***store,
2988                                 apr_size_t **store_len)
2989 {
2990     struct ssi_internal_ctx *intern = ctx->intern;
2991     const char *p = data;
2992     const char *ep = data + len;
2993
2994     switch (intern->state) {
2995     case PARSE_ARG:
2996         /*
2997          * create argument structure and append it to the current list
2998          */
2999         intern->current_arg = apr_palloc(ctx->dpool,
3000                                          sizeof(*intern->current_arg));
3001         intern->current_arg->next = NULL;
3002
3003         ++(ctx->argc);
3004         if (!intern->argv) {
3005             intern->argv = intern->current_arg;
3006         }
3007         else {
3008             arg_item_t *newarg = intern->argv;
3009
3010             while (newarg->next) {
3011                 newarg = newarg->next;
3012             }
3013             newarg->next = intern->current_arg;
3014         }
3015
3016         /* check whether it's a valid one. If it begins with a quote, we
3017          * can safely assume, someone forgot the name of the argument
3018          */
3019         switch (*p) {
3020         case '"': case '\'': case '`':
3021             *store = NULL;
3022
3023             intern->state = PARSE_ARG_VAL;
3024             intern->quote = *p++;
3025             intern->current_arg->name = NULL;
3026             intern->current_arg->name_len = 0;
3027             intern->error = 1;
3028
3029             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
3030                           "argument name for value to tag %s in %s",
3031                           apr_pstrmemdup(intern->r->pool, intern->directive,
3032                                          intern->directive_len),
3033                                          intern->r->filename);
3034
3035             return (p - data);
3036
3037         default:
3038             intern->state = PARSE_ARG_NAME;
3039         }
3040         /* continue immediately with next state */
3041
3042     case PARSE_ARG_NAME:
3043         while (p < ep && !apr_isspace(*p) && *p != '=') {
3044             ++p;
3045         }
3046
3047         if (p < ep) {
3048             intern->state = PARSE_ARG_POSTNAME;
3049             *store = &intern->current_arg->name;
3050             *store_len = &intern->current_arg->name_len;
3051             return (p - data);
3052         }
3053         break;
3054
3055     case PARSE_ARG_POSTNAME:
3056         intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
3057                                                  intern->current_arg->name,
3058                                                  intern->current_arg->name_len);
3059         if (!intern->current_arg->name_len) {
3060             intern->error = 1;
3061             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
3062                           "argument name for value to tag %s in %s",
3063                           apr_pstrmemdup(intern->r->pool, intern->directive,
3064                                          intern->directive_len),
3065                                          intern->r->filename);
3066         }
3067         else {
3068             char *sp = intern->current_arg->name;
3069
3070             /* normalize the name */
3071             while (*sp) {
3072                 *sp = apr_tolower(*sp);
3073                 ++sp;
3074             }
3075         }
3076
3077         intern->state = PARSE_ARG_EQ;
3078         /* continue with next state immediately */
3079
3080     case PARSE_ARG_EQ:
3081         *store = NULL;
3082
3083         while (p < ep && apr_isspace(*p)) {
3084             ++p;
3085         }
3086
3087         if (p < ep) {
3088             if (*p == '=') {
3089                 intern->state = PARSE_ARG_PREVAL;
3090                 ++p;
3091             }
3092             else { /* no value */
3093                 intern->current_arg->value = NULL;
3094                 intern->state = PARSE_PRE_ARG;
3095             }
3096
3097             return (p - data);
3098         }
3099         break;
3100
3101     case PARSE_ARG_PREVAL:
3102         *store = NULL;
3103
3104         while (p < ep && apr_isspace(*p)) {
3105             ++p;
3106         }
3107
3108         /* buffer doesn't consist of whitespaces only */
3109         if (p < ep) {
3110             intern->state = PARSE_ARG_VAL;
3111             switch (*p) {
3112             case '"': case '\'': case '`':
3113                 intern->quote = *p++;
3114                 break;
3115             default:
3116                 intern->quote = '\0';
3117                 break;
3118             }
3119
3120             return (p - data);
3121         }
3122         break;
3123
3124     case PARSE_ARG_VAL_ESC:
3125         if (*p == intern->quote) {
3126             ++p;
3127         }
3128         intern->state = PARSE_ARG_VAL;
3129         /* continue with next state immediately */
3130
3131     case PARSE_ARG_VAL:
3132         for (; p < ep; ++p) {
3133             if (intern->quote && *p == '\\') {
3134                 ++p;
3135                 if (p == ep) {
3136                     intern->state = PARSE_ARG_VAL_ESC;
3137                     break;
3138                 }
3139
3140                 if (*p != intern->quote) {
3141                     --p;
3142                 }
3143             }
3144             else if (intern->quote && *p == intern->quote) {
3145                 ++p;
3146                 *store = &intern->current_arg->value;
3147                 *store_len = &intern->current_arg->value_len;
3148                 intern->state = PARSE_ARG_POSTVAL;
3149                 break;
3150             }
3151             else if (!intern->quote && apr_isspace(*p)) {
3152                 ++p;
3153                 *store = &intern->current_arg->value;
3154                 *store_len = &intern->current_arg->value_len;
3155                 intern->state = PARSE_ARG_POSTVAL;
3156                 break;
3157             }
3158         }
3159
3160         return (p - data);
3161
3162     case PARSE_ARG_POSTVAL:
3163         /*
3164          * The value is still the raw input string. Finally clean it up.
3165          */
3166         --(intern->current_arg->value_len);
3167         intern->current_arg->value[intern->current_arg->value_len] = '\0';
3168
3169         /* strip quote escaping \ from the string */
3170         if (intern->quote) {
3171             apr_size_t shift = 0;
3172             char *sp;
3173
3174             sp = intern->current_arg->value;
3175             ep = intern->current_arg->value + intern->current_arg->value_len;
3176             while (sp < ep && *sp != '\\') {
3177                 ++sp;
3178             }
3179             for (; sp < ep; ++sp) {
3180                 if (*sp == '\\' && sp[1] == intern->quote) {
3181                     ++sp;
3182                     ++shift;
3183                 }
3184                 if (shift) {
3185                     *(sp-shift) = *sp;
3186                 }
3187             }
3188
3189             intern->current_arg->value_len -= shift;
3190         }
3191
3192         intern->current_arg->value[intern->current_arg->value_len] = '\0';
3193         intern->state = PARSE_PRE_ARG;
3194
3195         return 0;
3196
3197     default:
3198         /* get a rid of a gcc warning about unhandled enumerations */
3199         break;
3200     }
3201
3202     return len; /* partial match of something */
3203 }
3204
3205 /*
3206  * This is the main loop over the current bucket brigade.
3207  */
3208 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3209 {
3210     include_ctx_t *ctx = f->ctx;
3211     struct ssi_internal_ctx *intern = ctx->intern;
3212     request_rec *r = f->r;
3213     apr_bucket *b = APR_BRIGADE_FIRST(bb);
3214     apr_bucket_brigade *pass_bb;
3215     apr_status_t rv = APR_SUCCESS;
3216     char *magic; /* magic pointer for sentinel use */
3217
3218     /* fast exit */
3219     if (APR_BRIGADE_EMPTY(bb)) {
3220         return APR_SUCCESS;
3221     }
3222
3223     /* we may crash, since already cleaned up; hand over the responsibility
3224      * to the next filter;-)
3225      */
3226     if (intern->seen_eos) {
3227         return ap_pass_brigade(f->next, bb);
3228     }
3229
3230     /* All stuff passed along has to be put into that brigade */
3231     pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3232
3233     /* initialization for this loop */
3234     intern->bytes_read = 0;
3235     intern->error = 0;
3236     intern->r = r;
3237     ctx->flush_now = 0;
3238
3239     /* loop over the current bucket brigade */
3240     while (b != APR_BRIGADE_SENTINEL(bb)) {
3241         const char *data = NULL;
3242         apr_size_t len, index, release;
3243         apr_bucket *newb = NULL;
3244         char **store = &magic;
3245         apr_size_t *store_len;
3246
3247         /* handle meta buckets before reading any data */
3248         if (APR_BUCKET_IS_METADATA(b)) {
3249             newb = APR_BUCKET_NEXT(b);
3250
3251             APR_BUCKET_REMOVE(b);
3252
3253             if (APR_BUCKET_IS_EOS(b)) {
3254                 intern->seen_eos = 1;
3255
3256                 /* Hit end of stream, time for cleanup ... But wait!
3257                  * Perhaps we're not ready yet. We may have to loop one or
3258                  * two times again to finish our work. In that case, we
3259                  * just re-insert the EOS bucket to allow for an extra loop.
3260                  *
3261                  * PARSE_EXECUTE means, we've hit a directive just before the
3262                  *    EOS, which is now waiting for execution.
3263                  *
3264                  * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3265                  *    no argument and no space between directive and end_seq
3266                  *    just before the EOS. (consider <!--#printenv--> as last
3267                  *    or only string within the stream). This state, however,
3268                  *    just cleans up and turns itself to PARSE_EXECUTE, which
3269                  *    will be passed through within the next (and actually
3270                  *    last) round.
3271                  */
3272                 if (PARSE_EXECUTE            == intern->state ||
3273                     PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3274                     APR_BUCKET_INSERT_BEFORE(newb, b);
3275                 }
3276                 else {
3277                     break; /* END OF STREAM */
3278                 }
3279             }
3280             else {
3281                 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3282
3283                 if (APR_BUCKET_IS_FLUSH(b)) {
3284                     ctx->flush_now = 1;
3285                 }
3286
3287                 b = newb;
3288                 continue;
3289             }
3290         }
3291
3292         /* enough is enough ... */
3293         if (ctx->flush_now ||
3294             intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3295
3296             if (!APR_BRIGADE_EMPTY(pass_bb)) {
3297                 rv = ap_pass_brigade(f->next, pass_bb);
3298                 if (!APR_STATUS_IS_SUCCESS(rv)) {
3299                     apr_brigade_destroy(pass_bb);
3300                     return rv;
3301                 }
3302             }
3303
3304             ctx->flush_now = 0;
3305             intern->bytes_read = 0;
3306         }
3307
3308         /* read the current bucket data */
3309         len = 0;
3310         if (!intern->seen_eos) {
3311             if (intern->bytes_read > 0) {
3312                 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3313                 if (APR_STATUS_IS_EAGAIN(rv)) {
3314                     ctx->flush_now = 1;
3315                     continue;
3316                 }
3317             }
3318
3319             if (!len || !APR_STATUS_IS_SUCCESS(rv)) {
3320                 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3321             }
3322
3323             if (!APR_STATUS_IS_SUCCESS(rv)) {
3324                 apr_brigade_destroy(pass_bb);
3325                 return rv;
3326             }
3327
3328             intern->bytes_read += len;
3329         }
3330
3331         /* zero length bucket, fetch next one */
3332         if (!len && !intern->seen_eos) {
3333             b = APR_BUCKET_NEXT(b);
3334             continue;
3335         }
3336
3337         /*
3338          * it's actually a data containing bucket, start/continue parsing
3339          */
3340
3341         switch (intern->state) {
3342         /* no current tag; search for start sequence */
3343         case PARSE_PRE_HEAD:
3344             index = find_start_sequence(ctx, data, len);
3345
3346             if (index < len) {
3347                 apr_bucket_split(b, index);
3348             }
3349
3350             newb = APR_BUCKET_NEXT(b);
3351             if (ctx->flags & SSI_FLAG_PRINTING) {
3352                 APR_BUCKET_REMOVE(b);
3353                 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3354             }
3355             else {
3356                 apr_bucket_delete(b);
3357             }
3358
3359             if (index < len) {
3360                 /* now delete the start_seq stuff from the remaining bucket */
3361                 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3362                     apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3363                     ctx->flush_now = 1; /* pass pre-tag stuff */
3364                 }
3365
3366                 b = APR_BUCKET_NEXT(newb);
3367                 apr_bucket_delete(newb);
3368             }
3369             else {
3370                 b = newb;
3371             }
3372
3373             break;
3374
3375         /* we're currently looking for the end of the start sequence */
3376         case PARSE_HEAD:
3377             index = find_partial_start_sequence(ctx, data, len, &release);
3378
3379             /* check if we mismatched earlier and have to release some chars */
3380             if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3381                 char *to_release = apr_palloc(ctx->pool, release);
3382
3383                 memcpy(to_release, intern->start_seq, release);
3384                 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3385                                               f->c->bucket_alloc);
3386                 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3387             }
3388
3389             if (index) { /* any match */
3390                 /* now delete the start_seq stuff from the remaining bucket */
3391                 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3392                     apr_bucket_split(b, index);
3393                     ctx->flush_now = 1; /* pass pre-tag stuff */
3394                 }
3395                 newb = APR_BUCKET_NEXT(b);
3396                 apr_bucket_delete(b);
3397                 b = newb;
3398             }
3399
3400             break;
3401
3402         /* we're currently grabbing the directive name */
3403         case PARSE_DIRECTIVE:
3404         case PARSE_DIRECTIVE_POSTNAME:
3405         case PARSE_DIRECTIVE_TAIL:
3406         case PARSE_DIRECTIVE_POSTTAIL:
3407             index = find_directive(ctx, data, len, &store, &store_len);
3408
3409             if (index) {
3410                 apr_bucket_split(b, index);
3411                 newb = APR_BUCKET_NEXT(b);
3412             }
3413
3414             if (store) {
3415                 if (index) {
3416                     APR_BUCKET_REMOVE(b);
3417                     APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3418                     b = newb;
3419                 }
3420
3421                 /* time for cleanup? */
3422                 if (store != &magic) {
3423                     apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3424                                          ctx->dpool);
3425                     apr_brigade_cleanup(intern->tmp_bb);
3426                 }
3427             }
3428             else if (index) {
3429                 apr_bucket_delete(b);
3430                 b = newb;
3431             }
3432
3433             break;
3434
3435         /* skip WS and find out what comes next (arg or end_seq) */
3436         case PARSE_PRE_ARG:
3437             index = find_arg_or_tail(ctx, data, len);
3438
3439             if (index) { /* skipped whitespaces */
3440                 if (index < len) {
3441                     apr_bucket_split(b, index);
3442                 }
3443                 newb = APR_BUCKET_NEXT(b);
3444                 apr_bucket_delete(b);
3445                 b = newb;
3446             }
3447
3448             break;
3449
3450         /* currently parsing name[=val] */
3451         case PARSE_ARG:
3452         case PARSE_ARG_NAME:
3453         case PARSE_ARG_POSTNAME:
3454         case PARSE_ARG_EQ:
3455         case PARSE_ARG_PREVAL:
3456         case PARSE_ARG_VAL:
3457         case PARSE_ARG_VAL_ESC:
3458         case PARSE_ARG_POSTVAL:
3459             index = find_argument(ctx, data, len, &store, &store_len);
3460
3461             if (index) {
3462                 apr_bucket_split(b, index);
3463                 newb = APR_BUCKET_NEXT(b);
3464             }
3465
3466             if (store) {
3467                 if (index) {
3468                     APR_BUCKET_REMOVE(b);
3469                     APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3470                     b = newb;
3471                 }
3472
3473                 /* time for cleanup? */
3474                 if (store != &magic) {
3475                     apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3476                                          ctx->dpool);
3477                     apr_brigade_cleanup(intern->tmp_bb);
3478                 }
3479             }
3480             else if (index) {
3481                 apr_bucket_delete(b);
3482                 b = newb;
3483             }
3484
3485             break;
3486
3487         /* try to match end_seq at current pos. */
3488         case PARSE_TAIL:
3489         case PARSE_TAIL_SEQ:
3490             index = find_tail(ctx, data, len);
3491
3492             switch (intern->state) {
3493             case PARSE_EXECUTE:  /* full match */
3494                 apr_bucket_split(b, index);
3495                 newb = APR_BUCKET_NEXT(b);
3496                 apr_bucket_delete(b);
3497                 b = newb;
3498                 break;
3499
3500             case PARSE_ARG:      /* no match */
3501                 /* PARSE_ARG must reparse at the beginning */
3502                 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3503                 b = APR_BRIGADE_FIRST(bb);
3504                 break;
3505
3506             default:             /* partial match */
3507                 newb = APR_BUCKET_NEXT(b);
3508                 APR_BUCKET_REMOVE(b);
3509                 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3510                 b = newb;
3511                 break;
3512             }
3513
3514             break;
3515
3516         /* now execute the parsed directive, cleanup the space and
3517          * start again with PARSE_PRE_HEAD
3518          */
3519         case PARSE_EXECUTE:
3520             /* if there was an error, it was already logged; just stop here */
3521             if (intern->error) {
3522                 if (ctx->flags & SSI_FLAG_PRINTING) {
3523                     SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3524                     intern->error = 0;
3525                 }
3526             }
3527             else {
3528                 include_handler_fn_t *handle_func;
3529
3530                 handle_func = apr_hash_get(include_handlers, intern->directive,
3531                                            intern->directive_len);
3532
3533                 if (handle_func) {
3534                     rv = handle_func(ctx, f, pass_bb);
3535                     if (!APR_STATUS_IS_SUCCESS(rv)) {
3536                         apr_brigade_destroy(pass_bb);
3537                         return rv;
3538                     }
3539                 }
3540                 else {
3541                     ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3542                                   "unknown directive \"%s\" in parsed doc %s",
3543                                   apr_pstrmemdup(r->pool, intern->directive,
3544                                                  intern->directive_len),
3545                                                  r->filename);
3546                     if (ctx->flags & SSI_FLAG_PRINTING) {
3547                         SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3548                     }
3549                 }
3550             }
3551
3552             /* cleanup */
3553             apr_pool_clear(ctx->dpool);
3554             apr_brigade_cleanup(intern->tmp_bb);
3555
3556             /* Oooof. Done here, start next round */
3557             intern->state = PARSE_PRE_HEAD;
3558             break;
3559
3560         } /* switch(ctx->state) */
3561
3562     } /* while(brigade) */
3563
3564     /* End of stream. Final cleanup */
3565     if (intern->seen_eos) {
3566         if (PARSE_HEAD == intern->state) {
3567             if (ctx->flags & SSI_FLAG_PRINTING) {
3568                 char *to_release = apr_palloc(ctx->pool, intern->parse_pos);
3569
3570                 memcpy(to_release, intern->start_seq, intern->parse_pos);
3571                 APR_BRIGADE_INSERT_TAIL(pass_bb,
3572                                         apr_bucket_pool_create(to_release,
3573                                         intern->parse_pos, ctx->pool,
3574                                         f->c->bucket_alloc));
3575             }
3576         }
3577         else if (PARSE_PRE_HEAD != intern->state) {
3578             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3579                           "SSI directive was not properly finished at the end "
3580                           "of parsed document %s", r->filename);
3581             if (ctx->flags & SSI_FLAG_PRINTING) {
3582                 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3583             }
3584         }
3585
3586         if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3587             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3588                           "missing closing endif directive in parsed document"
3589                           " %s", r->filename);
3590         }
3591
3592         /* cleanup our temporary memory */
3593         apr_brigade_destroy(intern->tmp_bb);
3594         apr_pool_destroy(ctx->dpool);
3595
3596         /* don't forget to finally insert the EOS bucket */
3597         APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3598     }
3599
3600     /* if something's left over, pass it along */
3601     if (!APR_BRIGADE_EMPTY(pass_bb)) {
3602         rv = ap_pass_brigade(f->next, pass_bb);
3603     }
3604     else {
3605         rv = APR_SUCCESS;
3606     }
3607
3608     apr_brigade_destroy(pass_bb);
3609     return rv;
3610 }
3611
3612
3613 /*
3614  * +-------------------------------------------------------+
3615  * |                                                       |
3616  * |                     Runtime Hooks
3617  * |                                                       |
3618  * +-------------------------------------------------------+
3619  */
3620
3621 static int includes_setup(ap_filter_t *f)
3622 {
3623     include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3624                                                     &include_module);
3625
3626     /* When our xbithack value isn't set to full or our platform isn't
3627      * providing group-level protection bits or our group-level bits do not
3628      * have group-execite on, we will set the no_local_copy value to 1 so
3629      * that we will not send 304s.
3630      */
3631     if ((conf->xbithack != XBITHACK_FULL)
3632         || !(f->r->finfo.valid & APR_FINFO_GPROT)
3633         || !(f->r->finfo.protection & APR_GEXECUTE)) {
3634         f->r->no_local_copy = 1;
3635     }
3636
3637     return OK;
3638 }
3639
3640 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3641 {
3642     request_rec *r = f->r;
3643     include_ctx_t *ctx = f->ctx;
3644     request_rec *parent;
3645     include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3646                                                     &include_module);
3647
3648     include_server_config *sconf= ap_get_module_config(r->server->module_config,
3649                                                        &include_module);
3650
3651     if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3652         return ap_pass_brigade(f->next, b);
3653     }
3654
3655     if (!f->ctx) {
3656         struct ssi_internal_ctx *intern;
3657
3658         /* create context for this filter */
3659         f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3660         ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3661         ctx->pool = r->pool;
3662         apr_pool_create(&ctx->dpool, ctx->pool);
3663
3664         /* runtime data */
3665         intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3666         intern->seen_eos = 0;
3667         intern->state = PARSE_PRE_HEAD;
3668         ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3669         if (ap_allow_options(r) & OPT_INCNOEXEC) {
3670             ctx->flags |= SSI_FLAG_NO_EXEC;
3671         }
3672
3673         ctx->if_nesting_level = 0;
3674         intern->re = NULL;
3675
3676         ctx->error_str = conf->default_error_msg;
3677         ctx->time_str = conf->default_time_fmt;
3678         intern->start_seq  = sconf->default_start_tag;
3679         intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3680                                              strlen(intern->start_seq));
3681         intern->end_seq = sconf->default_end_tag;
3682         intern->end_seq_len = strlen(intern->end_seq);
3683     }
3684
3685     if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3686         /* Kludge --- for nested includes, we want to keep the subprocess
3687          * environment of the base document (for compatibility); that means
3688          * torquing our own last_modified date as well so that the
3689          * LAST_MODIFIED variable gets reset to the proper value if the
3690          * nested document resets <!--#config timefmt -->.
3691          */
3692         r->subprocess_env = r->main->subprocess_env;
3693         apr_pool_join(r->main->pool, r->pool);
3694         r->finfo.mtime = r->main->finfo.mtime;
3695     }
3696     else {
3697         /* we're not a nested include, so we create an initial
3698          * environment */
3699         ap_add_common_vars(r);
3700         ap_add_cgi_vars(r);
3701         add_include_vars(r, conf->default_time_fmt);
3702     }
3703     /* Always unset the content-length.  There is no way to know if
3704      * the content will be modified at some point by send_parsed_content.
3705      * It is very possible for us to not find any content in the first
3706      * 9k of the file, but still have to modify the content of the file.
3707      * If we are going to pass the file through send_parsed_content, then
3708      * the content-length should just be unset.
3709      */
3710     apr_table_unset(f->r->headers_out, "Content-Length");
3711
3712     /* Always unset the ETag/Last-Modified fields - see RFC2616 - 13.3.4.
3713      * We don't know if we are going to be including a file or executing
3714      * a program which may change the Last-Modified header or make the 
3715      * content completely dynamic.  Therefore, we can't support these
3716      * headers.
3717      * Exception: XBitHack full means we *should* set the Last-Modified field.
3718      */
3719     apr_table_unset(f->r->headers_out, "ETag");
3720
3721     /* Assure the platform supports Group protections */
3722     if ((conf->xbithack == XBITHACK_FULL)
3723         && (r->finfo.valid & APR_FINFO_GPROT)
3724         && (r->finfo.protection & APR_GEXECUTE)) {
3725         ap_update_mtime(r, r->finfo.mtime);
3726         ap_set_last_modified(r);
3727     }
3728     else {
3729         apr_table_unset(f->r->headers_out, "Last-Modified");
3730     }
3731
3732     /* add QUERY stuff to env cause it ain't yet */
3733     if (r->args) {
3734         char *arg_copy = apr_pstrdup(r->pool, r->args);
3735
3736         apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3737         ap_unescape_url(arg_copy);
3738         apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3739                   ap_escape_shell_cmd(r->pool, arg_copy));
3740     }
3741
3742     return send_parsed_content(f, b);
3743 }
3744
3745 static int include_fixup(request_rec *r)
3746 {
3747     include_dir_config *conf;
3748  
3749     conf = ap_get_module_config(r->per_dir_config, &include_module);
3750  
3751     if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) 
3752     {
3753         if (!r->content_type || !*r->content_type) {
3754             ap_set_content_type(r, "text/html");
3755         }
3756         r->handler = "default-handler";
3757     }
3758     else 
3759 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3760     /* These OS's don't support xbithack. This is being worked on. */
3761     {
3762         return DECLINED;
3763     }
3764 #else
3765     {
3766         if (conf->xbithack == XBITHACK_OFF) {
3767             return DECLINED;
3768         }
3769
3770         if (!(r->finfo.protection & APR_UEXECUTE)) {
3771             return DECLINED;
3772         }
3773
3774         if (!r->content_type || strcmp(r->content_type, "text/html")) {
3775             return DECLINED;
3776         }
3777     }
3778 #endif
3779
3780     /* We always return declined, because the default handler actually
3781      * serves the file.  All we have to do is add the filter.
3782      */
3783     ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3784     return DECLINED;
3785 }
3786
3787
3788 /*
3789  * +-------------------------------------------------------+
3790  * |                                                       |
3791  * |                Configuration Handling
3792  * |                                                       |
3793  * +-------------------------------------------------------+
3794  */
3795
3796 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3797 {
3798     include_dir_config *result = apr_palloc(p, sizeof(include_dir_config));
3799
3800     result->default_error_msg = DEFAULT_ERROR_MSG;
3801     result->default_time_fmt  = DEFAULT_TIME_FORMAT;
3802     result->xbithack          = DEFAULT_XBITHACK;
3803
3804     return result;
3805 }
3806
3807 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
3808 {
3809     include_server_config *result;
3810
3811     result = apr_palloc(p, sizeof(include_server_config));
3812     result->default_end_tag    = DEFAULT_END_SEQUENCE;
3813     result->default_start_tag  = DEFAULT_START_SEQUENCE;
3814     result->undefined_echo     = DEFAULT_UNDEFINED_ECHO;
3815     result->undefined_echo_len = sizeof(DEFAULT_UNDEFINED_ECHO) - 1;
3816
3817     return result; 
3818 }
3819
3820 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
3821 {
3822     include_dir_config *conf = mconfig;
3823
3824     if (!strcasecmp(arg, "off")) {
3825         conf->xbithack = XBITHACK_OFF;
3826     }
3827     else if (!strcasecmp(arg, "on")) {
3828         conf->xbithack = XBITHACK_ON;
3829     }
3830     else if (!strcasecmp(arg, "full")) {
3831         conf->xbithack = XBITHACK_FULL;
3832     }
3833     else {
3834         return "XBitHack must be set to Off, On, or Full";
3835     }
3836
3837     return NULL;
3838 }
3839
3840 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
3841                                          const char *tag)
3842 {
3843     include_server_config *conf;
3844     const char *p = tag;
3845
3846     /* be consistent. (See below in set_default_end_tag) */
3847     while (*p) {
3848         if (apr_isspace(*p)) {
3849             return "SSIStartTag may not contain any whitespaces";
3850         }
3851         ++p;
3852     }
3853
3854     conf= ap_get_module_config(cmd->server->module_config , &include_module);
3855     conf->default_start_tag = tag;
3856
3857     return NULL;
3858 }
3859
3860 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
3861                                        const char *tag)
3862 {
3863     include_server_config *conf;
3864     const char *p = tag;
3865
3866     /* sanity check. The parser may fail otherwise */
3867     while (*p) {
3868         if (apr_isspace(*p)) {
3869             return "SSIEndTag may not contain any whitespaces";
3870         }
3871         ++p;
3872     }
3873
3874     conf= ap_get_module_config(cmd->server->module_config , &include_module);
3875     conf->default_end_tag = tag;
3876
3877     return NULL;
3878 }
3879
3880 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
3881                                       const char *msg)
3882 {
3883     include_server_config *conf;
3884
3885     conf = ap_get_module_config(cmd->server->module_config, &include_module);
3886     conf->undefined_echo = msg;
3887     conf->undefined_echo_len = strlen(msg);
3888
3889     return NULL;
3890 }
3891
3892 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
3893                                          const char *msg)
3894 {
3895     include_dir_config *conf = mconfig;
3896     conf->default_error_msg = msg;
3897
3898     return NULL;
3899 }
3900
3901 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
3902                                         const char *fmt)
3903 {
3904     include_dir_config *conf = mconfig;
3905     conf->default_time_fmt = fmt;
3906
3907     return NULL;
3908 }
3909
3910
3911 /*
3912  * +-------------------------------------------------------+
3913  * |                                                       |
3914  * |        Module Initialization and Configuration
3915  * |                                                       |
3916  * +-------------------------------------------------------+
3917  */
3918
3919 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3920                                 apr_pool_t *ptemp, server_rec *s)
3921 {
3922     include_handlers = apr_hash_make(p);
3923
3924     ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3925
3926     if(ssi_pfn_register) {
3927         ssi_pfn_register("if", handle_if);
3928         ssi_pfn_register("set", handle_set);
3929         ssi_pfn_register("else", handle_else);
3930         ssi_pfn_register("elif", handle_elif);
3931         ssi_pfn_register("echo", handle_echo);
3932         ssi_pfn_register("endif", handle_endif);
3933         ssi_pfn_register("fsize", handle_fsize);
3934         ssi_pfn_register("config", handle_config);
3935         ssi_pfn_register("include", handle_include);
3936         ssi_pfn_register("flastmod", handle_flastmod);
3937         ssi_pfn_register("printenv", handle_printenv);
3938     }
3939
3940     return OK;
3941 }
3942
3943 static const command_rec includes_cmds[] =
3944 {
3945     AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, 
3946                   "Off, On, or Full"),
3947     AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, 
3948                   "a string"),
3949     AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3950                   "a strftime(3) formatted string"),
3951     AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3952                   "SSI Start String Tag"),
3953     AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3954                   "SSI End String Tag"),
3955     AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, RSRC_CONF,
3956                   "String to be displayed if an echoed variable is undefined"),
3957     {NULL}
3958 };
3959
3960 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3961 {
3962     apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
3963 }
3964
3965 static void register_hooks(apr_pool_t *p)
3966 {
3967     APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3968     APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3969     APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3970     ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3971     ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3972     ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3973                               AP_FTYPE_RESOURCE);
3974 }
3975
3976 module AP_MODULE_DECLARE_DATA include_module =
3977 {
3978     STANDARD20_MODULE_STUFF,
3979     create_includes_dir_config,   /* dir config creater */
3980     NULL,                         /* dir merger --- default is to override */
3981     create_includes_server_config,/* server config */
3982     NULL,                         /* merge server config */
3983     includes_cmds,                /* command apr_table_t */
3984     register_hooks                /* register hooks */
3985 };