Skip to content

Commit 157b166

Browse files
authored
Merge pull request #1 from Caldfir/read_hex
allowHexadecimal reader option
2 parents 69098a1 + a672fc1 commit 157b166

File tree

5 files changed

+165
-1
lines changed

5 files changed

+165
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
/doc/doxyfile
1212
/dist/
1313
/.cache/
14+
/.vscode/
1415

1516
# MSVC project files:
1617
*.sln

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ project(jsoncpp
7272
# 2. ./include/json/version.h
7373
# 3. ./CMakeLists.txt
7474
# IMPORTANT: also update the PROJECT_SOVERSION!!
75-
VERSION 1.9.5 # <major>[.<minor>[.<patch>[.<tweak>]]]
75+
VERSION 1.10.0 # <major>[.<minor>[.<patch>[.<tweak>]]]
7676
LANGUAGES CXX)
7777

7878
message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")

include/json/reader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ class JSON_API CharReaderBuilder : public CharReader::Factory {
324324
* - `"allowSpecialFloats": false or true`
325325
* - If true, special float values (NaNs and infinities) are allowed and
326326
* their values are lossfree restorable.
327+
* - `"allowHexadecimal": false or true`
328+
* - If true, allow hexadecimal (eg 0xFFFF) to be used as unsigned integers.
327329
* - `"skipBom": false or true`
328330
* - If true, if the input starts with the Unicode byte order mark (BOM),
329331
* it is skipped.

src/lib_json/json_reader.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ class OurFeatures {
875875
bool failIfExtra_;
876876
bool rejectDupKeys_;
877877
bool allowSpecialFloats_;
878+
bool allowHexadecimal_;
878879
bool skipBom_;
879880
size_t stackLimit_;
880881
}; // OurFeatures
@@ -914,6 +915,7 @@ class OurReader {
914915
tokenArrayEnd,
915916
tokenString,
916917
tokenNumber,
918+
tokenHexadecimal,
917919
tokenTrue,
918920
tokenFalse,
919921
tokenNull,
@@ -952,11 +954,14 @@ class OurReader {
952954
bool readString();
953955
bool readStringSingleQuote();
954956
bool readNumber(bool checkInf);
957+
bool readHexadecimal();
955958
bool readValue();
956959
bool readObject(Token& token);
957960
bool readArray(Token& token);
958961
bool decodeNumber(Token& token);
959962
bool decodeNumber(Token& token, Value& decoded);
963+
bool decodeHexadecimal(Token& token);
964+
bool decodeHexadecimal(Token& token, Value& decoded);
960965
bool decodeString(Token& token);
961966
bool decodeString(Token& token, String& decoded);
962967
bool decodeDouble(Token& token);
@@ -1078,6 +1083,9 @@ bool OurReader::readValue() {
10781083
case tokenNumber:
10791084
successful = decodeNumber(token);
10801085
break;
1086+
case tokenHexadecimal:
1087+
successful = decodeHexadecimal(token);
1088+
break;
10811089
case tokenString:
10821090
successful = decodeString(token);
10831091
break;
@@ -1191,6 +1199,16 @@ bool OurReader::readToken(Token& token) {
11911199
ok = readComment();
11921200
break;
11931201
case '0':
1202+
if(match("x", 1)) {
1203+
token.type_ = tokenHexadecimal;
1204+
ok = features_.allowHexadecimal_;
1205+
readHexadecimal();
1206+
}
1207+
else {
1208+
token.type_ = tokenNumber;
1209+
readNumber(false);
1210+
}
1211+
break;
11941212
case '1':
11951213
case '2':
11961214
case '3':
@@ -1419,6 +1437,20 @@ bool OurReader::readNumber(bool checkInf) {
14191437
}
14201438
return true;
14211439
}
1440+
1441+
bool OurReader::readHexadecimal(void) {
1442+
Location p = current_;
1443+
for (; p < end_; ++p)
1444+
{
1445+
char c = *p;
1446+
if ( (c < '0' || c > '9')
1447+
&& (c < 'a' || c > 'f')
1448+
&& (c < 'A' || c > 'F') ) break;
1449+
}
1450+
current_ = p;
1451+
return true;
1452+
}
1453+
14221454
bool OurReader::readString() {
14231455
Char c = 0;
14241456
while (current_ != end_) {
@@ -1639,6 +1671,41 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) {
16391671
return true;
16401672
}
16411673

1674+
bool OurReader::decodeHexadecimal(Token& token) {
1675+
Value decoded;
1676+
if (!decodeHexadecimal(token, decoded))
1677+
return false;
1678+
currentValue().swapPayload(decoded);
1679+
currentValue().setOffsetStart(token.start_ - begin_);
1680+
currentValue().setOffsetLimit(token.end_ - begin_);
1681+
return true;
1682+
}
1683+
1684+
bool OurReader::decodeHexadecimal(Token& token, Value& decoded) {
1685+
Location current = token.start_;
1686+
if (current < token.end_ && *current == '0') ++current;
1687+
if (current < token.end_ && *current == 'x') ++current;
1688+
Json::LargestUInt value = 0;
1689+
if (current >= token.end_)
1690+
return addError("Zero hexadecimal digits.", token);
1691+
if (current + (sizeof(value) * 2) < token.end_)
1692+
return addError("Token too long to be unsigned integer.", token, current);
1693+
for (; current < token.end_; ++current) {
1694+
Char c = *current;
1695+
if (c >= 'a')
1696+
c -= 'a' - 10;
1697+
else if (c >= 'A')
1698+
c -= 'A' - 10;
1699+
else if (c >= '0')
1700+
c -= '0';
1701+
else
1702+
return addError("Contains non-hexadecimal digits.", token, current);
1703+
value = value << 4 | static_cast<Value::UInt>(c);
1704+
}
1705+
decoded = value;
1706+
return true;
1707+
}
1708+
16421709
bool OurReader::decodeDouble(Token& token) {
16431710
Value decoded;
16441711
if (!decodeDouble(token, decoded))
@@ -1908,6 +1975,7 @@ CharReader* CharReaderBuilder::newCharReader() const {
19081975
features.failIfExtra_ = settings_["failIfExtra"].asBool();
19091976
features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool();
19101977
features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool();
1978+
features.allowHexadecimal_ = settings_["allowHexadecimal"].asBool();
19111979
features.skipBom_ = settings_["skipBom"].asBool();
19121980
return new OurCharReader(collectComments, features);
19131981
}
@@ -1925,6 +1993,7 @@ bool CharReaderBuilder::validate(Json::Value* invalid) const {
19251993
"failIfExtra",
19261994
"rejectDupKeys",
19271995
"allowSpecialFloats",
1996+
"allowHexadecimal",
19281997
"skipBom",
19291998
};
19301999
for (auto si = settings_.begin(); si != settings_.end(); ++si) {

src/test_lib_json/main.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,98 @@ JSONTEST_FIXTURE_LOCAL(CharReaderAllowZeroesTest, issue176) {
35563556
}
35573557
}
35583558

3559+
struct CharReaderAllowHexadecimal : JsonTest::TestCase {};
3560+
3561+
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, disallowHex) {
3562+
Json::CharReaderBuilder b;
3563+
CharReaderPtr reader(b.newCharReader());
3564+
Json::Value root;
3565+
Json::String errs;
3566+
{
3567+
char const doc[] = R"({ "a":0x9, "b":0xf, "c":0xF })";
3568+
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
3569+
JSONTEST_ASSERT(!ok);
3570+
JSONTEST_ASSERT_STRING_EQUAL(
3571+
"* Line 1, Column 7\n"
3572+
" Syntax error: value, object or array expected.\n",
3573+
errs);
3574+
}
3575+
}
3576+
3577+
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, hexObject) {
3578+
Json::CharReaderBuilder b;
3579+
b.settings_["allowHexadecimal"] = true;
3580+
Json::Value invalid;
3581+
JSONTEST_ASSERT(b.validate(&invalid)) << invalid;
3582+
CharReaderPtr reader(b.newCharReader());
3583+
{
3584+
Json::Value root;
3585+
Json::String errs;
3586+
char const doc[] = R"({ "a":0x9, "b":0xf, "c":0xF })";
3587+
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
3588+
JSONTEST_ASSERT(ok);
3589+
JSONTEST_ASSERT_STRING_EQUAL("", errs);
3590+
JSONTEST_ASSERT_EQUAL(3u, root.size());
3591+
JSONTEST_ASSERT_EQUAL(0x9u, root.get("a", 0).asUInt());
3592+
JSONTEST_ASSERT_EQUAL(0xfu, root.get("b", 0).asUInt());
3593+
JSONTEST_ASSERT_EQUAL(0xFu, root.get("c", 0).asUInt());
3594+
}
3595+
}
3596+
3597+
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, hexNumbers) {
3598+
Json::CharReaderBuilder b;
3599+
b.settings_["allowHexadecimal"] = true;
3600+
CharReaderPtr reader(b.newCharReader());
3601+
3602+
struct TestData {
3603+
bool ok;
3604+
Json::LargestUInt out;
3605+
Json::String in;
3606+
};
3607+
constexpr int _ = 0; // ignored
3608+
const TestData test_data[] = {
3609+
{true, 99, "99"}, // regular number
3610+
{true, 0x99, "0x99"}, // hexadecimal number
3611+
{false, _, "AA"}, // missing prefix
3612+
{false, _, "xAA"}, // partial prefix
3613+
{true, 0xAA, "0xAA"}, // with prefix
3614+
{true, 0x00, "0x00"}, // zero
3615+
{true, 0x0123456789, "0x0123456789"}, // numeric hex
3616+
{true, 0xABCDEF, "0xABCDEF"}, // uppercase-letter hex
3617+
{true, 0xabcdef, "0xabcdef"}, // lowercase-letter hex
3618+
#ifdef JSON_HAS_INT64
3619+
{true, 0xFfffFfffFfffFfff, "0xFfffFfffFfffFfff"}, // max
3620+
{false, _, "0x1FfffFfffFfffFfff"}, // too long
3621+
#else
3622+
{true, 0xFfffFfff, "0xFfffFfff"}, // max
3623+
{false, _, "0x1FfffFfff"}, // too long
3624+
#endif
3625+
{false, _, "0x000000000000000000000000000000000000000"}, // too long
3626+
{false, _, "x"}, // leading x
3627+
{false, _, "0x"}, // empty number
3628+
{false, _, "0xx"} // extra x
3629+
};
3630+
for (const auto& td : test_data) {
3631+
Json::Value root;
3632+
Json::String errs;
3633+
const char* c0 = td.in.c_str();
3634+
const char* c1 = c0 + td.in.size();
3635+
bool ok = reader->parse(c0, c1, &root, &errs);
3636+
JSONTEST_ASSERT(td.ok == ok) << "in: " << td.in;
3637+
if (td.ok)
3638+
{
3639+
JSONTEST_ASSERT_STRING_EQUAL("", errs);
3640+
JSONTEST_ASSERT(root.isUInt64());
3641+
if (root.isUInt64())
3642+
JSONTEST_ASSERT_EQUAL(td.out, root.asLargestUInt());
3643+
}
3644+
else
3645+
{
3646+
JSONTEST_ASSERT(errs.size() > 0);
3647+
}
3648+
}
3649+
}
3650+
35593651
struct CharReaderAllowSpecialFloatsTest : JsonTest::TestCase {};
35603652

35613653
JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, specialFloat) {

0 commit comments

Comments
 (0)