1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include "apr_strings.h"
19 #include "apr_thread_proc.h"
23 #include "apr_optional.h"
25 #define APR_WANT_STRFUNC
26 #define APR_WANT_MEMFUNC
29 #include "ap_config.h"
30 #include "util_filter.h"
32 #include "http_config.h"
33 #include "http_core.h"
34 #include "http_request.h"
35 #include "http_core.h"
36 #include "http_protocol.h"
38 #include "http_main.h"
39 #include "util_script.h"
40 #include "http_core.h"
41 #include "mod_include.h"
44 /* helper for Latin1 <-> entity encoding */
45 #if APR_CHARSET_EBCDIC
46 #include "util_ebcdic.h"
47 #define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
49 #else /* APR_CHARSET_EBCDIC */
50 #define RAW_ASCII_CHAR(ch) (ch)
51 #endif /* !APR_CHARSET_EBCDIC */
55 * +-------------------------------------------------------+
57 * | Types and Structures
59 * +-------------------------------------------------------+
62 /* sll used for string expansion */
63 typedef struct result_item {
64 struct result_item *next;
69 /* conditional expression parser stuff */
96 typedef struct parse_node {
97 struct parse_node *parent;
98 struct parse_node *left;
99 struct parse_node *right;
116 const char *default_error_msg;
117 const char *default_time_fmt;
118 const char *undefined_echo;
120 signed char lastmodified;
122 signed char legacy_expr;
123 } include_dir_config;
126 const char *default_start_tag;
127 const char *default_end_tag;
128 } include_server_config;
130 /* main parser states */
135 PARSE_DIRECTIVE_POSTNAME,
136 PARSE_DIRECTIVE_TAIL,
137 PARSE_DIRECTIVE_POSTTAIL,
152 typedef struct arg_item {
153 struct arg_item *next;
157 apr_size_t value_len;
164 ap_regmatch_t match[AP_MAX_REG_MATCH];
171 apr_size_t pattern_len;
174 struct ssi_internal_ctx {
178 char quote; /* quote character value (or \0) */
179 apr_size_t parse_pos; /* parse position of partial matches */
180 apr_size_t bytes_read;
182 apr_bucket_brigade *tmp_bb;
184 const char *start_seq;
185 bndm_t *start_seq_pat;
187 apr_size_t end_seq_len;
188 char *directive; /* name of the current directive */
189 apr_size_t directive_len; /* length of the current directive name */
191 arg_item_t *current_arg; /* currently parsed argument */
192 arg_item_t *argv; /* all arguments */
194 backref_t *re; /* NULL if there wasn't a regex yet */
196 const char *undefined_echo;
197 apr_size_t undefined_echo_len;
199 char legacy_expr; /* use ap_expr or legacy mod_include
200 expression parser? */
202 ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */
203 const char *expr_vary_this; /* for use by ap_expr_eval_ctx */
204 const char *expr_err; /* for use by ap_expr_eval_ctx */
208 apr_bucket_brigade *bb;
215 * +-------------------------------------------------------+
217 * | Debugging Utilities
219 * +-------------------------------------------------------+
224 #define TYPE_TOKEN(token, ttype) do { \
225 (token)->type = ttype; \
226 (token)->s = #ttype; \
229 #define CREATE_NODE(ctx, name) do { \
230 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
231 (name)->parent = (name)->left = (name)->right = NULL; \
233 (name)->dump_done = 0; \
236 static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
242 debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
245 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
246 debug__str, strlen(debug__str), ctx->pool,
247 ctx->intern->debug.f->c->bucket_alloc));
250 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
251 parse_node_t *d__c = node->child; \
253 if (!d__c->dump_done) { \
254 if (d__c->parent != node) { \
255 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
256 if (!d__c->parent) { \
257 debug_printf(ctx, "Parent of " #child " child node is " \
261 debug_printf(ctx, "Parent of " #child " child node " \
262 "points to another node (of type %s)!\n", \
263 d__c->parent->token.s); \
272 debug_printf(ctx, "%s(missing)\n", is); \
276 static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
278 parse_node_t *current;
282 debug_printf(ctx, " -- Parse Tree empty --\n\n");
286 debug_printf(ctx, " ----- Parse Tree -----\n");
291 switch (current->token.type) {
294 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
295 current->token.value);
296 current->dump_done = 1;
297 current = current->parent;
304 if (!current->dump_done) {
305 debug_printf(ctx, "%s%s\n", is, current->token.s);
306 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
307 current->dump_done = 1;
310 DUMP__CHILD(ctx, is, current, right)
312 if (!current->right || current->right->dump_done) {
313 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
314 if (current->right) current->right->dump_done = 0;
315 current = current->parent;
320 if (!current->dump_done) {
321 debug_printf(ctx, "%s%s\n", is, current->token.s);
322 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
323 current->dump_done = 1;
326 DUMP__CHILD(ctx, is, current, left)
327 DUMP__CHILD(ctx, is, current, right)
329 if ((!current->left || current->left->dump_done) &&
330 (!current->right || current->right->dump_done)) {
332 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
333 if (current->left) current->left->dump_done = 0;
334 if (current->right) current->right->dump_done = 0;
335 current = current->parent;
341 /* it is possible to call this function within the parser loop, to see
342 * how the tree is built. That way, we must cleanup after us to dump
343 * always the whole tree
346 if (root->left) root->left->dump_done = 0;
347 if (root->right) root->right->dump_done = 0;
349 debug_printf(ctx, " --- End Parse Tree ---\n\n");
354 #define DEBUG_INIT(ctx, filter, brigade) do { \
355 (ctx)->intern->debug.f = filter; \
356 (ctx)->intern->debug.bb = brigade; \
359 #define DEBUG_PRINTF(arg) debug_printf arg
361 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
362 token_t *d__t = (token); \
364 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
365 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
368 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
372 #define DEBUG_DUMP_EVAL(ctx, node) do { \
374 switch ((node)->token.type) { \
376 debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
377 (node)->token.value, ((node)->value) ? '1':'0'); \
381 debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
383 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
384 : "short circuited"), \
385 (((node)->right->done) ? ((node)->right->value?"1":"0") \
386 : "short circuited"), \
387 (node)->value ? '1' : '0'); \
395 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
396 debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
398 (node)->left->token.value, \
399 c, (node)->right->token.value, c, \
400 (node)->value ? '1' : '0'); \
403 debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
404 (node)->value ? '1' : '0'); \
409 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
411 DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
415 #define DEBUG_DUMP_COND(ctx, text) \
416 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
417 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
419 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
421 #else /* DEBUG_INCLUDE */
423 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
425 #define CREATE_NODE(ctx, name) do { \
426 (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
427 (name)->parent = (name)->left = (name)->right = NULL; \
431 #define DEBUG_INIT(ctx, f, bb)
432 #define DEBUG_PRINTF(arg)
433 #define DEBUG_DUMP_TOKEN(ctx, token)
434 #define DEBUG_DUMP_EVAL(ctx, node)
435 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
436 #define DEBUG_DUMP_COND(ctx, text)
437 #define DEBUG_DUMP_TREE(ctx, root)
439 #endif /* !DEBUG_INCLUDE */
443 * +-------------------------------------------------------+
445 * | Static Module Data
447 * +-------------------------------------------------------+
450 /* global module structure */
451 module AP_MODULE_DECLARE_DATA include_module;
453 /* function handlers for include directives */
454 static apr_hash_t *include_handlers;
456 /* forward declaration of handler registry */
457 static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
459 /* Sentinel value to store in subprocess_env for items that
460 * shouldn't be evaluated until/unless they're actually used
462 static const char lazy_eval_sentinel = '\0';
463 #define LAZY_VALUE (&lazy_eval_sentinel)
466 #define DEFAULT_START_SEQUENCE "<!--#"
467 #define DEFAULT_END_SEQUENCE "-->"
468 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
469 #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
470 #define DEFAULT_UNDEFINED_ECHO "(none)"
475 #define DEFAULT_XBITHACK XBITHACK_FULL
477 #define DEFAULT_XBITHACK XBITHACK_OFF
482 * +-------------------------------------------------------+
484 * | Environment/Expansion Functions
486 * +-------------------------------------------------------+
490 * decodes a string containing html entities or numeric character references.
491 * 's' is overwritten with the decoded string.
492 * If 's' is syntatically incorrect, then the followed fixups will be made:
493 * unknown entities will be left undecoded;
494 * references to unused numeric characters will be deleted.
495 * In particular, � will not be decoded, but will be deleted.
498 /* maximum length of any ISO-LATIN-1 HTML entity name. */
499 #define MAXENTLEN (6)
501 /* The following is a shrinking transformation, therefore safe. */
503 /* Note: this function is deprecated in favour of apr_unescape_entity() in APR */
504 static void decodehtml(char *s)
509 static const char * const entlist[MAXENTLEN + 1] =
513 "lt\074gt\076", /* 2 */
514 "amp\046ETH\320eth\360", /* 3 */
515 "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
516 "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
518 "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
519 "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
520 "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
522 "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
523 "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
524 "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
525 "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
526 "\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
527 "oslash\370ugrave\371uacute\372yacute\375" /* 6 */
530 /* Do a fast scan through the string until we find anything
531 * that needs more complicated handling
533 for (; *s != '&'; s++) {
539 for (p = s; *s != '\0'; s++, p++) {
544 /* find end of entity */
545 for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
549 if (s[i] == '\0') { /* treat as normal data */
554 /* is it numeric ? */
556 for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
557 val = val * 10 + s[j] - '0';
560 if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
561 (val >= 127 && val <= 160) || val >= 256) {
562 p--; /* no data to output */
565 *p = RAW_ASCII_CHAR(val);
570 if (j > MAXENTLEN || entlist[j] == NULL) {
573 continue; /* skip it */
575 for (ents = entlist[j]; *ents != '\0'; ents += i) {
576 if (strncmp(s + 1, ents, j) == 0) {
582 *p = '&'; /* unknown */
585 *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
594 static void add_include_vars(request_rec *r)
596 apr_table_t *e = r->subprocess_env;
599 apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
600 apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
601 apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
602 apr_table_setn(e, "DOCUMENT_URI", r->uri);
603 if (r->path_info && *r->path_info) {
604 apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
606 apr_table_setn(e, "USER_NAME", LAZY_VALUE);
607 if (r->filename && (t = strrchr(r->filename, '/'))) {
608 apr_table_setn(e, "DOCUMENT_NAME", ++t);
611 apr_table_setn(e, "DOCUMENT_NAME", r->uri);
614 char *arg_copy = apr_pstrdup(r->pool, r->args);
616 ap_unescape_url(arg_copy);
617 apr_table_setn(e, "QUERY_STRING_UNESCAPED",
618 ap_escape_shell_cmd(r->pool, arg_copy));
622 static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt)
625 if (!strcasecmp(var, "DATE_LOCAL")) {
626 val = ap_ht_time(r->pool, r->request_time, timefmt, 0);
628 else if (!strcasecmp(var, "DATE_GMT")) {
629 val = ap_ht_time(r->pool, r->request_time, timefmt, 1);
631 else if (!strcasecmp(var, "LAST_MODIFIED")) {
632 val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0);
634 else if (!strcasecmp(var, "USER_NAME")) {
635 if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
644 apr_table_setn(r->subprocess_env, var, val);
649 static const char *get_include_var(const char *var, include_ctx_t *ctx)
652 request_rec *r = ctx->r;
654 if (apr_isdigit(*var) && !var[1]) {
655 apr_size_t idx = *var - '0';
656 backref_t *re = ctx->intern->re;
658 /* Handle $0 .. $9 from the last regex evaluated.
659 * The choice of returning NULL strings on not-found,
660 * v.s. empty strings on an empty match is deliberate.
662 if (!re || !re->have_match) {
663 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329)
664 "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
668 else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
669 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330)
670 "regex capture $%" APR_SIZE_T_FMT
671 " is out of range (last regex was: '%s') in %s",
672 idx, re->rexp, r->filename);
675 else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
676 /* This particular subpattern was not used by the regex */
680 val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
681 re->match[idx].rm_eo - re->match[idx].rm_so);
685 val = apr_table_get(r->subprocess_env, var);
687 if (val == LAZY_VALUE) {
688 val = add_include_vars_lazy(r, var, ctx->time_str);
695 static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx,
699 const char *res, *name = data;
700 include_ctx_t *ctx = eval_ctx->data;
701 if (name[0] == 'e') {
702 /* keep legacy "env" semantics */
703 if ((res = apr_table_get(ctx->r->notes, arg)) != NULL)
705 else if ((res = get_include_var(arg, ctx)) != NULL)
711 return get_include_var(arg, ctx);
715 static int include_expr_lookup(ap_expr_lookup_parms *parms)
717 switch (parms->type) {
718 case AP_EXPR_FUNC_STRING:
719 if (strcasecmp(parms->name, "v") == 0 ||
720 strcasecmp(parms->name, "reqenv") == 0 ||
721 strcasecmp(parms->name, "env") == 0) {
722 *parms->func = include_expr_var_fn;
723 *parms->data = parms->name;
728 * We could also make the SSI vars available as %{...} style variables
729 * (AP_EXPR_FUNC_VAR), but this would create problems if we ever want
730 * to cache parsed expressions for performance reasons.
733 return ap_run_expr_lookup(parms);
738 * Do variable substitution on strings
740 * (Note: If out==NULL, this function allocs a buffer for the resulting
741 * string from ctx->pool. The return value is always the parsed string)
743 static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
744 apr_size_t length, int leave_name)
746 request_rec *r = ctx->r;
747 result_item_t *result = NULL, *current = NULL;
748 apr_size_t outlen = 0, inlen, span;
749 char *ret = NULL, *eout = NULL;
753 /* sanity check, out && !length is not supported */
754 ap_assert(out && length);
757 eout = out + length - 1;
760 span = strcspn(in, "\\$");
766 apr_cpystrn(out, in, length);
769 ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
770 ? length - 1 : inlen);
776 /* well, actually something to do */
781 memcpy(out, in, (out+span <= eout) ? span : (eout-out));
786 current = result = apr_palloc(ctx->dpool, sizeof(*result));
787 current->next = NULL;
788 current->string = in;
793 /* loop for specials */
795 if ((out && out >= eout) || (length && outlen >= length)) {
799 /* prepare next entry */
800 if (!out && current->len) {
801 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
802 current = current->next;
803 current->next = NULL;
812 *out++ = (p[1] == '$') ? *++p : *p;
817 current->string = (p[1] == '$') ? ++p : p;
826 else { /* *p == '$' */
827 const char *newp = NULL, *ep, *key = NULL;
830 ep = ap_strchr_c(++p, '}');
832 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on "
833 "variable \"%s\" in %s", p, r->filename);
838 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
845 while (*ep == '_' || apr_isalnum(*ep)) {
850 key = apr_pstrmemdup(ctx->dpool, p, ep - p);
856 /* empty name results in a copy of '$' in the output string */
863 current->string = p++;
868 const char *val = get_include_var(key, ctx);
874 else if (leave_name) {
881 memcpy(out, val, (out+len <= eout) ? len : (eout-out));
886 current->string = val;
895 if ((out && out >= eout) || (length && outlen >= length)) {
899 /* check the remainder */
900 if (*p && (span = strcspn(p, "\\$")) > 0) {
901 if (!out && current->len) {
902 current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
903 current = current->next;
904 current->next = NULL;
908 memcpy(out, p, (out+span <= eout) ? span : (eout-out));
919 } while (p < in+inlen);
921 /* assemble result */
933 if (length && outlen > length) {
937 ret = out = apr_palloc(ctx->pool, outlen + 1);
942 memcpy(out, result->string, (out+result->len <= ep)
943 ? result->len : (ep-out));
946 result = result->next;
947 } while (result && out < ep);
957 * +-------------------------------------------------------+
959 * | Conditional Expression Parser
961 * +-------------------------------------------------------+
964 static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
967 ap_regex_t *compiled;
968 backref_t *re = ctx->intern->re;
970 compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED);
972 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02667)
973 "unable to compile pattern \"%s\"", rexp);
978 re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
981 re->source = apr_pstrdup(ctx->pool, string);
982 re->rexp = apr_pstrdup(ctx->pool, rexp);
983 re->nsub = compiled->re_nsub;
984 re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH,
987 ap_pregfree(ctx->dpool, compiled);
988 return re->have_match;
991 static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous)
1003 /* Skip leading white space */
1004 while (apr_isspace(**parse)) {
1013 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
1017 switch (*(*parse)++) {
1019 TYPE_TOKEN(token, TOKEN_LBRACE);
1022 TYPE_TOKEN(token, TOKEN_RBRACE);
1025 if (**parse == '=') ++*parse;
1026 TYPE_TOKEN(token, TOKEN_EQ);
1029 if (**parse == '=') {
1030 TYPE_TOKEN(token, TOKEN_NE);
1034 TYPE_TOKEN(token, TOKEN_NOT);
1040 /* if last token was ACCESS, this token is STRING */
1041 if (previous != NULL && TOKEN_ACCESS == previous->type) {
1044 TYPE_TOKEN(token, TOKEN_RE);
1048 if (**parse == '|') {
1049 TYPE_TOKEN(token, TOKEN_OR);
1055 if (**parse == '&') {
1056 TYPE_TOKEN(token, TOKEN_AND);
1062 if (**parse == '=') {
1063 TYPE_TOKEN(token, TOKEN_GE);
1067 TYPE_TOKEN(token, TOKEN_GT);
1070 if (**parse == '=') {
1071 TYPE_TOKEN(token, TOKEN_LE);
1075 TYPE_TOKEN(token, TOKEN_LT);
1078 if (**parse == 'A') {
1079 TYPE_TOKEN(token, TOKEN_ACCESS);
1086 /* It's a string or regex token
1087 * Now search for the next token, which finishes this string
1090 p = *parse = token->value = unmatched ? *parse : p;
1092 for (; **parse; p = ++*parse) {
1093 if (**parse == '\\') {
1103 if (**parse == unmatched) {
1108 } else if (apr_isspace(**parse)) {
1126 if ((*parse)[1] == **parse) {
1140 token->value = apr_pstrdup(ctx->dpool, "");
1143 apr_size_t len = p - token->value - shift;
1144 char *c = apr_palloc(ctx->dpool, len + 1);
1150 const char *e = ap_strchr_c(p, '\\');
1168 static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1170 parse_node_t *new, *root = NULL, *current = NULL;
1171 request_rec *r = ctx->r;
1172 request_rec *rr = NULL;
1173 const char *error = "Invalid expression \"%s\" in file %s";
1174 const char *parse = expr;
1183 /* Create Parse Tree */
1185 /* uncomment this to see how the tree a built:
1187 * DEBUG_DUMP_TREE(ctx, root);
1189 CREATE_NODE(ctx, new);
1192 #ifdef DEBUG_INCLUDE
1195 get_ptoken(ctx, &parse, &new->token,
1196 (current != NULL ? ¤t->token : NULL));
1200 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
1201 DEBUG_DUMP_TOKEN(ctx, &new->token);
1205 switch (new->token.type) {
1210 root = current = new;
1214 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
1221 switch (new->token.type) {
1223 switch (current->token.type) {
1225 current->token.value =
1226 apr_pstrcat(ctx->dpool, current->token.value,
1227 *current->token.value ? " " : "",
1228 new->token.value, NULL);
1237 new->parent = current;
1238 current = current->right = new;
1244 switch (current->token.type) {
1247 new->parent = current;
1248 current = current->right = new;
1259 switch (current->token.type) {
1263 current = current->parent;
1266 switch (current->token.type) {
1273 current = current->parent;
1282 current = root = new;
1286 new->left = current->right;
1287 new->left->parent = new;
1288 new->parent = current;
1289 current = current->right = new;
1303 if (current->token.type == TOKEN_STRING) {
1304 current = current->parent;
1309 current = root = new;
1313 switch (current->token.type) {
1317 new->left = current->right;
1318 new->left->parent = new;
1319 new->parent = current;
1320 current = current->right = new;
1330 while (current && current->token.type != TOKEN_LBRACE) {
1331 current = current->parent;
1335 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
1339 error = "Unmatched ')' in \"%s\" in file %s";
1345 switch (current->token.type) {
1353 current->right = new;
1354 new->parent = current;
1364 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
1369 DEBUG_DUMP_TREE(ctx, root);
1371 /* Evaluate Parse Tree */
1375 switch (current->token.type) {
1377 current->token.value =
1378 ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
1379 SSI_EXPAND_DROP_NAME);
1380 current->value = !!*current->token.value;
1385 if (!current->left || !current->right) {
1386 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332)
1387 "Invalid expression \"%s\" in file %s",
1393 if (!current->left->done) {
1394 switch (current->left->token.type) {
1396 current->left->token.value =
1397 ap_ssi_parse_string(ctx, current->left->token.value,
1398 NULL, 0, SSI_EXPAND_DROP_NAME);
1399 current->left->value = !!*current->left->token.value;
1400 DEBUG_DUMP_EVAL(ctx, current->left);
1401 current->left->done = 1;
1405 current = current->left;
1410 /* short circuit evaluation */
1411 if (!current->right->done && !regex &&
1412 ((current->token.type == TOKEN_AND && !current->left->value) ||
1413 (current->token.type == TOKEN_OR && current->left->value))) {
1414 current->value = current->left->value;
1417 if (!current->right->done) {
1418 switch (current->right->token.type) {
1420 current->right->token.value =
1421 ap_ssi_parse_string(ctx,current->right->token.value,
1422 NULL, 0, SSI_EXPAND_DROP_NAME);
1423 current->right->value = !!*current->right->token.value;
1424 DEBUG_DUMP_EVAL(ctx, current->right);
1425 current->right->done = 1;
1429 current = current->right;
1434 if (current->token.type == TOKEN_AND) {
1435 current->value = current->left->value &&
1436 current->right->value;
1439 current->value = current->left->value ||
1440 current->right->value;
1447 if (!current->left || !current->right ||
1448 current->left->token.type != TOKEN_STRING ||
1449 (current->right->token.type != TOKEN_STRING &&
1450 current->right->token.type != TOKEN_RE)) {
1451 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333)
1452 "Invalid expression \"%s\" in file %s",
1457 current->left->token.value =
1458 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1459 SSI_EXPAND_DROP_NAME);
1460 current->right->token.value =
1461 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1462 SSI_EXPAND_DROP_NAME);
1464 if (current->right->token.type == TOKEN_RE) {
1465 current->value = re_check(ctx, current->left->token.value,
1466 current->right->token.value);
1470 current->value = !strcmp(current->left->token.value,
1471 current->right->token.value);
1474 if (current->token.type == TOKEN_NE) {
1475 current->value = !current->value;
1483 if (!current->left || !current->right ||
1484 current->left->token.type != TOKEN_STRING ||
1485 current->right->token.type != TOKEN_STRING) {
1486 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334)
1487 "Invalid expression \"%s\" in file %s",
1493 current->left->token.value =
1494 ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
1495 SSI_EXPAND_DROP_NAME);
1496 current->right->token.value =
1497 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1498 SSI_EXPAND_DROP_NAME);
1500 current->value = strcmp(current->left->token.value,
1501 current->right->token.value);
1503 switch (current->token.type) {
1504 case TOKEN_GE: current->value = current->value >= 0; break;
1505 case TOKEN_GT: current->value = current->value > 0; break;
1506 case TOKEN_LE: current->value = current->value <= 0; break;
1507 case TOKEN_LT: current->value = current->value < 0; break;
1508 default: current->value = 0; break; /* should not happen */
1514 if (current->right) {
1515 if (!current->right->done) {
1516 current = current->right;
1519 current->value = current->right->value;
1525 if (current->token.type == TOKEN_NOT) {
1526 current->value = !current->value;
1531 if (current->left || !current->right ||
1532 (current->right->token.type != TOKEN_STRING &&
1533 current->right->token.type != TOKEN_RE)) {
1534 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335)
1535 "Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.",
1540 current->right->token.value =
1541 ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
1542 SSI_EXPAND_DROP_NAME);
1543 rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
1544 /* 400 and higher are considered access denied */
1545 if (rr->status < HTTP_BAD_REQUEST) {
1550 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336)
1551 "mod_include: The tested "
1552 "subrequest -A \"%s\" returned an error code.",
1553 current->right->token.value);
1555 ap_destroy_sub_req(rr);
1560 error = "No operator before regex in expr \"%s\" in file %s";
1564 error = "Unmatched '(' in \"%s\" in file %s";
1568 error = "internal parser error in \"%s\" in file %s";
1571 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,r->filename);
1576 DEBUG_DUMP_EVAL(ctx, current);
1578 current = current->parent;
1581 return (root ? root->value : 0);
1584 /* same as above, but use common ap_expr syntax / API */
1585 static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error)
1587 ap_expr_info_t expr_info;
1590 backref_t *re = ctx->intern->re;
1591 ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx;
1593 expr_info.filename = ctx->r->filename;
1594 expr_info.line_number = 0;
1595 expr_info.module_index = APLOG_MODULE_INDEX;
1596 expr_info.flags = AP_EXPR_FLAG_RESTRICTED;
1597 err = ap_expr_parse(ctx->r->pool, ctx->r->pool, &expr_info, expr,
1598 include_expr_lookup);
1600 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337)
1601 "Could not parse expr \"%s\" in %s: %s", expr,
1602 ctx->r->filename, err);
1608 ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re));
1611 /* ap_expr_exec_ctx() does not care about re->have_match but only about
1614 if (!re->have_match)
1619 eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx));
1620 ctx->intern->expr_eval_ctx = eval_ctx;
1621 eval_ctx->r = ctx->r;
1622 eval_ctx->c = ctx->r->connection;
1623 eval_ctx->s = ctx->r->server;
1624 eval_ctx->p = ctx->r->pool;
1625 eval_ctx->data = ctx;
1626 eval_ctx->err = &ctx->intern->expr_err;
1627 eval_ctx->vary_this = &ctx->intern->expr_vary_this;
1628 eval_ctx->re_nmatch = AP_MAX_REG_MATCH;
1629 eval_ctx->re_pmatch = re->match;
1630 eval_ctx->re_source = &re->source;
1633 eval_ctx->info = &expr_info;
1634 ret = ap_expr_exec_ctx(eval_ctx);
1636 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338)
1637 "Could not evaluate expr \"%s\" in %s: %s", expr,
1638 ctx->r->filename, ctx->intern->expr_err);
1649 * +-------------------------------------------------------+
1653 * +-------------------------------------------------------+
1657 * Extract the next tag name and value.
1658 * If there are no more tags, set the tag name to NULL.
1659 * The tag value is html decoded if dodecode is non-zero.
1660 * The tag value may be NULL if there is no tag value..
1662 static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
1663 char **tag_val, int dodecode)
1665 if (!ctx->intern->argv) {
1672 *tag_val = ctx->intern->argv->value;
1673 *tag = ctx->intern->argv->name;
1675 ctx->intern->argv = ctx->intern->argv->next;
1677 if (dodecode && *tag_val) {
1678 decodehtml(*tag_val);
1684 static int find_file(request_rec *r, const char *directive, const char *tag,
1685 char *tag_val, apr_finfo_t *finfo)
1687 char *to_send = tag_val;
1688 request_rec *rr = NULL;
1690 char *error_fmt = NULL;
1691 apr_status_t rv = APR_SUCCESS;
1693 if (!strcmp(tag, "file")) {
1696 /* be safe; only files in this directory or below allowed */
1697 rv = apr_filepath_merge(&newpath, NULL, tag_val,
1698 APR_FILEPATH_SECUREROOTTEST |
1699 APR_FILEPATH_NOTABSOLUTE, r->pool);
1701 if (rv != APR_SUCCESS) {
1702 error_fmt = APLOGNO(02668) "unable to access file \"%s\" "
1703 "in parsed file %s";
1706 /* note: it is okay to pass NULL for the "next filter" since
1707 we never attempt to "run" this sub request. */
1708 rr = ap_sub_req_lookup_file(newpath, r, NULL);
1710 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
1711 to_send = rr->filename;
1712 if ((rv = apr_stat(finfo, to_send,
1713 APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
1714 && rv != APR_INCOMPLETE) {
1715 error_fmt = APLOGNO(02669) "unable to get information "
1716 "about \"%s\" in parsed file %s";
1720 error_fmt = APLOGNO(02670) "unable to lookup information "
1721 "about \"%s\" in parsed file %s";
1727 ap_log_rerror(APLOG_MARK, APLOG_ERR,
1728 rv, r, error_fmt, to_send, r->filename);
1731 if (rr) ap_destroy_sub_req(rr);
1735 else if (!strcmp(tag, "virtual")) {
1736 /* note: it is okay to pass NULL for the "next filter" since
1737 we never attempt to "run" this sub request. */
1738 rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
1740 if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
1741 memcpy((char *) finfo, (const char *) &rr->finfo,
1743 ap_destroy_sub_req(rr);
1747 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get "
1748 "information about \"%s\" in parsed file %s",
1749 tag_val, r->filename);
1750 ap_destroy_sub_req(rr);
1755 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" "
1756 "to tag %s in %s", tag, directive, r->filename);
1762 * <!--#include virtual|file="..." [onerror|virtual|file="..."] ... -->
1764 * Output each file/virtual in turn until one of them returns an error.
1765 * On error, ignore all further file/virtual attributes until we reach
1766 * an onerror attribute, where we make an attempt to serve the onerror
1767 * virtual url. If onerror fails, or no onerror is present, the default
1768 * error string is inserted into the stream.
1770 static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
1771 apr_bucket_brigade *bb)
1773 request_rec *r = f->r;
1777 ap_log_rerror(APLOG_MARK,
1778 (ctx->flags & SSI_FLAG_PRINTING)
1779 ? APLOG_ERR : APLOG_WARNING,
1780 0, r, APLOGNO(01341)
1781 "missing argument for include element in %s",
1785 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1790 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1797 char *tag_val = NULL;
1798 request_rec *rr = NULL;
1799 char *error_fmt = NULL;
1800 char *parsed_string;
1801 apr_status_t rv = APR_SUCCESS;
1804 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1805 if (!tag || !tag_val) {
1809 if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag,
1811 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter "
1812 "\"%s\" to tag include in %s", tag, r->filename);
1813 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1817 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
1818 SSI_EXPAND_DROP_NAME);
1819 if (tag[0] == 'f') {
1822 /* be safe; only files in this directory or below allowed */
1823 rv = apr_filepath_merge(&newpath, NULL, parsed_string,
1824 APR_FILEPATH_SECUREROOTTEST |
1825 APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
1827 if (rv != APR_SUCCESS) {
1828 error_fmt = "unable to include file \"%s\" in parsed file %s";
1831 rr = ap_sub_req_lookup_file(newpath, r, f->next);
1834 else if ((tag[0] == 'v' && !last_error)
1835 || (tag[0] == 'o' && last_error)) {
1837 rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
1840 rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
1847 if (!error_fmt && rr->status != HTTP_OK) {
1848 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d";
1851 if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
1852 rr->content_type && strncmp(rr->content_type, "text/", 5)) {
1854 error_fmt = "unable to include potential exec \"%s\" in parsed "
1855 "file %s, content type not text/*";
1858 /* See the Kludge in includes_filter for why.
1859 * Basically, it puts a bread crumb in here, then looks
1860 * for the crumb later to see if its been here.
1863 ap_set_module_config(rr->request_config, &include_module, r);
1866 if (!error_fmt && ((status = ap_run_sub_req(rr)))) {
1867 error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d";
1871 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val,
1872 r->filename, status ? status : rr ? rr->status : 0);
1874 /* onerror threw an error, give up completely */
1877 last_error = error_fmt;
1883 /* Do *not* destroy the subrequest here; it may have allocated
1884 * variables in this r->subprocess_env in the subrequest's
1885 * r->pool, so that pool must survive as long as this request.
1886 * Yes, this is a memory leak. */
1891 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1898 * <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."]
1899 * [encoding="..."] var="..." ... -->
1901 static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
1902 apr_bucket_brigade *bb)
1904 const char *encoding = "entity", *decoding = "none";
1905 request_rec *r = f->r;
1909 ap_log_rerror(APLOG_MARK,
1910 (ctx->flags & SSI_FLAG_PRINTING)
1911 ? APLOG_ERR : APLOG_WARNING,
1912 0, r, APLOGNO(01343)
1913 "missing argument for echo element in %s",
1917 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
1922 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1928 char *tag_val = NULL;
1930 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
1931 if (!tag || !tag_val) {
1935 if (!strcmp(tag, "var")) {
1937 const char *echo_text = NULL;
1940 val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
1941 0, SSI_EXPAND_DROP_NAME),
1946 char *e, *d, *token;
1950 d = apr_pstrdup(ctx->pool, decoding);
1951 token = apr_strtok(d, ", \t", &last);
1954 if (!strcasecmp(token, "none")) {
1957 else if (!strcasecmp(token, "url")) {
1958 char *buf = apr_pstrdup(ctx->pool, echo_text);
1959 ap_unescape_url(buf);
1962 else if (!strcasecmp(token, "urlencoded")) {
1963 char *buf = apr_pstrdup(ctx->pool, echo_text);
1964 ap_unescape_urlencoded(buf);
1967 else if (!strcasecmp(token, "entity")) {
1968 char *buf = apr_pstrdup(ctx->pool, echo_text);
1972 else if (!strcasecmp(token, "base64")) {
1973 echo_text = ap_pbase64decode(ctx->dpool, echo_text);
1976 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value "
1977 "\"%s\" to parameter \"decoding\" of tag echo in "
1978 "%s", token, r->filename);
1979 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
1983 token = apr_strtok(NULL, ", \t", &last);
1986 e = apr_pstrdup(ctx->pool, encoding);
1987 token = apr_strtok(e, ", \t", &last);
1990 if (!strcasecmp(token, "none")) {
1993 else if (!strcasecmp(token, "url")) {
1994 echo_text = ap_escape_uri(ctx->dpool, echo_text);
1996 else if (!strcasecmp(token, "urlencoded")) {
1997 echo_text = ap_escape_urlencoded(ctx->dpool, echo_text);
1999 else if (!strcasecmp(token, "entity")) {
2000 echo_text = ap_escape_html2(ctx->dpool, echo_text, 0);
2002 else if (!strcasecmp(token, "base64")) {
2004 buf = ap_pbase64encode(ctx->dpool, (char *)echo_text);
2008 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value "
2009 "\"%s\" to parameter \"encoding\" of tag echo in "
2010 "%s", token, r->filename);
2011 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2015 token = apr_strtok(NULL, ", \t", &last);
2018 e_len = strlen(echo_text);
2021 echo_text = ctx->intern->undefined_echo;
2022 e_len = ctx->intern->undefined_echo_len;
2029 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
2030 apr_pmemdup(ctx->pool, echo_text, e_len),
2031 e_len, ctx->pool, f->c->bucket_alloc));
2033 else if (!strcmp(tag, "decoding")) {
2036 else if (!strcmp(tag, "encoding")) {
2040 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter "
2041 "\"%s\" in tag echo of %s", tag, r->filename);
2042 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2051 * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
2052 * [echomsg="..."] -->
2054 static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
2055 apr_bucket_brigade *bb)
2057 request_rec *r = f->r;
2058 apr_table_t *env = r->subprocess_env;
2061 ap_log_rerror(APLOG_MARK,
2062 (ctx->flags & SSI_FLAG_PRINTING)
2063 ? APLOG_ERR : APLOG_WARNING,
2064 0, r, APLOGNO(01347)
2065 "missing argument for config element in %s",
2069 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2074 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2080 char *tag_val = NULL;
2082 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
2083 if (!tag || !tag_val) {
2087 if (!strcmp(tag, "errmsg")) {
2088 ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2089 SSI_EXPAND_DROP_NAME);
2091 else if (!strcmp(tag, "echomsg")) {
2092 ctx->intern->undefined_echo =
2093 ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
2094 ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
2096 else if (!strcmp(tag, "timefmt")) {
2097 apr_time_t date = r->request_time;
2099 ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2100 SSI_EXPAND_DROP_NAME);
2102 apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
2104 apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
2106 apr_table_setn(env, "LAST_MODIFIED",
2107 ap_ht_time(r->pool, r->finfo.mtime,
2110 else if (!strcmp(tag, "sizefmt")) {
2111 char *parsed_string;
2113 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2114 SSI_EXPAND_DROP_NAME);
2115 if (!strcmp(parsed_string, "bytes")) {
2116 ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
2118 else if (!strcmp(parsed_string, "abbrev")) {
2119 ctx->flags &= SSI_FLAG_SIZE_ABBREV;
2122 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value "
2123 "\"%s\" to parameter \"sizefmt\" of tag config "
2124 "in %s", parsed_string, r->filename);
2125 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2130 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter "
2131 "\"%s\" to tag config in %s", tag, r->filename);
2132 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2141 * <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
2143 static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
2144 apr_bucket_brigade *bb)
2146 request_rec *r = f->r;
2149 ap_log_rerror(APLOG_MARK,
2150 (ctx->flags & SSI_FLAG_PRINTING)
2151 ? APLOG_ERR : APLOG_WARNING,
2152 0, r, APLOGNO(01350)
2153 "missing argument for fsize element in %s",
2157 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2162 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2168 char *tag_val = NULL;
2170 char *parsed_string;
2172 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2173 if (!tag || !tag_val) {
2177 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2178 SSI_EXPAND_DROP_NAME);
2180 if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
2184 if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
2185 buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
2186 len = 4; /* omit the \0 terminator */
2189 apr_size_t l, x, pos;
2192 tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
2193 len = l = strlen(tmp);
2195 for (x = 0; x < l; ++x) {
2196 if (x && !((l - x) % 3)) {
2202 buf = apr_pstrmemdup(ctx->pool, tmp, len);
2205 buf = apr_palloc(ctx->pool, len);
2207 for (pos = x = 0; x < l; ++x) {
2208 if (x && !((l - x) % 3)) {
2211 buf[pos++] = tmp[x];
2216 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
2217 ctx->pool, f->c->bucket_alloc));
2220 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2229 * <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
2231 static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
2232 apr_bucket_brigade *bb)
2234 request_rec *r = f->r;
2237 ap_log_rerror(APLOG_MARK,
2238 (ctx->flags & SSI_FLAG_PRINTING)
2239 ? APLOG_ERR : APLOG_WARNING,
2240 0, r, APLOGNO(01351)
2241 "missing argument for flastmod element in %s",
2245 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2250 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2256 char *tag_val = NULL;
2258 char *parsed_string;
2260 ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
2261 if (!tag || !tag_val) {
2265 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2266 SSI_EXPAND_DROP_NAME);
2268 if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
2272 t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
2273 t_len = strlen(t_val);
2275 APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
2276 ctx->pool, f->c->bucket_alloc));
2279 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2288 * <!--#if expr="..." -->
2290 static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
2291 apr_bucket_brigade *bb)
2295 request_rec *r = f->r;
2296 int expr_ret, was_error;
2298 if (ctx->argc != 1) {
2299 ap_log_rerror(APLOG_MARK,
2300 (ctx->flags & SSI_FLAG_PRINTING)
2301 ? APLOG_ERR : APLOG_WARNING,
2304 ? APLOGNO(01352) "too many arguments for if element in %s"
2305 : APLOGNO(01353) "missing expr argument for if element in %s",
2309 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2310 ++(ctx->if_nesting_level);
2314 if (ctx->argc != 1) {
2315 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2319 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2321 if (strcmp(tag, "expr")) {
2322 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" "
2323 "to tag if in %s", tag, r->filename);
2324 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2329 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if "
2330 "element in %s", r->filename);
2331 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2335 DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
2337 if (ctx->intern->legacy_expr)
2338 expr_ret = parse_expr(ctx, expr, &was_error);
2340 expr_ret = parse_ap_expr(ctx, expr, &was_error);
2343 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2348 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2351 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2354 DEBUG_DUMP_COND(ctx, " if");
2356 ctx->if_nesting_level = 0;
2362 * <!--#elif expr="..." -->
2364 static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
2365 apr_bucket_brigade *bb)
2369 request_rec *r = f->r;
2370 int expr_ret, was_error;
2372 if (ctx->argc != 1) {
2373 ap_log_rerror(APLOG_MARK,
2374 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2377 ? APLOGNO(01356) "too many arguments for if element in %s"
2378 : APLOGNO(01357) "missing expr argument for if element in %s",
2382 if (ctx->if_nesting_level) {
2386 if (ctx->argc != 1) {
2387 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2391 ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
2393 if (strcmp(tag, "expr")) {
2394 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" "
2395 "to tag if in %s", tag, r->filename);
2396 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2401 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif "
2402 "statement: %s", r->filename);
2403 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2407 DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
2408 DEBUG_DUMP_COND(ctx, " elif");
2410 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2411 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2415 if (ctx->intern->legacy_expr)
2416 expr_ret = parse_expr(ctx, expr, &was_error);
2418 expr_ret = parse_ap_expr(ctx, expr, &was_error);
2421 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2426 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2429 ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
2432 DEBUG_DUMP_COND(ctx, " elif");
2440 static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
2441 apr_bucket_brigade *bb)
2443 request_rec *r = f->r;
2446 ap_log_rerror(APLOG_MARK,
2447 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2448 0, r, APLOGNO(01360)
2449 "else directive does not take tags in %s",
2453 if (ctx->if_nesting_level) {
2458 if (ctx->flags & SSI_FLAG_PRINTING) {
2459 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2465 DEBUG_DUMP_COND(ctx, " else");
2467 if (ctx->flags & SSI_FLAG_COND_TRUE) {
2468 ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
2471 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2480 static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
2481 apr_bucket_brigade *bb)
2483 request_rec *r = f->r;
2486 ap_log_rerror(APLOG_MARK,
2487 (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
2488 0, r, APLOGNO(01361)
2489 "endif directive does not take tags in %s",
2493 if (ctx->if_nesting_level) {
2494 --(ctx->if_nesting_level);
2499 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2503 DEBUG_DUMP_COND(ctx, "endif");
2505 ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
2511 * <!--#set var="..." value="..." ... -->
2513 static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
2514 apr_bucket_brigade *bb)
2516 const char *encoding = "none", *decoding = "none";
2518 request_rec *r = f->r;
2519 request_rec *sub = r->main;
2520 apr_pool_t *p = r->pool;
2523 if (ctx->argc < 2) {
2524 ap_log_rerror(APLOG_MARK,
2525 (ctx->flags & SSI_FLAG_PRINTING)
2526 ? APLOG_ERR : APLOG_WARNING,
2528 APLOGNO(01362) "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_RAW);
2555 if (!tag || !tag_val) {
2559 if (!strcmp(tag, "var")) {
2560 decodehtml(tag_val);
2561 var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2562 SSI_EXPAND_DROP_NAME);
2564 else if (!strcmp(tag, "decoding")) {
2567 else if (!strcmp(tag, "encoding")) {
2570 else if (!strcmp(tag, "value")) {
2571 char *parsed_string;
2574 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must "
2575 "precede value in set directive in %s",
2577 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2581 parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
2582 SSI_EXPAND_DROP_NAME);
2584 if (parsed_string) {
2586 char *e, *d, *token;
2588 d = apr_pstrdup(ctx->pool, decoding);
2589 token = apr_strtok(d, ", \t", &last);
2592 if (!strcasecmp(token, "none")) {
2595 else if (!strcasecmp(token, "url")) {
2596 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2597 ap_unescape_url(buf);
2598 parsed_string = buf;
2600 else if (!strcasecmp(token, "urlencoded")) {
2601 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2602 ap_unescape_urlencoded(buf);
2603 parsed_string = buf;
2605 else if (!strcasecmp(token, "entity")) {
2606 char *buf = apr_pstrdup(ctx->pool, parsed_string);
2608 parsed_string = buf;
2610 else if (!strcasecmp(token, "base64")) {
2611 parsed_string = ap_pbase64decode(ctx->dpool, parsed_string);
2614 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value "
2615 "\"%s\" to parameter \"decoding\" of tag set in "
2616 "%s", token, r->filename);
2617 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2621 token = apr_strtok(NULL, ", \t", &last);
2624 e = apr_pstrdup(ctx->pool, encoding);
2625 token = apr_strtok(e, ", \t", &last);
2628 if (!strcasecmp(token, "none")) {
2631 else if (!strcasecmp(token, "url")) {
2632 parsed_string = ap_escape_uri(ctx->dpool, parsed_string);
2634 else if (!strcasecmp(token, "urlencoded")) {
2635 parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string);
2637 else if (!strcasecmp(token, "entity")) {
2638 parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0);
2640 else if (!strcasecmp(token, "base64")) {
2642 buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string);
2643 parsed_string = buf;
2646 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value "
2647 "\"%s\" to parameter \"encoding\" of tag set in "
2648 "%s", token, r->filename);
2649 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2653 token = apr_strtok(NULL, ", \t", &last);
2662 apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
2663 apr_pstrdup(p, parsed_string));
2666 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set "
2667 "directive in %s", r->filename);
2668 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2679 static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
2680 apr_bucket_brigade *bb)
2682 request_rec *r = f->r;
2683 const apr_array_header_t *arr;
2684 const apr_table_entry_t *elts;
2688 ap_log_rerror(APLOG_MARK,
2689 (ctx->flags & SSI_FLAG_PRINTING)
2690 ? APLOG_ERR : APLOG_WARNING,
2692 APLOGNO(01367) "printenv directive does not take tags in %s",
2696 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
2701 SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
2705 arr = apr_table_elts(r->subprocess_env);
2706 elts = (apr_table_entry_t *)arr->elts;
2708 for (i = 0; i < arr->nelts; ++i) {
2709 const char *key_text, *val_text;
2712 key_text = ap_escape_html(ctx->dpool, elts[i].key);
2715 val_text = elts[i].val;
2716 if (val_text == LAZY_VALUE)
2717 val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
2718 val_text = ap_escape_html(ctx->dpool, val_text);
2720 apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n",
2730 * +-------------------------------------------------------+
2732 * | Main Includes-Filter Engine
2734 * +-------------------------------------------------------+
2737 /* This is an implementation of the BNDM search algorithm.
2739 * Fast and Flexible String Matching by Combining Bit-parallelism and
2740 * Suffix Automata (2001)
2741 * Gonzalo Navarro, Mathieu Raffinot
2743 * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
2745 * Initial code submitted by Sascha Schumann.
2748 /* Precompile the bndm_t data structure. */
2749 static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
2752 const char *ne = n + nl;
2753 bndm_t *t = apr_palloc(pool, sizeof(*t));
2755 memset(t->T, 0, sizeof(unsigned int) * 256);
2756 t->pattern_len = nl;
2758 for (x = 1; n < ne; x <<= 1) {
2759 t->T[(unsigned char) *n++] |= x;
2767 /* Implements the BNDM search algorithm (as described above).
2769 * h - the string to look in
2770 * hl - length of the string to look for
2771 * t - precompiled bndm structure against the pattern
2773 * Returns the count of character that is the first match or hl if no
2776 static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
2779 const char *he, *p, *pi;
2780 unsigned int *T, x, d;
2787 nl = t->pattern_len;
2789 pi = h - 1; /* pi: p initial */
2790 p = pi + nl; /* compare window right to left. point to the first char */
2796 d &= T[(unsigned char) *p--];
2819 * returns the index position of the first byte of start_seq (or the len of
2820 * the buffer as non-match)
2822 static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
2825 struct ssi_internal_ctx *intern = ctx->intern;
2826 apr_size_t slen = intern->start_seq_pat->pattern_len;
2831 p = data; /* try partial match at the end of the buffer (below) */
2834 /* try fast bndm search over the buffer
2835 * (hopefully the whole start sequence can be found in this buffer)
2837 index = bndm(intern->start_seq_pat, data, len);
2839 /* wow, found it. ready. */
2841 intern->state = PARSE_DIRECTIVE;
2845 /* ok, the pattern can't be found as whole in the buffer,
2846 * check the end for a partial match
2848 p = data + len - slen + 1;
2854 while (p < ep && *p != *intern->start_seq) {
2860 /* found a possible start_seq start */
2865 while (p < ep && *p == intern->start_seq[pos]) {
2870 /* partial match found. Store the info for the next round */
2872 intern->state = PARSE_HEAD;
2873 intern->parse_pos = pos;
2878 /* we must try all combinations; consider (e.g.) SSIStartTag "--->"
2879 * and a string data of "--.-" and the end of the buffer
2881 p = data + index + 1;
2889 * returns the first byte *after* the partial (or final) match.
2891 * If we had to trick with the start_seq start, 'release' returns the
2892 * number of chars of the start_seq which appeared not to be part of a
2893 * full tag and may have to be passed down the filter chain.
2895 static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
2898 apr_size_t *release)
2900 struct ssi_internal_ctx *intern = ctx->intern;
2901 apr_size_t pos, spos = 0;
2902 apr_size_t slen = intern->start_seq_pat->pattern_len;
2905 pos = intern->parse_pos;
2912 while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
2919 intern->state = PARSE_DIRECTIVE;
2923 /* the whole buffer is a partial match */
2925 intern->parse_pos = pos;
2929 /* No match so far, but again:
2930 * We must try all combinations, since the start_seq is a random
2931 * user supplied string
2933 * So: look if the first char of start_seq appears somewhere within
2934 * the current partial match. If it does, try to start a match that
2935 * begins with this offset. (This can happen, if a strange
2936 * start_seq like "---->" spans buffers)
2938 if (spos < intern->parse_pos) {
2942 p = intern->start_seq + spos;
2943 pos = intern->parse_pos - spos;
2945 while (pos && *p != *intern->start_seq) {
2952 /* if a matching beginning char was found, try to match the
2953 * remainder of the old buffer.
2959 while (t < pos && *p == intern->start_seq[t]) {
2965 /* yeah, another partial match found in the *old*
2966 * buffer, now test the *current* buffer for
2980 } while (1); /* work hard to find a match ;-) */
2982 /* no match at all, release all (wrongly) matched chars so far */
2983 *release = intern->parse_pos;
2984 intern->state = PARSE_PRE_HEAD;
2989 * returns the position after the directive
2991 static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
2992 apr_size_t len, char ***store,
2993 apr_size_t **store_len)
2995 struct ssi_internal_ctx *intern = ctx->intern;
2996 const char *p = data;
2997 const char *ep = data + len;
3000 switch (intern->state) {
3001 case PARSE_DIRECTIVE:
3002 while (p < ep && !apr_isspace(*p)) {
3003 /* we have to consider the case of missing space between directive
3004 * and end_seq (be somewhat lenient), e.g. <!--#printenv-->
3006 if (*p == *intern->end_seq) {
3007 intern->state = PARSE_DIRECTIVE_TAIL;
3008 intern->parse_pos = 1;
3015 if (p < ep) { /* found delimiter whitespace */
3016 intern->state = PARSE_DIRECTIVE_POSTNAME;
3017 *store = &intern->directive;
3018 *store_len = &intern->directive_len;
3023 case PARSE_DIRECTIVE_TAIL:
3024 pos = intern->parse_pos;
3026 while (p < ep && pos < intern->end_seq_len &&
3027 *p == intern->end_seq[pos]) {
3032 /* full match, we're done */
3033 if (pos == intern->end_seq_len) {
3034 intern->state = PARSE_DIRECTIVE_POSTTAIL;
3035 *store = &intern->directive;
3036 *store_len = &intern->directive_len;
3040 /* partial match, the buffer is too small to match fully */
3042 intern->parse_pos = pos;
3046 /* no match. continue normal parsing */
3047 intern->state = PARSE_DIRECTIVE;
3050 case PARSE_DIRECTIVE_POSTTAIL:
3051 intern->state = PARSE_EXECUTE;
3052 intern->directive_len -= intern->end_seq_len;
3053 /* continue immediately with the next state */
3055 case PARSE_DIRECTIVE_POSTNAME:
3056 if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
3057 intern->state = PARSE_PRE_ARG;
3060 intern->argv = NULL;
3062 if (!intern->directive_len) {
3064 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing "
3065 "directive name in parsed document %s",
3069 char *sp = intern->directive;
3070 char *sep = intern->directive + intern->directive_len;
3072 /* normalize directive name */
3073 for (; sp < sep; ++sp) {
3074 *sp = apr_tolower(*sp);
3081 /* get a rid of a gcc warning about unhandled enumerations */
3089 * find out whether the next token is (a possible) end_seq or an argument
3091 static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
3094 struct ssi_internal_ctx *intern = ctx->intern;
3095 const char *p = data;
3096 const char *ep = data + len;
3098 /* skip leading WS */
3099 while (p < ep && apr_isspace(*p)) {
3103 /* buffer doesn't consist of whitespaces only */
3105 intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
3112 * test the stream for end_seq. If it doesn't match at all, it must be an
3115 static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
3118 struct ssi_internal_ctx *intern = ctx->intern;
3119 const char *p = data;
3120 const char *ep = data + len;
3121 apr_size_t pos = intern->parse_pos;
3123 if (PARSE_TAIL == intern->state) {
3124 intern->state = PARSE_TAIL_SEQ;
3125 pos = intern->parse_pos = 0;
3128 while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
3133 /* bingo, full match */
3134 if (pos == intern->end_seq_len) {
3135 intern->state = PARSE_EXECUTE;
3139 /* partial match, the buffer is too small to match fully */
3141 intern->parse_pos = pos;
3145 /* no match. It must be an argument string then
3146 * The caller should cleanup and rewind to the reparse point
3148 intern->state = PARSE_ARG;
3153 * extract name=value from the buffer
3154 * A pcre-pattern could look (similar to):
3155 * name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
3157 static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
3158 apr_size_t len, char ***store,
3159 apr_size_t **store_len)
3161 struct ssi_internal_ctx *intern = ctx->intern;
3162 const char *p = data;
3163 const char *ep = data + len;
3165 switch (intern->state) {
3168 * create argument structure and append it to the current list
3170 intern->current_arg = apr_palloc(ctx->dpool,
3171 sizeof(*intern->current_arg));
3172 intern->current_arg->next = NULL;
3175 if (!intern->argv) {
3176 intern->argv = intern->current_arg;
3179 arg_item_t *newarg = intern->argv;
3181 while (newarg->next) {
3182 newarg = newarg->next;
3184 newarg->next = intern->current_arg;
3187 /* check whether it's a valid one. If it begins with a quote, we
3188 * can safely assume, someone forgot the name of the argument
3191 case '"': case '\'': case '`':
3194 intern->state = PARSE_ARG_VAL;
3195 intern->quote = *p++;
3196 intern->current_arg->name = NULL;
3197 intern->current_arg->name_len = 0;
3200 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing "
3201 "argument name for value to tag %s in %s",
3202 apr_pstrmemdup(ctx->r->pool, intern->directive,
3203 intern->directive_len),
3209 intern->state = PARSE_ARG_NAME;
3211 /* continue immediately with next state */
3213 case PARSE_ARG_NAME:
3214 while (p < ep && !apr_isspace(*p) && *p != '=') {
3219 intern->state = PARSE_ARG_POSTNAME;
3220 *store = &intern->current_arg->name;
3221 *store_len = &intern->current_arg->name_len;
3226 case PARSE_ARG_POSTNAME:
3227 intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
3228 intern->current_arg->name,
3229 intern->current_arg->name_len);
3230 if (!intern->current_arg->name_len) {
3232 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing "
3233 "argument name for value to tag %s in %s",
3234 apr_pstrmemdup(ctx->r->pool, intern->directive,
3235 intern->directive_len),
3239 ap_str_tolower(intern->current_arg->name);
3242 intern->state = PARSE_ARG_EQ;
3243 /* continue with next state immediately */
3248 while (p < ep && apr_isspace(*p)) {
3254 intern->state = PARSE_ARG_PREVAL;
3257 else { /* no value */
3258 intern->current_arg->value = NULL;
3259 intern->state = PARSE_PRE_ARG;
3266 case PARSE_ARG_PREVAL:
3269 while (p < ep && apr_isspace(*p)) {
3273 /* buffer doesn't consist of whitespaces only */
3275 intern->state = PARSE_ARG_VAL;
3277 case '"': case '\'': case '`':
3278 intern->quote = *p++;
3281 intern->quote = '\0';
3289 case PARSE_ARG_VAL_ESC:
3290 if (*p == intern->quote) {
3293 intern->state = PARSE_ARG_VAL;
3294 /* continue with next state immediately */
3297 for (; p < ep; ++p) {
3298 if (intern->quote && *p == '\\') {
3301 intern->state = PARSE_ARG_VAL_ESC;
3305 if (*p != intern->quote) {
3309 else if (intern->quote && *p == intern->quote) {
3311 *store = &intern->current_arg->value;
3312 *store_len = &intern->current_arg->value_len;
3313 intern->state = PARSE_ARG_POSTVAL;
3316 else if (!intern->quote && apr_isspace(*p)) {
3318 *store = &intern->current_arg->value;
3319 *store_len = &intern->current_arg->value_len;
3320 intern->state = PARSE_ARG_POSTVAL;
3327 case PARSE_ARG_POSTVAL:
3329 * The value is still the raw input string. Finally clean it up.
3331 --(intern->current_arg->value_len);
3333 /* strip quote escaping \ from the string */
3334 if (intern->quote) {
3335 apr_size_t shift = 0;
3338 sp = intern->current_arg->value;
3339 ep = intern->current_arg->value + intern->current_arg->value_len;
3340 while (sp < ep && *sp != '\\') {
3343 for (; sp < ep; ++sp) {
3344 if (*sp == '\\' && sp[1] == intern->quote) {
3353 intern->current_arg->value_len -= shift;
3356 intern->current_arg->value[intern->current_arg->value_len] = '\0';
3357 intern->state = PARSE_PRE_ARG;
3362 /* get a rid of a gcc warning about unhandled enumerations */
3366 return len; /* partial match of something */
3370 * This is the main loop over the current bucket brigade.
3372 static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
3374 include_ctx_t *ctx = f->ctx;
3375 struct ssi_internal_ctx *intern = ctx->intern;
3376 request_rec *r = f->r;
3377 apr_bucket *b = APR_BRIGADE_FIRST(bb);
3378 apr_bucket_brigade *pass_bb;
3379 apr_status_t rv = APR_SUCCESS;
3380 char *magic; /* magic pointer for sentinel use */
3383 if (APR_BRIGADE_EMPTY(bb)) {
3387 /* we may crash, since already cleaned up; hand over the responsibility
3388 * to the next filter;-)
3390 if (intern->seen_eos) {
3391 return ap_pass_brigade(f->next, bb);
3394 /* All stuff passed along has to be put into that brigade */
3395 pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3397 /* initialization for this loop */
3398 intern->bytes_read = 0;
3402 /* loop over the current bucket brigade */
3403 while (b != APR_BRIGADE_SENTINEL(bb)) {
3404 const char *data = NULL;
3405 apr_size_t len, index, release;
3406 apr_bucket *newb = NULL;
3407 char **store = &magic;
3408 apr_size_t *store_len = NULL;
3410 /* handle meta buckets before reading any data */
3411 if (APR_BUCKET_IS_METADATA(b)) {
3412 newb = APR_BUCKET_NEXT(b);
3414 APR_BUCKET_REMOVE(b);
3416 if (APR_BUCKET_IS_EOS(b)) {
3417 intern->seen_eos = 1;
3419 /* Hit end of stream, time for cleanup ... But wait!
3420 * Perhaps we're not ready yet. We may have to loop one or
3421 * two times again to finish our work. In that case, we
3422 * just re-insert the EOS bucket to allow for an extra loop.
3424 * PARSE_EXECUTE means, we've hit a directive just before the
3425 * EOS, which is now waiting for execution.
3427 * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
3428 * no argument and no space between directive and end_seq
3429 * just before the EOS. (consider <!--#printenv--> as last
3430 * or only string within the stream). This state, however,
3431 * just cleans up and turns itself to PARSE_EXECUTE, which
3432 * will be passed through within the next (and actually
3435 if (PARSE_EXECUTE == intern->state ||
3436 PARSE_DIRECTIVE_POSTTAIL == intern->state) {
3437 APR_BUCKET_INSERT_BEFORE(newb, b);
3440 break; /* END OF STREAM */
3444 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3446 if (APR_BUCKET_IS_FLUSH(b)) {
3455 /* enough is enough ... */
3456 if (ctx->flush_now ||
3457 intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
3459 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3460 rv = ap_pass_brigade(f->next, pass_bb);
3461 if (rv != APR_SUCCESS) {
3462 apr_brigade_destroy(pass_bb);
3468 intern->bytes_read = 0;
3471 /* read the current bucket data */
3473 if (!intern->seen_eos) {
3474 if (intern->bytes_read > 0) {
3475 rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
3476 if (APR_STATUS_IS_EAGAIN(rv)) {
3482 if (!len || rv != APR_SUCCESS) {
3483 rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
3486 if (rv != APR_SUCCESS) {
3487 apr_brigade_destroy(pass_bb);
3491 intern->bytes_read += len;
3494 /* zero length bucket, fetch next one */
3495 if (!len && !intern->seen_eos) {
3496 b = APR_BUCKET_NEXT(b);
3501 * it's actually a data containing bucket, start/continue parsing
3504 switch (intern->state) {
3505 /* no current tag; search for start sequence */
3506 case PARSE_PRE_HEAD:
3507 index = find_start_sequence(ctx, data, len);
3510 apr_bucket_split(b, index);
3513 newb = APR_BUCKET_NEXT(b);
3514 if (ctx->flags & SSI_FLAG_PRINTING) {
3515 APR_BUCKET_REMOVE(b);
3516 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3519 apr_bucket_delete(b);
3523 /* now delete the start_seq stuff from the remaining bucket */
3524 if (PARSE_DIRECTIVE == intern->state) { /* full match */
3525 apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
3526 ctx->flush_now = 1; /* pass pre-tag stuff */
3529 b = APR_BUCKET_NEXT(newb);
3530 apr_bucket_delete(newb);
3538 /* we're currently looking for the end of the start sequence */
3540 index = find_partial_start_sequence(ctx, data, len, &release);
3542 /* check if we mismatched earlier and have to release some chars */
3543 if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
3544 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
3546 newb = apr_bucket_pool_create(to_release, release, ctx->pool,
3547 f->c->bucket_alloc);
3548 APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
3551 if (index) { /* any match */
3552 /* now delete the start_seq stuff from the remaining bucket */
3553 if (PARSE_DIRECTIVE == intern->state) { /* final match */
3554 apr_bucket_split(b, index);
3555 ctx->flush_now = 1; /* pass pre-tag stuff */
3557 newb = APR_BUCKET_NEXT(b);
3558 apr_bucket_delete(b);
3564 /* we're currently grabbing the directive name */
3565 case PARSE_DIRECTIVE:
3566 case PARSE_DIRECTIVE_POSTNAME:
3567 case PARSE_DIRECTIVE_TAIL:
3568 case PARSE_DIRECTIVE_POSTTAIL:
3569 index = find_directive(ctx, data, len, &store, &store_len);
3572 apr_bucket_split(b, index);
3573 newb = APR_BUCKET_NEXT(b);
3578 APR_BUCKET_REMOVE(b);
3579 apr_bucket_setaside(b, r->pool);
3580 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3584 /* time for cleanup? */
3585 if (store != &magic) {
3586 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3588 apr_brigade_cleanup(intern->tmp_bb);
3592 apr_bucket_delete(b);
3598 /* skip WS and find out what comes next (arg or end_seq) */
3600 index = find_arg_or_tail(ctx, data, len);
3602 if (index) { /* skipped whitespaces */
3604 apr_bucket_split(b, index);
3606 newb = APR_BUCKET_NEXT(b);
3607 apr_bucket_delete(b);
3613 /* currently parsing name[=val] */
3615 case PARSE_ARG_NAME:
3616 case PARSE_ARG_POSTNAME:
3618 case PARSE_ARG_PREVAL:
3620 case PARSE_ARG_VAL_ESC:
3621 case PARSE_ARG_POSTVAL:
3622 index = find_argument(ctx, data, len, &store, &store_len);
3625 apr_bucket_split(b, index);
3626 newb = APR_BUCKET_NEXT(b);
3631 APR_BUCKET_REMOVE(b);
3632 apr_bucket_setaside(b, r->pool);
3633 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3637 /* time for cleanup? */
3638 if (store != &magic) {
3639 apr_brigade_pflatten(intern->tmp_bb, store, store_len,
3641 apr_brigade_cleanup(intern->tmp_bb);
3645 apr_bucket_delete(b);
3651 /* try to match end_seq at current pos. */
3653 case PARSE_TAIL_SEQ:
3654 index = find_tail(ctx, data, len);
3656 switch (intern->state) {
3657 case PARSE_EXECUTE: /* full match */
3658 apr_bucket_split(b, index);
3659 newb = APR_BUCKET_NEXT(b);
3660 apr_bucket_delete(b);
3664 case PARSE_ARG: /* no match */
3665 /* PARSE_ARG must reparse at the beginning */
3666 APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
3667 b = APR_BRIGADE_FIRST(bb);
3670 default: /* partial match */
3671 newb = APR_BUCKET_NEXT(b);
3672 APR_BUCKET_REMOVE(b);
3673 apr_bucket_setaside(b, r->pool);
3674 APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
3681 /* now execute the parsed directive, cleanup the space and
3682 * start again with PARSE_PRE_HEAD
3685 /* if there was an error, it was already logged; just stop here */
3686 if (intern->error) {
3687 if (ctx->flags & SSI_FLAG_PRINTING) {
3688 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3693 include_handler_fn_t *handle_func;
3696 (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
3697 intern->directive_len);
3700 DEBUG_INIT(ctx, f, pass_bb);
3701 rv = handle_func(ctx, f, pass_bb);
3702 if (rv != APR_SUCCESS) {
3703 apr_brigade_destroy(pass_bb);
3708 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371)
3709 "unknown directive \"%s\" in parsed doc %s",
3710 apr_pstrmemdup(r->pool, intern->directive,
3711 intern->directive_len),
3713 if (ctx->flags & SSI_FLAG_PRINTING) {
3714 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3720 apr_pool_clear(ctx->dpool);
3721 apr_brigade_cleanup(intern->tmp_bb);
3723 /* Oooof. Done here, start next round */
3724 intern->state = PARSE_PRE_HEAD;
3727 } /* switch(ctx->state) */
3729 } /* while(brigade) */
3731 /* End of stream. Final cleanup */
3732 if (intern->seen_eos) {
3733 if (PARSE_HEAD == intern->state) {
3734 if (ctx->flags & SSI_FLAG_PRINTING) {
3735 char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
3738 APR_BRIGADE_INSERT_TAIL(pass_bb,
3739 apr_bucket_pool_create(to_release,
3740 intern->parse_pos, ctx->pool,
3741 f->c->bucket_alloc));
3744 else if (PARSE_PRE_HEAD != intern->state) {
3745 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372)
3746 "SSI directive was not properly finished at the end "
3747 "of parsed document %s", r->filename);
3748 if (ctx->flags & SSI_FLAG_PRINTING) {
3749 SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
3753 if (!(ctx->flags & SSI_FLAG_PRINTING)) {
3754 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373)
3755 "missing closing endif directive in parsed document"
3756 " %s", r->filename);
3759 /* cleanup our temporary memory */
3760 apr_brigade_destroy(intern->tmp_bb);
3761 apr_pool_destroy(ctx->dpool);
3763 /* don't forget to finally insert the EOS bucket */
3764 APR_BRIGADE_INSERT_TAIL(pass_bb, b);
3767 /* if something's left over, pass it along */
3768 if (!APR_BRIGADE_EMPTY(pass_bb)) {
3769 rv = ap_pass_brigade(f->next, pass_bb);
3773 apr_brigade_destroy(pass_bb);
3780 * +-------------------------------------------------------+
3784 * +-------------------------------------------------------+
3787 static int includes_setup(ap_filter_t *f)
3789 include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
3792 /* When our xbithack value isn't set to full or our platform isn't
3793 * providing group-level protection bits or our group-level bits do not
3794 * have group-execite on, we will set the no_local_copy value to 1 so
3795 * that we will not send 304s.
3797 if ((conf->xbithack != XBITHACK_FULL)
3798 || !(f->r->finfo.valid & APR_FINFO_GPROT)
3799 || !(f->r->finfo.protection & APR_GEXECUTE)) {
3800 f->r->no_local_copy = 1;
3803 /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
3804 * We don't know if we are going to be including a file or executing
3805 * a program - in either case a strong ETag header will likely be invalid.
3807 if (conf->etag <= 0) {
3808 apr_table_setn(f->r->notes, "no-etag", "");
3814 static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
3816 request_rec *r = f->r;
3817 request_rec *parent;
3818 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3821 include_server_config *sconf= ap_get_module_config(r->server->module_config,
3824 if (!(ap_allow_options(r) & OPT_INCLUDES)) {
3825 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374)
3826 "mod_include: Options +Includes (or IncludesNoExec) "
3827 "wasn't set, INCLUDES filter removed: %s", r->uri);
3828 ap_remove_output_filter(f);
3829 return ap_pass_brigade(f->next, b);
3833 struct ssi_internal_ctx *intern;
3836 /* create context for this filter */
3837 f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
3839 ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
3840 ctx->pool = r->pool;
3841 apr_pool_create(&ctx->dpool, ctx->pool);
3844 intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
3845 intern->seen_eos = 0;
3846 intern->state = PARSE_PRE_HEAD;
3847 ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
3848 if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) {
3849 ctx->flags |= SSI_FLAG_NO_EXEC;
3851 intern->legacy_expr = (conf->legacy_expr > 0);
3852 intern->expr_eval_ctx = NULL;
3853 intern->expr_err = NULL;
3854 intern->expr_vary_this = NULL;
3856 ctx->if_nesting_level = 0;
3859 ctx->error_str = conf->default_error_msg ? conf->default_error_msg :
3861 ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt :
3862 DEFAULT_TIME_FORMAT;
3863 intern->start_seq = sconf->default_start_tag;
3864 intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
3865 strlen(intern->start_seq));
3866 intern->end_seq = sconf->default_end_tag;
3867 intern->end_seq_len = strlen(intern->end_seq);
3868 intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo :
3869 DEFAULT_UNDEFINED_ECHO;
3870 intern->undefined_echo_len = strlen(intern->undefined_echo);
3873 if ((parent = ap_get_module_config(r->request_config, &include_module))) {
3874 /* Kludge --- for nested includes, we want to keep the subprocess
3875 * environment of the base document (for compatibility); that means
3876 * torquing our own last_modified date as well so that the
3877 * LAST_MODIFIED variable gets reset to the proper value if the
3878 * nested document resets <!--#config timefmt -->.
3880 r->subprocess_env = r->main->subprocess_env;
3881 apr_pool_join(r->main->pool, r->pool);
3882 r->finfo.mtime = r->main->finfo.mtime;
3885 /* we're not a nested include, so we create an initial
3887 ap_add_common_vars(r);
3889 add_include_vars(r);
3891 /* Always unset the content-length. There is no way to know if
3892 * the content will be modified at some point by send_parsed_content.
3893 * It is very possible for us to not find any content in the first
3894 * 9k of the file, but still have to modify the content of the file.
3895 * If we are going to pass the file through send_parsed_content, then
3896 * the content-length should just be unset.
3898 apr_table_unset(f->r->headers_out, "Content-Length");
3900 /* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
3901 * We don't know if we are going to be including a file or executing
3902 * a program which may change the Last-Modified header or make the
3903 * content completely dynamic. Therefore, we can't support these
3906 * Exception: XBitHack full means we *should* set the
3907 * Last-Modified field.
3909 * SSILastModified on means we *should* set the Last-Modified field
3910 * if not present, or respect an existing value if present.
3913 /* Must we respect the last modified header? By default, no */
3914 if (conf->lastmodified > 0) {
3916 /* update the last modified if we have a valid time, and only if
3917 * we don't already have a valid last modified.
3919 if (r->finfo.valid & APR_FINFO_MTIME
3920 && !apr_table_get(f->r->headers_out, "Last-Modified")) {
3921 ap_update_mtime(r, r->finfo.mtime);
3922 ap_set_last_modified(r);
3927 /* Assure the platform supports Group protections */
3928 else if (((conf->xbithack == XBITHACK_FULL ||
3929 (conf->xbithack == XBITHACK_UNSET &&
3930 DEFAULT_XBITHACK == XBITHACK_FULL))
3931 && (r->finfo.valid & APR_FINFO_GPROT)
3932 && (r->finfo.protection & APR_GEXECUTE))) {
3933 ap_update_mtime(r, r->finfo.mtime);
3934 ap_set_last_modified(r);
3937 apr_table_unset(f->r->headers_out, "Last-Modified");
3940 /* add QUERY stuff to env cause it ain't yet */
3942 char *arg_copy = apr_pstrdup(r->pool, r->args);
3944 apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
3945 ap_unescape_url(arg_copy);
3946 apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
3947 ap_escape_shell_cmd(r->pool, arg_copy));
3950 return send_parsed_content(f, b);
3953 static int include_fixup(request_rec *r)
3955 if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
3957 if (!r->content_type || !*r->content_type) {
3958 ap_set_content_type(r, "text/html");
3960 r->handler = "default-handler";
3963 #if defined(OS2) || defined(WIN32) || defined(NETWARE)
3964 /* These OS's don't support xbithack. This is being worked on. */
3970 include_dir_config *conf = ap_get_module_config(r->per_dir_config,
3973 if (conf->xbithack == XBITHACK_OFF ||
3974 (DEFAULT_XBITHACK == XBITHACK_OFF &&
3975 conf->xbithack == XBITHACK_UNSET))
3980 if (!(r->finfo.protection & APR_UEXECUTE)) {
3984 if (!r->content_type || strncmp(r->content_type, "text/html", 9)) {
3990 /* We always return declined, because the default handler actually
3991 * serves the file. All we have to do is add the filter.
3993 ap_add_output_filter("INCLUDES", NULL, r, r->connection);
3999 * +-------------------------------------------------------+
4001 * | Configuration Handling
4003 * +-------------------------------------------------------+
4006 static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
4008 include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config));
4010 result->xbithack = XBITHACK_UNSET;
4011 result->lastmodified = UNSET;
4012 result->etag = UNSET;
4013 result->legacy_expr = UNSET;
4018 #define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val
4019 static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv)
4021 include_dir_config *base = (include_dir_config *)basev,
4022 *over = (include_dir_config *)overridesv,
4023 *new = apr_palloc(p, sizeof(include_dir_config));
4024 MERGE(base, over, new, default_error_msg, NULL);
4025 MERGE(base, over, new, default_time_fmt, NULL);
4026 MERGE(base, over, new, undefined_echo, NULL);
4027 MERGE(base, over, new, xbithack, XBITHACK_UNSET);
4028 MERGE(base, over, new, lastmodified, UNSET);
4029 MERGE(base, over, new, etag, UNSET);
4030 MERGE(base, over, new, legacy_expr, UNSET);
4034 static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
4036 include_server_config *result;
4038 result = apr_palloc(p, sizeof(include_server_config));
4039 result->default_end_tag = DEFAULT_END_SEQUENCE;
4040 result->default_start_tag = DEFAULT_START_SEQUENCE;
4045 static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
4047 include_dir_config *conf = mconfig;
4049 if (!strcasecmp(arg, "off")) {
4050 conf->xbithack = XBITHACK_OFF;
4052 else if (!strcasecmp(arg, "on")) {
4053 conf->xbithack = XBITHACK_ON;
4055 else if (!strcasecmp(arg, "full")) {
4056 conf->xbithack = XBITHACK_FULL;
4059 return "XBitHack must be set to Off, On, or Full";
4065 static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
4068 include_server_config *conf;
4069 const char *p = tag;
4071 /* be consistent. (See below in set_default_end_tag) */
4073 if (apr_isspace(*p)) {
4074 return "SSIStartTag may not contain any whitespaces";
4079 conf= ap_get_module_config(cmd->server->module_config , &include_module);
4080 conf->default_start_tag = tag;
4085 static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
4088 include_server_config *conf;
4089 const char *p = tag;
4091 /* sanity check. The parser may fail otherwise */
4093 if (apr_isspace(*p)) {
4094 return "SSIEndTag may not contain any whitespaces";
4099 conf= ap_get_module_config(cmd->server->module_config , &include_module);
4100 conf->default_end_tag = tag;
4105 static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
4108 include_dir_config *conf = mconfig;
4109 conf->undefined_echo = msg;
4114 static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
4117 include_dir_config *conf = mconfig;
4118 conf->default_error_msg = msg;
4123 static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
4126 include_dir_config *conf = mconfig;
4127 conf->default_time_fmt = fmt;
4134 * +-------------------------------------------------------+
4136 * | Module Initialization and Configuration
4138 * +-------------------------------------------------------+
4141 static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
4142 apr_pool_t *ptemp, server_rec *s)
4144 include_handlers = apr_hash_make(p);
4146 ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
4148 if(ssi_pfn_register) {
4149 ssi_pfn_register("if", handle_if);
4150 ssi_pfn_register("set", handle_set);
4151 ssi_pfn_register("else", handle_else);
4152 ssi_pfn_register("elif", handle_elif);
4153 ssi_pfn_register("echo", handle_echo);
4154 ssi_pfn_register("endif", handle_endif);
4155 ssi_pfn_register("fsize", handle_fsize);
4156 ssi_pfn_register("config", handle_config);
4157 ssi_pfn_register("include", handle_include);
4158 ssi_pfn_register("flastmod", handle_flastmod);
4159 ssi_pfn_register("printenv", handle_printenv);
4165 static const command_rec includes_cmds[] =
4167 AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
4168 "Off, On, or Full"),
4169 AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
4171 AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
4172 "a strftime(3) formatted string"),
4173 AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
4174 "SSI Start String Tag"),
4175 AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
4176 "SSI End String Tag"),
4177 AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
4178 "String to be displayed if an echoed variable is undefined"),
4179 AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char,
4180 (void *)APR_OFFSETOF(include_dir_config, legacy_expr),
4182 "Whether to use the legacy expression parser compatible "
4183 "with <= 2.2.x. Limited to 'on' or 'off'"),
4184 AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char,
4185 (void *)APR_OFFSETOF(include_dir_config, lastmodified),
4186 OR_LIMIT, "Whether to set the last modified header or respect "
4187 "an existing header. Limited to 'on' or 'off'"),
4188 AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char,
4189 (void *)APR_OFFSETOF(include_dir_config, etag),
4190 OR_LIMIT, "Whether to allow the generation of ETags within the server. "
4191 "Existing ETags will be preserved. Limited to 'on' or 'off'"),
4195 static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
4197 apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
4200 static void register_hooks(apr_pool_t *p)
4202 APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
4203 APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
4204 APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
4205 ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
4206 ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
4207 ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
4211 AP_DECLARE_MODULE(include) =
4213 STANDARD20_MODULE_STUFF,
4214 create_includes_dir_config, /* dir config creater */
4215 merge_includes_dir_config, /* dir config merger */
4216 create_includes_server_config,/* server config */
4217 NULL, /* merge server config */
4218 includes_cmds, /* command apr_table_t */
4219 register_hooks /* register hooks */