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