prototype-related fixes
[platform/upstream/flac.git] / src / share / utf8 / iconvert.c
1 /*
2  * Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org>
3  * 
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #if HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #ifdef HAVE_ICONV
24
25 #include <assert.h>
26 #include <errno.h>
27 #include <iconv.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "iconvert.h"
32
33 /*
34  * Convert data from one encoding to another. Return:
35  *
36  *  -2 : memory allocation failed
37  *  -1 : unknown encoding
38  *   0 : data was converted exactly
39  *   1 : data was converted inexactly
40  *   2 : data was invalid (but still converted)
41  *
42  * We convert in two steps, via UTF-8, as this is the only
43  * reliable way of distinguishing between invalid input
44  * and valid input which iconv refuses to transliterate.
45  * We convert from UTF-8 twice, because we have no way of
46  * knowing whether the conversion was exact if iconv returns
47  * E2BIG (due to a bug in the specification of iconv).
48  * An alternative approach is to assume that the output of
49  * iconv is never more than 4 times as long as the input,
50  * but I prefer to avoid that assumption if possible.
51  */
52
53 int iconvert(const char *fromcode, const char *tocode,
54              const char *from, size_t fromlen,
55              char **to, size_t *tolen)
56 {
57   int ret = 0;
58   iconv_t cd1, cd2;
59   char *ib;
60   char *ob;
61   char *utfbuf = 0, *outbuf, *newbuf;
62   size_t utflen, outlen, ibl, obl, k;
63   char tbuf[2048];
64
65   cd1 = iconv_open("UTF-8", fromcode);
66   if (cd1 == (iconv_t)(-1))
67     return -1;
68
69   cd2 = (iconv_t)(-1);
70   /* Don't use strcasecmp() as it's locale-dependent. */
71   if (!strchr("Uu", tocode[0]) ||
72       !strchr("Tt", tocode[1]) ||
73       !strchr("Ff", tocode[2]) ||
74       tocode[3] != '-' ||
75       tocode[4] != '8' ||
76       tocode[5] != '\0') {
77     char *tocode1;
78
79     /*
80      * Try using this non-standard feature of glibc and libiconv.
81      * This is deliberately not a config option as people often
82      * change their iconv library without rebuilding applications.
83      */
84     tocode1 = (char *)malloc(strlen(tocode) + 11);
85     if (!tocode1)
86       goto fail;
87
88     strcpy(tocode1, tocode);
89     strcat(tocode1, "//TRANSLIT");
90     cd2 = iconv_open(tocode1, "UTF-8");
91     free(tocode1);
92
93     if (cd2 == (iconv_t)(-1))
94       cd2 = iconv_open(tocode, fromcode);
95
96     if (cd2 == (iconv_t)(-1)) {
97       iconv_close(cd1);
98       return -1;
99     }
100   }
101
102   utflen = 1; /*fromlen * 2 + 1; XXX */
103   utfbuf = (char *)malloc(utflen);
104   if (!utfbuf)
105     goto fail;
106
107   /* Convert to UTF-8 */
108   ib = (char *)from;
109   ibl = fromlen;
110   ob = utfbuf;
111   obl = utflen;
112   for (;;) {
113     k = iconv(cd1, &ib, &ibl, &ob, &obl);
114     assert((!k && !ibl) ||
115            (k == (size_t)(-1) && errno == E2BIG && ibl && obl < 6) ||
116            (k == (size_t)(-1) &&
117             (errno == EILSEQ || errno == EINVAL) && ibl));
118     if (!ibl)
119       break;
120     if (obl < 6) {
121       /* Enlarge the buffer */
122       utflen *= 2;
123       newbuf = (char *)realloc(utfbuf, utflen);
124       if (!newbuf)
125         goto fail;
126       ob = (ob - utfbuf) + newbuf;
127       obl = utflen - (ob - newbuf);
128       utfbuf = newbuf;
129     }
130     else {
131       /* Invalid input */
132       ib++, ibl--;
133       *ob++ = '#', obl--;
134       ret = 2;
135       iconv(cd1, 0, 0, 0, 0);
136     }
137   }
138
139   if (cd2 == (iconv_t)(-1)) {
140     /* The target encoding was UTF-8 */
141     if (tolen)
142       *tolen = ob - utfbuf;
143     if (!to) {
144       free(utfbuf);
145       iconv_close(cd1);
146       return ret;
147     }
148     newbuf = (char *)realloc(utfbuf, (ob - utfbuf) + 1);
149     if (!newbuf)
150       goto fail;
151     ob = (ob - utfbuf) + newbuf;
152     *ob = '\0';
153     *to = newbuf;
154     iconv_close(cd1);
155     return ret;
156   }
157
158   /* Truncate the buffer to be tidy */
159   utflen = ob - utfbuf;
160   newbuf = (char *)realloc(utfbuf, utflen);
161   if (!newbuf)
162     goto fail;
163   utfbuf = newbuf;
164
165   /* Convert from UTF-8 to discover how long the output is */
166   outlen = 0;
167   ib = utfbuf;
168   ibl = utflen;
169   while (ibl) {
170     ob = tbuf;
171     obl = sizeof(tbuf);
172     k = iconv(cd2, &ib, &ibl, &ob, &obl);
173     assert((k != (size_t)(-1) && !ibl) ||
174            (k == (size_t)(-1) && errno == E2BIG && ibl) ||
175            (k == (size_t)(-1) && errno == EILSEQ && ibl));
176     if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
177       /* Replace one character */
178       char *tb = "?";
179       size_t tbl = 1;
180
181       outlen += ob - tbuf;
182       ob = tbuf;
183       obl = sizeof(tbuf);
184       k = iconv(cd2, &tb, &tbl, &ob, &obl);
185       assert((!k && !tbl) ||
186              (k == (size_t)(-1) && errno == EILSEQ && tbl));
187       for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
188         ;
189     }
190     outlen += ob - tbuf;
191   }
192   ob = tbuf;
193   obl = sizeof(tbuf);
194   k = iconv(cd2, 0, 0, &ob, &obl);
195   assert(!k);
196   outlen += ob - tbuf;
197
198   /* Convert from UTF-8 for real */
199   outbuf = (char *)malloc(outlen + 1);
200   if (!outbuf)
201     goto fail;
202   ib = utfbuf;
203   ibl = utflen;
204   ob = outbuf;
205   obl = outlen;
206   while (ibl) {
207     k = iconv(cd2, &ib, &ibl, &ob, &obl);
208     assert((k != (size_t)(-1) && !ibl) ||
209            (k == (size_t)(-1) && errno == EILSEQ && ibl));
210     if (k && !ret)
211       ret = 1;
212     if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
213       /* Replace one character */
214       char *tb = "?";
215       size_t tbl = 1;
216
217       k = iconv(cd2, &tb, &tbl, &ob, &obl);
218       assert((!k && !tbl) ||
219              (k == (size_t)(-1) && errno == EILSEQ && tbl));
220       for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
221         ;
222     }
223   }
224   k = iconv(cd2, 0, 0, &ob, &obl);
225   assert(!k);
226   assert(!obl);
227   *ob = '\0';
228
229   free(utfbuf);
230   iconv_close(cd1);
231   iconv_close(cd2);
232   if (tolen)
233     *tolen = outlen;
234   if (!to) {
235     free(outbuf);
236     return ret;
237   }
238   *to = outbuf;
239   return ret;
240
241  fail:
242   if(0 != utfbuf)
243     free(utfbuf);
244   iconv_close(cd1);
245   if (cd2 != (iconv_t)(-1))
246     iconv_close(cd2);
247   return -2;
248 }
249
250 #endif /* HAVE_ICONV */