diff options
-rw-r--r-- | CHANGES | 2 | ||||
-rw-r--r-- | NWGNUmakefile | 1 | ||||
-rw-r--r-- | build/nw_export.inc | 1 | ||||
-rw-r--r-- | include/ap_expr.h | 101 | ||||
-rw-r--r-- | include/ap_mmn.h | 3 | ||||
-rw-r--r-- | libhttpd.dsp | 8 | ||||
-rw-r--r-- | server/Makefile.in | 2 | ||||
-rw-r--r-- | server/util_expr.c | 874 |
8 files changed, 990 insertions, 2 deletions
@@ -2,6 +2,8 @@ Changes with Apache 2.3.0 [ When backported to 2.2.x, remove entry from this file ] + *) Introduced ap_expr API for expression evaluation. + *) mod_authz_dbd: When redirecting after successful login/logout per AuthzDBDRedirectQuery, do not report authorization failure, and use first row returned by database query instead of last row. diff --git a/NWGNUmakefile b/NWGNUmakefile index b0fd434ae8..7e56d21ae1 100644 --- a/NWGNUmakefile +++ b/NWGNUmakefile @@ -237,6 +237,7 @@ FILES_nlm_objs = \ $(OBJDIR)/util_cfgtree.o \ $(OBJDIR)/util_charset.o \ $(OBJDIR)/util_debug.o \ + $(OBJDIR)/util_expr.o \ $(OBJDIR)/util_filter.o \ $(OBJDIR)/util_md5.o \ $(OBJDIR)/util_mutex.o \ diff --git a/build/nw_export.inc b/build/nw_export.inc index 5a5661d7f2..09f7f876ce 100644 --- a/build/nw_export.inc +++ b/build/nw_export.inc @@ -24,6 +24,7 @@ #include "ap_mpm.h" #include "ap_provider.h" #include "ap_release.h" +#include "ap_expr.h" #include "http_config.h" #include "http_connection.h" #include "http_core.h" diff --git a/include/ap_expr.h b/include/ap_expr.h new file mode 100644 index 0000000000..5ff987b6f7 --- /dev/null +++ b/include/ap_expr.h @@ -0,0 +1,101 @@ +/* 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. + */ + +/* conditional expression parser stuff */ +typedef enum { + TOKEN_STRING, + TOKEN_RE, + TOKEN_AND, + TOKEN_OR, + TOKEN_NOT, + TOKEN_EQ, + TOKEN_NE, + TOKEN_RBRACE, + TOKEN_LBRACE, + TOKEN_GROUP, + TOKEN_GE, + TOKEN_LE, + TOKEN_GT, + TOKEN_LT, + TOKEN_ACCESS +} token_type_t; + +typedef struct { + token_type_t type; + const char *value; +#ifdef DEBUG_INCLUDE + const char *s; +#endif +} token_t; + +typedef struct parse_node { + struct parse_node *parent; + struct parse_node *left; + struct parse_node *right; + token_t token; + int value; + int done; +#ifdef DEBUG_INCLUDE + int dump_done; +#endif +} parse_node_t; + +typedef struct { + const char *source; + const char *rexp; + apr_size_t nsub; + ap_regmatch_t match[AP_MAX_REG_MATCH]; +} backref_t; + +typedef char *(*string_func_t)(request_rec*, const char*); +typedef int (*opt_func_t)(request_rec*, parse_node_t*, string_func_t); + +/** + * Parse an expression into a parse tree + * @param pool Pool + * @param expr The expression to parse + * @param was_error On return, set to zero if parse successful, nonzero on error + * @return The parse tree + */ +AP_DECLARE(parse_node_t*) ap_expr_parse(apr_pool_t *pool, const char *expr, + int *was_error); +/** + * Evaluate a parse tree + * @param r The current request + * @param root The root node of the parse tree + * @param was_error On return, set to zero if parse successful, nonzero on error + * @param reptr Regular expression memory for backreferencing if a regexp was parsed + * @param string_func String parser function - perform variable substitutions + * @param eval_func Option evaluation function (e.g. -A filename) + * @return the value the expression parsed to + */ +AP_DECLARE(int) ap_expr_eval(request_rec *r, parse_node_t *root, + int *was_error, backref_t **reptr, + string_func_t string_func, opt_func_t eval_func); +/** + * Evaluate an expression + * @param r The current request + * @param expr The expression to parse + * @param was_error On return, set to zero if parse successful, nonzero on error + * @param reptr Regular expression memory for backreferencing if a regexp was parsed + * @param string_func String parser function - perform variable substitutions + * @param eval_func Option evaluation function (e.g. -A filename) + * @return the value the expression parsed to + */ +AP_DECLARE(int) ap_expr_evalstring(request_rec *r, const char *expr, + int *was_error, backref_t **reptr, + string_func_t string_func, + opt_func_t eval_func); diff --git a/include/ap_mmn.h b/include/ap_mmn.h index c1fed6412b..02ee76152f 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -149,6 +149,7 @@ * 20071108.7 (2.3.0-dev) Add *ftp_directory_charset to proxy_dir_conf * 20071108.8 (2.3.0-dev) Add optional function ap_logio_add_bytes_in() to mog_logio * 20071108.9 (2.3.0-dev) Add chroot support to unixd_config + * 20071108.10(2.3.0-dev) Introduce new ap_expr API */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -156,7 +157,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20071108 #endif -#define MODULE_MAGIC_NUMBER_MINOR 9 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 10 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/libhttpd.dsp b/libhttpd.dsp index 07ed3b29b4..f1310468ca 100644 --- a/libhttpd.dsp +++ b/libhttpd.dsp @@ -116,6 +116,10 @@ SOURCE=.\include\ap_config.h # End Source File # Begin Source File +SOURCE=.\include\ap_expr.h +# End Source File +# Begin Source File + SOURCE=.\include\ap_mmn.h # End Source File # Begin Source File @@ -492,6 +496,10 @@ SOURCE=.\include\util_ebcdic.h # End Source File # Begin Source File +SOURCE=.\server\util_expr.c +# End Source File +# Begin Source File + SOURCE=.\server\util_filter.c # End Source File # Begin Source File diff --git a/server/Makefile.in b/server/Makefile.in index 0a64441aba..5fc286a571 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -12,7 +12,7 @@ LTLIBRARY_SOURCES = \ util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \ connection.c listen.c util_mutex.c \ mpm_common.c util_charset.c util_debug.c util_xml.c \ - util_filter.c util_pcre.c exports.c \ + util_expr.c util_filter.c util_pcre.c exports.c \ scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ eoc_bucket.c eor_bucket.c core_filters.c diff --git a/server/util_expr.c b/server/util_expr.c new file mode 100644 index 0000000000..d1ab34236d --- /dev/null +++ b/server/util_expr.c @@ -0,0 +1,874 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_lib.h" + +#define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC +#include "apr_want.h" + +#include "httpd.h" +#include "http_log.h" + +#include "ap_expr.h" +#if 1 +/* + * +-------------------------------------------------------+ + * | | + * | Debugging Utilities + * | | + * +-------------------------------------------------------+ + */ + +#ifdef DEBUG_INCLUDE + +#define TYPE_TOKEN(token, ttype) do { \ + (token)->type = ttype; \ + (token)->s = #ttype; \ +} while(0) + +#define CREATE_NODE(pool,name) do { \ + (name) = apr_palloc(pool, sizeof(*(name))); \ + (name)->parent = (name)->left = (name)->right = NULL; \ + (name)->done = 0; \ + (name)->dump_done = 0; \ +} while(0) + +static void debug_printf(request_rec *r, const char *fmt, ...) +{ + va_list ap; + char *debug__str; + + va_start(ap, fmt); + debug__str = apr_pvsprintf(r->pool, fmt, ap); + va_end(ap); +/* + APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create( + debug__str, strlen(debug__str), ctx->pool, + ctx->intern->debug.f->c->bucket_alloc)); + */ +} + +#define DUMP__CHILD(ctx, is, node, child) if (1) { \ + parse_node_t *d__c = node->child; \ + if (d__c) { \ + if (!d__c->dump_done) { \ + if (d__c->parent != node) { \ + debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \ + if (!d__c->parent) { \ + debug_printf(ctx, "Parent of " #child " child node is " \ + "NULL.\n"); \ + } \ + else { \ + debug_printf(ctx, "Parent of " #child " child node " \ + "points to another node (of type %s)!\n", \ + d__c->parent->token.s); \ + } \ + return; \ + } \ + node = d__c; \ + continue; \ + } \ + } \ + else { \ + debug_printf(ctx, "%s(missing)\n", is); \ + } \ +} + +static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root) +{ + parse_node_t *current; + char *is; + + if (!root) { + debug_printf(ctx, " -- Parse Tree empty --\n\n"); + return; + } + + debug_printf(ctx, " ----- Parse Tree -----\n"); + current = root; + is = " "; + + while (current) { + switch (current->token.type) { + case TOKEN_STRING: + case TOKEN_RE: + debug_printf(ctx, "%s%s (%s)\n", is, current->token.s, + current->token.value); + current->dump_done = 1; + current = current->parent; + continue; + + case TOKEN_NOT: + case TOKEN_GROUP: + case TOKEN_RBRACE: + case TOKEN_LBRACE: + if (!current->dump_done) { + debug_printf(ctx, "%s%s\n", is, current->token.s); + is = apr_pstrcat(ctx->dpool, is, " ", NULL); + current->dump_done = 1; + } + + DUMP__CHILD(ctx, is, current, right) + + if (!current->right || current->right->dump_done) { + is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); + if (current->right) current->right->dump_done = 0; + current = current->parent; + } + continue; + + default: + if (!current->dump_done) { + debug_printf(ctx, "%s%s\n", is, current->token.s); + is = apr_pstrcat(ctx->dpool, is, " ", NULL); + current->dump_done = 1; + } + + DUMP__CHILD(ctx, is, current, left) + DUMP__CHILD(ctx, is, current, right) + + if ((!current->left || current->left->dump_done) && + (!current->right || current->right->dump_done)) { + + is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4); + if (current->left) current->left->dump_done = 0; + if (current->right) current->right->dump_done = 0; + current = current->parent; + } + continue; + } + } + + /* it is possible to call this function within the parser loop, to see + * how the tree is built. That way, we must cleanup after us to dump + * always the whole tree + */ + root->dump_done = 0; + if (root->left) root->left->dump_done = 0; + if (root->right) root->right->dump_done = 0; + + debug_printf(ctx, " --- End Parse Tree ---\n\n"); + + return; +} + +#define DEBUG_INIT(ctx, filter, brigade) do { \ + (ctx)->intern->debug.f = filter; \ + (ctx)->intern->debug.bb = brigade; \ +} while(0) + +#define DEBUG_PRINTF(arg) debug_printf arg + +#define DEBUG_DUMP_TOKEN(ctx, token) do { \ + token_t *d__t = (token); \ + \ + if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \ + DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \ + } \ + else { \ + DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \ + } \ +} while(0) + +#define DEBUG_DUMP_EVAL(r, node) do { \ + char c = '"'; \ + switch ((node)->token.type) { \ + case TOKEN_STRING: \ + debug_printf((r), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\ + (node)->token.value, ((node)->value) ? '1':'0'); \ + break; \ + case TOKEN_AND: \ + case TOKEN_OR: \ + debug_printf((r), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\ + (node)->token.s, \ + (((node)->left->done) ? ((node)->left->value ?"1":"0") \ + : "short circuited"), \ + (((node)->right->done) ? ((node)->right->value?"1":"0") \ + : "short circuited"), \ + (node)->value ? '1' : '0'); \ + break; \ + case TOKEN_EQ: \ + case TOKEN_NE: \ + case TOKEN_GT: \ + case TOKEN_GE: \ + case TOKEN_LT: \ + case TOKEN_LE: \ + if ((node)->right->token.type == TOKEN_RE) c = '/'; \ + debug_printf((r), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \ + (node)->token.s, \ + (node)->left->token.value, \ + c, (node)->right->token.value, c, \ + (node)->value ? '1' : '0'); \ + break; \ + default: \ + debug_printf((r), " Evaluate: %s -> %c\n", (node)->token.s, \ + (node)->value ? '1' : '0'); \ + break; \ + } \ +} while(0) + +#define DEBUG_DUMP_UNMATCHED(r, unmatched) do { \ + if (unmatched) { \ + DEBUG_PRINTF(((r), " Unmatched %c\n", (char)(unmatched))); \ + } \ +} while(0) + +#define DEBUG_DUMP_COND(ctx, text) \ + DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \ + ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0')) + +#define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root) + +#else /* DEBUG_INCLUDE */ + +#define TYPE_TOKEN(token, ttype) (token)->type = ttype + +#define CREATE_NODE(pool,name) do { \ + (name) = apr_palloc(pool, sizeof(*(name))); \ + (name)->parent = (name)->left = (name)->right = NULL; \ + (name)->done = 0; \ +} while(0) + +#define DEBUG_INIT(ctx, f, bb) +#define DEBUG_PRINTF(arg) +#define DEBUG_DUMP_TOKEN(ctx, token) +#define DEBUG_DUMP_EVAL(ctx, node) +#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) +#define DEBUG_DUMP_COND(ctx, text) +#define DEBUG_DUMP_TREE(ctx, root) + +#endif /* !DEBUG_INCLUDE */ + +#endif /* 0 */ + + +/* + * +-------------------------------------------------------+ + * | | + * | Conditional Expression Parser + * | | + * +-------------------------------------------------------+ + */ +static APR_INLINE int re_check(request_rec *r, const char *string, + const char *rexp, backref_t **reptr) +{ + ap_regex_t *compiled; + backref_t *re = *reptr; + int rc; + + compiled = ap_pregcomp(r->pool, rexp, AP_REG_EXTENDED); + if (!compiled) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to " + "compile pattern \"%s\"", rexp); + return -1; + } + + if (!re) { + re = *reptr = apr_palloc(r->pool, sizeof(*re)); + } + + re->source = apr_pstrdup(r->pool, string); + re->rexp = apr_pstrdup(r->pool, rexp); + re->nsub = compiled->re_nsub; + rc = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, re->match, 0); + + ap_pregfree(r->pool, compiled); + return rc; +} + +static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token, + token_t *previous) +{ + const char *p; + apr_size_t shift; + int unmatched; + + token->value = NULL; + + if (!*parse) { + return 0; + } + + /* Skip leading white space */ + while (apr_isspace(**parse)) { + ++*parse; + } + + if (!**parse) { + *parse = NULL; + return 0; + } + + TYPE_TOKEN(token, TOKEN_STRING); /* the default type */ + p = *parse; + unmatched = 0; + + switch (*(*parse)++) { + case '(': + TYPE_TOKEN(token, TOKEN_LBRACE); + return 0; + case ')': + TYPE_TOKEN(token, TOKEN_RBRACE); + return 0; + case '=': + if (**parse == '=') ++*parse; + TYPE_TOKEN(token, TOKEN_EQ); + return 0; + case '!': + if (**parse == '=') { + TYPE_TOKEN(token, TOKEN_NE); + ++*parse; + return 0; + } + TYPE_TOKEN(token, TOKEN_NOT); + return 0; + case '\'': + unmatched = '\''; + break; + case '/': + /* if last token was ACCESS, this token is STRING */ + if (previous != NULL && TOKEN_ACCESS == previous->type) { + break; + } + TYPE_TOKEN(token, TOKEN_RE); + unmatched = '/'; + break; + case '|': + if (**parse == '|') { + TYPE_TOKEN(token, TOKEN_OR); + ++*parse; + return 0; + } + break; + case '&': + if (**parse == '&') { + TYPE_TOKEN(token, TOKEN_AND); + ++*parse; + return 0; + } + break; + case '>': + if (**parse == '=') { + TYPE_TOKEN(token, TOKEN_GE); + ++*parse; + return 0; + } + TYPE_TOKEN(token, TOKEN_GT); + return 0; + case '<': + if (**parse == '=') { + TYPE_TOKEN(token, TOKEN_LE); + ++*parse; + return 0; + } + TYPE_TOKEN(token, TOKEN_LT); + return 0; + case '-': + if (apr_isalnum(**parse) && apr_isspace((*parse)[1])) { + TYPE_TOKEN(token, TOKEN_ACCESS); + token->value = *parse; + ++*parse; + return 0; + } + break; + } + + /* It's a string or regex token + * Now search for the next token, which finishes this string + */ + shift = 0; + p = *parse = token->value = unmatched ? *parse : p; + + for (; **parse; p = ++*parse) { + if (**parse == '\\') { + if (!*(++*parse)) { + p = *parse; + break; + } + + ++shift; + } + else { + if (unmatched) { + if (**parse == unmatched) { + unmatched = 0; + ++*parse; + break; + } + } + else if (apr_isspace(**parse)) { + break; + } + else { + int found = 0; + + switch (**parse) { + case '(': + case ')': + case '=': + case '!': + case '<': + case '>': + ++found; + break; + + case '|': + case '&': + if ((*parse)[1] == **parse) { + ++found; + } + break; + } + + if (found) { + break; + } + } + } + } + + if (unmatched) { + token->value = apr_pstrdup(pool, ""); + } + else { + apr_size_t len = p - token->value - shift; + char *c = apr_palloc(pool, len + 1); + + p = token->value; + token->value = c; + + while (shift--) { + const char *e = ap_strchr_c(p, '\\'); + + memcpy(c, p, e-p); + c += e-p; + *c++ = *++e; + len -= e-p; + p = e+1; + } + + if (len) { + memcpy(c, p, len); + } + c[len] = '\0'; + } + + return unmatched; +} + +/* This is what we export. We can split it in two. */ +AP_DECLARE(parse_node_t*) ap_expr_parse(apr_pool_t* pool, const char *expr, + int *was_error) +{ + parse_node_t *new, *root = NULL, *current = NULL; + const char *error = "Invalid expression \"%s\" in file %s"; + const char *parse = expr; + int was_unmatched = 0; + unsigned regex = 0; + + *was_error = 0; + + if (!parse) { + return 0; + } + + /* Create Parse Tree */ + while (1) { + /* uncomment this to see how the tree a built: + * + * DEBUG_DUMP_TREE(ctx, root); + */ + CREATE_NODE(pool, new); + + was_unmatched = get_ptoken(pool, &parse, &new->token, + (current != NULL ? ¤t->token : NULL)); + if (!parse) { + break; + } + + DEBUG_DUMP_UNMATCHED(ctx, was_unmatched); + DEBUG_DUMP_TOKEN(ctx, &new->token); + + if (!current) { + switch (new->token.type) { + case TOKEN_STRING: + case TOKEN_NOT: + case TOKEN_ACCESS: + case TOKEN_LBRACE: + root = current = new; + continue; + + default: + *was_error = 1; + return 0; + } + } + + switch (new->token.type) { + case TOKEN_STRING: + switch (current->token.type) { + case TOKEN_STRING: + current->token.value = + apr_pstrcat(pool, current->token.value, + *current->token.value ? " " : "", + new->token.value, NULL); + continue; + + case TOKEN_RE: + case TOKEN_RBRACE: + case TOKEN_GROUP: + break; + + default: + new->parent = current; + current = current->right = new; + continue; + } + break; + + case TOKEN_RE: + switch (current->token.type) { + case TOKEN_EQ: + case TOKEN_NE: + new->parent = current; + current = current->right = new; + ++regex; + continue; + + default: + break; + } + break; + + case TOKEN_AND: + case TOKEN_OR: + switch (current->token.type) { + case TOKEN_STRING: + case TOKEN_RE: + case TOKEN_GROUP: + current = current->parent; + + while (current) { + switch (current->token.type) { + case TOKEN_AND: + case TOKEN_OR: + case TOKEN_LBRACE: + break; + + default: + current = current->parent; + continue; + } + break; + } + + if (!current) { + new->left = root; + root->parent = new; + current = root = new; + continue; + } + + new->left = current->right; + new->left->parent = new; + new->parent = current; + current = current->right = new; + continue; + + default: + break; + } + break; + + case TOKEN_EQ: + case TOKEN_NE: + case TOKEN_GE: + case TOKEN_GT: + case TOKEN_LE: + case TOKEN_LT: + if (current->token.type == TOKEN_STRING) { + current = current->parent; + + if (!current) { + new->left = root; + root->parent = new; + current = root = new; + continue; + } + + switch (current->token.type) { + case TOKEN_LBRACE: + case TOKEN_AND: + case TOKEN_OR: + new->left = current->right; + new->left->parent = new; + new->parent = current; + current = current->right = new; + continue; + + default: + break; + } + } + break; + + case TOKEN_RBRACE: + while (current && current->token.type != TOKEN_LBRACE) { + current = current->parent; + } + + if (current) { + TYPE_TOKEN(¤t->token, TOKEN_GROUP); + continue; + } + + error = "Unmatched ')' in \"%s\" in file %s"; + break; + + case TOKEN_NOT: + case TOKEN_ACCESS: + case TOKEN_LBRACE: + switch (current->token.type) { + case TOKEN_STRING: + case TOKEN_RE: + case TOKEN_RBRACE: + case TOKEN_GROUP: + break; + + default: + current->right = new; + new->parent = current; + current = new; + continue; + } + break; + + default: + break; + } + + *was_error = 1; + return 0; + } + + DEBUG_DUMP_TREE(ctx, root); + return root; +} + +#define PARSE_STRING(r,s) (string_func ? string_func((r),(s)) : (s)) +AP_DECLARE(int) ap_expr_eval(request_rec *r, parse_node_t *root, + int *was_error, backref_t **reptr, + string_func_t string_func, opt_func_t eval_func) +{ + parse_node_t *current = root; + const char *error = NULL; + unsigned int regex = 0; + + /* Evaluate Parse Tree */ + while (current) { + switch (current->token.type) { + case TOKEN_STRING: + current->token.value = PARSE_STRING(r, current->token.value); + current->value = !!*current->token.value; + break; + + case TOKEN_AND: + case TOKEN_OR: + if (!current->left || !current->right) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Invalid expression in file %s", r->filename); + *was_error = 1; + return 0; + } + + if (!current->left->done) { + switch (current->left->token.type) { + case TOKEN_STRING: + current->left->token.value = + PARSE_STRING(r, current->left->token.value); + current->left->value = !!*current->left->token.value; + DEBUG_DUMP_EVAL(ctx, current->left); + current->left->done = 1; + break; + + default: + current = current->left; + continue; + } + } + + /* short circuit evaluation */ + if (!current->right->done && !regex && + ((current->token.type == TOKEN_AND && !current->left->value) || + (current->token.type == TOKEN_OR && current->left->value))) { + current->value = current->left->value; + } + else { + if (!current->right->done) { + switch (current->right->token.type) { + case TOKEN_STRING: + current->right->token.value = + PARSE_STRING(r,current->right->token.value); + current->right->value = !!*current->right->token.value; + DEBUG_DUMP_EVAL(r, current->right); + current->right->done = 1; + break; + + default: + current = current->right; + continue; + } + } + + if (current->token.type == TOKEN_AND) { + current->value = current->left->value && + current->right->value; + } + else { + current->value = current->left->value || + current->right->value; + } + } + break; + + case TOKEN_EQ: + case TOKEN_NE: + if (!current->left || !current->right || + current->left->token.type != TOKEN_STRING || + (current->right->token.type != TOKEN_STRING && + current->right->token.type != TOKEN_RE)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Invalid expression in file %s", r->filename); + *was_error = 1; + return 0; + } + current->left->token.value = + PARSE_STRING(r, current->left->token.value); + current->right->token.value = + PARSE_STRING(r, current->right->token.value); + + if (current->right->token.type == TOKEN_RE) { + current->value = re_check(r, current->left->token.value, + current->right->token.value, reptr); + --regex; + } + else { + current->value = !strcmp(current->left->token.value, + current->right->token.value); + } + + if (current->token.type == TOKEN_NE) { + current->value = !current->value; + } + break; + + case TOKEN_GE: + case TOKEN_GT: + case TOKEN_LE: + case TOKEN_LT: + if (!current->left || !current->right || + current->left->token.type != TOKEN_STRING || + current->right->token.type != TOKEN_STRING) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Invalid expression in file %s", r->filename); + *was_error = 1; + return 0; + } + + current->left->token.value = + PARSE_STRING(r, current->left->token.value); + current->right->token.value = + PARSE_STRING(r, current->right->token.value); + + current->value = strcmp(current->left->token.value, + current->right->token.value); + + switch (current->token.type) { + case TOKEN_GE: current->value = current->value >= 0; break; + case TOKEN_GT: current->value = current->value > 0; break; + case TOKEN_LE: current->value = current->value <= 0; break; + case TOKEN_LT: current->value = current->value < 0; break; + default: current->value = 0; break; /* should not happen */ + } + break; + + case TOKEN_NOT: + case TOKEN_GROUP: + if (current->right) { + if (!current->right->done) { + current = current->right; + continue; + } + current->value = current->right->value; + } + else { + current->value = 1; + } + + if (current->token.type == TOKEN_NOT) { + current->value = !current->value; + } + break; + case TOKEN_ACCESS: + if (eval_func) { + *was_error = eval_func(r, current, string_func); + if (*was_error) { + return 0; + } + } + break; + + case TOKEN_RE: + if (!error) { + error = "No operator before regex in file %s"; + } + case TOKEN_LBRACE: + if (!error) { + error = "Unmatched '(' in file %s"; + } + default: + if (!error) { + error = "internal parser error in file %s"; + } + + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, r->filename); + *was_error = 1; + return 0; + } + + DEBUG_DUMP_EVAL(r, current); + current->done = 1; + current = current->parent; + } + + return (root ? root->value : 0); +} +AP_DECLARE(int) ap_expr_evalstring(request_rec *r, const char *expr, + int *was_error, backref_t **reptr, + string_func_t string_func, + opt_func_t eval_func) +{ + parse_node_t *root = ap_expr_parse(r->pool, expr, was_error); + if (*was_error || !root) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Error parsing expression in %s", r->filename); + return 0; + } + return ap_expr_eval(r, root, was_error, reptr, string_func, eval_func); +} |