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