# HG changeset patch # User Aki Tuomi # Date 1500892262 -10800 # Node ID 3bae5694a7a5a32bd127ea555e9a0b661db1d3d3 # Parent 0819f28fc76b86faf89658074e3ebe5d387eaccf var-expand: Add support for conditionals %{if;value-a,op,value-b:true-value:false-value} diff -r 0819f28fc76b -r 3bae5694a7a5 src/lib/Makefile.am --- a/src/lib/Makefile.am Mon Oct 19 13:49:54 2015 +0300 +++ b/src/lib/Makefile.am Mon Jul 24 13:31:02 2017 +0300 @@ -152,6 +152,7 @@ utc-offset.c \ utc-mktime.c \ var-expand.c \ + var-expand-if.c \ wildcard-match.c \ write-full.c diff -r 0819f28fc76b -r 3bae5694a7a5 src/lib/var-expand-if.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/var-expand-if.c Mon Jul 24 13:31:02 2017 +0300 @@ -0,0 +1,263 @@ +/* Copyright (c) 2003-2017 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 + +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); + 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 = ab; + 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; + 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(siz); + (void)regerror(ec, ®, errbuf, siz); + i_error("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_start, *par_end; + string_t *parbuf; + const char *const *parms; + unsigned int depth = 0; + 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); + par_start = p+1; + + 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 = par_start; *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_append(¶ms, &par, 1); + par_start = par_end + 1; + 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_append(¶ms, &par, 1); + } + + if (array_count(¶ms) != 5) { + if (array_count(¶ms) == 4) { + const char *empty = ""; + array_append(¶ms, &empty, 1); + } 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_idx(¶ms, 0); + t_array_init(¶ms, 6); + + for(;*parms != NULL; parms++) { + /* expand the parameters */ + string_t *param = t_str_new(64); + var_expand_with_funcs(param, *parms, ctx->table, + ctx->func_table, ctx->context); + const char *p = str_c(param); + array_append(¶ms, &p, 1); + } + + i_assert(array_count(¶ms) == 5); + + /* execute comparison */ + const char *const *args = array_idx(¶ms, 0); + 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; +} + diff -r 0819f28fc76b -r 3bae5694a7a5 src/lib/var-expand-private.h --- a/src/lib/var-expand-private.h Mon Oct 19 13:49:54 2015 +0300 +++ b/src/lib/var-expand-private.h Mon Jul 24 13:31:02 2017 +0300 @@ -49,4 +49,8 @@ void var_expand_register_func_array(const struct var_expand_extension_func_table *funcs); void var_expand_unregister_func_array(const struct var_expand_extension_func_table *funcs); +int var_expand_if(struct var_expand_context *ctx, + const char *key, const char *field, + const char **result_r, const char **error_r); + #endif diff -r 0819f28fc76b -r 3bae5694a7a5 src/lib/var-expand.c --- a/src/lib/var-expand.c Mon Oct 19 13:49:54 2015 +0300 +++ b/src/lib/var-expand.c Mon Jul 24 13:31:02 2017 +0300 @@ -728,6 +728,11 @@ array_append_space(&var_expand_extensions); func->key = "pkcs5"; func->func = var_expand_hash; + + /* if */ + func = array_append_space(&var_expand_extensions); + func->key = "if"; + func->func = var_expand_if; } void