* lib/arcfour.c: Assume config.h.
[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. */
27 #include <string.h>
28
29 /* Get errno. */
30 #include <errno.h>
31
32 #ifdef _LIBC
33 # define HAVE_ICONV 1
34 #else
35 /* Get strdup. */
36 # include "strdup.h"
37 #endif
38
39 #if HAVE_ICONV
40 /* Get iconv etc. */
41 # include <iconv.h>
42 /* Get MB_LEN_MAX, CHAR_BIT.  */
43 # include <limits.h>
44 #endif
45
46 #ifndef SIZE_MAX
47 # define SIZE_MAX ((size_t) -1)
48 #endif
49
50 /* Convert a zero-terminated string STR from the FROM_CODSET code set
51    to the TO_CODESET code set.  The returned string is allocated using
52    malloc, and must be dellocated by the caller using free.  On
53    failure, NULL is returned and errno holds the error reason.  Note
54    that if TO_CODESET uses \0 for anything but to terminate the
55    string, the caller of this function may have difficulties finding
56    out the length of the output string.  */
57 char *
58 iconv_string (const char *str, const char *from_codeset,
59               const char *to_codeset)
60 {
61   char *dest = NULL;
62 #if HAVE_ICONV
63   iconv_t cd;
64 #endif
65
66   if (strcmp (to_codeset, from_codeset) == 0)
67     return strdup (str);
68
69 #if HAVE_ICONV
70   cd = iconv_open (to_codeset, from_codeset);
71   if (cd == (iconv_t) -1)
72     return NULL;
73
74   dest = iconv_alloc (cd, str);
75
76   {
77     int save_errno = errno;
78
79     if (iconv_close (cd) < 0 && dest)
80       {
81         int save_errno2 = errno;
82         /* If we didn't have a real error before, make sure we restore
83            the iconv_close error below. */
84         free (dest);
85         dest = NULL;
86         errno = save_errno2;
87       }
88     else
89       errno = save_errno;
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     return NULL;
130
131 again:
132   err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining);
133
134   if (err == (size_t) -1)
135     {
136       switch (errno)
137         {
138         case EINVAL:
139           /* Incomplete text, do not report an error */
140           break;
141
142         case E2BIG:
143           {
144             size_t used = outp - dest;
145             size_t newsize = outbuf_size * 2;
146             char *newdest;
147
148             if (newsize <= outbuf_size)
149               {
150                 errno = ENOMEM;
151                 have_error = 1;
152                 goto out;
153               }
154             newdest = (char *) realloc (dest, newsize);
155             if (newdest == NULL)
156               {
157                 have_error = 1;
158                 goto out;
159               }
160             dest = newdest;
161             outbuf_size = newsize;
162
163             outp = dest + used;
164             outbytes_remaining = outbuf_size - used - 1;        /* -1 for NUL */
165
166             goto again;
167           }
168           break;
169
170         case EILSEQ:
171           have_error = 1;
172           break;
173
174         default:
175           have_error = 1;
176           break;
177         }
178     }
179
180   *outp = '\0';
181
182 out:
183   if (have_error && dest)
184     {
185       int save_errno = errno;
186       free (dest);
187       errno = save_errno;
188       dest = NULL;
189     }
190
191   return dest;
192 }
193 #endif