remove special makefile variants for libiconv on darwin
[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 /*
32  * Convert data from one encoding to another. Return:
33  *
34  *  -2 : memory allocation failed
35  *  -1 : unknown encoding
36  *   0 : data was converted exactly
37  *   1 : data was converted inexactly
38  *   2 : data was invalid (but still converted)
39  *
40  * We convert in two steps, via UTF-8, as this is the only
41  * reliable way of distinguishing between invalid input
42  * and valid input which iconv refuses to transliterate.
43  * We convert from UTF-8 twice, because we have no way of
44  * knowing whether the conversion was exact if iconv returns
45  * E2BIG (due to a bug in the specification of iconv).
46  * An alternative approach is to assume that the output of
47  * iconv is never more than 4 times as long as the input,
48  * but I prefer to avoid that assumption if possible.
49  */
50
51 int iconvert(const char *fromcode, const char *tocode,
52              const char *from, size_t fromlen,
53              char **to, size_t *tolen)
54 {
55   int ret = 0;
56   iconv_t cd1, cd2;
57   char *ib;
58   char *ob;
59   char *utfbuf = 0, *outbuf, *newbuf;
60   size_t utflen, outlen, ibl, obl, k;
61   char tbuf[2048];
62
63   cd1 = iconv_open("UTF-8", fromcode);
64   if (cd1 == (iconv_t)(-1))
65     return -1;
66
67   cd2 = (iconv_t)(-1);
68   /* Don't use strcasecmp() as it's locale-dependent. */
69   if (!strchr("Uu", tocode[0]) ||
70       !strchr("Tt", tocode[1]) ||
71       !strchr("Ff", tocode[2]) ||
72       tocode[3] != '-' ||
73       tocode[4] != '8' ||
74       tocode[5] != '\0') {
75     char *tocode1;
76
77     /*
78      * Try using this non-standard feature of glibc and libiconv.
79      * This is deliberately not a config option as people often
80      * change their iconv library without rebuilding applications.
81      */
82     tocode1 = (char *)malloc(strlen(tocode) + 11);
83     if (!tocode1)
84       goto fail;
85
86     strcpy(tocode1, tocode);
87     strcat(tocode1, "//TRANSLIT");
88     cd2 = iconv_open(tocode1, "UTF-8");
89     free(tocode1);
90
91     if (cd2 == (iconv_t)(-1))
92       cd2 = iconv_open(tocode, fromcode);
93
94     if (cd2 == (iconv_t)(-1)) {
95       iconv_close(cd1);
96       return -1;
97     }
98   }
99
100   utflen = 1; /*fromlen * 2 + 1; XXX */
101   utfbuf = (char *)malloc(utflen);
102   if (!utfbuf)
103     goto fail;
104
105   /* Convert to UTF-8 */
106   ib = (char *)from;
107   ibl = fromlen;
108   ob = utfbuf;
109   obl = utflen;
110   for (;;) {
111     k = iconv(cd1, &ib, &ibl, &ob, &obl);
112     assert((!k && !ibl) ||
113            (k == (size_t)(-1) && errno == E2BIG && ibl && obl < 6) ||
114            (k == (size_t)(-1) &&
115             (errno == EILSEQ || errno == EINVAL) && ibl));
116     if (!ibl)
117       break;
118     if (obl < 6) {
119       /* Enlarge the buffer */
120       utflen *= 2;
121       newbuf = (char *)realloc(utfbuf, utflen);
122       if (!newbuf)
123         goto fail;
124       ob = (ob - utfbuf) + newbuf;
125       obl = utflen - (ob - newbuf);
126       utfbuf = newbuf;
127     }
128     else {
129       /* Invalid input */
130       ib++, ibl--;
131       *ob++ = '#', obl--;
132       ret = 2;
133       iconv(cd1, 0, 0, 0, 0);
134     }
135   }
136
137   if (cd2 == (iconv_t)(-1)) {
138     /* The target encoding was UTF-8 */
139     if (tolen)
140       *tolen = ob - utfbuf;
141     if (!to) {
142       free(utfbuf);
143       iconv_close(cd1);
144       return ret;
145     }
146     newbuf = (char *)realloc(utfbuf, (ob - utfbuf) + 1);
147     if (!newbuf)
148       goto fail;
149     ob = (ob - utfbuf) + newbuf;
150     *ob = '\0';
151     *to = newbuf;
152     iconv_close(cd1);
153     return ret;
154   }
155
156   /* Truncate the buffer to be tidy */
157   utflen = ob - utfbuf;
158   newbuf = (char *)realloc(utfbuf, utflen);
159   if (!newbuf)
160     goto fail;
161   utfbuf = newbuf;
162
163   /* Convert from UTF-8 to discover how long the output is */
164   outlen = 0;
165   ib = utfbuf;
166   ibl = utflen;
167   while (ibl) {
168     ob = tbuf;
169     obl = sizeof(tbuf);
170     k = iconv(cd2, &ib, &ibl, &ob, &obl);
171     assert((k != (size_t)(-1) && !ibl) ||
172            (k == (size_t)(-1) && errno == E2BIG && ibl) ||
173            (k == (size_t)(-1) && errno == EILSEQ && ibl));
174     if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
175       /* Replace one character */
176       char *tb = "?";
177       size_t tbl = 1;
178
179       outlen += ob - tbuf;
180       ob = tbuf;
181       obl = sizeof(tbuf);
182       k = iconv(cd2, &tb, &tbl, &ob, &obl);
183       assert((!k && !tbl) ||
184              (k == (size_t)(-1) && errno == EILSEQ && tbl));
185       for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
186         ;
187     }
188     outlen += ob - tbuf;
189   }
190   ob = tbuf;
191   obl = sizeof(tbuf);
192   k = iconv(cd2, 0, 0, &ob, &obl);
193   assert(!k);
194   outlen += ob - tbuf;
195
196   /* Convert from UTF-8 for real */
197   outbuf = (char *)malloc(outlen + 1);
198   if (!outbuf)
199     goto fail;
200   ib = utfbuf;
201   ibl = utflen;
202   ob = outbuf;
203   obl = outlen;
204   while (ibl) {
205     k = iconv(cd2, &ib, &ibl, &ob, &obl);
206     assert((k != (size_t)(-1) && !ibl) ||
207            (k == (size_t)(-1) && errno == EILSEQ && ibl));
208     if (k && !ret)
209       ret = 1;
210     if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
211       /* Replace one character */
212       char *tb = "?";
213       size_t tbl = 1;
214
215       k = iconv(cd2, &tb, &tbl, &ob, &obl);
216       assert((!k && !tbl) ||
217              (k == (size_t)(-1) && errno == EILSEQ && tbl));
218       for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
219         ;
220     }
221   }
222   k = iconv(cd2, 0, 0, &ob, &obl);
223   assert(!k);
224   assert(!obl);
225   *ob = '\0';
226
227   free(utfbuf);
228   iconv_close(cd1);
229   iconv_close(cd2);
230   if (tolen)
231     *tolen = outlen;
232   if (!to) {
233     free(outbuf);
234     return ret;
235   }
236   *to = outbuf;
237   return ret;
238
239  fail:
240   if(0 != utfbuf)
241     free(utfbuf);
242   iconv_close(cd1);
243   if (cd2 != (iconv_t)(-1))
244     iconv_close(cd2);
245   return -2;
246 }
247
248 #endif /* HAVE_ICONV */