/*
* Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "mutt.h"
#include "mime.h"
#include "charset.h"
#include "rfc2047.h"
#include <ctype.h>
#include <string.h>
typedef void encode_t (char *, size_t, const unsigned char *);
extern char MimeSpecials[];
extern char B64Chars[];
static void q_encode_string (char *d, size_t dlen, const unsigned char *s)
{
char charset[SHORT_STRING];
size_t cslen, wordlen;
char *wptr = d;
const unsigned char *t;
int hibit = 0;
for(t = s; *t && !hibit; t++)
if(*t & 0x80) hibit = 1;
if(hibit)
{
snprintf (charset, sizeof (charset), "=?%s?Q?",
mutt_strcasecmp ("us-ascii", Charset) == 0 ? "unknown-8bit" : NONULL(Charset));
}
else
strfcpy(charset, "=?us-ascii?Q?", sizeof(charset));
cslen = mutt_strlen (charset);
strcpy (wptr, charset);
wptr += cslen;
wordlen = cslen;
dlen -= cslen;
dlen -= 3; /* save room for the word terminator */
while (*s && dlen > 0)
{
if (wordlen >= 72)
{
if (dlen < 4 + cslen)
break;
strcpy (wptr, "?=\n ");
wptr += 4;
dlen -= 4;
strcpy (wptr, charset);
wptr += cslen;
wordlen = cslen;
dlen -= cslen;
}
if (*s == ' ')
{
*wptr++ = '_';
wordlen++;
dlen--;
}
else if ((*s & 0x80) || *s == '\t' || *s == '_' || strchr (MimeSpecials, *s))
{
if (wordlen >= 70)
{
if (dlen < 4 + cslen)
break;
strcpy (wptr, "?=\n ");
wptr += 4;
dlen -= 4;
strcpy (wptr, charset);
wptr += cslen;
wordlen = cslen;
dlen -= cslen;
}
if (dlen < 3)
break;
sprintf (wptr, "=%02X", *s);
wptr += 3;
wordlen += 3;
dlen -= 3;
}
else
{
*wptr++ = *s;
wordlen++;
dlen--;
}
s++;
}
strcpy (wptr, "?=");
}
static void b_encode_string (char *d, size_t dlen, const unsigned char *s)
{
char charset[SHORT_STRING];
char *wptr = d;
int cslen;
int wordlen;
snprintf (charset, sizeof (charset), "=?%s?B?", NONULL(Charset));
cslen = mutt_strlen (charset);
strcpy (wptr, charset);
wptr += cslen;
wordlen = cslen;
dlen -= cslen;
dlen -= 3; /* save room for the word terminator */
while (*s && dlen >= 4)
{
if (wordlen >= 71)
{
if (dlen < 4 + cslen)
break;
strcpy (wptr, "?=\n ");
wptr += 4;
dlen -= 4;
strcpy (wptr, charset);
wptr += cslen;
wordlen = cslen;
dlen -= cslen;
}
*wptr++ = B64Chars[ (*s >> 2) & 0x3f ];
*wptr++ = B64Chars[ ((*s & 0x3) << 4) | ((*(s+1) >> 4) & 0xf) ];
s++;
if (*s)
{
*wptr++ = B64Chars[ ((*s & 0xf) << 2) | ((*(s+1) >> 6) & 0x3) ];
s++;
if (*s)
{
*wptr++ = B64Chars[ *s & 0x3f ];
s++;
}
else
*wptr++ = '=';
}
else
{
*wptr++ = '=';
*wptr++ = '=';
}
wordlen += 4;
dlen -= 4;
}
strcpy (wptr, "?=");
}
void rfc2047_encode_string (char *d, size_t dlen, const unsigned char *s)
{
int count = 0;
int len;
const unsigned char *p = s;
encode_t *encoder;
/* First check to see if there are any 8-bit characters */
for (; *p; p++)
{
if (*p & 0x80)
count++;
else if (*p == '=' && *(p+1) == '?')
{
count += 2;
p++;
}
}
if (!count)
{
strfcpy (d, (const char *)s, dlen);
return;
}
if (mutt_strcasecmp("us-ascii", Charset) == 0 ||
mutt_strncasecmp("iso-8859", Charset, 8) == 0)
encoder = q_encode_string;
else
{
/* figure out which encoding generates the most compact representation */
len = mutt_strlen ((char *) s);
if ((count * 2) + len <= (4 * len) / 3)
encoder = q_encode_string;
else
encoder = b_encode_string;
}
/* Hack to pull the Re: and Fwd: out of the encoded word for better
handling by agents which do not support RFC2047. */
if (!mutt_strncasecmp ("re: ", (char *) s, 4))
{
strncpy (d, (char *) s, 4);
d += 4;
dlen -= 4;
s += 4;
}
else if (!mutt_strncasecmp ("fwd: ", (char *) s, 5))
{
strncpy (d, (char *) s, 5);
d += 5;
dlen -= 5;
s += 5;
}
(*encoder) (d, dlen, s);
}
void rfc2047_encode_adrlist (ADDRESS *addr)
{
ADDRESS *ptr = addr;
char buffer[STRING];
while (ptr)
{
if (ptr->personal)
{
rfc2047_encode_string (buffer, sizeof (buffer), (const unsigned char *)ptr->personal);
safe_free ((void **) &ptr->personal);
ptr->personal = safe_strdup (buffer);
}
#ifdef EXACT_ADDRESS
if (ptr->val)
{
rfc2047_encode_string (buffer, sizeof (buffer), (const unsigned char *)ptr->val);
safe_free ((void **) &ptr->val);
ptr->val = safe_strdup (buffer);
}
#endif
ptr = ptr->next;
}
}
static int rfc2047_decode_word (char *d, const char *s, size_t len)
{
char *p = safe_strdup (s);
char *pp = p;
char *pd = d;
int enc = 0, filter = 0, count = 0, c1, c2, c3, c4;
char *charset = NULL;
while ((pp = strtok (pp, "?")) != NULL)
{
count++;
switch (count)
{
case 2:
if (mutt_strcasecmp (pp, Charset) != 0)
{
filter = 1;
charset = pp;
}
break;
case 3:
if (toupper (*pp) == 'Q')
enc = ENCQUOTEDPRINTABLE;
else if (toupper (*pp) == 'B')
enc = ENCBASE64;
else
return (-1);
break;
case 4:
if (enc == ENCQUOTEDPRINTABLE)
{
while (*pp && len > 0)
{
if (*pp == '_')
{
*pd++ = ' ';
len--;
}
else if (*pp == '=')
{
if (pp[1] == 0 || pp[2] == 0)
break; /* something wrong */
*pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]);
len--;
pp += 2;
}
else
{
*pd++ = *pp;
len--;
}
pp++;
}
*pd = 0;
}
else if (enc == ENCBASE64)
{
while (*pp && len > 0)
{
if (pp[0] == '=' || pp[1] == 0 || pp[1] == '=')
break; /* something wrong */
c1 = base64val(pp[0]);
c2 = base64val(pp[1]);
*pd++ = (c1 << 2) | ((c2 >> 4) & 0x3);
if (--len == 0) break;
if (pp[2] == 0 || pp[2] == '=') break;
c3 = base64val(pp[2]);
*pd++ = ((c2 & 0xf) << 4) | ((c3 >> 2) & 0xf);
if (--len == 0)
break;
if (pp[3] == 0 || pp[3] == '=')
break;
c4 = base64val(pp[3]);
*pd++ = ((c3 & 0x3) << 6) | c4;
if (--len == 0)
break;
pp += 4;
}
*pd = 0;
}
break;
}
pp = 0;
}
if (filter)
{
if(mutt_is_utf8(charset))
{
CHARSET *chs = mutt_get_charset(Charset);
mutt_decode_utf8_string(d, chs);
}
else if (mutt_display_string(d, mutt_get_translation(charset, Charset)) == -1)
{
for(pd = d; *pd; pd++)
{
if (!IsPrint (*pd))
*pd = '?';
}
}
}
safe_free ((void **) &p);
return (0);
}
/* try to decode anything that looks like a valid RFC2047 encoded
* header field, ignoring RFC822 parsing rules
*/
void rfc2047_decode (char *d, const char *s, size_t dlen)
{
const char *p, *q;
size_t n;
int found_encoded = 0;
dlen--; /* save room for the terminal nul */
while (*s && dlen > 0)
{
if ((p = strstr (s, "=?")) == NULL ||
(q = strchr (p + 2, '?')) == NULL ||
(q = strchr (q + 1, '?')) == NULL ||
(q = strstr (q + 1, "?=")) == NULL)
{
/* no encoded words */
if (d != s)
strfcpy (d, s, dlen + 1);
return;
}
if (p != s)
{
n = (size_t) (p - s);
/* ignore spaces between encoded words */
if (!found_encoded || strspn (s, " \t\r\n") != n)
{
if (n > dlen)
n = dlen;
if (d != s)
memcpy (d, s, n);
d += n;
dlen -= n;
}
}
rfc2047_decode_word (d, p, dlen);
found_encoded = 1;
s = q + 2;
n = mutt_strlen (d);
dlen -= n;
d += n;
}
*d = 0;
}
void rfc2047_decode_adrlist (ADDRESS *a)
{
while (a)
{
if (a->personal && strstr (a->personal, "=?") != NULL)
rfc2047_decode (a->personal, a->personal, mutt_strlen (a->personal) + 1);
#ifdef EXACT_ADDRESS
if (a->val && strstr (a->val, "=?") != NULL)
rfc2047_decode (a->val, a->val, mutt_strlen (a->val) + 1);
#endif
a = a->next;
}
}
syntax highlighted by Code2HTML, v. 0.9.1