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