Merge pull request #1 from Caldfir/read_hex

allowHexadecimal reader option
This commit is contained in:
taitken 2023-09-16 20:25:17 -07:00 committed by GitHub
commit 157b166e23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 1 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@
/doc/doxyfile /doc/doxyfile
/dist/ /dist/
/.cache/ /.cache/
/.vscode/
# MSVC project files: # MSVC project files:
*.sln *.sln

View File

@ -72,7 +72,7 @@ project(jsoncpp
# 2. ./include/json/version.h # 2. ./include/json/version.h
# 3. ./CMakeLists.txt # 3. ./CMakeLists.txt
# IMPORTANT: also update the PROJECT_SOVERSION!! # IMPORTANT: also update the PROJECT_SOVERSION!!
VERSION 1.9.5 # <major>[.<minor>[.<patch>[.<tweak>]]] VERSION 1.10.0 # <major>[.<minor>[.<patch>[.<tweak>]]]
LANGUAGES CXX) LANGUAGES CXX)
message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") message(STATUS "JsonCpp Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")

View File

@ -324,6 +324,8 @@ public:
* - `"allowSpecialFloats": false or true` * - `"allowSpecialFloats": false or true`
* - If true, special float values (NaNs and infinities) are allowed and * - If true, special float values (NaNs and infinities) are allowed and
* their values are lossfree restorable. * their values are lossfree restorable.
* - `"allowHexadecimal": false or true`
* - If true, allow hexadecimal (eg 0xFFFF) to be used as unsigned integers.
* - `"skipBom": false or true` * - `"skipBom": false or true`
* - If true, if the input starts with the Unicode byte order mark (BOM), * - If true, if the input starts with the Unicode byte order mark (BOM),
* it is skipped. * it is skipped.

View File

@ -875,6 +875,7 @@ public:
bool failIfExtra_; bool failIfExtra_;
bool rejectDupKeys_; bool rejectDupKeys_;
bool allowSpecialFloats_; bool allowSpecialFloats_;
bool allowHexadecimal_;
bool skipBom_; bool skipBom_;
size_t stackLimit_; size_t stackLimit_;
}; // OurFeatures }; // OurFeatures
@ -914,6 +915,7 @@ private:
tokenArrayEnd, tokenArrayEnd,
tokenString, tokenString,
tokenNumber, tokenNumber,
tokenHexadecimal,
tokenTrue, tokenTrue,
tokenFalse, tokenFalse,
tokenNull, tokenNull,
@ -952,11 +954,14 @@ private:
bool readString(); bool readString();
bool readStringSingleQuote(); bool readStringSingleQuote();
bool readNumber(bool checkInf); bool readNumber(bool checkInf);
bool readHexadecimal();
bool readValue(); bool readValue();
bool readObject(Token& token); bool readObject(Token& token);
bool readArray(Token& token); bool readArray(Token& token);
bool decodeNumber(Token& token); bool decodeNumber(Token& token);
bool decodeNumber(Token& token, Value& decoded); bool decodeNumber(Token& token, Value& decoded);
bool decodeHexadecimal(Token& token);
bool decodeHexadecimal(Token& token, Value& decoded);
bool decodeString(Token& token); bool decodeString(Token& token);
bool decodeString(Token& token, String& decoded); bool decodeString(Token& token, String& decoded);
bool decodeDouble(Token& token); bool decodeDouble(Token& token);
@ -1078,6 +1083,9 @@ bool OurReader::readValue() {
case tokenNumber: case tokenNumber:
successful = decodeNumber(token); successful = decodeNumber(token);
break; break;
case tokenHexadecimal:
successful = decodeHexadecimal(token);
break;
case tokenString: case tokenString:
successful = decodeString(token); successful = decodeString(token);
break; break;
@ -1191,6 +1199,16 @@ bool OurReader::readToken(Token& token) {
ok = readComment(); ok = readComment();
break; break;
case '0': case '0':
if(match("x", 1)) {
token.type_ = tokenHexadecimal;
ok = features_.allowHexadecimal_;
readHexadecimal();
}
else {
token.type_ = tokenNumber;
readNumber(false);
}
break;
case '1': case '1':
case '2': case '2':
case '3': case '3':
@ -1419,6 +1437,20 @@ bool OurReader::readNumber(bool checkInf) {
} }
return true; return true;
} }
bool OurReader::readHexadecimal(void) {
Location p = current_;
for (; p < end_; ++p)
{
char c = *p;
if ( (c < '0' || c > '9')
&& (c < 'a' || c > 'f')
&& (c < 'A' || c > 'F') ) break;
}
current_ = p;
return true;
}
bool OurReader::readString() { bool OurReader::readString() {
Char c = 0; Char c = 0;
while (current_ != end_) { while (current_ != end_) {
@ -1639,6 +1671,41 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) {
return true; return true;
} }
bool OurReader::decodeHexadecimal(Token& token) {
Value decoded;
if (!decodeHexadecimal(token, decoded))
return false;
currentValue().swapPayload(decoded);
currentValue().setOffsetStart(token.start_ - begin_);
currentValue().setOffsetLimit(token.end_ - begin_);
return true;
}
bool OurReader::decodeHexadecimal(Token& token, Value& decoded) {
Location current = token.start_;
if (current < token.end_ && *current == '0') ++current;
if (current < token.end_ && *current == 'x') ++current;
Json::LargestUInt value = 0;
if (current >= token.end_)
return addError("Zero hexadecimal digits.", token);
if (current + (sizeof(value) * 2) < token.end_)
return addError("Token too long to be unsigned integer.", token, current);
for (; current < token.end_; ++current) {
Char c = *current;
if (c >= 'a')
c -= 'a' - 10;
else if (c >= 'A')
c -= 'A' - 10;
else if (c >= '0')
c -= '0';
else
return addError("Contains non-hexadecimal digits.", token, current);
value = value << 4 | static_cast<Value::UInt>(c);
}
decoded = value;
return true;
}
bool OurReader::decodeDouble(Token& token) { bool OurReader::decodeDouble(Token& token) {
Value decoded; Value decoded;
if (!decodeDouble(token, decoded)) if (!decodeDouble(token, decoded))
@ -1908,6 +1975,7 @@ CharReader* CharReaderBuilder::newCharReader() const {
features.failIfExtra_ = settings_["failIfExtra"].asBool(); features.failIfExtra_ = settings_["failIfExtra"].asBool();
features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool();
features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool();
features.allowHexadecimal_ = settings_["allowHexadecimal"].asBool();
features.skipBom_ = settings_["skipBom"].asBool(); features.skipBom_ = settings_["skipBom"].asBool();
return new OurCharReader(collectComments, features); return new OurCharReader(collectComments, features);
} }
@ -1925,6 +1993,7 @@ bool CharReaderBuilder::validate(Json::Value* invalid) const {
"failIfExtra", "failIfExtra",
"rejectDupKeys", "rejectDupKeys",
"allowSpecialFloats", "allowSpecialFloats",
"allowHexadecimal",
"skipBom", "skipBom",
}; };
for (auto si = settings_.begin(); si != settings_.end(); ++si) { for (auto si = settings_.begin(); si != settings_.end(); ++si) {

View File

@ -3556,6 +3556,98 @@ JSONTEST_FIXTURE_LOCAL(CharReaderAllowZeroesTest, issue176) {
} }
} }
struct CharReaderAllowHexadecimal : JsonTest::TestCase {};
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, disallowHex) {
Json::CharReaderBuilder b;
CharReaderPtr reader(b.newCharReader());
Json::Value root;
Json::String errs;
{
char const doc[] = R"({ "a":0x9, "b":0xf, "c":0xF })";
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
JSONTEST_ASSERT(!ok);
JSONTEST_ASSERT_STRING_EQUAL(
"* Line 1, Column 7\n"
" Syntax error: value, object or array expected.\n",
errs);
}
}
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, hexObject) {
Json::CharReaderBuilder b;
b.settings_["allowHexadecimal"] = true;
Json::Value invalid;
JSONTEST_ASSERT(b.validate(&invalid)) << invalid;
CharReaderPtr reader(b.newCharReader());
{
Json::Value root;
Json::String errs;
char const doc[] = R"({ "a":0x9, "b":0xf, "c":0xF })";
bool ok = reader->parse(doc, doc + std::strlen(doc), &root, &errs);
JSONTEST_ASSERT(ok);
JSONTEST_ASSERT_STRING_EQUAL("", errs);
JSONTEST_ASSERT_EQUAL(3u, root.size());
JSONTEST_ASSERT_EQUAL(0x9u, root.get("a", 0).asUInt());
JSONTEST_ASSERT_EQUAL(0xfu, root.get("b", 0).asUInt());
JSONTEST_ASSERT_EQUAL(0xFu, root.get("c", 0).asUInt());
}
}
JSONTEST_FIXTURE_LOCAL(CharReaderAllowHexadecimal, hexNumbers) {
Json::CharReaderBuilder b;
b.settings_["allowHexadecimal"] = true;
CharReaderPtr reader(b.newCharReader());
struct TestData {
bool ok;
Json::LargestUInt out;
Json::String in;
};
constexpr int _ = 0; // ignored
const TestData test_data[] = {
{true, 99, "99"}, // regular number
{true, 0x99, "0x99"}, // hexadecimal number
{false, _, "AA"}, // missing prefix
{false, _, "xAA"}, // partial prefix
{true, 0xAA, "0xAA"}, // with prefix
{true, 0x00, "0x00"}, // zero
{true, 0x0123456789, "0x0123456789"}, // numeric hex
{true, 0xABCDEF, "0xABCDEF"}, // uppercase-letter hex
{true, 0xabcdef, "0xabcdef"}, // lowercase-letter hex
#ifdef JSON_HAS_INT64
{true, 0xFfffFfffFfffFfff, "0xFfffFfffFfffFfff"}, // max
{false, _, "0x1FfffFfffFfffFfff"}, // too long
#else
{true, 0xFfffFfff, "0xFfffFfff"}, // max
{false, _, "0x1FfffFfff"}, // too long
#endif
{false, _, "0x000000000000000000000000000000000000000"}, // too long
{false, _, "x"}, // leading x
{false, _, "0x"}, // empty number
{false, _, "0xx"} // extra x
};
for (const auto& td : test_data) {
Json::Value root;
Json::String errs;
const char* c0 = td.in.c_str();
const char* c1 = c0 + td.in.size();
bool ok = reader->parse(c0, c1, &root, &errs);
JSONTEST_ASSERT(td.ok == ok) << "in: " << td.in;
if (td.ok)
{
JSONTEST_ASSERT_STRING_EQUAL("", errs);
JSONTEST_ASSERT(root.isUInt64());
if (root.isUInt64())
JSONTEST_ASSERT_EQUAL(td.out, root.asLargestUInt());
}
else
{
JSONTEST_ASSERT(errs.size() > 0);
}
}
}
struct CharReaderAllowSpecialFloatsTest : JsonTest::TestCase {}; struct CharReaderAllowSpecialFloatsTest : JsonTest::TestCase {};
JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, specialFloat) { JSONTEST_FIXTURE_LOCAL(CharReaderAllowSpecialFloatsTest, specialFloat) {