Make the API of mem_cd_iconv more useful.
[gnulib.git] / lib / striconv.c
1 /* Charset conversion.
2    Copyright (C) 2001-2007 Free Software Foundation, Inc.
3    Written by Bruno Haible and Simon Josefsson.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 #include <config.h>
20
21 /* Specification.  */
22 #include "striconv.h"
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #if HAVE_ICONV
29 # include <iconv.h>
30 /* Get MB_LEN_MAX, CHAR_BIT.  */
31 # include <limits.h>
32 #endif
33
34 #include "strdup.h"
35 #include "c-strcase.h"
36
37 #ifndef SIZE_MAX
38 # define SIZE_MAX ((size_t) -1)
39 #endif
40
41
42 #if HAVE_ICONV
43
44 int
45 mem_cd_iconv (const char *src, size_t srclen, iconv_t cd,
46               char **resultp, size_t *lengthp)
47 {
48 # define tmpbufsize 4096
49   size_t length;
50   char *result;
51
52   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
53 # if defined _LIBICONV_VERSION \
54      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
55   /* Set to the initial state.  */
56   iconv (cd, NULL, NULL, NULL, NULL);
57 # endif
58
59   /* Determine the length we need.  */
60   {
61     size_t count = 0;
62     /* The alignment is needed when converting e.g. to glibc's WCHAR_T or
63        libiconv's UCS-4-INTERNAL encoding.  */
64     union { unsigned int align; char buf[tmpbufsize]; } tmp;
65 # define tmpbuf tmp.buf
66     const char *inptr = src;
67     size_t insize = srclen;
68
69     while (insize > 0)
70       {
71         char *outptr = tmpbuf;
72         size_t outsize = tmpbufsize;
73         size_t res = iconv (cd,
74                             (ICONV_CONST char **) &inptr, &insize,
75                             &outptr, &outsize);
76
77         if (res == (size_t)(-1))
78           {
79             if (errno == E2BIG)
80               ;
81             else if (errno == EINVAL)
82               break;
83             else
84               return -1;
85           }
86 # if !defined _LIBICONV_VERSION && !defined __GLIBC__
87         /* Irix iconv() inserts a NUL byte if it cannot convert.
88            NetBSD iconv() inserts a question mark if it cannot convert.
89            Only GNU libiconv and GNU libc are known to prefer to fail rather
90            than doing a lossy conversion.  */
91         else if (res > 0)
92           {
93             errno = EILSEQ;
94             return -1;
95           }
96 # endif
97         count += outptr - tmpbuf;
98       }
99     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
100 # if defined _LIBICONV_VERSION \
101      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
102     {
103       char *outptr = tmpbuf;
104       size_t outsize = tmpbufsize;
105       size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
106
107       if (res == (size_t)(-1))
108         return -1;
109       count += outptr - tmpbuf;
110     }
111 # endif
112     length = count;
113 # undef tmpbuf
114   }
115
116   if (length == 0)
117     {
118       *lengthp = 0;
119       return 0;
120     }
121   if (*resultp != NULL && *lengthp >= length)
122     result = *resultp;
123   else
124     {
125       result = (char *) malloc (length);
126       if (result == NULL)
127         {
128           errno = ENOMEM;
129           return -1;
130         }
131     }
132
133   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
134 # if defined _LIBICONV_VERSION \
135      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
136   /* Return to the initial state.  */
137   iconv (cd, NULL, NULL, NULL, NULL);
138 # endif
139
140   /* Do the conversion for real.  */
141   {
142     const char *inptr = src;
143     size_t insize = srclen;
144     char *outptr = result;
145     size_t outsize = length;
146
147     while (insize > 0)
148       {
149         size_t res = iconv (cd,
150                             (ICONV_CONST char **) &inptr, &insize,
151                             &outptr, &outsize);
152
153         if (res == (size_t)(-1))
154           {
155             if (errno == EINVAL)
156               break;
157             else
158               goto fail;
159           }
160 # if !defined _LIBICONV_VERSION && !defined __GLIBC__
161         /* Irix iconv() inserts a NUL byte if it cannot convert.
162            NetBSD iconv() inserts a question mark if it cannot convert.
163            Only GNU libiconv and GNU libc are known to prefer to fail rather
164            than doing a lossy conversion.  */
165         else if (res > 0)
166           {
167             errno = EILSEQ;
168             goto fail;
169           }
170 # endif
171       }
172     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
173 # if defined _LIBICONV_VERSION \
174      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
175     {
176       size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
177
178       if (res == (size_t)(-1))
179         goto fail;
180     }
181 # endif
182     if (outsize != 0)
183       abort ();
184   }
185
186   *resultp = result;
187   *lengthp = length;
188
189   return 0;
190
191  fail:
192   {
193     if (result != *resultp)
194       {
195         int saved_errno = errno;
196         free (result);
197         errno = saved_errno;
198       }
199     return -1;
200   }
201 # undef tmpbufsize
202 }
203
204 char *
205 str_cd_iconv (const char *src, iconv_t cd)
206 {
207   /* For most encodings, a trailing NUL byte in the input will be converted
208      to a trailing NUL byte in the output.  But not for UTF-7.  So that this
209      function is usable for UTF-7, we have to exclude the NUL byte from the
210      conversion and add it by hand afterwards.  */
211 # if !defined _LIBICONV_VERSION && !defined __GLIBC__
212   /* Irix iconv() inserts a NUL byte if it cannot convert.
213      NetBSD iconv() inserts a question mark if it cannot convert.
214      Only GNU libiconv and GNU libc are known to prefer to fail rather
215      than doing a lossy conversion.  For other iconv() implementations,
216      we have to look at the number of irreversible conversions returned;
217      but this information is lost when iconv() returns for an E2BIG reason.
218      Therefore we cannot use the second, faster algorithm.  */
219
220   char *result = NULL;
221   size_t length = 0;
222   int retval = mem_cd_iconv (src, strlen (src), cd, &result, &length);
223   char *final_result;
224
225   if (retval < 0)
226     {
227       if (result != NULL)
228         abort ();
229       return NULL;
230     }
231
232   /* Add the terminating NUL byte.  */
233   final_result =
234     (result != NULL ? realloc (result, length + 1) : malloc (length + 1));
235   if (final_result == NULL)
236     {
237       if (result != NULL)
238         free (result);
239       errno = ENOMEM;
240       return NULL;
241     }
242   final_result[length] = '\0';
243
244   return final_result;
245
246 # else
247   /* This algorithm is likely faster than the one above.  But it may produce
248      iconv() returns for an E2BIG reason, when the output size guess is too
249      small.  Therefore it can only be used when we don't need the number of
250      irreversible conversions performed.  */
251   char *result;
252   size_t result_size;
253   size_t length;
254   const char *inptr = src;
255   size_t inbytes_remaining = strlen (src);
256
257   /* Make a guess for the worst-case output size, in order to avoid a
258      realloc.  It's OK if the guess is wrong as long as it is not zero and
259      doesn't lead to an integer overflow.  */
260   result_size = inbytes_remaining;
261   {
262     size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
263     if (result_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
264       result_size *= MB_LEN_MAX;
265   }
266   result_size += 1; /* for the terminating NUL */
267
268   result = (char *) malloc (result_size);
269   if (result == NULL)
270     {
271       errno = ENOMEM;
272       return NULL;
273     }
274
275   /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug.  */
276 # if defined _LIBICONV_VERSION \
277      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
278   /* Set to the initial state.  */
279   iconv (cd, NULL, NULL, NULL, NULL);
280 # endif
281
282   /* Do the conversion.  */
283   {
284     char *outptr = result;
285     size_t outbytes_remaining = result_size - 1;
286
287     for (;;)
288       {
289         /* Here inptr + inbytes_remaining = src + strlen (src),
290                 outptr + outbytes_remaining = result + result_size - 1.  */
291         size_t res = iconv (cd,
292                             (ICONV_CONST char **) &inptr, &inbytes_remaining,
293                             &outptr, &outbytes_remaining);
294
295         if (res == (size_t)(-1))
296           {
297             if (errno == EINVAL)
298               break;
299             else if (errno == E2BIG)
300               {
301                 size_t used = outptr - result;
302                 size_t newsize = result_size * 2;
303                 char *newresult;
304
305                 if (!(newsize > result_size))
306                   {
307                     errno = ENOMEM;
308                     goto failed;
309                   }
310                 newresult = (char *) realloc (result, newsize);
311                 if (newresult == NULL)
312                   {
313                     errno = ENOMEM;
314                     goto failed;
315                   }
316                 result = newresult;
317                 result_size = newsize;
318                 outptr = result + used;
319                 outbytes_remaining = result_size - 1 - used;
320               }
321             else
322               goto failed;
323           }
324         else
325           break;
326       }
327     /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
328 # if defined _LIBICONV_VERSION \
329      || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
330     for (;;)
331       {
332         /* Here outptr + outbytes_remaining = result + result_size - 1.  */
333         size_t res = iconv (cd, NULL, NULL, &outptr, &outbytes_remaining);
334
335         if (res == (size_t)(-1))
336           {
337             if (errno == E2BIG)
338               {
339                 size_t used = outptr - result;
340                 size_t newsize = result_size * 2;
341                 char *newresult;
342
343                 if (!(newsize > result_size))
344                   {
345                     errno = ENOMEM;
346                     goto failed;
347                   }
348                 newresult = (char *) realloc (result, newsize);
349                 if (newresult == NULL)
350                   {
351                     errno = ENOMEM;
352                     goto failed;
353                   }
354                 result = newresult;
355                 result_size = newsize;
356                 outptr = result + used;
357                 outbytes_remaining = result_size - 1 - used;
358               }
359             else
360               goto failed;
361           }
362         else
363           break;
364       }
365 # endif
366
367     /* Add the terminating NUL byte.  */
368     *outptr++ = '\0';
369
370     length = outptr - result;
371   }
372
373   /* Give away unused memory.  */
374   if (length < result_size)
375     {
376       char *smaller_result = (char *) realloc (result, length);
377
378       if (smaller_result != NULL)
379         result = smaller_result;
380     }
381
382   return result;
383
384  failed:
385   {
386     int saved_errno = errno;
387     free (result);
388     errno = saved_errno;
389     return NULL;
390   }
391
392 # endif
393 }
394
395 #endif
396
397 char *
398 str_iconv (const char *src, const char *from_codeset, const char *to_codeset)
399 {
400   if (c_strcasecmp (from_codeset, to_codeset) == 0)
401     return strdup (src);
402   else
403     {
404 #if HAVE_ICONV
405       iconv_t cd;
406       char *result;
407
408       /* Avoid glibc-2.1 bug with EUC-KR.  */
409 # if (__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) && !defined _LIBICONV_VERSION
410       if (c_strcasecmp (from_codeset, "EUC-KR") == 0
411           || c_strcasecmp (to_codeset, "EUC-KR") == 0)
412         {
413           errno = EINVAL;
414           return NULL;
415         }
416 # endif
417       cd = iconv_open (to_codeset, from_codeset);
418       if (cd == (iconv_t) -1)
419         return NULL;
420
421       result = str_cd_iconv (src, cd);
422
423       if (result == NULL)
424         {
425           /* Close cd, but preserve the errno from str_cd_iconv.  */
426           int saved_errno = errno;
427           iconv_close (cd);
428           errno = saved_errno;
429         }
430       else
431         {
432           if (iconv_close (cd) < 0)
433             {
434               /* Return NULL, but free the allocated memory, and while doing
435                  that, preserve the errno from iconv_close.  */
436               int saved_errno = errno;
437               free (result);
438               errno = saved_errno;
439               return NULL;
440             }
441         }
442       return result;
443 #else
444       /* This is a different error code than if iconv_open existed but didn't
445          support from_codeset and to_codeset, so that the caller can emit
446          an error message such as
447            "iconv() is not supported. Installing GNU libiconv and
448             then reinstalling this package would fix this."  */
449       errno = ENOSYS;
450       return NULL;
451 #endif
452     }
453 }