Files
mtkports/www-client/netsurf/compat/iconv.c
T

236 lines
5.5 KiB
C

/*
* iconv() for the MontaukOS NetSurf port, implemented over libparserutils'
* charset codecs. See iconv.h for why.
*
* Structure: one codec decodes the source charset to UCS-4, a second encodes
* UCS-4 to the destination charset. iconv() pumps a small stack buffer of
* UCS-4 between them so neither side needs an unbounded intermediate.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <parserutils/charset/codec.h>
#include <parserutils/errors.h>
#include "iconv.h"
/* UCS-4 code points staged between decode and encode, per inner iteration. */
#define STAGE_POINTS 64
struct montauk_iconv {
parserutils_charset_codec *from; /* source charset -> UCS-4 */
parserutils_charset_codec *to; /* UCS-4 -> dest charset */
};
/*
* Strip glibc's "//TRANSLIT" / "//IGNORE" suffixes, which libparserutils does
* not understand. Returns a malloc'd bare charset name.
*/
static char *strip_suffix(const char *name)
{
const char *sep;
size_t len;
char *out;
if (name == NULL)
return NULL;
sep = strstr(name, "//");
len = (sep != NULL) ? (size_t) (sep - name) : strlen(name);
out = malloc(len + 1);
if (out == NULL)
return NULL;
memcpy(out, name, len);
out[len] = '\0';
return out;
}
static parserutils_charset_codec *make_codec(const char *name)
{
parserutils_charset_codec *codec = NULL;
parserutils_charset_codec_optparams params;
char *bare;
bare = strip_suffix(name);
if (bare == NULL)
return NULL;
if (parserutils_charset_codec_create(bare, &codec) != PARSERUTILS_OK) {
free(bare);
return NULL;
}
free(bare);
/*
* LOOSE: substitute unrepresentable characters instead of aborting.
* NetSurf renders best-effort; a hard failure mid-document is worse
* than a replacement character.
*/
params.error_mode.mode = PARSERUTILS_CHARSET_CODEC_ERROR_LOOSE;
(void) parserutils_charset_codec_setopt(codec,
PARSERUTILS_CHARSET_CODEC_ERROR_MODE, &params);
return codec;
}
iconv_t iconv_open(const char *tocode, const char *fromcode)
{
struct montauk_iconv *cd;
cd = calloc(1, sizeof(*cd));
if (cd == NULL) {
errno = ENOMEM;
return (iconv_t) -1;
}
cd->from = make_codec(fromcode);
cd->to = make_codec(tocode);
if (cd->from == NULL || cd->to == NULL) {
if (cd->from != NULL)
parserutils_charset_codec_destroy(cd->from);
if (cd->to != NULL)
parserutils_charset_codec_destroy(cd->to);
free(cd);
errno = EINVAL; /* unsupported conversion */
return (iconv_t) -1;
}
return (iconv_t) cd;
}
int iconv_close(iconv_t handle)
{
struct montauk_iconv *cd = (struct montauk_iconv *) handle;
if (cd == NULL || handle == (iconv_t) -1) {
errno = EBADF;
return -1;
}
parserutils_charset_codec_destroy(cd->from);
parserutils_charset_codec_destroy(cd->to);
free(cd);
return 0;
}
size_t iconv(iconv_t handle, char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft)
{
struct montauk_iconv *cd = (struct montauk_iconv *) handle;
size_t converted = 0;
if (cd == NULL || handle == (iconv_t) -1) {
errno = EBADF;
return (size_t) -1;
}
/* Reset request: iconv(cd, NULL, NULL, ...) returns to initial state. */
if (inbuf == NULL || *inbuf == NULL) {
parserutils_charset_codec_reset(cd->from);
parserutils_charset_codec_reset(cd->to);
return 0;
}
while (*inbytesleft > 0) {
uint8_t stage[STAGE_POINTS * 4];
const uint8_t *src = (const uint8_t *) *inbuf;
size_t srclen = *inbytesleft;
uint8_t *mid = stage;
size_t midlen = sizeof(stage);
size_t staged;
const uint8_t *midsrc;
size_t midsrclen;
uint8_t *dst;
size_t dstlen;
parserutils_error perr;
/* ---- source charset -> UCS-4 ---- */
perr = parserutils_charset_codec_decode(cd->from,
&src, &srclen, &mid, &midlen);
staged = sizeof(stage) - midlen;
if (staged == 0) {
/* nothing decoded: classify why and stop */
if (perr == PARSERUTILS_NEEDDATA)
errno = EINVAL; /* incomplete tail sequence */
else if (perr == PARSERUTILS_INVALID)
errno = EILSEQ; /* malformed input */
else if (perr == PARSERUTILS_NOMEM)
errno = E2BIG;
else
errno = EILSEQ;
return (size_t) -1;
}
/* ---- UCS-4 -> destination charset ---- */
midsrc = stage;
midsrclen = staged;
dst = (uint8_t *) *outbuf;
dstlen = *outbytesleft;
perr = parserutils_charset_codec_encode(cd->to,
&midsrc, &midsrclen, &dst, &dstlen);
/*
* Advance the caller's input only by what was both decoded AND
* encoded. If the encoder ran out of output room mid-stage,
* the undrained UCS-4 must not be counted as consumed -- the
* caller will grow its buffer and call again.
*/
{
size_t encoded_points = (staged - midsrclen) / 4;
size_t consumed_in;
*outbytesleft = dstlen;
*outbuf = (char *) dst;
if (midsrclen == 0) {
/* whole stage drained: input consumed as decoded */
consumed_in = (size_t) ((const char *) src - *inbuf);
} else {
/*
* Partial drain. Re-decode from the original
* position next time; consume nothing this
* round beyond what actually made it out.
*/
if (encoded_points == 0) {
errno = E2BIG;
return (size_t) -1;
}
consumed_in = 0;
}
if (consumed_in > 0) {
*inbuf += consumed_in;
*inbytesleft -= consumed_in;
}
converted += encoded_points;
if (midsrclen != 0) {
/* output was too small to take the rest */
errno = E2BIG;
return (size_t) -1;
}
}
if (perr == PARSERUTILS_NOMEM) {
errno = E2BIG;
return (size_t) -1;
}
if (*outbytesleft == 0 && *inbytesleft > 0) {
errno = E2BIG;
return (size_t) -1;
}
}
return converted;
}