/* * 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 #include #include #include #include #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; }