share/utf8: Add missing check
[flac.git] / src / share / utf8 / utf8.c
1 /*
2  * Copyright (C) 2001 Peter Harris <peter.harris@hummingbird.com>
3  * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org>
4  *
5  * Buffer overflow checking added: Josh Coalson, 9/9/2007
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 /*
23  * Convert a string between UTF-8 and the locale's charset.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include <config.h>
28 #endif
29
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "share/alloc.h"
34 #include "share/safe_str.h"
35 #include "share/utf8.h"
36
37
38 #ifdef _WIN32
39
40         /* Thanks to Peter Harris <peter.harris@hummingbird.com> for this win32
41          * code.
42          */
43
44 #include <stdio.h>
45 #include <windows.h>
46
47 static unsigned char *make_utf8_string(const wchar_t *unicode)
48 {
49     size_t size = 0, n;
50     int indx = 0, out_index = 0;
51     unsigned char *out;
52     unsigned short c;
53
54     /* first calculate the size of the target string */
55     c = unicode[indx++];
56     while(c) {
57         if(c < 0x0080) {
58             n = 1;
59         } else if(c < 0x0800) {
60             n = 2;
61         } else {
62             n = 3;
63         }
64         if(size+n < size) /* overflow check */
65             return NULL;
66         size += n;
67         c = unicode[indx++];
68     }
69
70     out = safe_malloc_add_2op_(size, /*+*/1);
71     if (out == NULL)
72         return NULL;
73     indx = 0;
74
75     c = unicode[indx++];
76     while(c)
77     {
78         if(c < 0x080) {
79             out[out_index++] = (unsigned char)c;
80         } else if(c < 0x800) {
81             out[out_index++] = 0xc0 | (c >> 6);
82             out[out_index++] = 0x80 | (c & 0x3f);
83         } else {
84             out[out_index++] = 0xe0 | (c >> 12);
85             out[out_index++] = 0x80 | ((c >> 6) & 0x3f);
86             out[out_index++] = 0x80 | (c & 0x3f);
87         }
88         c = unicode[indx++];
89     }
90     out[out_index] = 0x00;
91
92     return out;
93 }
94
95 static wchar_t *make_unicode_string(const unsigned char *utf8)
96 {
97     size_t size = 0;
98     int indx = 0, out_index = 0;
99     wchar_t *out;
100     unsigned char c;
101
102     /* first calculate the size of the target string */
103     c = utf8[indx++];
104     while(c) {
105         if((c & 0x80) == 0) {
106             indx += 0;
107         } else if((c & 0xe0) == 0xe0) {
108             indx += 2;
109         } else {
110             indx += 1;
111         }
112         if(size + 1 == 0) /* overflow check */
113             return NULL;
114         size++;
115         c = utf8[indx++];
116     }
117
118     if(size + 1 == 0) /* overflow check */
119         return NULL;
120     out = safe_malloc_mul_2op_(size+1, /*times*/sizeof(wchar_t));
121     if (out == NULL)
122         return NULL;
123     indx = 0;
124
125     c = utf8[indx++];
126     while(c)
127     {
128         if((c & 0x80) == 0) {
129             out[out_index++] = c;
130         } else if((c & 0xe0) == 0xe0) {
131             out[out_index] = (c & 0x1F) << 12;
132                 c = utf8[indx++];
133             out[out_index] |= (c & 0x3F) << 6;
134                 c = utf8[indx++];
135             out[out_index++] |= (c & 0x3F);
136         } else {
137             out[out_index] = (c & 0x3F) << 6;
138                 c = utf8[indx++];
139             out[out_index++] |= (c & 0x3F);
140         }
141         c = utf8[indx++];
142     }
143     out[out_index] = 0;
144
145     return out;
146 }
147
148 int utf8_encode(const char *from, char **to)
149 {
150         wchar_t *unicode;
151         int wchars, err;
152
153         wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from,
154                         strlen(from), NULL, 0);
155
156         if(wchars == 0)
157         {
158                 fprintf(stderr, "Unicode translation error %d\n", (int)GetLastError());
159                 return -1;
160         }
161
162         if(wchars < 0) /* underflow check */
163                 return -1;
164
165         unicode = safe_calloc_((size_t)wchars + 1, sizeof(unsigned short));
166         if(unicode == NULL)
167         {
168                 fprintf(stderr, "Out of memory processing string to UTF8\n");
169                 return -1;
170         }
171
172         err = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from,
173                         strlen(from), unicode, wchars);
174         if(err != wchars)
175         {
176                 free(unicode);
177                 fprintf(stderr, "Unicode translation error %d\n", (int)GetLastError());
178                 return -1;
179         }
180
181         /* On NT-based windows systems, we could use WideCharToMultiByte(), but
182          * MS doesn't actually have a consistent API across win32.
183          */
184         *to = (char*)make_utf8_string(unicode);
185         if(*to == NULL)
186         {
187                 free(unicode);
188                 fprintf(stderr, "Out of memory processing string from UNICODE16 to UTF8\n");
189                 return -1;
190         }
191
192         free(unicode);
193         return 0;
194 }
195
196 int utf8_decode(const char *from, char **to)
197 {
198     wchar_t *unicode;
199     int chars, err;
200
201     /* On NT-based windows systems, we could use MultiByteToWideChar(CP_UTF8), but
202      * MS doesn't actually have a consistent API across win32.
203      */
204     unicode = make_unicode_string((const unsigned char*)from);
205     if(unicode == NULL)
206     {
207         fprintf(stderr, "Out of memory processing string from UTF8 to UNICODE16\n");
208         return -1;
209     }
210
211     chars = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode,
212             -1, NULL, 0, NULL, NULL);
213
214     if(chars < 0) /* underflow check */
215         return -1;
216
217     if(chars == 0)
218     {
219         fprintf(stderr, "Unicode translation error %d\n", (int)GetLastError());
220         free(unicode);
221         return -1;
222     }
223
224     *to = safe_calloc_((size_t)chars + 1, sizeof(unsigned char));
225     if(*to == NULL)
226     {
227         fprintf(stderr, "Out of memory processing string to local charset\n");
228         free(unicode);
229         return -1;
230     }
231
232     err = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode,
233             -1, *to, chars, NULL, NULL);
234     if(err != chars)
235     {
236         fprintf(stderr, "Unicode translation error %d\n", (int)GetLastError());
237         free(unicode);
238         free(*to);
239         *to = NULL;
240         return -1;
241     }
242
243     free(unicode);
244     return 0;
245 }
246
247 #else /* End win32. Rest is for real operating systems */
248
249
250 #ifdef HAVE_LANGINFO_CODESET
251 #include <langinfo.h>
252 #endif
253
254 #include "iconvert.h"
255 #include "charset.h"
256
257 static const char *current_charset(void)
258 {
259   const char *c = 0;
260 #ifdef HAVE_LANGINFO_CODESET
261   c = nl_langinfo(CODESET);
262 #endif
263
264   if (!c)
265     c = getenv("CHARSET");
266
267   return c? c : "US-ASCII";
268 }
269
270 static int convert_buffer(const char *fromcode, const char *tocode,
271                           const char *from, size_t fromlen,
272                           char **to, size_t *tolen)
273 {
274   int ret = -1;
275
276 #ifdef HAVE_ICONV
277   ret = iconvert(fromcode, tocode, from, fromlen, to, tolen);
278   if (ret != -1)
279     return ret;
280 #endif
281
282 #ifndef HAVE_ICONV /* should be ifdef USE_CHARSET_CONVERT */
283   ret = charset_convert(fromcode, tocode, from, fromlen, to, tolen);
284   if (ret != -1)
285     return ret;
286 #endif
287
288   return ret;
289 }
290
291 static int convert_string(const char *fromcode, const char *tocode,
292                           const char *from, char **to, char replace)
293 {
294   int ret;
295   size_t fromlen;
296   char *s;
297
298   fromlen = strlen(from);
299   ret = convert_buffer(fromcode, tocode, from, fromlen, to, 0);
300   if (ret == -2)
301     return -1;
302   if (ret != -1)
303     return ret;
304
305   s = safe_malloc_add_2op_(fromlen, /*+*/1);
306   if (!s)
307     return -1;
308   safe_strncpy(s, from, fromlen + 1);
309   *to = s;
310   for (; *s; s++)
311     if (*s & ~0x7f)
312       *s = replace;
313   return 3;
314 }
315
316 int utf8_encode(const char *from, char **to)
317 {
318   return convert_string(current_charset(), "UTF-8", from, to, '#');
319 }
320
321 int utf8_decode(const char *from, char **to)
322 {
323   return convert_string("UTF-8", current_charset(), from, to, '?');
324 }
325
326 #endif