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