/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "apr.h" #include "apr_strings.h" #include "apr_thread_proc.h" #include "apr_hash.h" #include "apr_user.h" #include "apr_lib.h" #include "apr_optional.h" #define APR_WANT_STRFUNC #define APR_WANT_MEMFUNC #include "apr_want.h" #include "ap_config.h" #include "util_filter.h" #include "httpd.h" #include "http_config.h" #include "http_core.h" #include "http_request.h" #include "http_core.h" #include "http_protocol.h" #include "http_log.h" #include "http_main.h" #include "util_script.h" #include "http_core.h" #include "mod_include.h" #include "ap_expr.h" /* helper for Latin1 <-> entity encoding */ #if APR_CHARSET_EBCDIC #include "util_ebcdic.h" #define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \ (unsigned char)ch) #else /* APR_CHARSET_EBCDIC */ #define RAW_ASCII_CHAR(ch) (ch) #endif /* !APR_CHARSET_EBCDIC */ /* * +-------------------------------------------------------+ * | | * | Types and Structures * | | * +-------------------------------------------------------+ */ /* sll used for string expansion */ typedef struct result_item { struct result_item *next; apr_size_t len; const char *string; } result_item_t; /* conditional expression parser stuff */ typedef enum { TOKEN_STRING, TOKEN_RE, TOKEN_AND, TOKEN_OR, TOKEN_NOT, TOKEN_EQ, TOKEN_NE, TOKEN_RBRACE, TOKEN_LBRACE, TOKEN_GROUP, TOKEN_GE, TOKEN_LE, TOKEN_GT, TOKEN_LT, TOKEN_ACCESS } token_type_t; typedef struct { token_type_t type; const char *value; #ifdef DEBUG_INCLUDE const char *s; #endif } token_t; typedef struct parse_node { struct parse_node *parent; struct parse_node *left; struct parse_node *right; token_t token; int value; int done; #ifdef DEBUG_INCLUDE int dump_done; #endif } parse_node_t; typedef enum { XBITHACK_OFF, XBITHACK_ON, XBITHACK_FULL, XBITHACK_UNSET } xbithack_t; typedef struct { const char *default_error_msg; const char *default_time_fmt; const char *undefined_echo; xbithack_t xbithack; signed char lastmodified; signed char etag; signed char legacy_expr; } include_dir_config; typedef struct { const char *default_start_tag; const char *default_end_tag; } include_server_config; /* main parser states */ typedef enum { PARSE_PRE_HEAD, PARSE_HEAD, PARSE_DIRECTIVE, PARSE_DIRECTIVE_POSTNAME, PARSE_DIRECTIVE_TAIL, PARSE_DIRECTIVE_POSTTAIL, PARSE_PRE_ARG, PARSE_ARG, PARSE_ARG_NAME, PARSE_ARG_POSTNAME, PARSE_ARG_EQ, PARSE_ARG_PREVAL, PARSE_ARG_VAL, PARSE_ARG_VAL_ESC, PARSE_ARG_POSTVAL, PARSE_TAIL, PARSE_TAIL_SEQ, PARSE_EXECUTE } parse_state_t; typedef struct arg_item { struct arg_item *next; char *name; apr_size_t name_len; char *value; apr_size_t value_len; } arg_item_t; typedef struct { const char *source; const char *rexp; apr_size_t nsub; ap_regmatch_t match[AP_MAX_REG_MATCH]; int have_match; } backref_t; typedef struct { unsigned int T[256]; unsigned int x; apr_size_t pattern_len; } bndm_t; struct ssi_internal_ctx { parse_state_t state; int seen_eos; int error; char quote; /* quote character value (or \0) */ apr_size_t parse_pos; /* parse position of partial matches */ apr_size_t bytes_read; apr_bucket_brigade *tmp_bb; const char *start_seq; bndm_t *start_seq_pat; const char *end_seq; apr_size_t end_seq_len; char *directive; /* name of the current directive */ apr_size_t directive_len; /* length of the current directive name */ arg_item_t *current_arg; /* currently parsed argument */ arg_item_t *argv; /* all arguments */ backref_t *re; /* NULL if there wasn't a regex yet */ const char *undefined_echo; apr_size_t undefined_echo_len; char legacy_expr; /* use ap_expr or legacy mod_include expression parser? */ ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */ const char *expr_vary_this; /* for use by ap_expr_eval_ctx */ const char *expr_err; /* for use by ap_expr_eval_ctx */ #ifdef DEBUG_INCLUDE struct { ap_filter_t *f; apr_bucket_brigade *bb; } debug; #endif }; /* * +-------------------------------------------------------+ * | | * | Debugging Utilities * | | * +-------------------------------------------------------+ */ #ifdef DEBUG_INCLUDE #define TYPE_TOKEN(token, ttype) do { \ (token)->type = ttype; \ (token)->s = #ttype; \ } while(0) #define CREATE_NODE(ctx, name) do { \ (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \ (name)->parent = (name)->left = (name)->right = NULL; \ (name)->done = 0; \ (name)->dump_done = 0; \ } while(0) static void debug_printf(include_ctx_t *ctx, const char *fmt, ...) { va_list ap; char *debug__str; va_start(ap, fmt); debug__str = apr_pvsprintf(ctx->pool, fmt, ap); va_end(ap); APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create( debug__str, strlen(debug__str), ctx->pool, ctx->intern->debug.f->c->bucket_alloc)); } #define DUMP__CHILD(ctx, is, node, child) if (1) { \ parse_node_t *d__c = node->child; \ if (d__c) { \ if (!d__c->dump_done) { \ if (d__c->parent != node) { \ debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \ if (!d__c->parent) { \ debug_printf(ctx, "Parent of " #child " child node is " \ "NULL.\n"); \ } \ else { \ debug_printf(ctx, "Parent of " #child " child node " \ "points to another node (of type %s)!\n", \ d__c->parent->token.s); \ } \ return; \ } \ node = d__c; \ continue; \ } \ } \ else { \ debug_printf(ctx, "%s(missing)\n", is); \ } \ } static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root) { parse_node_t *current; char *is; if (!root) { debug_printf(ctx, " -- Parse Tree empty --\n\n"); return; } debug_printf(ctx, " ----- Parse Tree -----\n"); current = root; is = " "; while (current) { switch (current->token.type) { case TOKEN_STRING: case TOKEN_RE: debug_printf(ctx, "%s%s (%s)\n", is, current->token.s, current->token.value); current->dump_done = 1; current = current->parent; continue; case TOKEN_NOT: case TOKEN_GROUP: case TOKEN_RBRACE: case TOKEN_LBRACE: if (!current->dump_done) { debug_printf(ctx, "%s%s\n", is, current->token.s); is = apr_pstrcat(ctx->dpool, is, " ", NULL); current->dump_done = 1; } DUMP__CHILD(ctx, is, current, right) if (!current->right || current->right->dump_done) { is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); if (current->right) current->right->dump_done = 0; current = current->parent; } continue; default: if (!current->dump_done) { debug_printf(ctx, "%s%s\n", is, current->token.s); is = apr_pstrcat(ctx->dpool, is, " ", NULL); current->dump_done = 1; } DUMP__CHILD(ctx, is, current, left) DUMP__CHILD(ctx, is, current, right) if ((!current->left || current->left->dump_done) && (!current->right || current->right->dump_done)) { is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); if (current->left) current->left->dump_done = 0; if (current->right) current->right->dump_done = 0; current = current->parent; } continue; } } /* it is possible to call this function within the parser loop, to see * how the tree is built. That way, we must cleanup after us to dump * always the whole tree */ root->dump_done = 0; if (root->left) root->left->dump_done = 0; if (root->right) root->right->dump_done = 0; debug_printf(ctx, " --- End Parse Tree ---\n\n"); } #define DEBUG_INIT(ctx, filter, brigade) do { \ (ctx)->intern->debug.f = filter; \ (ctx)->intern->debug.bb = brigade; \ } while(0) #define DEBUG_PRINTF(arg) debug_printf arg #define DEBUG_DUMP_TOKEN(ctx, token) do { \ token_t *d__t = (token); \ \ if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \ DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \ } \ else { \ DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \ } \ } while(0) #define DEBUG_DUMP_EVAL(ctx, node) do { \ char c = '"'; \ switch ((node)->token.type) { \ case TOKEN_STRING: \ debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\ (node)->token.value, ((node)->value) ? '1':'0'); \ break; \ case TOKEN_AND: \ case TOKEN_OR: \ debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\ (node)->token.s, \ (((node)->left->done) ? ((node)->left->value ?"1":"0") \ : "short circuited"), \ (((node)->right->done) ? ((node)->right->value?"1":"0") \ : "short circuited"), \ (node)->value ? '1' : '0'); \ break; \ case TOKEN_EQ: \ case TOKEN_NE: \ case TOKEN_GT: \ case TOKEN_GE: \ case TOKEN_LT: \ case TOKEN_LE: \ if ((node)->right->token.type == TOKEN_RE) c = '/'; \ debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \ (node)->token.s, \ (node)->left->token.value, \ c, (node)->right->token.value, c, \ (node)->value ? '1' : '0'); \ break; \ default: \ debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \ (node)->value ? '1' : '0'); \ break; \ } \ } while(0) #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \ if (unmatched) { \ DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \ } \ } while(0) #define DEBUG_DUMP_COND(ctx, text) \ DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \ ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0')) #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root) #else /* DEBUG_INCLUDE */ #define TYPE_TOKEN(token, ttype) (token)->type = ttype #define CREATE_NODE(ctx, name) do { \ (name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \ (name)->parent = (name)->left = (name)->right = NULL; \ (name)->done = 0; \ } while(0) #define DEBUG_INIT(ctx, f, bb) #define DEBUG_PRINTF(arg) #define DEBUG_DUMP_TOKEN(ctx, token) #define DEBUG_DUMP_EVAL(ctx, node) #define DEBUG_DUMP_UNMATCHED(ctx, unmatched) #define DEBUG_DUMP_COND(ctx, text) #define DEBUG_DUMP_TREE(ctx, root) #endif /* !DEBUG_INCLUDE */ /* * +-------------------------------------------------------+ * | | * | Static Module Data * | | * +-------------------------------------------------------+ */ /* global module structure */ module AP_MODULE_DECLARE_DATA include_module; /* function handlers for include directives */ static apr_hash_t *include_handlers; /* forward declaration of handler registry */ static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register; /* Sentinel value to store in subprocess_env for items that * shouldn't be evaluated until/unless they're actually used */ static const char lazy_eval_sentinel = '\0'; #define LAZY_VALUE (&lazy_eval_sentinel) /* default values */ #define DEFAULT_START_SEQUENCE "" #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]" #define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z" #define DEFAULT_UNDEFINED_ECHO "(none)" #define UNSET -1 #ifdef XBITHACK #define DEFAULT_XBITHACK XBITHACK_FULL #else #define DEFAULT_XBITHACK XBITHACK_OFF #endif /* * +-------------------------------------------------------+ * | | * | Environment/Expansion Functions * | | * +-------------------------------------------------------+ */ /* * decodes a string containing html entities or numeric character references. * 's' is overwritten with the decoded string. * If 's' is syntatically incorrect, then the followed fixups will be made: * unknown entities will be left undecoded; * references to unused numeric characters will be deleted. * In particular, � will not be decoded, but will be deleted. */ /* maximum length of any ISO-LATIN-1 HTML entity name. */ #define MAXENTLEN (6) /* The following is a shrinking transformation, therefore safe. */ static void decodehtml(char *s) { int val, i, j; char *p; const char *ents; static const char * const entlist[MAXENTLEN + 1] = { NULL, /* 0 */ NULL, /* 1 */ "lt\074gt\076", /* 2 */ "amp\046ETH\320eth\360", /* 3 */ "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml" "\353iuml\357ouml\366uuml\374yuml\377", /* 4 */ "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc" "\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352" "icirc\356ocirc\364ucirc\373thorn\376", /* 5 */ "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311" "Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde" "\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340" "aacute\341atilde\343ccedil\347egrave\350eacute\351igrave" "\354iacute\355ntilde\361ograve\362oacute\363otilde\365" "oslash\370ugrave\371uacute\372yacute\375" /* 6 */ }; /* Do a fast scan through the string until we find anything * that needs more complicated handling */ for (; *s != '&'; s++) { if (*s == '\0') { return; } } for (p = s; *s != '\0'; s++, p++) { if (*s != '&') { *p = *s; continue; } /* find end of entity */ for (i = 1; s[i] != ';' && s[i] != '\0'; i++) { continue; } if (s[i] == '\0') { /* treat as normal data */ *p = *s; continue; } /* is it numeric ? */ if (s[1] == '#') { for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) { val = val * 10 + s[j] - '0'; } s += i; if (j < i || val <= 8 || (val >= 11 && val <= 31) || (val >= 127 && val <= 160) || val >= 256) { p--; /* no data to output */ } else { *p = RAW_ASCII_CHAR(val); } } else { j = i - 1; if (j > MAXENTLEN || entlist[j] == NULL) { /* wrong length */ *p = '&'; continue; /* skip it */ } for (ents = entlist[j]; *ents != '\0'; ents += i) { if (strncmp(s + 1, ents, j) == 0) { break; } } if (*ents == '\0') { *p = '&'; /* unknown */ } else { *p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]); s += i; } } } *p = '\0'; } static void add_include_vars(request_rec *r) { apr_table_t *e = r->subprocess_env; char *t; apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE); apr_table_setn(e, "DATE_GMT", LAZY_VALUE); apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE); apr_table_setn(e, "DOCUMENT_URI", r->uri); apr_table_setn(e, "DOCUMENT_ARGS", r->args ? r->args : ""); if (r->path_info && *r->path_info) { apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info); } apr_table_setn(e, "USER_NAME", LAZY_VALUE); if (r->filename && (t = strrchr(r->filename, '/'))) { apr_table_setn(e, "DOCUMENT_NAME", ++t); } else { apr_table_setn(e, "DOCUMENT_NAME", r->uri); } if (r->args) { char *arg_copy = apr_pstrdup(r->pool, r->args); ap_unescape_url(arg_copy); apr_table_setn(e, "QUERY_STRING_UNESCAPED", ap_escape_shell_cmd(r->pool, arg_copy)); } } static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt) { char *val; if (!strcasecmp(var, "DATE_LOCAL")) { val = ap_ht_time(r->pool, r->request_time, timefmt, 0); } else if (!strcasecmp(var, "DATE_GMT")) { val = ap_ht_time(r->pool, r->request_time, timefmt, 1); } else if (!strcasecmp(var, "LAST_MODIFIED")) { val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0); } else if (!strcasecmp(var, "USER_NAME")) { if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) { val = ""; } } else { val = NULL; } if (val) { apr_table_setn(r->subprocess_env, var, val); } return val; } static const char *get_include_var(const char *var, include_ctx_t *ctx) { const char *val; request_rec *r = ctx->r; if (apr_isdigit(*var) && !var[1]) { apr_size_t idx = *var - '0'; backref_t *re = ctx->intern->re; /* Handle $0 .. $9 from the last regex evaluated. * The choice of returning NULL strings on not-found, * v.s. empty strings on an empty match is deliberate. */ if (!re || !re->have_match) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329) "regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s", idx, r->filename); return NULL; } else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330) "regex capture $%" APR_SIZE_T_FMT " is out of range (last regex was: '%s') in %s", idx, re->rexp, r->filename); return NULL; } else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) { /* This particular subpattern was not used by the regex */ return NULL; } else { val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so, re->match[idx].rm_eo - re->match[idx].rm_so); } } else { val = apr_table_get(r->subprocess_env, var); if (val == LAZY_VALUE) { val = add_include_vars_lazy(r, var, ctx->time_str); } } return val; } static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx, const void *data, const char *arg) { const char *res, *name = data; include_ctx_t *ctx = eval_ctx->data; if ((name[0] == 'e') || (name[0] == 'E')) { /* keep legacy "env" semantics */ if ((res = apr_table_get(ctx->r->notes, arg)) != NULL) return res; else if ((res = get_include_var(arg, ctx)) != NULL) return res; else return getenv(arg); } else { return get_include_var(arg, ctx); } } static int include_expr_lookup(ap_expr_lookup_parms *parms) { switch (parms->type) { case AP_EXPR_FUNC_STRING: if (strcasecmp(parms->name, "v") == 0 || strcasecmp(parms->name, "reqenv") == 0 || strcasecmp(parms->name, "env") == 0) { *parms->func = include_expr_var_fn; *parms->data = parms->name; return OK; } break; /* * We could also make the SSI vars available as %{...} style variables * (AP_EXPR_FUNC_VAR), but this would create problems if we ever want * to cache parsed expressions for performance reasons. */ } return ap_run_expr_lookup(parms); } /* * Do variable substitution on strings * * (Note: If out==NULL, this function allocs a buffer for the resulting * string from ctx->pool. The return value is always the parsed string) */ static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out, apr_size_t length, int leave_name) { request_rec *r = ctx->r; result_item_t *result = NULL, *current = NULL; apr_size_t outlen = 0, inlen, span; char *ret = NULL, *eout = NULL; const char *p; if (out) { /* sanity check, out && !length is not supported */ ap_assert(out && length); ret = out; eout = out + length - 1; } span = strcspn(in, "\\$"); inlen = strlen(in); /* fast exit */ if (inlen == span) { if (out) { apr_cpystrn(out, in, length); } else { ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen) ? length - 1 : inlen); } return ret; } /* well, actually something to do */ p = in + span; if (out) { if (span) { memcpy(out, in, (out+span <= eout) ? span : (eout-out)); out += span; } } else { current = result = apr_palloc(ctx->dpool, sizeof(*result)); current->next = NULL; current->string = in; current->len = span; outlen = span; } /* loop for specials */ do { if ((out && out >= eout) || (length && outlen >= length)) { break; } /* prepare next entry */ if (!out && current->len) { current->next = apr_palloc(ctx->dpool, sizeof(*current->next)); current = current->next; current->next = NULL; current->len = 0; } /* * escaped character */ if (*p == '\\') { if (out) { *out++ = (p[1] == '$') ? *++p : *p; ++p; } else { current->len = 1; current->string = (p[1] == '$') ? ++p : p; ++p; ++outlen; } } /* * variable expansion */ else { /* *p == '$' */ const char *newp = NULL, *ep, *key = NULL; if (*++p == '{') { ep = ap_strchr_c(++p, '}'); if (!ep) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on " "variable \"%s\" in %s", p, r->filename); break; } if (p < ep) { key = apr_pstrmemdup(ctx->dpool, p, ep - p); newp = ep + 1; } p -= 2; } else { ep = p; while (*ep == '_' || apr_isalnum(*ep)) { ++ep; } if (p < ep) { key = apr_pstrmemdup(ctx->dpool, p, ep - p); newp = ep; } --p; } /* empty name results in a copy of '$' in the output string */ if (!key) { if (out) { *out++ = *p++; } else { current->len = 1; current->string = p++; ++outlen; } } else { const char *val = get_include_var(key, ctx); apr_size_t len = 0; if (val) { len = strlen(val); } else if (leave_name) { val = p; len = ep - p; } if (val && len) { if (out) { memcpy(out, val, (out+len <= eout) ? len : (eout-out)); out += len; } else { current->len = len; current->string = val; outlen += len; } } p = newp; } } if ((out && out >= eout) || (length && outlen >= length)) { break; } /* check the remainder */ if (*p && (span = strcspn(p, "\\$")) > 0) { if (!out && current->len) { current->next = apr_palloc(ctx->dpool, sizeof(*current->next)); current = current->next; current->next = NULL; } if (out) { memcpy(out, p, (out+span <= eout) ? span : (eout-out)); out += span; } else { current->len = span; current->string = p; outlen += span; } p += span; } } while (p < in+inlen); /* assemble result */ if (out) { if (out > eout) { *eout = '\0'; } else { *out = '\0'; } } else { const char *ep; if (length && outlen > length) { outlen = length - 1; } ret = out = apr_palloc(ctx->pool, outlen + 1); ep = ret + outlen; do { if (result->len) { memcpy(out, result->string, (out+result->len <= ep) ? result->len : (ep-out)); out += result->len; } result = result->next; } while (result && out < ep); ret[outlen] = '\0'; } return ret; } /* * +-------------------------------------------------------+ * | | * | Conditional Expression Parser * | | * +-------------------------------------------------------+ */ static APR_INLINE int re_check(include_ctx_t *ctx, const char *string, const char *rexp) { ap_regex_t *compiled; backref_t *re = ctx->intern->re; compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED); if (!compiled) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02667) "unable to compile pattern \"%s\"", rexp); return -1; } if (!re) { re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re)); } re->source = apr_pstrdup(ctx->pool, string); re->rexp = apr_pstrdup(ctx->pool, rexp); re->nsub = compiled->re_nsub; re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, re->match, 0); ap_pregfree(ctx->dpool, compiled); return re->have_match; } static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous) { const char *p; apr_size_t shift; int unmatched; token->value = NULL; if (!*parse) { return 0; } /* Skip leading white space */ while (apr_isspace(**parse)) { ++*parse; } if (!**parse) { *parse = NULL; return 0; } TYPE_TOKEN(token, TOKEN_STRING); /* the default type */ p = *parse; unmatched = 0; switch (*(*parse)++) { case '(': TYPE_TOKEN(token, TOKEN_LBRACE); return 0; case ')': TYPE_TOKEN(token, TOKEN_RBRACE); return 0; case '=': if (**parse == '=') ++*parse; TYPE_TOKEN(token, TOKEN_EQ); return 0; case '!': if (**parse == '=') { TYPE_TOKEN(token, TOKEN_NE); ++*parse; return 0; } TYPE_TOKEN(token, TOKEN_NOT); return 0; case '\'': unmatched = '\''; break; case '/': /* if last token was ACCESS, this token is STRING */ if (previous != NULL && TOKEN_ACCESS == previous->type) { break; } TYPE_TOKEN(token, TOKEN_RE); unmatched = '/'; break; case '|': if (**parse == '|') { TYPE_TOKEN(token, TOKEN_OR); ++*parse; return 0; } break; case '&': if (**parse == '&') { TYPE_TOKEN(token, TOKEN_AND); ++*parse; return 0; } break; case '>': if (**parse == '=') { TYPE_TOKEN(token, TOKEN_GE); ++*parse; return 0; } TYPE_TOKEN(token, TOKEN_GT); return 0; case '<': if (**parse == '=') { TYPE_TOKEN(token, TOKEN_LE); ++*parse; return 0; } TYPE_TOKEN(token, TOKEN_LT); return 0; case '-': if (**parse == 'A') { TYPE_TOKEN(token, TOKEN_ACCESS); ++*parse; return 0; } break; } /* It's a string or regex token * Now search for the next token, which finishes this string */ shift = 0; p = *parse = token->value = unmatched ? *parse : p; for (; **parse; p = ++*parse) { if (**parse == '\\') { if (!*(++*parse)) { p = *parse; break; } ++shift; } else { if (unmatched) { if (**parse == unmatched) { unmatched = 0; ++*parse; break; } } else if (apr_isspace(**parse)) { break; } else { int found = 0; switch (**parse) { case '(': case ')': case '=': case '!': case '<': case '>': ++found; break; case '|': case '&': if ((*parse)[1] == **parse) { ++found; } break; } if (found) { break; } } } } if (unmatched) { token->value = apr_pstrdup(ctx->dpool, ""); } else { apr_size_t len = p - token->value - shift; char *c = apr_palloc(ctx->dpool, len + 1); p = token->value; token->value = c; while (shift--) { const char *e = ap_strchr_c(p, '\\'); memcpy(c, p, e-p); c += e-p; *c++ = *++e; len -= e-p; p = e+1; } if (len) { memcpy(c, p, len); } c[len] = '\0'; } return unmatched; } static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error) { parse_node_t *new, *root = NULL, *current = NULL; request_rec *r = ctx->r; request_rec *rr = NULL; const char *error = APLOGNO(03188) "Invalid expression \"%s\" in file %s"; const char *parse = expr; unsigned regex = 0; *was_error = 0; if (!parse) { return 0; } /* Create Parse Tree */ while (1) { /* uncomment this to see how the tree a built: * * DEBUG_DUMP_TREE(ctx, root); */ CREATE_NODE(ctx, new); { #ifdef DEBUG_INCLUDE int was_unmatched = #endif get_ptoken(ctx, &parse, &new->token, (current != NULL ? ¤t->token : NULL)); if (!parse) break; DEBUG_DUMP_UNMATCHED(ctx, was_unmatched); DEBUG_DUMP_TOKEN(ctx, &new->token); } if (!current) { switch (new->token.type) { case TOKEN_STRING: case TOKEN_NOT: case TOKEN_ACCESS: case TOKEN_LBRACE: root = current = new; continue; default: /* Intentional no APLOGNO */ /* error text provides APLOGNO */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename); *was_error = 1; return 0; } } switch (new->token.type) { case TOKEN_STRING: switch (current->token.type) { case TOKEN_STRING: current->token.value = apr_pstrcat(ctx->dpool, current->token.value, *current->token.value ? " " : "", new->token.value, NULL); continue; case TOKEN_RE: case TOKEN_RBRACE: case TOKEN_GROUP: break; default: new->parent = current; current = current->right = new; continue; } break; case TOKEN_RE: switch (current->token.type) { case TOKEN_EQ: case TOKEN_NE: new->parent = current; current = current->right = new; ++regex; continue; default: break; } break; case TOKEN_AND: case TOKEN_OR: switch (current->token.type) { case TOKEN_STRING: case TOKEN_RE: case TOKEN_GROUP: current = current->parent; while (current) { switch (current->token.type) { case TOKEN_AND: case TOKEN_OR: case TOKEN_LBRACE: break; default: current = current->parent; continue; } break; } if (!current) { new->left = root; root->parent = new; current = root = new; continue; } new->left = current->right; new->left->parent = new; new->parent = current; current = current->right = new; continue; default: break; } break; case TOKEN_EQ: case TOKEN_NE: case TOKEN_GE: case TOKEN_GT: case TOKEN_LE: case TOKEN_LT: if (current->token.type == TOKEN_STRING) { current = current->parent; if (!current) { new->left = root; root->parent = new; current = root = new; continue; } switch (current->token.type) { case TOKEN_LBRACE: case TOKEN_AND: case TOKEN_OR: new->left = current->right; new->left->parent = new; new->parent = current; current = current->right = new; continue; default: break; } } break; case TOKEN_RBRACE: while (current && current->token.type != TOKEN_LBRACE) { current = current->parent; } if (current) { TYPE_TOKEN(¤t->token, TOKEN_GROUP); continue; } error = APLOGNO(03189) "Unmatched ')' in \"%s\" in file %s"; break; case TOKEN_NOT: case TOKEN_ACCESS: case TOKEN_LBRACE: switch (current->token.type) { case TOKEN_STRING: case TOKEN_RE: case TOKEN_RBRACE: case TOKEN_GROUP: break; default: current->right = new; new->parent = current; current = new; continue; } break; default: break; } /* Intentional no APLOGNO */ /* error text provides APLOGNO */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename); *was_error = 1; return 0; } DEBUG_DUMP_TREE(ctx, root); /* Evaluate Parse Tree */ current = root; error = NULL; while (current) { switch (current->token.type) { case TOKEN_STRING: current->token.value = ap_ssi_parse_string(ctx, current->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->value = !!*current->token.value; break; case TOKEN_AND: case TOKEN_OR: if (!current->left || !current->right) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332) "Invalid expression \"%s\" in file %s", expr, r->filename); *was_error = 1; return 0; } if (!current->left->done) { switch (current->left->token.type) { case TOKEN_STRING: current->left->token.value = ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->left->value = !!*current->left->token.value; DEBUG_DUMP_EVAL(ctx, current->left); current->left->done = 1; break; default: current = current->left; continue; } } /* short circuit evaluation */ if (!current->right->done && !regex && ((current->token.type == TOKEN_AND && !current->left->value) || (current->token.type == TOKEN_OR && current->left->value))) { current->value = current->left->value; } else { if (!current->right->done) { switch (current->right->token.type) { case TOKEN_STRING: current->right->token.value = ap_ssi_parse_string(ctx,current->right->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->right->value = !!*current->right->token.value; DEBUG_DUMP_EVAL(ctx, current->right); current->right->done = 1; break; default: current = current->right; continue; } } if (current->token.type == TOKEN_AND) { current->value = current->left->value && current->right->value; } else { current->value = current->left->value || current->right->value; } } break; case TOKEN_EQ: case TOKEN_NE: if (!current->left || !current->right || current->left->token.type != TOKEN_STRING || (current->right->token.type != TOKEN_STRING && current->right->token.type != TOKEN_RE)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333) "Invalid expression \"%s\" in file %s", expr, r->filename); *was_error = 1; return 0; } current->left->token.value = ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->right->token.value = ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); if (current->right->token.type == TOKEN_RE) { current->value = re_check(ctx, current->left->token.value, current->right->token.value); --regex; } else { current->value = !strcmp(current->left->token.value, current->right->token.value); } if (current->token.type == TOKEN_NE) { current->value = !current->value; } break; case TOKEN_GE: case TOKEN_GT: case TOKEN_LE: case TOKEN_LT: if (!current->left || !current->right || current->left->token.type != TOKEN_STRING || current->right->token.type != TOKEN_STRING) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334) "Invalid expression \"%s\" in file %s", expr, r->filename); *was_error = 1; return 0; } current->left->token.value = ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->right->token.value = ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); current->value = strcmp(current->left->token.value, current->right->token.value); switch (current->token.type) { case TOKEN_GE: current->value = current->value >= 0; break; case TOKEN_GT: current->value = current->value > 0; break; case TOKEN_LE: current->value = current->value <= 0; break; case TOKEN_LT: current->value = current->value < 0; break; default: current->value = 0; break; /* should not happen */ } break; case TOKEN_NOT: case TOKEN_GROUP: if (current->right) { if (!current->right->done) { current = current->right; continue; } current->value = current->right->value; } else { current->value = 1; } if (current->token.type == TOKEN_NOT) { current->value = !current->value; } break; case TOKEN_ACCESS: if (current->left || !current->right || (current->right->token.type != TOKEN_STRING && current->right->token.type != TOKEN_RE)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335) "Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.", expr, r->filename); *was_error = 1; return 0; } current->right->token.value = ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0, SSI_EXPAND_DROP_NAME); rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL); /* 400 and higher are considered access denied */ if (rr->status < HTTP_BAD_REQUEST) { current->value = 1; } else { current->value = 0; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336) "mod_include: The tested " "subrequest -A \"%s\" returned an error code.", current->right->token.value); } ap_destroy_sub_req(rr); break; case TOKEN_RE: if (!error) { error = APLOGNO(03190) "No operator before regex in expr \"%s\" in file %s"; } case TOKEN_LBRACE: if (!error) { error = APLOGNO(03191) "Unmatched '(' in \"%s\" in file %s"; } default: if (!error) { error = APLOGNO(03192) "internal parser error in \"%s\" in file %s"; } /* Intentional no APLOGNO */ /* error text provides APLOGNO */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename); *was_error = 1; return 0; } DEBUG_DUMP_EVAL(ctx, current); current->done = 1; current = current->parent; } return (root ? root->value : 0); } /* same as above, but use common ap_expr syntax / API */ static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error) { ap_expr_info_t *expr_info = apr_pcalloc(ctx->pool, sizeof (*expr_info)); const char *err; int ret; backref_t *re = ctx->intern->re; ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx; expr_info->filename = ctx->r->filename; expr_info->line_number = 0; expr_info->module_index = APLOG_MODULE_INDEX; expr_info->flags = AP_EXPR_FLAG_RESTRICTED; err = ap_expr_parse(ctx->r->pool, ctx->r->pool, expr_info, expr, include_expr_lookup); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337) "Could not parse expr \"%s\" in %s: %s", expr, ctx->r->filename, err); *was_error = 1; return 0; } if (!re) { ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re)); } else { /* ap_expr_exec_ctx() does not care about re->have_match but only about * re->source */ if (!re->have_match) re->source = NULL; } if (!eval_ctx) { eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx)); ctx->intern->expr_eval_ctx = eval_ctx; eval_ctx->r = ctx->r; eval_ctx->c = ctx->r->connection; eval_ctx->s = ctx->r->server; eval_ctx->p = ctx->r->pool; eval_ctx->data = ctx; eval_ctx->err = &ctx->intern->expr_err; eval_ctx->vary_this = &ctx->intern->expr_vary_this; eval_ctx->re_nmatch = AP_MAX_REG_MATCH; eval_ctx->re_pmatch = re->match; eval_ctx->re_source = &re->source; } eval_ctx->info = expr_info; ret = ap_expr_exec_ctx(eval_ctx); if (ret < 0) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338) "Could not evaluate expr \"%s\" in %s: %s", expr, ctx->r->filename, ctx->intern->expr_err); *was_error = 1; return 0; } *was_error = 0; if (re->source) re->have_match = 1; return ret; } /* * +-------------------------------------------------------+ * | | * | Action Handlers * | | * +-------------------------------------------------------+ */ /* * Extract the next tag name and value. * If there are no more tags, set the tag name to NULL. * The tag value is html decoded if dodecode is non-zero. * The tag value may be NULL if there is no tag value.. */ static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag, char **tag_val, int dodecode) { if (!ctx->intern->argv) { *tag = NULL; *tag_val = NULL; return; } *tag_val = ctx->intern->argv->value; *tag = ctx->intern->argv->name; ctx->intern->argv = ctx->intern->argv->next; if (dodecode && *tag_val) { decodehtml(*tag_val); } } static int find_file(request_rec *r, const char *directive, const char *tag, char *tag_val, apr_finfo_t *finfo) { char *to_send = tag_val; request_rec *rr = NULL; int ret=0; char *error_fmt = NULL; apr_status_t rv = APR_SUCCESS; if (!strcmp(tag, "file")) { char *newpath; /* be safe; only files in this directory or below allowed */ rv = apr_filepath_merge(&newpath, NULL, tag_val, APR_FILEPATH_SECUREROOTTEST | APR_FILEPATH_NOTABSOLUTE, r->pool); if (rv != APR_SUCCESS) { error_fmt = APLOGNO(02668) "unable to access file \"%s\" " "in parsed file %s"; } else { /* note: it is okay to pass NULL for the "next filter" since we never attempt to "run" this sub request. */ rr = ap_sub_req_lookup_file(newpath, r, NULL); if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) { to_send = rr->filename; if ((rv = apr_stat(finfo, to_send, APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS && rv != APR_INCOMPLETE) { error_fmt = APLOGNO(02669) "unable to get information " "about \"%s\" in parsed file %s"; } } else { error_fmt = APLOGNO(02670) "unable to lookup information " "about \"%s\" in parsed file %s"; } } if (error_fmt) { ret = -1; /* Intentional no APLOGNO */ /* error_fmt provides APLOGNO */ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, to_send, r->filename); } if (rr) ap_destroy_sub_req(rr); return ret; } else if (!strcmp(tag, "virtual")) { /* note: it is okay to pass NULL for the "next filter" since we never attempt to "run" this sub request. */ rr = ap_sub_req_lookup_uri(tag_val, r, NULL); if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) { memcpy((char *) finfo, (const char *) &rr->finfo, sizeof(rr->finfo)); ap_destroy_sub_req(rr); return 0; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get " "information about \"%s\" in parsed file %s", tag_val, r->filename); ap_destroy_sub_req(rr); return -1; } } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" " "to tag %s in %s", tag, directive, r->filename); return -1; } } /* * */ static apr_status_t handle_comment(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { return APR_SUCCESS; } /* * * * Output each file/virtual in turn until one of them returns an error. * On error, ignore all further file/virtual attributes until we reach * an onerror attribute, where we make an attempt to serve the onerror * virtual url. If onerror fails, or no onerror is present, the default * error string is inserted into the stream. */ static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; char *last_error; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01341) "missing argument for include element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (!ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } last_error = NULL; while (1) { char *tag = NULL; char *tag_val = NULL; request_rec *rr = NULL; char *error_fmt = NULL; char *parsed_string; apr_status_t rv = APR_SUCCESS; int status = 0; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); if (!tag || !tag_val) { break; } if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag, "onerror")) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter " "\"%s\" to tag include in %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); if (tag[0] == 'f') { char *newpath; /* be safe; only files in this directory or below allowed */ rv = apr_filepath_merge(&newpath, NULL, parsed_string, APR_FILEPATH_SECUREROOTTEST | APR_FILEPATH_NOTABSOLUTE, ctx->dpool); if (rv != APR_SUCCESS) { error_fmt = "unable to include file \"%s\" in parsed file %s"; } else { rr = ap_sub_req_lookup_file(newpath, r, f->next); } } else if ((tag[0] == 'v' && !last_error) || (tag[0] == 'o' && last_error)) { if (r->kept_body) { rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next); } else { rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); } } else { continue; } if (!error_fmt && rr->status != HTTP_OK) { error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d"; } if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) && rr->content_type && strncmp(rr->content_type, "text/", 5)) { error_fmt = "unable to include potential exec \"%s\" in parsed " "file %s, content type not text/*"; } /* See the Kludge in includes_filter for why. * Basically, it puts a bread crumb in here, then looks * for the crumb later to see if its been here. */ if (rr) { ap_set_module_config(rr->request_config, &include_module, r); } if (!error_fmt && ((status = ap_run_sub_req(rr)))) { error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d"; } if (error_fmt) { /* Intentional no APLOGNO */ /* error text is also sent to client */ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val, r->filename, status ? status : rr ? rr->status : 0); if (last_error) { /* onerror threw an error, give up completely */ break; } last_error = error_fmt; } else { last_error = NULL; } /* Do *not* destroy the subrequest here; it may have allocated * variables in this r->subprocess_env in the subrequest's * r->pool, so that pool must survive as long as this request. * Yes, this is a memory leak. */ } if (last_error) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); } return APR_SUCCESS; } /* * */ static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { const char *encoding = "entity", *decoding = "none"; request_rec *r = f->r; int error = 0; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01343) "missing argument for echo element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (!ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } while (1) { char *tag = NULL; char *tag_val = NULL; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); if (!tag || !tag_val) { break; } if (!strcmp(tag, "var")) { const char *val; const char *echo_text = NULL; apr_size_t e_len; val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME), ctx); if (val) { char *last = NULL; char *e, *d, *token; echo_text = val; d = apr_pstrdup(ctx->pool, decoding); token = apr_strtok(d, ", \t", &last); while (token) { if (!strcasecmp(token, "none")) { /* do nothing */ } else if (!strcasecmp(token, "url")) { char *buf = apr_pstrdup(ctx->pool, echo_text); ap_unescape_url(buf); echo_text = buf; } else if (!strcasecmp(token, "urlencoded")) { char *buf = apr_pstrdup(ctx->pool, echo_text); ap_unescape_urlencoded(buf); echo_text = buf; } else if (!strcasecmp(token, "entity")) { char *buf = apr_pstrdup(ctx->pool, echo_text); decodehtml(buf); echo_text = buf; } else if (!strcasecmp(token, "base64")) { echo_text = ap_pbase64decode(ctx->dpool, echo_text); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value " "\"%s\" to parameter \"decoding\" of tag echo in " "%s", token, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); error = 1; break; } token = apr_strtok(NULL, ", \t", &last); } e = apr_pstrdup(ctx->pool, encoding); token = apr_strtok(e, ", \t", &last); while (token) { if (!strcasecmp(token, "none")) { /* do nothing */ } else if (!strcasecmp(token, "url")) { echo_text = ap_escape_uri(ctx->dpool, echo_text); } else if (!strcasecmp(token, "urlencoded")) { echo_text = ap_escape_urlencoded(ctx->dpool, echo_text); } else if (!strcasecmp(token, "entity")) { echo_text = ap_escape_html2(ctx->dpool, echo_text, 0); } else if (!strcasecmp(token, "base64")) { char *buf; buf = ap_pbase64encode(ctx->dpool, (char *)echo_text); echo_text = buf; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value " "\"%s\" to parameter \"encoding\" of tag echo in " "%s", token, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); error = 1; break; } token = apr_strtok(NULL, ", \t", &last); } e_len = strlen(echo_text); } else { echo_text = ctx->intern->undefined_echo; e_len = ctx->intern->undefined_echo_len; } if (error) { break; } APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create( apr_pmemdup(ctx->pool, echo_text, e_len), e_len, ctx->pool, f->c->bucket_alloc)); } else if (!strcmp(tag, "decoding")) { decoding = tag_val; } else if (!strcmp(tag, "encoding")) { encoding = tag_val; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter " "\"%s\" in tag echo of %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } return APR_SUCCESS; } /* * */ static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; apr_table_t *env = r->subprocess_env; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01347) "missing argument for config element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (!ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } while (1) { char *tag = NULL; char *tag_val = NULL; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW); if (!tag || !tag_val) { break; } if (!strcmp(tag, "errmsg")) { ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); } else if (!strcmp(tag, "echomsg")) { ctx->intern->undefined_echo = ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME); ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo); } else if (!strcmp(tag, "timefmt")) { apr_time_t date = r->request_time; ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date, ctx->time_str, 0)); apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date, ctx->time_str, 1)); apr_table_setn(env, "LAST_MODIFIED", ap_ht_time(r->pool, r->finfo.mtime, ctx->time_str, 0)); } else if (!strcmp(tag, "sizefmt")) { char *parsed_string; parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); if (!strcmp(parsed_string, "bytes")) { ctx->flags |= SSI_FLAG_SIZE_IN_BYTES; } else if (!strcmp(parsed_string, "abbrev")) { ctx->flags &= SSI_FLAG_SIZE_ABBREV; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value " "\"%s\" to parameter \"sizefmt\" of tag config " "in %s", parsed_string, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter " "\"%s\" to tag config in %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } return APR_SUCCESS; } /* * */ static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01350) "missing argument for fsize element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (!ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } while (1) { char *tag = NULL; char *tag_val = NULL; apr_finfo_t finfo; char *parsed_string; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); if (!tag || !tag_val) { break; } parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); if (!find_file(r, "fsize", tag, parsed_string, &finfo)) { char *buf; apr_size_t len; if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) { buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5)); len = 4; /* omit the \0 terminator */ } else { apr_size_t l, x, pos; char *tmp; tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size); len = l = strlen(tmp); for (x = 0; x < l; ++x) { if (x && !((l - x) % 3)) { ++len; } } if (len == l) { buf = apr_pstrmemdup(ctx->pool, tmp, len); } else { buf = apr_palloc(ctx->pool, len); for (pos = x = 0; x < l; ++x) { if (x && !((l - x) % 3)) { buf[pos++] = ','; } buf[pos++] = tmp[x]; } } } APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len, ctx->pool, f->c->bucket_alloc)); } else { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } return APR_SUCCESS; } /* * */ static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; if (!ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01351) "missing argument for flastmod element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (!ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } while (1) { char *tag = NULL; char *tag_val = NULL; apr_finfo_t finfo; char *parsed_string; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED); if (!tag || !tag_val) { break; } parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) { char *t_val; apr_size_t t_len; t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0); t_len = strlen(t_val); APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len, ctx->pool, f->c->bucket_alloc)); } else { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } return APR_SUCCESS; } /* * */ static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { char *tag = NULL; char *expr = NULL; request_rec *r = f->r; int expr_ret, was_error; if (ctx->argc != 1) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, (ctx->argc) ? APLOGNO(01352) "too many arguments for if element in %s" : APLOGNO(01353) "missing expr argument for if element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { ++(ctx->if_nesting_level); return APR_SUCCESS; } if (ctx->argc != 1) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW); if (strcmp(tag, "expr")) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" " "to tag if in %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } if (!expr) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if " "element in %s", r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr)); if (ctx->intern->legacy_expr) expr_ret = parse_expr(ctx, expr, &was_error); else expr_ret = parse_ap_expr(ctx, expr, &was_error); if (was_error) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } if (expr_ret) { ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); } else { ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND; } DEBUG_DUMP_COND(ctx, " if"); ctx->if_nesting_level = 0; return APR_SUCCESS; } /* * */ static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { char *tag = NULL; char *expr = NULL; request_rec *r = f->r; int expr_ret, was_error; if (ctx->argc != 1) { ap_log_rerror(APLOG_MARK, (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 0, r, (ctx->argc) ? APLOGNO(01356) "too many arguments for if element in %s" : APLOGNO(01357) "missing expr argument for if element in %s", r->filename); } if (ctx->if_nesting_level) { return APR_SUCCESS; } if (ctx->argc != 1) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW); if (strcmp(tag, "expr")) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" " "to tag if in %s", tag, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } if (!expr) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif " "statement: %s", r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr)); DEBUG_DUMP_COND(ctx, " elif"); if (ctx->flags & SSI_FLAG_COND_TRUE) { ctx->flags &= SSI_FLAG_CLEAR_PRINTING; return APR_SUCCESS; } if (ctx->intern->legacy_expr) expr_ret = parse_expr(ctx, expr, &was_error); else expr_ret = parse_ap_expr(ctx, expr, &was_error); if (was_error) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } if (expr_ret) { ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); } else { ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND; } DEBUG_DUMP_COND(ctx, " elif"); return APR_SUCCESS; } /* * */ static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; if (ctx->argc) { ap_log_rerror(APLOG_MARK, (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01360) "else directive does not take tags in %s", r->filename); } if (ctx->if_nesting_level) { return APR_SUCCESS; } if (ctx->argc) { if (ctx->flags & SSI_FLAG_PRINTING) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); } return APR_SUCCESS; } DEBUG_DUMP_COND(ctx, " else"); if (ctx->flags & SSI_FLAG_COND_TRUE) { ctx->flags &= SSI_FLAG_CLEAR_PRINTING; } else { ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); } return APR_SUCCESS; } /* * */ static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; if (ctx->argc) { ap_log_rerror(APLOG_MARK, (!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01361) "endif directive does not take tags in %s", r->filename); } if (ctx->if_nesting_level) { --(ctx->if_nesting_level); return APR_SUCCESS; } if (ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } DEBUG_DUMP_COND(ctx, "endif"); ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); return APR_SUCCESS; } /* * */ static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { const char *encoding = "none", *decoding = "none"; char *var = NULL; request_rec *r = f->r; request_rec *sub = r->main; apr_pool_t *p = r->pool; int error = 0; if (ctx->argc < 2) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01362) "missing argument for set element in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (ctx->argc < 2) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } /* we need to use the 'main' request pool to set notes as that is * a notes lifetime */ while (sub) { p = sub->pool; sub = sub->main; } while (1) { char *tag = NULL; char *tag_val = NULL; ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW); if (!tag || !tag_val) { break; } if (!strcmp(tag, "var")) { decodehtml(tag_val); var = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); } else if (!strcmp(tag, "decoding")) { decoding = tag_val; } else if (!strcmp(tag, "encoding")) { encoding = tag_val; } else if (!strcmp(tag, "value")) { char *parsed_string; if (!var) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must " "precede value in set directive in %s", r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, SSI_EXPAND_DROP_NAME); if (parsed_string) { char *last = NULL; char *e, *d, *token; d = apr_pstrdup(ctx->pool, decoding); token = apr_strtok(d, ", \t", &last); while (token) { if (!strcasecmp(token, "none")) { /* do nothing */ } else if (!strcasecmp(token, "url")) { char *buf = apr_pstrdup(ctx->pool, parsed_string); ap_unescape_url(buf); parsed_string = buf; } else if (!strcasecmp(token, "urlencoded")) { char *buf = apr_pstrdup(ctx->pool, parsed_string); ap_unescape_urlencoded(buf); parsed_string = buf; } else if (!strcasecmp(token, "entity")) { char *buf = apr_pstrdup(ctx->pool, parsed_string); decodehtml(buf); parsed_string = buf; } else if (!strcasecmp(token, "base64")) { parsed_string = ap_pbase64decode(ctx->dpool, parsed_string); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value " "\"%s\" to parameter \"decoding\" of tag set in " "%s", token, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); error = 1; break; } token = apr_strtok(NULL, ", \t", &last); } e = apr_pstrdup(ctx->pool, encoding); token = apr_strtok(e, ", \t", &last); while (token) { if (!strcasecmp(token, "none")) { /* do nothing */ } else if (!strcasecmp(token, "url")) { parsed_string = ap_escape_uri(ctx->dpool, parsed_string); } else if (!strcasecmp(token, "urlencoded")) { parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string); } else if (!strcasecmp(token, "entity")) { parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0); } else if (!strcasecmp(token, "base64")) { char *buf; buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string); parsed_string = buf; } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value " "\"%s\" to parameter \"encoding\" of tag set in " "%s", token, r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); error = 1; break; } token = apr_strtok(NULL, ", \t", &last); } } if (error) { break; } apr_table_setn(r->subprocess_env, apr_pstrdup(p, var), apr_pstrdup(p, parsed_string)); } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set " "directive in %s", r->filename); SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } return APR_SUCCESS; } /* * */ static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f, apr_bucket_brigade *bb) { request_rec *r = f->r; const apr_array_header_t *arr; const apr_table_entry_t *elts; int i; if (ctx->argc) { ap_log_rerror(APLOG_MARK, (ctx->flags & SSI_FLAG_PRINTING) ? APLOG_ERR : APLOG_WARNING, 0, r, APLOGNO(01367) "printenv directive does not take tags in %s", r->filename); } if (!(ctx->flags & SSI_FLAG_PRINTING)) { return APR_SUCCESS; } if (ctx->argc) { SSI_CREATE_ERROR_BUCKET(ctx, f, bb); return APR_SUCCESS; } arr = apr_table_elts(r->subprocess_env); elts = (apr_table_entry_t *)arr->elts; for (i = 0; i < arr->nelts; ++i) { const char *key_text, *val_text; /* get key */ key_text = ap_escape_html(ctx->dpool, elts[i].key); /* get value */ val_text = elts[i].val; if (val_text == LAZY_VALUE) val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str); val_text = ap_escape_html(ctx->dpool, val_text); apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n", NULL); } ctx->flush_now = 1; return APR_SUCCESS; } /* * +-------------------------------------------------------+ * | | * | Main Includes-Filter Engine * | | * +-------------------------------------------------------+ */ /* This is an implementation of the BNDM search algorithm. * * Fast and Flexible String Matching by Combining Bit-parallelism and * Suffix Automata (2001) * Gonzalo Navarro, Mathieu Raffinot * * http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz * * Initial code submitted by Sascha Schumann. */ /* Precompile the bndm_t data structure. */ static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl) { unsigned int x; const char *ne = n + nl; bndm_t *t = apr_palloc(pool, sizeof(*t)); memset(t->T, 0, sizeof(unsigned int) * 256); t->pattern_len = nl; for (x = 1; n < ne; x <<= 1) { t->T[(unsigned char) *n++] |= x; } t->x = x - 1; return t; } /* Implements the BNDM search algorithm (as described above). * * h - the string to look in * hl - length of the string to look for * t - precompiled bndm structure against the pattern * * Returns the count of character that is the first match or hl if no * match is found. */ static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl) { const char *skip; const char *he, *p, *pi; unsigned int *T, x, d; apr_size_t nl; he = h + hl; T = t->T; x = t->x; nl = t->pattern_len; pi = h - 1; /* pi: p initial */ p = pi + nl; /* compare window right to left. point to the first char */ while (p < he) { skip = p; d = x; do { d &= T[(unsigned char) *p--]; if (!d) { break; } if ((d & 1)) { if (p != pi) { skip = p; } else { return p - h + 1; } } d >>= 1; } while (d); pi = skip; p = pi + nl; } return hl; } /* * returns the index position of the first byte of start_seq (or the len of * the buffer as non-match) */ static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data, apr_size_t len) { struct ssi_internal_ctx *intern = ctx->intern; apr_size_t slen = intern->start_seq_pat->pattern_len; apr_size_t index; const char *p, *ep; if (len < slen) { p = data; /* try partial match at the end of the buffer (below) */ } else { /* try fast bndm search over the buffer * (hopefully the whole start sequence can be found in this buffer) */ index = bndm(intern->start_seq_pat, data, len); /* wow, found it. ready. */ if (index < len) { intern->state = PARSE_DIRECTIVE; return index; } else { /* ok, the pattern can't be found as whole in the buffer, * check the end for a partial match */ p = data + len - slen + 1; } } ep = data + len; do { while (p < ep && *p != *intern->start_seq) { ++p; } index = p - data; /* found a possible start_seq start */ if (p < ep) { apr_size_t pos = 1; ++p; while (p < ep && *p == intern->start_seq[pos]) { ++p; ++pos; } /* partial match found. Store the info for the next round */ if (p == ep) { intern->state = PARSE_HEAD; intern->parse_pos = pos; return index; } } /* we must try all combinations; consider (e.g.) SSIStartTag "--->" * and a string data of "--.-" and the end of the buffer */ p = data + index + 1; } while (p < ep); /* no match */ return len; } /* * returns the first byte *after* the partial (or final) match. * * If we had to trick with the start_seq start, 'release' returns the * number of chars of the start_seq which appeared not to be part of a * full tag and may have to be passed down the filter chain. */ static apr_size_t find_partial_start_sequence(include_ctx_t *ctx, const char *data, apr_size_t len, apr_size_t *release) { struct ssi_internal_ctx *intern = ctx->intern; apr_size_t pos, spos = 0; apr_size_t slen = intern->start_seq_pat->pattern_len; const char *p, *ep; pos = intern->parse_pos; ep = data + len; *release = 0; do { p = data; while (p < ep && pos < slen && *p == intern->start_seq[pos]) { ++p; ++pos; } /* full match */ if (pos == slen) { intern->state = PARSE_DIRECTIVE; return (p - data); } /* the whole buffer is a partial match */ if (p == ep) { intern->parse_pos = pos; return (p - data); } /* No match so far, but again: * We must try all combinations, since the start_seq is a random * user supplied string * * So: look if the first char of start_seq appears somewhere within * the current partial match. If it does, try to start a match that * begins with this offset. (This can happen, if a strange * start_seq like "---->" spans buffers) */ if (spos < intern->parse_pos) { do { ++spos; ++*release; p = intern->start_seq + spos; pos = intern->parse_pos - spos; while (pos && *p != *intern->start_seq) { ++p; ++spos; ++*release; --pos; } /* if a matching beginning char was found, try to match the * remainder of the old buffer. */ if (pos > 1) { apr_size_t t = 1; ++p; while (t < pos && *p == intern->start_seq[t]) { ++p; ++t; } if (t == pos) { /* yeah, another partial match found in the *old* * buffer, now test the *current* buffer for * continuing match */ break; } } } while (pos > 1); if (pos) { continue; } } break; } while (1); /* work hard to find a match ;-) */ /* no match at all, release all (wrongly) matched chars so far */ *release = intern->parse_pos; intern->state = PARSE_PRE_HEAD; return 0; } /* * returns the position after the directive */ static apr_size_t find_directive(include_ctx_t *ctx, const char *data, apr_size_t len, char ***store, apr_size_t **store_len) { struct ssi_internal_ctx *intern = ctx->intern; const char *p = data; const char *ep = data + len; apr_size_t pos; switch (intern->state) { case PARSE_DIRECTIVE: while (p < ep && !apr_isspace(*p)) { /* we have to consider the case of missing space between directive * and end_seq (be somewhat lenient), e.g. */ if (*p == *intern->end_seq) { intern->state = PARSE_DIRECTIVE_TAIL; intern->parse_pos = 1; ++p; return (p - data); } ++p; } if (p < ep) { /* found delimiter whitespace */ intern->state = PARSE_DIRECTIVE_POSTNAME; *store = &intern->directive; *store_len = &intern->directive_len; } break; case PARSE_DIRECTIVE_TAIL: pos = intern->parse_pos; while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) { ++p; ++pos; } /* full match, we're done */ if (pos == intern->end_seq_len) { intern->state = PARSE_DIRECTIVE_POSTTAIL; *store = &intern->directive; *store_len = &intern->directive_len; break; } /* partial match, the buffer is too small to match fully */ if (p == ep) { intern->parse_pos = pos; break; } /* no match. continue normal parsing */ intern->state = PARSE_DIRECTIVE; return 0; case PARSE_DIRECTIVE_POSTTAIL: intern->state = PARSE_EXECUTE; intern->directive_len -= intern->end_seq_len; /* continue immediately with the next state */ case PARSE_DIRECTIVE_POSTNAME: if (PARSE_DIRECTIVE_POSTNAME == intern->state) { intern->state = PARSE_PRE_ARG; } ctx->argc = 0; intern->argv = NULL; if (!intern->directive_len) { intern->error = 1; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing " "directive name in parsed document %s", ctx->r->filename); } else { char *sp = intern->directive; char *sep = intern->directive + intern->directive_len; /* normalize directive name */ for (; sp < sep; ++sp) { *sp = apr_tolower(*sp); } } return 0; default: /* get a rid of a gcc warning about unhandled enumerations */ break; } return (p - data); } /* * find out whether the next token is (a possible) end_seq or an argument */ static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data, apr_size_t len) { struct ssi_internal_ctx *intern = ctx->intern; const char *p = data; const char *ep = data + len; /* skip leading WS */ while (p < ep && apr_isspace(*p)) { ++p; } /* buffer doesn't consist of whitespaces only */ if (p < ep) { intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG; } return (p - data); } /* * test the stream for end_seq. If it doesn't match at all, it must be an * argument */ static apr_size_t find_tail(include_ctx_t *ctx, const char *data, apr_size_t len) { struct ssi_internal_ctx *intern = ctx->intern; const char *p = data; const char *ep = data + len; apr_size_t pos = intern->parse_pos; if (PARSE_TAIL == intern->state) { intern->state = PARSE_TAIL_SEQ; pos = intern->parse_pos = 0; } while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) { ++p; ++pos; } /* bingo, full match */ if (pos == intern->end_seq_len) { intern->state = PARSE_EXECUTE; return (p - data); } /* partial match, the buffer is too small to match fully */ if (p == ep) { intern->parse_pos = pos; return (p - data); } /* no match. It must be an argument string then * The caller should cleanup and rewind to the reparse point */ intern->state = PARSE_ARG; return 0; } /* * extract name=value from the buffer * A pcre-pattern could look (similar to): * name\s*(?:=\s*(["'`]?)value\1(?>\s*))? */ static apr_size_t find_argument(include_ctx_t *ctx, const char *data, apr_size_t len, char ***store, apr_size_t **store_len) { struct ssi_internal_ctx *intern = ctx->intern; const char *p = data; const char *ep = data + len; switch (intern->state) { case PARSE_ARG: /* * create argument structure and append it to the current list */ intern->current_arg = apr_palloc(ctx->dpool, sizeof(*intern->current_arg)); intern->current_arg->next = NULL; ++(ctx->argc); if (!intern->argv) { intern->argv = intern->current_arg; } else { arg_item_t *newarg = intern->argv; while (newarg->next) { newarg = newarg->next; } newarg->next = intern->current_arg; } /* check whether it's a valid one. If it begins with a quote, we * can safely assume, someone forgot the name of the argument */ switch (*p) { case '"': case '\'': case '`': *store = NULL; intern->state = PARSE_ARG_VAL; intern->quote = *p++; intern->current_arg->name = NULL; intern->current_arg->name_len = 0; intern->error = 1; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing " "argument name for value to tag %s in %s", apr_pstrmemdup(ctx->r->pool, intern->directive, intern->directive_len), ctx->r->filename); return (p - data); default: intern->state = PARSE_ARG_NAME; } /* continue immediately with next state */ case PARSE_ARG_NAME: while (p < ep && !apr_isspace(*p) && *p != '=') { ++p; } if (p < ep) { intern->state = PARSE_ARG_POSTNAME; *store = &intern->current_arg->name; *store_len = &intern->current_arg->name_len; return (p - data); } break; case PARSE_ARG_POSTNAME: intern->current_arg->name = apr_pstrmemdup(ctx->dpool, intern->current_arg->name, intern->current_arg->name_len); if (!intern->current_arg->name_len) { intern->error = 1; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing " "argument name for value to tag %s in %s", apr_pstrmemdup(ctx->r->pool, intern->directive, intern->directive_len), ctx->r->filename); } else { ap_str_tolower(intern->current_arg->name); } intern->state = PARSE_ARG_EQ; /* continue with next state immediately */ case PARSE_ARG_EQ: *store = NULL; while (p < ep && apr_isspace(*p)) { ++p; } if (p < ep) { if (*p == '=') { intern->state = PARSE_ARG_PREVAL; ++p; } else { /* no value */ intern->current_arg->value = NULL; intern->state = PARSE_PRE_ARG; } return (p - data); } break; case PARSE_ARG_PREVAL: *store = NULL; while (p < ep && apr_isspace(*p)) { ++p; } /* buffer doesn't consist of whitespaces only */ if (p < ep) { intern->state = PARSE_ARG_VAL; switch (*p) { case '"': case '\'': case '`': intern->quote = *p++; break; default: intern->quote = '\0'; break; } return (p - data); } break; case PARSE_ARG_VAL_ESC: if (*p == intern->quote) { ++p; } intern->state = PARSE_ARG_VAL; /* continue with next state immediately */ case PARSE_ARG_VAL: for (; p < ep; ++p) { if (intern->quote && *p == '\\') { ++p; if (p == ep) { intern->state = PARSE_ARG_VAL_ESC; break; } if (*p != intern->quote) { --p; } } else if (intern->quote && *p == intern->quote) { ++p; *store = &intern->current_arg->value; *store_len = &intern->current_arg->value_len; intern->state = PARSE_ARG_POSTVAL; break; } else if (!intern->quote && apr_isspace(*p)) { ++p; *store = &intern->current_arg->value; *store_len = &intern->current_arg->value_len; intern->state = PARSE_ARG_POSTVAL; break; } } return (p - data); case PARSE_ARG_POSTVAL: /* * The value is still the raw input string. Finally clean it up. */ --(intern->current_arg->value_len); /* strip quote escaping \ from the string */ if (intern->quote) { apr_size_t shift = 0; char *sp; sp = intern->current_arg->value; ep = intern->current_arg->value + intern->current_arg->value_len; while (sp < ep && *sp != '\\') { ++sp; } for (; sp < ep; ++sp) { if (*sp == '\\' && sp[1] == intern->quote) { ++sp; ++shift; } if (shift) { *(sp-shift) = *sp; } } intern->current_arg->value_len -= shift; } intern->current_arg->value[intern->current_arg->value_len] = '\0'; intern->state = PARSE_PRE_ARG; return 0; default: /* get a rid of a gcc warning about unhandled enumerations */ break; } return len; /* partial match of something */ } /* * This is the main loop over the current bucket brigade. */ static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb) { include_ctx_t *ctx = f->ctx; struct ssi_internal_ctx *intern = ctx->intern; request_rec *r = f->r; apr_bucket *b = APR_BRIGADE_FIRST(bb); apr_bucket_brigade *pass_bb; apr_status_t rv = APR_SUCCESS; char *magic; /* magic pointer for sentinel use */ /* fast exit */ if (APR_BRIGADE_EMPTY(bb)) { return APR_SUCCESS; } /* we may crash, since already cleaned up; hand over the responsibility * to the next filter;-) */ if (intern->seen_eos) { return ap_pass_brigade(f->next, bb); } /* All stuff passed along has to be put into that brigade */ pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc); /* initialization for this loop */ intern->bytes_read = 0; intern->error = 0; ctx->flush_now = 0; /* loop over the current bucket brigade */ while (b != APR_BRIGADE_SENTINEL(bb)) { const char *data = NULL; apr_size_t len, index, release; apr_bucket *newb = NULL; char **store = &magic; apr_size_t *store_len = NULL; /* handle meta buckets before reading any data */ if (APR_BUCKET_IS_METADATA(b)) { newb = APR_BUCKET_NEXT(b); APR_BUCKET_REMOVE(b); if (APR_BUCKET_IS_EOS(b)) { intern->seen_eos = 1; /* Hit end of stream, time for cleanup ... But wait! * Perhaps we're not ready yet. We may have to loop one or * two times again to finish our work. In that case, we * just re-insert the EOS bucket to allow for an extra loop. * * PARSE_EXECUTE means, we've hit a directive just before the * EOS, which is now waiting for execution. * * PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with * no argument and no space between directive and end_seq * just before the EOS. (consider as last * or only string within the stream). This state, however, * just cleans up and turns itself to PARSE_EXECUTE, which * will be passed through within the next (and actually * last) round. */ if (PARSE_EXECUTE == intern->state || PARSE_DIRECTIVE_POSTTAIL == intern->state) { APR_BUCKET_INSERT_BEFORE(newb, b); } else { break; /* END OF STREAM */ } } else { APR_BRIGADE_INSERT_TAIL(pass_bb, b); if (APR_BUCKET_IS_FLUSH(b)) { ctx->flush_now = 1; } b = newb; continue; } } /* enough is enough ... */ if (ctx->flush_now || intern->bytes_read > AP_MIN_BYTES_TO_WRITE) { if (!APR_BRIGADE_EMPTY(pass_bb)) { rv = ap_pass_brigade(f->next, pass_bb); if (rv != APR_SUCCESS) { apr_brigade_destroy(pass_bb); return rv; } } ctx->flush_now = 0; intern->bytes_read = 0; } /* read the current bucket data */ len = 0; if (!intern->seen_eos) { if (intern->bytes_read > 0) { rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ); if (APR_STATUS_IS_EAGAIN(rv)) { ctx->flush_now = 1; continue; } } if (!len || rv != APR_SUCCESS) { rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); } if (rv != APR_SUCCESS) { apr_brigade_destroy(pass_bb); return rv; } intern->bytes_read += len; } /* zero length bucket, fetch next one */ if (!len && !intern->seen_eos) { b = APR_BUCKET_NEXT(b); continue; } /* * it's actually a data containing bucket, start/continue parsing */ switch (intern->state) { /* no current tag; search for start sequence */ case PARSE_PRE_HEAD: index = find_start_sequence(ctx, data, len); if (index < len) { apr_bucket_split(b, index); } newb = APR_BUCKET_NEXT(b); if (ctx->flags & SSI_FLAG_PRINTING) { APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(pass_bb, b); } else { apr_bucket_delete(b); } if (index < len) { /* now delete the start_seq stuff from the remaining bucket */ if (PARSE_DIRECTIVE == intern->state) { /* full match */ apr_bucket_split(newb, intern->start_seq_pat->pattern_len); ctx->flush_now = 1; /* pass pre-tag stuff */ } b = APR_BUCKET_NEXT(newb); apr_bucket_delete(newb); } else { b = newb; } break; /* we're currently looking for the end of the start sequence */ case PARSE_HEAD: index = find_partial_start_sequence(ctx, data, len, &release); /* check if we mismatched earlier and have to release some chars */ if (release && (ctx->flags & SSI_FLAG_PRINTING)) { char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release); newb = apr_bucket_pool_create(to_release, release, ctx->pool, f->c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(pass_bb, newb); } if (index) { /* any match */ /* now delete the start_seq stuff from the remaining bucket */ if (PARSE_DIRECTIVE == intern->state) { /* final match */ apr_bucket_split(b, index); ctx->flush_now = 1; /* pass pre-tag stuff */ } newb = APR_BUCKET_NEXT(b); apr_bucket_delete(b); b = newb; } break; /* we're currently grabbing the directive name */ case PARSE_DIRECTIVE: case PARSE_DIRECTIVE_POSTNAME: case PARSE_DIRECTIVE_TAIL: case PARSE_DIRECTIVE_POSTTAIL: index = find_directive(ctx, data, len, &store, &store_len); if (index) { apr_bucket_split(b, index); newb = APR_BUCKET_NEXT(b); } if (store) { if (index) { APR_BUCKET_REMOVE(b); apr_bucket_setaside(b, r->pool); APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); b = newb; } /* time for cleanup? */ if (store != &magic) { apr_brigade_pflatten(intern->tmp_bb, store, store_len, ctx->dpool); apr_brigade_cleanup(intern->tmp_bb); } } else if (index) { apr_bucket_delete(b); b = newb; } break; /* skip WS and find out what comes next (arg or end_seq) */ case PARSE_PRE_ARG: index = find_arg_or_tail(ctx, data, len); if (index) { /* skipped whitespaces */ if (index < len) { apr_bucket_split(b, index); } newb = APR_BUCKET_NEXT(b); apr_bucket_delete(b); b = newb; } break; /* currently parsing name[=val] */ case PARSE_ARG: case PARSE_ARG_NAME: case PARSE_ARG_POSTNAME: case PARSE_ARG_EQ: case PARSE_ARG_PREVAL: case PARSE_ARG_VAL: case PARSE_ARG_VAL_ESC: case PARSE_ARG_POSTVAL: index = find_argument(ctx, data, len, &store, &store_len); if (index) { apr_bucket_split(b, index); newb = APR_BUCKET_NEXT(b); } if (store) { if (index) { APR_BUCKET_REMOVE(b); apr_bucket_setaside(b, r->pool); APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); b = newb; } /* time for cleanup? */ if (store != &magic) { apr_brigade_pflatten(intern->tmp_bb, store, store_len, ctx->dpool); apr_brigade_cleanup(intern->tmp_bb); } } else if (index) { apr_bucket_delete(b); b = newb; } break; /* try to match end_seq at current pos. */ case PARSE_TAIL: case PARSE_TAIL_SEQ: index = find_tail(ctx, data, len); switch (intern->state) { case PARSE_EXECUTE: /* full match */ apr_bucket_split(b, index); newb = APR_BUCKET_NEXT(b); apr_bucket_delete(b); b = newb; break; case PARSE_ARG: /* no match */ /* PARSE_ARG must reparse at the beginning */ APR_BRIGADE_PREPEND(bb, intern->tmp_bb); b = APR_BRIGADE_FIRST(bb); break; default: /* partial match */ newb = APR_BUCKET_NEXT(b); APR_BUCKET_REMOVE(b); apr_bucket_setaside(b, r->pool); APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); b = newb; break; } break; /* now execute the parsed directive, cleanup the space and * start again with PARSE_PRE_HEAD */ case PARSE_EXECUTE: /* if there was an error, it was already logged; just stop here */ if (intern->error) { if (ctx->flags & SSI_FLAG_PRINTING) { SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); intern->error = 0; } } else { include_handler_fn_t *handle_func; handle_func = (include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive, intern->directive_len); if (handle_func) { DEBUG_INIT(ctx, f, pass_bb); rv = handle_func(ctx, f, pass_bb); if (rv != APR_SUCCESS) { apr_brigade_destroy(pass_bb); return rv; } } else { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371) "unknown directive \"%s\" in parsed doc %s", apr_pstrmemdup(r->pool, intern->directive, intern->directive_len), r->filename); if (ctx->flags & SSI_FLAG_PRINTING) { SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); } } } /* cleanup */ apr_pool_clear(ctx->dpool); apr_brigade_cleanup(intern->tmp_bb); /* Oooof. Done here, start next round */ intern->state = PARSE_PRE_HEAD; break; } /* switch(ctx->state) */ } /* while (brigade) */ /* End of stream. Final cleanup */ if (intern->seen_eos) { if (PARSE_HEAD == intern->state) { if (ctx->flags & SSI_FLAG_PRINTING) { char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, intern->parse_pos); APR_BRIGADE_INSERT_TAIL(pass_bb, apr_bucket_pool_create(to_release, intern->parse_pos, ctx->pool, f->c->bucket_alloc)); } } else if (PARSE_PRE_HEAD != intern->state) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372) "SSI directive was not properly finished at the end " "of parsed document %s", r->filename); if (ctx->flags & SSI_FLAG_PRINTING) { SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb); } } if (!(ctx->flags & SSI_FLAG_PRINTING)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373) "missing closing endif directive in parsed document" " %s", r->filename); } /* cleanup our temporary memory */ apr_brigade_destroy(intern->tmp_bb); apr_pool_destroy(ctx->dpool); /* don't forget to finally insert the EOS bucket */ APR_BRIGADE_INSERT_TAIL(pass_bb, b); } /* if something's left over, pass it along */ if (!APR_BRIGADE_EMPTY(pass_bb)) { rv = ap_pass_brigade(f->next, pass_bb); } else { rv = APR_SUCCESS; apr_brigade_destroy(pass_bb); } return rv; } /* * +-------------------------------------------------------+ * | | * | Runtime Hooks * | | * +-------------------------------------------------------+ */ static int includes_setup(ap_filter_t *f) { include_dir_config *conf = ap_get_module_config(f->r->per_dir_config, &include_module); /* When our xbithack value isn't set to full or our platform isn't * providing group-level protection bits or our group-level bits do not * have group-execite on, we will set the no_local_copy value to 1 so * that we will not send 304s. */ if ((conf->xbithack != XBITHACK_FULL) || !(f->r->finfo.valid & APR_FINFO_GPROT) || !(f->r->finfo.protection & APR_GEXECUTE)) { f->r->no_local_copy = 1; } /* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4. * We don't know if we are going to be including a file or executing * a program - in either case a strong ETag header will likely be invalid. */ if (conf->etag <= 0) { apr_table_setn(f->r->notes, "no-etag", ""); } return OK; } static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b) { request_rec *r = f->r; request_rec *parent; include_dir_config *conf = ap_get_module_config(r->per_dir_config, &include_module); include_server_config *sconf= ap_get_module_config(r->server->module_config, &include_module); if (!(ap_allow_options(r) & OPT_INCLUDES)) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374) "mod_include: Options +Includes (or IncludesNoExec) " "wasn't set, INCLUDES filter removed: %s", r->uri); ap_remove_output_filter(f); return ap_pass_brigade(f->next, b); } if (!f->ctx) { struct ssi_internal_ctx *intern; include_ctx_t *ctx; /* create context for this filter */ f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx)); ctx->r = r; ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern)); ctx->pool = r->pool; apr_pool_create(&ctx->dpool, ctx->pool); /* runtime data */ intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc); intern->seen_eos = 0; intern->state = PARSE_PRE_HEAD; ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE); if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) { ctx->flags |= SSI_FLAG_NO_EXEC; } intern->legacy_expr = (conf->legacy_expr > 0); intern->expr_eval_ctx = NULL; intern->expr_err = NULL; intern->expr_vary_this = NULL; ctx->if_nesting_level = 0; intern->re = NULL; ctx->error_str = conf->default_error_msg ? conf->default_error_msg : DEFAULT_ERROR_MSG; ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt : DEFAULT_TIME_FORMAT; intern->start_seq = sconf->default_start_tag; intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq, strlen(intern->start_seq)); intern->end_seq = sconf->default_end_tag; intern->end_seq_len = strlen(intern->end_seq); intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo : DEFAULT_UNDEFINED_ECHO; intern->undefined_echo_len = strlen(intern->undefined_echo); } if ((parent = ap_get_module_config(r->request_config, &include_module))) { /* Kludge --- for nested includes, we want to keep the subprocess * environment of the base document (for compatibility); that means * torquing our own last_modified date as well so that the * LAST_MODIFIED variable gets reset to the proper value if the * nested document resets . */ r->subprocess_env = r->main->subprocess_env; apr_pool_join(r->main->pool, r->pool); r->finfo.mtime = r->main->finfo.mtime; } else { /* we're not a nested include, so we create an initial * environment */ ap_add_common_vars(r); ap_add_cgi_vars(r); add_include_vars(r); } /* Always unset the content-length. There is no way to know if * the content will be modified at some point by send_parsed_content. * It is very possible for us to not find any content in the first * 9k of the file, but still have to modify the content of the file. * If we are going to pass the file through send_parsed_content, then * the content-length should just be unset. */ apr_table_unset(f->r->headers_out, "Content-Length"); /* Always unset the Last-Modified field - see RFC2616 - 13.3.4. * We don't know if we are going to be including a file or executing * a program which may change the Last-Modified header or make the * content completely dynamic. Therefore, we can't support these * headers. * * Exception: XBitHack full means we *should* set the * Last-Modified field. * * SSILastModified on means we *should* set the Last-Modified field * if not present, or respect an existing value if present. */ /* Must we respect the last modified header? By default, no */ if (conf->lastmodified > 0) { /* update the last modified if we have a valid time, and only if * we don't already have a valid last modified. */ if (r->finfo.valid & APR_FINFO_MTIME && !apr_table_get(f->r->headers_out, "Last-Modified")) { ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); } } /* Assure the platform supports Group protections */ else if (((conf->xbithack == XBITHACK_FULL || (conf->xbithack == XBITHACK_UNSET && DEFAULT_XBITHACK == XBITHACK_FULL)) && (r->finfo.valid & APR_FINFO_GPROT) && (r->finfo.protection & APR_GEXECUTE))) { ap_update_mtime(r, r->finfo.mtime); ap_set_last_modified(r); } else { apr_table_unset(f->r->headers_out, "Last-Modified"); } /* add QUERY stuff to env cause it ain't yet */ if (r->args) { char *arg_copy = apr_pstrdup(r->pool, r->args); apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args); ap_unescape_url(arg_copy); apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED", ap_escape_shell_cmd(r->pool, arg_copy)); } return send_parsed_content(f, b); } static int include_fixup(request_rec *r) { if (r->handler && (strcmp(r->handler, "server-parsed") == 0)) { if (!r->content_type || !*r->content_type) { ap_set_content_type(r, "text/html"); } r->handler = "default-handler"; } else #if defined(OS2) || defined(WIN32) || defined(NETWARE) /* These OS's don't support xbithack. This is being worked on. */ { return DECLINED; } #else { include_dir_config *conf = ap_get_module_config(r->per_dir_config, &include_module); if (conf->xbithack == XBITHACK_OFF || (DEFAULT_XBITHACK == XBITHACK_OFF && conf->xbithack == XBITHACK_UNSET)) { return DECLINED; } if (!(r->finfo.protection & APR_UEXECUTE)) { return DECLINED; } if (!r->content_type || strncmp(r->content_type, "text/html", 9)) { return DECLINED; } } #endif /* We always return declined, because the default handler actually * serves the file. All we have to do is add the filter. */ ap_add_output_filter("INCLUDES", NULL, r, r->connection); return DECLINED; } /* * +-------------------------------------------------------+ * | | * | Configuration Handling * | | * +-------------------------------------------------------+ */ static void *create_includes_dir_config(apr_pool_t *p, char *dummy) { include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config)); result->xbithack = XBITHACK_UNSET; result->lastmodified = UNSET; result->etag = UNSET; result->legacy_expr = UNSET; return result; } #define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv) { include_dir_config *base = (include_dir_config *)basev, *over = (include_dir_config *)overridesv, *new = apr_palloc(p, sizeof(include_dir_config)); MERGE(base, over, new, default_error_msg, NULL); MERGE(base, over, new, default_time_fmt, NULL); MERGE(base, over, new, undefined_echo, NULL); MERGE(base, over, new, xbithack, XBITHACK_UNSET); MERGE(base, over, new, lastmodified, UNSET); MERGE(base, over, new, etag, UNSET); MERGE(base, over, new, legacy_expr, UNSET); return new; } static void *create_includes_server_config(apr_pool_t *p, server_rec *server) { include_server_config *result; result = apr_palloc(p, sizeof(include_server_config)); result->default_end_tag = DEFAULT_END_SEQUENCE; result->default_start_tag = DEFAULT_START_SEQUENCE; return result; } static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg) { include_dir_config *conf = mconfig; if (!strcasecmp(arg, "off")) { conf->xbithack = XBITHACK_OFF; } else if (!strcasecmp(arg, "on")) { conf->xbithack = XBITHACK_ON; } else if (!strcasecmp(arg, "full")) { conf->xbithack = XBITHACK_FULL; } else { return "XBitHack must be set to Off, On, or Full"; } return NULL; } static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig, const char *tag) { include_server_config *conf; const char *p = tag; /* be consistent. (See below in set_default_end_tag) */ while (*p) { if (apr_isspace(*p)) { return "SSIStartTag may not contain any whitespaces"; } ++p; } conf= ap_get_module_config(cmd->server->module_config , &include_module); conf->default_start_tag = tag; return NULL; } static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig, const char *tag) { include_server_config *conf; const char *p = tag; /* sanity check. The parser may fail otherwise */ while (*p) { if (apr_isspace(*p)) { return "SSIEndTag may not contain any whitespaces"; } ++p; } conf= ap_get_module_config(cmd->server->module_config , &include_module); conf->default_end_tag = tag; return NULL; } static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig, const char *msg) { include_dir_config *conf = mconfig; conf->undefined_echo = msg; return NULL; } static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig, const char *msg) { include_dir_config *conf = mconfig; conf->default_error_msg = msg; return NULL; } static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig, const char *fmt) { include_dir_config *conf = mconfig; conf->default_time_fmt = fmt; return NULL; } /* * +-------------------------------------------------------+ * | | * | Module Initialization and Configuration * | | * +-------------------------------------------------------+ */ static int include_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { include_handlers = apr_hash_make(p); ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler); if (ssi_pfn_register) { ssi_pfn_register("if", handle_if); ssi_pfn_register("set", handle_set); ssi_pfn_register("else", handle_else); ssi_pfn_register("elif", handle_elif); ssi_pfn_register("echo", handle_echo); ssi_pfn_register("endif", handle_endif); ssi_pfn_register("fsize", handle_fsize); ssi_pfn_register("config", handle_config); ssi_pfn_register("comment", handle_comment); ssi_pfn_register("include", handle_include); ssi_pfn_register("flastmod", handle_flastmod); ssi_pfn_register("printenv", handle_printenv); } return OK; } static const command_rec includes_cmds[] = { AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS, "Off, On, or Full"), AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL, "a string"), AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL, "a strftime(3) formatted string"), AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF, "SSI Start String Tag"), AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF, "SSI End String Tag"), AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL, "String to be displayed if an echoed variable is undefined"), AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char, (void *)APR_OFFSETOF(include_dir_config, legacy_expr), OR_LIMIT, "Whether to use the legacy expression parser compatible " "with <= 2.2.x. Limited to 'on' or 'off'"), AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char, (void *)APR_OFFSETOF(include_dir_config, lastmodified), OR_LIMIT, "Whether to set the last modified header or respect " "an existing header. Limited to 'on' or 'off'"), AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char, (void *)APR_OFFSETOF(include_dir_config, etag), OR_LIMIT, "Whether to allow the generation of ETags within the server. " "Existing ETags will be preserved. Limited to 'on' or 'off'"), {NULL} }; static void ap_register_include_handler(char *tag, include_handler_fn_t *func) { apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func); } static void register_hooks(apr_pool_t *p) { APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value); APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string); APR_REGISTER_OPTIONAL_FN(ap_register_include_handler); ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST); ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST); ap_register_output_filter("INCLUDES", includes_filter, includes_setup, AP_FTYPE_RESOURCE); } AP_DECLARE_MODULE(include) = { STANDARD20_MODULE_STUFF, create_includes_dir_config, /* dir config creater */ merge_includes_dir_config, /* dir config merger */ create_includes_server_config,/* server config */ NULL, /* merge server config */ includes_cmds, /* command apr_table_t */ register_hooks /* register hooks */ };