summaryrefslogtreecommitdiffstats
path: root/modules/filters/mod_include.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/filters/mod_include.c')
-rw-r--r--modules/filters/mod_include.c4238
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, &#00; 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 ? &current->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(&current->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 */
+};