| /* 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; | |
| /* Use restricted-contents ap_expr() parser in htaccess context. */ | |
| if (cmd->pool == cmd->temp_pool) { | |
| info->flags |= AP_EXPR_FLAG_RESTRICTED_FILE_FUNC; | |
| } | |
| *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 const char *escapehtml_func(ap_expr_eval_ctx_t *ctx, const void *data, | |
| const char *arg) | |
| { | |
| return ap_escape_html(ctx->p, arg); | |
| } | |
| 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 apr_status_t stat_check(ap_expr_eval_ctx_t *ctx, const void *data, const char *arg) | |
| { | |
| apr_status_t rv = APR_SUCCESS; | |
| if (APR_SUCCESS != (rv = ap_stat_check(arg, ctx->p))) { | |
| *ctx->err = apr_psprintf(ctx->p, "stat of %s not allowed", arg); | |
| } | |
| return rv; | |
| } | |
| static const char *filesize_func(ap_expr_eval_ctx_t *ctx, const void *data, | |
| char *arg) | |
| { | |
| apr_finfo_t sb; | |
| if (APR_SUCCESS != stat_check(ctx, data, arg)) { | |
| return ""; | |
| } | |
| 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_SUCCESS != stat_check(ctx, data, arg)) { | |
| return ""; | |
| } | |
| 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_SUCCESS != stat_check(ctx, data, arg)) { | |
| return FALSE; | |
| } | |
| 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_SUCCESS != stat_check(ctx, data, arg)) { | |
| return FALSE; | |
| } | |
| 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_SUCCESS != stat_check(ctx, data, arg)) { | |
| return FALSE; | |
| } | |
| 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 < HTTP_BAD_REQUEST) { | |
| 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; | |
| if (APR_SUCCESS != stat_check(ctx, data, arg)) { | |
| return FALSE; | |
| } | |
| rsub = ap_sub_req_lookup_file(arg, r, NULL); | |
| if (rsub->status < HTTP_MULTIPLE_CHOICES && | |
| /* 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)); | |
| } | |
| #define RESTRICTED_FILE_TEST 0x01 | |
| #define RESTRICTED_FILE_FUNC 0x02 | |
| #define RESTRICTED_ALL (RESTRICTED_FILE_TEST | RESTRICTED_FILE_FUNC) | |
| struct expr_provider_single { | |
| const void *func; | |
| const char *name; | |
| ap_expr_lookup_fn_t *arg_parsing_func; | |
| unsigned 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, RESTRICTED_FILE_FUNC }, | |
| { filesize_func, "filesize", NULL, RESTRICTED_FILE_FUNC }, | |
| { filemod_func, "filemod", NULL, RESTRICTED_FILE_FUNC }, | |
| { 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 }, | |
| { escapehtml_func, "escapehtml", 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, RESTRICTED_FILE_TEST }, | |
| { op_file_min, "e", NULL, RESTRICTED_FILE_TEST }, | |
| { op_file_min, "f", NULL, RESTRICTED_FILE_TEST }, | |
| { op_file_min, "s", NULL, RESTRICTED_FILE_TEST }, | |
| { op_file_link, "L", NULL, RESTRICTED_FILE_TEST }, | |
| { op_file_link, "h", NULL, RESTRICTED_FILE_TEST }, | |
| { op_file_xbit, "x", NULL, RESTRICTED_FILE_TEST }, | |
| { 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 & RESTRICTED_ALL)) | |
| || ((parms->flags & AP_EXPR_FLAG_RESTRICTED_FILE_FUNC) | |
| && (prov->restricted & RESTRICTED_FILE_FUNC))) { | |
| *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); | |
| } | |