]> mj.ucw.cz Git - checkmail.git/blob - charset.c
Imported rfc822 code from mutt-1.5.16.
[checkmail.git] / charset.c
1 /*
2  *      Incoming Mail Checker: Charsets
3  *
4  *      (c) 2007 Martin Mares <mj@ucw.cz>
5  *
6  * The code for parsing rfc2047 encoding of headers has been adapted
7  * from the Mutt 1.5.16 MUA. Here is the original copyright message:
8  *
9  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
10  * Copyright (C) 2000-2001 Edmund Grimley Evans <edmundo@rano.org>
11  *
12  *     This program is free software; you can redistribute it and/or modify
13  *     it under the terms of the GNU General Public License as published by
14  *     the Free Software Foundation; either version 2 of the License, or
15  *     (at your option) any later version.
16  *
17  *     This program is distributed in the hope that it will be useful,
18  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *     GNU General Public License for more details.
21  *
22  *     You should have received a copy of the GNU General Public License
23  *     along with this program; if not, write to the Free Software
24  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
25  */
26
27 #include "util.h"
28 #include "charset.h"
29
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <wchar.h>
36 #include <wctype.h>
37 #include <locale.h>
38 #include <langinfo.h>
39 #include <iconv.h>
40
41 static char *system_charset;
42
43 #define strfcpy(A,B,C) strncpy(A,B,C), *(A+(C)-1)=0
44
45 enum encoding {
46   ENCOTHER,
47   ENCQUOTEDPRINTABLE,
48   ENCBASE64,
49 };
50
51 static int Index_hex[128] = {
52     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
53     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
54     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
55      0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
56     -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
57     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
58     -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
59     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
60 };
61
62 static int Index_64[128] = {
63     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
64     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
65     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
66     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
67     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
68     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
69     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
70     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
71 };
72
73 #define hexval(c) Index_hex[(unsigned int)(c)]
74 #define base64val(c) Index_64[(unsigned int)(c)]
75
76 #define OPTIGNORELWS 0
77
78 static int option(int opt UNUSED)
79 {
80   return 1;
81 }
82
83 static size_t convert_string (char *f, size_t flen,
84                               const char *from, const char *to,
85                               char **t, size_t *tlen)
86 {
87   iconv_t cd;
88   char *buf, *ob;
89   size_t obl, n;
90   int e;
91
92   cd = iconv_open (to, from);
93   if (cd == (iconv_t)(-1))
94     return (size_t)(-1);
95   obl = 4 * flen + 1;
96   ob = buf = xmalloc (obl);
97   n = iconv (cd, &f, &flen, &ob, &obl);
98   if (n == (size_t)(-1) || iconv (cd, 0, 0, &ob, &obl) == (size_t)(-1))
99   {
100     e = errno;
101     free(buf);
102     iconv_close (cd);
103     errno = e;
104     return (size_t)(-1);
105   }
106   *ob = '\0';
107
108   *tlen = ob - buf;
109
110   buf = xrealloc (buf, ob - buf + 1);
111   *t = buf;
112   iconv_close (cd);
113
114   return n;
115 }
116
117 static int rfc2047_decode_word (char *d, const char *s, size_t len)
118 {
119   const char *pp, *pp1;
120   char *pd, *d0;
121   const char *t, *t1;
122   int enc = 0, count = 0;
123   char *charset = NULL;
124
125   pd = d0 = xmalloc (strlen (s));
126
127   for (pp = s; (pp1 = strchr (pp, '?')); pp = pp1 + 1)
128   {
129     count++;
130     switch (count)
131     {
132       case 2:
133         /* ignore language specification a la RFC 2231 */
134         t = pp1;
135         if ((t1 = memchr (pp, '*', t - pp)))
136           t = t1;
137         charset = xmalloc (t - pp + 1);
138         memcpy (charset, pp, t - pp);
139         charset[t-pp] = '\0';
140         break;
141       case 3:
142         if (toupper ((unsigned char) *pp) == 'Q')
143           enc = ENCQUOTEDPRINTABLE;
144         else if (toupper ((unsigned char) *pp) == 'B')
145           enc = ENCBASE64;
146         else
147         {
148           free(charset);
149           free(d0);
150           return (-1);
151         }
152         break;
153       case 4:
154         if (enc == ENCQUOTEDPRINTABLE)
155         {
156           for (; pp < pp1; pp++)
157           {
158             if (*pp == '_')
159               *pd++ = ' ';
160             else if (*pp == '=' &&
161                      (!(pp[1] & ~127) && hexval(pp[1]) != -1) &&
162                      (!(pp[2] & ~127) && hexval(pp[2]) != -1))
163             {
164               *pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]);
165               pp += 2;
166             }
167             else
168               *pd++ = *pp;
169           }
170           *pd = 0;
171         }
172         else if (enc == ENCBASE64)
173         {
174           int c, b = 0, k = 0;
175
176           for (; pp < pp1; pp++)
177           {
178             if (*pp == '=')
179               break;
180             if ((*pp & ~127) || (c = base64val(*pp)) == -1)
181               continue;
182             if (k + 6 >= 8)
183             {
184               k -= 2;
185               *pd++ = b | (c >> k);
186               b = c << (8 - k);
187             }
188             else
189             {
190               b |= c << (k + 2);
191               k += 6;
192             }
193           }
194           *pd = 0;
195         }
196         break;
197     }
198   }
199
200   size_t dlen;
201   if (charset && system_charset)
202     convert_string (d0, strlen(d0), charset, system_charset, &d0, &dlen);
203   strfcpy (d, d0, len);
204   free (charset);
205   free (d0);
206   return (0);
207 }
208
209 /*
210  * Find the start and end of the first encoded word in the string.
211  * We use the grammar in section 2 of RFC 2047, but the "encoding"
212  * must be B or Q. Also, we don't require the encoded word to be
213  * separated by linear-white-space (section 5(1)).
214  */
215 static const char *find_encoded_word (const char *s, const char **x)
216 {
217   const char *p, *q;
218
219   q = s;
220   while ((p = strstr (q, "=?")))
221   {
222     for (q = p + 2;
223          0x20 < *q && *q < 0x7f && !strchr ("()<>@,;:\"/[]?.=", *q);
224          q++)
225       ;
226     if (q[0] != '?' || !strchr ("BbQq", q[1]) || q[2] != '?')
227       continue;
228     for (q = q + 3; 0x20 < *q && *q < 0x7f && *q != '?'; q++)
229       ;
230     if (q[0] != '?' || q[1] != '=')
231     {
232       --q;
233       continue;
234     }
235
236     *x = q + 2;
237     return p;
238   }
239
240   return 0;
241 }
242
243 /* return length of linear-white-space */
244 static size_t lwslen (const char *s, size_t n)
245 {
246   const char *p = s;
247   size_t len = n;
248
249   if (n <= 0)
250     return 0;
251
252   for (; p < s + n; p++)
253     if (!strchr (" \t\r\n", *p))
254     {
255       len = (size_t)(p - s);
256       break;
257     }
258   if (strchr ("\r\n", *(p-1))) /* LWS doesn't end with CRLF */
259     len = (size_t)0;
260   return len;
261 }
262
263 /* return length of linear-white-space : reverse */
264 static size_t lwsrlen (const char *s, size_t n)
265 {
266   const char *p = s + n - 1;
267   size_t len = n;
268
269   if (n <= 0)
270     return 0;
271
272   if (strchr ("\r\n", *p)) /* LWS doesn't end with CRLF */
273     return (size_t)0;
274
275   for (; p >= s; p--)
276     if (!strchr (" \t\r\n", *p))
277     {
278       len = (size_t)(s + n - 1 - p);
279       break;
280     }
281   return len;
282 }
283
284 /* try to decode anything that looks like a valid RFC2047 encoded
285  * header field, ignoring RFC822 parsing rules
286  */
287 static void rfc2047_decode (char **pd)
288 {
289   const char *p, *q;
290   size_t m, n;
291   int found_encoded = 0;
292   char *d0, *d;
293   const char *s = *pd;
294   size_t dlen;
295
296   if (!s || !*s)
297     return;
298
299   dlen = 4 * strlen (s); /* should be enough */
300   d = d0 = xmalloc (dlen + 1);
301
302   while (*s && dlen > 0)
303   {
304     if (!(p = find_encoded_word (s, &q)))
305     {
306       /* no encoded words */
307       if (option (OPTIGNORELWS))
308       {
309         n = strlen (s);
310         if (found_encoded && (m = lwslen (s, n)) != 0)
311         {
312           if (m != n)
313             *d = ' ', d++, dlen--;
314           s += m;
315         }
316       }
317       strncpy (d, s, dlen);
318       d += dlen;
319       break;
320     }
321
322     if (p != s)
323     {
324       n = (size_t) (p - s);
325       /* ignore spaces between encoded word
326        * and linear-white-space between encoded word and *text */
327       if (option (OPTIGNORELWS))
328       {
329         if (found_encoded && (m = lwslen (s, n)) != 0)
330         {
331           if (m != n)
332             *d = ' ', d++, dlen--;
333           n -= m, s += m;
334         }
335
336         if ((m = n - lwsrlen (s, n)) != 0)
337         {
338           if (m > dlen)
339             m = dlen;
340           memcpy (d, s, m);
341           d += m;
342           dlen -= m;
343           if (m != n)
344             *d = ' ', d++, dlen--;
345         }
346       }
347       else if (!found_encoded || strspn (s, " \t\r\n") != n)
348       {
349         if (n > dlen)
350           n = dlen;
351         memcpy (d, s, n);
352         d += n;
353         dlen -= n;
354       }
355     }
356
357     rfc2047_decode_word (d, p, dlen);
358     found_encoded = 1;
359     s = q;
360     n = strlen (d);
361     dlen -= n;
362     d += n;
363   }
364   *d = 0;
365
366   free (*pd);
367   *pd = d0;
368 }
369
370 /* Initialize the whole machinery */
371 void
372 charset_init(void)
373 {
374   setlocale(LC_CTYPE, "");
375   system_charset = nl_langinfo(CODESET);
376   if (!system_charset[0])
377     system_charset = NULL;
378   debug("Charset is %s\n", system_charset);
379 }
380
381 static void
382 do_add_snippet(char **ppos, char *term, unsigned char *add)
383 {
384   char *pos = *ppos;
385   int space = 1;
386   mbtowc(NULL, NULL, 0);
387
388   while (pos + MB_CUR_MAX < term)
389     {
390       wchar_t c;
391       int l = mbtowc(&c, add, MB_CUR_MAX);
392       if (!l)
393         break;
394       if (l < 0)
395         {
396           l = 1;
397           c = '?';
398         }
399       add += l;
400       if (!iswprint(c))
401         c = '?';
402       if (iswspace(c))
403         {
404           if (space)
405             continue;
406           space = 1;
407         }
408       else
409         space = 0;
410       l = wctomb(pos, c);
411       pos += l;
412     }
413   *ppos = pos;
414   *pos = 0;
415 }
416
417 void
418 add_snippet(char **ppos, char *term, char *add)
419 {
420   char *buf = xstrdup(add);
421   rfc2047_decode(&buf);
422   do_add_snippet(ppos, term, buf);
423   free(buf);
424 }