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