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