]> mj.ucw.cz Git - checkmail.git/blob - charset.c
Better rules for coloring of flagged messages.
[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 "rfc822.h"
29 #include "charset.h"
30
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <wchar.h>
37 #include <wctype.h>
38 #include <locale.h>
39 #include <langinfo.h>
40 #include <iconv.h>
41
42 static char *system_charset;
43
44 #define strfcpy(A,B,C) strncpy(A,B,C), *(A+(C)-1)=0
45
46 enum encoding {
47   ENCOTHER,
48   ENCQUOTEDPRINTABLE,
49   ENCBASE64,
50 };
51
52 static int Index_hex[128] = {
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     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
56      0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
57     -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
58     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
59     -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
60     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
61 };
62
63 static int Index_64[128] = {
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,-1, -1,-1,-1,-1,
66     -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
67     52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
68     -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
69     15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
70     -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
71     41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
72 };
73
74 #define hexval(c) Index_hex[(unsigned int)(c)]
75 #define base64val(c) Index_64[(unsigned int)(c)]
76
77 #define OPTIGNORELWS 0
78
79 static int option(int opt UNUSED)
80 {
81   return 1;
82 }
83
84 static size_t convert_string (char *f, size_t flen,
85                               const char *from, const char *to,
86                               char **t, size_t *tlen)
87 {
88   iconv_t cd;
89   char *buf, *ob;
90   size_t obl, n;
91   int e;
92
93   cd = iconv_open (to, from);
94   if (cd == (iconv_t)(-1))
95     return (size_t)(-1);
96   obl = 4 * flen + 1;
97   ob = buf = xmalloc (obl);
98   n = iconv (cd, &f, &flen, &ob, &obl);
99   if (n == (size_t)(-1) || iconv (cd, 0, 0, &ob, &obl) == (size_t)(-1))
100   {
101     e = errno;
102     free(buf);
103     iconv_close (cd);
104     errno = e;
105     return (size_t)(-1);
106   }
107   *ob = '\0';
108
109   *tlen = ob - buf;
110
111   buf = xrealloc (buf, ob - buf + 1);
112   *t = buf;
113   iconv_close (cd);
114
115   return n;
116 }
117
118 static int rfc2047_decode_word (char *d, const char *s, size_t len)
119 {
120   const char *pp, *pp1;
121   char *pd, *d0;
122   const char *t, *t1;
123   int enc = 0, count = 0;
124   char *charset = NULL;
125
126   pd = d0 = xmalloc (strlen (s));
127
128   for (pp = s; (pp1 = strchr (pp, '?')); pp = pp1 + 1)
129   {
130     count++;
131     switch (count)
132     {
133       case 2:
134         /* ignore language specification a la RFC 2231 */
135         t = pp1;
136         if ((t1 = memchr (pp, '*', t - pp)))
137           t = t1;
138         charset = xmalloc (t - pp + 1);
139         memcpy (charset, pp, t - pp);
140         charset[t-pp] = '\0';
141         break;
142       case 3:
143         if (toupper ((unsigned char) *pp) == 'Q')
144           enc = ENCQUOTEDPRINTABLE;
145         else if (toupper ((unsigned char) *pp) == 'B')
146           enc = ENCBASE64;
147         else
148         {
149           free(charset);
150           free(d0);
151           return (-1);
152         }
153         break;
154       case 4:
155         if (enc == ENCQUOTEDPRINTABLE)
156         {
157           for (; pp < pp1; pp++)
158           {
159             if (*pp == '_')
160               *pd++ = ' ';
161             else if (*pp == '=' &&
162                      (!(pp[1] & ~127) && hexval(pp[1]) != -1) &&
163                      (!(pp[2] & ~127) && hexval(pp[2]) != -1))
164             {
165               *pd++ = (hexval(pp[1]) << 4) | hexval(pp[2]);
166               pp += 2;
167             }
168             else
169               *pd++ = *pp;
170           }
171           *pd = 0;
172         }
173         else if (enc == ENCBASE64)
174         {
175           int c, b = 0, k = 0;
176
177           for (; pp < pp1; pp++)
178           {
179             if (*pp == '=')
180               break;
181             if ((*pp & ~127) || (c = base64val(*pp)) == -1)
182               continue;
183             if (k + 6 >= 8)
184             {
185               k -= 2;
186               *pd++ = b | (c >> k);
187               b = c << (8 - k);
188             }
189             else
190             {
191               b |= c << (k + 2);
192               k += 6;
193             }
194           }
195           *pd = 0;
196         }
197         break;
198     }
199   }
200
201   size_t dlen;
202   if (charset && system_charset)
203     convert_string (d0, strlen(d0), charset, system_charset, &d0, &dlen);
204   strfcpy (d, d0, len);
205   free (charset);
206   free (d0);
207   return (0);
208 }
209
210 /*
211  * Find the start and end of the first encoded word in the string.
212  * We use the grammar in section 2 of RFC 2047, but the "encoding"
213  * must be B or Q. Also, we don't require the encoded word to be
214  * separated by linear-white-space (section 5(1)).
215  */
216 static const char *find_encoded_word (const char *s, const char **x)
217 {
218   const char *p, *q;
219
220   q = s;
221   while ((p = strstr (q, "=?")))
222   {
223     for (q = p + 2;
224          0x20 < *q && *q < 0x7f && !strchr ("()<>@,;:\"/[]?.=", *q);
225          q++)
226       ;
227     if (q[0] != '?' || !strchr ("BbQq", q[1]) || q[2] != '?')
228       continue;
229     for (q = q + 3; 0x20 < *q && *q < 0x7f && *q != '?'; q++)
230       ;
231     if (q[0] != '?' || q[1] != '=')
232     {
233       --q;
234       continue;
235     }
236
237     *x = q + 2;
238     return p;
239   }
240
241   return 0;
242 }
243
244 /* return length of linear-white-space */
245 static size_t lwslen (const char *s, size_t n)
246 {
247   const char *p = s;
248   size_t len = n;
249
250   if (n <= 0)
251     return 0;
252
253   for (; p < s + n; p++)
254     if (!strchr (" \t\r\n", *p))
255     {
256       len = (size_t)(p - s);
257       break;
258     }
259   if (strchr ("\r\n", *(p-1))) /* LWS doesn't end with CRLF */
260     len = (size_t)0;
261   return len;
262 }
263
264 /* return length of linear-white-space : reverse */
265 static size_t lwsrlen (const char *s, size_t n)
266 {
267   const char *p = s + n - 1;
268   size_t len = n;
269
270   if (n <= 0)
271     return 0;
272
273   if (strchr ("\r\n", *p)) /* LWS doesn't end with CRLF */
274     return (size_t)0;
275
276   for (; p >= s; p--)
277     if (!strchr (" \t\r\n", *p))
278     {
279       len = (size_t)(s + n - 1 - p);
280       break;
281     }
282   return len;
283 }
284
285 /* try to decode anything that looks like a valid RFC2047 encoded
286  * header field, ignoring RFC822 parsing rules
287  */
288 static void rfc2047_decode (char **pd)
289 {
290   const char *p, *q;
291   size_t m, n;
292   int found_encoded = 0;
293   char *d0, *d;
294   const char *s = *pd;
295   size_t dlen;
296
297   if (!s || !*s)
298     return;
299
300   dlen = 4 * strlen (s); /* should be enough */
301   d = d0 = xmalloc (dlen + 1);
302
303   while (*s && dlen > 0)
304   {
305     if (!(p = find_encoded_word (s, &q)))
306     {
307       /* no encoded words */
308       if (option (OPTIGNORELWS))
309       {
310         n = strlen (s);
311         if (found_encoded && (m = lwslen (s, n)) != 0)
312         {
313           if (m != n)
314             *d = ' ', d++, dlen--;
315           s += m;
316         }
317       }
318       strncpy (d, s, dlen);
319       d += dlen;
320       break;
321     }
322
323     if (p != s)
324     {
325       n = (size_t) (p - s);
326       /* ignore spaces between encoded word
327        * and linear-white-space between encoded word and *text */
328       if (option (OPTIGNORELWS))
329       {
330         if (found_encoded && (m = lwslen (s, n)) != 0)
331         {
332           if (m != n)
333             *d = ' ', d++, dlen--;
334           n -= m, s += m;
335         }
336
337         if ((m = n - lwsrlen (s, n)) != 0)
338         {
339           if (m > dlen)
340             m = dlen;
341           memcpy (d, s, m);
342           d += m;
343           dlen -= m;
344           if (m != n)
345             *d = ' ', d++, dlen--;
346         }
347       }
348       else if (!found_encoded || strspn (s, " \t\r\n") != n)
349       {
350         if (n > dlen)
351           n = dlen;
352         memcpy (d, s, n);
353         d += n;
354         dlen -= n;
355       }
356     }
357
358     rfc2047_decode_word (d, p, dlen);
359     found_encoded = 1;
360     s = q;
361     n = strlen (d);
362     dlen -= n;
363     d += n;
364   }
365   *d = 0;
366
367   free (*pd);
368   *pd = d0;
369 }
370
371 /* Initialize the whole machinery */
372 void
373 charset_init(void)
374 {
375   setlocale(LC_CTYPE, "");
376   system_charset = nl_langinfo(CODESET);
377   if (!system_charset[0])
378     system_charset = NULL;
379   debug("Charset is %s\n", system_charset);
380 }
381
382 void
383 add_snippet(char **ppos, char *term, char *add)
384 {
385   char *pos = *ppos;
386   int space = 1;
387   mbtowc(NULL, NULL, 0);
388
389   while (pos + MB_CUR_MAX < term)
390     {
391       wchar_t c;
392       int l = mbtowc(&c, add, MB_CUR_MAX);
393       if (!l)
394         break;
395       if (l < 0)
396         {
397           l = 1;
398           c = '?';
399         }
400       add += l;
401       if (!iswprint(c))
402         c = '?';
403       if (iswspace(c))
404         {
405           if (space)
406             continue;
407           space = 1;
408         }
409       else
410         space = 0;
411       l = wctomb(pos, c);
412       pos += l;
413     }
414   *ppos = pos;
415   *pos = 0;
416 }
417
418 void
419 add_subject_snippet(char **ppos, char *term, char *add)
420 {
421   char *buf = xstrdup(add);
422   rfc2047_decode(&buf);
423   add_snippet(ppos, term, buf);
424   free(buf);
425 }
426
427 void
428 add_addr_snippet(char **ppos, char *term, char *add, int add_mbox, int add_personal)
429 {
430   ADDRESS *addr = rfc822_parse_adrlist(NULL, add);
431   if (!addr)
432     {
433       debug("%s: Cannot parse address (%s)\n", add, rfc822_error(RFC822Error));
434       add_subject_snippet(ppos, term, add);
435       return;
436     }
437   // debug("%s: pers=%s mbox=%s\n", add, addr->personal, addr->mailbox);
438   rfc2047_decode(&addr->personal);
439   if (!addr->mailbox || !addr->mailbox[0])
440     add_mbox = 0;
441   if (!addr->personal || !addr->personal[0])
442     {
443       if (addr->mailbox && addr->mailbox[0])
444         {
445           char *c = strchr(addr->mailbox, '@');
446           if (c)
447             *c = 0;
448           add_mbox = 1;
449         }
450       add_personal = 0;
451     }
452   if (add_mbox || add_personal)
453     {
454       if (add_personal)
455         add_snippet(ppos, term, addr->personal);
456       if (add_mbox && add_personal)
457         add_snippet(ppos, term, " <");
458       if (add_mbox)
459         add_snippet(ppos, term, addr->mailbox);
460       if (add_mbox && add_personal)
461         add_snippet(ppos, term, ">");
462     }
463   else
464     add_snippet(ppos, term, "???");
465   rfc822_free_address(&addr);
466 }