/* Convert multibyte character to 32-bit wide character. Copyright (C) 2020-2024 Free Software Foundation, Inc. This file is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ /* Written by Bruno Haible , 2020. */ #include /* Specification. */ #include #include "attribute.h" #include #include #if GL_CHAR32_T_IS_UNICODE # include "lc-charset-unicode.h" #endif #if GNULIB_defined_mbstate_t /* AIX, IRIX */ /* Implement mbrtoc32() on top of mbtowc() for the non-UTF-8 locales and directly for the UTF-8 locales. */ /* Note: On AIX (64-bit) we can implement mbrtoc32 in two equivalent ways: - in a way that parallels the override of mbrtowc; this is the code branch here; - in a way that invokes the overridden mbrtowc; this would be the #else branch below. They are equivalent. */ # if AVOID_ANY_THREADS /* The option '--disable-threads' explicitly requests no locking. */ # elif defined _WIN32 && !defined __CYGWIN__ # define WIN32_LEAN_AND_MEAN /* avoid including junk */ # include # elif HAVE_PTHREAD_API # include # if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS # include # pragma weak thrd_exit # define c11_threads_in_use() (thrd_exit != NULL) # else # define c11_threads_in_use() 0 # endif # elif HAVE_THREADS_H # include # endif # include "lc-charset-dispatch.h" # include "mbtowc-lock.h" static_assert (sizeof (mbstate_t) >= 4); static char internal_state[4]; size_t mbrtoc32 (char32_t *pwc, const char *s, size_t n, mbstate_t *ps) { # define FITS_IN_CHAR_TYPE(wc) 1 # include "mbrtowc-impl.h" } #else /* glibc, macOS, FreeBSD, NetBSD, OpenBSD, HP-UX, Solaris, Cygwin, mingw, MSVC, Minix, Android */ /* Implement mbrtoc32() based on the original mbrtoc32() or on mbrtowc(). */ # include # include "localcharset.h" # include "streq.h" # if MBRTOC32_IN_C_LOCALE_MAYBE_EILSEQ # include "hard-locale.h" # include # endif static mbstate_t internal_state; size_t mbrtoc32 (char32_t *pwc, const char *s, size_t n, mbstate_t *ps) # undef mbrtoc32 { /* It's simpler to handle the case s == NULL upfront, than to worry about this case later, before every test of pwc and n. */ if (s == NULL) { pwc = NULL; s = ""; n = 1; } # if MBRTOC32_EMPTY_INPUT_BUG || _GL_SMALL_WCHAR_T if (n == 0) return (size_t) -2; # endif if (ps == NULL) ps = &internal_state; # if HAVE_WORKING_MBRTOC32 && HAVE_WORKING_C32RTOMB && !MBRTOC32_MULTIBYTE_LOCALE_BUG /* mbrtoc32() may produce different values for wc than mbrtowc(). Therefore use mbrtoc32(). */ # if defined _WIN32 && !defined __CYGWIN__ char32_t wc; size_t ret = mbrtoc32 (&wc, s, n, ps); if (ret < (size_t) -2 && pwc != NULL) *pwc = wc; # else size_t ret = mbrtoc32 (pwc, s, n, ps); # endif # if GNULIB_MBRTOC32_REGULAR /* Verify that mbrtoc32 is regular. */ if (ret < (size_t) -3 && ! mbsinit (ps)) /* This occurs on glibc 2.36. */ mbszero (ps); if (ret == (size_t) -3) abort (); # endif # if MBRTOC32_IN_C_LOCALE_MAYBE_EILSEQ if ((size_t) -2 <= ret && n != 0 && ! hard_locale (LC_CTYPE)) { if (pwc != NULL) *pwc = (unsigned char) *s; return 1; } # endif return ret; # elif _GL_SMALL_WCHAR_T /* Special-case all encodings that may produce wide character values > WCHAR_MAX. */ const char *encoding = locale_charset (); if (STREQ_OPT (encoding, "UTF-8", 'U', 'T', 'F', '-', '8', 0, 0, 0, 0)) { /* Special-case the UTF-8 encoding. Assume that the wide-character encoding in a UTF-8 locale is UCS-2 or, equivalently, UTF-16. */ /* Here n > 0. */ char *pstate = (char *)ps; size_t nstate = pstate[0]; char buf[4]; const char *p; size_t m; int res; switch (nstate) { case 0: p = s; m = n; break; case 3: buf[2] = pstate[3]; FALLTHROUGH; case 2: buf[1] = pstate[2]; FALLTHROUGH; case 1: buf[0] = pstate[1]; p = buf; m = nstate; buf[m++] = s[0]; if (n >= 2 && m < 4) { buf[m++] = s[1]; if (n >= 3 && m < 4) buf[m++] = s[2]; } break; default: errno = EINVAL; return (size_t)(-1); } /* Here m > 0. */ { # define FITS_IN_CHAR_TYPE(wc) 1 # include "mbrtowc-impl-utf8.h" } success: if (nstate >= (res > 0 ? res : 1)) abort (); res -= nstate; /* Set *ps to an initial state. */ # if defined _WIN32 && !defined __CYGWIN__ /* Native Windows. */ /* MSVC defines 'mbstate_t' as an 8-byte struct; the first 4 bytes matter. On mingw, 'mbstate_t' is sometimes defined as 'int', sometimes defined as an 8-byte struct, of which the first 4 bytes matter. */ *(unsigned int *)pstate = 0; # elif defined __CYGWIN__ /* Cygwin defines 'mbstate_t' as an 8-byte struct; the first 4 bytes matter. */ ps->__count = 0; # else pstate[0] = 0; # endif return res; incomplete: { size_t k = nstate; /* Here 0 <= k < m < 4. */ pstate[++k] = s[0]; if (k < m) { pstate[++k] = s[1]; if (k < m) pstate[++k] = s[2]; } if (k != m) abort (); } pstate[0] = m; return (size_t)(-2); invalid: errno = EILSEQ; /* The conversion state is undefined, says POSIX. */ return (size_t)(-1); } else { wchar_t wc; size_t ret = mbrtowc (&wc, s, n, ps); if (ret < (size_t) -2 && pwc != NULL) *pwc = wc; return ret; } # else /* char32_t and wchar_t are equivalent. Use mbrtowc(). */ wchar_t wc; size_t ret = mbrtowc (&wc, s, n, ps); # if GNULIB_MBRTOC32_REGULAR /* Ensure that mbrtoc32 is regular. */ if (ret < (size_t) -2 && ! mbsinit (ps)) /* This occurs on glibc 2.12. */ mbszero (ps); # endif # if GL_CHAR32_T_IS_UNICODE && GL_CHAR32_T_VS_WCHAR_T_NEEDS_CONVERSION if (ret < (size_t) -2 && wc != 0) { wc = locale_encoding_to_unicode (wc); if (wc == 0) { ret = (size_t) -1; errno = EILSEQ; } } # endif if (ret < (size_t) -2 && pwc != NULL) *pwc = wc; return ret; # endif } #endif