[exdoc] Handle punctuation better in code formatting.
[mono.git] / mono / utils / strenc.c
1 /**
2  * \file
3  * string encoding conversions
4  *
5  * Author:
6  *      Dick Porter (dick@ximian.com)
7  *
8  * (C) 2003 Ximian, Inc.
9  */
10
11 #include <config.h>
12 #include <glib.h>
13 #include <string.h>
14
15 #include "strenc.h"
16
17 static const char trailingBytesForUTF8[256] = {
18         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
19         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
20         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
21         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
22         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
23         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
24         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
25         2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,0,0
26 };
27
28 /**
29  * mono_unicode_from_external:
30  * \param in pointers to the buffer.
31  * \param bytes number of bytes in the string.
32  * Tries to turn a NULL-terminated string into UTF-16.
33  *
34  * First, see if it's valid UTF-8, in which case just turn it directly
35  * into UTF-16.  Next, run through the colon-separated encodings in
36  * \c MONO_EXTERNAL_ENCODINGS and do an \c iconv conversion on each,
37  * returning the first successful conversion to UTF-16.  If no
38  * conversion succeeds, return NULL.
39  *
40  * Callers must free the returned string if not NULL. \p bytes holds the number
41  * of bytes in the returned string, not including the terminator.
42  */
43 gunichar2 *
44 mono_unicode_from_external (const gchar *in, gsize *bytes)
45 {
46         gchar *res=NULL;
47         gchar **encodings;
48         const gchar *encoding_list;
49         int i;
50         glong lbytes;
51         
52         if(in==NULL) {
53                 return(NULL);
54         }
55         
56         encoding_list=g_getenv ("MONO_EXTERNAL_ENCODINGS");
57         if(encoding_list==NULL) {
58                 encoding_list = "";
59         }
60         
61         encodings=g_strsplit (encoding_list, ":", 0);
62         for(i=0;encodings[i]!=NULL; i++) {
63                 /* "default_locale" is a special case encoding */
64                 if(!strcmp (encodings[i], "default_locale")) {
65                         gchar *utf8=g_locale_to_utf8 (in, -1, NULL, NULL, NULL);
66                         if(utf8!=NULL) {
67                                 res=(gchar *) g_utf8_to_utf16 (utf8, -1, NULL, &lbytes, NULL);
68                                 *bytes = (gsize) lbytes;
69                         }
70                         g_free (utf8);
71                 } else {
72                         /* Don't use UTF16 here. It returns the <FF FE> prepended to the string */
73                         res = g_convert (in, strlen (in), "UTF8", encodings[i], NULL, bytes, NULL);
74                         if (res != NULL) {
75                                 gchar *ptr = res;
76                                 res = (gchar *) g_utf8_to_utf16 (res, -1, NULL, &lbytes, NULL);
77                                 *bytes = (gsize) lbytes;
78                                 g_free (ptr);
79                         }
80                 }
81
82                 if(res!=NULL) {
83                         g_strfreev (encodings);
84                         *bytes *= 2;
85                         return((gunichar2 *)res);
86                 }
87         }
88         
89         g_strfreev (encodings);
90         
91         if(g_utf8_validate (in, -1, NULL)) {
92                 gunichar2 *unires=g_utf8_to_utf16 (in, -1, NULL, (glong *)bytes, NULL);
93                 *bytes *= 2;
94                 return(unires);
95         }
96
97         return(NULL);
98 }
99
100 /**
101  * mono_utf8_from_external:
102  * \param in pointer to the string buffer.
103  * Tries to turn a NULL-terminated string into UTF8.
104  *
105  * First, see if it's valid UTF-8, in which case there's nothing more
106  * to be done.  Next, run through the colon-separated encodings in
107  * \c MONO_EXTERNAL_ENCODINGS and do an \c iconv conversion on each,
108  * returning the first successful conversion to UTF-8.  If no
109  * conversion succeeds, return NULL.
110  *
111  * Callers must free the returned string if not NULL.
112  *
113  * This function is identical to \c mono_unicode_from_external, apart
114  * from returning UTF-8 not UTF-16; it's handy in a few places to work
115  * in UTF-8.
116  */
117 gchar *mono_utf8_from_external (const gchar *in)
118 {
119         gchar *res=NULL;
120         gchar **encodings;
121         const gchar *encoding_list;
122         int i;
123         
124         if(in==NULL) {
125                 return(NULL);
126         }
127         
128         encoding_list=g_getenv ("MONO_EXTERNAL_ENCODINGS");
129         if(encoding_list==NULL) {
130                 encoding_list = "";
131         }
132         
133         encodings=g_strsplit (encoding_list, ":", 0);
134         for(i=0;encodings[i]!=NULL; i++) {
135                 
136                 /* "default_locale" is a special case encoding */
137                 if(!strcmp (encodings[i], "default_locale")) {
138                         res=g_locale_to_utf8 (in, -1, NULL, NULL, NULL);
139                         if(res!=NULL && !g_utf8_validate (res, -1, NULL)) {
140                                 g_free (res);
141                                 res=NULL;
142                         }
143                 } else {
144                         res=g_convert (in, -1, "UTF8", encodings[i], NULL,
145                                        NULL, NULL);
146                 }
147
148                 if(res!=NULL) {
149                         g_strfreev (encodings);
150                         return(res);
151                 }
152         }
153         
154         g_strfreev (encodings);
155         
156         if(g_utf8_validate (in, -1, NULL)) {
157                 return(g_strdup (in));
158         }
159
160         return(NULL);
161 }
162
163 /**
164  * mono_unicode_to_external:
165  * \param uni a UTF-16 string to convert to an external representation.
166  * Turns NULL-terminated UTF-16 into either UTF-8, or the first
167  * working item in \c MONO_EXTERNAL_ENCODINGS if set.  If no conversions
168  * work, then UTF-8 is returned.
169  * Callers must free the returned string.
170  */
171 gchar *mono_unicode_to_external (const gunichar2 *uni)
172 {
173         gchar *utf8;
174         const gchar *encoding_list;
175         
176         /* Turn the unicode into utf8 to start with, because its
177          * easier to work with gchar * than gunichar2 *
178          */
179         utf8=g_utf16_to_utf8 (uni, -1, NULL, NULL, NULL);
180         g_assert (utf8!=NULL);
181         
182         encoding_list=g_getenv ("MONO_EXTERNAL_ENCODINGS");
183         if(encoding_list==NULL) {
184                 /* Do UTF8 */
185                 return(utf8);
186         } else {
187                 gchar *res, **encodings;
188                 int i;
189                 
190                 encodings=g_strsplit (encoding_list, ":", 0);
191                 for(i=0; encodings[i]!=NULL; i++) {
192                         if(!strcmp (encodings[i], "default_locale")) {
193                                 res=g_locale_from_utf8 (utf8, -1, NULL, NULL,
194                                                         NULL);
195                         } else {
196                                 res=g_convert (utf8, -1, encodings[i], "UTF8",
197                                                NULL, NULL, NULL);
198                         }
199
200                         if(res!=NULL) {
201                                 g_free (utf8);
202                                 g_strfreev (encodings);
203                                 
204                                 return(res);
205                         }
206                 }
207         
208                 g_strfreev (encodings);
209         }
210         
211         /* Nothing else worked, so just return the utf8 */
212         return(utf8);
213 }
214
215 /**
216  * mono_utf8_validate_and_len
217  * \param source Pointer to putative UTF-8 encoded string.
218  * Checks \p source for being valid UTF-8. \p utf is assumed to be
219  * null-terminated.
220  * \returns TRUE if \p source is valid.
221  * \p oEnd will equal the null terminator at the end of the string if valid.
222  * if not valid, it will equal the first charater of the invalid sequence.
223  * \p oLength will equal the length to \p oEnd
224  **/
225 gboolean
226 mono_utf8_validate_and_len (const gchar *source, glong* oLength, const gchar** oEnd)
227 {
228         gboolean retVal = TRUE;
229         gboolean lastRet = TRUE;
230         guchar* ptr = (guchar*) source;
231         guchar* srcPtr;
232         guint length;
233         guchar a;
234         *oLength = 0;
235         while (*ptr != 0) {
236                 length = trailingBytesForUTF8 [*ptr] + 1;
237                 srcPtr = (guchar*) ptr + length;
238                 switch (length) {
239                 default: retVal = FALSE;
240                 /* Everything else falls through when "TRUE"... */
241                 case 4: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
242                                 if ((a == (guchar) 0xBF || a == (guchar) 0xBE) && *(srcPtr-1) == (guchar) 0xBF) {
243                                 if (*(srcPtr-2) == (guchar) 0x8F || *(srcPtr-2) == (guchar) 0x9F ||
244                                         *(srcPtr-2) == (guchar) 0xAF || *(srcPtr-2) == (guchar) 0xBF)
245                                         retVal = FALSE;
246                                 }
247                 case 3: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
248                 case 2: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
249
250                 switch (*ptr) {
251                 /* no fall-through in this inner switch */
252                 case 0xE0: if (a < (guchar) 0xA0) retVal = FALSE; break;
253                 case 0xED: if (a > (guchar) 0x9F) retVal = FALSE; break;
254                 case 0xEF: if (a == (guchar)0xB7 && (*(srcPtr+1) > (guchar) 0x8F && *(srcPtr+1) < 0xB0)) retVal = FALSE;
255                                    if (a == (guchar)0xBF && (*(srcPtr+1) == (guchar) 0xBE || *(srcPtr+1) == 0xBF)) retVal = FALSE; break;
256                 case 0xF0: if (a < (guchar) 0x90) retVal = FALSE; break;
257                 case 0xF4: if (a > (guchar) 0x8F) retVal = FALSE; break;
258                 default:   if (a < (guchar) 0x80) retVal = FALSE;
259                 }
260
261                 case 1: if (*ptr >= (guchar ) 0x80 && *ptr < (guchar) 0xC2) retVal = FALSE;
262                 }
263                 if (*ptr > (guchar) 0xF4)
264                         retVal = FALSE;
265                 //If the string is invalid, set the end to the invalid byte.
266                 if (!retVal && lastRet) {
267                         if (oEnd != NULL)
268                                 *oEnd = (gchar*) ptr;
269                         lastRet = FALSE;
270                 }
271                 ptr += length;
272                 (*oLength)++;
273         }
274         if (retVal && oEnd != NULL)
275                 *oEnd = (gchar*) ptr;
276         return retVal;
277 }
278
279
280 /**
281  * mono_utf8_validate_and_len_with_bounds
282  * \param source: Pointer to putative UTF-8 encoded string.
283  * \param max_bytes: Max number of bytes that can be decoded.
284  *
285  * Checks \p source for being valid UTF-8. \p utf is assumed to be
286  * null-terminated.
287  *
288  * This function returns FALSE if it needs to decode characters beyond \p max_bytes.
289  *
290  * \returns TRUE if \p source is valid.
291  * \p oEnd will equal the null terminator at the end of the string if valid.
292  * if not valid, it will equal the first charater of the invalid sequence.
293  * \p oLength will equal the length to \p oEnd
294  **/
295 gboolean
296 mono_utf8_validate_and_len_with_bounds (const gchar *source, glong max_bytes, glong* oLength, const gchar** oEnd)
297 {
298         gboolean retVal = TRUE;
299         gboolean lastRet = TRUE;
300         guchar* ptr = (guchar*) source;
301         guchar *end = ptr + max_bytes;
302         guchar* srcPtr;
303         guint length;
304         guchar a;
305         *oLength = 0;
306
307         if (max_bytes < 1) {
308                 if (oEnd)
309                         *oEnd = (gchar*) ptr;
310                 return FALSE;
311         }
312
313         while (*ptr != 0) {
314                 length = trailingBytesForUTF8 [*ptr] + 1;
315                 srcPtr = (guchar*) ptr + length;
316                 
317                 /* since *ptr is not zero we must ensure that we can decode the current char + the byte after
318                    srcPtr points to the first byte after the current char.*/
319                 if (srcPtr >= end) {
320                         retVal = FALSE;
321                         break;
322                 }
323                 switch (length) {
324                 default: retVal = FALSE;
325                 /* Everything else falls through when "TRUE"... */
326                 case 4: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
327                                 if ((a == (guchar) 0xBF || a == (guchar) 0xBE) && *(srcPtr-1) == (guchar) 0xBF) {
328                                 if (*(srcPtr-2) == (guchar) 0x8F || *(srcPtr-2) == (guchar) 0x9F ||
329                                         *(srcPtr-2) == (guchar) 0xAF || *(srcPtr-2) == (guchar) 0xBF)
330                                         retVal = FALSE;
331                                 }
332                 case 3: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
333                 case 2: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
334
335                 switch (*ptr) {
336                 /* no fall-through in this inner switch */
337                 case 0xE0: if (a < (guchar) 0xA0) retVal = FALSE; break;
338                 case 0xED: if (a > (guchar) 0x9F) retVal = FALSE; break;
339                 case 0xEF: if (a == (guchar)0xB7 && (*(srcPtr+1) > (guchar) 0x8F && *(srcPtr+1) < 0xB0)) retVal = FALSE;
340                                    if (a == (guchar)0xBF && (*(srcPtr+1) == (guchar) 0xBE || *(srcPtr+1) == 0xBF)) retVal = FALSE; break;
341                 case 0xF0: if (a < (guchar) 0x90) retVal = FALSE; break;
342                 case 0xF4: if (a > (guchar) 0x8F) retVal = FALSE; break;
343                 default:   if (a < (guchar) 0x80) retVal = FALSE;
344                 }
345
346                 case 1: if (*ptr >= (guchar ) 0x80 && *ptr < (guchar) 0xC2) retVal = FALSE;
347                 }
348                 if (*ptr > (guchar) 0xF4)
349                         retVal = FALSE;
350                 //If the string is invalid, set the end to the invalid byte.
351                 if (!retVal && lastRet) {
352                         if (oEnd != NULL)
353                                 *oEnd = (gchar*) ptr;
354                         lastRet = FALSE;
355                 }
356                 ptr += length;
357                 (*oLength)++;
358         }
359         if (retVal && oEnd != NULL)
360                 *oEnd = (gchar*) ptr;
361         return retVal;
362 }
363