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