#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "iconvme.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef _LIBC
# define HAVE_ICONV 1
#else
# include "strdup.h"
#endif
#if HAVE_ICONV
# include <iconv.h>
# include <limits.h>
#endif
#ifndef SIZE_MAX
# define SIZE_MAX ((size_t) -1)
#endif
char *
iconv_string (const char *str, const char *from_codeset,
const char *to_codeset)
{
char *dest = NULL;
#if HAVE_ICONV
iconv_t cd;
char *outp;
char *p = (char *) str;
size_t inbytes_remaining = strlen (p);
size_t outbuf_size = inbytes_remaining + 1;
size_t outbytes_remaining;
size_t err;
int have_error = 0;
size_t approx_sqrt_SIZE_MAX = SIZE_MAX >> (sizeof (size_t) * CHAR_BIT / 2);
if (outbuf_size <= approx_sqrt_SIZE_MAX / MB_LEN_MAX)
outbuf_size *= MB_LEN_MAX;
outbytes_remaining = outbuf_size - 1;
#endif
if (strcmp (to_codeset, from_codeset) == 0)
return strdup (str);
#if HAVE_ICONV
cd = iconv_open (to_codeset, from_codeset);
if (cd == (iconv_t) -1)
return NULL;
outp = dest = (char *) malloc (outbuf_size);
if (dest == NULL)
goto out;
again:
err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining);
if (err == (size_t) - 1)
{
switch (errno)
{
case EINVAL:
break;
case E2BIG:
{
size_t used = outp - dest;
size_t newsize = outbuf_size * 2;
char *newdest;
if (newsize <= outbuf_size)
{
errno = ENOMEM;
have_error = 1;
goto out;
}
newdest = (char *) realloc (dest, newsize);
if (newdest == NULL)
{
have_error = 1;
goto out;
}
dest = newdest;
outbuf_size = newsize;
outp = dest + used;
outbytes_remaining = outbuf_size - used - 1;
goto again;
}
break;
case EILSEQ:
have_error = 1;
break;
default:
have_error = 1;
break;
}
}
*outp = '\0';
out:
{
int save_errno = errno;
if (iconv_close (cd) < 0 && !have_error)
{
save_errno = errno;
have_error = 1;
}
if (have_error && dest)
{
free (dest);
dest = NULL;
errno = save_errno;
}
}
#else
errno = ENOSYS;
#endif
return dest;
}