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