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;
166 } include_dir_config;
169 const char *default_start_tag;
170 const char *default_end_tag;
171 const char *undefined_echo;
172 apr_size_t undefined_echo_len;
173 } include_server_config;
175 /* main parser states */
180 PARSE_DIRECTIVE_POSTNAME,
181 PARSE_DIRECTIVE_TAIL,
182 PARSE_DIRECTIVE_POSTTAIL,
197 typedef struct arg_item {
198 struct arg_item *next;
202 apr_size_t value_len;
205 #define MAX_NMATCH 10
211 regmatch_t match[MAX_NMATCH];
217 apr_size_t pattern_len;
220 struct ssi_internal_ctx {
224 char quote; /* quote character value (or \0) */
225 apr_size_t parse_pos; /* parse position of partial matches */
226 apr_size_t bytes_read;
228 apr_bucket_brigade *tmp_bb;
231 const char *start_seq;
232 bndm_t *start_seq_pat;
234 apr_size_t end_seq_len;
235 char *directive; /* name of the current directive */
236 apr_size_t directive_len; /* length of the current directive name */
238 arg_item_t *current_arg; /* currently parsed argument */
239 arg_item_t *argv; /* all arguments */
241 backref_t *re; /* NULL if there wasn't a regex yet */
246 apr_bucket_brigade *bb;
253 * +-------------------------------------------------------+
255 * | Debugging Utilities
257 * +-------------------------------------------------------+
262 #define TYPE_TOKEN(token, ttype) do { \
263 (token)->type = ttype; \
264 (token)->s = #ttype; \
267 #define CREATE_NODE(ctx, name) do { \
268 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
269 (name)->parent = (name)->left = (name)->right = NULL; \
271 (name)->dump_done = 0; \
274 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
280 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
283 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
284 debug__str, strlen(debug__str), ctx->pool,
285 ctx->intern->debug.f->c->bucket_alloc));
288 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
289 parse_node_t *d__c = node->child; \
291 if (!d__c->dump_done) { \
292 if (d__c->parent != node) { \
293 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
294 if (!d__c->parent) { \
295 debug_printf(ctx, "Parent of " #child " child node is " \
299 debug_printf(ctx, "Parent of " #child " child node " \
300 "points to another node (of type %s)!\n", \
301 d__c->parent->token.s); \
310 debug_printf(ctx, "%s(missing)\n", is); \
314 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
316 parse_node_t *current;
320 debug_printf(ctx, " -- Parse Tree empty --\n\n");
324 debug_printf(ctx, " ----- Parse Tree -----\n");
329 switch (current->token.type) {
332 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
333 current->token.value);
334 current->dump_done = 1;
335 current = current->parent;
346 if (!current->dump_done) {
347 debug_printf(ctx, "%s%s\n", is, current->token.s);
348 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
349 current->dump_done = 1;
352 DUMP__CHILD(ctx, is, current, left)
353 DUMP__CHILD(ctx, is, current, right)
355 if ((!current->left || current->left->dump_done) &&
356 (!current->right || current->right->dump_done)) {
358 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
359 if (current->left) current->left->dump_done = 0;
360 if (current->right) current->right->dump_done = 0;
361 current = current->parent;
367 if (!current->dump_done) {
368 debug_printf(ctx, "%s%s\n", is, current->token.s);
369 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
370 current->dump_done = 1;
373 DUMP__CHILD(ctx, is, current, right)
375 if (!current->right || current->right->dump_done) {
376 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
377 if (current->right) current->right->dump_done = 0;
378 current = current->parent;
384 if (!current->dump_done) {
385 debug_printf(ctx, "%sunmatched %s\n", is, current->token.s);
386 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
387 current->dump_done = 1;
390 DUMP__CHILD(ctx, is, current, right)
392 if (!current->right || current->right->dump_done) {
393 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
394 if (current->right) current->right->dump_done = 0;
395 current = current->parent;
401 /* it is possible to call this function within the parser loop, to see
402 * how the tree is built. That way, we must cleanup after us to dump
403 * always the whole tree
406 if (root->left) root->left->dump_done = 0;
407 if (root->right) root->right->dump_done = 0;
409 debug_printf(ctx, " --- End Parse Tree ---\n\n");
414 #define DEBUG_INIT(ctx, filter, brigade) do { \
415 (ctx)->intern->debug.f = filter; \
416 (ctx)->intern->debug.bb = brigade; \
419 #define DEBUG_PRINTF(arg) debug_printf arg
421 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
422 token_t *d__t = (token); \
424 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
425 DEBUG_PRINTF(((ctx), " Token: %s (%s)\n", d__t->s, d__t->value)); \
428 DEBUG_PRINTF((ctx, " Token: %s\n", d__t->s)); \
432 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
434 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
438 #define DEBUG_DUMP_COND(ctx, text) \
439 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
440 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
442 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
444 #else /* DEBUG_INCLUDE */
446 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
448 #define CREATE_NODE(ctx, name) do { \
449 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
450 (name)->parent = (name)->left = (name)->right = NULL; \
454 #define DEBUG_INIT(ctx, f, bb)
455 #define DEBUG_PRINTF(arg)
456 #define DEBUG_DUMP_TOKEN(ctx, token)
457 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
458 #define DEBUG_DUMP_COND(ctx, text)
459 #define DEBUG_DUMP_TREE(ctx, root)
461 #endif /* !DEBUG_INCLUDE */
465 * +-------------------------------------------------------+
467 * | Static Module Data
469 * +-------------------------------------------------------+
472 /* global module structure */
473 module AP_MODULE_DECLARE_DATA include_module;
475 /* function handlers for include directives */
476 static apr_hash_t *include_handlers;
478 /* forward declaration of handler registry */
479 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
481 /* Sentinel value to store in subprocess_env for items that
482 * shouldn't be evaluated until/unless they're actually used
484 static const char lazy_eval_sentinel;
485 #define LAZY_VALUE (&lazy_eval_sentinel)
488 #define DEFAULT_START_SEQUENCE "<!--#"
489 #define DEFAULT_END_SEQUENCE "-->"
490 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
491 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
492 #define DEFAULT_UNDEFINED_ECHO "(none)"
495 #define DEFAULT_XBITHACK XBITHACK_FULL
497 #define DEFAULT_XBITHACK XBITHACK_OFF
502 * +-------------------------------------------------------+
504 * | Environment/Expansion Functions
506 * +-------------------------------------------------------+
510 * decodes a string containing html entities or numeric character references.
511 * 's' is overwritten with the decoded string.
512 * If 's' is syntatically incorrect, then the followed fixups will be made:
513 * unknown entities will be left undecoded;
514 * references to unused numeric characters will be deleted.
515 * In particular, � will not be decoded, but will be deleted.
518 /* maximum length of any ISO-LATIN-1 HTML entity name. */
519 #define MAXENTLEN (6)
521 /* The following is a shrinking transformation, therefore safe. */
523 static void decodehtml(char *s)
528 static const char * const entlist[MAXENTLEN + 1] =
532 "lt\074gt\076", /* 2 */
533 "amp\046ETH\320eth\360", /* 3 */
534 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
535 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
537 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
538 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
539 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
541 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
542 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
543 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
544 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
545 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
546 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
549 /* Do a fast scan through the string until we find anything
550 * that needs more complicated handling
552 for (; *s != '&'; s++) {
558 for (p = s; *s != '\0'; s++, p++) {
563 /* find end of entity */
564 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
568 if (s[i] == '\0') { /* treat as normal data */
573 /* is it numeric ? */
575 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
576 val = val * 10 + s[j] - '0';
579 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
580 (val >= 127 && val <= 160) || val >= 256) {
581 p--; /* no data to output */
584 *p = RAW_ASCII_CHAR(val);
589 if (j > MAXENTLEN || entlist[j] == NULL) {
592 continue; /* skip it */
594 for (ents = entlist[j]; *ents != '\0'; ents += i) {
595 if (strncmp(s + 1, ents, j) == 0) {
601 *p = '&'; /* unknown */
604 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
613 static void add_include_vars(request_rec *r, const char *timefmt)
615 apr_table_t *e = r->subprocess_env;
618 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
619 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
620 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
621 apr_table_setn(e, "DOCUMENT_URI", r->uri);
622 if (r->path_info && *r->path_info) {
623 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
625 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
626 if ((t = strrchr(r->filename, '/'))) {
627 apr_table_setn(e, "DOCUMENT_NAME", ++t);
630 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
633 char *arg_copy = apr_pstrdup(r->pool, r->args);
635 ap_unescape_url(arg_copy);
636 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
637 ap_escape_shell_cmd(r->pool, arg_copy));
641 static const char *add_include_vars_lazy(request_rec *r, const char *var)
644 if (!strcasecmp(var, "DATE_LOCAL")) {
645 include_dir_config *conf =
646 (include_dir_config *)ap_get_module_config(r->per_dir_config,
648 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 0);
650 else if (!strcasecmp(var, "DATE_GMT")) {
651 include_dir_config *conf =
652 (include_dir_config *)ap_get_module_config(r->per_dir_config,
654 val = ap_ht_time(r->pool, r->request_time, conf->default_time_fmt, 1);
656 else if (!strcasecmp(var, "LAST_MODIFIED")) {
657 include_dir_config *conf =
658 (include_dir_config *)ap_get_module_config(r->per_dir_config,
660 val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0);
662 else if (!strcasecmp(var, "USER_NAME")) {
663 if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
672 apr_table_setn(r->subprocess_env, var, val);
677 static const char *get_include_var(const char *var, include_ctx_t *ctx)
680 request_rec *r = ctx->intern->r;
682 if (apr_isdigit(*var) && !var[1]) {
683 int idx = *var - '0';
684 backref_t *re = ctx->intern->re;
686 /* Handle $0 .. $9 from the last regex evaluated.
687 * The choice of returning NULL strings on not-found,
688 * v.s. empty strings on an empty match is deliberate.
691 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "regex capture $%d "
692 "refers to no regex in %s", idx, r->filename);
696 if (re->nsub < idx) {
697 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
698 "regex capture $%d is out of range (last regex "
699 "was: '%s') in %s", idx, re->rexp, r->filename);
703 if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
707 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
708 re->match[idx].rm_eo - re->match[idx].rm_so);
712 val = apr_table_get(r->subprocess_env, var);
714 if (val == LAZY_VALUE) {
715 val = add_include_vars_lazy(r, var);
723 * Do variable substitution on strings
725 * (Note: If out==NULL, this function allocs a buffer for the resulting
726 * string from ctx->pool. The return value is always the parsed string)
728 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
729 apr_size_t length, int leave_name)
731 request_rec *r = ctx->intern->r;
732 result_item_t *result = NULL, *current = NULL;
733 apr_size_t outlen = 0, inlen, span;
734 char *ret = NULL, *eout = NULL;
738 /* sanity check, out && !length is not supported */
739 ap_assert(out && length);
742 eout = out + length - 1;
745 span = strcspn(in, "\\$");
751 apr_cpystrn(out, in, length);
754 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
755 ? length - 1 : inlen);
761 /* well, actually something to do */
766 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
771 current = result = apr_palloc(ctx->dpool, sizeof(*result));
772 current->next = NULL;
773 current->string = in;
778 /* loop for specials */
780 if ((out && out >= eout) || (length && outlen >= length)) {
784 /* prepare next entry */
785 if (!out && current->len) {
786 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
787 current = current->next;
788 current->next = NULL;
797 *out++ = (p[1] == '$') ? *++p : *p;
802 current->string = (p[1] == '$') ? ++p : p;
811 else { /* *p == '$' */
812 const char *newp = NULL, *ep, *key = NULL;
815 ep = ap_strchr_c(++p, '}');
817 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Missing '}' on "
818 "variable \"%s\" in %s", p, r->filename);
823 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
830 while (*ep == '_' || apr_isalnum(*ep)) {
835 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
841 /* empty name results in a copy of '$' in the output string */
848 current->string = p++;
853 const char *val = get_include_var(key, ctx);
859 else if (leave_name) {
866 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
871 current->string = val;
880 if ((out && out >= eout) || (length && outlen >= length)) {
884 /* check the remainder */
885 if (*p && (span = strcspn(p, "\\$")) > 0) {
886 if (!out && current->len) {
887 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
888 current = current->next;
889 current->next = NULL;
893 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
904 } while (p < in+inlen);
906 /* assemble result */
918 if (length && outlen > length) {
922 ret = out = apr_palloc(ctx->pool, outlen + 1);
927 memcpy(out, result->string, (out+result->len <= ep)
928 ? result->len : (ep-out));
931 result = result->next;
932 } while (result && out < ep);
942 * +-------------------------------------------------------+
944 * | Conditional Expression Parser
946 * +-------------------------------------------------------+
949 static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
953 backref_t *re = ctx->intern->re;
956 compiled = ap_pregcomp(ctx->dpool, rexp, REG_EXTENDED);
958 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->intern->r, "unable to "
959 "compile pattern \"%s\"", rexp);
964 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
967 re->source = apr_pstrdup(ctx->pool, string);
968 re->rexp = apr_pstrdup(ctx->pool, rexp);
969 re->nsub = compiled->re_nsub;
970 rc = !ap_regexec(compiled, string, MAX_NMATCH, re->match, 0);
972 ap_pregfree(ctx->dpool, compiled);
976 static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token)
988 /* Skip leading white space */
989 while (apr_isspace(**parse)) {
998 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
1002 switch (*(*parse)++) {
1004 TYPE_TOKEN(token, TOKEN_LBRACE);
1007 TYPE_TOKEN(token, TOKEN_RBRACE);
1010 TYPE_TOKEN(token, TOKEN_EQ);
1013 if (**parse == '=') {
1014 TYPE_TOKEN(token, TOKEN_NE);
1018 TYPE_TOKEN(token, TOKEN_NOT);
1024 TYPE_TOKEN(token, TOKEN_RE);
1028 if (**parse == '|') {
1029 TYPE_TOKEN(token, TOKEN_OR);
1035 if (**parse == '&') {
1036 TYPE_TOKEN(token, TOKEN_AND);
1042 if (**parse == '=') {
1043 TYPE_TOKEN(token, TOKEN_GE);
1047 TYPE_TOKEN(token, TOKEN_GT);
1050 if (**parse == '=') {
1051 TYPE_TOKEN(token, TOKEN_LE);
1055 TYPE_TOKEN(token, TOKEN_LT);
1060 * It's a string or regex token
1062 token->value = unmatched ? *parse : p;
1064 /* Now search for the next token, which finishes this string */
1067 for (; **parse; p = ++*parse) {
1068 if (**parse == '\\') {
1078 if (**parse == unmatched) {
1083 } else if (apr_isspace(**parse)) {
1101 if ((*parse)[1] == **parse) {
1115 token->value = apr_pstrdup(pool, "");
1118 apr_size_t len = p - token->value - shift;
1119 char *c = apr_palloc(pool, len + 1);
1125 const char *e = ap_strchr_c(p, '\\');
1143 static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1145 parse_node_t *new, *root = NULL, *current = NULL;
1146 request_rec *r = ctx->intern->r;
1148 const char *parse = expr;
1149 int retval = 0, was_unmatched = 0;
1157 /* Create Parse Tree */
1159 DEBUG_DUMP_TREE(ctx, root);
1160 CREATE_NODE(ctx, new);
1162 was_unmatched = get_ptoken(ctx->dpool, &parse, &new->token);
1167 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1168 DEBUG_DUMP_TOKEN(ctx, &new->token);
1170 switch (new->token.type) {
1173 root = current = new;
1177 switch (current->token.type) {
1179 current->token.value =
1180 apr_pstrcat(ctx->dpool, current->token.value,
1181 *current->token.value ? " " : "",
1182 new->token.value, NULL);
1195 new->parent = current;
1196 current = current->right = new;
1200 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1201 "Invalid expression \"%s\" in file %s",
1210 root = current = new;
1214 switch (current->token.type) {
1221 new->parent = current;
1222 current = current->right = new;
1226 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1227 "Invalid expression \"%s\" in file %s",
1237 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1238 "Invalid expression \"%s\" in file %s",
1243 /* Percolate upwards */
1245 switch (current->token.type) {
1258 current = current->parent;
1265 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1266 "Invalid expression \"%s\" in file %s",
1276 new->left->parent = new;
1281 new->left = current->right;
1282 current->right = new;
1283 new->parent = current;
1290 root = current = new;
1293 /* Percolate upwards */
1295 switch (current->token.type) {
1309 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1310 "Invalid expression \"%s\" in file %s",
1319 new->left->parent = new;
1324 new->left = current->right;
1325 current->right = new;
1326 new->parent = current;
1338 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1339 "Invalid expression \"%s\" in file %s",
1344 /* Percolate upwards */
1346 switch (current->token.type) {
1350 current = current->parent;
1359 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1360 "Invalid expression \"%s\" in file %s",
1370 new->left->parent = new;
1375 new->left = current->right;
1376 current->right = new;
1377 new->parent = current;
1384 if (current->token.type == TOKEN_LBRACE) {
1385 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1388 current = current->parent;
1391 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1392 "Unmatched ')' in \"%s\" in file %s",
1401 root = current = new;
1404 /* Percolate upwards */
1406 switch (current->token.type) {
1420 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1421 "Invalid expression \"%s\" in file %s",
1430 new->left->parent = new;
1435 new->left = current->right;
1436 current->right = new;
1437 new->parent = current;
1447 /* Evaluate Parse Tree */
1450 switch (current->token.type) {
1452 DEBUG_PRINTF((ctx, " Evaluate %s\n", current->token.s));
1454 buffer = ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1455 SSI_EXPAND_DROP_NAME);
1457 current->token.value = buffer;
1458 current->value = !!*current->token.value;
1460 current = current->parent;
1464 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1465 "No operator before regex of expr \"%s\" in file %s",
1472 DEBUG_PRINTF((ctx, " Evaluate %s\n", current->token.s));
1474 if (!current->left || !current->right) {
1475 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1476 "Invalid expression \"%s\" in file %s",
1481 if (!current->left->done) {
1482 switch (current->left->token.type) {
1484 buffer = ap_ssi_parse_string(ctx,
1485 current->left->token.value,
1486 NULL, 0, SSI_EXPAND_DROP_NAME);
1488 current->left->token.value = buffer;
1489 current->left->value = !!*current->left->token.value;
1490 current->left->done = 1;
1494 current = current->left;
1498 if (!current->right->done) {
1499 switch (current->right->token.type) {
1501 buffer = ap_ssi_parse_string(ctx,
1502 current->right->token.value,
1503 NULL, 0, SSI_EXPAND_DROP_NAME);
1505 current->right->token.value = buffer;
1506 current->right->value = !!*current->right->token.value;
1507 current->right->done = 1;
1511 current = current->right;
1516 DEBUG_PRINTF((ctx, " Left: %c\n", current->left->value
1518 DEBUG_PRINTF((ctx, " Right: %c\n", current->right->value
1521 if (current->token.type == TOKEN_AND) {
1522 current->value = current->left->value && current->right->value;
1525 current->value = current->left->value || current->right->value;
1528 DEBUG_PRINTF((ctx, " Returning %c\n", current->value
1532 current = current->parent;
1537 DEBUG_PRINTF((ctx, " Evaluate %s\n", current->token.s));
1539 if (!current->left || !current->right ||
1540 current->left->token.type != TOKEN_STRING ||
1541 (current->right->token.type != TOKEN_STRING &&
1542 current->right->token.type != TOKEN_RE)) {
1543 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1544 "Invalid expression \"%s\" in file %s",
1549 buffer = ap_ssi_parse_string(ctx, current->left->token.value,
1550 NULL, 0, SSI_EXPAND_DROP_NAME);
1552 current->left->token.value = buffer;
1553 buffer = ap_ssi_parse_string(ctx, current->right->token.value,
1554 NULL, 0, SSI_EXPAND_DROP_NAME);
1556 current->right->token.value = buffer;
1558 if (current->right->token.type == TOKEN_RE) {
1559 DEBUG_PRINTF((ctx, " Re Compare (%s) with /%s/\n",
1560 current->left->token.value,
1561 current->right->token.value));
1563 current->value = re_check(ctx, current->left->token.value,
1564 current->right->token.value);
1567 DEBUG_PRINTF((ctx, " Compare (%s) with (%s)\n",
1568 current->left->token.value,
1569 current->right->token.value));
1571 current->value = !strcmp(current->left->token.value,
1572 current->right->token.value);
1575 if (current->token.type == TOKEN_NE) {
1576 current->value = !current->value;
1579 DEBUG_PRINTF((ctx, " Returning %c\n", current->value
1583 current = current->parent;
1590 DEBUG_PRINTF((ctx, " Evaluate %s\n", current->token.s));
1592 if (!current->left || !current->right ||
1593 current->left->token.type != TOKEN_STRING ||
1594 current->right->token.type != TOKEN_STRING) {
1595 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1596 "Invalid expression \"%s\" in file %s",
1601 buffer = ap_ssi_parse_string(ctx, current->left->token.value, NULL,
1602 0, SSI_EXPAND_DROP_NAME);
1604 current->left->token.value = buffer;
1605 buffer = ap_ssi_parse_string(ctx, current->right->token.value, NULL,
1606 0, SSI_EXPAND_DROP_NAME);
1607 current->right->token.value = buffer;
1609 DEBUG_PRINTF((ctx, " Compare (%s) with (%s)\n",
1610 current->left->token.value,
1611 current->right->token.value));
1613 current->value = strcmp(current->left->token.value,
1614 current->right->token.value);
1616 if (current->token.type == TOKEN_GE) {
1617 current->value = current->value >= 0;
1619 else if (current->token.type == TOKEN_GT) {
1620 current->value = current->value > 0;
1622 else if (current->token.type == TOKEN_LE) {
1623 current->value = current->value <= 0;
1625 else if (current->token.type == TOKEN_LT) {
1626 current->value = current->value < 0;
1629 current->value = 0; /* Don't return -1 if unknown token */
1632 DEBUG_PRINTF((ctx, " Returning %c\n", current->value
1636 current = current->parent;
1640 if (current->right) {
1641 if (!current->right->done) {
1642 current = current->right;
1645 current->value = !current->right->value;
1651 DEBUG_PRINTF((ctx, " Evaluate %s: %c\n", current->token.s,
1652 current->value ? '1' : '0'));
1655 current = current->parent;
1659 if (current->right) {
1660 if (!current->right->done) {
1661 current = current->right;
1664 current->value = current->right->value;
1670 DEBUG_PRINTF((ctx, " Evaluate %s: %c\n", current->token.s,
1671 current->value ? '1' : '0'));
1674 current = current->parent;
1678 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1679 "Unmatched '(' in \"%s\" in file %s",
1685 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1686 "Unmatched ')' in \"%s\" in file %s",
1692 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
1693 "bad token type (internal parser error)");
1699 return (root ? root->value : 0);
1704 * +-------------------------------------------------------+
1708 * +-------------------------------------------------------+
1712 * Extract the next tag name and value.
1713 * If there are no more tags, set the tag name to NULL.
1714 * The tag value is html decoded if dodecode is non-zero.
1715 * The tag value may be NULL if there is no tag value..
1717 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1718 char **tag_val, int dodecode)
1720 if (!ctx->intern->argv) {
1727 *tag_val = ctx->intern->argv->value;
1728 *tag = ctx->intern->argv->name;
1730 ctx->intern->argv = ctx->intern->argv->next;
1732 if (dodecode && *tag_val) {
1733 decodehtml(*tag_val);
1739 static int find_file(request_rec *r, const char *directive, const char *tag,
1740 char *tag_val, apr_finfo_t *finfo)
1742 char *to_send = tag_val;
1743 request_rec *rr = NULL;
1745 char *error_fmt = NULL;
1746 apr_status_t rv = APR_SUCCESS;
1748 if (!strcmp(tag, "file")) {
1751 /* be safe; only files in this directory or below allowed */
1752 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1753 APR_FILEPATH_NOTABOVEROOT |
1754 APR_FILEPATH_SECUREROOTTEST |
1755 APR_FILEPATH_NOTABSOLUTE, r->pool);
1757 if (!APR_STATUS_IS_SUCCESS(rv)) {
1758 error_fmt = "unable to access file \"%s\" "
1759 "in parsed file %s";
1762 /* note: it is okay to pass NULL for the "next filter" since
1763 we never attempt to "run" this sub request. */
1764 rr = ap_sub_req_lookup_file(newpath, r, NULL);
1766 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1767 to_send = rr->filename;
1768 if ((rv = apr_stat(finfo, to_send,
1769 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1770 && rv != APR_INCOMPLETE) {
1771 error_fmt = "unable to get information about \"%s\" "
1772 "in parsed file %s";
1776 error_fmt = "unable to lookup information about \"%s\" "
1777 "in parsed file %s";
1783 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1784 rv, r, error_fmt, to_send, r->filename);
1787 if (rr) ap_destroy_sub_req(rr);
1791 else if (!strcmp(tag, "virtual")) {
1792 /* note: it is okay to pass NULL for the "next filter" since
1793 we never attempt to "run" this sub request. */
1794 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1796 if (rr->status == HTTP_OK && rr->finfo.filetype != 0) {
1797 memcpy((char *) finfo, (const char *) &rr->finfo,
1799 ap_destroy_sub_req(rr);
1803 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to get "
1804 "information about \"%s\" in parsed file %s",
1805 tag_val, r->filename);
1806 ap_destroy_sub_req(rr);
1811 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
1812 "to tag %s in %s", tag, directive, r->filename);
1818 * <!--#include virtual|file="..." [virtual|file="..."] ... -->
1820 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1821 apr_bucket_brigade *bb)
1823 request_rec *r = f->r;
1826 ap_log_rerror(APLOG_MARK,
1827 (ctx->flags & SSI_FLAG_PRINTING)
1828 ? APLOG_ERR : APLOG_WARNING,
1829 0, r, "missing argument for include element in %s",
1833 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1838 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1844 char *tag_val = NULL;
1845 request_rec *rr = NULL;
1846 char *error_fmt = NULL;
1847 char *parsed_string;
1849 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1850 if (!tag || !tag_val) {
1854 if (strcmp(tag, "virtual") && strcmp(tag, "file")) {
1855 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
1856 "\"%s\" to tag include in %s", tag, r->filename);
1857 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1861 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1862 SSI_EXPAND_DROP_NAME);
1863 if (tag[0] == 'f') {
1867 /* be safe; only files in this directory or below allowed */
1868 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1869 APR_FILEPATH_NOTABOVEROOT |
1870 APR_FILEPATH_SECUREROOTTEST |
1871 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1873 if (!APR_STATUS_IS_SUCCESS(rv)) {
1874 error_fmt = "unable to include file \"%s\" in parsed file %s";
1877 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1881 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1884 if (!error_fmt && rr->status != HTTP_OK) {
1885 error_fmt = "unable to include \"%s\" in parsed file %s";
1888 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1889 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1891 error_fmt = "unable to include potential exec \"%s\" in parsed "
1899 /* try to avoid recursive includes. We do this by walking
1900 * up the r->main list of subrequests, and at each level
1901 * walking back through any internal redirects. At each
1902 * step, we compare the filenames and the URIs.
1904 * The filename comparison catches a recursive include
1905 * with an ever-changing URL, eg.
1906 * <!--#include virtual=
1907 * "$REQUEST_URI/$QUERY_STRING?$QUERY_STRING/x" -->
1908 * which, although they would eventually be caught because
1909 * we have a limit on the length of files, etc., can
1910 * recurse for a while.
1912 * The URI comparison catches the case where the filename
1913 * is changed while processing the request, so the
1914 * current name is never the same as any previous one.
1915 * This can happen with "DocumentRoot /foo" when you
1916 * request "/" on the server and it includes "/".
1917 * This only applies to modules such as mod_dir that
1918 * (somewhat improperly) mess with r->filename outside
1919 * of a filename translation phase.
1921 for (p = r; p && !founddupe; p = p->main) {
1922 for (q = p; q; q = q->prev) {
1923 if ((q->filename && rr->filename &&
1924 (strcmp(q->filename, rr->filename) == 0)) ||
1925 ((*q->uri == '/') &&
1926 (strcmp(q->uri, rr->uri) == 0))) {
1935 error_fmt = "Recursive include of \"%s\" in parsed file %s";
1939 /* See the Kludge in includes_filter for why.
1940 * Basically, it puts a bread crumb in here, then looks
1941 * for the crumb later to see if its been here.
1944 ap_set_module_config(rr->request_config, &include_module, r);
1947 if (!error_fmt && ap_run_sub_req(rr)) {
1948 error_fmt = "unable to include \"%s\" in parsed file %s";
1952 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val,
1954 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1957 /* destroy the sub request */
1959 ap_destroy_sub_req(rr);
1971 * <!--#echo [encoding="..."] var="..." [encoding="..."] var="..." ... -->
1973 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1974 apr_bucket_brigade *bb)
1976 enum {E_NONE, E_URL, E_ENTITY} encode;
1977 request_rec *r = f->r;
1980 ap_log_rerror(APLOG_MARK,
1981 (ctx->flags & SSI_FLAG_PRINTING)
1982 ? APLOG_ERR : APLOG_WARNING,
1983 0, r, "missing argument for echo element in %s",
1987 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1992 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2000 char *tag_val = NULL;
2002 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2003 if (!tag || !tag_val) {
2007 if (!strcmp(tag, "var")) {
2009 const char *echo_text = NULL;
2012 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
2013 0, SSI_EXPAND_DROP_NAME),
2022 echo_text = ap_escape_uri(ctx->dpool, val);
2025 echo_text = ap_escape_html(ctx->dpool, val);
2029 e_len = strlen(echo_text);
2032 include_server_config *sconf;
2034 sconf = ap_get_module_config(r->server->module_config,
2036 echo_text = sconf->undefined_echo;
2037 e_len = sconf->undefined_echo_len;
2040 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
2041 apr_pstrmemdup(ctx->pool, echo_text, e_len),
2042 e_len, ctx->pool, f->c->bucket_alloc));
2044 else if (!strcmp(tag, "encoding")) {
2045 if (!strcasecmp(tag_val, "none")) {
2048 else if (!strcasecmp(tag_val, "url")) {
2051 else if (!strcasecmp(tag_val, "entity")) {
2055 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
2056 "\"%s\" to parameter \"encoding\" of tag echo in "
2057 "%s", tag_val, r->filename);
2058 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2063 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
2064 "\"%s\" in tag echo of %s", tag, r->filename);
2065 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2074 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."] -->
2076 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
2077 apr_bucket_brigade *bb)
2079 request_rec *r = f->r;
2080 apr_table_t *env = r->subprocess_env;
2083 ap_log_rerror(APLOG_MARK,
2084 (ctx->flags & SSI_FLAG_PRINTING)
2085 ? APLOG_ERR : APLOG_WARNING,
2086 0, r, "missing argument for config element in %s",
2090 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2095 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2101 char *tag_val = NULL;
2102 char *parsed_string;
2104 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
2105 if (!tag || !tag_val) {
2109 if (!strcmp(tag, "errmsg")) {
2110 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2111 SSI_EXPAND_DROP_NAME);
2113 else if (!strcmp(tag, "timefmt")) {
2114 apr_time_t date = r->request_time;
2116 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2117 SSI_EXPAND_DROP_NAME);
2119 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
2121 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
2123 apr_table_setn(env, "LAST_MODIFIED",
2124 ap_ht_time(r->pool, r->finfo.mtime,
2127 else if (!strcmp(tag, "sizefmt")) {
2128 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2129 SSI_EXPAND_DROP_NAME);
2130 if (!strcmp(parsed_string, "bytes")) {
2131 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
2133 else if (!strcmp(parsed_string, "abbrev")) {
2134 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
2137 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value "
2138 "\"%s\" to parameter \"sizefmt\" of tag config "
2139 "in %s", parsed_string, r->filename);
2140 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2145 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter "
2146 "\"%s\" to tag config in %s", tag, r->filename);
2147 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2156 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
2158 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
2159 apr_bucket_brigade *bb)
2161 request_rec *r = f->r;
2164 ap_log_rerror(APLOG_MARK,
2165 (ctx->flags & SSI_FLAG_PRINTING)
2166 ? APLOG_ERR : APLOG_WARNING,
2167 0, r, "missing argument for fsize element in %s",
2171 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2176 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2182 char *tag_val = NULL;
2184 char *parsed_string;
2186 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2187 if (!tag || !tag_val) {
2191 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2192 SSI_EXPAND_DROP_NAME);
2194 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
2198 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
2199 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
2200 len = 4; /* omit the \0 terminator */
2203 apr_size_t l, x, pos;
2206 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
2207 len = l = strlen(tmp);
2209 for (x = 0; x < l; ++x) {
2210 if (x && !((l - x) % 3)) {
2216 buf = apr_pstrmemdup(ctx->pool, tmp, len);
2219 buf = apr_palloc(ctx->pool, len);
2221 for (pos = x = 0; x < l; ++x) {
2222 if (x && !((l - x) % 3)) {
2225 buf[pos++] = tmp[x];
2230 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
2231 ctx->pool, f->c->bucket_alloc));
2234 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2243 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
2245 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
2246 apr_bucket_brigade *bb)
2248 request_rec *r = f->r;
2251 ap_log_rerror(APLOG_MARK,
2252 (ctx->flags & SSI_FLAG_PRINTING)
2253 ? APLOG_ERR : APLOG_WARNING,
2254 0, r, "missing argument for flastmod element in %s",
2258 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2263 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2269 char *tag_val = NULL;
2271 char *parsed_string;
2273 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2274 if (!tag || !tag_val) {
2278 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2279 SSI_EXPAND_DROP_NAME);
2281 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2285 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2286 t_len = strlen(t_val);
2288 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2289 ctx->pool, f->c->bucket_alloc));
2292 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2301 * <!--#if expr="..." -->
2303 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2304 apr_bucket_brigade *bb)
2308 request_rec *r = f->r;
2309 int expr_ret, was_error;
2311 if (ctx->argc != 1) {
2312 ap_log_rerror(APLOG_MARK,
2313 (ctx->flags & SSI_FLAG_PRINTING)
2314 ? APLOG_ERR : APLOG_WARNING,
2316 ? "too many arguments for if element in %s"
2317 : "missing expr argument for if element in %s",
2321 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2322 ++(ctx->if_nesting_level);
2326 if (ctx->argc != 1) {
2327 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2331 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2333 if (strcmp(tag, "expr")) {
2334 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2335 "to tag if in %s", tag, r->filename);
2336 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2341 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr value for if "
2342 "element in %s", r->filename);
2343 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2347 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2349 expr_ret = parse_expr(ctx, expr, &was_error);
2352 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2357 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2360 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2363 DEBUG_DUMP_COND(ctx, " if");
2365 ctx->if_nesting_level = 0;
2371 * <!--#elif expr="..." -->
2373 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2374 apr_bucket_brigade *bb)
2378 request_rec *r = f->r;
2379 int expr_ret, was_error;
2381 if (ctx->argc != 1) {
2382 ap_log_rerror(APLOG_MARK,
2383 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2385 ? "too many arguments for if element in %s"
2386 : "missing expr argument for if element in %s",
2390 if (ctx->if_nesting_level) {
2394 if (ctx->argc != 1) {
2395 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2399 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2401 if (strcmp(tag, "expr")) {
2402 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" "
2403 "to tag if in %s", tag, r->filename);
2404 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2409 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "missing expr in elif "
2410 "statement: %s", r->filename);
2411 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2415 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2416 DEBUG_DUMP_COND(ctx, " elif");
2418 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2419 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2423 expr_ret = parse_expr(ctx, expr, &was_error);
2426 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2431 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2434 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2437 DEBUG_DUMP_COND(ctx, " elif");
2445 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2446 apr_bucket_brigade *bb)
2448 request_rec *r = f->r;
2451 ap_log_rerror(APLOG_MARK,
2452 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2453 0, r, "else directive does not take tags in %s",
2457 if (ctx->if_nesting_level) {
2462 if (ctx->flags & SSI_FLAG_PRINTING) {
2463 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2469 DEBUG_DUMP_COND(ctx, " else");
2471 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2472 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2475 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2484 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2485 apr_bucket_brigade *bb)
2487 request_rec *r = f->r;
2490 ap_log_rerror(APLOG_MARK,
2491 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2492 0, r, "endif directive does not take tags in %s",
2496 if (ctx->if_nesting_level) {
2497 --(ctx->if_nesting_level);
2502 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2506 DEBUG_DUMP_COND(ctx, "endif");
2508 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2514 * <!--#set var="..." value="..." ... -->
2516 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2517 apr_bucket_brigade *bb)
2520 request_rec *r = f->r;
2521 request_rec *sub = r->main;
2522 apr_pool_t *p = r->pool;
2524 if (ctx->argc < 2) {
2525 ap_log_rerror(APLOG_MARK,
2526 (ctx->flags & SSI_FLAG_PRINTING)
2527 ? APLOG_ERR : APLOG_WARNING,
2528 0, r, "missing argument for set element in %s",
2532 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2536 if (ctx->argc < 2) {
2537 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2541 /* we need to use the 'main' request pool to set notes as that is
2551 char *tag_val = NULL;
2553 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2555 if (!tag || !tag_val) {
2559 if (!strcmp(tag, "var")) {
2560 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2561 SSI_EXPAND_DROP_NAME);
2563 else if (!strcmp(tag, "value")) {
2564 char *parsed_string;
2567 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must "
2568 "precede value in set directive in %s",
2570 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2574 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2575 SSI_EXPAND_DROP_NAME);
2576 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2577 apr_pstrdup(p, parsed_string));
2580 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Invalid tag for set "
2581 "directive in %s", r->filename);
2582 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2593 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2594 apr_bucket_brigade *bb)
2596 request_rec *r = f->r;
2597 const apr_array_header_t *arr;
2598 const apr_table_entry_t *elts;
2602 ap_log_rerror(APLOG_MARK,
2603 (ctx->flags & SSI_FLAG_PRINTING)
2604 ? APLOG_ERR : APLOG_WARNING,
2605 0, r, "printenv directive does not take tags in %s",
2609 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2614 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2618 arr = apr_table_elts(r->subprocess_env);
2619 elts = (apr_table_entry_t *)arr->elts;
2621 for (i = 0; i < arr->nelts; ++i) {
2622 const char *key_text, *val_text;
2623 char *key_val, *next;
2624 apr_size_t k_len, v_len, kv_length;
2627 key_text = ap_escape_html(ctx->dpool, elts[i].key);
2628 k_len = strlen(key_text);
2631 val_text = elts[i].val;
2632 if (val_text == LAZY_VALUE) {
2633 val_text = add_include_vars_lazy(r, elts[i].key);
2635 val_text = ap_escape_html(ctx->dpool, elts[i].val);
2636 v_len = strlen(val_text);
2638 /* assemble result */
2639 kv_length = k_len + v_len + sizeof("=\n");
2640 key_val = apr_palloc(ctx->pool, kv_length);
2643 memcpy(next, key_text, k_len);
2646 memcpy(next, val_text, v_len);
2651 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1,
2652 ctx->pool, f->c->bucket_alloc));
2661 * +-------------------------------------------------------+
2663 * | Main Includes-Filter Engine
2665 * +-------------------------------------------------------+
2668 /* This is an implementation of the BNDM search algorithm.
2670 * Fast and Flexible String Matching by Combining Bit-parallelism and
2671 * Suffix Automata (2001)
2672 * Gonzalo Navarro, Mathieu Raffinot
2674 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2676 * Initial code submitted by Sascha Schumann.
2679 /* Precompile the bndm_t data structure. */
2680 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2683 const char *ne = n + nl;
2684 bndm_t *t = apr_palloc(pool, sizeof(*t));
2686 memset(t->T, 0, sizeof(unsigned int) * 256);
2687 t->pattern_len = nl;
2689 for (x = 1; n < ne; x <<= 1) {
2690 t->T[(unsigned char) *n++] |= x;
2698 /* Implements the BNDM search algorithm (as described above).
2700 * h - the string to look in
2701 * hl - length of the string to look for
2702 * t - precompiled bndm structure against the pattern
2704 * Returns the count of character that is the first match or hl if no
2707 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2710 const char *he, *p, *pi;
2711 unsigned int *T, x, d;
2718 nl = t->pattern_len;
2720 pi = h - 1; /* pi: p initial */
2721 p = pi + nl; /* compare window right to left. point to the first char */
2727 d &= T[(unsigned char) *p--];
2750 * returns the index position of the first byte of start_seq (or the len of
2751 * the buffer as non-match)
2753 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2756 struct ssi_internal_ctx *intern = ctx->intern;
2757 apr_size_t slen = intern->start_seq_pat->pattern_len;
2762 p = data; /* try partial match at the end of the buffer (below) */
2765 /* try fast bndm search over the buffer
2766 * (hopefully the whole start sequence can be found in this buffer)
2768 index = bndm(intern->start_seq_pat, data, len);
2770 /* wow, found it. ready. */
2772 intern->state = PARSE_DIRECTIVE;
2776 /* ok, the pattern can't be found as whole in the buffer,
2777 * check the end for a partial match
2779 p = data + len - slen + 1;
2785 while (p < ep && *p != *intern->start_seq) {
2791 /* found a possible start_seq start */
2796 while (p < ep && *p == intern->start_seq[pos]) {
2801 /* partial match found. Store the info for the next round */
2803 intern->state = PARSE_HEAD;
2804 intern->parse_pos = pos;
2809 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2810 * and a string data of "--.-" and the end of the buffer
2812 p = data + index + 1;
2820 * returns the first byte *after* the partial (or final) match.
2822 * If we had to trick with the start_seq start, 'release' returns the
2823 * number of chars of the start_seq which appeared not to be part of a
2824 * full tag and may have to be passed down the filter chain.
2826 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2829 apr_size_t *release)
2831 struct ssi_internal_ctx *intern = ctx->intern;
2832 apr_size_t pos, spos = 0;
2833 apr_size_t slen = intern->start_seq_pat->pattern_len;
2836 pos = intern->parse_pos;
2843 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2850 intern->state = PARSE_DIRECTIVE;
2854 /* the whole buffer is a partial match */
2856 intern->parse_pos = pos;
2860 /* No match so far, but again:
2861 * We must try all combinations, since the start_seq is a random
2862 * user supplied string
2864 * So: look if the first char of start_seq appears somewhere within
2865 * the current partial match. If it does, try to start a match that
2866 * begins with this offset. (This can happen, if a strange
2867 * start_seq like "---->" spans buffers)
2869 if (spos < intern->parse_pos) {
2873 p = intern->start_seq + spos;
2874 pos = intern->parse_pos - spos;
2876 while (pos && *p != *intern->start_seq) {
2883 /* if a matching beginning char was found, try to match the
2884 * remainder of the old buffer.
2890 while (t < pos && *p == intern->start_seq[t]) {
2896 /* yeah, another partial match found in the *old*
2897 * buffer, now test the *current* buffer for
2911 } while (1); /* work hard to find a match ;-) */
2913 /* no match at all, release all (wrongly) matched chars so far */
2914 *release = intern->parse_pos;
2915 intern->state = PARSE_PRE_HEAD;
2920 * returns the position after the directive
2922 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2923 apr_size_t len, char ***store,
2924 apr_size_t **store_len)
2926 struct ssi_internal_ctx *intern = ctx->intern;
2927 const char *p = data;
2928 const char *ep = data + len;
2931 switch (intern->state) {
2932 case PARSE_DIRECTIVE:
2933 while (p < ep && !apr_isspace(*p)) {
2934 /* we have to consider the case of missing space between directive
2935 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
2937 if (*p == *intern->end_seq) {
2938 intern->state = PARSE_DIRECTIVE_TAIL;
2939 intern->parse_pos = 1;
2946 if (p < ep) { /* found delimiter whitespace */
2947 intern->state = PARSE_DIRECTIVE_POSTNAME;
2948 *store = &intern->directive;
2949 *store_len = &intern->directive_len;
2954 case PARSE_DIRECTIVE_TAIL:
2955 pos = intern->parse_pos;
2957 while (p < ep && pos < intern->end_seq_len &&
2958 *p == intern->end_seq[pos]) {
2963 /* full match, we're done */
2964 if (pos == intern->end_seq_len) {
2965 intern->state = PARSE_DIRECTIVE_POSTTAIL;
2966 *store = &intern->directive;
2967 *store_len = &intern->directive_len;
2971 /* partial match, the buffer is too small to match fully */
2973 intern->parse_pos = pos;
2977 /* no match. continue normal parsing */
2978 intern->state = PARSE_DIRECTIVE;
2981 case PARSE_DIRECTIVE_POSTTAIL:
2982 intern->state = PARSE_EXECUTE;
2983 intern->directive_len -= intern->end_seq_len;
2984 /* continue immediately with the next state */
2986 case PARSE_DIRECTIVE_POSTNAME:
2987 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
2988 intern->state = PARSE_PRE_ARG;
2991 intern->argv = NULL;
2993 if (!intern->directive_len) {
2995 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
2996 "directive name in parsed document %s",
2997 intern->r->filename);
3000 char *sp = intern->directive;
3001 char *sep = intern->directive + intern->directive_len;
3003 /* normalize directive name */
3004 for (; sp < sep; ++sp) {
3005 *sp = apr_tolower(*sp);
3012 /* get a rid of a gcc warning about unhandled enumerations */
3020 * find out whether the next token is (a possible) end_seq or an argument
3022 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
3025 struct ssi_internal_ctx *intern = ctx->intern;
3026 const char *p = data;
3027 const char *ep = data + len;
3029 /* skip leading WS */
3030 while (p < ep && apr_isspace(*p)) {
3034 /* buffer doesn't consist of whitespaces only */
3036 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
3043 * test the stream for end_seq. If it doesn't match at all, it must be an
3046 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
3049 struct ssi_internal_ctx *intern = ctx->intern;
3050 const char *p = data;
3051 const char *ep = data + len;
3052 apr_size_t pos = intern->parse_pos;
3054 if (PARSE_TAIL == intern->state) {
3055 intern->state = PARSE_TAIL_SEQ;
3056 pos = intern->parse_pos = 0;
3059 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
3064 /* bingo, full match */
3065 if (pos == intern->end_seq_len) {
3066 intern->state = PARSE_EXECUTE;
3070 /* partial match, the buffer is too small to match fully */
3072 intern->parse_pos = pos;
3076 /* no match. It must be an argument string then
3077 * The caller should cleanup and rewind to the reparse point
3079 intern->state = PARSE_ARG;
3084 * extract name=value from the buffer
3085 * A pcre-pattern could look (similar to):
3086 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
3088 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
3089 apr_size_t len, char ***store,
3090 apr_size_t **store_len)
3092 struct ssi_internal_ctx *intern = ctx->intern;
3093 const char *p = data;
3094 const char *ep = data + len;
3096 switch (intern->state) {
3099 * create argument structure and append it to the current list
3101 intern->current_arg = apr_palloc(ctx->dpool,
3102 sizeof(*intern->current_arg));
3103 intern->current_arg->next = NULL;
3106 if (!intern->argv) {
3107 intern->argv = intern->current_arg;
3110 arg_item_t *newarg = intern->argv;
3112 while (newarg->next) {
3113 newarg = newarg->next;
3115 newarg->next = intern->current_arg;
3118 /* check whether it's a valid one. If it begins with a quote, we
3119 * can safely assume, someone forgot the name of the argument
3122 case '"': case '\'': case '`':
3125 intern->state = PARSE_ARG_VAL;
3126 intern->quote = *p++;
3127 intern->current_arg->name = NULL;
3128 intern->current_arg->name_len = 0;
3131 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
3132 "argument name for value to tag %s in %s",
3133 apr_pstrmemdup(intern->r->pool, intern->directive,
3134 intern->directive_len),
3135 intern->r->filename);
3140 intern->state = PARSE_ARG_NAME;
3142 /* continue immediately with next state */
3144 case PARSE_ARG_NAME:
3145 while (p < ep && !apr_isspace(*p) && *p != '=') {
3150 intern->state = PARSE_ARG_POSTNAME;
3151 *store = &intern->current_arg->name;
3152 *store_len = &intern->current_arg->name_len;
3157 case PARSE_ARG_POSTNAME:
3158 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
3159 intern->current_arg->name,
3160 intern->current_arg->name_len);
3161 if (!intern->current_arg->name_len) {
3163 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing "
3164 "argument name for value to tag %s in %s",
3165 apr_pstrmemdup(intern->r->pool, intern->directive,
3166 intern->directive_len),
3167 intern->r->filename);
3170 char *sp = intern->current_arg->name;
3172 /* normalize the name */
3174 *sp = apr_tolower(*sp);
3179 intern->state = PARSE_ARG_EQ;
3180 /* continue with next state immediately */
3185 while (p < ep && apr_isspace(*p)) {
3191 intern->state = PARSE_ARG_PREVAL;
3194 else { /* no value */
3195 intern->current_arg->value = NULL;
3196 intern->state = PARSE_PRE_ARG;
3203 case PARSE_ARG_PREVAL:
3206 while (p < ep && apr_isspace(*p)) {
3210 /* buffer doesn't consist of whitespaces only */
3212 intern->state = PARSE_ARG_VAL;
3214 case '"': case '\'': case '`':
3215 intern->quote = *p++;
3218 intern->quote = '\0';
3226 case PARSE_ARG_VAL_ESC:
3227 if (*p == intern->quote) {
3230 intern->state = PARSE_ARG_VAL;
3231 /* continue with next state immediately */
3234 for (; p < ep; ++p) {
3235 if (intern->quote && *p == '\\') {
3238 intern->state = PARSE_ARG_VAL_ESC;
3242 if (*p != intern->quote) {
3246 else if (intern->quote && *p == intern->quote) {
3248 *store = &intern->current_arg->value;
3249 *store_len = &intern->current_arg->value_len;
3250 intern->state = PARSE_ARG_POSTVAL;
3253 else if (!intern->quote && apr_isspace(*p)) {
3255 *store = &intern->current_arg->value;
3256 *store_len = &intern->current_arg->value_len;
3257 intern->state = PARSE_ARG_POSTVAL;
3264 case PARSE_ARG_POSTVAL:
3266 * The value is still the raw input string. Finally clean it up.
3268 --(intern->current_arg->value_len);
3269 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3271 /* strip quote escaping \ from the string */
3272 if (intern->quote) {
3273 apr_size_t shift = 0;
3276 sp = intern->current_arg->value;
3277 ep = intern->current_arg->value + intern->current_arg->value_len;
3278 while (sp < ep && *sp != '\\') {
3281 for (; sp < ep; ++sp) {
3282 if (*sp == '\\' && sp[1] == intern->quote) {
3291 intern->current_arg->value_len -= shift;
3294 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3295 intern->state = PARSE_PRE_ARG;
3300 /* get a rid of a gcc warning about unhandled enumerations */
3304 return len; /* partial match of something */
3308 * This is the main loop over the current bucket brigade.
3310 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3312 include_ctx_t *ctx = f->ctx;
3313 struct ssi_internal_ctx *intern = ctx->intern;
3314 request_rec *r = f->r;
3315 apr_bucket *b = APR_BRIGADE_FIRST(bb);
3316 apr_bucket_brigade *pass_bb;
3317 apr_status_t rv = APR_SUCCESS;
3318 char *magic; /* magic pointer for sentinel use */
3321 if (APR_BRIGADE_EMPTY(bb)) {
3325 /* we may crash, since already cleaned up; hand over the responsibility
3326 * to the next filter;-)
3328 if (intern->seen_eos) {
3329 return ap_pass_brigade(f->next, bb);
3332 /* All stuff passed along has to be put into that brigade */
3333 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3335 /* initialization for this loop */
3336 intern->bytes_read = 0;
3341 /* loop over the current bucket brigade */
3342 while (b != APR_BRIGADE_SENTINEL(bb)) {
3343 const char *data = NULL;
3344 apr_size_t len, index, release;
3345 apr_bucket *newb = NULL;
3346 char **store = &magic;
3347 apr_size_t *store_len;
3349 /* handle meta buckets before reading any data */
3350 if (APR_BUCKET_IS_METADATA(b)) {
3351 newb = APR_BUCKET_NEXT(b);
3353 APR_BUCKET_REMOVE(b);
3355 if (APR_BUCKET_IS_EOS(b)) {
3356 intern->seen_eos = 1;
3358 /* Hit end of stream, time for cleanup ... But wait!
3359 * Perhaps we're not ready yet. We may have to loop one or
3360 * two times again to finish our work. In that case, we
3361 * just re-insert the EOS bucket to allow for an extra loop.
3363 * PARSE_EXECUTE means, we've hit a directive just before the
3364 * EOS, which is now waiting for execution.
3366 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3367 * no argument and no space between directive and end_seq
3368 * just before the EOS. (consider <!--#printenv--> as last
3369 * or only string within the stream). This state, however,
3370 * just cleans up and turns itself to PARSE_EXECUTE, which
3371 * will be passed through within the next (and actually
3374 if (PARSE_EXECUTE == intern->state ||
3375 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3376 APR_BUCKET_INSERT_BEFORE(newb, b);
3379 break; /* END OF STREAM */
3383 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3385 if (APR_BUCKET_IS_FLUSH(b)) {
3394 /* enough is enough ... */
3395 if (ctx->flush_now ||
3396 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3398 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3399 rv = ap_pass_brigade(f->next, pass_bb);
3400 if (!APR_STATUS_IS_SUCCESS(rv)) {
3401 apr_brigade_destroy(pass_bb);
3407 intern->bytes_read = 0;
3410 /* read the current bucket data */
3412 if (!intern->seen_eos) {
3413 if (intern->bytes_read > 0) {
3414 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3415 if (APR_STATUS_IS_EAGAIN(rv)) {
3421 if (!len || !APR_STATUS_IS_SUCCESS(rv)) {
3422 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3425 if (!APR_STATUS_IS_SUCCESS(rv)) {
3426 apr_brigade_destroy(pass_bb);
3430 intern->bytes_read += len;
3433 /* zero length bucket, fetch next one */
3434 if (!len && !intern->seen_eos) {
3435 b = APR_BUCKET_NEXT(b);
3440 * it's actually a data containing bucket, start/continue parsing
3443 switch (intern->state) {
3444 /* no current tag; search for start sequence */
3445 case PARSE_PRE_HEAD:
3446 index = find_start_sequence(ctx, data, len);
3449 apr_bucket_split(b, index);
3452 newb = APR_BUCKET_NEXT(b);
3453 if (ctx->flags & SSI_FLAG_PRINTING) {
3454 APR_BUCKET_REMOVE(b);
3455 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3458 apr_bucket_delete(b);
3462 /* now delete the start_seq stuff from the remaining bucket */
3463 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3464 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3465 ctx->flush_now = 1; /* pass pre-tag stuff */
3468 b = APR_BUCKET_NEXT(newb);
3469 apr_bucket_delete(newb);
3477 /* we're currently looking for the end of the start sequence */
3479 index = find_partial_start_sequence(ctx, data, len, &release);
3481 /* check if we mismatched earlier and have to release some chars */
3482 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3483 char *to_release = apr_palloc(ctx->pool, release);
3485 memcpy(to_release, intern->start_seq, release);
3486 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3487 f->c->bucket_alloc);
3488 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3491 if (index) { /* any match */
3492 /* now delete the start_seq stuff from the remaining bucket */
3493 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3494 apr_bucket_split(b, index);
3495 ctx->flush_now = 1; /* pass pre-tag stuff */
3497 newb = APR_BUCKET_NEXT(b);
3498 apr_bucket_delete(b);
3504 /* we're currently grabbing the directive name */
3505 case PARSE_DIRECTIVE:
3506 case PARSE_DIRECTIVE_POSTNAME:
3507 case PARSE_DIRECTIVE_TAIL:
3508 case PARSE_DIRECTIVE_POSTTAIL:
3509 index = find_directive(ctx, data, len, &store, &store_len);
3512 apr_bucket_split(b, index);
3513 newb = APR_BUCKET_NEXT(b);
3518 APR_BUCKET_REMOVE(b);
3519 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3523 /* time for cleanup? */
3524 if (store != &magic) {
3525 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3527 apr_brigade_cleanup(intern->tmp_bb);
3531 apr_bucket_delete(b);
3537 /* skip WS and find out what comes next (arg or end_seq) */
3539 index = find_arg_or_tail(ctx, data, len);
3541 if (index) { /* skipped whitespaces */
3543 apr_bucket_split(b, index);
3545 newb = APR_BUCKET_NEXT(b);
3546 apr_bucket_delete(b);
3552 /* currently parsing name[=val] */
3554 case PARSE_ARG_NAME:
3555 case PARSE_ARG_POSTNAME:
3557 case PARSE_ARG_PREVAL:
3559 case PARSE_ARG_VAL_ESC:
3560 case PARSE_ARG_POSTVAL:
3561 index = find_argument(ctx, data, len, &store, &store_len);
3564 apr_bucket_split(b, index);
3565 newb = APR_BUCKET_NEXT(b);
3570 APR_BUCKET_REMOVE(b);
3571 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3575 /* time for cleanup? */
3576 if (store != &magic) {
3577 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3579 apr_brigade_cleanup(intern->tmp_bb);
3583 apr_bucket_delete(b);
3589 /* try to match end_seq at current pos. */
3591 case PARSE_TAIL_SEQ:
3592 index = find_tail(ctx, data, len);
3594 switch (intern->state) {
3595 case PARSE_EXECUTE: /* full match */
3596 apr_bucket_split(b, index);
3597 newb = APR_BUCKET_NEXT(b);
3598 apr_bucket_delete(b);
3602 case PARSE_ARG: /* no match */
3603 /* PARSE_ARG must reparse at the beginning */
3604 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3605 b = APR_BRIGADE_FIRST(bb);
3608 default: /* partial match */
3609 newb = APR_BUCKET_NEXT(b);
3610 APR_BUCKET_REMOVE(b);
3611 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3618 /* now execute the parsed directive, cleanup the space and
3619 * start again with PARSE_PRE_HEAD
3622 /* if there was an error, it was already logged; just stop here */
3623 if (intern->error) {
3624 if (ctx->flags & SSI_FLAG_PRINTING) {
3625 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3630 include_handler_fn_t *handle_func;
3632 handle_func = apr_hash_get(include_handlers, intern->directive,
3633 intern->directive_len);
3636 DEBUG_INIT(ctx, f, pass_bb);
3637 rv = handle_func(ctx, f, pass_bb);
3638 if (!APR_STATUS_IS_SUCCESS(rv)) {
3639 apr_brigade_destroy(pass_bb);
3644 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3645 "unknown directive \"%s\" in parsed doc %s",
3646 apr_pstrmemdup(r->pool, intern->directive,
3647 intern->directive_len),
3649 if (ctx->flags & SSI_FLAG_PRINTING) {
3650 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3656 apr_pool_clear(ctx->dpool);
3657 apr_brigade_cleanup(intern->tmp_bb);
3659 /* Oooof. Done here, start next round */
3660 intern->state = PARSE_PRE_HEAD;
3663 } /* switch(ctx->state) */
3665 } /* while(brigade) */
3667 /* End of stream. Final cleanup */
3668 if (intern->seen_eos) {
3669 if (PARSE_HEAD == intern->state) {
3670 if (ctx->flags & SSI_FLAG_PRINTING) {
3671 char *to_release = apr_palloc(ctx->pool, intern->parse_pos);
3673 memcpy(to_release, intern->start_seq, intern->parse_pos);
3674 APR_BRIGADE_INSERT_TAIL(pass_bb,
3675 apr_bucket_pool_create(to_release,
3676 intern->parse_pos, ctx->pool,
3677 f->c->bucket_alloc));
3680 else if (PARSE_PRE_HEAD != intern->state) {
3681 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
3682 "SSI directive was not properly finished at the end "
3683 "of parsed document %s", r->filename);
3684 if (ctx->flags & SSI_FLAG_PRINTING) {
3685 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3689 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3690 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
3691 "missing closing endif directive in parsed document"
3692 " %s", r->filename);
3695 /* cleanup our temporary memory */
3696 apr_brigade_destroy(intern->tmp_bb);
3697 apr_pool_destroy(ctx->dpool);
3699 /* don't forget to finally insert the EOS bucket */
3700 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3703 /* if something's left over, pass it along */
3704 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3705 rv = ap_pass_brigade(f->next, pass_bb);
3711 apr_brigade_destroy(pass_bb);
3717 * +-------------------------------------------------------+
3721 * +-------------------------------------------------------+
3724 static int includes_setup(ap_filter_t *f)
3726 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3729 /* When our xbithack value isn't set to full or our platform isn't
3730 * providing group-level protection bits or our group-level bits do not
3731 * have group-execite on, we will set the no_local_copy value to 1 so
3732 * that we will not send 304s.
3734 if ((conf->xbithack != XBITHACK_FULL)
3735 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3736 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3737 f->r->no_local_copy = 1;
3743 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3745 request_rec *r = f->r;
3746 include_ctx_t *ctx = f->ctx;
3747 request_rec *parent;
3748 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3751 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3754 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3755 return ap_pass_brigade(f->next, b);
3759 struct ssi_internal_ctx *intern;
3761 /* create context for this filter */
3762 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3763 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3764 ctx->pool = r->pool;
3765 apr_pool_create(&ctx->dpool, ctx->pool);
3768 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3769 intern->seen_eos = 0;
3770 intern->state = PARSE_PRE_HEAD;
3771 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3772 if (ap_allow_options(r) & OPT_INCNOEXEC) {
3773 ctx->flags |= SSI_FLAG_NO_EXEC;
3776 ctx->if_nesting_level = 0;
3779 ctx->error_str = conf->default_error_msg;
3780 ctx->time_str = conf->default_time_fmt;
3781 intern->start_seq = sconf->default_start_tag;
3782 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3783 strlen(intern->start_seq));
3784 intern->end_seq = sconf->default_end_tag;
3785 intern->end_seq_len = strlen(intern->end_seq);
3788 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3789 /* Kludge --- for nested includes, we want to keep the subprocess
3790 * environment of the base document (for compatibility); that means
3791 * torquing our own last_modified date as well so that the
3792 * LAST_MODIFIED variable gets reset to the proper value if the
3793 * nested document resets <!--#config timefmt -->.
3795 r->subprocess_env = r->main->subprocess_env;
3796 apr_pool_join(r->main->pool, r->pool);
3797 r->finfo.mtime = r->main->finfo.mtime;
3800 /* we're not a nested include, so we create an initial
3802 ap_add_common_vars(r);
3804 add_include_vars(r, conf->default_time_fmt);
3806 /* Always unset the content-length. There is no way to know if
3807 * the content will be modified at some point by send_parsed_content.
3808 * It is very possible for us to not find any content in the first
3809 * 9k of the file, but still have to modify the content of the file.
3810 * If we are going to pass the file through send_parsed_content, then
3811 * the content-length should just be unset.
3813 apr_table_unset(f->r->headers_out, "Content-Length");
3815 /* Always unset the ETag/Last-Modified fields - see RFC2616 - 13.3.4.
3816 * We don't know if we are going to be including a file or executing
3817 * a program which may change the Last-Modified header or make the
3818 * content completely dynamic. Therefore, we can't support these
3820 * Exception: XBitHack full means we *should* set the Last-Modified field.
3822 apr_table_unset(f->r->headers_out, "ETag");
3824 /* Assure the platform supports Group protections */
3825 if ((conf->xbithack == XBITHACK_FULL)
3826 && (r->finfo.valid & APR_FINFO_GPROT)
3827 && (r->finfo.protection & APR_GEXECUTE)) {
3828 ap_update_mtime(r, r->finfo.mtime);
3829 ap_set_last_modified(r);
3832 apr_table_unset(f->r->headers_out, "Last-Modified");
3835 /* add QUERY stuff to env cause it ain't yet */
3837 char *arg_copy = apr_pstrdup(r->pool, r->args);
3839 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3840 ap_unescape_url(arg_copy);
3841 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3842 ap_escape_shell_cmd(r->pool, arg_copy));
3845 return send_parsed_content(f, b);
3848 static int include_fixup(request_rec *r)
3850 include_dir_config *conf;
3852 conf = ap_get_module_config(r->per_dir_config, &include_module);
3854 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3856 if (!r->content_type || !*r->content_type) {
3857 ap_set_content_type(r, "text/html");
3859 r->handler = "default-handler";
3862 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3863 /* These OS's don't support xbithack. This is being worked on. */
3869 if (conf->xbithack == XBITHACK_OFF) {
3873 if (!(r->finfo.protection & APR_UEXECUTE)) {
3877 if (!r->content_type || strcmp(r->content_type, "text/html")) {
3883 /* We always return declined, because the default handler actually
3884 * serves the file. All we have to do is add the filter.
3886 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3892 * +-------------------------------------------------------+
3894 * | Configuration Handling
3896 * +-------------------------------------------------------+
3899 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
3901 include_dir_config *result = apr_palloc(p, sizeof(include_dir_config));
3903 result->default_error_msg = DEFAULT_ERROR_MSG;
3904 result->default_time_fmt = DEFAULT_TIME_FORMAT;
3905 result->xbithack = DEFAULT_XBITHACK;
3910 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
3912 include_server_config *result;
3914 result = apr_palloc(p, sizeof(include_server_config));
3915 result->default_end_tag = DEFAULT_END_SEQUENCE;
3916 result->default_start_tag = DEFAULT_START_SEQUENCE;
3917 result->undefined_echo = DEFAULT_UNDEFINED_ECHO;
3918 result->undefined_echo_len = sizeof(DEFAULT_UNDEFINED_ECHO) - 1;
3923 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
3925 include_dir_config *conf = mconfig;
3927 if (!strcasecmp(arg, "off")) {
3928 conf->xbithack = XBITHACK_OFF;
3930 else if (!strcasecmp(arg, "on")) {
3931 conf->xbithack = XBITHACK_ON;
3933 else if (!strcasecmp(arg, "full")) {
3934 conf->xbithack = XBITHACK_FULL;
3937 return "XBitHack must be set to Off, On, or Full";
3943 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
3946 include_server_config *conf;
3947 const char *p = tag;
3949 /* be consistent. (See below in set_default_end_tag) */
3951 if (apr_isspace(*p)) {
3952 return "SSIStartTag may not contain any whitespaces";
3957 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3958 conf->default_start_tag = tag;
3963 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
3966 include_server_config *conf;
3967 const char *p = tag;
3969 /* sanity check. The parser may fail otherwise */
3971 if (apr_isspace(*p)) {
3972 return "SSIEndTag may not contain any whitespaces";
3977 conf= ap_get_module_config(cmd->server->module_config , &include_module);
3978 conf->default_end_tag = tag;
3983 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
3986 include_server_config *conf;
3988 conf = ap_get_module_config(cmd->server->module_config, &include_module);
3989 conf->undefined_echo = msg;
3990 conf->undefined_echo_len = strlen(msg);
3995 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
3998 include_dir_config *conf = mconfig;
3999 conf->default_error_msg = msg;
4004 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
4007 include_dir_config *conf = mconfig;
4008 conf->default_time_fmt = fmt;
4015 * +-------------------------------------------------------+
4017 * | Module Initialization and Configuration
4019 * +-------------------------------------------------------+
4022 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
4023 apr_pool_t *ptemp, server_rec *s)
4025 include_handlers = apr_hash_make(p);
4027 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
4029 if(ssi_pfn_register) {
4030 ssi_pfn_register("if", handle_if);
4031 ssi_pfn_register("set", handle_set);
4032 ssi_pfn_register("else", handle_else);
4033 ssi_pfn_register("elif", handle_elif);
4034 ssi_pfn_register("echo", handle_echo);
4035 ssi_pfn_register("endif", handle_endif);
4036 ssi_pfn_register("fsize", handle_fsize);
4037 ssi_pfn_register("config", handle_config);
4038 ssi_pfn_register("include", handle_include);
4039 ssi_pfn_register("flastmod", handle_flastmod);
4040 ssi_pfn_register("printenv", handle_printenv);
4046 static const command_rec includes_cmds[] =
4048 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
4049 "Off, On, or Full"),
4050 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
4052 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
4053 "a strftime(3) formatted string"),
4054 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
4055 "SSI Start String Tag"),
4056 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
4057 "SSI End String Tag"),
4058 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, RSRC_CONF,
4059 "String to be displayed if an echoed variable is undefined"),
4063 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
4065 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
4068 static void register_hooks(apr_pool_t *p)
4070 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
4071 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
4072 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
4073 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
4074 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
4075 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
4079 module AP_MODULE_DECLARE_DATA include_module =
4081 STANDARD20_MODULE_STUFF,
4082 create_includes_dir_config, /* dir config creater */
4083 NULL, /* dir merger --- default is to override */
4084 create_includes_server_config,/* server config */
4085 NULL, /* merge server config */
4086 includes_cmds, /* command apr_table_t */
4087 register_hooks /* register hooks */