/* Charset conversion. Copyright (C) 2001-2007, 2010-2026 Free Software Foundation, Inc. Written by Bruno Haible and Simon Josefsson. This file is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include /* Specification. */ #include "striconv.h" #include #include #include #if HAVE_ICONV # include /* Get MB_LEN_MAX, CHAR_BIT. */ # include #endif #include "c-strcase.h" #ifndef SIZE_MAX # define SIZE_MAX ((size_t) -1) #endif #if HAVE_ICONV int mem_cd_iconv (const char *src, size_t srclen, iconv_t cd, char **resultp, size_t *lengthp) { # define tmpbufsize 4096 /* Set to the initial state. */ iconv (cd, NULL, NULL, NULL, NULL); /* Determine the length we need. */ size_t length; { size_t count = 0; /* The alignment is needed when converting e.g. to glibc's WCHAR_T or libiconv's UCS-4-INTERNAL encoding. */ union { unsigned int align; char buf[tmpbufsize]; } tmp; # define tmpbuf tmp.buf const char *inptr = src; size_t insize = srclen; while (insize > 0) { char *outptr = tmpbuf; size_t outsize = tmpbufsize; size_t res = iconv (cd, (ICONV_CONST char **) &inptr, &insize, &outptr, &outsize); if (res == (size_t)(-1)) { if (errno == E2BIG) ; else if (errno == EINVAL) break; else return -1; } # if !(defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \ && !(defined __GLIBC__ && !defined __UCLIBC__) /* NetBSD iconv() inserts a question mark if it cannot convert. Only GNU libiconv (excluding the bastard Apple iconv) and GNU libc are known to prefer to fail rather than doing a lossy conversion. */ else if (res > 0) { errno = EILSEQ; return -1; } # endif count += outptr - tmpbuf; } { char *outptr = tmpbuf; size_t outsize = tmpbufsize; size_t res = iconv (cd, NULL, NULL, &outptr, &outsize); if (res == (size_t)(-1)) return -1; count += outptr - tmpbuf; } length = count; # undef tmpbuf } if (length == 0) { *lengthp = 0; return 0; } char *result; if (*resultp != NULL && *lengthp >= length) result = *resultp; else { result = (char *) malloc (length); if (result == NULL) { errno = ENOMEM; return -1; } } /* Return to the initial state. */ iconv (cd, NULL, NULL, NULL, NULL); /* Do the conversion for real. */ { const char *inptr = src; size_t insize = srclen; char *outptr = result; size_t outsize = length; while (insize > 0) { size_t res = iconv (cd, (ICONV_CONST char **) &inptr, &insize, &outptr, &outsize); if (res == (size_t)(-1)) { if (errno == EINVAL) break; else goto fail; } # if !(defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \ && !(defined __GLIBC__ && !defined __UCLIBC__) /* NetBSD iconv() inserts a question mark if it cannot convert. Only GNU libiconv (excluding the bastard Apple iconv) and GNU libc are known to prefer to fail rather than doing a lossy conversion. */ else if (res > 0) { errno = EILSEQ; goto fail; } # endif } { size_t res = iconv (cd, NULL, NULL, &outptr, &outsize); if (res == (size_t)(-1)) goto fail; } if (outsize != 0) abort (); } *resultp = result; *lengthp = length; return 0; fail: { if (result != *resultp) free (result); return -1; } # undef tmpbufsize } char * str_cd_iconv (const char *src, iconv_t cd) { /* For most encodings, a trailing NUL byte in the input will be converted to a trailing NUL byte in the output. But not for UTF-7. So that this function is usable for UTF-7, we have to exclude the NUL byte from the conversion and add it by hand afterwards. */ # if !(defined _LIBICONV_VERSION && !(_LIBICONV_VERSION == 0x10b && defined __APPLE__)) \ && !(defined __GLIBC__ && !defined __UCLIBC__) /* NetBSD iconv() inserts a question mark if it cannot convert. Only GNU libiconv (excluding the bastard Apple iconv) and GNU libc are known to prefer to fail rather than doing a lossy conversion. For other iconv() implementations, we have to look at the number of irreversible conversions returned; but this information is lost when iconv() returns for an E2BIG reason. Therefore we cannot use the second, faster algorithm. */ char *result = NULL; size_t length = 0; int retval = mem_cd_iconv (src, strlen (src), cd, &result, &length); if (retval < 0) { if (result != NULL) abort (); return NULL; } /* Add the terminating NUL byte. */ char *final_result = (result != NULL ? realloc (result, length + 1) : malloc (length + 1)); if (final_result == NULL) { free (result); errno = ENOMEM; return NULL; } final_result[length] = '\0'; return final_result; # else /* This algorithm is likely faster than the one above. But it may produce iconv() returns for an E2BIG reason, when the output size guess is too small. Therefore it can only be used when we don't need the number of irreversible conversions performed. */ size_t inbytes_remaining = strlen (src); /* Make a guess for the worst-case output size, in order to avoid a realloc. It's OK if the guess is wrong as long as it is not zero and doesn't lead to an integer overflow. */ size_t result_size = inbytes_remaining; { size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2); if (result_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX) result_size *= MB_LEN_MAX; } result_size += 1; /* for the terminating NUL */ char *result = (char *) malloc (result_size); if (result == NULL) { errno = ENOMEM; return NULL; } /* Set to the initial state. */ iconv (cd, NULL, NULL, NULL, NULL); /* Do the conversion. */ size_t length; const char *inptr = src; { char *outptr = result; size_t outbytes_remaining = result_size - 1; for (;;) { /* Here inptr + inbytes_remaining = src + strlen (src), outptr + outbytes_remaining = result + result_size - 1. */ size_t res = iconv (cd, (ICONV_CONST char **) &inptr, &inbytes_remaining, &outptr, &outbytes_remaining); if (res == (size_t)(-1)) { if (errno == EINVAL) break; else if (errno == E2BIG) { size_t used = outptr - result; size_t newsize = result_size * 2; char *newresult; if (!(newsize > result_size)) { errno = ENOMEM; goto failed; } newresult = (char *) realloc (result, newsize); if (newresult == NULL) { errno = ENOMEM; goto failed; } result = newresult; result_size = newsize; outptr = result + used; outbytes_remaining = result_size - 1 - used; } else goto failed; } else break; } for (;;) { /* Here outptr + outbytes_remaining = result + result_size - 1. */ size_t res = iconv (cd, NULL, NULL, &outptr, &outbytes_remaining); if (res == (size_t)(-1)) { if (errno == E2BIG) { size_t used = outptr - result; size_t newsize = result_size * 2; char *newresult; if (!(newsize > result_size)) { errno = ENOMEM; goto failed; } newresult = (char *) realloc (result, newsize); if (newresult == NULL) { errno = ENOMEM; goto failed; } result = newresult; result_size = newsize; outptr = result + used; outbytes_remaining = result_size - 1 - used; } else goto failed; } else break; } /* Add the terminating NUL byte. */ *outptr++ = '\0'; length = outptr - result; } /* Give away unused memory. */ if (length < result_size) { char *smaller_result = (char *) realloc (result, length); if (smaller_result != NULL) result = smaller_result; } return result; failed: free (result); return NULL; # endif } #endif char * str_iconv (const char *src, const char *from_codeset, const char *to_codeset) { if (*src == '\0' || c_strcasecmp (from_codeset, to_codeset) == 0) { char *result = strdup (src); if (result == NULL) errno = ENOMEM; return result; } else { #if HAVE_ICONV iconv_t cd = iconv_open (to_codeset, from_codeset); if (cd == (iconv_t) -1) return NULL; char *result = str_cd_iconv (src, cd); if (result == NULL) { /* Close cd, but preserve the errno from str_cd_iconv. */ int saved_errno = errno; iconv_close (cd); errno = saved_errno; } else { if (iconv_close (cd) < 0) { free (result); return NULL; } } return result; #else /* This is a different error code than if iconv_open existed but didn't support from_codeset and to_codeset, so that the caller can emit an error message such as "iconv() is not supported. Installing GNU libiconv and then reinstalling this package would fix this." */ errno = ENOSYS; return NULL; #endif } }