New module 'vasprintf-posix'.
[gnulib.git] / lib / iconvme.c
1 /* Recode strings between character sets, using iconv.
2    Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
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, or (at your option)
7    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 along
15    with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 #include <config.h>
19
20 /* Get prototype. */
21 #include "iconvme.h"
22
23 /* Get malloc. */
24 #include <stdlib.h>
25
26 /* Get strcmp, strdup. */
27 #include <string.h>
28
29 /* Get errno. */
30 #include <errno.h>
31
32 #ifdef _LIBC
33 # define HAVE_ICONV 1
34 #endif
35
36 #if HAVE_ICONV
37 /* Get iconv etc. */
38 # include <iconv.h>
39 /* Get MB_LEN_MAX, CHAR_BIT.  */
40 # include <limits.h>
41 #endif
42
43 #ifndef SIZE_MAX
44 # define SIZE_MAX ((size_t) -1)
45 #endif
46
47 /* Convert a zero-terminated string STR from the FROM_CODSET code set
48    to the TO_CODESET code set.  The returned string is allocated using
49    malloc, and must be dellocated by the caller using free.  On
50    failure, NULL is returned and errno holds the error reason.  Note
51    that if TO_CODESET uses \0 for anything but to terminate the
52    string, the caller of this function may have difficulties finding
53    out the length of the output string.  */
54 char *
55 iconv_string (const char *str, const char *from_codeset,
56               const char *to_codeset)
57 {
58   char *dest = NULL;
59 #if HAVE_ICONV
60   iconv_t cd;
61 #endif
62
63   if (strcmp (to_codeset, from_codeset) == 0)
64     return strdup (str);
65
66 #if HAVE_ICONV
67   cd = iconv_open (to_codeset, from_codeset);
68   if (cd == (iconv_t) -1)
69     return NULL;
70
71   dest = iconv_alloc (cd, str);
72
73   if (dest == NULL)
74     {
75       int saved_errno = errno;
76       iconv_close (cd);
77       errno = saved_errno;
78     }
79   else
80     {
81       if (iconv_close (cd) < 0)
82         {
83           int saved_errno2 = errno;
84           /* If we didn't have a real error before, make sure we restore
85              the iconv_close error below. */
86           free (dest);
87           dest = NULL;
88           errno = saved_errno2;
89         }
90     }
91 #else
92   errno = ENOSYS;
93 #endif
94
95   return dest;
96 }
97
98 /* Convert a zero-terminated string STR using iconv descriptor CD.
99    The returned string is allocated using malloc, and must be
100    dellocated by the caller using free.  On failure, NULL is returned
101    and errno holds the error reason.  Note that if the target
102    character set uses \0 for anything but to terminate the string,
103    the caller of this function may have difficulties finding
104    out the length of the output string.  */
105 #if HAVE_ICONV
106 char *
107 iconv_alloc (iconv_t cd, const char *str)
108 {
109   char *dest;
110   char *p = (char *) str;
111   char *outp;
112   size_t inbytes_remaining = strlen (p);
113   /* Guess the maximum length the output string can have.  */
114   size_t outbuf_size = inbytes_remaining + 1;
115   size_t outbytes_remaining;
116   size_t err;
117   int have_error = 0;
118
119   /* Use a worst-case output size guess, so long as that wouldn't be
120      too large for comfort.  It's OK if the guess is wrong so long as
121      it's nonzero.  */
122   size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
123   if (outbuf_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
124     outbuf_size *= MB_LEN_MAX;
125   outbytes_remaining = outbuf_size - 1;
126
127   outp = dest = (char *) malloc (outbuf_size);
128   if (dest == NULL)
129     {
130       errno = ENOMEM;
131       return NULL;
132     }
133
134   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
135 # if defined _LIBICONV_VERSION \
136     || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
137   /* Set to the initial state.  */
138   iconv (cd, NULL, NULL, NULL, NULL);
139 # endif
140
141 again:
142   err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining);
143
144   if (err == (size_t) -1)
145     {
146       switch (errno)
147         {
148         case EINVAL:
149           /* Incomplete text, do not report an error */
150           break;
151
152         case E2BIG:
153           {
154             size_t used = outp - dest;
155             size_t newsize = outbuf_size * 2;
156             char *newdest;
157
158             if (newsize <= outbuf_size)
159               {
160                 errno = ENOMEM;
161                 have_error = 1;
162                 goto out;
163               }
164             newdest = (char *) realloc (dest, newsize);
165             if (newdest == NULL)
166               {
167                 errno = ENOMEM;
168                 have_error = 1;
169                 goto out;
170               }
171             dest = newdest;
172             outbuf_size = newsize;
173
174             outp = dest + used;
175             outbytes_remaining = outbuf_size - used - 1;        /* -1 for NUL */
176
177             goto again;
178           }
179           break;
180
181         case EILSEQ:
182           have_error = 1;
183           break;
184
185         default:
186           have_error = 1;
187           break;
188         }
189     }
190 # if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi)
191   /* Irix iconv() inserts a NUL byte if it cannot convert.  */
192   else if (err > 0)
193     {
194       errno = EILSEQ;
195       have_error = 1;
196       goto out;
197     }
198 # endif
199
200 again2:
201   err = iconv (cd, NULL, NULL, &outp, &outbytes_remaining);
202
203   if (err == (size_t) -1)
204     {
205       switch (errno)
206         {
207         case E2BIG:
208           {
209             size_t used = outp - dest;
210             size_t newsize = outbuf_size * 2;
211             char *newdest;
212
213             if (newsize <= outbuf_size)
214               {
215                 errno = ENOMEM;
216                 have_error = 1;
217                 goto out;
218               }
219             newdest = (char *) realloc (dest, newsize);
220             if (newdest == NULL)
221               {
222                 errno = ENOMEM;
223                 have_error = 1;
224                 goto out;
225               }
226             dest = newdest;
227             outbuf_size = newsize;
228
229             outp = dest + used;
230             outbytes_remaining = outbuf_size - used - 1;        /* -1 for NUL */
231
232             goto again2;
233           }
234           break;
235
236         default:
237           have_error = 1;
238           break;
239         }
240     }
241 # if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi)
242   /* Irix iconv() inserts a NUL byte if it cannot convert.  */
243   else if (err > 0)
244     {
245       errno = EILSEQ;
246       have_error = 1;
247       goto out;
248     }
249 # endif
250
251   *outp++ = '\0';
252
253   /* Give away unused memory.  */
254   if (outp - dest < outbuf_size)
255     {
256       char *newdest = (char *) realloc (dest, outp - dest);
257
258       if (newdest != NULL)
259         dest = newdest;
260     }
261
262 out:
263   if (have_error)
264     {
265       int save_errno = errno;
266       free (dest);
267       errno = save_errno;
268       dest = NULL;
269     }
270
271   return dest;
272 }
273 #endif