/* Sieve variables extension (RFC 5229)
Copyright (C) 2016-2021 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
. */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
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, please report"), __FILE__, __LINE__);
abort ();
}
static void
variable_set (mu_sieve_machine_t mach, char const *name, char *value)
{
struct sieve_variable *var, **vptr;
int rc;
rc = mu_assoc_install_ref (mach->vartab, name, &vptr);
switch (rc)
{
case 0:
var = malloc (sizeof (*var));
if (!var)
{
mu_sieve_error (mach, "variable_set: %s", mu_strerror (rc));
mu_sieve_abort (mach);
}
*vptr = var;
break;
case MU_ERR_EXISTS:
var = *vptr;
mu_sieve_free (mach, var->value);
break;
default:
mu_sieve_error (mach, "variable_set: %s", mu_strerror (rc));
mu_sieve_abort (mach);
}
var->value = value;
}
static int
sieve_action_set (mu_sieve_machine_t mach)
{
size_t i;
char *name;
char *value;
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;
}
variable_set (mach, name, value);
return 0;
}
static int
varini_append (mu_sieve_machine_t mach,
struct mu_sieve_variable_initializer *vini)
{
if (!mu_sieve_has_variables (mach))
return EINVAL;
if (!mach->init_var)
{
mu_list_create (&mach->init_var);
mu_list_set_destroy_item (mach->init_var, mu_list_free_item);
}
return mu_list_append (mach->init_var, vini);
}
static struct mu_sieve_variable_initializer *
varini_alloc (const char *name, const char *value)
{
struct mu_sieve_variable_initializer *vini;
size_t namelen;
namelen = strlen (name);
vini = malloc (sizeof (*vini) + namelen + strlen (value) + 2);
if (vini)
{
char *p = (char*) (vini + 1);
vini->name = p;
vini->value = p + namelen + 1;
strcpy (vini->name, name);
strcpy (vini->value, value);
}
return vini;
}
int
mu_sieve_variable_initialize (mu_sieve_machine_t mach, char const *name,
char const *value)
{
struct mu_sieve_variable_initializer *vini;
int rc;
if (!name || !value)
return EINVAL;
if (!mu_sieve_has_variables (mach))
return EINVAL;
vini = varini_alloc (name, value);
if (!vini)
return ENOMEM;
rc = varini_append (mach, vini);
if (rc)
free (vini);
return rc;
}
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_range (MU_LOG_ERROR, &t->locus,
_("%s and %s can't be used together"),
tmp.tag, t->tag);
mu_diag_at_locus_range (MU_LOG_ERROR, &tmp.locus,
_("%s encountered here"),
tmp.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
};
static int
retrieve_string (void *item, void *data, size_t idx, char **pval)
{
if (idx)
return MU_ERR_NOENT;
*pval = strdup ((char*)item);
if (!*pval)
return errno;
return 0;
}
int
fold_string (void *item, void *data, void *prev, void **ret)
{
char *str = item;
size_t count = *(size_t*) prev;
/* 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 (*str)
++count;
*(size_t*)ret = count;
return 0;
}
/* RFC 5229, 5. Test string */
int
sieve_test_string (mu_sieve_machine_t mach)
{
mu_sieve_value_t *source, *key_list;
source = mu_sieve_get_arg_untyped (mach, 0);
key_list = mu_sieve_get_arg_untyped (mach, 1);
return mu_sieve_vlist_compare (mach, source, key_list,
retrieve_string, fold_string, mach);
}
static mu_sieve_data_type string_args[] = {
SVT_STRING_LIST,
SVT_STRING_LIST,
SVT_VOID
};
static 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_get (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, MU_ASSOC_ICASE);
if (rc)
mu_sieve_error (mach, "mu_assoc_create: %s", mu_strerror (rc));
mu_assoc_set_destroy_item (mach->vartab, mu_list_free_item);
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;
}
static int
copy_init_var (void *item, void *data)
{
struct mu_sieve_variable_initializer *vini = item, *vini_new;
mu_sieve_machine_t mach = data;
vini_new = varini_alloc (vini->name, vini->value);
if (!vini_new)
return ENOMEM;
return varini_append (mach, vini_new);
}
void
mu_i_sv_copy_variables (mu_sieve_machine_t child, mu_sieve_machine_t parent)
{
mu_iterator_t itr;
int rc;
mu_sieve_require_variables (child);
rc = mu_assoc_get_iterator (parent->vartab, &itr);
if (rc)
{
mu_sieve_error (child, "mu_assoc_get_iterator: %s", mu_strerror (rc));
mu_sieve_abort (child);
}
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 = malloc (sizeof (*newval));
if (!newval)
mu_sieve_abort (child);
newval->value = mu_sieve_strdup (child, val->value);
mu_assoc_install (child->vartab, name, newval);
}
mu_iterator_destroy (&itr);
rc = mu_list_foreach (parent->init_var, copy_init_var, child);
if (rc)
{
mu_sieve_error (child, "copy_init_var: %s", mu_strerror (rc));
mu_sieve_abort (child);
}
}
static int
sieve_setvar (void *item, void *data)
{
struct mu_sieve_variable_initializer *vini = item;
mu_sieve_machine_t mach = data;
variable_set (mach, vini->name, mu_sieve_strdup (mach, vini->value));
return 0;
}
void
mu_i_sv_init_variables (mu_sieve_machine_t mach)
{
if (mu_sieve_has_variables (mach))
{
mu_assoc_clear (mach->vartab);
mu_list_foreach (mach->init_var, sieve_setvar, mach);
}
}