#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#if !defined _WIN32 && defined HAVE_ICONV
#include <assert.h>
#include <errno.h>
#include <iconv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "iconvert.h"
#include "share/alloc.h"
#include "share/safe_str.h"
int iconvert(const char *fromcode, const char *tocode,
const char *from, size_t fromlen,
char **to, size_t *tolen)
{
int ret = 0;
iconv_t cd1, cd2;
char *ib;
char *ob;
char *utfbuf = 0, *outbuf, *newbuf;
size_t utflen, outlen, ibl, obl, obp, k;
char tbuf[2048];
cd1 = iconv_open("UTF-8", fromcode);
if (cd1 == (iconv_t)(-1))
return -1;
cd2 = (iconv_t)(-1);
if (!strchr("Uu", tocode[0]) ||
!strchr("Tt", tocode[1]) ||
!strchr("Ff", tocode[2]) ||
tocode[3] != '-' ||
tocode[4] != '8' ||
tocode[5] != '\0') {
char *tocode1;
int rc;
rc = asprintf(&tocode1, "%s//TRANSLIT", tocode);
if (rc < 0 || ! tocode1)
goto fail;
cd2 = iconv_open(tocode1, "UTF-8");
free(tocode1);
if (cd2 == (iconv_t)(-1))
cd2 = iconv_open(tocode, fromcode);
if (cd2 == (iconv_t)(-1)) {
iconv_close(cd1);
return -1;
}
}
utflen = 1;
utfbuf = malloc(utflen);
if (!utfbuf)
goto fail;
ib = (char *)from;
ibl = fromlen;
ob = utfbuf;
obl = utflen;
for (;;) {
k = iconv(cd1, &ib, &ibl, &ob, &obl);
assert((!k && !ibl) ||
(k == (size_t)(-1) && errno == E2BIG && ibl && obl < 6) ||
(k == (size_t)(-1) &&
(errno == EILSEQ || errno == EINVAL) && ibl));
if (!ibl)
break;
if (obl < 6) {
if(utflen*2 < utflen)
goto fail;
utflen *= 2;
obp = ob - utfbuf;
newbuf = realloc(utfbuf, utflen);
if (!newbuf)
goto fail;
ob = newbuf + obp;
obl = utflen - obp;
utfbuf = newbuf;
}
else {
ib++, ibl--;
*ob++ = '#', obl--;
ret = 2;
iconv(cd1, 0, 0, 0, 0);
}
}
if (cd2 == (iconv_t)(-1)) {
if (tolen)
*tolen = ob - utfbuf;
if (!to) {
free(utfbuf);
iconv_close(cd1);
return ret;
}
newbuf = safe_realloc_nofree_add_2op_(utfbuf, (ob - utfbuf), 1);
if (!newbuf)
goto fail;
ob = (ob - utfbuf) + newbuf;
*ob = '\0';
*to = newbuf;
iconv_close(cd1);
return ret;
}
utflen = ob - utfbuf;
if (utflen == 0)
goto fail;
newbuf = realloc(utfbuf, utflen);
if (!newbuf)
goto fail;
utfbuf = newbuf;
outlen = 0;
ib = utfbuf;
ibl = utflen;
while (ibl) {
ob = tbuf;
obl = sizeof(tbuf);
k = iconv(cd2, &ib, &ibl, &ob, &obl);
assert((k != (size_t)(-1) && !ibl) ||
(k == (size_t)(-1) && errno == E2BIG && ibl) ||
(k == (size_t)(-1) && errno == EILSEQ && ibl));
if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
char *tb = "?";
size_t tbl = 1;
outlen += ob - tbuf;
ob = tbuf;
obl = sizeof(tbuf);
k = iconv(cd2, &tb, &tbl, &ob, &obl);
assert((!k && !tbl) ||
(k == (size_t)(-1) && errno == EILSEQ && tbl));
for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
;
}
outlen += ob - tbuf;
}
ob = tbuf;
obl = sizeof(tbuf);
k = iconv(cd2, 0, 0, &ob, &obl);
assert(!k);
outlen += ob - tbuf;
outbuf = safe_malloc_add_2op_(outlen, 1);
if (!outbuf)
goto fail;
ib = utfbuf;
ibl = utflen;
ob = outbuf;
obl = outlen;
while (ibl) {
k = iconv(cd2, &ib, &ibl, &ob, &obl);
assert((k != (size_t)(-1) && !ibl) ||
(k == (size_t)(-1) && errno == EILSEQ && ibl));
if (k && !ret)
ret = 1;
if (ibl && !(k == (size_t)(-1) && errno == E2BIG)) {
char *tb = "?";
size_t tbl = 1;
k = iconv(cd2, &tb, &tbl, &ob, &obl);
assert((!k && !tbl) ||
(k == (size_t)(-1) && errno == EILSEQ && tbl));
for (++ib, --ibl; ibl && (*ib & 0x80); ib++, ibl--)
;
}
}
k = iconv(cd2, 0, 0, &ob, &obl);
assert(!k);
assert(!obl);
*ob = '\0';
free(utfbuf);
iconv_close(cd1);
iconv_close(cd2);
if (tolen)
*tolen = outlen;
if (!to) {
free(outbuf);
return ret;
}
*to = outbuf;
return ret;
fail:
if(0 != utfbuf)
free(utfbuf);
iconv_close(cd1);
if (cd2 != (iconv_t)(-1))
iconv_close(cd2);
return -2;
}
#endif