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