]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
var-expand: Add support for conditionals
authorAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 24 Jul 2017 10:31:02 +0000 (13:31 +0300)
committerAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 24 Jul 2017 12:01:23 +0000 (15:01 +0300)
%{if;value-a,op,value-b:true-value:false-value}

src/lib/Makefile.am
src/lib/var-expand-if.c [new file with mode: 0644]
src/lib/var-expand-private.h
src/lib/var-expand.c

index 915fa61e423c92b35532701ecf583dd4ecd8ed1e..f1dbf56842051a4d01deeb21b0649de4975e4596 100644 (file)
@@ -155,6 +155,7 @@ liblib_la_SOURCES = \
        utc-offset.c \
        utc-mktime.c \
        var-expand.c \
+       var-expand-if.c \
        wildcard-match.c \
        write-full.c
 
diff --git a/src/lib/var-expand-if.c b/src/lib/var-expand-if.c
new file mode 100644 (file)
index 0000000..d3bb633
--- /dev/null
@@ -0,0 +1,268 @@
+/* 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 <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);
+       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;
+       case OP_STR_REGEXP: {
+               int ec;
+               bool res;
+               regex_t reg;
+               if ((ec = regcomp(&reg, rhs, REG_EXTENDED)) != 0) {
+                       size_t siz;
+                       char *errbuf;
+                       siz = regerror(ec, &reg, NULL, 0);
+                       errbuf = t_malloc_no0(siz);
+                       (void)regerror(ec, &reg, errbuf, siz);
+                       *error_r = t_strdup_printf("if: regex failed: %s",
+                                                  errbuf);
+                       return -1;
+               }
+               if ((ec = regexec(&reg, lhs, 0, 0, 0)) != 0) {
+                       i_assert(ec == REG_NOMATCH);
+                       res = FALSE;
+               } else {
+                       res = TRUE;
+               }
+               regfree(&reg);
+               /* 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;
+       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(&params, 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(&params, &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(&params, &par, 1);
+       }
+
+       if (array_count(&params) != 5) {
+               if (array_count(&params) == 4) {
+                       const char *empty = "";
+                       array_append(&params, &empty, 1);
+               } else {
+                       *error_r = t_strdup_printf("if: requires four or five parameters, got %u",
+                                                  array_count(&params));
+                       return -1;
+               }
+       }
+
+       array_append_zero(&params);
+       parms = array_idx(&params, 0);
+       t_array_init(&params, 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_append(&params, &p, 1);
+       }
+
+       i_assert(array_count(&params) == 5);
+
+       /* execute comparison */
+       const char *const *args = array_idx(&params, 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;
+}
+
index e7a6ed361b75ce46609e70bdd33153251ae0489a..f02356a322de305769b6c87d1112d81427faabf2 100644 (file)
@@ -49,4 +49,8 @@ void var_expand_extensions_deinit(void);
 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
index d1194dacefbd3075d28c5dcda7d47022205fd7e4..cdcb5da4dc41d61dc611b1fa091d7d6379a15238 100644 (file)
@@ -743,6 +743,11 @@ void var_expand_extensions_init(void)
                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