Skip to content

Commit db0f7c5

Browse files
committed
combine utf-16 surrogate pairs in json string parser
1 parent a8478d2 commit db0f7c5

2 files changed

Lines changed: 68 additions & 11 deletions

File tree

contrib/epee/src/parserse_base_utils.cpp

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,18 +129,31 @@ namespace misc_utils
129129
case '/': //Slash character
130130
val.push_back('/');break;
131131
case 'u': //Unicode code point
132-
if (buf_end - it < 5)
133132
{
134-
ASSERT_MES_AND_THROW("Invalid Unicode escape sequence");
135-
}
136-
else
137-
{
138-
uint32_t dst = 0;
139-
for (int i = 0; i < 4; ++i)
133+
auto read_hex4 = [&]() -> uint32_t {
134+
CHECK_AND_ASSERT_THROW_MES(buf_end - it >= 5, "Invalid Unicode escape sequence");
135+
uint32_t v = 0;
136+
for (int i = 0; i < 4; ++i)
137+
{
138+
const unsigned char tmp = isx[(unsigned char)*++it];
139+
CHECK_AND_ASSERT_THROW_MES(tmp != 0xff, "Bad Unicode encoding");
140+
v = v << 4 | tmp;
141+
}
142+
return v;
143+
};
144+
uint32_t dst = read_hex4();
145+
// combine a UTF-16 surrogate pair into a single code point; reject lone surrogates
146+
if (dst >= 0xd800 && dst <= 0xdbff)
140147
{
141-
const unsigned char tmp = isx[(unsigned char)*++it];
142-
CHECK_AND_ASSERT_THROW_MES(tmp != 0xff, "Bad Unicode encoding");
143-
dst = dst << 4 | tmp;
148+
CHECK_AND_ASSERT_THROW_MES(buf_end - it >= 3 && *(it + 1) == '\\' && *(it + 2) == 'u', "Invalid UTF-16 surrogate pair");
149+
it += 2;
150+
const uint32_t low = read_hex4();
151+
CHECK_AND_ASSERT_THROW_MES(low >= 0xdc00 && low <= 0xdfff, "Invalid UTF-16 surrogate pair");
152+
dst = 0x10000 + ((dst - 0xd800) << 10) + (low - 0xdc00);
153+
}
154+
else
155+
{
156+
CHECK_AND_ASSERT_THROW_MES(dst < 0xdc00 || dst > 0xdfff, "Invalid UTF-16 surrogate pair");
144157
}
145158
// encode as UTF-8
146159
if (dst <= 0x7f)
@@ -160,7 +173,10 @@ namespace misc_utils
160173
}
161174
else
162175
{
163-
ASSERT_MES_AND_THROW("Unicode code point is out or range");
176+
val.push_back(0xf0 | (dst >> 18));
177+
val.push_back(0x80 | ((dst >> 12) & 0x3f));
178+
val.push_back(0x80 | ((dst >> 6) & 0x3f));
179+
val.push_back(0x80 | (dst & 0x3f));
164180
}
165181
}
166182
break;

tests/unit_tests/epee_serialization.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,44 @@ TEST(epee_binary, any_empty_seq)
202202
EXPECT_TRUE(epee::serialization::load_t_from_binary(i, epee::span<const std::uint8_t>(data_empty_object)));
203203
EXPECT_EQ(0, i.x.size());
204204
}
205+
206+
namespace
207+
{
208+
struct ObjOfString
209+
{
210+
std::string x;
211+
212+
BEGIN_KV_SERIALIZE_MAP()
213+
KV_SERIALIZE(x)
214+
END_KV_SERIALIZE_MAP()
215+
};
216+
}
217+
218+
TEST(epee_json, unicode_escape_surrogate_pair)
219+
{
220+
// 😀 is the UTF-16 surrogate pair for U+1F600, which must decode to
221+
// the 4-byte UTF-8 sequence F0 9F 98 80 (not two 3-byte encoded surrogates).
222+
ObjOfString o{};
223+
EXPECT_TRUE(epee::serialization::load_t_from_json(o, "{\"x\":\"\\uD83D\\uDE00\"}"));
224+
EXPECT_EQ(std::string("\xF0\x9F\x98\x80"), o.x);
225+
226+
// Highest valid code point U+10FFFF.
227+
ObjOfString o2{};
228+
EXPECT_TRUE(epee::serialization::load_t_from_json(o2, "{\"x\":\"\\uDBFF\\uDFFF\"}"));
229+
EXPECT_EQ(std::string("\xF4\x8F\xBF\xBF"), o2.x);
230+
231+
// A basic multilingual plane escape is unaffected.
232+
ObjOfString o3{};
233+
EXPECT_TRUE(epee::serialization::load_t_from_json(o3, "{\"x\":\"\\u20AC\"}"));
234+
EXPECT_EQ(std::string("\xE2\x82\xAC"), o3.x);
235+
}
236+
237+
TEST(epee_json, unicode_escape_bad_surrogate)
238+
{
239+
// A lone high surrogate, a lone low surrogate, and a high surrogate not
240+
// followed by a low surrogate are all invalid and must fail to parse.
241+
ObjOfString o{};
242+
EXPECT_FALSE(epee::serialization::load_t_from_json(o, "{\"x\":\"\\uD83D\"}"));
243+
EXPECT_FALSE(epee::serialization::load_t_from_json(o, "{\"x\":\"\\uDE00\"}"));
244+
EXPECT_FALSE(epee::serialization::load_t_from_json(o, "{\"x\":\"\\uD83D\\u0041\"}"));
245+
}

0 commit comments

Comments
 (0)