diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/var-expand-if.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/lib/var-expand-if.c b/src/lib/var-expand-if.c new file mode 100644 index 0000000..aa0a9b3 --- /dev/null +++ b/src/lib/var-expand-if.c @@ -0,0 +1,269 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "str.h" +#include "var-expand.h" +#include "var-expand-private.h" +#include "wildcard-match.h" + +#include <regex.h> + +enum var_expand_if_op { + OP_UNKNOWN, + OP_NUM_EQ, + OP_NUM_LT, + OP_NUM_LE, + OP_NUM_GT, + OP_NUM_GE, + OP_NUM_NE, +/* put all numeric comparisons before this line */ + OP_STR_EQ, + OP_STR_LT, + OP_STR_LE, + OP_STR_GT, + OP_STR_GE, + OP_STR_NE, + OP_STR_LIKE, + OP_STR_NOT_LIKE, + OP_STR_REGEXP, + OP_STR_NOT_REGEXP, +/* keep this as last */ + OP_COUNT +}; + +static enum var_expand_if_op var_expand_if_str_to_comp(const char *op) +{ + const char *ops[OP_COUNT] = { + NULL, + "==", + "<", + "<=", + ">", + ">=", + "!=", + "eq", + "lt", + "le", + "gt", + "ge", + "ne", + "*", + "!*", + "~", + "!~", + }; + for(enum var_expand_if_op i = 1; i < OP_COUNT; i++) { + i_assert(ops[i] != NULL); + if (strcmp(op, ops[i]) == 0) + return i; + } + return OP_UNKNOWN; +} + +static int var_expand_if_comp(const char *lhs, const char *_op, const char *rhs, + bool *result_r, const char **error_r) +{ + bool neg = FALSE; + enum var_expand_if_op op = var_expand_if_str_to_comp(_op); + + *result_r = FALSE; + if (op == OP_UNKNOWN) { + *error_r = t_strdup_printf("if: Unsupported comparator '%s'", _op); + return -1; + } + + if (op < OP_STR_EQ) { + intmax_t a; + intmax_t b; + if (str_to_intmax(lhs, &a) < 0) { + *error_r = t_strdup_printf("if: %s (lhs) is not a number", lhs); + return -1; + } + if (str_to_intmax(rhs, &b) < 0) { + *error_r = t_strdup_printf("if: %s (rhs) is not a number", rhs); + return -1; + } + switch(op) { + case OP_NUM_EQ: + *result_r = a==b; + return 0; + case OP_NUM_LT: + *result_r = a<b; + return 0; + case OP_NUM_LE: + *result_r = a<=b; + return 0; + case OP_NUM_GT: + *result_r = a>b; + return 0; + case OP_NUM_GE: + *result_r = a>=b; + return 0; + case OP_NUM_NE: + *result_r = a!=b; + return 0; + default: + i_panic("Missing numeric comparator %u", op); + } + } + + switch(op) { + case OP_STR_EQ: + *result_r = strcmp(lhs,rhs)==0; + return 0; + case OP_STR_LT: + *result_r = strcmp(lhs,rhs)<0; + return 0; + case OP_STR_LE: + *result_r = strcmp(lhs,rhs)<=0; + return 0; + case OP_STR_GT: + *result_r = strcmp(lhs,rhs)>0; + return 0; + case OP_STR_GE: + *result_r = strcmp(lhs,rhs)>=0; + return 0; + case OP_STR_NE: + *result_r = strcmp(lhs,rhs)!=0; + return 0; + case OP_STR_LIKE: + *result_r = wildcard_match(lhs, rhs); + return 0; + case OP_STR_NOT_LIKE: + *result_r = !wildcard_match(lhs, rhs); + return 0; + case OP_STR_NOT_REGEXP: + neg = TRUE; + /* fall through */ + case OP_STR_REGEXP: { + int ec; + bool res; + regex_t reg; + if ((ec = regcomp(®, rhs, REG_EXTENDED)) != 0) { + size_t siz; + char *errbuf; + siz = regerror(ec, ®, NULL, 0); + errbuf = t_malloc_no0(siz); + (void)regerror(ec, ®, errbuf, siz); + *error_r = t_strdup_printf("if: regex failed: %s", + errbuf); + return -1; + } + if ((ec = regexec(®, lhs, 0, 0, 0)) != 0) { + i_assert(ec == REG_NOMATCH); + res = FALSE; + } else { + res = TRUE; + } + regfree(®); + /* this should be same as neg. + if NOT_REGEXP, neg == TRUE and res should be FALSE + if REGEXP, ned == FALSE, and res should be TRUE + */ + *result_r = res != neg; + return 0; + } + default: + i_panic("Missing generic comparator %u", op); + } +} + +int var_expand_if(struct var_expand_context *ctx, + const char *key, const char *field, + const char **result_r, const char **error_r) +{ + /* in case the original input had :, we need to fix that + by concatenating the key and field together. */ + const char *input = t_strconcat(key, ":", field, NULL); + const char *p = strchr(input, ';'); + const char *par_end; + string_t *parbuf; + const char *const *parms; + unsigned int depth = 0; + int ret; + bool result, escape = FALSE, maybe_var = FALSE; + + if (p == NULL) { + *error_r = "if: missing parameter(s)"; + return -1; + } + ARRAY_TYPE(const_string) params; + t_array_init(¶ms, 6); + + parbuf = t_str_new(64); + /* we need to skip any %{} parameters here, so we can split the string + correctly from , without breaking any inner expansions */ + for(par_end = p+1; *par_end != '\0'; par_end++) { + if (*par_end == '\\') { + escape = TRUE; + continue; + } else if (escape) { + str_append_c(parbuf, *par_end); + escape = FALSE; + continue; + } + if (*par_end == '%') { + maybe_var = TRUE; + } else if (maybe_var && *par_end == '{') { + depth++; + maybe_var = FALSE; + } else if (depth > 0 && *par_end == '}') { + depth--; + } else if (depth == 0 && *par_end == ';') { + const char *par = str_c(parbuf); + array_push_back(¶ms, &par); + parbuf = t_str_new(64); + continue; + /* if there is a unescaped : at top level it means + that the key + arguments end here. it's probably + a by-product of the t_strconcat at top of function, + which is best handled here. */ + } else if (depth == 0 && *par_end == ':') { + break; + } + str_append_c(parbuf, *par_end); + } + + if (str_len(parbuf) > 0) { + const char *par = str_c(parbuf); + array_push_back(¶ms, &par); + } + + if (array_count(¶ms) != 5) { + if (array_count(¶ms) == 4) { + const char *empty = ""; + array_push_back(¶ms, &empty); + } else { + *error_r = t_strdup_printf("if: requires four or five parameters, got %u", + array_count(¶ms)); + return -1; + } + } + + array_append_zero(¶ms); + parms = array_front(¶ms); + t_array_init(¶ms, 6); + + for(;*parms != NULL; parms++) { + /* expand the parameters */ + string_t *param = t_str_new(64); + if ((ret = var_expand_with_funcs(param, *parms, ctx->table, + ctx->func_table, ctx->context, + error_r)) <= 0) { + return ret; + } + const char *p = str_c(param); + array_push_back(¶ms, &p); + } + + i_assert(array_count(¶ms) == 5); + + /* execute comparison */ + const char *const *args = array_front(¶ms); + if (var_expand_if_comp(args[0], args[1], args[2], &result, error_r)<0) + return -1; + *result_r = result ? args[3] : args[4]; + return 1; +} + |