exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / striconv.c
blobf7d9ccf8e237bd35244fef85b1a691771f9d86fb
1 /* Charset conversion.
2 Copyright (C) 2001-2007, 2010-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible and Simon Josefsson.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 This file 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "striconv.h"
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #if HAVE_ICONV
28 # include <iconv.h>
29 /* Get MB_LEN_MAX, CHAR_BIT. */
30 # include <limits.h>
31 #endif
33 #include "c-strcase.h"
35 #ifndef SIZE_MAX
36 # define SIZE_MAX ((size_t) -1)
37 #endif
40 #if HAVE_ICONV
42 int
43 mem_cd_iconv (const char *src, size_t srclen, iconv_t cd,
44 char **resultp, size_t *lengthp)
46 # define tmpbufsize 4096
47 size_t length;
48 char *result;
50 /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug. */
51 # if defined _LIBICONV_VERSION \
52 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
53 || defined __sun)
54 /* Set to the initial state. */
55 iconv (cd, NULL, NULL, NULL, NULL);
56 # endif
58 /* Determine the length we need. */
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;
68 while (insize > 0)
70 char *outptr = tmpbuf;
71 size_t outsize = tmpbufsize;
72 size_t res = iconv (cd,
73 (ICONV_CONST char **) &inptr, &insize,
74 &outptr, &outsize);
76 if (res == (size_t)(-1))
78 if (errno == E2BIG)
80 else if (errno == EINVAL)
81 break;
82 else
83 return -1;
85 # if !(defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \
86 && !(defined __GLIBC__ && !defined __UCLIBC__)
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 (excluding the bastard Apple iconv) and GNU libc
90 are known to prefer to fail rather than doing a lossy conversion. */
91 else if (res > 0)
93 errno = EILSEQ;
94 return -1;
96 # endif
97 count += outptr - tmpbuf;
99 /* Avoid glibc-2.1 bug and Solaris 2.7 bug. */
100 # if defined _LIBICONV_VERSION \
101 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
102 || defined __sun)
104 char *outptr = tmpbuf;
105 size_t outsize = tmpbufsize;
106 size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
108 if (res == (size_t)(-1))
109 return -1;
110 count += outptr - tmpbuf;
112 # endif
113 length = count;
114 # undef tmpbuf
117 if (length == 0)
119 *lengthp = 0;
120 return 0;
122 if (*resultp != NULL && *lengthp >= length)
123 result = *resultp;
124 else
126 result = (char *) malloc (length);
127 if (result == NULL)
129 errno = ENOMEM;
130 return -1;
134 /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug. */
135 # if defined _LIBICONV_VERSION \
136 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
137 || defined __sun)
138 /* Return to the initial state. */
139 iconv (cd, NULL, NULL, NULL, NULL);
140 # endif
142 /* Do the conversion for real. */
144 const char *inptr = src;
145 size_t insize = srclen;
146 char *outptr = result;
147 size_t outsize = length;
149 while (insize > 0)
151 size_t res = iconv (cd,
152 (ICONV_CONST char **) &inptr, &insize,
153 &outptr, &outsize);
155 if (res == (size_t)(-1))
157 if (errno == EINVAL)
158 break;
159 else
160 goto fail;
162 # if !(defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \
163 && !(defined __GLIBC__ && !defined __UCLIBC__)
164 /* Irix iconv() inserts a NUL byte if it cannot convert.
165 NetBSD iconv() inserts a question mark if it cannot convert.
166 Only GNU libiconv (excluding the bastard Apple iconv) and GNU libc
167 are known to prefer to fail rather than doing a lossy conversion. */
168 else if (res > 0)
170 errno = EILSEQ;
171 goto fail;
173 # endif
175 /* Avoid glibc-2.1 bug and Solaris 2.7 bug. */
176 # if defined _LIBICONV_VERSION \
177 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
178 || defined __sun)
180 size_t res = iconv (cd, NULL, NULL, &outptr, &outsize);
182 if (res == (size_t)(-1))
183 goto fail;
185 # endif
186 if (outsize != 0)
187 abort ();
190 *resultp = result;
191 *lengthp = length;
193 return 0;
195 fail:
197 if (result != *resultp)
198 free (result);
199 return -1;
201 # undef tmpbufsize
204 char *
205 str_cd_iconv (const char *src, iconv_t cd)
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 && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \
212 && !(defined __GLIBC__ && !defined __UCLIBC__)
213 /* Irix iconv() inserts a NUL byte if it cannot convert.
214 NetBSD iconv() inserts a question mark if it cannot convert.
215 Only GNU libiconv (excluding the bastard Apple iconv) and GNU libc are
216 known to prefer to fail rather than doing a lossy conversion. For other
217 iconv() implementations, we have to look at the number of irreversible
218 conversions returned; but this information is lost when iconv() returns
219 for an E2BIG reason. Therefore we cannot use the second, faster
220 algorithm. */
222 char *result = NULL;
223 size_t length = 0;
224 int retval = mem_cd_iconv (src, strlen (src), cd, &result, &length);
225 char *final_result;
227 if (retval < 0)
229 if (result != NULL)
230 abort ();
231 return NULL;
234 /* Add the terminating NUL byte. */
235 final_result =
236 (result != NULL ? realloc (result, length + 1) : malloc (length + 1));
237 if (final_result == NULL)
239 free (result);
240 errno = ENOMEM;
241 return NULL;
243 final_result[length] = '\0';
245 return final_result;
247 # else
248 /* This algorithm is likely faster than the one above. But it may produce
249 iconv() returns for an E2BIG reason, when the output size guess is too
250 small. Therefore it can only be used when we don't need the number of
251 irreversible conversions performed. */
252 char *result;
253 size_t result_size;
254 size_t length;
255 const char *inptr = src;
256 size_t inbytes_remaining = strlen (src);
258 /* Make a guess for the worst-case output size, in order to avoid a
259 realloc. It's OK if the guess is wrong as long as it is not zero and
260 doesn't lead to an integer overflow. */
261 result_size = inbytes_remaining;
263 size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
264 if (result_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
265 result_size *= MB_LEN_MAX;
267 result_size += 1; /* for the terminating NUL */
269 result = (char *) malloc (result_size);
270 if (result == NULL)
272 errno = ENOMEM;
273 return NULL;
276 /* Avoid glibc-2.1 bug and Solaris 2.7-2.9 bug. */
277 # if defined _LIBICONV_VERSION \
278 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
279 || defined __sun)
280 /* Set to the initial state. */
281 iconv (cd, NULL, NULL, NULL, NULL);
282 # endif
284 /* Do the conversion. */
286 char *outptr = result;
287 size_t outbytes_remaining = result_size - 1;
289 for (;;)
291 /* Here inptr + inbytes_remaining = src + strlen (src),
292 outptr + outbytes_remaining = result + result_size - 1. */
293 size_t res = iconv (cd,
294 (ICONV_CONST char **) &inptr, &inbytes_remaining,
295 &outptr, &outbytes_remaining);
297 if (res == (size_t)(-1))
299 if (errno == EINVAL)
300 break;
301 else if (errno == E2BIG)
303 size_t used = outptr - result;
304 size_t newsize = result_size * 2;
305 char *newresult;
307 if (!(newsize > result_size))
309 errno = ENOMEM;
310 goto failed;
312 newresult = (char *) realloc (result, newsize);
313 if (newresult == NULL)
315 errno = ENOMEM;
316 goto failed;
318 result = newresult;
319 result_size = newsize;
320 outptr = result + used;
321 outbytes_remaining = result_size - 1 - used;
323 else
324 goto failed;
326 else
327 break;
329 /* Avoid glibc-2.1 bug and Solaris 2.7 bug. */
330 # if defined _LIBICONV_VERSION \
331 || !(((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
332 || defined __sun)
333 for (;;)
335 /* Here outptr + outbytes_remaining = result + result_size - 1. */
336 size_t res = iconv (cd, NULL, NULL, &outptr, &outbytes_remaining);
338 if (res == (size_t)(-1))
340 if (errno == E2BIG)
342 size_t used = outptr - result;
343 size_t newsize = result_size * 2;
344 char *newresult;
346 if (!(newsize > result_size))
348 errno = ENOMEM;
349 goto failed;
351 newresult = (char *) realloc (result, newsize);
352 if (newresult == NULL)
354 errno = ENOMEM;
355 goto failed;
357 result = newresult;
358 result_size = newsize;
359 outptr = result + used;
360 outbytes_remaining = result_size - 1 - used;
362 else
363 goto failed;
365 else
366 break;
368 # endif
370 /* Add the terminating NUL byte. */
371 *outptr++ = '\0';
373 length = outptr - result;
376 /* Give away unused memory. */
377 if (length < result_size)
379 char *smaller_result = (char *) realloc (result, length);
381 if (smaller_result != NULL)
382 result = smaller_result;
385 return result;
387 failed:
388 free (result);
389 return NULL;
391 # endif
394 #endif
396 char *
397 str_iconv (const char *src, const char *from_codeset, const char *to_codeset)
399 if (*src == '\0' || c_strcasecmp (from_codeset, to_codeset) == 0)
401 char *result = strdup (src);
403 if (result == NULL)
404 errno = ENOMEM;
405 return result;
407 else
409 #if HAVE_ICONV
410 iconv_t cd;
411 char *result;
413 /* Avoid glibc-2.1 bug with EUC-KR. */
414 # if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
415 && !defined _LIBICONV_VERSION
416 if (c_strcasecmp (from_codeset, "EUC-KR") == 0
417 || c_strcasecmp (to_codeset, "EUC-KR") == 0)
419 errno = EINVAL;
420 return NULL;
422 # endif
423 cd = iconv_open (to_codeset, from_codeset);
424 if (cd == (iconv_t) -1)
425 return NULL;
427 result = str_cd_iconv (src, cd);
429 if (result == NULL)
431 /* Close cd, but preserve the errno from str_cd_iconv. */
432 int saved_errno = errno;
433 iconv_close (cd);
434 errno = saved_errno;
436 else
438 if (iconv_close (cd) < 0)
440 free (result);
441 return NULL;
444 return result;
445 #else
446 /* This is a different error code than if iconv_open existed but didn't
447 support from_codeset and to_codeset, so that the caller can emit
448 an error message such as
449 "iconv() is not supported. Installing GNU libiconv and
450 then reinstalling this package would fix this." */
451 errno = ENOSYS;
452 return NULL;
453 #endif