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