diff options
Diffstat (limited to 'src/rcciconv.c')
-rw-r--r-- | src/rcciconv.c | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/src/rcciconv.c b/src/rcciconv.c new file mode 100644 index 0000000..d309a5f --- /dev/null +++ b/src/rcciconv.c @@ -0,0 +1,72 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <iconv.h> + +#include "internal.h" +#include "rcciconv.h" + +static void rccIConvCopySymbol(char **in_buf, int *in_left, char **out_buf, int *out_left) { + if ((out_left>0)&&(in_left>0)) { + (**out_buf)=(**in_buf); + (*out_buf)++; + (*in_buf)++; + (*in_left)--; + (*out_left)--; + } +} + +static int rccIConvUTFBytes(unsigned char c) { + int j; + if (c<128) return 1; + + for (j=6;j>=0;j--) + if ((c&(1<<j))==0) break; + + if ((j==0)||(j==6)) return 1; + return 6-j; +} + +int rccIConv(rcc_context ctx, iconv_t icnv, const char *buf, int len) { + char *in_buf, *out_buf, *res, err; + int in_left, out_left, olen; + int ub, utf_mode=0; + int errors=0; + + if ((!buf)||(!ctx)||(icnv == (iconv_t)-1)) return -1; + + len = STRNLEN(buf,len); + + if (iconv(icnv, NULL, NULL, NULL, NULL) == -1) return -1; + +loop_restart: + errors = 0; + in_buf = (char*)buf; /*DS*/ + in_left = len; + out_buf = ctx->tmpbuffer; + out_left = RCC_MAX_STRING_CHARS; + +loop: + err=iconv(icnv, &in_buf, &in_left, &out_buf, &out_left); + if (err<0) { + if (errno==E2BIG) { + *(int*)(ctx->tmpbuffer+(RCC_MAX_STRING_CHARS-sizeof(int)))=0; + } else if (errno==EILSEQ) { + if (errors++<RCC_MAX_ERRORS) { + for (ub=utf_mode?rccIConvUTFBytes(*in_buf):1;ub>0;ub--) + rccIConvCopySymbol(&in_buf, &in_left, &out_buf, &out_left); + if (in_left>0) goto loop; + } else if (!utf_mode) { + utf_mode = 1; + goto loop_restart; + } else { + return -1; + } + } else { + return -1; + } + } + + return RCC_MAX_STRING_CHARS - out_left; +} |