summaryrefslogtreecommitdiffstats
path: root/server/util_expr_eval.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--server/util_expr_eval.c1822
1 files changed, 1822 insertions, 0 deletions
diff --git a/server/util_expr_eval.c b/server/util_expr_eval.c
new file mode 100644
index 0000000..0f92f41
--- /dev/null
+++ b/server/util_expr_eval.c
@@ -0,0 +1,1822 @@
+/* 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.
+ */
+
+/* _ _
+ * ap_expr_eval.c, based on ssl_expr_eval.c from mod_ssl
+ */
+
+#include "httpd.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_provider.h"
+#include "util_expr_private.h"
+#include "util_md5.h"
+
+#include "apr_lib.h"
+#include "apr_fnmatch.h"
+#include "apr_base64.h"
+#include "apr_sha1.h"
+
+#include <limits.h> /* for INT_MAX */
+
+/* we know core's module_index is 0 */
+#undef APLOG_MODULE_INDEX
+#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX
+
+APR_HOOK_STRUCT(
+ APR_HOOK_LINK(expr_lookup)
+)
+
+AP_IMPLEMENT_HOOK_RUN_FIRST(int, expr_lookup, (ap_expr_lookup_parms *parms),
+ (parms), DECLINED)
+
+#define LOG_MARK(info) __FILE__, __LINE__, (info)->module_index
+
+static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx,
+ const ap_expr_t *info,
+ const ap_expr_t *args);
+static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx,
+ unsigned int n);
+static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx,
+ ap_expr_var_func_t *func,
+ const void *data);
+
+/* define AP_EXPR_DEBUG to log the parse tree when parsing an expression */
+#ifdef AP_EXPR_DEBUG
+static void expr_dump_tree(const ap_expr_t *e, const server_rec *s,
+ int loglevel, int indent);
+#endif
+
+/*
+ * To reduce counting overhead, we only count calls to
+ * ap_expr_eval_word() and ap_expr_eval(). The max number of
+ * stack frames is larger by some factor.
+ */
+#define AP_EXPR_MAX_RECURSION 20
+static int inc_rec(ap_expr_eval_ctx_t *ctx)
+{
+ if (ctx->reclvl < AP_EXPR_MAX_RECURSION) {
+ ctx->reclvl++;
+ return 0;
+ }
+ *ctx->err = "Recursion limit reached";
+ /* short circuit further evaluation */
+ ctx->reclvl = INT_MAX;
+ return 1;
+}
+
+static const char *ap_expr_eval_word(ap_expr_eval_ctx_t *ctx,
+ const ap_expr_t *node)
+{
+ const char *result = "";
+ if (inc_rec(ctx))
+ return result;
+ switch (node->node_op) {
+ case op_Digit:
+ case op_String:
+ result = node->node_arg1;
+ break;
+ case op_Var:
+ result = ap_expr_eval_var(ctx, (ap_expr_var_func_t *)node->node_arg1,
+ node->node_arg2);
+ break;
+ case op_Concat:
+ if (((ap_expr_t *)node->node_arg2)->node_op != op_Concat &&
+ ((ap_expr_t *)node->node_arg1)->node_op != op_Concat) {
+ const char *s1 = ap_expr_eval_word(ctx, node->node_arg1);
+ const char *s2 = ap_expr_eval_word(ctx, node->node_arg2);
+ if (!*s1)
+ result = s2;
+ else if (!*s2)
+ result = s1;
+ else
+ result = apr_pstrcat(ctx->p, s1, s2, NULL);
+ }
+ else if (((ap_expr_t *)node->node_arg1)->node_op == op_Concat) {
+ const ap_expr_t *nodep = node;
+ int n;
+ int i = 1;
+ struct iovec *vec;
+ do {
+ nodep = nodep->node_arg1;
+ i++;
+ } while (nodep->node_op == op_Concat);
+ vec = apr_palloc(ctx->p, i * sizeof(struct iovec));
+ n = i;
+ nodep = node;
+ i--;
+ do {
+ vec[i].iov_base = (void *)ap_expr_eval_word(ctx,
+ nodep->node_arg2);
+ vec[i].iov_len = strlen(vec[i].iov_base);
+ i--;
+ nodep = nodep->node_arg1;
+ } while (nodep->node_op == op_Concat);
+ vec[i].iov_base = (void *)ap_expr_eval_word(ctx, nodep);
+ vec[i].iov_len = strlen(vec[i].iov_base);
+ result = apr_pstrcatv(ctx->p, vec, n, NULL);
+ }
+ else {
+ const ap_expr_t *nodep = node;
+ int i = 1;
+ struct iovec *vec;
+ do {
+ nodep = nodep->node_arg2;
+ i++;
+ } while (nodep->node_op == op_Concat);
+ vec = apr_palloc(ctx->p, i * sizeof(struct iovec));
+ nodep = node;
+ i = 0;
+ do {
+ vec[i].iov_base = (void *)ap_expr_eval_word(ctx,
+ nodep->node_arg1);
+ vec[i].iov_len = strlen(vec[i].iov_base);
+ i++;
+ nodep = nodep->node_arg2;
+ } while (nodep->node_op == op_Concat);
+ vec[i].iov_base = (void *)ap_expr_eval_word(ctx, nodep);
+ vec[i].iov_len = strlen(vec[i].iov_base);
+ i++;
+ result = apr_pstrcatv(ctx->p, vec, i, NULL);
+ }
+ break;
+ case op_StringFuncCall: {
+ const ap_expr_t *info = node->node_arg1;
+ const ap_expr_t *args = node->node_arg2;
+ result = ap_expr_eval_string_func(ctx, info, args);
+ break;
+ }
+ case op_RegexBackref: {
+ const unsigned int *np = node->node_arg1;
+ result = ap_expr_eval_re_backref(ctx, *np);
+ break;
+ }
+ default:
+ *ctx->err = "Internal evaluation error: Unknown word expression node";
+ break;
+ }
+ if (!result)
+ result = "";
+ ctx->reclvl--;
+ return result;
+}
+
+static const char *ap_expr_eval_var(ap_expr_eval_ctx_t *ctx,
+ ap_expr_var_func_t *func,
+ const void *data)
+{
+ AP_DEBUG_ASSERT(func != NULL);
+ AP_DEBUG_ASSERT(data != NULL);
+ return (*func)(ctx, data);
+}
+
+static const char *ap_expr_eval_re_backref(ap_expr_eval_ctx_t *ctx, unsigned int n)
+{
+ int len;
+
+ if (!ctx->re_pmatch || !ctx->re_source || !*ctx->re_source
+ || **ctx->re_source == '\0' || ctx->re_nmatch < n + 1)
+ return "";
+
+ len = ctx->re_pmatch[n].rm_eo - ctx->re_pmatch[n].rm_so;
+ if (len == 0)
+ return "";
+
+ return apr_pstrndup(ctx->p, *ctx->re_source + ctx->re_pmatch[n].rm_so, len);
+}
+
+static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx,
+ const ap_expr_t *info,
+ const ap_expr_t *arg)
+{
+ ap_expr_string_func_t *func = (ap_expr_string_func_t *)info->node_arg1;
+ const void *data = info->node_arg2;
+
+ AP_DEBUG_ASSERT(info->node_op == op_StringFuncInfo);
+ AP_DEBUG_ASSERT(func != NULL);
+ AP_DEBUG_ASSERT(data != NULL);
+ return (*func)(ctx, data, ap_expr_eval_word(ctx, arg));
+}
+
+static int intstrcmp(const char *s1, const char *s2)
+{
+ apr_int64_t i1 = apr_atoi64(s1);
+ apr_int64_t i2 = apr_atoi64(s2);
+
+ if (i1 < i2)
+ return -1;
+ else if (i1 == i2)
+ return 0;
+ else
+ return 1;
+}
+
+static int ap_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node)
+{
+ const ap_expr_t *e1 = node->node_arg1;
+ const ap_expr_t *e2 = node->node_arg2;
+ switch (node->node_op) {
+ case op_EQ:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0);
+ case op_NE:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0);
+ case op_LT:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0);
+ case op_LE:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0);
+ case op_GT:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0);
+ case op_GE:
+ return (intstrcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0);
+ case op_STR_EQ:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0);
+ case op_STR_NE:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0);
+ case op_STR_LT:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0);
+ case op_STR_LE:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0);
+ case op_STR_GT:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0);
+ case op_STR_GE:
+ return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0);
+ case op_IN: {
+ const char *needle = ap_expr_eval_word(ctx, e1);
+ if (e2->node_op == op_ListElement) {
+ do {
+ const ap_expr_t *val = e2->node_arg1;
+ AP_DEBUG_ASSERT(e2->node_op == op_ListElement);
+ if (strcmp(needle, ap_expr_eval_word(ctx, val)) == 0)
+ return 1;
+ e2 = e2->node_arg2;
+ } while (e2 != NULL);
+ }
+ else if (e2->node_op == op_ListFuncCall) {
+ const ap_expr_t *info = e2->node_arg1;
+ const ap_expr_t *arg = e2->node_arg2;
+ ap_expr_list_func_t *func = (ap_expr_list_func_t *)info->node_arg1;
+ apr_array_header_t *haystack;
+
+ AP_DEBUG_ASSERT(func != NULL);
+ AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo);
+ haystack = (*func)(ctx, info->node_arg2, ap_expr_eval_word(ctx, arg));
+ if (haystack == NULL) {
+ return 0;
+ }
+ if (ap_array_str_contains(haystack, needle)) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+ case op_REG:
+ case op_NRE: {
+ const char *word = ap_expr_eval_word(ctx, e1);
+ const ap_regex_t *regex = e2->node_arg1;
+ int result;
+
+ /*
+ * $0 ... $9 may contain stuff the user wants to keep. Therefore
+ * we only set them if there are capturing parens in the regex.
+ */
+ if (regex->re_nsub > 0) {
+ result = (0 == ap_regexec(regex, word, ctx->re_nmatch,
+ ctx->re_pmatch, 0));
+ *ctx->re_source = result ? word : NULL;
+ }
+ else {
+ result = (0 == ap_regexec(regex, word, 0, NULL, 0));
+ }
+
+ if (node->node_op == op_REG)
+ return result;
+ else
+ return !result;
+ }
+ default:
+ *ctx->err = "Internal evaluation error: Unknown comp expression node";
+ return -1;
+ }
+}
+
+/* combined string/int comparison for compatibility with ssl_expr */
+static int strcmplex(const char *str1, const char *str2)
+{
+ int i, n1, n2;
+
+ if (str1 == NULL)
+ return -1;
+ if (str2 == NULL)
+ return +1;
+ n1 = strlen(str1);
+ n2 = strlen(str2);
+ if (n1 > n2)
+ return 1;
+ if (n1 < n2)
+ return -1;
+ for (i = 0; i < n1; i++) {
+ if (str1[i] > str2[i])
+ return 1;
+ if (str1[i] < str2[i])
+ return -1;
+ }
+ return 0;
+}
+
+static int ssl_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node)
+{
+ const ap_expr_t *e1 = node->node_arg1;
+ const ap_expr_t *e2 = node->node_arg2;
+ switch (node->node_op) {
+ case op_EQ:
+ case op_STR_EQ:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) == 0);
+ case op_NE:
+ case op_STR_NE:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) != 0);
+ case op_LT:
+ case op_STR_LT:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) < 0);
+ case op_LE:
+ case op_STR_LE:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) <= 0);
+ case op_GT:
+ case op_STR_GT:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) > 0);
+ case op_GE:
+ case op_STR_GE:
+ return (strcmplex(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0);
+ default:
+ return ap_expr_eval_comp(ctx, node);
+ }
+}
+
+AP_DECLARE_NONSTD(int) ap_expr_lookup_default(ap_expr_lookup_parms *parms)
+{
+ return ap_run_expr_lookup(parms);
+}
+
+AP_DECLARE(const char *) ap_expr_parse(apr_pool_t *pool, apr_pool_t *ptemp,
+ ap_expr_info_t *info, const char *expr,
+ ap_expr_lookup_fn_t *lookup_fn)
+{
+ ap_expr_parse_ctx_t ctx;
+ int rc;
+
+ ctx.pool = pool;
+ ctx.ptemp = ptemp;
+ ctx.inputbuf = expr;
+ ctx.inputlen = strlen(expr);
+ ctx.inputptr = ctx.inputbuf;
+ ctx.expr = NULL;
+ ctx.error = NULL; /* generic bison error message (XXX: usually not very useful, should be axed) */
+ ctx.error2 = NULL; /* additional error message */
+ ctx.flags = info->flags;
+ ctx.scan_del = '\0';
+ ctx.scan_buf[0] = '\0';
+ ctx.scan_ptr = ctx.scan_buf;
+ ctx.lookup_fn = lookup_fn ? lookup_fn : ap_expr_lookup_default;
+ ctx.at_start = 1;
+
+ ap_expr_yylex_init(&ctx.scanner);
+ ap_expr_yyset_extra(&ctx, ctx.scanner);
+ rc = ap_expr_yyparse(&ctx);
+ ap_expr_yylex_destroy(ctx.scanner);
+ if (ctx.error) {
+ if (ctx.error2)
+ return apr_psprintf(pool, "%s: %s", ctx.error, ctx.error2);
+ else
+ return ctx.error;
+ }
+ else if (ctx.error2) {
+ return ctx.error2;
+ }
+
+ if (rc) /* XXX can this happen? */
+ return "syntax error";
+
+#ifdef AP_EXPR_DEBUG
+ if (ctx.expr)
+ expr_dump_tree(ctx.expr, NULL, APLOG_NOTICE, 2);
+#endif
+
+ info->root_node = ctx.expr;
+
+ return NULL;
+}
+
+AP_DECLARE(ap_expr_info_t*) ap_expr_parse_cmd_mi(const cmd_parms *cmd,
+ const char *expr,
+ unsigned int flags,
+ const char **err,
+ ap_expr_lookup_fn_t *lookup_fn,
+ int module_index)
+{
+ ap_expr_info_t *info = apr_pcalloc(cmd->pool, sizeof(ap_expr_info_t));
+ info->filename = cmd->directive->filename;
+ info->line_number = cmd->directive->line_num;
+ info->flags = flags;
+ info->module_index = module_index;
+ *err = ap_expr_parse(cmd->pool, cmd->temp_pool, info, expr, lookup_fn);
+
+ if (*err)
+ return NULL;
+
+ return info;
+}
+
+ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *a1, const void *a2,
+ ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+ node->node_op = op;
+ node->node_arg1 = a1;
+ node->node_arg2 = a2;
+ return node;
+}
+
+static ap_expr_t *ap_expr_info_make(int type, const char *name,
+ ap_expr_parse_ctx_t *ctx,
+ const ap_expr_t *arg)
+{
+ ap_expr_t *info = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+ ap_expr_lookup_parms parms;
+ parms.type = type;
+ parms.flags = ctx->flags;
+ parms.pool = ctx->pool;
+ parms.ptemp = ctx->ptemp;
+ parms.name = name;
+ parms.func = &info->node_arg1;
+ parms.data = &info->node_arg2;
+ parms.err = &ctx->error2;
+ parms.arg = (arg && arg->node_op == op_String) ? arg->node_arg1 : NULL;
+ if (ctx->lookup_fn(&parms) != OK)
+ return NULL;
+ return info;
+}
+
+ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg,
+ ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_STRING, name, ctx, arg);
+ if (!info)
+ return NULL;
+
+ info->node_op = op_StringFuncInfo;
+ return ap_expr_make(op_StringFuncCall, info, arg, ctx);
+}
+
+ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg,
+ ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_LIST, name, ctx, arg);
+ if (!info)
+ return NULL;
+
+ info->node_op = op_ListFuncInfo;
+ return ap_expr_make(op_ListFuncCall, info, arg, ctx);
+}
+
+ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg,
+ ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_UNARY, name, ctx, arg);
+ if (!info)
+ return NULL;
+
+ info->node_op = op_UnaryOpInfo;
+ return ap_expr_make(op_UnaryOpCall, info, arg, ctx);
+}
+
+ap_expr_t *ap_expr_binary_op_make(const char *name, const ap_expr_t *arg1,
+ const ap_expr_t *arg2, ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *args;
+ ap_expr_t *info = ap_expr_info_make(AP_EXPR_FUNC_OP_BINARY, name, ctx,
+ arg2);
+ if (!info)
+ return NULL;
+
+ info->node_op = op_BinaryOpInfo;
+ args = ap_expr_make(op_BinaryOpArgs, arg1, arg2, ctx);
+ return ap_expr_make(op_BinaryOpCall, info, args, ctx);
+}
+
+
+ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx)
+{
+ ap_expr_t *node = ap_expr_info_make(AP_EXPR_FUNC_VAR, name, ctx, NULL);
+ if (!node)
+ return NULL;
+
+ node->node_op = op_Var;
+ return node;
+}
+
+#ifdef AP_EXPR_DEBUG
+
+#define MARK APLOG_MARK,loglevel,0,s
+#define DUMP_E_E(op, e1, e2) \
+ do { ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, e1, e2); \
+ if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \
+ if (e2) expr_dump_tree(e2, s, loglevel, indent + 2); \
+ } while (0)
+#define DUMP_S_E(op, s1, e1) \
+ do { ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, e1); \
+ if (e1) expr_dump_tree(e1, s, loglevel, indent + 2); \
+ } while (0)
+#define DUMP_S_P(op, s1, p1) \
+ ap_log_error(MARK,"%*s%s: '%s' %pp", indent, " ", op, (char *)s1, p1);
+#define DUMP_P_P(op, p1, p2) \
+ ap_log_error(MARK,"%*s%s: %pp %pp", indent, " ", op, p1, p2);
+#define DUMP_S_S(op, s1, s2) \
+ ap_log_error(MARK,"%*s%s: '%s' '%s'", indent, " ", op, (char *)s1, (char *)s2)
+#define DUMP_P(op, p1) \
+ ap_log_error(MARK,"%*s%s: %pp", indent, " ", op, p1);
+#define DUMP_IP(op, p1) \
+ ap_log_error(MARK,"%*s%s: %d", indent, " ", op, *(int *)p1);
+#define DUMP_S(op, s1) \
+ ap_log_error(MARK,"%*s%s: '%s'", indent, " ", op, (char *)s1)
+
+#define CASE_OP(op) case op: name = #op ; break;
+
+static void expr_dump_tree(const ap_expr_t *e, const server_rec *s,
+ int loglevel, int indent)
+{
+ switch (e->node_op) {
+ /* no arg */
+ case op_NOP:
+ case op_True:
+ case op_False:
+ {
+ char *name;
+ switch (e->node_op) {
+ CASE_OP(op_NOP);
+ CASE_OP(op_True);
+ CASE_OP(op_False);
+ default:
+ ap_assert(0);
+ }
+ ap_log_error(MARK, "%*s%s", indent, " ", name);
+ }
+ break;
+
+ /* arg1: string, arg2: expr */
+ case op_UnaryOpCall:
+ case op_BinaryOpCall:
+ case op_BinaryOpArgs:
+ {
+ char *name;
+ switch (e->node_op) {
+ CASE_OP(op_BinaryOpCall);
+ CASE_OP(op_UnaryOpCall);
+ CASE_OP(op_BinaryOpArgs);
+ default:
+ ap_assert(0);
+ }
+ DUMP_S_E(name, e->node_arg1, e->node_arg2);
+ }
+ break;
+
+ /* arg1: expr, arg2: expr */
+ case op_Comp:
+ case op_Not:
+ case op_Or:
+ case op_And:
+ case op_EQ:
+ case op_NE:
+ case op_LT:
+ case op_LE:
+ case op_GT:
+ case op_GE:
+ case op_STR_EQ:
+ case op_STR_NE:
+ case op_STR_LT:
+ case op_STR_LE:
+ case op_STR_GT:
+ case op_STR_GE:
+ case op_IN:
+ case op_REG:
+ case op_NRE:
+ case op_Concat:
+ case op_StringFuncCall:
+ case op_ListFuncCall:
+ case op_ListElement:
+ {
+ char *name;
+ switch (e->node_op) {
+ CASE_OP(op_Comp);
+ CASE_OP(op_Not);
+ CASE_OP(op_Or);
+ CASE_OP(op_And);
+ CASE_OP(op_EQ);
+ CASE_OP(op_NE);
+ CASE_OP(op_LT);
+ CASE_OP(op_LE);
+ CASE_OP(op_GT);
+ CASE_OP(op_GE);
+ CASE_OP(op_STR_EQ);
+ CASE_OP(op_STR_NE);
+ CASE_OP(op_STR_LT);
+ CASE_OP(op_STR_LE);
+ CASE_OP(op_STR_GT);
+ CASE_OP(op_STR_GE);
+ CASE_OP(op_IN);
+ CASE_OP(op_REG);
+ CASE_OP(op_NRE);
+ CASE_OP(op_Concat);
+ CASE_OP(op_StringFuncCall);
+ CASE_OP(op_ListFuncCall);
+ CASE_OP(op_ListElement);
+ default:
+ ap_assert(0);
+ }
+ DUMP_E_E(name, e->node_arg1, e->node_arg2);
+ }
+ break;
+ /* arg1: string */
+ case op_Digit:
+ case op_String:
+ {
+ char *name;
+ switch (e->node_op) {
+ CASE_OP(op_Digit);
+ CASE_OP(op_String);
+ default:
+ ap_assert(0);
+ }
+ DUMP_S(name, e->node_arg1);
+ }
+ break;
+ /* arg1: pointer, arg2: pointer */
+ case op_Var:
+ case op_StringFuncInfo:
+ case op_UnaryOpInfo:
+ case op_BinaryOpInfo:
+ case op_ListFuncInfo:
+ {
+ char *name;
+ switch (e->node_op) {
+ CASE_OP(op_Var);
+ CASE_OP(op_StringFuncInfo);
+ CASE_OP(op_UnaryOpInfo);
+ CASE_OP(op_BinaryOpInfo);
+ CASE_OP(op_ListFuncInfo);
+ default:
+ ap_assert(0);
+ }
+ DUMP_P_P(name, e->node_arg1, e->node_arg2);
+ }
+ break;
+ /* arg1: pointer */
+ case op_Regex:
+ DUMP_P("op_Regex", e->node_arg1);
+ break;
+ /* arg1: pointer to int */
+ case op_RegexBackref:
+ DUMP_IP("op_RegexBackref", e->node_arg1);
+ break;
+ default:
+ ap_log_error(MARK, "%*sERROR: INVALID OP %d", indent, " ", e->node_op);
+ break;
+ }
+}
+#endif /* AP_EXPR_DEBUG */
+
+static int ap_expr_eval_unary_op(ap_expr_eval_ctx_t *ctx, const ap_expr_t *info,
+ const ap_expr_t *arg)
+{
+ ap_expr_op_unary_t *op_func = (ap_expr_op_unary_t *)info->node_arg1;
+ const void *data = info->node_arg2;
+
+ AP_DEBUG_ASSERT(info->node_op == op_UnaryOpInfo);
+ AP_DEBUG_ASSERT(op_func != NULL);
+ AP_DEBUG_ASSERT(data != NULL);
+ return (*op_func)(ctx, data, ap_expr_eval_word(ctx, arg));
+}
+
+static int ap_expr_eval_binary_op(ap_expr_eval_ctx_t *ctx,
+ const ap_expr_t *info,
+ const ap_expr_t *args)
+{
+ ap_expr_op_binary_t *op_func = (ap_expr_op_binary_t *)info->node_arg1;
+ const void *data = info->node_arg2;
+ const ap_expr_t *a1 = args->node_arg1;
+ const ap_expr_t *a2 = args->node_arg2;
+
+ AP_DEBUG_ASSERT(info->node_op == op_BinaryOpInfo);
+ AP_DEBUG_ASSERT(args->node_op == op_BinaryOpArgs);
+ AP_DEBUG_ASSERT(op_func != NULL);
+ AP_DEBUG_ASSERT(data != NULL);
+ return (*op_func)(ctx, data, ap_expr_eval_word(ctx, a1),
+ ap_expr_eval_word(ctx, a2));
+}
+
+
+static int ap_expr_eval(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node)
+{
+ const ap_expr_t *e1 = node->node_arg1;
+ const ap_expr_t *e2 = node->node_arg2;
+ int result = FALSE;
+ if (inc_rec(ctx))
+ return result;
+ while (1) {
+ switch (node->node_op) {
+ case op_True:
+ result ^= TRUE;
+ goto out;
+ case op_False:
+ result ^= FALSE;
+ goto out;
+ case op_Not:
+ result = !result;
+ node = e1;
+ break;
+ case op_Or:
+ do {
+ if (e1->node_op == op_Not) {
+ if (!ap_expr_eval(ctx, e1->node_arg1)) {
+ result ^= TRUE;
+ goto out;
+ }
+ }
+ else {
+ if (ap_expr_eval(ctx, e1)) {
+ result ^= TRUE;
+ goto out;
+ }
+ }
+ node = node->node_arg2;
+ e1 = node->node_arg1;
+ } while (node->node_op == op_Or);
+ break;
+ case op_And:
+ do {
+ if (e1->node_op == op_Not) {
+ if (ap_expr_eval(ctx, e1->node_arg1)) {
+ result ^= FALSE;
+ goto out;
+ }
+ }
+ else {
+ if (!ap_expr_eval(ctx, e1)) {
+ result ^= FALSE;
+ goto out;
+ }
+ }
+ node = node->node_arg2;
+ e1 = node->node_arg1;
+ } while (node->node_op == op_And);
+ break;
+ case op_UnaryOpCall:
+ result ^= ap_expr_eval_unary_op(ctx, e1, e2);
+ goto out;
+ case op_BinaryOpCall:
+ result ^= ap_expr_eval_binary_op(ctx, e1, e2);
+ goto out;
+ case op_Comp:
+ if (ctx->info->flags & AP_EXPR_FLAG_SSL_EXPR_COMPAT)
+ result ^= ssl_expr_eval_comp(ctx, e1);
+ else
+ result ^= ap_expr_eval_comp(ctx, e1);
+ goto out;
+ default:
+ *ctx->err = "Internal evaluation error: Unknown expression node";
+ goto out;
+ }
+ e1 = node->node_arg1;
+ e2 = node->node_arg2;
+ }
+out:
+ ctx->reclvl--;
+ return result;
+}
+
+AP_DECLARE(int) ap_expr_exec(request_rec *r, const ap_expr_info_t *info,
+ const char **err)
+{
+ return ap_expr_exec_re(r, info, 0, NULL, NULL, err);
+}
+
+AP_DECLARE(int) ap_expr_exec_ctx(ap_expr_eval_ctx_t *ctx)
+{
+ int rc;
+
+ AP_DEBUG_ASSERT(ctx->p != NULL);
+ /* XXX: allow r, c == NULL */
+ AP_DEBUG_ASSERT(ctx->r != NULL);
+ AP_DEBUG_ASSERT(ctx->c != NULL);
+ AP_DEBUG_ASSERT(ctx->s != NULL);
+ AP_DEBUG_ASSERT(ctx->err != NULL);
+ AP_DEBUG_ASSERT(ctx->info != NULL);
+ if (ctx->re_pmatch) {
+ AP_DEBUG_ASSERT(ctx->re_source != NULL);
+ AP_DEBUG_ASSERT(ctx->re_nmatch > 0);
+ }
+ ctx->reclvl = 0;
+
+ *ctx->err = NULL;
+ if (ctx->info->flags & AP_EXPR_FLAG_STRING_RESULT) {
+ *ctx->result_string = ap_expr_eval_word(ctx, ctx->info->root_node);
+ if (*ctx->err != NULL) {
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r,
+ "Evaluation of expression from %s:%d failed: %s",
+ ctx->info->filename, ctx->info->line_number, *ctx->err);
+ return -1;
+ } else {
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r,
+ "Evaluation of string expression from %s:%d gave: %s",
+ ctx->info->filename, ctx->info->line_number,
+ *ctx->result_string);
+ return 1;
+ }
+ }
+ else {
+ rc = ap_expr_eval(ctx, ctx->info->root_node);
+ if (*ctx->err != NULL) {
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_ERR, 0, ctx->r,
+ "Evaluation of expression from %s:%d failed: %s",
+ ctx->info->filename, ctx->info->line_number, *ctx->err);
+ return -1;
+ } else {
+ rc = rc ? 1 : 0;
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE4, 0, ctx->r,
+ "Evaluation of expression from %s:%d gave: %d",
+ ctx->info->filename, ctx->info->line_number, rc);
+
+ if (ctx->vary_this && *ctx->vary_this)
+ apr_table_merge(ctx->r->headers_out, "Vary", *ctx->vary_this);
+
+ return rc;
+ }
+ }
+}
+
+AP_DECLARE(int) ap_expr_exec_re(request_rec *r, const ap_expr_info_t *info,
+ apr_size_t nmatch, ap_regmatch_t *pmatch,
+ const char **source, const char **err)
+{
+ ap_expr_eval_ctx_t ctx;
+ int dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY);
+ const char *tmp_source = NULL, *vary_this = NULL;
+ ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH];
+
+ AP_DEBUG_ASSERT((info->flags & AP_EXPR_FLAG_STRING_RESULT) == 0);
+
+ ctx.r = r;
+ ctx.c = r->connection;
+ ctx.s = r->server;
+ ctx.p = r->pool;
+ ctx.err = err;
+ ctx.info = info;
+ ctx.re_nmatch = nmatch;
+ ctx.re_pmatch = pmatch;
+ ctx.re_source = source;
+ ctx.vary_this = dont_vary ? NULL : &vary_this;
+ ctx.data = NULL;
+
+ if (!pmatch) {
+ ctx.re_nmatch = AP_MAX_REG_MATCH;
+ ctx.re_pmatch = tmp_pmatch;
+ ctx.re_source = &tmp_source;
+ }
+
+ return ap_expr_exec_ctx(&ctx);
+}
+
+AP_DECLARE(const char *) ap_expr_str_exec_re(request_rec *r,
+ const ap_expr_info_t *info,
+ apr_size_t nmatch,
+ ap_regmatch_t *pmatch,
+ const char **source,
+ const char **err)
+{
+ ap_expr_eval_ctx_t ctx;
+ int dont_vary, rc;
+ const char *tmp_source, *vary_this;
+ ap_regmatch_t tmp_pmatch[AP_MAX_REG_MATCH];
+ const char *result;
+
+ AP_DEBUG_ASSERT(info->flags & AP_EXPR_FLAG_STRING_RESULT);
+
+ if (info->root_node->node_op == op_String) {
+ /* short-cut for constant strings */
+ *err = NULL;
+ return (const char *)info->root_node->node_arg1;
+ }
+
+ tmp_source = NULL;
+ vary_this = NULL;
+
+ dont_vary = (info->flags & AP_EXPR_FLAG_DONT_VARY);
+
+ ctx.r = r;
+ ctx.c = r->connection;
+ ctx.s = r->server;
+ ctx.p = r->pool;
+ ctx.err = err;
+ ctx.info = info;
+ ctx.re_nmatch = nmatch;
+ ctx.re_pmatch = pmatch;
+ ctx.re_source = source;
+ ctx.vary_this = dont_vary ? NULL : &vary_this;
+ ctx.data = NULL;
+ ctx.result_string = &result;
+
+ if (!pmatch) {
+ ctx.re_nmatch = AP_MAX_REG_MATCH;
+ ctx.re_pmatch = tmp_pmatch;
+ ctx.re_source = &tmp_source;
+ }
+
+ rc = ap_expr_exec_ctx(&ctx);
+ if (rc > 0)
+ return result;
+ else if (rc < 0)
+ return NULL;
+ else
+ ap_assert(0);
+ /* Not reached */
+ return NULL;
+}
+
+AP_DECLARE(const char *) ap_expr_str_exec(request_rec *r,
+ const ap_expr_info_t *info,
+ const char **err)
+{
+ return ap_expr_str_exec_re(r, info, 0, NULL, NULL, err);
+}
+
+
+static void add_vary(ap_expr_eval_ctx_t *ctx, const char *name)
+{
+ if (!ctx->vary_this)
+ return;
+
+ if (*ctx->vary_this) {
+ *ctx->vary_this = apr_pstrcat(ctx->p, *ctx->vary_this, ", ", name,
+ NULL);
+ }
+ else {
+ *ctx->vary_this = name;
+ }
+}
+
+static const char *req_table_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ const char *name = (const char *)data;
+ apr_table_t *t;
+ if (!ctx->r)
+ return "";
+
+ if (name[2] == 's') { /* resp */
+ /* Try r->headers_out first, fall back on err_headers_out. */
+ const char *v = apr_table_get(ctx->r->headers_out, arg);
+ if (v) {
+ return v;
+ }
+ t = ctx->r->err_headers_out;
+ }
+ else if (name[0] == 'n') /* notes */
+ t = ctx->r->notes;
+ else if (name[3] == 'e') /* reqenv */
+ t = ctx->r->subprocess_env;
+ else if (name[3] == '_') /* req_novary */
+ t = ctx->r->headers_in;
+ else { /* req, http */
+ t = ctx->r->headers_in;
+ /* Skip the 'Vary: Host' header combination
+ * as indicated in rfc7231 section-7.1.4
+ */
+ if (strcasecmp(arg, "Host")){
+ add_vary(ctx, arg);
+ }
+ }
+ return apr_table_get(t, arg);
+}
+
+static const char *env_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ const char *res;
+ /* this order is for ssl_expr compatibility */
+ if (ctx->r) {
+ if ((res = apr_table_get(ctx->r->notes, arg)) != NULL)
+ return res;
+ else if ((res = apr_table_get(ctx->r->subprocess_env, arg)) != NULL)
+ return res;
+ }
+ return getenv(arg);
+}
+
+static const char *osenv_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return getenv(arg);
+}
+
+static const char *tolower_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ char *result = apr_pstrdup(ctx->p, arg);
+ ap_str_tolower(result);
+ return result;
+}
+
+static const char *toupper_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ char *result = apr_pstrdup(ctx->p, arg);
+ ap_str_toupper(result);
+ return result;
+}
+
+static const char *escape_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return ap_escape_uri(ctx->p, arg);
+}
+
+static const char *base64_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return ap_pbase64encode(ctx->p, (char *)arg);
+}
+
+static const char *unbase64_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return ap_pbase64decode(ctx->p, arg);
+}
+
+static const char *sha1_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ apr_sha1_ctx_t context;
+ apr_byte_t sha1[APR_SHA1_DIGESTSIZE];
+ char *out;
+
+ out = apr_palloc(ctx->p, APR_SHA1_DIGESTSIZE*2+1);
+
+ apr_sha1_init(&context);
+ apr_sha1_update(&context, arg, strlen(arg));
+ apr_sha1_final(sha1, &context);
+
+ ap_bin2hex(sha1, APR_SHA1_DIGESTSIZE, out);
+
+ return out;
+}
+
+static const char *md5_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ return ap_md5(ctx->p, (const unsigned char *)arg);
+}
+
+
+#define MAX_FILE_SIZE 10*1024*1024
+static const char *file_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ char *arg)
+{
+ apr_file_t *fp;
+ char *buf;
+ apr_off_t offset;
+ apr_size_t len;
+ apr_finfo_t finfo;
+
+ if (apr_file_open(&fp, arg, APR_READ|APR_BUFFERED,
+ APR_OS_DEFAULT, ctx->p) != APR_SUCCESS) {
+ *ctx->err = apr_psprintf(ctx->p, "Cannot open file %s", arg);
+ return "";
+ }
+ apr_file_info_get(&finfo, APR_FINFO_SIZE, fp);
+ if (finfo.size > MAX_FILE_SIZE) {
+ *ctx->err = apr_psprintf(ctx->p, "File %s too large", arg);
+ apr_file_close(fp);
+ return "";
+ }
+ len = (apr_size_t)finfo.size;
+ if (len == 0) {
+ apr_file_close(fp);
+ return "";
+ }
+ else {
+ if ((buf = (char *)apr_palloc(ctx->p, sizeof(char)*(len+1))) == NULL) {
+ *ctx->err = "Cannot allocate memory";
+ apr_file_close(fp);
+ return "";
+ }
+ offset = 0;
+ apr_file_seek(fp, APR_SET, &offset);
+ if (apr_file_read(fp, buf, &len) != APR_SUCCESS) {
+ *ctx->err = apr_psprintf(ctx->p, "Cannot read from file %s", arg);
+ apr_file_close(fp);
+ return "";
+ }
+ buf[len] = '\0';
+ }
+ apr_file_close(fp);
+ return buf;
+}
+
+static const char *filesize_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ char *arg)
+{
+ apr_finfo_t sb;
+ if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) == APR_SUCCESS
+ && sb.filetype == APR_REG && sb.size > 0)
+ return apr_psprintf(ctx->p, "%" APR_OFF_T_FMT, sb.size);
+ else
+ return "0";
+}
+
+static const char *unescape_func(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg)
+{
+ char *result = apr_pstrdup(ctx->p, arg);
+ int ret = ap_unescape_url_keep2f(result, 0);
+ if (ret == OK)
+ return result;
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_DEBUG, 0, ctx->r, APLOGNO(00538)
+ "%s %% escape in unescape('%s') at %s:%d",
+ ret == HTTP_BAD_REQUEST ? "Bad" : "Forbidden", arg,
+ ctx->info->filename, ctx->info->line_number);
+ return "";
+}
+
+static int op_nz(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ const char *name = (const char *)data;
+ if (name[0] == 'z')
+ return (arg[0] == '\0');
+ else
+ return (arg[0] != '\0');
+}
+
+static int op_file_min(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ apr_finfo_t sb;
+ const char *name = (const char *)data;
+ if (apr_stat(&sb, arg, APR_FINFO_MIN, ctx->p) != APR_SUCCESS)
+ return FALSE;
+ switch (name[0]) {
+ case 'd':
+ return (sb.filetype == APR_DIR);
+ case 'e':
+ return TRUE;
+ case 'f':
+ return (sb.filetype == APR_REG);
+ case 's':
+ return (sb.filetype == APR_REG && sb.size > 0);
+ default:
+ ap_assert(0);
+ }
+ return FALSE;
+}
+
+static int op_file_link(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+#if !defined(OS2)
+ apr_finfo_t sb;
+ if (apr_stat(&sb, arg, APR_FINFO_MIN | APR_FINFO_LINK, ctx->p) == APR_SUCCESS
+ && sb.filetype == APR_LNK) {
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+static int op_file_xbit(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ apr_finfo_t sb;
+ if (apr_stat(&sb, arg, APR_FINFO_PROT| APR_FINFO_LINK, ctx->p) == APR_SUCCESS
+ && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int op_url_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ int rc = FALSE;
+ request_rec *rsub, *r = ctx->r;
+ if (!r)
+ return FALSE;
+ /* avoid some infinite recursions */
+ if (r->main && r->main->uri && r->uri && strcmp(r->main->uri, r->uri) == 0)
+ return FALSE;
+
+ rsub = ap_sub_req_lookup_uri(arg, r, NULL);
+ if (rsub->status < 400) {
+ rc = TRUE;
+ }
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r,
+ "Subrequest for -U %s at %s:%d gave status: %d",
+ arg, ctx->info->filename, ctx->info->line_number,
+ rsub->status);
+ ap_destroy_sub_req(rsub);
+ return rc;
+}
+
+static int op_file_subr(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ int rc = FALSE;
+ apr_finfo_t sb;
+ request_rec *rsub, *r = ctx->r;
+ if (!r)
+ return FALSE;
+ rsub = ap_sub_req_lookup_file(arg, r, NULL);
+ if (rsub->status < 300 &&
+ /* double-check that file exists since default result is 200 */
+ apr_stat(&sb, rsub->filename, APR_FINFO_MIN, ctx->p) == APR_SUCCESS) {
+ rc = TRUE;
+ }
+ ap_log_rerror(LOG_MARK(ctx->info), APLOG_TRACE5, 0, r,
+ "Subrequest for -F %s at %s:%d gave status: %d",
+ arg, ctx->info->filename, ctx->info->line_number,
+ rsub->status);
+ ap_destroy_sub_req(rsub);
+ return rc;
+}
+
+
+APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *is_https = NULL;
+
+APR_DECLARE_OPTIONAL_FN(int, http2_is_h2, (conn_rec *));
+static APR_OPTIONAL_FN_TYPE(http2_is_h2) *is_http2 = NULL;
+
+static const char *conn_var_names[] = {
+ "HTTPS", /* 0 */
+ "IPV6", /* 1 */
+ "CONN_LOG_ID", /* 2 */
+ "CONN_REMOTE_ADDR", /* 3 */
+ "HTTP2", /* 4 */
+ NULL
+};
+
+static const char *conn_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ int index = ((const char **)data - conn_var_names);
+ conn_rec *c = ctx->c;
+ if (!c)
+ return "";
+
+ switch (index) {
+ case 0:
+ if (is_https && is_https(c))
+ return "on";
+ else
+ return "off";
+ case 1:
+#if APR_HAVE_IPV6
+ {
+ apr_sockaddr_t *addr = c->client_addr;
+ if (addr->family == AF_INET6
+ && !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr))
+ return "on";
+ else
+ return "off";
+ }
+#else
+ return "off";
+#endif
+ case 2:
+ return c->log_id;
+ case 3:
+ return c->client_ip;
+ case 4:
+ if (is_http2 && is_http2(c))
+ return "on";
+ else
+ return "off";
+ default:
+ ap_assert(0);
+ return NULL;
+ }
+}
+
+static const char *request_var_names[] = {
+ "REQUEST_METHOD", /* 0 */
+ "REQUEST_SCHEME", /* 1 */
+ "REQUEST_URI", /* 2 */
+ "REQUEST_FILENAME", /* 3 */
+ "REMOTE_HOST", /* 4 */
+ "REMOTE_IDENT", /* 5 */
+ "REMOTE_USER", /* 6 */
+ "SERVER_ADMIN", /* 7 */
+ "SERVER_NAME", /* 8 */
+ "SERVER_PORT", /* 9 */
+ "SERVER_PROTOCOL", /* 10 */
+ "SCRIPT_FILENAME", /* 11 */
+ "PATH_INFO", /* 12 */
+ "QUERY_STRING", /* 13 */
+ "IS_SUBREQ", /* 14 */
+ "DOCUMENT_ROOT", /* 15 */
+ "AUTH_TYPE", /* 16 */
+ "THE_REQUEST", /* 17 */
+ "CONTENT_TYPE", /* 18 */
+ "HANDLER", /* 19 */
+ "REQUEST_LOG_ID", /* 20 */
+ "SCRIPT_USER", /* 21 */
+ "SCRIPT_GROUP", /* 22 */
+ "DOCUMENT_URI", /* 23 */
+ "LAST_MODIFIED", /* 24 */
+ "CONTEXT_PREFIX", /* 25 */
+ "CONTEXT_DOCUMENT_ROOT", /* 26 */
+ "REQUEST_STATUS", /* 27 */
+ "REMOTE_ADDR", /* 28 */
+ "REMOTE_PORT", /* 29 */
+ NULL
+};
+
+static const char *request_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ int index = ((const char **)data - request_var_names);
+ request_rec *r = ctx->r;
+ if (!r)
+ return "";
+
+ switch (index) {
+ case 0:
+ return r->method;
+ case 1:
+ return ap_http_scheme(r);
+ case 2:
+ return r->uri;
+ case 3:
+ return r->filename;
+ case 4:
+ return ap_get_useragent_host(r, REMOTE_NAME, NULL);
+ case 5:
+ return ap_get_remote_logname(r);
+ case 6:
+ return r->user;
+ case 7:
+ return r->server->server_admin;
+ case 8:
+ return ap_get_server_name_for_url(r);
+ case 9:
+ return apr_psprintf(ctx->p, "%u", ap_get_server_port(r));
+ case 10:
+ return r->protocol;
+ case 11:
+ return r->filename;
+ case 12:
+ return r->path_info;
+ case 13:
+ return r->args;
+ case 14:
+ return (r->main != NULL ? "true" : "false");
+ case 15:
+ return ap_document_root(r);
+ case 16:
+ return r->ap_auth_type;
+ case 17:
+ return r->the_request;
+ case 18:
+ return r->content_type;
+ case 19:
+ return r->handler;
+ case 20:
+ return r->log_id;
+ case 21:
+ {
+ char *result = "";
+ if (r->finfo.valid & APR_FINFO_USER)
+ apr_uid_name_get(&result, r->finfo.user, ctx->p);
+ return result;
+ }
+ case 22:
+ {
+ char *result = "";
+ if (r->finfo.valid & APR_FINFO_USER)
+ apr_gid_name_get(&result, r->finfo.group, ctx->p);
+ return result;
+ }
+ case 23:
+ {
+ const char *uri = apr_table_get(r->subprocess_env, "DOCUMENT_URI");
+ return uri ? uri : r->uri;
+ }
+ case 24:
+ {
+ apr_time_exp_t tm;
+ apr_time_exp_lt(&tm, r->mtime);
+ return apr_psprintf(ctx->p, "%02d%02d%02d%02d%02d%02d%02d",
+ (tm.tm_year / 100) + 19, (tm.tm_year % 100),
+ tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec);
+ }
+ case 25:
+ return ap_context_prefix(r);
+ case 26:
+ return ap_context_document_root(r);
+ case 27:
+ return r->status ? apr_psprintf(ctx->p, "%d", r->status) : "";
+ case 28:
+ return r->useragent_ip;
+ case 29:
+ return apr_psprintf(ctx->p, "%u", ctx->c->client_addr->port);
+ default:
+ ap_assert(0);
+ return NULL;
+ }
+}
+
+static const char *req_header_var_names[] = {
+ "HTTP_USER_AGENT", /* 0 */
+ "HTTP_PROXY_CONNECTION", /* 1 */
+ "HTTP_REFERER", /* 2 */
+ "HTTP_COOKIE", /* 3 */
+ "HTTP_FORWARDED", /* 4 */
+ "HTTP_HOST", /* 5 */
+ "HTTP_ACCEPT", /* 6 */
+ NULL
+};
+
+static const char *req_header_header_names[] = {
+ "User-Agent",
+ "Proxy-Connection",
+ "Referer",
+ "Cookie",
+ "Forwarded",
+ "Host",
+ "Accept"
+};
+
+static const char *req_header_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ const char **varname = (const char **)data;
+ int index = (varname - req_header_var_names);
+ const char *name;
+
+ AP_DEBUG_ASSERT(index < 7);
+ if (!ctx->r)
+ return "";
+
+ name = req_header_header_names[index];
+ /* Skip the 'Vary: Host' header combination
+ * as indicated in rfc7231 section-7.1.4
+ */
+ if (strcasecmp(name, "Host")){
+ add_vary(ctx, name);
+ }
+ return apr_table_get(ctx->r->headers_in, name);
+}
+
+static const char *misc_var_names[] = {
+ "TIME_YEAR", /* 0 */
+ "TIME_MON", /* 1 */
+ "TIME_DAY", /* 2 */
+ "TIME_HOUR", /* 3 */
+ "TIME_MIN", /* 4 */
+ "TIME_SEC", /* 5 */
+ "TIME_WDAY", /* 6 */
+ "TIME", /* 7 */
+ "SERVER_SOFTWARE", /* 8 */
+ "API_VERSION", /* 9 */
+ NULL
+};
+
+static const char *misc_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ apr_time_exp_t tm;
+ int index = ((const char **)data - misc_var_names);
+ apr_time_exp_lt(&tm, apr_time_now());
+
+ switch (index) {
+ case 0:
+ return apr_psprintf(ctx->p, "%02d%02d", (tm.tm_year / 100) + 19,
+ tm.tm_year % 100);
+ case 1:
+ return apr_psprintf(ctx->p, "%02d", tm.tm_mon+1);
+ case 2:
+ return apr_psprintf(ctx->p, "%02d", tm.tm_mday);
+ case 3:
+ return apr_psprintf(ctx->p, "%02d", tm.tm_hour);
+ case 4:
+ return apr_psprintf(ctx->p, "%02d", tm.tm_min);
+ case 5:
+ return apr_psprintf(ctx->p, "%02d", tm.tm_sec);
+ case 6:
+ return apr_psprintf(ctx->p, "%d", tm.tm_wday);
+ case 7:
+ return apr_psprintf(ctx->p, "%02d%02d%02d%02d%02d%02d%02d",
+ (tm.tm_year / 100) + 19, (tm.tm_year % 100),
+ tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min,
+ tm.tm_sec);
+ case 8:
+ return ap_get_server_banner();
+ case 9:
+ return apr_itoa(ctx->p, MODULE_MAGIC_NUMBER_MAJOR);
+ default:
+ ap_assert(0);
+ }
+
+ return NULL;
+}
+
+static int subnet_parse_arg(ap_expr_lookup_parms *parms)
+{
+ apr_ipsubnet_t *subnet;
+ const char *addr = parms->arg;
+ const char *mask;
+ apr_status_t ret;
+
+ if (!parms->arg) {
+ *parms->err = apr_psprintf(parms->ptemp,
+ "-%s requires subnet/netmask as constant argument",
+ parms->name);
+ return !OK;
+ }
+
+ mask = ap_strchr_c(addr, '/');
+ if (mask) {
+ addr = apr_pstrmemdup(parms->ptemp, addr, mask - addr);
+ mask++;
+ }
+
+ ret = apr_ipsubnet_create(&subnet, addr, mask, parms->pool);
+ if (ret != APR_SUCCESS) {
+ *parms->err = "parsing of subnet/netmask failed";
+ return !OK;
+ }
+
+ *parms->data = subnet;
+ return OK;
+}
+
+static int op_ipmatch(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1,
+ const char *arg2)
+{
+ apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data;
+ apr_sockaddr_t *saddr;
+
+ AP_DEBUG_ASSERT(subnet != NULL);
+
+ /* maybe log an error if this goes wrong? */
+ if (apr_sockaddr_info_get(&saddr, arg1, APR_UNSPEC, 0, 0, ctx->p) != APR_SUCCESS)
+ return FALSE;
+
+ return apr_ipsubnet_test(subnet, saddr);
+}
+
+static int op_R(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg1)
+{
+ apr_ipsubnet_t *subnet = (apr_ipsubnet_t *)data;
+
+ AP_DEBUG_ASSERT(subnet != NULL);
+
+ if (!ctx->r)
+ return FALSE;
+
+ return apr_ipsubnet_test(subnet, ctx->r->useragent_addr);
+}
+
+static int op_T(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg)
+{
+ switch (arg[0]) {
+ case '\0':
+ return FALSE;
+ case 'o':
+ case 'O':
+ return strcasecmp(arg, "off") == 0 ? FALSE : TRUE;
+ case 'n':
+ case 'N':
+ return strcasecmp(arg, "no") == 0 ? FALSE : TRUE;
+ case 'f':
+ case 'F':
+ return strcasecmp(arg, "false") == 0 ? FALSE : TRUE;
+ case '0':
+ return arg[1] == '\0' ? FALSE : TRUE;
+ default:
+ return TRUE;
+ }
+}
+
+static int op_fnmatch(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg1, const char *arg2)
+{
+ return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_PATHNAME));
+}
+
+static int op_strmatch(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg1, const char *arg2)
+{
+ return (APR_SUCCESS == apr_fnmatch(arg2, arg1, 0));
+}
+
+static int op_strcmatch(ap_expr_eval_ctx_t *ctx, const void *data,
+ const char *arg1, const char *arg2)
+{
+ return (APR_SUCCESS == apr_fnmatch(arg2, arg1, APR_FNM_CASE_BLIND));
+}
+
+struct expr_provider_single {
+ const void *func;
+ const char *name;
+ ap_expr_lookup_fn_t *arg_parsing_func;
+ int restricted;
+};
+
+struct expr_provider_multi {
+ const void *func;
+ const char **names;
+};
+
+static const struct expr_provider_multi var_providers[] = {
+ { misc_var_fn, misc_var_names },
+ { req_header_var_fn, req_header_var_names },
+ { request_var_fn, request_var_names },
+ { conn_var_fn, conn_var_names },
+ { NULL, NULL }
+};
+
+static const struct expr_provider_single string_func_providers[] = {
+ { osenv_func, "osenv", NULL, 0 },
+ { env_func, "env", NULL, 0 },
+ { req_table_func, "resp", NULL, 0 },
+ { req_table_func, "req", NULL, 0 },
+ /* 'http' as alias for 'req' for compatibility with ssl_expr */
+ { req_table_func, "http", NULL, 0 },
+ { req_table_func, "note", NULL, 0 },
+ { req_table_func, "reqenv", NULL, 0 },
+ { req_table_func, "req_novary", NULL, 0 },
+ { tolower_func, "tolower", NULL, 0 },
+ { toupper_func, "toupper", NULL, 0 },
+ { escape_func, "escape", NULL, 0 },
+ { unescape_func, "unescape", NULL, 0 },
+ { file_func, "file", NULL, 1 },
+ { filesize_func, "filesize", NULL, 1 },
+ { base64_func, "base64", NULL, 0 },
+ { unbase64_func, "unbase64", NULL, 0 },
+ { sha1_func, "sha1", NULL, 0 },
+ { md5_func, "md5", NULL, 0 },
+ { NULL, NULL, NULL}
+};
+
+static const struct expr_provider_single unary_op_providers[] = {
+ { op_nz, "n", NULL, 0 },
+ { op_nz, "z", NULL, 0 },
+ { op_R, "R", subnet_parse_arg, 0 },
+ { op_T, "T", NULL, 0 },
+ { op_file_min, "d", NULL, 1 },
+ { op_file_min, "e", NULL, 1 },
+ { op_file_min, "f", NULL, 1 },
+ { op_file_min, "s", NULL, 1 },
+ { op_file_link, "L", NULL, 1 },
+ { op_file_link, "h", NULL, 1 },
+ { op_file_xbit, "x", NULL, 1 },
+ { op_file_subr, "F", NULL, 0 },
+ { op_url_subr, "U", NULL, 0 },
+ { op_url_subr, "A", NULL, 0 },
+ { NULL, NULL, NULL }
+};
+
+static const struct expr_provider_single binary_op_providers[] = {
+ { op_ipmatch, "ipmatch", subnet_parse_arg, 0 },
+ { op_fnmatch, "fnmatch", NULL, 0 },
+ { op_strmatch, "strmatch", NULL, 0 },
+ { op_strcmatch, "strcmatch", NULL, 0 },
+ { NULL, NULL, NULL }
+};
+
+static int core_expr_lookup(ap_expr_lookup_parms *parms)
+{
+ switch (parms->type) {
+ case AP_EXPR_FUNC_VAR: {
+ const struct expr_provider_multi *prov = var_providers;
+ while (prov->func) {
+ const char **name = prov->names;
+ while (*name) {
+ if (strcasecmp(*name, parms->name) == 0) {
+ *parms->func = prov->func;
+ *parms->data = name;
+ return OK;
+ }
+ name++;
+ }
+ prov++;
+ }
+ }
+ break;
+ case AP_EXPR_FUNC_STRING:
+ case AP_EXPR_FUNC_OP_UNARY:
+ case AP_EXPR_FUNC_OP_BINARY: {
+ const struct expr_provider_single *prov = NULL;
+ switch (parms->type) {
+ case AP_EXPR_FUNC_STRING:
+ prov = string_func_providers;
+ break;
+ case AP_EXPR_FUNC_OP_UNARY:
+ prov = unary_op_providers;
+ break;
+ case AP_EXPR_FUNC_OP_BINARY:
+ prov = binary_op_providers;
+ break;
+ default:
+ ap_assert(0);
+ }
+ while (prov && prov->func) {
+ int match;
+ if (parms->type == AP_EXPR_FUNC_OP_UNARY)
+ match = !strcmp(prov->name, parms->name);
+ else
+ match = !strcasecmp(prov->name, parms->name);
+ if (match) {
+ if ((parms->flags & AP_EXPR_FLAG_RESTRICTED)
+ && prov->restricted) {
+ *parms->err =
+ apr_psprintf(parms->ptemp,
+ "%s%s not available in restricted context",
+ (parms->type == AP_EXPR_FUNC_STRING) ? "" : "-",
+ prov->name);
+ return !OK;
+ }
+ *parms->func = prov->func;
+ if (prov->arg_parsing_func) {
+ return prov->arg_parsing_func(parms);
+ }
+ else {
+ *parms->data = prov->name;
+ return OK;
+ }
+ }
+ prov++;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return DECLINED;
+}
+
+static int expr_lookup_not_found(ap_expr_lookup_parms *parms)
+{
+ const char *type;
+ const char *prefix = "";
+
+ switch (parms->type) {
+ case AP_EXPR_FUNC_VAR:
+ type = "Variable";
+ break;
+ case AP_EXPR_FUNC_STRING:
+ type = "Function";
+ break;
+ case AP_EXPR_FUNC_LIST:
+ type = "List-returning function";
+ break;
+ case AP_EXPR_FUNC_OP_UNARY:
+ type = "Unary operator";
+ break;
+ case AP_EXPR_FUNC_OP_BINARY:
+ type = "Binary operator";
+ break;
+ default:
+ *parms->err = "Inavalid expression type in expr_lookup";
+ return !OK;
+ }
+ if ( parms->type == AP_EXPR_FUNC_OP_UNARY
+ || parms->type == AP_EXPR_FUNC_OP_BINARY) {
+ prefix = "-";
+ }
+ *parms->err = apr_psprintf(parms->ptemp, "%s '%s%s' does not exist", type,
+ prefix, parms->name);
+ return !OK;
+}
+
+static int ap_expr_post_config(apr_pool_t *pconf, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
+{
+ is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+ is_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
+ apr_pool_cleanup_register(pconf, &is_https, ap_pool_cleanup_set_null,
+ apr_pool_cleanup_null);
+ return OK;
+}
+
+void ap_expr_init(apr_pool_t *p)
+{
+ ap_hook_expr_lookup(core_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_expr_lookup(expr_lookup_not_found, NULL, NULL, APR_HOOK_REALLY_LAST);
+ ap_hook_post_config(ap_expr_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+}
+