1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2003 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 * http_include.c: Handles the server-parsed HTML documents
62 * Original by Rob McCool; substantial fixups by David Robinson;
63 * incorporated into the Apache module framework by rst.
68 #include "apr_strings.h"
69 #include "apr_thread_proc.h"
73 #include "apr_optional.h"
75 #define APR_WANT_STRFUNC
76 #define APR_WANT_MEMFUNC
79 #include "ap_config.h"
80 #include "util_filter.h"
82 #include "http_config.h"
83 #include "http_core.h"
84 #include "http_request.h"
85 #include "http_core.h"
86 #include "http_protocol.h"
88 #include "http_main.h"
89 #include "util_script.h"
90 #include "http_core.h"
91 #include "mod_include.h"
93 /* helper for Latin1 <-> entity encoding */
94 #if APR_CHARSET_EBCDIC
95 #include "util_ebcdic.h"
96 #define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
98 #else /* APR_CHARSET_EBCDIC */
99 #define RAW_ASCII_CHAR(ch) (ch)
100 #endif /* !APR_CHARSET_EBCDIC */
104 * +-------------------------------------------------------+
106 * | Types and Structures
108 * +-------------------------------------------------------+
111 /* sll used for string expansion */
112 typedef struct result_item {
113 struct result_item *next;
118 /* conditional expression parser stuff */
144 typedef struct parse_node {
145 struct parse_node *parent;
146 struct parse_node *left;
147 struct parse_node *right;
163 const char *default_error_msg;
164 const char *default_time_fmt;
165 const char *undefined_echo;
167 } include_dir_config;
170 const char *default_start_tag;
171 const char *default_end_tag;
172 } include_server_config;
174 /* main parser states */
179 PARSE_DIRECTIVE_POSTNAME,
180 PARSE_DIRECTIVE_TAIL,
181 PARSE_DIRECTIVE_POSTTAIL,
196 typedef struct arg_item {
197 struct arg_item *next;
201 apr_size_t value_len;
204 #define MAX_NMATCH 10
210 regmatch_t match[MAX_NMATCH];
216 apr_size_t pattern_len;
219 struct ssi_internal_ctx {
223 char quote; /* quote character value (or \0) */
224 apr_size_t parse_pos; /* parse position of partial matches */
225 apr_size_t bytes_read;
227 apr_bucket_brigade *tmp_bb;
230 const char *start_seq;
231 bndm_t *start_seq_pat;
233 apr_size_t end_seq_len;
234 char *directive; /* name of the current directive */
235 apr_size_t directive_len; /* length of the current directive name */
237 arg_item_t *current_arg; /* currently parsed argument */
238 arg_item_t *argv; /* all arguments */
240 backref_t *re; /* NULL if there wasn't a regex yet */
242 const char *undefined_echo;
243 apr_size_t undefined_echo_len;
248 apr_bucket_brigade *bb;
255 * +-------------------------------------------------------+
257 * | Debugging Utilities
259 * +-------------------------------------------------------+
264 #define TYPE_TOKEN(token, ttype) do { \
265 (token)->type = ttype; \
266 (token)->s = #ttype; \
269 #define CREATE_NODE(ctx, name) do { \
270 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
271 (name)->parent = (name)->left = (name)->right = NULL; \
273 (name)->dump_done = 0; \
276 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
282 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
285 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
286 debug__str, strlen(debug__str), ctx->pool,
287 ctx->intern->debug.f->c->bucket_alloc));
290 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
291 parse_node_t *d__c = node->child; \
293 if (!d__c->dump_done) { \
294 if (d__c->parent != node) { \
295 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
296 if (!d__c->parent) { \
297 debug_printf(ctx, "Parent of " #child " child node is " \
301 debug_printf(ctx, "Parent of " #child " child node " \
302 "points to another node (of type %s)!\n", \
303 d__c->parent->token.s); \
312 debug_printf(ctx, "%s(missing)\n", is); \
316 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
318 parse_node_t *current;
322 debug_printf(ctx, " -- Parse Tree empty --\n\n");
326 debug_printf(ctx, " ----- Parse Tree -----\n");
331 switch (current->token.type) {
334 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
335 current->token.value);
336 current->dump_done = 1;
337 current = current->parent;
344 if (!current->dump_done) {
345 debug_printf(ctx, "%s%s\n", is, current->token.s);
346 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
347 current->dump_done = 1;
350 DUMP__CHILD(ctx, is, current, right)
352 if (!current->right || current->right->dump_done) {
353 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
354 if (current->right) current->right->dump_done = 0;
355 current = current->parent;
360 if (!current->dump_done) {
361 debug_printf(ctx, "%s%s\n", is, current->token.s);
362 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
363 current->dump_done = 1;
366 DUMP__CHILD(ctx, is, current, left)
367 DUMP__CHILD(ctx, is, current, right)
369 if ((!current->left || current->left->dump_done) &&
370 (!current->right || current->right->dump_done)) {
372 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
373 if (current->left) current->left->dump_done = 0;
374 if (current->right) current->right->dump_done = 0;
375 current = current->parent;
381 /* it is possible to call this function within the parser loop, to see
382 * how the tree is built. That way, we must cleanup after us to dump
383 * always the whole tree
386 if (root->left) root->left->dump_done = 0;
387 if (root->right) root->right->dump_done = 0;
389 debug_printf(ctx, " --- End Parse Tree ---\n\n");
394 #define DEBUG_INIT(ctx, filter, brigade) do { \
395 (ctx)->intern->debug.f = filter; \
396 (ctx)->intern->debug.bb = brigade; \
399 #define DEBUG_PRINTF(arg) debug_printf arg
401 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
402 token_t *d__t = (token); \
404 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
405 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
408 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
412 #define DEBUG_DUMP_EVAL(ctx, node) do { \
414 switch ((node)->token.type) { \
416 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
417 (node)->token.value, ((node)->value) ? '1':'0'); \
421 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
423 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
424 : "short circuited"), \
425 (((node)->right->done) ? ((node)->right->value?"1":"0") \
426 : "short circuited"), \
427 (node)->value ? '1' : '0'); \
435 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
436 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
438 (node)->left->token.value, \
439 c, (node)->right->token.value, c, \
440 (node)->value ? '1' : '0'); \
443 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
444 (node)->value ? '1' : '0'); \
449 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
451 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
455 #define DEBUG_DUMP_COND(ctx, text) \
456 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
457 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
459 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
461 #else /* DEBUG_INCLUDE */
463 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
465 #define CREATE_NODE(ctx, name) do { \
466 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
467 (name)->parent = (name)->left = (name)->right = NULL; \
471 #define DEBUG_INIT(ctx, f, bb)
472 #define DEBUG_PRINTF(arg)
473 #define DEBUG_DUMP_TOKEN(ctx, token)
474 #define DEBUG_DUMP_EVAL(ctx, node)
475 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
476 #define DEBUG_DUMP_COND(ctx, text)
477 #define DEBUG_DUMP_TREE(ctx, root)
479 #endif /* !DEBUG_INCLUDE */
483 * +-------------------------------------------------------+
485 * | Static Module Data
487 * +-------------------------------------------------------+
490 /* global module structure */
491 module AP_MODULE_DECLARE_DATA include_module;
493 /* function handlers for include directives */
494 static apr_hash_t *include_handlers;
496 /* forward declaration of handler registry */
497 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
499 /* Sentinel value to store in subprocess_env for items that
500 * shouldn't be evaluated until/unless they're actually used
502 static const char lazy_eval_sentinel;
503 #define LAZY_VALUE (&lazy_eval_sentinel)
506 #define DEFAULT_START_SEQUENCE "<!--#"
507 #define DEFAULT_END_SEQUENCE "-->"
508 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
509 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
510 #define DEFAULT_UNDEFINED_ECHO "(none)"
513 #define DEFAULT_XBITHACK XBITHACK_FULL
515 #define DEFAULT_XBITHACK XBITHACK_OFF
520 * +-------------------------------------------------------+
522 * | Environment/Expansion Functions
524 * +-------------------------------------------------------+
528 * decodes a string containing html entities or numeric character references.
529 * 's' is overwritten with the decoded string.
530 * If 's' is syntatically incorrect, then the followed fixups will be made:
531 * unknown entities will be left undecoded;
532 * references to unused numeric characters will be deleted.
533 * In particular, � will not be decoded, but will be deleted.
536 /* maximum length of any ISO-LATIN-1 HTML entity name. */
537 #define MAXENTLEN (6)
539 /* The following is a shrinking transformation, therefore safe. */
541 static void decodehtml(char *s)
546 static const char * const entlist[MAXENTLEN + 1] =
550 "lt\074gt\076", /* 2 */
551 "amp\046ETH\320eth\360", /* 3 */
552 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
553 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
555 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
556 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
557 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
559 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
560 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
561 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
562 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
563 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
564 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
567 /* Do a fast scan through the string until we find anything
568 * that needs more complicated handling
570 for (; *s != '&'; s++) {
576 for (p = s; *s != '\0'; s++, p++) {
581 /* find end of entity */
582 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
586 if (s[i] == '\0') { /* treat as normal data */
591 /* is it numeric ? */
593 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
594 val = val * 10 + s[j] - '0';
597 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
598 (val >= 127 && val <= 160) || val >= 256) {
599 p--; /* no data to output */
602 *p = RAW_ASCII_CHAR(val);
607 if (j > MAXENTLEN || entlist[j] == NULL) {
610 continue; /* skip it */
612 for (ents = entlist[j]; *ents != '\0'; ents += i) {
613 if (strncmp(s + 1, ents, j) == 0) {
619 *p = '&'; /* unknown */
622 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
631 static void add_include_vars(request_rec *r, const char *timefmt)
633 apr_table_t *e = r->subprocess_env;
636 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
637 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
638 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
639 apr_table_setn(e, "DOCUMENT_URI", r->uri);
640 if (r->path_info && *r->path_info) {
641 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
643 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
644 if ((t = strrchr(r->filename, '/'))) {
645 apr_table_setn(e, "DOCUMENT_NAME", ++t);
648 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
651 char *arg_copy = apr_pstrdup(r->pool, r->args);
653 ap_unescape_url(arg_copy);
654 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
655 ap_escape_shell_cmd(r->pool, arg_copy));
659 static const char *add_include_vars_lazy(request_rec *r, const char *var)
662 if (!strcasecmp(var, "DATE_LOCAL")) {
663 include_dir_config *conf =
664 (include_dir_config *)ap_get_module_config(r->per_dir_config,
666 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
668 else if (!strcasecmp(var, "DATE_GMT")) {
669 include_dir_config *conf =
670 (include_dir_config *)ap_get_module_config(r->per_dir_config,
672 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
674 else if (!strcasecmp(var, "LAST_MODIFIED")) {
675 include_dir_config *conf =
676 (include_dir_config *)ap_get_module_config(r->per_dir_config,
678 val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
680 else if (!strcasecmp(var, "USER_NAME")) {
681 if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
690 apr_table_setn(r->subprocess_env, var, val);
695 static const char *get_include_var(const char *var, include_ctx_t *ctx)
698 request_rec *r = ctx->intern->r;
700 if (apr_isdigit(*var) && !var[1]) {
701 apr_size_t idx = *var - '0';
702 backref_t *re = ctx->intern->re;
704 /* Handle $0 .. $9 from the last regex evaluated.
705 * The choice of returning NULL strings on not-found,
706 * v.s. empty strings on an empty match is deliberate.
709 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "regex capture $%d "
710 "refers to no regex in %s", idx, r->filename);
714 if (re->nsub < idx) {
715 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
716 "regex capture $%d is out of range (last regex "
717 "was: '%s') in %s", idx, re->rexp, r->filename);
721 if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
725 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
726 re->match[idx].rm_eo - re->match[idx].rm_so);
730 val = apr_table_get(r->subprocess_env, var);
732 if (val == LAZY_VALUE) {
733 val = add_include_vars_lazy(r, var);
741 * Do variable substitution on strings
743 * (Note: If out==NULL, this function allocs a buffer for the resulting
744 * string from ctx->pool. The return value is always the parsed string)
746 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
747 apr_size_t length, int leave_name)
749 request_rec *r = ctx->intern->r;
750 result_item_t *result = NULL, *current = NULL;
751 apr_size_t outlen = 0, inlen, span;
752 char *ret = NULL, *eout = NULL;
756 /* sanity check, out && !length is not supported */
757 ap_assert(out && length);
760 eout = out + length - 1;
763 span = strcspn(in, "\\$");
769 apr_cpystrn(out, in, length);
772 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
773 ? length - 1 : inlen);
779 /* well, actually something to do */
784 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
789 current = result = apr_palloc(ctx->dpool, sizeof(*result));
790 current->next = NULL;
791 current->string = in;
796 /* loop for specials */
798 if ((out && out >= eout) || (length && outlen >= length)) {
802 /* prepare next entry */
803 if (!out && current->len) {
804 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
805 current = current->next;
806 current->next = NULL;
815 *out++ = (p[1] == '$') ? *++p : *p;
820 current->string = (p[1] == '$') ? ++p : p;
829 else { /* *p == '$' */
830 const char *newp = NULL, *ep, *key = NULL;
833 ep = ap_strchr_c(++p, '}');
835 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
836 "variable \"%s\" in %s", p, r->filename);
841 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
848 while (*ep == '_' || apr_isalnum(*ep)) {
853 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
859 /* empty name results in a copy of '$' in the output string */
866 current->string = p++;
871 const char *val = get_include_var(key, ctx);
877 else if (leave_name) {
884 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
889 current->string = val;
898 if ((out && out >= eout) || (length && outlen >= length)) {
902 /* check the remainder */
903 if (*p && (span = strcspn(p, "\\$")) > 0) {
904 if (!out && current->len) {
905 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
906 current = current->next;
907 current->next = NULL;
911 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
922 } while (p < in+inlen);
924 /* assemble result */
936 if (length && outlen > length) {
940 ret = out = apr_palloc(ctx->pool, outlen + 1);
945 memcpy(out, result->string, (out+result->len <= ep)
946 ? result->len : (ep-out));
949 result = result->next;
950 } while (result && out < ep);
960 * +-------------------------------------------------------+
962 * | Conditional Expression Parser
964 * +-------------------------------------------------------+
967 static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
971 backref_t *re = ctx->intern->re;
974 compiled = ap_pregcomp(ctx->dpool, rexp, REG_EXTENDED);
976 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->intern->r, "unable to "
977 "compile pattern \"%s\"", rexp);
982 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
985 re->source = apr_pstrdup(ctx->pool, string);
986 re->rexp = apr_pstrdup(ctx->pool, rexp);
987 re->nsub = compiled->re_nsub;
988 rc = !ap_regexec(compiled, string, MAX_NMATCH, re->match, 0);
990 ap_pregfree(ctx->dpool, compiled);
994 static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token)
1000 token->value = NULL;
1006 /* Skip leading white space */
1007 while (apr_isspace(**parse)) {
1016 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
1020 switch (*(*parse)++) {
1022 TYPE_TOKEN(token, TOKEN_LBRACE);
1025 TYPE_TOKEN(token, TOKEN_RBRACE);
1028 if (**parse == '=') ++*parse;
1029 TYPE_TOKEN(token, TOKEN_EQ);
1032 if (**parse == '=') {
1033 TYPE_TOKEN(token, TOKEN_NE);
1037 TYPE_TOKEN(token, TOKEN_NOT);
1043 TYPE_TOKEN(token, TOKEN_RE);
1047 if (**parse == '|') {
1048 TYPE_TOKEN(token, TOKEN_OR);
1054 if (**parse == '&') {
1055 TYPE_TOKEN(token, TOKEN_AND);
1061 if (**parse == '=') {
1062 TYPE_TOKEN(token, TOKEN_GE);
1066 TYPE_TOKEN(token, TOKEN_GT);
1069 if (**parse == '=') {
1070 TYPE_TOKEN(token, TOKEN_LE);
1074 TYPE_TOKEN(token, TOKEN_LT);
1078 /* It's a string or regex token
1079 * Now search for the next token, which finishes this string
1082 p = *parse = token->value = unmatched ? *parse : p;
1084 for (; **parse; p = ++*parse) {
1085 if (**parse == '\\') {
1095 if (**parse == unmatched) {
1100 } else if (apr_isspace(**parse)) {
1118 if ((*parse)[1] == **parse) {
1132 token->value = apr_pstrdup(pool, "");
1135 apr_size_t len = p - token->value - shift;
1136 char *c = apr_palloc(pool, len + 1);
1142 const char *e = ap_strchr_c(p, '\\');
1160 static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1162 parse_node_t *new, *root = NULL, *current = NULL;
1163 request_rec *r = ctx->intern->r;
1164 const char *error = "Invalid expression \"%s\" in file %s";
1165 const char *parse = expr;
1166 int was_unmatched = 0;
1175 /* Create Parse Tree */
1177 /* uncomment this to see how the tree a built:
1179 * DEBUG_DUMP_TREE(ctx, root);
1181 CREATE_NODE(ctx, new);
1183 was_unmatched = get_ptoken(ctx->dpool, &parse, &new->token);
1188 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1189 DEBUG_DUMP_TOKEN(ctx, &new->token);
1192 switch (new->token.type) {
1196 root = current = new;
1200 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
1207 switch (new->token.type) {
1209 switch (current->token.type) {
1211 current->token.value =
1212 apr_pstrcat(ctx->dpool, current->token.value,
1213 *current->token.value ? " " : "",
1214 new->token.value, NULL);
1223 new->parent = current;
1224 current = current->right = new;
1230 switch (current->token.type) {
1233 new->parent = current;
1234 current = current->right = new;
1245 switch (current->token.type) {
1249 current = current->parent;
1252 switch (current->token.type) {
1259 current = current->parent;
1268 current = root = new;
1272 new->left = current->right;
1273 new->left->parent = new;
1274 new->parent = current;
1275 current = current->right = new;
1289 if (current->token.type == TOKEN_STRING) {
1290 current = current->parent;
1295 current = root = new;
1299 switch (current->token.type) {
1303 new->left = current->right;
1304 new->left->parent = new;
1305 new->parent = current;
1306 current = current->right = new;
1316 while (current && current->token.type != TOKEN_LBRACE) {
1317 current = current->parent;
1321 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1325 error = "Unmatched ')' in \"%s\" in file %s";
1330 switch (current->token.type) {
1338 current->right = new;
1339 new->parent = current;
1349 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1354 DEBUG_DUMP_TREE(ctx, root);
1356 /* Evaluate Parse Tree */
1360 switch (current->token.type) {
1362 current->token.value =
1363 ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1364 SSI_EXPAND_DROP_NAME);
1365 current->value = !!*current->token.value;
1370 if (!current->left || !current->right) {
1371 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1372 "Invalid expression \"%s\" in file %s",
1378 if (!current->left->done) {
1379 switch (current->left->token.type) {
1381 current->left->token.value =
1382 ap_ssi_parse_string(ctx, current->left->token.value,
1383 NULL, 0, SSI_EXPAND_DROP_NAME);
1384 current->left->value = !!*current->left->token.value;
1385 DEBUG_DUMP_EVAL(ctx, current->left);
1386 current->left->done = 1;
1390 current = current->left;
1395 /* short circuit evaluation */
1396 if (!current->right->done && !regex &&
1397 ((current->token.type == TOKEN_AND && !current->left->value) ||
1398 (current->token.type == TOKEN_OR && current->left->value))) {
1399 current->value = current->left->value;
1402 if (!current->right->done) {
1403 switch (current->right->token.type) {
1405 current->right->token.value =
1406 ap_ssi_parse_string(ctx,current->right->token.value,
1407 NULL, 0, SSI_EXPAND_DROP_NAME);
1408 current->right->value = !!*current->right->token.value;
1409 DEBUG_DUMP_EVAL(ctx, current->right);
1410 current->right->done = 1;
1414 current = current->right;
1419 if (current->token.type == TOKEN_AND) {
1420 current->value = current->left->value &&
1421 current->right->value;
1424 current->value = current->left->value ||
1425 current->right->value;
1432 if (!current->left || !current->right ||
1433 current->left->token.type != TOKEN_STRING ||
1434 (current->right->token.type != TOKEN_STRING &&
1435 current->right->token.type != TOKEN_RE)) {
1436 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1437 "Invalid expression \"%s\" in file %s",
1442 current->left->token.value =
1443 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1444 SSI_EXPAND_DROP_NAME);
1445 current->right->token.value =
1446 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1447 SSI_EXPAND_DROP_NAME);
1449 if (current->right->token.type == TOKEN_RE) {
1450 current->value = re_check(ctx, current->left->token.value,
1451 current->right->token.value);
1455 current->value = !strcmp(current->left->token.value,
1456 current->right->token.value);
1459 if (current->token.type == TOKEN_NE) {
1460 current->value = !current->value;
1468 if (!current->left || !current->right ||
1469 current->left->token.type != TOKEN_STRING ||
1470 current->right->token.type != TOKEN_STRING) {
1471 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1472 "Invalid expression \"%s\" in file %s",
1478 current->left->token.value =
1479 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1480 SSI_EXPAND_DROP_NAME);
1481 current->right->token.value =
1482 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1483 SSI_EXPAND_DROP_NAME);
1485 current->value = strcmp(current->left->token.value,
1486 current->right->token.value);
1488 switch (current->token.type) {
1489 case TOKEN_GE: current->value = current->value >= 0; break;
1490 case TOKEN_GT: current->value = current->value > 0; break;
1491 case TOKEN_LE: current->value = current->value <= 0; break;
1492 case TOKEN_LT: current->value = current->value < 0; break;
1493 default: current->value = 0; break; /* should not happen */
1499 if (current->right) {
1500 if (!current->right->done) {
1501 current = current->right;
1504 current->value = current->right->value;
1510 if (current->token.type == TOKEN_NOT) {
1511 current->value = !current->value;
1517 error = "No operator before regex in expr \"%s\" in file %s";
1521 error = "Unmatched '(' in \"%s\" in file %s";
1525 error = "internal parser error in \"%s\" in file %s";
1528 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,r->filename);
1533 DEBUG_DUMP_EVAL(ctx, current);
1535 current = current->parent;
1538 return (root ? root->value : 0);
1543 * +-------------------------------------------------------+
1547 * +-------------------------------------------------------+
1551 * Extract the next tag name and value.
1552 * If there are no more tags, set the tag name to NULL.
1553 * The tag value is html decoded if dodecode is non-zero.
1554 * The tag value may be NULL if there is no tag value..
1556 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1557 char **tag_val, int dodecode)
1559 if (!ctx->intern->argv) {
1566 *tag_val = ctx->intern->argv->value;
1567 *tag = ctx->intern->argv->name;
1569 ctx->intern->argv = ctx->intern->argv->next;
1571 if (dodecode && *tag_val) {
1572 decodehtml(*tag_val);
1578 static int find_file(request_rec *r, const char *directive, const char *tag,
1579 char *tag_val, apr_finfo_t *finfo)
1581 char *to_send = tag_val;
1582 request_rec *rr = NULL;
1584 char *error_fmt = NULL;
1585 apr_status_t rv = APR_SUCCESS;
1587 if (!strcmp(tag, "file")) {
1590 /* be safe; only files in this directory or below allowed */
1591 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1592 APR_FILEPATH_NOTABOVEROOT |
1593 APR_FILEPATH_SECUREROOTTEST |
1594 APR_FILEPATH_NOTABSOLUTE, r->pool);
1596 if (!APR_STATUS_IS_SUCCESS(rv)) {
1597 error_fmt = "unable to access file \"%s\" "
1598 "in parsed file %s";
1601 /* note: it is okay to pass NULL for the "next filter" since
1602 we never attempt to "run" this sub request. */
1603 rr = ap_sub_req_lookup_file(newpath, r, NULL);
1605 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1606 to_send = rr->filename;
1607 if ((rv = apr_stat(finfo, to_send,
1608 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1609 && rv != APR_INCOMPLETE) {
1610 error_fmt = "unable to get information about \"%s\" "
1611 "in parsed file %s";
1615 error_fmt = "unable to lookup information about \"%s\" "
1616 "in parsed file %s";
1622 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1623 rv, r, error_fmt, to_send, r->filename);
1626 if (rr) ap_destroy_sub_req(rr);
1630 else if (!strcmp(tag, "virtual")) {
1631 /* note: it is okay to pass NULL for the "next filter" since
1632 we never attempt to "run" this sub request. */
1633 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1635 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1636 memcpy((char *) finfo, (const char *) &rr->finfo,
1638 ap_destroy_sub_req(rr);
1642 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1643 "information about \"%s\" in parsed file %s",
1644 tag_val, r->filename);
1645 ap_destroy_sub_req(rr);
1650 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1651 "to tag %s in %s", tag, directive, r->filename);
1657 * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1659 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1660 apr_bucket_brigade *bb)
1662 request_rec *r = f->r;
1665 ap_log_rerror(APLOG_MARK,
1666 (ctx->flags & SSI_FLAG_PRINTING)
1667 ? APLOG_ERR : APLOG_WARNING,
1668 0, r, "missing argument for include element in %s",
1672 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1677 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1683 char *tag_val = NULL;
1684 request_rec *rr = NULL;
1685 char *error_fmt = NULL;
1686 char *parsed_string;
1688 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1689 if (!tag || !tag_val) {
1693 if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1694 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1695 "\"%s\" to tag include in %s", tag, r->filename);
1696 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1700 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1701 SSI_EXPAND_DROP_NAME);
1702 if (tag[0] == 'f') {
1706 /* be safe; only files in this directory or below allowed */
1707 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1708 APR_FILEPATH_NOTABOVEROOT |
1709 APR_FILEPATH_SECUREROOTTEST |
1710 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1712 if (!APR_STATUS_IS_SUCCESS(rv)) {
1713 error_fmt = "unable to include file \"%s\" in parsed file %s";
1716 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1720 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1723 if (!error_fmt && rr->status != HTTP_OK) {
1724 error_fmt = "unable to include \"%s\" in parsed file %s";
1727 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1728 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1730 error_fmt = "unable to include potential exec \"%s\" in parsed "
1738 /* try to avoid recursive includes. We do this by walking
1739 * up the r->main list of subrequests, and at each level
1740 * walking back through any internal redirects. At each
1741 * step, we compare the filenames and the URIs.
1743 * The filename comparison catches a recursive include
1744 * with an ever-changing URL, eg.
1745 * <!--#include virtual=
1746 * "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
1747 * which, although they would eventually be caught because
1748 * we have a limit on the length of files, etc., can
1749 * recurse for a while.
1751 * The URI comparison catches the case where the filename
1752 * is changed while processing the request, so the
1753 * current name is never the same as any previous one.
1754 * This can happen with "DocumentRoot /foo" when you
1755 * request "/" on the server and it includes "/".
1756 * This only applies to modules such as mod_dir that
1757 * (somewhat improperly) mess with r->filename outside
1758 * of a filename translation phase.
1760 for (p = r; p && !founddupe; p = p->main) {
1761 for (q = p; q; q = q->prev) {
1762 if ((q->filename && rr->filename &&
1763 (strcmp(q->filename, rr->filename) == 0)) ||
1764 ((*q->uri == '/') &&
1765 (strcmp(q->uri, rr->uri) == 0))) {
1774 error_fmt = "Recursive include of \"%s\" in parsed file %s";
1778 /* See the Kludge in includes_filter for why.
1779 * Basically, it puts a bread crumb in here, then looks
1780 * for the crumb later to see if its been here.
1783 ap_set_module_config(rr->request_config, &include_module, r);
1786 if (!error_fmt && ap_run_sub_req(rr)) {
1787 error_fmt = "unable to include \"%s\" in parsed file %s";
1791 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1793 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1796 /* destroy the sub request */
1798 ap_destroy_sub_req(rr);
1810 * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1812 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1813 apr_bucket_brigade *bb)
1815 enum {E_NONE, E_URL, E_ENTITY} encode;
1816 request_rec *r = f->r;
1819 ap_log_rerror(APLOG_MARK,
1820 (ctx->flags & SSI_FLAG_PRINTING)
1821 ? APLOG_ERR : APLOG_WARNING,
1822 0, r, "missing argument for echo element in %s",
1826 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1831 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1839 char *tag_val = NULL;
1841 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1842 if (!tag || !tag_val) {
1846 if (!strcmp(tag, "var")) {
1848 const char *echo_text = NULL;
1851 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1852 0, SSI_EXPAND_DROP_NAME),
1861 echo_text = ap_escape_uri(ctx->dpool, val);
1864 echo_text = ap_escape_html(ctx->dpool, val);
1868 e_len = strlen(echo_text);
1871 echo_text = ctx->intern->undefined_echo;
1872 e_len = ctx->intern->undefined_echo_len;
1875 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
1876 apr_pmemdup(ctx->pool, echo_text, e_len),
1877 e_len, ctx->pool, f->c->bucket_alloc));
1879 else if (!strcmp(tag, "encoding")) {
1880 if (!strcasecmp(tag_val, "none")) {
1883 else if (!strcasecmp(tag_val, "url")) {
1886 else if (!strcasecmp(tag_val, "entity")) {
1890 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1891 "\"%s\" to parameter \"encoding\" of tag echo in "
1892 "%s", tag_val, r->filename);
1893 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1898 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1899 "\"%s\" in tag echo of %s", tag, r->filename);
1900 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1909 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."] -->
1911 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
1912 apr_bucket_brigade *bb)
1914 request_rec *r = f->r;
1915 apr_table_t *env = r->subprocess_env;
1918 ap_log_rerror(APLOG_MARK,
1919 (ctx->flags & SSI_FLAG_PRINTING)
1920 ? APLOG_ERR : APLOG_WARNING,
1921 0, r, "missing argument for config element in %s",
1925 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1930 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1936 char *tag_val = NULL;
1937 char *parsed_string;
1939 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
1940 if (!tag || !tag_val) {
1944 if (!strcmp(tag, "errmsg")) {
1945 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1946 SSI_EXPAND_DROP_NAME);
1948 else if (!strcmp(tag, "timefmt")) {
1949 apr_time_t date = r->request_time;
1951 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1952 SSI_EXPAND_DROP_NAME);
1954 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
1956 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
1958 apr_table_setn(env, "LAST_MODIFIED",
1959 ap_ht_time(r->pool, r->finfo.mtime,
1962 else if (!strcmp(tag, "sizefmt")) {
1963 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1964 SSI_EXPAND_DROP_NAME);
1965 if (!strcmp(parsed_string, "bytes")) {
1966 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
1968 else if (!strcmp(parsed_string, "abbrev")) {
1969 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
1972 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
1973 "\"%s\" to parameter \"sizefmt\" of tag config "
1974 "in %s", parsed_string, r->filename);
1975 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1980 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1981 "\"%s\" to tag config in %s", tag, r->filename);
1982 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1991 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
1993 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
1994 apr_bucket_brigade *bb)
1996 request_rec *r = f->r;
1999 ap_log_rerror(APLOG_MARK,
2000 (ctx->flags & SSI_FLAG_PRINTING)
2001 ? APLOG_ERR : APLOG_WARNING,
2002 0, r, "missing argument for fsize element in %s",
2006 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2011 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2017 char *tag_val = NULL;
2019 char *parsed_string;
2021 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2022 if (!tag || !tag_val) {
2026 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2027 SSI_EXPAND_DROP_NAME);
2029 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
2033 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
2034 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
2035 len = 4; /* omit the \0 terminator */
2038 apr_size_t l, x, pos;
2041 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
2042 len = l = strlen(tmp);
2044 for (x = 0; x < l; ++x) {
2045 if (x && !((l - x) % 3)) {
2051 buf = apr_pstrmemdup(ctx->pool, tmp, len);
2054 buf = apr_palloc(ctx->pool, len);
2056 for (pos = x = 0; x < l; ++x) {
2057 if (x && !((l - x) % 3)) {
2060 buf[pos++] = tmp[x];
2065 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
2066 ctx->pool, f->c->bucket_alloc));
2069 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2078 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
2080 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
2081 apr_bucket_brigade *bb)
2083 request_rec *r = f->r;
2086 ap_log_rerror(APLOG_MARK,
2087 (ctx->flags & SSI_FLAG_PRINTING)
2088 ? APLOG_ERR : APLOG_WARNING,
2089 0, r, "missing argument for flastmod element in %s",
2093 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2098 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2104 char *tag_val = NULL;
2106 char *parsed_string;
2108 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2109 if (!tag || !tag_val) {
2113 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2114 SSI_EXPAND_DROP_NAME);
2116 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2120 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2121 t_len = strlen(t_val);
2123 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2124 ctx->pool, f->c->bucket_alloc));
2127 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2136 * <!--#if expr="..." -->
2138 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2139 apr_bucket_brigade *bb)
2143 request_rec *r = f->r;
2144 int expr_ret, was_error;
2146 if (ctx->argc != 1) {
2147 ap_log_rerror(APLOG_MARK,
2148 (ctx->flags & SSI_FLAG_PRINTING)
2149 ? APLOG_ERR : APLOG_WARNING,
2151 ? "too many arguments for if element in %s"
2152 : "missing expr argument for if element in %s",
2156 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2157 ++(ctx->if_nesting_level);
2161 if (ctx->argc != 1) {
2162 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2166 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2168 if (strcmp(tag, "expr")) {
2169 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2170 "to tag if in %s", tag, r->filename);
2171 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2176 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
2177 "element in %s", r->filename);
2178 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2182 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2184 expr_ret = parse_expr(ctx, expr, &was_error);
2187 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2192 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2195 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2198 DEBUG_DUMP_COND(ctx, " if");
2200 ctx->if_nesting_level = 0;
2206 * <!--#elif expr="..." -->
2208 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2209 apr_bucket_brigade *bb)
2213 request_rec *r = f->r;
2214 int expr_ret, was_error;
2216 if (ctx->argc != 1) {
2217 ap_log_rerror(APLOG_MARK,
2218 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2220 ? "too many arguments for if element in %s"
2221 : "missing expr argument for if element in %s",
2225 if (ctx->if_nesting_level) {
2229 if (ctx->argc != 1) {
2230 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2234 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2236 if (strcmp(tag, "expr")) {
2237 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2238 "to tag if in %s", tag, r->filename);
2239 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2244 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
2245 "statement: %s", r->filename);
2246 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2250 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2251 DEBUG_DUMP_COND(ctx, " elif");
2253 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2254 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2258 expr_ret = parse_expr(ctx, expr, &was_error);
2261 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2266 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2269 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2272 DEBUG_DUMP_COND(ctx, " elif");
2280 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2281 apr_bucket_brigade *bb)
2283 request_rec *r = f->r;
2286 ap_log_rerror(APLOG_MARK,
2287 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2288 0, r, "else directive does not take tags in %s",
2292 if (ctx->if_nesting_level) {
2297 if (ctx->flags & SSI_FLAG_PRINTING) {
2298 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2304 DEBUG_DUMP_COND(ctx, " else");
2306 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2307 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2310 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2319 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2320 apr_bucket_brigade *bb)
2322 request_rec *r = f->r;
2325 ap_log_rerror(APLOG_MARK,
2326 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2327 0, r, "endif directive does not take tags in %s",
2331 if (ctx->if_nesting_level) {
2332 --(ctx->if_nesting_level);
2337 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2341 DEBUG_DUMP_COND(ctx, "endif");
2343 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2349 * <!--#set var="..." value="..." ... -->
2351 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2352 apr_bucket_brigade *bb)
2355 request_rec *r = f->r;
2356 request_rec *sub = r->main;
2357 apr_pool_t *p = r->pool;
2359 if (ctx->argc < 2) {
2360 ap_log_rerror(APLOG_MARK,
2361 (ctx->flags & SSI_FLAG_PRINTING)
2362 ? APLOG_ERR : APLOG_WARNING,
2363 0, r, "missing argument for set element in %s",
2367 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2371 if (ctx->argc < 2) {
2372 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2376 /* we need to use the 'main' request pool to set notes as that is
2386 char *tag_val = NULL;
2388 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2390 if (!tag || !tag_val) {
2394 if (!strcmp(tag, "var")) {
2395 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2396 SSI_EXPAND_DROP_NAME);
2398 else if (!strcmp(tag, "value")) {
2399 char *parsed_string;
2402 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
2403 "precede value in set directive in %s",
2405 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2409 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2410 SSI_EXPAND_DROP_NAME);
2411 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2412 apr_pstrdup(p, parsed_string));
2415 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
2416 "directive in %s", r->filename);
2417 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2428 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2429 apr_bucket_brigade *bb)
2431 request_rec *r = f->r;
2432 const apr_array_header_t *arr;
2433 const apr_table_entry_t *elts;
2437 ap_log_rerror(APLOG_MARK,
2438 (ctx->flags & SSI_FLAG_PRINTING)
2439 ? APLOG_ERR : APLOG_WARNING,
2440 0, r, "printenv directive does not take tags in %s",
2444 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2449 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2453 arr = apr_table_elts(r->subprocess_env);
2454 elts = (apr_table_entry_t *)arr->elts;
2456 for (i = 0; i < arr->nelts; ++i) {
2457 const char *key_text, *val_text;
2458 char *key_val, *next;
2459 apr_size_t k_len, v_len, kv_length;
2462 key_text = ap_escape_html(ctx->dpool, elts[i].key);
2463 k_len = strlen(key_text);
2466 val_text = elts[i].val;
2467 if (val_text == LAZY_VALUE) {
2468 val_text = add_include_vars_lazy(r, elts[i].key);
2470 val_text = ap_escape_html(ctx->dpool, elts[i].val);
2471 v_len = strlen(val_text);
2473 /* assemble result */
2474 kv_length = k_len + v_len + sizeof("=\n");
2475 key_val = apr_palloc(ctx->pool, kv_length);
2478 memcpy(next, key_text, k_len);
2481 memcpy(next, val_text, v_len);
2486 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
2487 ctx->pool, f->c->bucket_alloc));
2496 * +-------------------------------------------------------+
2498 * | Main Includes-Filter Engine
2500 * +-------------------------------------------------------+
2503 /* This is an implementation of the BNDM search algorithm.
2505 * Fast and Flexible String Matching by Combining Bit-parallelism and
2506 * Suffix Automata (2001)
2507 * Gonzalo Navarro, Mathieu Raffinot
2509 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2511 * Initial code submitted by Sascha Schumann.
2514 /* Precompile the bndm_t data structure. */
2515 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2518 const char *ne = n + nl;
2519 bndm_t *t = apr_palloc(pool, sizeof(*t));
2521 memset(t->T, 0, sizeof(unsigned int) * 256);
2522 t->pattern_len = nl;
2524 for (x = 1; n < ne; x <<= 1) {
2525 t->T[(unsigned char) *n++] |= x;
2533 /* Implements the BNDM search algorithm (as described above).
2535 * h - the string to look in
2536 * hl - length of the string to look for
2537 * t - precompiled bndm structure against the pattern
2539 * Returns the count of character that is the first match or hl if no
2542 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2545 const char *he, *p, *pi;
2546 unsigned int *T, x, d;
2553 nl = t->pattern_len;
2555 pi = h - 1; /* pi: p initial */
2556 p = pi + nl; /* compare window right to left. point to the first char */
2562 d &= T[(unsigned char) *p--];
2585 * returns the index position of the first byte of start_seq (or the len of
2586 * the buffer as non-match)
2588 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2591 struct ssi_internal_ctx *intern = ctx->intern;
2592 apr_size_t slen = intern->start_seq_pat->pattern_len;
2597 p = data; /* try partial match at the end of the buffer (below) */
2600 /* try fast bndm search over the buffer
2601 * (hopefully the whole start sequence can be found in this buffer)
2603 index = bndm(intern->start_seq_pat, data, len);
2605 /* wow, found it. ready. */
2607 intern->state = PARSE_DIRECTIVE;
2611 /* ok, the pattern can't be found as whole in the buffer,
2612 * check the end for a partial match
2614 p = data + len - slen + 1;
2620 while (p < ep && *p != *intern->start_seq) {
2626 /* found a possible start_seq start */
2631 while (p < ep && *p == intern->start_seq[pos]) {
2636 /* partial match found. Store the info for the next round */
2638 intern->state = PARSE_HEAD;
2639 intern->parse_pos = pos;
2644 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2645 * and a string data of "--.-" and the end of the buffer
2647 p = data + index + 1;
2655 * returns the first byte *after* the partial (or final) match.
2657 * If we had to trick with the start_seq start, 'release' returns the
2658 * number of chars of the start_seq which appeared not to be part of a
2659 * full tag and may have to be passed down the filter chain.
2661 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2664 apr_size_t *release)
2666 struct ssi_internal_ctx *intern = ctx->intern;
2667 apr_size_t pos, spos = 0;
2668 apr_size_t slen = intern->start_seq_pat->pattern_len;
2671 pos = intern->parse_pos;
2678 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2685 intern->state = PARSE_DIRECTIVE;
2689 /* the whole buffer is a partial match */
2691 intern->parse_pos = pos;
2695 /* No match so far, but again:
2696 * We must try all combinations, since the start_seq is a random
2697 * user supplied string
2699 * So: look if the first char of start_seq appears somewhere within
2700 * the current partial match. If it does, try to start a match that
2701 * begins with this offset. (This can happen, if a strange
2702 * start_seq like "---->" spans buffers)
2704 if (spos < intern->parse_pos) {
2708 p = intern->start_seq + spos;
2709 pos = intern->parse_pos - spos;
2711 while (pos && *p != *intern->start_seq) {
2718 /* if a matching beginning char was found, try to match the
2719 * remainder of the old buffer.
2725 while (t < pos && *p == intern->start_seq[t]) {
2731 /* yeah, another partial match found in the *old*
2732 * buffer, now test the *current* buffer for
2746 } while (1); /* work hard to find a match ;-) */
2748 /* no match at all, release all (wrongly) matched chars so far */
2749 *release = intern->parse_pos;
2750 intern->state = PARSE_PRE_HEAD;
2755 * returns the position after the directive
2757 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2758 apr_size_t len, char ***store,
2759 apr_size_t **store_len)
2761 struct ssi_internal_ctx *intern = ctx->intern;
2762 const char *p = data;
2763 const char *ep = data + len;
2766 switch (intern->state) {
2767 case PARSE_DIRECTIVE:
2768 while (p < ep && !apr_isspace(*p)) {
2769 /* we have to consider the case of missing space between directive
2770 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2772 if (*p == *intern->end_seq) {
2773 intern->state = PARSE_DIRECTIVE_TAIL;
2774 intern->parse_pos = 1;
2781 if (p < ep) { /* found delimiter whitespace */
2782 intern->state = PARSE_DIRECTIVE_POSTNAME;
2783 *store = &intern->directive;
2784 *store_len = &intern->directive_len;
2789 case PARSE_DIRECTIVE_TAIL:
2790 pos = intern->parse_pos;
2792 while (p < ep && pos < intern->end_seq_len &&
2793 *p == intern->end_seq[pos]) {
2798 /* full match, we're done */
2799 if (pos == intern->end_seq_len) {
2800 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2801 *store = &intern->directive;
2802 *store_len = &intern->directive_len;
2806 /* partial match, the buffer is too small to match fully */
2808 intern->parse_pos = pos;
2812 /* no match. continue normal parsing */
2813 intern->state = PARSE_DIRECTIVE;
2816 case PARSE_DIRECTIVE_POSTTAIL:
2817 intern->state = PARSE_EXECUTE;
2818 intern->directive_len -= intern->end_seq_len;
2819 /* continue immediately with the next state */
2821 case PARSE_DIRECTIVE_POSTNAME:
2822 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2823 intern->state = PARSE_PRE_ARG;
2826 intern->argv = NULL;
2828 if (!intern->directive_len) {
2830 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2831 "directive name in parsed document %s",
2832 intern->r->filename);
2835 char *sp = intern->directive;
2836 char *sep = intern->directive + intern->directive_len;
2838 /* normalize directive name */
2839 for (; sp < sep; ++sp) {
2840 *sp = apr_tolower(*sp);
2847 /* get a rid of a gcc warning about unhandled enumerations */
2855 * find out whether the next token is (a possible) end_seq or an argument
2857 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
2860 struct ssi_internal_ctx *intern = ctx->intern;
2861 const char *p = data;
2862 const char *ep = data + len;
2864 /* skip leading WS */
2865 while (p < ep && apr_isspace(*p)) {
2869 /* buffer doesn't consist of whitespaces only */
2871 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
2878 * test the stream for end_seq. If it doesn't match at all, it must be an
2881 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
2884 struct ssi_internal_ctx *intern = ctx->intern;
2885 const char *p = data;
2886 const char *ep = data + len;
2887 apr_size_t pos = intern->parse_pos;
2889 if (PARSE_TAIL == intern->state) {
2890 intern->state = PARSE_TAIL_SEQ;
2891 pos = intern->parse_pos = 0;
2894 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
2899 /* bingo, full match */
2900 if (pos == intern->end_seq_len) {
2901 intern->state = PARSE_EXECUTE;
2905 /* partial match, the buffer is too small to match fully */
2907 intern->parse_pos = pos;
2911 /* no match. It must be an argument string then
2912 * The caller should cleanup and rewind to the reparse point
2914 intern->state = PARSE_ARG;
2919 * extract name=value from the buffer
2920 * A pcre-pattern could look (similar to):
2921 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
2923 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
2924 apr_size_t len, char ***store,
2925 apr_size_t **store_len)
2927 struct ssi_internal_ctx *intern = ctx->intern;
2928 const char *p = data;
2929 const char *ep = data + len;
2931 switch (intern->state) {
2934 * create argument structure and append it to the current list
2936 intern->current_arg = apr_palloc(ctx->dpool,
2937 sizeof(*intern->current_arg));
2938 intern->current_arg->next = NULL;
2941 if (!intern->argv) {
2942 intern->argv = intern->current_arg;
2945 arg_item_t *newarg = intern->argv;
2947 while (newarg->next) {
2948 newarg = newarg->next;
2950 newarg->next = intern->current_arg;
2953 /* check whether it's a valid one. If it begins with a quote, we
2954 * can safely assume, someone forgot the name of the argument
2957 case '"': case '\'': case '`':
2960 intern->state = PARSE_ARG_VAL;
2961 intern->quote = *p++;
2962 intern->current_arg->name = NULL;
2963 intern->current_arg->name_len = 0;
2966 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2967 "argument name for value to tag %s in %s",
2968 apr_pstrmemdup(intern->r->pool, intern->directive,
2969 intern->directive_len),
2970 intern->r->filename);
2975 intern->state = PARSE_ARG_NAME;
2977 /* continue immediately with next state */
2979 case PARSE_ARG_NAME:
2980 while (p < ep && !apr_isspace(*p) && *p != '=') {
2985 intern->state = PARSE_ARG_POSTNAME;
2986 *store = &intern->current_arg->name;
2987 *store_len = &intern->current_arg->name_len;
2992 case PARSE_ARG_POSTNAME:
2993 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
2994 intern->current_arg->name,
2995 intern->current_arg->name_len);
2996 if (!intern->current_arg->name_len) {
2998 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2999 "argument name for value to tag %s in %s",
3000 apr_pstrmemdup(intern->r->pool, intern->directive,
3001 intern->directive_len),
3002 intern->r->filename);
3005 char *sp = intern->current_arg->name;
3007 /* normalize the name */
3009 *sp = apr_tolower(*sp);
3014 intern->state = PARSE_ARG_EQ;
3015 /* continue with next state immediately */
3020 while (p < ep && apr_isspace(*p)) {
3026 intern->state = PARSE_ARG_PREVAL;
3029 else { /* no value */
3030 intern->current_arg->value = NULL;
3031 intern->state = PARSE_PRE_ARG;
3038 case PARSE_ARG_PREVAL:
3041 while (p < ep && apr_isspace(*p)) {
3045 /* buffer doesn't consist of whitespaces only */
3047 intern->state = PARSE_ARG_VAL;
3049 case '"': case '\'': case '`':
3050 intern->quote = *p++;
3053 intern->quote = '\0';
3061 case PARSE_ARG_VAL_ESC:
3062 if (*p == intern->quote) {
3065 intern->state = PARSE_ARG_VAL;
3066 /* continue with next state immediately */
3069 for (; p < ep; ++p) {
3070 if (intern->quote && *p == '\\') {
3073 intern->state = PARSE_ARG_VAL_ESC;
3077 if (*p != intern->quote) {
3081 else if (intern->quote && *p == intern->quote) {
3083 *store = &intern->current_arg->value;
3084 *store_len = &intern->current_arg->value_len;
3085 intern->state = PARSE_ARG_POSTVAL;
3088 else if (!intern->quote && apr_isspace(*p)) {
3090 *store = &intern->current_arg->value;
3091 *store_len = &intern->current_arg->value_len;
3092 intern->state = PARSE_ARG_POSTVAL;
3099 case PARSE_ARG_POSTVAL:
3101 * The value is still the raw input string. Finally clean it up.
3103 --(intern->current_arg->value_len);
3104 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3106 /* strip quote escaping \ from the string */
3107 if (intern->quote) {
3108 apr_size_t shift = 0;
3111 sp = intern->current_arg->value;
3112 ep = intern->current_arg->value + intern->current_arg->value_len;
3113 while (sp < ep && *sp != '\\') {
3116 for (; sp < ep; ++sp) {
3117 if (*sp == '\\' && sp[1] == intern->quote) {
3126 intern->current_arg->value_len -= shift;
3129 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3130 intern->state = PARSE_PRE_ARG;
3135 /* get a rid of a gcc warning about unhandled enumerations */
3139 return len; /* partial match of something */
3143 * This is the main loop over the current bucket brigade.
3145 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3147 include_ctx_t *ctx = f->ctx;
3148 struct ssi_internal_ctx *intern = ctx->intern;
3149 request_rec *r = f->r;
3150 apr_bucket *b = APR_BRIGADE_FIRST(bb);
3151 apr_bucket_brigade *pass_bb;
3152 apr_status_t rv = APR_SUCCESS;
3153 char *magic; /* magic pointer for sentinel use */
3156 if (APR_BRIGADE_EMPTY(bb)) {
3160 /* we may crash, since already cleaned up; hand over the responsibility
3161 * to the next filter;-)
3163 if (intern->seen_eos) {
3164 return ap_pass_brigade(f->next, bb);
3167 /* All stuff passed along has to be put into that brigade */
3168 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3170 /* initialization for this loop */
3171 intern->bytes_read = 0;
3176 /* loop over the current bucket brigade */
3177 while (b != APR_BRIGADE_SENTINEL(bb)) {
3178 const char *data = NULL;
3179 apr_size_t len, index, release;
3180 apr_bucket *newb = NULL;
3181 char **store = &magic;
3182 apr_size_t *store_len;
3184 /* handle meta buckets before reading any data */
3185 if (APR_BUCKET_IS_METADATA(b)) {
3186 newb = APR_BUCKET_NEXT(b);
3188 APR_BUCKET_REMOVE(b);
3190 if (APR_BUCKET_IS_EOS(b)) {
3191 intern->seen_eos = 1;
3193 /* Hit end of stream, time for cleanup ... But wait!
3194 * Perhaps we're not ready yet. We may have to loop one or
3195 * two times again to finish our work. In that case, we
3196 * just re-insert the EOS bucket to allow for an extra loop.
3198 * PARSE_EXECUTE means, we've hit a directive just before the
3199 * EOS, which is now waiting for execution.
3201 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3202 * no argument and no space between directive and end_seq
3203 * just before the EOS. (consider <!--#printenv--> as last
3204 * or only string within the stream). This state, however,
3205 * just cleans up and turns itself to PARSE_EXECUTE, which
3206 * will be passed through within the next (and actually
3209 if (PARSE_EXECUTE == intern->state ||
3210 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3211 APR_BUCKET_INSERT_BEFORE(newb, b);
3214 break; /* END OF STREAM */
3218 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3220 if (APR_BUCKET_IS_FLUSH(b)) {
3229 /* enough is enough ... */
3230 if (ctx->flush_now ||
3231 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3233 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3234 rv = ap_pass_brigade(f->next, pass_bb);
3235 if (!APR_STATUS_IS_SUCCESS(rv)) {
3236 apr_brigade_destroy(pass_bb);
3242 intern->bytes_read = 0;
3245 /* read the current bucket data */
3247 if (!intern->seen_eos) {
3248 if (intern->bytes_read > 0) {
3249 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3250 if (APR_STATUS_IS_EAGAIN(rv)) {
3256 if (!len || !APR_STATUS_IS_SUCCESS(rv)) {
3257 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3260 if (!APR_STATUS_IS_SUCCESS(rv)) {
3261 apr_brigade_destroy(pass_bb);
3265 intern->bytes_read += len;
3268 /* zero length bucket, fetch next one */
3269 if (!len && !intern->seen_eos) {
3270 b = APR_BUCKET_NEXT(b);
3275 * it's actually a data containing bucket, start/continue parsing
3278 switch (intern->state) {
3279 /* no current tag; search for start sequence */
3280 case PARSE_PRE_HEAD:
3281 index = find_start_sequence(ctx, data, len);
3284 apr_bucket_split(b, index);
3287 newb = APR_BUCKET_NEXT(b);
3288 if (ctx->flags & SSI_FLAG_PRINTING) {
3289 APR_BUCKET_REMOVE(b);
3290 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3293 apr_bucket_delete(b);
3297 /* now delete the start_seq stuff from the remaining bucket */
3298 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3299 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3300 ctx->flush_now = 1; /* pass pre-tag stuff */
3303 b = APR_BUCKET_NEXT(newb);
3304 apr_bucket_delete(newb);
3312 /* we're currently looking for the end of the start sequence */
3314 index = find_partial_start_sequence(ctx, data, len, &release);
3316 /* check if we mismatched earlier and have to release some chars */
3317 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3318 char *to_release = apr_palloc(ctx->pool, release);
3320 memcpy(to_release, intern->start_seq, release);
3321 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3322 f->c->bucket_alloc);
3323 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3326 if (index) { /* any match */
3327 /* now delete the start_seq stuff from the remaining bucket */
3328 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3329 apr_bucket_split(b, index);
3330 ctx->flush_now = 1; /* pass pre-tag stuff */
3332 newb = APR_BUCKET_NEXT(b);
3333 apr_bucket_delete(b);
3339 /* we're currently grabbing the directive name */
3340 case PARSE_DIRECTIVE:
3341 case PARSE_DIRECTIVE_POSTNAME:
3342 case PARSE_DIRECTIVE_TAIL:
3343 case PARSE_DIRECTIVE_POSTTAIL:
3344 index = find_directive(ctx, data, len, &store, &store_len);
3347 apr_bucket_split(b, index);
3348 newb = APR_BUCKET_NEXT(b);
3353 APR_BUCKET_REMOVE(b);
3354 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3358 /* time for cleanup? */
3359 if (store != &magic) {
3360 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3362 apr_brigade_cleanup(intern->tmp_bb);
3366 apr_bucket_delete(b);
3372 /* skip WS and find out what comes next (arg or end_seq) */
3374 index = find_arg_or_tail(ctx, data, len);
3376 if (index) { /* skipped whitespaces */
3378 apr_bucket_split(b, index);
3380 newb = APR_BUCKET_NEXT(b);
3381 apr_bucket_delete(b);
3387 /* currently parsing name[=val] */
3389 case PARSE_ARG_NAME:
3390 case PARSE_ARG_POSTNAME:
3392 case PARSE_ARG_PREVAL:
3394 case PARSE_ARG_VAL_ESC:
3395 case PARSE_ARG_POSTVAL:
3396 index = find_argument(ctx, data, len, &store, &store_len);
3399 apr_bucket_split(b, index);
3400 newb = APR_BUCKET_NEXT(b);
3405 APR_BUCKET_REMOVE(b);
3406 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3410 /* time for cleanup? */
3411 if (store != &magic) {
3412 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3414 apr_brigade_cleanup(intern->tmp_bb);
3418 apr_bucket_delete(b);
3424 /* try to match end_seq at current pos. */
3426 case PARSE_TAIL_SEQ:
3427 index = find_tail(ctx, data, len);
3429 switch (intern->state) {
3430 case PARSE_EXECUTE: /* full match */
3431 apr_bucket_split(b, index);
3432 newb = APR_BUCKET_NEXT(b);
3433 apr_bucket_delete(b);
3437 case PARSE_ARG: /* no match */
3438 /* PARSE_ARG must reparse at the beginning */
3439 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3440 b = APR_BRIGADE_FIRST(bb);
3443 default: /* partial match */
3444 newb = APR_BUCKET_NEXT(b);
3445 APR_BUCKET_REMOVE(b);
3446 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3453 /* now execute the parsed directive, cleanup the space and
3454 * start again with PARSE_PRE_HEAD
3457 /* if there was an error, it was already logged; just stop here */
3458 if (intern->error) {
3459 if (ctx->flags & SSI_FLAG_PRINTING) {
3460 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3465 include_handler_fn_t *handle_func;
3467 handle_func = apr_hash_get(include_handlers, intern->directive,
3468 intern->directive_len);
3471 DEBUG_INIT(ctx, f, pass_bb);
3472 rv = handle_func(ctx, f, pass_bb);
3473 if (!APR_STATUS_IS_SUCCESS(rv)) {
3474 apr_brigade_destroy(pass_bb);
3479 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3480 "unknown directive \"%s\" in parsed doc %s",
3481 apr_pstrmemdup(r->pool, intern->directive,
3482 intern->directive_len),
3484 if (ctx->flags & SSI_FLAG_PRINTING) {
3485 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3491 apr_pool_clear(ctx->dpool);
3492 apr_brigade_cleanup(intern->tmp_bb);
3494 /* Oooof. Done here, start next round */
3495 intern->state = PARSE_PRE_HEAD;
3498 } /* switch(ctx->state) */
3500 } /* while(brigade) */
3502 /* End of stream. Final cleanup */
3503 if (intern->seen_eos) {
3504 if (PARSE_HEAD == intern->state) {
3505 if (ctx->flags & SSI_FLAG_PRINTING) {
3506 char *to_release = apr_palloc(ctx->pool, intern->parse_pos);
3508 memcpy(to_release, intern->start_seq, intern->parse_pos);
3509 APR_BRIGADE_INSERT_TAIL(pass_bb,
3510 apr_bucket_pool_create(to_release,
3511 intern->parse_pos, ctx->pool,
3512 f->c->bucket_alloc));
3515 else if (PARSE_PRE_HEAD != intern->state) {
3516 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3517 "SSI directive was not properly finished at the end "
3518 "of parsed document %s", r->filename);
3519 if (ctx->flags & SSI_FLAG_PRINTING) {
3520 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3524 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3525 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3526 "missing closing endif directive in parsed document"
3527 " %s", r->filename);
3530 /* cleanup our temporary memory */
3531 apr_brigade_destroy(intern->tmp_bb);
3532 apr_pool_destroy(ctx->dpool);
3534 /* don't forget to finally insert the EOS bucket */
3535 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3538 /* if something's left over, pass it along */
3539 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3540 rv = ap_pass_brigade(f->next, pass_bb);
3546 apr_brigade_destroy(pass_bb);
3552 * +-------------------------------------------------------+
3556 * +-------------------------------------------------------+
3559 static int includes_setup(ap_filter_t *f)
3561 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3564 /* When our xbithack value isn't set to full or our platform isn't
3565 * providing group-level protection bits or our group-level bits do not
3566 * have group-execite on, we will set the no_local_copy value to 1 so
3567 * that we will not send 304s.
3569 if ((conf->xbithack != XBITHACK_FULL)
3570 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3571 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3572 f->r->no_local_copy = 1;
3578 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3580 request_rec *r = f->r;
3581 include_ctx_t *ctx = f->ctx;
3582 request_rec *parent;
3583 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3586 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3589 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3590 return ap_pass_brigade(f->next, b);
3594 struct ssi_internal_ctx *intern;
3596 /* create context for this filter */
3597 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3598 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3599 ctx->pool = r->pool;
3600 apr_pool_create(&ctx->dpool, ctx->pool);
3603 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3604 intern->seen_eos = 0;
3605 intern->state = PARSE_PRE_HEAD;
3606 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3607 if (ap_allow_options(r) & OPT_INCNOEXEC) {
3608 ctx->flags |= SSI_FLAG_NO_EXEC;
3611 ctx->if_nesting_level = 0;
3614 ctx->error_str = conf->default_error_msg;
3615 ctx->time_str = conf->default_time_fmt;
3616 intern->start_seq = sconf->default_start_tag;
3617 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3618 strlen(intern->start_seq));
3619 intern->end_seq = sconf->default_end_tag;
3620 intern->end_seq_len = strlen(intern->end_seq);
3621 intern->undefined_echo = conf->undefined_echo;
3622 intern->undefined_echo_len = strlen(conf->undefined_echo);
3625 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3626 /* Kludge --- for nested includes, we want to keep the subprocess
3627 * environment of the base document (for compatibility); that means
3628 * torquing our own last_modified date as well so that the
3629 * LAST_MODIFIED variable gets reset to the proper value if the
3630 * nested document resets <!--#config timefmt -->.
3632 r->subprocess_env = r->main->subprocess_env;
3633 apr_pool_join(r->main->pool, r->pool);
3634 r->finfo.mtime = r->main->finfo.mtime;
3637 /* we're not a nested include, so we create an initial
3639 ap_add_common_vars(r);
3641 add_include_vars(r, conf->default_time_fmt);
3643 /* Always unset the content-length. There is no way to know if
3644 * the content will be modified at some point by send_parsed_content.
3645 * It is very possible for us to not find any content in the first
3646 * 9k of the file, but still have to modify the content of the file.
3647 * If we are going to pass the file through send_parsed_content, then
3648 * the content-length should just be unset.
3650 apr_table_unset(f->r->headers_out, "Content-Length");
3652 /* Always unset the ETag/Last-Modified fields - see RFC2616 - 13.3.4.
3653 * We don't know if we are going to be including a file or executing
3654 * a program which may change the Last-Modified header or make the
3655 * content completely dynamic. Therefore, we can't support these
3657 * Exception: XBitHack full means we *should* set the Last-Modified field.
3659 apr_table_unset(f->r->headers_out, "ETag");
3661 /* Assure the platform supports Group protections */
3662 if ((conf->xbithack == XBITHACK_FULL)
3663 && (r->finfo.valid & APR_FINFO_GPROT)
3664 && (r->finfo.protection & APR_GEXECUTE)) {
3665 ap_update_mtime(r, r->finfo.mtime);
3666 ap_set_last_modified(r);
3669 apr_table_unset(f->r->headers_out, "Last-Modified");
3672 /* add QUERY stuff to env cause it ain't yet */
3674 char *arg_copy = apr_pstrdup(r->pool, r->args);
3676 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3677 ap_unescape_url(arg_copy);
3678 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3679 ap_escape_shell_cmd(r->pool, arg_copy));
3682 return send_parsed_content(f, b);
3685 static int include_fixup(request_rec *r)
3687 include_dir_config *conf;
3689 conf = ap_get_module_config(r->per_dir_config, &include_module);
3691 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3693 if (!r->content_type || !*r->content_type) {
3694 ap_set_content_type(r, "text/html");
3696 r->handler = "default-handler";
3699 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3700 /* These OS's don't support xbithack. This is being worked on. */
3706 if (conf->xbithack == XBITHACK_OFF) {
3710 if (!(r->finfo.protection & APR_UEXECUTE)) {
3714 if (!r->content_type || strcmp(r->content_type, "text/html")) {
3720 /* We always return declined, because the default handler actually
3721 * serves the file. All we have to do is add the filter.
3723 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3729 * +-------------------------------------------------------+
3731 * | Configuration Handling
3733 * +-------------------------------------------------------+
3736 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3738 include_dir_config *result = apr_palloc(p, sizeof(include_dir_config));
3740 result->default_error_msg = DEFAULT_ERROR_MSG;
3741 result->default_time_fmt = DEFAULT_TIME_FORMAT;
3742 result->undefined_echo = DEFAULT_UNDEFINED_ECHO;
3743 result->xbithack = DEFAULT_XBITHACK;
3748 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
3750 include_server_config *result;
3752 result = apr_palloc(p, sizeof(include_server_config));
3753 result->default_end_tag = DEFAULT_END_SEQUENCE;
3754 result->default_start_tag = DEFAULT_START_SEQUENCE;
3759 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
3761 include_dir_config *conf = mconfig;
3763 if (!strcasecmp(arg, "off")) {
3764 conf->xbithack = XBITHACK_OFF;
3766 else if (!strcasecmp(arg, "on")) {
3767 conf->xbithack = XBITHACK_ON;
3769 else if (!strcasecmp(arg, "full")) {
3770 conf->xbithack = XBITHACK_FULL;
3773 return "XBitHack must be set to Off, On, or Full";
3779 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
3782 include_server_config *conf;
3783 const char *p = tag;
3785 /* be consistent. (See below in set_default_end_tag) */
3787 if (apr_isspace(*p)) {
3788 return "SSIStartTag may not contain any whitespaces";
3793 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3794 conf->default_start_tag = tag;
3799 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
3802 include_server_config *conf;
3803 const char *p = tag;
3805 /* sanity check. The parser may fail otherwise */
3807 if (apr_isspace(*p)) {
3808 return "SSIEndTag may not contain any whitespaces";
3813 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3814 conf->default_end_tag = tag;
3819 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
3822 include_dir_config *conf = mconfig;
3823 conf->undefined_echo = msg;
3828 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
3831 include_dir_config *conf = mconfig;
3832 conf->default_error_msg = msg;
3837 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
3840 include_dir_config *conf = mconfig;
3841 conf->default_time_fmt = fmt;
3848 * +-------------------------------------------------------+
3850 * | Module Initialization and Configuration
3852 * +-------------------------------------------------------+
3855 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
3856 apr_pool_t *ptemp, server_rec *s)
3858 include_handlers = apr_hash_make(p);
3860 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
3862 if(ssi_pfn_register) {
3863 ssi_pfn_register("if", handle_if);
3864 ssi_pfn_register("set", handle_set);
3865 ssi_pfn_register("else", handle_else);
3866 ssi_pfn_register("elif", handle_elif);
3867 ssi_pfn_register("echo", handle_echo);
3868 ssi_pfn_register("endif", handle_endif);
3869 ssi_pfn_register("fsize", handle_fsize);
3870 ssi_pfn_register("config", handle_config);
3871 ssi_pfn_register("include", handle_include);
3872 ssi_pfn_register("flastmod", handle_flastmod);
3873 ssi_pfn_register("printenv", handle_printenv);
3879 static const command_rec includes_cmds[] =
3881 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
3882 "Off, On, or Full"),
3883 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
3885 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
3886 "a strftime(3) formatted string"),
3887 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
3888 "SSI Start String Tag"),
3889 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
3890 "SSI End String Tag"),
3891 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
3892 "String to be displayed if an echoed variable is undefined"),
3896 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
3898 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
3901 static void register_hooks(apr_pool_t *p)
3903 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
3904 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
3905 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
3906 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
3907 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
3908 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
3912 module AP_MODULE_DECLARE_DATA include_module =
3914 STANDARD20_MODULE_STUFF,
3915 create_includes_dir_config, /* dir config creater */
3916 NULL, /* dir merger --- default is to override */
3917 create_includes_server_config,/* server config */
3918 NULL, /* merge server config */
3919 includes_cmds, /* command apr_table_t */
3920 register_hooks /* register hooks */