diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/filters/mod_include.c | |
parent | Initial commit. (diff) | |
download | apache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.tar.xz apache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/filters/mod_include.c')
-rw-r--r-- | modules/filters/mod_include.c | 4238 |
1 files changed, 4238 insertions, 0 deletions
diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c new file mode 100644 index 0000000..584d8fb --- /dev/null +++ b/modules/filters/mod_include.c @@ -0,0 +1,4238 @@ +/* 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_END_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. */ + +/* Note: this function is deprecated in favour of apr_unescape_entity() in APR */ +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 = "<unknown>"; + } + } + 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; + } +} + +/* + * <!--#comment blah blah blah ... --> + */ +static apr_status_t handle_comment(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) +{ + return APR_SUCCESS; +} + +/* + * <!--#include virtual|file="..." [onerror|virtual|file="..."] ... --> + * + * 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; +} + +/* + * <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."] + * [encoding="..."] var="..." ... --> + */ +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 (!ap_cstr_casecmp(token, "none")) { + /* do nothing */ + } + else if (!ap_cstr_casecmp(token, "url")) { + char *buf = apr_pstrdup(ctx->pool, echo_text); + ap_unescape_url(buf); + echo_text = buf; + } + else if (!ap_cstr_casecmp(token, "urlencoded")) { + char *buf = apr_pstrdup(ctx->pool, echo_text); + ap_unescape_urlencoded(buf); + echo_text = buf; + } + else if (!ap_cstr_casecmp(token, "entity")) { + char *buf = apr_pstrdup(ctx->pool, echo_text); + decodehtml(buf); + echo_text = buf; + } + else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(token, "none")) { + /* do nothing */ + } + else if (!ap_cstr_casecmp(token, "url")) { + echo_text = ap_escape_uri(ctx->dpool, echo_text); + } + else if (!ap_cstr_casecmp(token, "urlencoded")) { + echo_text = ap_escape_urlencoded(ctx->dpool, echo_text); + } + else if (!ap_cstr_casecmp(token, "entity")) { + echo_text = ap_escape_html2(ctx->dpool, echo_text, 0); + } + else if (!ap_cstr_casecmp(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; +} + +/* + * <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."] + * [echomsg="..."] --> + */ +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; +} + +/* + * <!--#fsize virtual|file="..." [virtual|file="..."] ... --> + */ +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; +} + +/* + * <!--#flastmod virtual|file="..." [virtual|file="..."] ... --> + */ +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; +} + +/* + * <!--#if expr="..." --> + */ +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; +} + +/* + * <!--#elif expr="..." --> + */ +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; +} + +/* + * <!--#else --> + */ +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; +} + +/* + * <!--#endif --> + */ +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; +} + +/* + * <!--#set var="..." value="..." ... --> + */ +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 (!ap_cstr_casecmp(token, "none")) { + /* do nothing */ + } + else if (!ap_cstr_casecmp(token, "url")) { + char *buf = apr_pstrdup(ctx->pool, parsed_string); + ap_unescape_url(buf); + parsed_string = buf; + } + else if (!ap_cstr_casecmp(token, "urlencoded")) { + char *buf = apr_pstrdup(ctx->pool, parsed_string); + ap_unescape_urlencoded(buf); + parsed_string = buf; + } + else if (!ap_cstr_casecmp(token, "entity")) { + char *buf = apr_pstrdup(ctx->pool, parsed_string); + decodehtml(buf); + parsed_string = buf; + } + else if (!ap_cstr_casecmp(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 (!ap_cstr_casecmp(token, "none")) { + /* do nothing */ + } + else if (!ap_cstr_casecmp(token, "url")) { + parsed_string = ap_escape_uri(ctx->dpool, parsed_string); + } + else if (!ap_cstr_casecmp(token, "urlencoded")) { + parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string); + } + else if (!ap_cstr_casecmp(token, "entity")) { + parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0); + } + else if (!ap_cstr_casecmp(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; +} + +/* + * <!--#printenv --> + */ +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. <!--#printenv--> + */ + 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 <!--#printenv--> 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); + apr_pool_tag(ctx->dpool, "includes_dpool"); + + /* 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 <!--#config timefmt -->. + */ + 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 */ +}; |