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