diff --git a/src/ccutil/unichar.cpp b/src/ccutil/unichar.cpp index ae6d61339f..f7a98dbc8b 100644 --- a/src/ccutil/unichar.cpp +++ b/src/ccutil/unichar.cpp @@ -223,12 +223,16 @@ std::vector UNICHAR::UTF8ToUTF32(const char *utf8_str) { unicodes.reserve(utf8_length); const_iterator end_it(end(utf8_str, utf8_length)); for (const_iterator it(begin(utf8_str, utf8_length)); it != end_it; ++it) { - if (it.is_legal()) { - unicodes.push_back(*it); - } else { + // utf8_step() reports the width from the leading byte alone; reject a + // truncated trailing sequence rather than let the iterator run past the + // end of the string (issue #4495). + const int remaining = end_it.utf8_data() - it.utf8_data(); + const int step = utf8_step(it.utf8_data()); + if (step <= 0 || step > remaining) { unicodes.clear(); return unicodes; } + unicodes.push_back(*it); } return unicodes; } diff --git a/unittest/unichar_test.cc b/unittest/unichar_test.cc index e03dad1718..028be7dc18 100644 --- a/unittest/unichar_test.cc +++ b/unittest/unichar_test.cc @@ -40,4 +40,23 @@ TEST(UnicharTest, InvalidText) { EXPECT_TRUE(utf8.empty()); } +TEST(UnicharTest, TruncatedUtf8) { + // This test verifies that UTF8ToUTF32 does not read past the end of a + // string that ends with a truncated multibyte prefix (issue #4495). + // A truncated multibyte prefix is invalid UTF-8, so the conversion + // must return an empty vector instead of reading past the NUL. + // Keep the explicit NULs to make the truncation boundary visible in each + // fixture; without them, the literal terminator is implicit. + const char *kTruncated2 = "\xC2\0"; + const char *kTruncated3 = "\xE8\0"; + const char *kTruncated4 = "\xF0\0"; + const char *kTruncatedMid = "ab\xE8\0"; + const char *kIllegalLeading = "\x80\0"; + EXPECT_TRUE(UNICHAR::UTF8ToUTF32(kTruncated2).empty()); + EXPECT_TRUE(UNICHAR::UTF8ToUTF32(kTruncated3).empty()); + EXPECT_TRUE(UNICHAR::UTF8ToUTF32(kTruncated4).empty()); + EXPECT_TRUE(UNICHAR::UTF8ToUTF32(kTruncatedMid).empty()); + EXPECT_TRUE(UNICHAR::UTF8ToUTF32(kIllegalLeading).empty()); +} + } // namespace tesseract