/* 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_pcalloc(pool, sizeof(*(name))); } 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) { \ ap_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, ap_parse_node_t *root) { ap_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_pcalloc(pool, sizeof(*(name))); \ } 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 ? *reptr : NULL; 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 = apr_palloc(r->pool, sizeof(*re)); if (reptr) { *reptr = re; } } re->source = apr_pstrdup(r->pool, string); re->rexp = apr_pstrdup(r->pool, rexp); re->nsub = compiled->re_nsub; re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH, re->match, 0); ap_pregfree(r->pool, compiled); return re->have_match; } 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(ap_parse_node_t*) ap_expr_parse(apr_pool_t* pool, const char *expr, int *was_error) { ap_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; } static ap_parse_node_t *ap_expr_clone_tree(apr_pool_t *pool, ap_parse_node_t *pnode, ap_parse_node_t *parent) { ap_parse_node_t *ret; ret = apr_pmemdup(pool, pnode, sizeof(ap_parse_node_t)); if (pnode->left) { ret->left = ap_expr_clone_tree(pool, pnode->left, ret); } if (pnode->right) { ret->right = ap_expr_clone_tree(pool, pnode->right, ret); } ret->parent = parent; return ret; } #define PARSE_STRING(r,s) (string_func ? string_func((r),(s)) : (s)) static int expr_eval(request_rec *r, ap_parse_node_t *root, int *was_error, backref_t **reptr, string_func_t string_func, opt_func_t eval_func) { ap_parse_node_t *current = root; const char *error = NULL; unsigned int regex = 0; const char *val; const char *lval; const char *rval; /* Evaluate Parse Tree */ while (current) { switch (current->token.type) { case TOKEN_STRING: val = PARSE_STRING(r, current->token.value); current->value = !!*val; 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: lval = PARSE_STRING(r, current->left->token.value); current->left->value = !!*lval; 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: rval = PARSE_STRING(r,current->right->token.value); current->right->value = !!*rval; 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; } lval = PARSE_STRING(r, current->left->token.value); rval = PARSE_STRING(r, current->right->token.value); if (current->right->token.type == TOKEN_RE) { current->value = re_check(r, lval, rval, reptr); --regex; } else { current->value = !strcmp(lval, rval); } 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; } lval = PARSE_STRING(r, current->left->token.value); rval = PARSE_STRING(r, current->right->token.value); current->value = strcmp(lval, rval); 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_eval(request_rec *r, ap_parse_node_t *root, int *was_error, backref_t **reptr, string_func_t string_func, opt_func_t eval_func) { ap_parse_node_t *clone; if (root == NULL) { /* no condition == unconditional */ return 1; } clone = ap_expr_clone_tree(r->pool, root, NULL); return expr_eval(r, clone, was_error, reptr, string_func, eval_func); } 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) { ap_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 expr_eval(r, root, was_error, reptr, string_func, eval_func); } static ap_regex_t *isvar = NULL; AP_DECLARE_NONSTD(const char*) ap_expr_string(request_rec *r, const char *str) { /* a default string evaluator: support headers and env */ const char *ret = str; ap_regmatch_t match[3]; ap_assert(isvar != NULL); if (ap_regexec(isvar, str, 3, match, 0) == 0) { apr_table_t *table = NULL; int len = match[1].rm_eo-match[1].rm_so; const char *name = str+match[1].rm_so; if (!strncasecmp("req", name, len)) { table = r->headers_in; } else if (!strncasecmp("resp", name, len)) { table = r->headers_out; } else if (!strncasecmp("env", name, len)) { table = r->subprocess_env; } if (table != NULL) { char *key = apr_pstrndup(r->pool, str+match[2].rm_so, match[2].rm_eo-match[2].rm_so); ret = apr_table_get(table, key); } } else if (str[0] == '$') { if (!strcasecmp(str, "$handler")) { ret = r->handler; } else if (!strcasecmp(str, "$content-type")) { ret = r->content_type; } } /* TODO: provide a hook so modules can interpret other patterns */ /* OhBugger, where's the regexp for backreferences ? */ if (!ret) { ret = ""; } return ret; /* default - literal string as-is */ } static apr_status_t ap_expr_term(void *expr) { if (isvar) { ap_regfree(isvar); isvar = NULL; } return APR_SUCCESS; } AP_DECLARE(apr_status_t) ap_expr_init(apr_pool_t *pool) { static ap_regex_t var; if (!isvar) { isvar = &var; if (ap_regcomp(isvar, "\\$([A-Za-z0-9]+)\\{([^\\}]+)\\}", 0)) { isvar = NULL; } else { apr_pool_cleanup_register(pool, isvar, ap_expr_term, apr_pool_cleanup_null); } } return isvar ? APR_SUCCESS : APR_EGENERAL; }