diff --git a/Tests/LibC/TestWchar.cpp b/Tests/LibC/TestWchar.cpp index f893969a62..b96c432ab3 100644 --- a/Tests/LibC/TestWchar.cpp +++ b/Tests/LibC/TestWchar.cpp @@ -316,3 +316,50 @@ TEST_CASE(wcrtomb) ret = wcrtomb(buf, L'\uFFFD', nullptr); EXPECT_NE(ret, (size_t)-1); } + +TEST_CASE(wcsrtombs) +{ + mbstate_t state = {}; + char buf[MB_LEN_MAX * 4]; + const wchar_t good_chars[] = { L'\U0001F41E', L'\U0001F41E', L'\0' }; + const wchar_t bad_chars[] = { L'\U0001F41E', static_cast(0x1111F41E), L'\0' }; + const wchar_t* src; + size_t ret = 0; + + // Convert normal and valid wchar_t values. + src = good_chars; + ret = wcsrtombs(buf, &src, 9, &state); + EXPECT_EQ(ret, 8ul); + EXPECT_EQ(memcmp(buf, "\xf0\x9f\x90\x9e\xf0\x9f\x90\x9e", 9), 0); + EXPECT_EQ(src, nullptr); + EXPECT_NE(mbsinit(&state), 0); + + // Stop on invalid wchar values. + src = bad_chars; + ret = wcsrtombs(buf, &src, 9, &state); + EXPECT_EQ(ret, -1ul); + EXPECT_EQ(memcmp(buf, "\xf0\x9f\x90\x9e", 4), 0); + EXPECT_EQ(errno, EILSEQ); + EXPECT_EQ(src, bad_chars + 1); + + // Valid characters but not enough space. + src = good_chars; + ret = wcsrtombs(buf, &src, 7, &state); + EXPECT_EQ(ret, 4ul); + EXPECT_EQ(memcmp(buf, "\xf0\x9f\x90\x9e", 4), 0); + EXPECT_EQ(src, good_chars + 1); + + // Try a conversion with no destination and too short length. + src = good_chars; + ret = wcsrtombs(nullptr, &src, 2, &state); + EXPECT_EQ(ret, 8ul); + EXPECT_EQ(src, nullptr); + EXPECT_NE(mbsinit(&state), 0); + + // Try a conversion using the internal anonymous state. + src = good_chars; + ret = wcsrtombs(buf, &src, 9, nullptr); + EXPECT_EQ(ret, 8ul); + EXPECT_EQ(memcmp(buf, "\xf0\x9f\x90\x9e\xf0\x9f\x90\x9e", 9), 0); + EXPECT_EQ(src, nullptr); +} diff --git a/Userland/Libraries/LibC/wchar.cpp b/Userland/Libraries/LibC/wchar.cpp index a0e1b361b6..7bed4b5518 100644 --- a/Userland/Libraries/LibC/wchar.cpp +++ b/Userland/Libraries/LibC/wchar.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include static unsigned int mbstate_expected_bytes(mbstate_t* state) @@ -462,4 +463,46 @@ int wcwidth(wchar_t wc) // TODO: Implement wcwidth for non-ASCII characters. return 1; } + +size_t wcsrtombs(char* dest, const wchar_t** src, size_t len, mbstate_t* ps) +{ + static mbstate_t _anonymous_state = {}; + + if (ps == nullptr) + ps = &_anonymous_state; + + size_t written = 0; + while (true) { + size_t ret = 0; + char buf[MB_LEN_MAX]; + + // Convert next wchar to multibyte. + ret = wcrtomb(buf, **src, ps); + + // wchar can't be represented as multibyte. + if (ret == (size_t)-1) { + errno = EILSEQ; + return (size_t)-1; + } + + // New bytes don't fit the buffer. + if (dest && len < written + ret) { + return written; + } + + if (dest) { + memcpy(dest, buf, ret); + dest += ret; + } + + // Null character has been reached + if (**src == L'\0') { + *src = nullptr; + return written; + } + + *src += 1; + written += ret; + } +} } diff --git a/Userland/Libraries/LibC/wchar.h b/Userland/Libraries/LibC/wchar.h index 958b992bf8..b7f2008415 100644 --- a/Userland/Libraries/LibC/wchar.h +++ b/Userland/Libraries/LibC/wchar.h @@ -56,5 +56,6 @@ double wcstod(const wchar_t*, wchar_t**); long double wcstold(const wchar_t*, wchar_t**); int swprintf(wchar_t*, size_t, const wchar_t*, ...); int wcwidth(wchar_t); +size_t wcsrtombs(char*, const wchar_t**, size_t, mbstate_t*); __END_DECLS