+/* iconv_carefully_1 is like iconv_carefully, except that it stops after
+ converting one character or one shift sequence. */
+static size_t
+iconv_carefully_1 (iconv_t cd,
+ const char **inbuf, size_t *inbytesleft,
+ char **outbuf, size_t *outbytesleft,
+ bool *incremented)
+{
+ const char *inptr_before = *inbuf;
+ const char *inptr = inptr_before;
+ const char *inptr_end = inptr_before + *inbytesleft;
+ char *outptr = *outbuf;
+ size_t outsize = *outbytesleft;
+ size_t res = (size_t)(-1);
+ size_t insize;
+
+ for (insize = 1; inptr_before + insize <= inptr_end; insize++)
+ {
+ inptr = inptr_before;
+ res = iconv (cd,
+ (ICONV_CONST char **) &inptr, &insize,
+ &outptr, &outsize);
+ if (!(res == (size_t)(-1) && errno == EINVAL))
+ break;
+ /* iconv can eat up a shift sequence but give EINVAL while attempting
+ to convert the first character. E.g. libiconv does this. */
+ if (inptr > inptr_before)
+ {
+ res = 0;
+ break;
+ }
+ }
+
+ *inbuf = inptr;
+ *inbytesleft = inptr_end - inptr;
+# if !defined _LIBICONV_VERSION && !defined __GLIBC__
+ /* Irix iconv() inserts a NUL byte if it cannot convert.
+ NetBSD iconv() inserts a question mark if it cannot convert.
+ Only GNU libiconv and GNU libc are known to prefer to fail rather
+ than doing a lossy conversion. */
+ if (res != (size_t)(-1) && res > 0)
+ {
+ /* iconv() has already incremented INPTR. We cannot go back to a
+ previous INPTR, otherwise the state inside CD would become invalid,
+ if FROM_CODESET is a stateful encoding. So, tell the caller that
+ *INBUF has already been incremented. */
+ *incremented = (inptr > inptr_before);
+ errno = EILSEQ;
+ return (size_t)(-1);
+ }
+# endif
+
+ if (res != (size_t)(-1))
+ {
+ *outbuf = outptr;
+ *outbytesleft = outsize;
+ }
+ *incremented = false;
+ return res;
+}
+
+/* utf8conv_carefully is like iconv, except that
+ - it converts from UTF-8 to UTF-8,
+ - it stops as soon as it encounters a conversion error, and it returns
+ in *INCREMENTED a boolean telling whether it has incremented the input
+ pointers past the error location,
+ - if one_character_only is true, it stops after converting one
+ character. */
+static size_t
+utf8conv_carefully (bool one_character_only,
+ const char **inbuf, size_t *inbytesleft,
+ char **outbuf, size_t *outbytesleft,
+ bool *incremented)
+{
+ const char *inptr = *inbuf;
+ size_t insize = *inbytesleft;
+ char *outptr = *outbuf;
+ size_t outsize = *outbytesleft;
+ size_t res;
+
+ res = 0;
+ do
+ {
+ ucs4_t uc;
+ int n;
+ int m;
+
+ n = u8_mbtoucr (&uc, (const uint8_t *) inptr, insize);
+ if (n < 0)
+ {
+ errno = (n == -2 ? EINVAL : EILSEQ);
+ n = u8_mbtouc (&uc, (const uint8_t *) inptr, insize);
+ inptr += n;
+ insize -= n;
+ res = (size_t)(-1);
+ *incremented = true;
+ break;
+ }
+ if (outsize == 0)
+ {
+ errno = E2BIG;
+ res = (size_t)(-1);
+ *incremented = false;
+ break;
+ }
+ m = u8_uctomb ((uint8_t *) outptr, uc, outsize);
+ if (m == -2)
+ {
+ errno = E2BIG;
+ res = (size_t)(-1);
+ *incremented = false;
+ break;
+ }
+ inptr += n;
+ insize -= n;
+ if (m == -1)
+ {
+ errno = EILSEQ;
+ res = (size_t)(-1);
+ *incremented = true;
+ break;
+ }
+ outptr += m;
+ outsize -= m;
+ }
+ while (!one_character_only && insize > 0);
+
+ *inbuf = inptr;
+ *inbytesleft = insize;
+ *outbuf = outptr;
+ *outbytesleft = outsize;
+ return res;
+}
+