Only ignore linear-white-space between two encoded-words, lax
[infodrom/newmail] / rfc2047.c
1 /*
2     Copyright (c) 2006  Joey Schulze <joey@infodrom.org>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include "charset.h"
24
25 /*
26  * Index_hex and Index_64 imported from Mutt:handler.c
27  * decode_quotedprintable() and decode_base64() as well
28  */
29 int Index_hex[128] = {
30   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
31   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
32   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
33   0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
34   -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
35   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
36   -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
37   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
38 };
39
40 int Index_64[128] = {
41   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
42   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
43   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
44   52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
45   -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
46   15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
47   -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
48   41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
49 };
50 #define hexval(c) Index_hex[(unsigned int)(c)]
51 #define base64val(c) Index_64[(unsigned int)(c)]
52
53
54 /*
55  * Decode a string from quoted printable
56  */
57 char *decode_quotedprintable(char *buf)
58 {
59   char *inp, *outp;
60   char *endp;
61
62   endp = buf+strlen(buf);
63   
64   for (inp = outp = buf; inp < endp; inp++) {
65     if (*inp == '_')
66       *(outp++) = ' ';
67     else if (*inp == '=' && inp+2 < endp &&
68              (!(*(inp+1) & ~127) && hexval(*(inp+1)) != -1) &&
69              (!(*(inp+2) & ~127) && hexval(*(inp+2)) != -1)) {
70       *(outp++) = (hexval(*(inp+1)) << 4) | hexval(*(inp+2));
71       inp += 2;
72     } else
73       *(outp++) = *inp;
74   }
75   *outp = '\0';
76
77   return buf;
78 }
79
80 /*
81  * Decode a string from base43
82  */
83 char *decode_base64(char *buf)
84 {
85   char *inp, *outp;
86   char *endp;
87   int c, b = 0, k = 0;
88
89   endp = buf+strlen(buf);
90
91   for (inp = outp = buf; inp < endp; inp++) {
92     if (*inp == '=')
93       break;
94     if ((*inp & ~127) || (c = base64val(*inp)) == -1)
95       continue;
96     if (k + 6 >= 8)
97       {
98         k -= 2;
99         *(outp++) = b | (c >> k);
100         b = c << (8 - k);
101       }
102     else
103       {
104         b |= c << (k + 2);
105         k += 6;
106       }
107     *outp = '\0';
108   }
109
110   return buf;
111 }
112
113 /*
114  * converts a header line into the current character set if required
115  */
116 char *convert_header(char *buf)
117 {
118   char *charset;
119   int encoding;
120   char *inp, *outp, *encstart, *wordp, *encp, *endp;
121   char *decode;
122   char *output;
123   size_t size;
124   size_t outlen;
125
126   endp = buf+strlen(buf);
127   inp = buf;
128
129   outlen = strlen(buf)+1;
130   if ((output = (char *)malloc(outlen)) == NULL) {
131     perror ("malloc");
132     return buf;
133   }
134
135   memset(output, 0, outlen);
136   outp = output;
137
138   while ((encstart = strstr (inp, "=?"))) {
139     if (encstart != inp) {
140       // -1 nur falls vorher kein encoded-word
141       memcpy (outp, inp, encstart-inp);
142       outp += encstart-inp;
143     }
144     charset = encstart+2;
145
146     if ((encp = strchr (charset, '?')) == NULL) {
147       memcpy (outp, inp, strlen(inp)+1);
148       break;
149     }
150
151     *encp = '\0';
152
153     if ((encp + 3 >= endp) || !strchr ("BbQq", *encp) || (*(encp+2) != '?')) {
154       *encp = '?';
155       memcpy (outp, inp, strlen(inp)+1);
156       outp += strlen(inp)+1;
157       inp += strlen(inp)+1;
158       break;
159     }
160
161     encoding = toupper(*(encp+1));
162
163     inp = encp + 3;
164
165     if ((wordp = strstr (inp, "?=")) == NULL) {
166       memcpy (outp, inp, strlen(inp)+1);
167       outp += strlen(inp)+1;
168       inp += strlen(inp)+1;
169       break;
170     }
171
172     *wordp = '\0';
173     wordp += 2;
174
175     /* Look for next =?, spaces will be eaten between two encoded-words */
176     if (*wordp && *wordp == ' ' && *(wordp+1) && *(wordp+1) == '=' && *(wordp+2) && *(wordp+2) == '?')
177       wordp++;
178
179     switch (encoding) {
180     case 'B':
181       decode = decode_base64 (inp);
182       break;
183     case 'Q':
184       decode = decode_quotedprintable (inp);
185       break;
186     default:
187       decode = inp;
188       break;
189     }
190
191     size = outlen - strlen(output);
192     decode = convert_word (charset, decode, outp, size);
193
194     outp += strlen(decode);
195
196     inp = wordp;
197   }
198
199   if (strlen(inp))
200     memcpy (outp, inp, strlen(inp)+1);
201
202   memcpy (buf, output, strlen(output)+1);
203   free (output);
204
205   return buf;
206 }
207
208
209 /*
210  * Needs to be called with LANG=de_DE.ISO-8859-1
211
212 #include <stdio.h>
213
214 void test_rfc2047()
215 {
216   char *qp;
217   char *b64;
218   char *subject;
219
220   set_charset();
221
222   qp = strdup ("f=FCr");
223   printf ("\t%s -> ", qp);
224   b64 = decode_quotedprintable (qp);
225   printf ("%s\n", qp);
226   if (!strcmp (qp, "für"))
227     printf ("rfc2047.c: decode_quotedprintable() passed\n");
228   else
229     printf ("rfc2047.c: decode_quotedprintable() failed\n");
230   free (qp);
231
232   qp = strdup ("f=FCr_Umlaute_=EFm_Sub");
233   printf ("\t%s -> ", qp);
234   b64 = decode_quotedprintable (qp);
235   printf ("%s\n", qp);
236   if (!strcmp (qp, "für Umlaute ïm Sub"))
237     printf ("rfc2047.c: decode_quotedprintable() passed\n");
238   else
239     printf ("rfc2047.c: decode_quotedprintable() failed\n");
240   free (qp);
241
242   b64 = strdup ("ZvxyINxtbOT8dOs=");
243   printf ("\t%s -> ", b64);
244   b64 = decode_base64 (b64);
245   printf ("%s\n", b64);
246   if (!strcmp (b64, "für Ümläütë"))
247     printf ("rfc2047.c: decode_base64() passed\n");
248   else
249     printf ("rfc2047.c: decode_base64() failed\n");
250   free (b64);
251
252   subject = strdup ("Vorschlag =?ISO-8859-1?Q?f=FCr?= ein Eintrittskonzept");
253   printf ("\t%s\n", subject);
254   subject = convert_header (subject);
255   printf ("\t%s\n", subject);
256   if (!strcmp (subject, "Vorschlag für ein Eintrittskonzept"))
257     printf ("rfc2047.c: convert_header() passed\n");
258   else
259     printf ("rfc2047.c: convert_header() failed\n");
260   free (subject);
261
262   subject = strdup ("=?utf-8?q?Google=3A_Chat_f=C3=BCr_die_Ewigkeit?=");
263   printf ("\t%s\n", subject);
264   subject = convert_header (subject);
265   printf ("\t%s\n", subject);
266   if (!strcmp (subject, "Google: Chat für die Ewigkeit"))
267     printf ("rfc2047.c: convert_header() passed\n");
268   else
269     printf ("rfc2047.c: convert_header() failed\n");
270   free (subject);
271
272   subject = strdup ("=?iso-8859-1?B?ZvxyINxtbOT8dOs=?= testen");
273   printf ("\t%s\n", subject);
274   subject = convert_header (subject);
275   printf ("\t%s\n", subject);
276   if (!strcmp (subject, "für Ümläütë testen"))
277     printf ("rfc2047.c: convert_header() passed\n");
278   else
279     printf ("rfc2047.c: convert_header() failed\n");
280   free (subject);
281
282 }
283 */