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