Blame gettext-runtime/gnulib-lib/striconv.c

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