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