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