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