323225c231a06972832ca3858bc4431a5257f69a
[exim.git] / src / src / imap_utf7.c
1 #include "exim.h"
2
3 #ifdef SUPPORT_I18N
4
5 uschar *
6 imap_utf7_encode(uschar *string, const uschar *charset, uschar sep,
7 uschar *specials, uschar **error)
8 {
9 static uschar encode_base64[64] =
10 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
11 size_t slen;
12 uschar *sptr;
13 gstring * yield = NULL;
14 int i = 0, j; /* compiler quietening */
15 uschar c = 0; /* compiler quietening */
16 BOOL base64mode = FALSE;
17 BOOL lastsep = FALSE;
18 uschar utf16buf[256];
19 uschar *utf16ptr;
20 uschar *s;
21 uschar outbuf[256];
22 uschar *outptr = outbuf;
23 #if HAVE_ICONV
24 iconv_t icd;
25 #endif
26
27 if (!specials) specials = US"";
28
29 /* Pass over the string. If it consists entirely of "normal" characters
30 (possibly with leading seps), return it as is. */
31 for (s = string; *s; s++)
32 {
33 if (s == string && *s == sep)
34 string++;
35 if ( *s >= 0x7f
36 || *s < 0x20
37 || strchr("./&", *s)
38 || *s == sep
39 || Ustrchr(specials, *s)
40 )
41 break;
42 }
43
44 if (!*s)
45 return string;
46
47 sptr = string;
48 slen = Ustrlen(string);
49
50 #if HAVE_ICONV
51 if ((icd = iconv_open("UTF-16BE", CCS charset)) == (iconv_t)-1)
52 {
53 *error = string_sprintf(
54 "imapfolder: iconv_open(\"UTF-16BE\", \"%s\") failed: %s%s",
55 charset, strerror(errno),
56 errno == EINVAL ? " (maybe unsupported conversion)" : "");
57 return NULL;
58 }
59 #endif
60
61 while (slen > 0)
62 {
63 #if HAVE_ICONV
64 size_t left = sizeof(utf16buf);
65 utf16ptr = utf16buf;
66
67 if ( iconv(icd, (ICONV_ARG2_TYPE)&sptr, &slen, CSS &utf16ptr, &left)
68 == (size_t)-1
69 && errno != E2BIG
70 )
71 {
72 *error = string_sprintf("imapfolder: iconv() failed to convert from %s: %s",
73 charset, strerror(errno));
74 iconv_close(icd);
75 return NULL;
76 }
77 #else
78 for (utf16ptr = utf16buf;
79 slen > 0 && (utf16ptr - utf16buf) < sizeof(utf16buf);
80 utf16ptr += 2, slen--, sptr++)
81 {
82 *utf16ptr = *sptr;
83 *(utf16ptr+1) = '\0';
84 }
85 #endif
86
87 s = utf16buf;
88 while (s < utf16ptr)
89 {
90 /* Now encode utf16buf as modified UTF-7 */
91 if ( s[0] != 0
92 || s[1] >= 0x7f
93 || s[1] < 0x20
94 || (Ustrchr(specials, s[1]) && s[1] != sep)
95 )
96 {
97 lastsep = FALSE;
98 /* Encode as modified BASE64 */
99 if (!base64mode)
100 {
101 *outptr++ = '&';
102 base64mode = TRUE;
103 i = 0;
104 }
105
106 for (j = 0; j < 2; j++, s++) switch (i++)
107 {
108 case 0:
109 /* Top 6 bits of the first octet */
110 *outptr++ = encode_base64[(*s >> 2) & 0x3F];
111 c = (*s & 0x03); break;
112 case 1:
113 /* Bottom 2 bits of the first octet, and top 4 bits of the second */
114 *outptr++ = encode_base64[(c << 4) | ((*s >> 4) & 0x0F)];
115 c = (*s & 0x0F); break;
116 case 2:
117 /* Bottom 4 bits of the second octet and top 2 bits of the third */
118 *outptr++ = encode_base64[(c << 2) | ((*s >> 6) & 0x03)];
119 /* Bottom 6 bits of the third octet */
120 *outptr++ = encode_base64[*s & 0x3F];
121 i = 0;
122 }
123 }
124
125 else if ( (s[1] != '.' && s[1] != '/')
126 || s[1] == sep
127 )
128 {
129 /* Encode as self (almost) */
130 if (base64mode)
131 {
132 switch (i)
133 {
134 case 1:
135 /* Remaining bottom 2 bits of the last octet */
136 *outptr++ = encode_base64[c << 4];
137 break;
138 case 2:
139 /* Remaining bottom 4 bits of the last octet */
140 *outptr++ = encode_base64[c << 2];
141 }
142 *outptr++ = '-';
143 base64mode = FALSE;
144 }
145
146 if (*++s == sep)
147 {
148 if (!lastsep)
149 {
150 *outptr++ = '.';
151 lastsep = TRUE;
152 }
153 }
154 else
155 {
156 *outptr++ = *s;
157 if (*s == '&')
158 *outptr++ = '-';
159 lastsep = FALSE;
160 }
161
162 s++;
163 }
164 else
165 {
166 *error = string_sprintf("imapfolder: illegal character '%c'", s[1]);
167 return NULL;
168 }
169
170 if (outptr > outbuf + sizeof(outbuf) - 3)
171 {
172 yield = string_catn(yield, outbuf, outptr - outbuf);
173 outptr = outbuf;
174 }
175
176 }
177 } /* End of input string */
178
179 if (base64mode)
180 {
181 switch (i)
182 {
183 case 1:
184 /* Remaining bottom 2 bits of the last octet */
185 *outptr++ = encode_base64[c << 4];
186 break;
187 case 2:
188 /* Remaining bottom 4 bits of the last octet */
189 *outptr++ = encode_base64[c << 2];
190 }
191 *outptr++ = '-';
192 }
193
194 #if HAVE_ICONV
195 iconv_close(icd);
196 #endif
197
198 yield = string_catn(yield, outbuf, outptr - outbuf);
199
200 if (yield->s[yield->ptr-1] == '.')
201 yield->ptr--;
202
203 return string_from_gstring(yield);
204 }
205
206 #endif /* whole file */
207 /* vi: aw ai sw=2
208 */