e2b3340198876a8dcaa3c3ced5b242b6149dad2b
[apps/madmutt.git] / imap / utf7.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000 Edmund Grimley Evans <edmundo@rano.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <lib-lib/mem.h>
15
16 #include "mutt.h"
17 #include "charset.h"
18 #include "imap_private.h"
19
20 /*
21  * Convert the data (u7,u7len) from RFC 2060's UTF-7 to UTF-8.
22  * The result is null-terminated and returned, and also stored
23  * in (*u8,*u8len) if u8 or u8len is non-zero.
24  * If input data is invalid, return 0 and don't store anything.
25  * RFC 2060 obviously intends the encoding to be unique (see
26  * point 5 in section 5.1.3), so we reject any non-canonical
27  * form, such as &ACY- (instead of &-) or &AMA-&AMA- (instead
28  * of &AMAAwA-).
29  */
30 static char *utf7_to_utf8 (const char *u7, size_t u7len, char **u8,
31                            size_t * u8len)
32 {
33   char *buf, *p;
34   int b, ch, k;
35
36   p = buf = p_new(char, u7len + u7len / 8 + 1);
37
38   for (; u7len; u7++, u7len--) {
39     if (*u7 == '&') {
40       u7++, u7len--;
41
42       if (u7len && *u7 == '-') {
43         *p++ = '&';
44         continue;
45       }
46
47       ch = 0;
48       k = 10;
49       for (; u7len; u7++, u7len--) {
50         if ((b = base64val(*u7)) < 0)
51           break;
52         if (k > 0) {
53           ch |= b << k;
54           k -= 6;
55         }
56         else {
57           ch |= b >> (-k);
58           if (ch < 0x80) {
59             if (0x20 <= ch && ch < 0x7f)
60               /* Printable US-ASCII */
61               goto bail;
62             *p++ = ch;
63           }
64           else if (ch < 0x800) {
65             *p++ = 0xc0 | (ch >> 6);
66             *p++ = 0x80 | (ch & 0x3f);
67           }
68           else {
69             *p++ = 0xe0 | (ch >> 12);
70             *p++ = 0x80 | ((ch >> 6) & 0x3f);
71             *p++ = 0x80 | (ch & 0x3f);
72           }
73           ch = (b << (16 + k)) & 0xffff;
74           k += 10;
75         }
76       }
77       if (ch || k < 6)
78         /* Non-zero or too many extra bits */
79         goto bail;
80       if (!u7len || *u7 != '-')
81         /* BASE64 not properly terminated */
82         goto bail;
83       if (u7len > 2 && u7[1] == '&' && u7[2] != '-')
84         /* Adjacent BASE64 sections */
85         goto bail;
86     }
87     else if (*u7 < 0x20 || *u7 >= 0x7f)
88       /* Not printable US-ASCII */
89       goto bail;
90     else
91       *p++ = *u7;
92   }
93   *p++ = '\0';
94   if (u8len)
95     *u8len = p - buf;
96
97   p_realloc(&buf, p - buf);
98   if (u8)
99     *u8 = buf;
100   return buf;
101
102 bail:
103   p_delete(&buf);
104   return 0;
105 }
106
107 /*
108  * Convert the data (u8,u8len) from UTF-8 to RFC 2060's UTF-7.
109  * The result is null-terminated and returned, and also stored
110  * in (*u7,*u7len) if u7 or u7len is non-zero.
111  * Unicode characters above U+FFFF are replaced by U+FFFE.
112  * If input data is invalid, return 0 and don't store anything.
113  */
114 static char *utf8_to_utf7 (const char *u8, size_t u8len, char **u7,
115                            size_t * u7len)
116 {
117   char *buf, *p;
118   int ch;
119   int n, i, b = 0, k = 0;
120   int base64 = 0;
121
122   /*
123    * In the worst case we convert 2 chars to 7 chars. For example:
124    * "\x10&\x10&..." -> "&ABA-&-&ABA-&-...".
125    */
126   p = buf = p_new(char, (u8len / 2) * 7 + 6);
127
128   while (u8len) {
129     unsigned char c = *u8;
130
131     if (c < 0x80)
132       ch = c, n = 0;
133     else if (c < 0xc2)
134       goto bail;
135     else if (c < 0xe0)
136       ch = c & 0x1f, n = 1;
137     else if (c < 0xf0)
138       ch = c & 0x0f, n = 2;
139     else if (c < 0xf8)
140       ch = c & 0x07, n = 3;
141     else if (c < 0xfc)
142       ch = c & 0x03, n = 4;
143     else if (c < 0xfe)
144       ch = c & 0x01, n = 5;
145     else
146       goto bail;
147
148     u8++, u8len--;
149     if (n > u8len)
150       goto bail;
151     for (i = 0; i < n; i++) {
152       if ((u8[i] & 0xc0) != 0x80)
153         goto bail;
154       ch = (ch << 6) | (u8[i] & 0x3f);
155     }
156     if (n > 1 && !(ch >> (n * 5 + 1)))
157       goto bail;
158     u8 += n, u8len -= n;
159
160     if (ch < 0x20 || ch >= 0x7f) {
161       if (!base64) {
162         *p++ = '&';
163         base64 = 1;
164         b = 0;
165         k = 10;
166       }
167       if (ch & ~0xffff)
168         ch = 0xfffe;
169       *p++ = __m_b64chars[b | ch >> k];
170       k -= 6;
171       for (; k >= 0; k -= 6)
172         *p++ = __m_b64chars[(ch >> k) & 0x3f];
173       b = (ch << (-k)) & 0x3f;
174       k += 16;
175     }
176     else {
177       if (base64) {
178         if (k > 10)
179           *p++ = __m_b64chars[b];
180         *p++ = '-';
181         base64 = 0;
182       }
183       *p++ = ch;
184       if (ch == '&')
185         *p++ = '-';
186     }
187   }
188
189   if (u8len) {
190     p_delete(&buf);
191     return 0;
192   }
193
194   if (base64) {
195     if (k > 10)
196       *p++ = __m_b64chars[b];
197     *p++ = '-';
198   }
199
200   *p++ = '\0';
201   if (u7len)
202     *u7len = p - buf;
203   p_realloc(&buf, p - buf);
204   if (u7)
205     *u7 = buf;
206   return buf;
207
208 bail:
209   p_delete(&buf);
210   return 0;
211 }
212
213 void imap_utf7_encode (char **s)
214 {
215   if (Charset) {
216     char *t = m_strdup(*s);
217
218     if (!mutt_convert_string (&t, Charset, "UTF-8", 0)) {
219       char *u7 = utf8_to_utf7 (t, strlen (t), NULL, 0);
220       p_delete(s);
221       *s = u7;
222     }
223     p_delete(&t);
224   }
225 }
226
227 void imap_utf7_decode (char **s)
228 {
229   if (Charset) {
230     char *t = utf7_to_utf8 (*s, m_strlen(*s), 0, 0);
231
232     if (t && !mutt_convert_string (&t, "UTF-8", Charset, 0)) {
233       p_delete(s);
234       *s = t;
235     }
236   }
237 }