Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_018Ae69wJS7sueQMUwNxV3nX
236 lines
5.5 KiB
C
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, ¶ms);
|
|
|
|
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;
|
|
}
|