mirror of
https://github.com/apache/httpd.git
synced 2025-04-18 22:24:07 +03:00
In ap_expr_parse(), ap_expr_yylex_init() will return 1 on failure, and ctx.scanner will remain NULL. However the return value of ap_expr_yylex_init() is not checked, and there is a dereference of ctx.scanner in following function ap_expr_yyset_extra(), which may lead to NULL pointer dereference. Fix this bug by adding return value check of ap_expr_yylex_init. Submitted by: Zhou Qingyang <zhou1615@umn.edu> Github: closes #308 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1908772 13f79535-47bb-0310-9956-ffa450edef68
2243 lines
68 KiB
C
2243 lines
68 KiB
C
/* 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 "http_ssl.h"
|
|
#include "ap_provider.h"
|
|
#include "util_varbuf.h"
|
|
#include "util_expr_private.h"
|
|
#include "util_md5.h"
|
|
#include "util_varbuf.h"
|
|
|
|
#include "apr_lib.h"
|
|
#include "apr_fnmatch.h"
|
|
#include "apr_base64.h"
|
|
#include "apr_sha1.h"
|
|
#include "apr_version.h"
|
|
#include "apr_strings.h"
|
|
#include "apr_strmatch.h"
|
|
#if APR_VERSION_AT_LEAST(1,5,0)
|
|
#include "apr_escape.h"
|
|
#endif
|
|
|
|
#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 int ap_expr_eval_cond(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node);
|
|
|
|
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);
|
|
|
|
typedef struct {
|
|
int flags;
|
|
const ap_expr_t *subst;
|
|
} ap_expr_regctx_t;
|
|
|
|
static const char *ap_expr_regexec(const char *subject,
|
|
const ap_expr_t *reg,
|
|
apr_array_header_t *list,
|
|
ap_expr_eval_ctx_t *ctx);
|
|
|
|
static apr_array_header_t *ap_expr_list_make(ap_expr_eval_ctx_t *ctx,
|
|
const ap_expr_t *node);
|
|
|
|
/* 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_cond(). 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_list_pstrcat(apr_pool_t *p,
|
|
const apr_array_header_t *list,
|
|
const char *sep)
|
|
{
|
|
if (list->nelts <= 0) {
|
|
return NULL;
|
|
}
|
|
else if (list->nelts == 1) {
|
|
return APR_ARRAY_IDX(list, 0, const char*);
|
|
}
|
|
else {
|
|
struct ap_varbuf vb;
|
|
int n = list->nelts - 1, i;
|
|
apr_size_t slen = strlen(sep), vlen;
|
|
const char *val;
|
|
|
|
ap_varbuf_init(p, &vb, 0);
|
|
for (i = 0; i < n; ++i) {
|
|
val = APR_ARRAY_IDX(list, i, const char*);
|
|
vlen = strlen(val);
|
|
ap_varbuf_strmemcat(&vb, val, vlen);
|
|
ap_varbuf_strmemcat(&vb, sep, slen);
|
|
}
|
|
val = APR_ARRAY_IDX(list, n, const char*);
|
|
ap_varbuf_strmemcat(&vb, val, strlen(val));
|
|
|
|
return vb.buf;
|
|
}
|
|
}
|
|
|
|
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_Word:
|
|
result = ap_expr_eval_word(ctx, node->node_arg1);
|
|
break;
|
|
case op_Bool:
|
|
result = ap_expr_eval_cond(ctx, node->node_arg1) ? "true" : "false";
|
|
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_Join: {
|
|
const char *sep;
|
|
apr_array_header_t *list = ap_expr_list_make(ctx, node->node_arg1);
|
|
sep = node->node_arg2 ? ap_expr_eval_word(ctx, node->node_arg2) : "";
|
|
result = ap_expr_list_pstrcat(ctx->p, list, sep);
|
|
break;
|
|
}
|
|
case op_Sub: {
|
|
const ap_expr_t *reg = node->node_arg2;
|
|
const char *subject = ap_expr_eval_word(ctx, node->node_arg1);
|
|
result = ap_expr_regexec(subject, reg, NULL, ctx);
|
|
break;
|
|
}
|
|
case op_Backref: {
|
|
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)
|
|
{
|
|
const void *data = info->node_arg2;
|
|
|
|
AP_DEBUG_ASSERT(info->node_op == op_StringFuncInfo);
|
|
AP_DEBUG_ASSERT(info->node_arg1 != NULL);
|
|
AP_DEBUG_ASSERT(data != NULL);
|
|
if (arg->node_op == op_ListElement) {
|
|
/* Evaluate the list elements and store them in apr_array_header. */
|
|
ap_expr_string_list_func_t *func = (ap_expr_string_list_func_t *)info->node_arg1;
|
|
apr_array_header_t *args = ap_expr_list_make(ctx, arg);
|
|
return (*func)(ctx, data, args);
|
|
}
|
|
else {
|
|
ap_expr_string_func_t *func = (ap_expr_string_func_t *)info->node_arg1;
|
|
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 const char *ap_expr_regexec(const char *subject,
|
|
const ap_expr_t *reg,
|
|
apr_array_header_t *list,
|
|
ap_expr_eval_ctx_t *ctx)
|
|
{
|
|
struct ap_varbuf vb;
|
|
const char *val = subject;
|
|
const ap_regex_t *regex = reg->node_arg1;
|
|
const ap_expr_regctx_t *regctx = reg->node_arg2;
|
|
ap_regmatch_t *pmatch = NULL, match0;
|
|
apr_size_t nmatch = 0;
|
|
const char *str = "";
|
|
apr_size_t len = 0;
|
|
int empty = 0, rv;
|
|
|
|
ap_varbuf_init(ctx->p, &vb, 0);
|
|
if (ctx->re_nmatch > 0) {
|
|
nmatch = ctx->re_nmatch;
|
|
pmatch = ctx->re_pmatch;
|
|
}
|
|
else if (regctx->subst) {
|
|
nmatch = 1;
|
|
pmatch = &match0;
|
|
}
|
|
do {
|
|
/* If previous match was empty, we can't issue the exact same one or
|
|
* we'd loop indefinitely. So let's instead ask for an anchored and
|
|
* non-empty match (i.e. something not empty at the start of the value)
|
|
* and if nothing is found advance by one character below.
|
|
*/
|
|
rv = ap_regexec(regex, val, nmatch, pmatch,
|
|
empty ? AP_REG_ANCHORED | AP_REG_NOTEMPTY : 0);
|
|
if (rv == 0) {
|
|
int pos = pmatch[0].rm_so,
|
|
end = pmatch[0].rm_eo;
|
|
AP_DEBUG_ASSERT(pos >= 0 && pos <= end);
|
|
|
|
if (regctx->subst) {
|
|
*ctx->re_source = val;
|
|
str = ap_expr_eval_word(ctx, regctx->subst);
|
|
len = strlen(str);
|
|
}
|
|
if (list) {
|
|
char *tmp = apr_palloc(ctx->p, pos + len + 1);
|
|
memcpy(tmp, val, pos);
|
|
memcpy(tmp + pos, str, len + 1);
|
|
APR_ARRAY_PUSH(list, const char*) = tmp;
|
|
}
|
|
else {
|
|
ap_varbuf_grow(&vb, pos + len + 1);
|
|
ap_varbuf_strmemcat(&vb, val, pos);
|
|
ap_varbuf_strmemcat(&vb, str, len);
|
|
if (!(regctx->flags & AP_REG_MULTI)) {
|
|
/* Single substitution, preserve remaining data */
|
|
ap_varbuf_strmemcat(&vb, val + end, strlen(val) - end);
|
|
break;
|
|
}
|
|
}
|
|
/* Note an empty match */
|
|
empty = (end == 0);
|
|
val += end;
|
|
}
|
|
else if (empty) {
|
|
/* Skip this non-matching character (or full CRLF) and restart
|
|
* another "normal" match (possibly empty) from there.
|
|
*/
|
|
if (val[0] == '\r' && val[1] == '\n') {
|
|
val += 2;
|
|
}
|
|
else {
|
|
val++;
|
|
}
|
|
empty = 0;
|
|
}
|
|
else {
|
|
if (list) {
|
|
APR_ARRAY_PUSH(list, const char*) = val;
|
|
}
|
|
else if (vb.avail) {
|
|
ap_varbuf_strmemcat(&vb, val, strlen(val));
|
|
}
|
|
else {
|
|
return val;
|
|
}
|
|
break;
|
|
}
|
|
} while (*val);
|
|
|
|
return vb.buf;
|
|
}
|
|
|
|
static apr_array_header_t *ap_expr_list_make(ap_expr_eval_ctx_t *ctx,
|
|
const ap_expr_t *node)
|
|
{
|
|
apr_array_header_t *list = NULL;
|
|
|
|
if (node->node_op == op_Split) {
|
|
const ap_expr_t *arg = node->node_arg1;
|
|
const ap_expr_t *reg = node->node_arg2;
|
|
const apr_array_header_t *source = ap_expr_list_make(ctx, arg);
|
|
int i;
|
|
|
|
list = apr_array_make(ctx->p, source->nelts, sizeof(const char*));
|
|
for (i = 0; i < source->nelts; ++i) {
|
|
const char *val = APR_ARRAY_IDX(source, i, const char*);
|
|
(void)ap_expr_regexec(val, reg, list, ctx);
|
|
}
|
|
}
|
|
else if (node->node_op == op_ListElement) {
|
|
int n = 0;
|
|
const ap_expr_t *elem;
|
|
for (elem = node; elem; elem = elem->node_arg2) {
|
|
AP_DEBUG_ASSERT(elem->node_op == op_ListElement);
|
|
n++;
|
|
}
|
|
|
|
list = apr_array_make(ctx->p, n, sizeof(const char*));
|
|
for (elem = node; elem; elem = elem->node_arg2) {
|
|
APR_ARRAY_PUSH(list, const char*) =
|
|
ap_expr_eval_word(ctx, elem->node_arg1);
|
|
}
|
|
}
|
|
else if (node->node_op == op_ListFuncCall) {
|
|
const ap_expr_t *info = node->node_arg1;
|
|
ap_expr_list_func_t *func = info->node_arg1;
|
|
|
|
AP_DEBUG_ASSERT(func != NULL);
|
|
AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo);
|
|
list = (*func)(ctx, info->node_arg2,
|
|
ap_expr_eval_word(ctx, node->node_arg2));
|
|
}
|
|
else {
|
|
list = apr_array_make(ctx->p, 1, sizeof(const char*));
|
|
APR_ARRAY_PUSH(list, const char*) = ap_expr_eval_word(ctx, node);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
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: {
|
|
int n;
|
|
const char *needle, *subject;
|
|
apr_array_header_t *haystack;
|
|
haystack = ap_expr_list_make(ctx, e2);
|
|
if (haystack) {
|
|
needle = ap_expr_eval_word(ctx, e1);
|
|
for (n = 0; n < haystack->nelts; ++n) {
|
|
subject = APR_ARRAY_IDX(haystack, n, const char*);
|
|
if (strcmp(needle, subject) == 0) {
|
|
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));
|
|
}
|
|
|
|
return result ^ (node->node_op == op_NRE);
|
|
}
|
|
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)
|
|
{
|
|
apr_size_t 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;
|
|
|
|
memset(&ctx, 0, sizeof ctx);
|
|
ctx.pool = pool;
|
|
ctx.ptemp = ptemp;
|
|
ctx.inputbuf = expr;
|
|
ctx.inputlen = strlen(expr);
|
|
ctx.inputptr = ctx.inputbuf;
|
|
ctx.flags = info->flags;
|
|
ctx.lookup_fn = lookup_fn ? lookup_fn : ap_expr_lookup_default;
|
|
ctx.at_start = 1;
|
|
|
|
rc = ap_expr_yylex_init(&ctx.scanner);
|
|
if (rc)
|
|
return "ap_expr_yylex_init error";
|
|
|
|
ap_expr_yyset_extra(&ctx, ctx.scanner);
|
|
rc = ap_expr_yyparse(&ctx);
|
|
ap_expr_yylex_destroy(ctx.scanner);
|
|
|
|
/* ctx.error: the generic bison error message
|
|
* (XXX: usually not very useful, should be axed)
|
|
* ctx.error2: an additional error message
|
|
*/
|
|
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;
|
|
}
|
|
|
|
ap_expr_t *ap_expr_concat_make(const void *a1, const void *a2,
|
|
ap_expr_parse_ctx_t *ctx)
|
|
{
|
|
const ap_expr_t *node;
|
|
|
|
/* Optimize out empty string(s) concatenation */
|
|
if ((node = a1)
|
|
&& node->node_op == op_String
|
|
&& !*(const char *)node->node_arg1) {
|
|
return (ap_expr_t *)a2;
|
|
}
|
|
if ((node = a2)
|
|
&& node->node_op == op_String
|
|
&& !*(const char *)node->node_arg1) {
|
|
return (ap_expr_t *)a1;
|
|
}
|
|
|
|
return ap_expr_make(op_Concat, a1, a2, ctx);
|
|
}
|
|
|
|
ap_expr_t *ap_expr_regex_make(const char *pattern, const ap_expr_t *subst,
|
|
const char *flags, ap_expr_parse_ctx_t *ctx)
|
|
{
|
|
ap_expr_t *node = NULL;
|
|
ap_expr_regctx_t *regctx;
|
|
ap_regex_t *regex;
|
|
|
|
regctx = apr_pcalloc(ctx->pool, sizeof *regctx);
|
|
regctx->subst = subst;
|
|
if (flags) {
|
|
for (; *flags; ++flags) {
|
|
switch (*flags) {
|
|
case 'i':
|
|
regctx->flags |= AP_REG_ICASE;
|
|
break;
|
|
case 'm':
|
|
regctx->flags |= AP_REG_NEWLINE;
|
|
break;
|
|
case 's':
|
|
regctx->flags |= AP_REG_DOTALL;
|
|
break;
|
|
case 'g':
|
|
regctx->flags |= AP_REG_MULTI;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
regex = ap_pregcomp(ctx->pool, pattern, regctx->flags);
|
|
if (!regex) {
|
|
return NULL;
|
|
}
|
|
|
|
node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
|
|
node->node_op = op_Regex;
|
|
node->node_arg1 = regex;
|
|
node->node_arg2 = regctx;
|
|
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 = NULL;
|
|
if (arg) {
|
|
switch(arg->node_op) {
|
|
case op_String:
|
|
parms.arg = arg->node_arg1;
|
|
break;
|
|
case op_ListElement:
|
|
/* save the first literal/simple string argument */
|
|
do {
|
|
const ap_expr_t *val = arg->node_arg1;
|
|
if (val && val->node_op == op_String) {
|
|
parms.arg = val->node_arg1;
|
|
break;
|
|
}
|
|
arg = arg->node_arg2;
|
|
} while (arg != NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
ap_expr_t *ap_expr_backref_make(int num, ap_expr_parse_ctx_t *ctx)
|
|
{
|
|
int *n = apr_pmemdup(ctx->pool, &num, sizeof(num));
|
|
return ap_expr_make(op_Backref, n, NULL, ctx);
|
|
}
|
|
|
|
#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_Word:
|
|
case op_Bool:
|
|
case op_Sub:
|
|
case op_Join:
|
|
case op_Split:
|
|
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_Word);
|
|
CASE_OP(op_Bool);
|
|
CASE_OP(op_Sub);
|
|
CASE_OP(op_Join);
|
|
CASE_OP(op_Split);
|
|
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_Backref:
|
|
DUMP_IP("op_Backref", e->node_arg1);
|
|
break;
|
|
default:
|
|
ap_log_error(MARK, "%*sERROR: INVALID OP %d", indent, " ", e->node_op);
|
|
break;
|
|
}
|
|
}
|
|
#endif /* AP_EXPR_DEBUG */
|
|
|
|
#define expr_eval_log(ctx, level, ...) do { \
|
|
ap_expr_eval_ctx_t *x = (ctx); \
|
|
if (x->r) { \
|
|
ap_log_rerror(LOG_MARK(x->info), (level), 0, x->r, __VA_ARGS__); \
|
|
} \
|
|
else if (x->c) { \
|
|
ap_log_cerror(LOG_MARK(x->info), (level), 0, x->c, __VA_ARGS__); \
|
|
} \
|
|
else { \
|
|
ap_log_error(LOG_MARK(x->info), (level), 0, x->s, __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
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_cond(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_cond(ctx, e1->node_arg1)) {
|
|
result ^= TRUE;
|
|
goto out;
|
|
}
|
|
}
|
|
else {
|
|
if (ap_expr_eval_cond(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_cond(ctx, e1->node_arg1)) {
|
|
result ^= FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
else {
|
|
if (!ap_expr_eval_cond(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);
|
|
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);
|
|
}
|
|
if (!ctx->s) {
|
|
if (ctx->r) {
|
|
ctx->s = ctx->r->server;
|
|
}
|
|
else if (ctx->c) {
|
|
ctx->s = ctx->c->base_server;
|
|
}
|
|
}
|
|
if (!ctx->c) {
|
|
if (ctx->r) {
|
|
ctx->c = ctx->r->connection;
|
|
}
|
|
}
|
|
AP_DEBUG_ASSERT(ctx->s != NULL);
|
|
|
|
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) {
|
|
expr_eval_log(ctx, APLOG_ERR, APLOGNO(03298)
|
|
"Evaluation of string expression from %s:%d failed: %s",
|
|
ctx->info->filename, ctx->info->line_number, *ctx->err);
|
|
return -1;
|
|
} else {
|
|
expr_eval_log(ctx, APLOG_TRACE4,
|
|
"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_cond(ctx, ctx->info->root_node);
|
|
if (*ctx->err != NULL) {
|
|
expr_eval_log(ctx, APLOG_ERR, APLOGNO(03299)
|
|
"Evaluation of expression from %s:%d failed: %s",
|
|
ctx->info->filename, ctx->info->line_number, *ctx->err);
|
|
return -1;
|
|
} else {
|
|
rc = rc ? 1 : 0;
|
|
expr_eval_log(ctx, APLOG_TRACE4,
|
|
"Evaluation of expression from %s:%d gave: %d",
|
|
ctx->info->filename, ctx->info->line_number, rc);
|
|
|
|
if (ctx->r && 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, (unsigned int)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);
|
|
}
|
|
|
|
#if APR_VERSION_AT_LEAST(1,6,0)
|
|
static const char *ldap_func(ap_expr_eval_ctx_t *ctx, const void *data,
|
|
const char *arg)
|
|
{
|
|
return apr_pescape_ldap(ctx->p, arg, APR_ESCAPE_STRING, APR_ESCAPE_LDAP_ALL);
|
|
}
|
|
#endif
|
|
|
|
static int replace_func_parse_arg(ap_expr_lookup_parms *parms)
|
|
{
|
|
const char *original = parms->arg;
|
|
const apr_strmatch_pattern *pattern;
|
|
|
|
if (!parms->arg) {
|
|
*parms->err = apr_psprintf(parms->ptemp, "replace() function needs an argument");
|
|
return !OK;
|
|
}
|
|
|
|
pattern = apr_strmatch_precompile(parms->pool, original, 0);
|
|
*parms->data = pattern;
|
|
return OK;
|
|
}
|
|
|
|
static const char *replace_func(ap_expr_eval_ctx_t *ctx, const void *data,
|
|
const apr_array_header_t *args)
|
|
{
|
|
char *buff, *original, *replacement;
|
|
struct ap_varbuf vb;
|
|
apr_size_t repl_len, orig_len;
|
|
const char *repl;
|
|
apr_size_t bytes;
|
|
apr_size_t len;
|
|
const apr_strmatch_pattern *pattern = data;
|
|
if (args->nelts != 3) {
|
|
*ctx->err = apr_psprintf(ctx->p, "replace() function needs "
|
|
"exactly 3 arguments, got %d", args->nelts);
|
|
return "";
|
|
}
|
|
|
|
buff = APR_ARRAY_IDX(args, 0, char *);
|
|
original = APR_ARRAY_IDX(args, 1, char *);
|
|
replacement = APR_ARRAY_IDX(args, 2, char *);
|
|
repl_len = strlen(replacement);
|
|
orig_len = strlen(original);
|
|
bytes = strlen(buff);
|
|
|
|
ap_varbuf_init(ctx->p, &vb, 0);
|
|
vb.strlen = 0;
|
|
|
|
while ((repl = apr_strmatch(pattern, buff, bytes))) {
|
|
len = (apr_size_t) (repl - buff);
|
|
ap_varbuf_strmemcat(&vb, buff, len);
|
|
ap_varbuf_strmemcat(&vb, replacement, repl_len);
|
|
|
|
len += orig_len;
|
|
bytes -= len;
|
|
buff += len;
|
|
}
|
|
|
|
return ap_varbuf_pdup(ctx->p, &vb, NULL, 0, buff, bytes, &len);
|
|
}
|
|
|
|
#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 *filemod_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.mtime > 0)
|
|
return apr_psprintf(ctx->p, "%" APR_OFF_T_FMT, (apr_off_t)sb.mtime);
|
|
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;
|
|
expr_eval_log(ctx, APLOG_DEBUG, 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;
|
|
}
|
|
expr_eval_log(ctx, APLOG_TRACE5,
|
|
"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;
|
|
}
|
|
expr_eval_log(ctx, APLOG_TRACE5,
|
|
"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, http2_is_h2, (conn_rec *));
|
|
static APR_OPTIONAL_FN_TYPE(http2_is_h2) *is_http2 = NULL;
|
|
|
|
static const char *const 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 (ap_ssl_conn_is_ssl(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 *const 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 */
|
|
"SERVER_PROTOCOL_VERSION", /* 29 */
|
|
"SERVER_PROTOCOL_VERSION_MAJOR", /* 30 */
|
|
"SERVER_PROTOCOL_VERSION_MINOR", /* 31 */
|
|
"REMOTE_PORT", /* 32 */
|
|
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:
|
|
switch (r->proto_num) {
|
|
case 1001: return "1001"; /* 1.1 */
|
|
case 1000: return "1000"; /* 1.0 */
|
|
case 9: return "9"; /* 0.9 */
|
|
}
|
|
return apr_psprintf(ctx->p, "%d", r->proto_num);
|
|
case 30:
|
|
switch (HTTP_VERSION_MAJOR(r->proto_num)) {
|
|
case 0: return "0";
|
|
case 1: return "1";
|
|
}
|
|
return apr_psprintf(ctx->p, "%d", HTTP_VERSION_MAJOR(r->proto_num));
|
|
case 31:
|
|
switch (HTTP_VERSION_MINOR(r->proto_num)) {
|
|
case 0: return "0";
|
|
case 1: return "1";
|
|
case 9: return "9";
|
|
}
|
|
return apr_psprintf(ctx->p, "%d", HTTP_VERSION_MINOR(r->proto_num));
|
|
case 32:
|
|
return apr_psprintf(ctx->p, "%u", ctx->c->client_addr->port);
|
|
default:
|
|
ap_assert(0);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static const char *const 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 *const 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 **const 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 *const 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 *const *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 },
|
|
{ filemod_func, "filemod", NULL, 1 },
|
|
{ base64_func, "base64", NULL, 0 },
|
|
{ unbase64_func, "unbase64", NULL, 0 },
|
|
{ sha1_func, "sha1", NULL, 0 },
|
|
{ md5_func, "md5", NULL, 0 },
|
|
#if APR_VERSION_AT_LEAST(1,6,0)
|
|
{ ldap_func, "ldap", NULL, 0 },
|
|
#endif
|
|
{ replace_func, "replace", replace_func_parse_arg, 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 *const *name = prov->names;
|
|
while (*name) {
|
|
if (ap_cstr_casecmp(*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 = !ap_cstr_casecmp(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 = "Invalid 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_http2 = APR_RETRIEVE_OPTIONAL_FN(http2_is_h2);
|
|
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);
|
|
}
|
|
|