Commit 79951953 799519539556b38fc9f84d44927861cb9239d14c by Sergey Poznyakoff

Implement the "variables" Sieve extension (RFC 5229)

* include/mailutils/sieve.h (mu_sieve_string): New fields
"constant" and "changed".
(mu_sieve_match_part_tags): New extern.
(mu_sieve_relational_count)
(mu_sieve_require_variables)
(mu_sieve_has_variables)
(mu_sieve_string_get): New functions.
* libmu_sieve/variables.c: New file.
* libmu_sieve/Makefile.am: Add variables.c
* libmu_sieve/comparator.c: Use mu_sieve_string_get to obtain
the actual value of the string.
* libmu_sieve/require.c: Support the "variables" extension.
* libmu_sieve/sieve-priv.h (mu_sieve_machine): New fields
vartab, match_string, match_buf, match_count, match_max.
(mu_i_sv_copy_variables)
(mu_i_sv_expand_variables): New protos.
* libmu_sieve/sieve.l (line_add): zero length means add entire
asciiz string.
* libmu_sieve/sieve.y (mu_sieve_machine_reset): Reset the new
fields.
(mu_sieve_machine_clone): Copy variables and initialize new
fields.
(string_rescan): New function.
(sieve_parse): Rescan string to determine their properties.
* libmu_sieve/strexp.c (update_len): Allow for NULL replacement
values.
* libmu_sieve/string.c (mu_sieve_string_get): New function.
(mu_sieve_string): Use it.
* libmu_sieve/tests.c (do_count): Rename to mu_sieve_relational_count,
make global. All uses changed.
(match_part_tags): Rename to mu_sieve_match_part_tags, make global.
All uses changed.

* sieve/tests/variables.at: New file.
* sieve/tests/Makefile.am: Add new testcases.
* sieve/tests/testsuite.at: Likewise.
1 parent cab2fd5c
......@@ -35,9 +35,11 @@ typedef struct mu_sieve_machine *mu_sieve_machine_t;
typedef struct mu_sieve_string
{
char *orig;
char *exp;
void *rx;
unsigned constant:1; /* String is constant */
unsigned changed:1; /* String value has changed */
char *orig; /* String original value */
char *exp; /* Actual string value after expansion */
void *rx; /* Pointer to the corresponding regular expr */
} mu_sieve_string_t;
typedef int (*mu_sieve_handler_t) (mu_sieve_machine_t mach);
......@@ -141,7 +143,9 @@ extern mu_debug_handle_t mu_sieve_debug_handle;
extern mu_list_t mu_sieve_include_path;
extern mu_list_t mu_sieve_library_path;
extern mu_list_t mu_sieve_library_path_prefix;
extern mu_sieve_tag_def_t mu_sieve_match_part_tags[];
/* Memory allocation functions */
typedef void (*mu_sieve_reclaim_t) (void *);
void mu_sieve_register_memory (mu_sieve_machine_t mach, void *ptr,
......@@ -197,6 +201,11 @@ void mu_sieve_register_comparator (mu_sieve_machine_t mach, const char *name,
mu_sieve_comparator_t eq);
int mu_sieve_require_relational (mu_sieve_machine_t mach, const char *name);
int mu_sieve_relational_count (mu_sieve_machine_t mach, size_t count,
int retval);
int mu_sieve_require_variables (mu_sieve_machine_t mach);
int mu_sieve_has_variables (mu_sieve_machine_t mach);
void *mu_sieve_load_ext (mu_sieve_machine_t mach, const char *name);
void mu_sieve_unload_ext (void *handle);
......@@ -234,9 +243,10 @@ void mu_sieve_get_arg (mu_sieve_machine_t mach, size_t index,
char *mu_sieve_string (mu_sieve_machine_t mach,
mu_sieve_slice_t slice,
size_t i);
struct mu_sieve_string *mu_sieve_string_raw (mu_sieve_machine_t mach,
mu_sieve_slice_t slice,
size_t i);
mu_sieve_string_t *mu_sieve_string_raw (mu_sieve_machine_t mach,
mu_sieve_slice_t slice,
size_t i);
char *mu_sieve_string_get (mu_sieve_machine_t mach, mu_sieve_string_t *string);
/* Operations on value lists */
int mu_sieve_vlist_do (mu_sieve_machine_t mach,
......
......@@ -44,7 +44,8 @@ libmu_sieve_la_SOURCES = \
strexp.c\
string.c\
tests.c\
util.c
util.c\
variables.c
libmu_sieve_la_LIBADD = ${MU_LIB_MAILUTILS} @LTDL_LIB@
libmu_sieve_la_LDFLAGS = -version-info @VI_CURRENT@:@VI_REVISION@:@VI_AGE@
......
......@@ -79,11 +79,20 @@ compile_pattern (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, int flags)
{
int rc;
regex_t *preg;
char *str;
str = mu_sieve_string_get (mach, pattern);
if (pattern->rx)
return;
preg = mu_sieve_malloc (mach, sizeof (*preg));
rc = regcomp (preg, pattern->orig, REG_EXTENDED | flags);
{
if (!pattern->changed)
return;
preg = pattern->rx;
regfree (preg);
}
else
preg = mu_sieve_malloc (mach, sizeof (*preg));
rc = regcomp (preg, str, REG_EXTENDED | flags);
if (rc)
{
size_t size = regerror (rc, preg, NULL, 0);
......@@ -107,11 +116,23 @@ compile_wildcard (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
{
int rc;
regex_t *preg;
char *str;
str = mu_sieve_string_get (mach, pattern);
if (pattern->rx)
return;
preg = mu_sieve_malloc (mach, sizeof (*preg));
rc = mu_glob_compile (preg, pattern->orig, flags);
{
if (!pattern->changed)
return;
preg = pattern->rx;
regfree (preg);
}
else
preg = mu_sieve_malloc (mach, sizeof (*preg));
if (mu_sieve_has_variables (mach))
flags |= MU_GLOBF_SUB;
rc = mu_glob_compile (preg, str, flags);
if (rc)
{
mu_sieve_error (mach, _("can't compile pattern"));
......@@ -184,7 +205,7 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
if (strcmp (match->tag, "count") == 0)
{
mu_sieve_value_t *val;
char *str;
mu_sieve_string_t *argstr;
if (compname && strcmp (compname, "i;ascii-numeric"))
{
......@@ -213,14 +234,17 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
mu_i_sv_error (mach);
return 1;
}
str = mu_sieve_string_raw (mach, &val->v.list, 0)->orig;
str = mu_str_skip_class (str, MU_CTYPE_DIGIT);
if (*str)
argstr = mu_sieve_string_raw (mach, &val->v.list, 0);
if (argstr->constant)
{
mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
_("second argument cannot be converted to number"));
mu_i_sv_error (mach);
return 1;
char *p = mu_str_skip_class (argstr->orig, MU_CTYPE_DIGIT);
if (*p)
{
mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
_("second argument cannot be converted to number"));
mu_i_sv_error (mach);
return 1;
}
}
}
else
......@@ -258,7 +282,30 @@ mu_sieve_match_part_checker (mu_sieve_machine_t mach)
return 0;
}
static int
regmatch (mu_sieve_machine_t mach, mu_sieve_string_t *pattern, char const *text)
{
regex_t *reg = pattern->rx;
regmatch_t *match_buf = NULL;
size_t match_count = 0;
if (mu_sieve_has_variables (mach))
{
match_count = reg->re_nsub + 1;
while (mach->match_max < match_count)
mu_i_sv_2nrealloc (mach, (void **) &mach->match_buf,
&mach->match_max,
sizeof (mach->match_buf[0]));
mach->match_count = match_count;
mu_sieve_free (mach, mach->match_string);
mach->match_string = mu_sieve_strdup (mach, text);
match_buf = mach->match_buf;
}
return regexec (reg, text, match_count, match_buf, 0) == 0;
}
/* Particular comparators */
/* :comparator i;octet */
......@@ -267,14 +314,14 @@ static int
i_octet_is (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
return strcmp (pattern->orig, text) == 0;
return strcmp (mu_sieve_string_get (mach, pattern), text) == 0;
}
static int
i_octet_contains (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
return strstr (text, pattern->orig) != NULL;
return strstr (text, mu_sieve_string_get (mach, pattern)) != NULL;
}
static int
......@@ -282,7 +329,7 @@ i_octet_matches (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
compile_wildcard (mach, pattern, 0);
return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
return regmatch (mach, pattern, text);
}
static int
......@@ -290,14 +337,14 @@ i_octet_regex (mu_sieve_machine_t mach, mu_sieve_string_t *pattern,
const char *text)
{
compile_pattern (mach, pattern, 0);
return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
return regmatch (mach, pattern, text);
}
static int
i_octet_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return strcmp (text, pattern->orig);
return strcmp (text, mu_sieve_string_get (mach, pattern));
}
/* :comparator i;ascii-casemap */
......@@ -305,14 +352,14 @@ static int
i_ascii_casemap_is (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasecmp (pattern->orig, text) == 0;
return mu_c_strcasecmp (mu_sieve_string_get (mach, pattern), text) == 0;
}
static int
i_ascii_casemap_contains (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasestr (text, pattern->orig) != NULL;
return mu_c_strcasestr (text, mu_sieve_string_get (mach, pattern)) != NULL;
}
static int
......@@ -320,7 +367,7 @@ i_ascii_casemap_matches (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
compile_wildcard (mach, pattern, MU_GLOBF_ICASE);
return regexec ((regex_t *)pattern->rx, text, 0, NULL, 0) == 0;
return regmatch (mach, pattern, text);
}
static int
......@@ -328,14 +375,14 @@ i_ascii_casemap_regex (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
compile_pattern (mach, pattern, REG_ICASE);
return regexec ((regex_t *) pattern->rx, text, 0, NULL, 0) == 0;
return regmatch (mach, pattern, text);
}
static int
i_ascii_casemap_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
return mu_c_strcasecmp (text, pattern->orig);
return mu_c_strcasecmp (text, mu_sieve_string_get (mach, pattern));
}
/* :comparator i;ascii-numeric */
......@@ -343,11 +390,12 @@ static int
i_ascii_numeric_is (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
if (mu_isdigit (*pattern->orig))
char *str = mu_sieve_string_get (mach, pattern);
if (mu_isdigit (*str))
{
if (mu_isdigit (*text))
//FIXME: Error checking
return strtol (pattern->orig, NULL, 10) == strtol (text, NULL, 10);
return strtol (str, NULL, 10) == strtol (text, NULL, 10);
else
return 0;
}
......@@ -361,11 +409,12 @@ static int
i_ascii_numeric_eq (mu_sieve_machine_t mach,
mu_sieve_string_t *pattern, const char *text)
{
if (mu_isdigit (*pattern->orig))
char *str = mu_sieve_string_get (mach, pattern);
if (mu_isdigit (*str))
{
if (mu_isdigit (*text))
{
size_t a = strtoul (pattern->orig, NULL, 10);
size_t a = strtoul (str, NULL, 10);
size_t b = strtoul (text, NULL, 10);
if (b > a)
return 1;
......
......@@ -37,7 +37,9 @@ mu_sieve_require (mu_sieve_machine_t mach, mu_sieve_slice_t list)
char *name = str->orig;
int rc;
if (strcmp (name, "relational") == 0) /* RFC 3431 */
if (strcmp (name, "variables") == 0) /* RFC 5229 */
rc = mu_sieve_require_variables (mach);
else if (strcmp (name, "relational") == 0) /* RFC 3431 */
rc = mu_sieve_require_relational (mach, name);
else if (strcmp (name, "encoded-character") == 0) /* RFC 5228, 2.4.2.4 */
rc = mu_sieve_require_encoded_character (mach, name);
......@@ -52,8 +54,7 @@ mu_sieve_require (mu_sieve_machine_t mach, mu_sieve_slice_t list)
if (rc)
{
mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
_("can't require %s is not available"),
mu_diag_at_locus (MU_LOG_ERROR, &mach->locus, _("can't require %s"),
name);
mu_i_sv_error (mach);
}
......
......@@ -228,7 +228,10 @@ sieve_run (mu_sieve_machine_t mach)
if (rc == 0)
{
mach->action_count = 0;
if (mu_sieve_has_variables (mach))
mu_assoc_clear (mach->vartab);
for (mach->pc = 1; mach->prog[mach->pc].handler; )
(*mach->prog[mach->pc++].instr) (mach);
......
......@@ -17,6 +17,7 @@
<http://www.gnu.org/licenses/>. */
#include <mailutils/sieve.h>
#include <mailutils/assoc.h>
#include <setjmp.h>
#include <string.h>
#include <regex.h>
......@@ -90,13 +91,20 @@ struct mu_sieve_machine
size_t pc; /* Current program counter */
long reg; /* Numeric register */
/* Support for variables (RFC 5229) */
mu_assoc_t vartab; /* Table of variables */
char *match_string; /* The string used in the most recent match */
regmatch_t *match_buf; /* Offsets of parenthesized groups */
size_t match_count; /* Actual number of elements used in match_buf */
size_t match_max; /* Total number of elements available in match_buf */
/* Call environment */
const char *identifier; /* Name of action or test being executed */
size_t argstart; /* Index of the first argument in valspace */
size_t argcount; /* Number of positional arguments */
size_t tagcount; /* Number of tagged arguments */
mu_sieve_comparator_t comparator; /* Comparator (for tests) */
int dry_run; /* Dry-run mode */
jmp_buf errbuf; /* Target location for non-local exits */
......@@ -233,4 +241,8 @@ size_t mu_i_sv_id_num (mu_sieve_machine_t mach, char const *name);
char *mu_i_sv_id_str (mu_sieve_machine_t mach, size_t n);
void mu_i_sv_free_idspace (mu_sieve_machine_t mach);
void mu_i_sv_copy_variables (mu_sieve_machine_t child,
mu_sieve_machine_t parent);
int mu_i_sv_expand_variables (char const *input, size_t len,
char **exp, void *data);
......
......@@ -529,6 +529,8 @@ multiline_strip_tabs (char *text)
static void
line_add (char *text, size_t len)
{
if (len == 0)
len = strlen (text);
mu_opool_append (mu_sieve_machine->string_pool, text, len);
}
......
......@@ -1067,6 +1067,12 @@ mu_sieve_machine_reset (mu_sieve_machine_t mach)
mach->progsize = 0;
mach->prog = NULL;
mu_assoc_destroy (&mach->vartab);
mach->match_string = NULL;
mach->match_buf = NULL;
mach->match_count = 0;
mach->match_max = 0;
mach->state = mu_sieve_state_init;
return 0;
......@@ -1174,6 +1180,16 @@ mu_sieve_machine_clone (mu_sieve_machine_t const parent,
sizeof child->prog[0]);
memcpy (child->prog, parent->prog,
parent->progsize * sizeof (child->prog[0]));
/* Copy variables */
if (mu_sieve_has_variables (parent))
{
mu_i_sv_copy_variables (child, parent);
child->match_string = NULL;
child->match_buf = NULL;
child->match_count = 0;
child->match_max = 0;
}
/* Copy user-defined settings */
......@@ -1468,6 +1484,30 @@ with_machine (mu_sieve_machine_t mach, char const *name,
return rc;
}
/* Rescan all registered strings to determine their properties */
static void
string_rescan (mu_sieve_machine_t mach)
{
size_t i;
int hasvar = mu_sieve_has_variables (mach);
for (i = 0; i < mach->stringcount; i++)
{
mach->stringspace[i].changed = 0;
if (hasvar)
{
mach->stringspace[i].constant = 0;
mu_sieve_string_get (mach, &mach->stringspace[i]);
mu_sieve_free (mach, mach->stringspace[i].exp);
mach->stringspace[i].exp = NULL;
mach->stringspace[i].constant = !mach->stringspace[i].changed;
mach->stringspace[i].changed = 0;
}
else
mach->stringspace[i].constant = 1;
}
}
static int
sieve_parse (void)
{
......@@ -1509,7 +1549,10 @@ sieve_parse (void)
if (mu_sieve_machine->state == mu_sieve_state_error)
rc = MU_ERR_PARSE;
else
mu_sieve_machine->state = mu_sieve_state_compiled;
{
string_rescan (mu_sieve_machine);
mu_sieve_machine->state = mu_sieve_state_compiled;
}
}
tree_free (&sieve_tree);
......
......@@ -155,7 +155,8 @@ update_len (void *item, void *data)
break;
case segm_repl:
st->len += strlen (segm->repl);
if (segm->repl)
st->len += strlen (segm->repl);
break;
}
return 0;
......@@ -176,8 +177,13 @@ append_segm (void *item, void *data)
break;
case segm_repl:
len = strlen (segm->repl);
memcpy (buf->endptr, segm->repl, len);
if (segm->repl)
{
len = strlen (segm->repl);
memcpy (buf->endptr, segm->repl, len);
}
else
len = 0;
}
buf->endptr += len;
......
......@@ -44,7 +44,7 @@ mu_i_sv_string_create (mu_sieve_machine_t mach, char *str)
return n;
}
struct mu_sieve_string *
mu_sieve_string_t *
mu_sieve_string_raw (mu_sieve_machine_t mach, mu_sieve_slice_t slice,
size_t i)
{
......@@ -54,8 +54,57 @@ mu_sieve_string_raw (mu_sieve_machine_t mach, mu_sieve_slice_t slice,
}
char *
mu_sieve_string_get (mu_sieve_machine_t mach, mu_sieve_string_t *string)
{
char *exp;
int rc;
if (string->constant)
return string->orig;
rc = mu_i_sv_string_expand (string->orig, mu_i_sv_expand_variables, mach,
&exp);
switch (rc)
{
case 0:
if (string->exp == NULL)
{
string->changed = strcmp (string->orig, exp) != 0;
string->exp = mu_sieve_strdup (mach, exp);
free (exp);
}
else if (strcmp (exp, string->exp) == 0)
{
string->changed = 0;
free (exp);
}
else
{
string->changed = 1;
mu_sieve_free (mach, string->exp);
string->exp = mu_sieve_strdup (mach, exp);
free (exp);
}
break;
case MU_ERR_CANCELED:
string->changed = 0;
return string->orig;
default:
mu_sieve_error (mach, "error expanding variables: %s",
mu_strerror (rc));
mu_sieve_abort (mach);
}
return string->exp;
}
char *
mu_sieve_string (mu_sieve_machine_t mach, mu_sieve_slice_t slice,
size_t i)
{
return mu_sieve_string_raw (mach, slice, i)->orig;
return mu_sieve_string_get (mach, mu_sieve_string_raw (mach, slice, i));
}
......
......@@ -55,21 +55,26 @@ struct address_closure
mu_address_t addr; /* Obtained address */
};
static int
do_count (mu_sieve_machine_t mach, size_t count, int retval)
int
mu_sieve_relational_count (mu_sieve_machine_t mach, size_t count, int retval)
{
char *relcmp;
if (mu_sieve_get_tag (mach, "count", SVT_STRING, &relcmp))
{
size_t limit;
char *str;
char *str, *p;
struct mu_sieve_slice slice;
mu_sieve_relcmpn_t stest;
mu_sieve_get_arg (mach, 1, SVT_STRING_LIST, &slice);
str = mu_sieve_string (mach, &slice, 0);
limit = strtoul (str, &str, 10);
limit = strtoul (str, &p, 10);
if (*p)
{
mu_sieve_error (mach, _("%s: not an integer"), str);
mu_sieve_abort (mach);
}
mu_sieve_str_to_relcmp (relcmp, NULL, &stest);
return stest (count, limit);
......@@ -122,7 +127,7 @@ sieve_test_address (mu_sieve_machine_t mach)
&count);
mu_address_destroy (&clos.addr);
return do_count (mach, count, rc);
return mu_sieve_relational_count (mach, count, rc);
}
struct header_closure
......@@ -196,7 +201,7 @@ sieve_test_header (mu_sieve_machine_t mach)
&count))
return 1;
return do_count (mach, count + mcount, 0);
return mu_sieve_relational_count (mach, count + mcount, 0);
}
int
......@@ -246,7 +251,7 @@ sieve_test_envelope (mu_sieve_machine_t mach)
rc = mu_sieve_vlist_compare (mach, h, v, comp, test, retrieve_envelope, &clos,
&count);
mu_address_destroy (&clos.addr);
return do_count (mach, count, rc);
return mu_sieve_relational_count (mach, count, rc);
}
int
......@@ -301,7 +306,7 @@ static mu_sieve_tag_def_t address_part_tags[] = {
{ NULL }
};
static mu_sieve_tag_def_t match_part_tags[] = {
mu_sieve_tag_def_t mu_sieve_match_part_tags[] = {
{ "is", SVT_VOID },
{ "contains", SVT_VOID },
{ "matches", SVT_VOID },
......@@ -327,7 +332,7 @@ static mu_sieve_tag_def_t mime_tags[] = {
{ address_part_tags, NULL }
#define MATCH_PART_GROUP \
{ match_part_tags, mu_sieve_match_part_checker }
{ mu_sieve_match_part_tags, mu_sieve_match_part_checker }
#define SIZE_GROUP { size_tags, NULL }
......
/* Sieve variables extension (RFC 5229)
Copyright (C) 2016 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
<http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sieve-priv.h>
#include <ctype.h>
struct sieve_variable
{
char *value;
};
/* FIXME: UTF support */
char *
mod_lower (mu_sieve_machine_t mach, char const *value)
{
char *newval = mu_sieve_malloc (mach, strlen (value) + 1);
char *p;
for (p = newval; *value; p++, value++)
*p = tolower (*value);
*p = 0;
return newval;
}
char *
mod_upper (mu_sieve_machine_t mach, char const *value)
{
char *newval = mu_sieve_malloc (mach, strlen (value) + 1);
char *p;
for (p = newval; *value; p++, value++)
*p = toupper (*value);
*p = 0;
return newval;
}
char *
mod_lowerfirst (mu_sieve_machine_t mach, char const *value)
{
char *newval = mu_sieve_strdup (mach, value);
*newval = tolower (*newval);
return newval;
}
char *
mod_upperfirst (mu_sieve_machine_t mach, char const *value)
{
char *newval = mu_sieve_strdup (mach, value);
*newval = toupper (*newval);
return newval;
}
char *
mod_quotewildcard (mu_sieve_machine_t mach, char const *value)
{
size_t len;
char *newval;
char const *p;
char *q;
len = 0;
for (p = value; *p; p++)
{
switch (*p)
{
case '*':
case '?':
case '\\':
len += 2;
break;
default:
len++;
}
}
newval = mu_sieve_malloc (mach, len + 1);
for (p = value, q = newval; *p;)
{
switch (*p)
{
case '*':
case '?':
case '\\':
*q++ = '\\';
}
*q++ = *p++;
}
*q = 0;
return newval;
}
char *
mod_length (mu_sieve_machine_t mach, char const *value)
{
char *newval, *p;
int rc = mu_asprintf (&newval, "%zu", strlen (value));
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_asprintf", NULL, rc);
mu_sieve_abort (mach);
}
p = mu_sieve_strdup (mach, newval);
free (newval);
return p;
}
static mu_sieve_tag_def_t set_tags[] = {
{ "lower", SVT_VOID },
{ "upper", SVT_VOID },
{ "lowerfirst", SVT_VOID },
{ "upperfirst", SVT_VOID },
{ "quotewildcard", SVT_VOID },
{ "length", SVT_VOID },
{ NULL }
};
struct modprec
{
char *name;
unsigned prec;
char *(*modify) (mu_sieve_machine_t mach, char const *value);
};
static struct modprec modprec[] = {
{ "lower", 40, mod_lower },
{ "upper", 40, mod_upper },
{ "lowerfirst", 30, mod_lowerfirst },
{ "upperfirst", 30, mod_upperfirst },
{ "quotewildcard", 20, mod_quotewildcard },
{ "length", 10, mod_length },
};
static struct modprec *
findprec (char const *name)
{
int i;
for (i = 0; i < MU_ARRAY_SIZE (modprec); i++)
if (strcmp (modprec[i].name, name) == 0)
return &modprec[i];
mu_error ("%s:%d: INTERNAL ERROR", __FILE__, __LINE__);
abort ();
}
static int
sieve_action_set (mu_sieve_machine_t mach)
{
size_t i;
char *name;
char *value;
struct sieve_variable *vptr;
int rc;
mu_sieve_get_arg (mach, 0, SVT_STRING, &name);
mu_sieve_get_arg (mach, 1, SVT_STRING, &value);
value = mu_sieve_strdup (mach, value);
for (i = 0; i < mach->tagcount; i++)
{
mu_sieve_value_t *p = mu_sieve_get_tag_n (mach, i);
char *str = findprec (p->tag)->modify (mach, value);
mu_sieve_free (mach, value);
value = str;
}
rc = mu_assoc_ref_install (mach->vartab, name, (void **)&vptr);
switch (rc)
{
case 0:
break;
case MU_ERR_EXISTS:
mu_sieve_free (mach, vptr->value);
break;
default:
mu_sieve_error (mach, "mu_assoc_ref_install: %s", mu_strerror (rc));
mu_sieve_abort (mach);
}
vptr->value = value;
return 0;
}
static int
set_tag_checker (mu_sieve_machine_t mach)
{
int i, j;
/* Sort tags by decreasing priority value (RFC 5229, 4.1) */
for (i = 1; i < mach->tagcount; i++)
{
mu_sieve_value_t tmp = *mu_sieve_get_tag_n (mach, i);
int tmp_prec = findprec (tmp.tag)->prec;
for (j = i - 1; j >= 0; j--)
{
mu_sieve_value_t *t = mu_sieve_get_tag_n (mach, j);
int prec = findprec (t->tag)->prec;
if (prec < tmp_prec)
*mu_sieve_get_tag_n (mach, j + 1) = *t;
else if (prec == tmp_prec)
{
mu_diag_at_locus (MU_LOG_ERROR, &mach->locus,
_("%s and %s can't be used together"),
tmp.tag, t->tag);
mu_i_sv_error (mach);
return 1;
}
else
break;
}
*mu_sieve_get_tag_n (mach, j + 1) = tmp;
}
return 0;
}
static mu_sieve_tag_group_t set_tag_groups[] = {
{ set_tags, set_tag_checker },
{ NULL }
};
static mu_sieve_data_type set_args[] = {
SVT_STRING,
SVT_STRING,
SVT_VOID
};
/* RFC 5229, 5. Test string */
int
sieve_test_string (mu_sieve_machine_t mach)
{
mu_sieve_value_t *source, *key_list;
mu_sieve_comparator_t comp = mu_sieve_get_comparator (mach);
mu_sieve_relcmp_t test = mu_sieve_get_relcmp (mach);
size_t count = 0;
int rc = 0;
size_t i;
source = mu_sieve_get_arg_untyped (mach, 0);
key_list = mu_sieve_get_arg_untyped (mach, 1);
for (i = 0; i < source->v.list.count; i++)
{
char *item = mu_sieve_string (mach, &source->v.list, i);
size_t k;
/* The "relational" extension [RELATIONAL] adds a match type called
":count". The count of a single string is 0 if it is the empty
string, or 1 otherwise. The count of a string list is the sum of the
counts of the member strings.
*/
if (item[0])
count++;
for (k = 0; k < key_list->v.list.count; k++)
{
mu_sieve_string_t *s =
mu_sieve_string_raw (mach, &key_list->v.list, k);
rc = test (comp (mach, s, item), 0);
if (rc)
return rc;
}
}
return mu_sieve_relational_count (mach, count, 0);
}
mu_sieve_data_type string_args[] = {
SVT_STRING_LIST,
SVT_STRING_LIST,
SVT_VOID
};
mu_sieve_tag_group_t string_tag_groups[] = {
{ mu_sieve_match_part_tags, mu_sieve_match_part_checker },
{ NULL }
};
int
mu_i_sv_expand_variables (char const *input, size_t len,
char **exp, void *data)
{
mu_sieve_machine_t mach = data;
if (mu_isdigit (*input))
{
char *p;
size_t idx = 0;
while (len)
{
if (mu_isdigit (*input))
{
int d = *input - '0';
idx = idx * 10 + d;
input++;
len--;
}
else
return 1;
}
if (idx > mach->match_count)
{
*exp = NULL;
return 0;
}
len = mach->match_buf[idx].rm_eo - mach->match_buf[idx].rm_so;
p = malloc (len + 1);
if (!p)
{
mu_sieve_error (mach, "%s", mu_strerror (errno));
mu_sieve_abort (mach);
}
memcpy (p, mach->match_string + mach->match_buf[idx].rm_so, len);
p[len] = 0;
*exp = p;
}
else if (mu_isalpha (*input))
{
size_t i;
char *name;
struct sieve_variable *var;
for (i = 0; i < len; i++)
if (!(mu_isalnum (input[i]) || input[i] == '_'))
return 1;
name = malloc (len + 1);
if (!name)
{
mu_sieve_error (mach, "%s", mu_strerror (errno));
mu_sieve_abort (mach);
}
memcpy (name, input, len);
name[len] = 0;
var = mu_assoc_ref (mach->vartab, name);
free (name);
if (var)
{
*exp = strdup (var->value);
if (!*exp)
{
mu_sieve_error (mach, "%s", mu_strerror (errno));
mu_sieve_abort (mach);
}
}
else
*exp = NULL;
}
else
return 1;
return 0;
}
int
mu_sieve_require_variables (mu_sieve_machine_t mach)
{
int rc;
if (mach->vartab)
return 0;
rc = mu_assoc_create (&mach->vartab, sizeof (struct sieve_variable),
MU_ASSOC_ICASE);
if (rc)
mu_sieve_error (mach, "mu_assoc_create: %s", mu_strerror (rc));
if (rc == 0)
{
mu_sieve_register_action (mach, "set", sieve_action_set,
set_args, set_tag_groups, 1);
mu_sieve_register_test (mach, "string", sieve_test_string,
string_args, string_tag_groups, 1);
}
return rc;
}
int
mu_sieve_has_variables (mu_sieve_machine_t mach)
{
return mach->vartab != NULL;
}
void
mu_i_sv_copy_variables (mu_sieve_machine_t child, mu_sieve_machine_t parent)
{
mu_iterator_t itr;
mu_sieve_require_variables (child);
mu_assoc_get_iterator (parent->vartab, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
const char *name;
struct sieve_variable const *val;
struct sieve_variable newval;
mu_iterator_current_kv (itr, (const void **)&name, (void**)&val);
newval.value = mu_sieve_strdup (child, val->value);
mu_assoc_install (child->vartab, name, &newval);
}
mu_iterator_destroy (&itr);
}
......@@ -69,6 +69,7 @@ TESTSUITE_AT = \
true.at\
testsuite.at\
vacation.at\
variables.at\
version.at
TESTSUITE = $(srcdir)/testsuite
......
......@@ -149,3 +149,4 @@ m4_include([addheader.at])
m4_include([delheader.at])
m4_include([vacation.at])
m4_include([variables.at])
......
# This file is part of GNU Mailutils. -*- Autotest -*-
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# GNU Mailutils is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 3, or (at
# your option) any later version.
#
# GNU Mailutils is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
AT_BANNER([Variables extension])
MUT_TESTCASE([match variable],[variables match match-variable],
[require [["variables", "fileinto"]];
if address :matches [[ "To", "Cc" ] [ "*@**.com", "*@**.edu" ]]
{
fileinto "INBOX.${3}.${1}";
}],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX.acme.example.roadrunner
FILEINTO on msg uid 2: delivering into INBOX.landru.example.rube
IMPLICIT KEEP on msg uid 3
])
MUT_TESTCASE([set action],[variables action set],
[require [["variables", "fileinto"]];
set "name" "value";
fileinto "INBOX.${name}";
],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX.value
FILEINTO on msg uid 2: delivering into INBOX.value
FILEINTO on msg uid 3: delivering into INBOX.value
])
MUT_TESTCASE([variables with encoded characters],[variables encoded-character],
[require [["encoded-character", "variables", "fileinto"]];
set "myvar" "INBOX";
fileinto "${${hex: 6D 79 76}ar}";
],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX
FILEINTO on msg uid 2: delivering into INBOX
FILEINTO on msg uid 3: delivering into INBOX
])
MUT_TESTCASE([set modifiers],[variables action set],
[require [["variables", "fileinto"]];
set "name" :upperfirst :lower "VALUE";
fileinto "INBOX.${name}";
],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX.Value
FILEINTO on msg uid 2: delivering into INBOX.Value
FILEINTO on msg uid 3: delivering into INBOX.Value
])
MUT_TESTCASE([:quotewildcard modifier],[variables action set],
[require [["variables", "fileinto"]];
set "name" :quotewildcard ".a*strange?name\\!";
fileinto "INBOX${name}";
],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX.a\*strange\?name\\!
FILEINTO on msg uid 2: delivering into INBOX.a\*strange\?name\\!
FILEINTO on msg uid 3: delivering into INBOX.a\*strange\?name\\!
])
MUT_TESTCASE([:length modifier],[variables action set],
[require [["variables", "fileinto"]];
set "name" :length "value";
fileinto "INBOX.${name}";
],
[],[0],[],
[FILEINTO on msg uid 1: delivering into INBOX.5
FILEINTO on msg uid 2: delivering into INBOX.5
FILEINTO on msg uid 3: delivering into INBOX.5
])
MUT_TESTCASE([string test],[variables test string],
[require "variables";
if address "To" :matches "*@*.*.*"
{
if string "${2}" [[ "landru", "ACME" ]]
{
discard;
}
}],
[],[0],[],
[DISCARD on msg uid 1: marking as deleted
DISCARD on msg uid 2: marking as deleted
IMPLICIT KEEP on msg uid 3
])