/* GNU Mailutils -- a suite of utilities for electronic mail
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
. */
/* The enchoded-character extension for Sieve (RFC 5228, 2.4.2.4) */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
typedef int (*convfun) (char const *str, size_t len, size_t *ncons, mu_opool_t pool);
static int hexconv (char const *str, size_t len, size_t *ncons, mu_opool_t pool);
static int uniconv (char const *str, size_t len, size_t *ncons, mu_opool_t pool);
struct convertor
{
char const *pfx;
size_t len;
convfun fun;
};
static struct convertor conv[] = {
{ "hex", 3, hexconv },
{ "unicode", 7, uniconv },
{ NULL }
};
static convfun
findconv (char const **pstr, size_t *plen)
{
struct convertor *cp;
char const *str = *pstr;
size_t len = *plen;
for (cp = conv; cp->pfx; cp++)
{
if (len > cp->len && strncasecmp (str, cp->pfx, cp->len) == 0 &&
str[cp->len] == ':')
{
*pstr += cp->len + 1;
*plen -= cp->len + 1;
return cp->fun;
}
}
return NULL;
}
int
mu_i_sv_expand_encoded_char (char const *input, size_t len,
char **exp, void *data)
{
int rc;
convfun fn;
mu_opool_t pool;
fn = findconv (&input, &len);
if (!fn)
return MU_ERR_NOENT;
rc = mu_opool_create (&pool, MU_OPOOL_DEFAULT);
if (rc)
return rc;
while (rc == 0 && len > 0)
{
if (mu_isblank (*input))
{
++input;
--len;
}
else if (mu_isxdigit (*input))
{
size_t n;
rc = fn (input, len, &n, pool);
if (rc)
break;
input += n;
len -= n;
}
else
{
rc = EILSEQ;
break;
}
}
if (rc == 0)
{
size_t len;
char *p = mu_opool_finish (pool, &len);
char *res;
res = malloc (len + 1);
if (!res)
rc = errno;
else
{
memcpy (res, p, len);
res[len] = 0;
*exp = res;
}
}
mu_opool_destroy (&pool);
return rc;
}
static int
hexconv (char const *str, size_t len, size_t *ncons, mu_opool_t pool)
{
char c;
if (len < 2)
return EILSEQ;
else
{
c = mu_hex2ul (*str);
++str;
if (!mu_isxdigit (*str))
return EILSEQ;
c = (c << 4) + mu_hex2ul (*str);
mu_opool_append_char (pool, c);
}
*ncons = 2;
return 0;
}
static int
utf8_wctomb (unsigned int wc, mu_opool_t pool)
{
int count;
char r[6];
/* FIXME: This implementation allows for full UTF-8 range. RFC 5228
states on page 10, that "It is an error for a script to use a hexadecimal
value that isn't in either the range 0 to D7FF or the range E000 to
10FFFF". I'm not sure that this limitation should be honored */
if (wc < 0x80)
count = 1;
else if (wc < 0x800)
count = 2;
else if (wc < 0x10000)
count = 3;
else if (wc < 0x200000)
count = 4;
else if (wc < 0x4000000)
count = 5;
else if (wc <= 0x7fffffff)
count = 6;
else
return EILSEQ;
switch (count)
{
/* Note: code falls through cases! */
case 6:
r[5] = 0x80 | (wc & 0x3f);
wc = wc >> 6;
wc |= 0x4000000;
case 5:
r[4] = 0x80 | (wc & 0x3f);
wc = wc >> 6;
wc |= 0x200000;
case 4:
r[3] = 0x80 | (wc & 0x3f);
wc = wc >> 6;
wc |= 0x10000;
case 3:
r[2] = 0x80 | (wc & 0x3f);
wc = wc >> 6;
wc |= 0x800;
case 2:
r[1] = 0x80 | (wc & 0x3f);
wc = wc >> 6;
wc |= 0xc0;
case 1:
r[0] = wc;
}
mu_opool_append (pool, r, count);
return 0;
}
static int
uniconv (char const *str, size_t len, size_t *ncons, mu_opool_t pool)
{
unsigned int wc = 0;
size_t i;
for (i = 0; i < len; i++)
{
if (i >= 12)
return EILSEQ;
if (!mu_isxdigit (str[i]))
break;
wc = (wc << 4) + mu_hex2ul (str[i]);
}
*ncons = i;
return utf8_wctomb (wc, pool);
}