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