String handling: refactor the expanding-string routines and users to use a descriptor...
[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 */