Merge pull request #3971 from arreina/fix/param-json-nesting
fix(libkernel): limit param.json nesting depth
This commit is contained in:
commit
f4b395e28e
|
|
@ -160,9 +160,12 @@ std::string parseJsonString(const std::string& text, std::size_t& position) {
|
|||
return result;
|
||||
}
|
||||
|
||||
JsonValue parseJsonValue(const std::string& text, std::size_t& position);
|
||||
constexpr std::size_t MaxNestingDepth = 512;
|
||||
|
||||
JsonValue parseJsonObject(const std::string& text, std::size_t& position) {
|
||||
JsonValue parseJsonValue(const std::string& text, std::size_t& position, std::size_t depth);
|
||||
|
||||
JsonValue parseJsonObject(const std::string& text, std::size_t& position, std::size_t depth) {
|
||||
if (depth >= MaxNestingDepth) throw std::runtime_error("param.json is nested too deeply");
|
||||
expectChar(text, position, '{');
|
||||
JsonValue value;
|
||||
value.type = JsonType::Object;
|
||||
|
|
@ -173,7 +176,7 @@ JsonValue parseJsonObject(const std::string& text, std::size_t& position) {
|
|||
std::string key = parseJsonString(text, position);
|
||||
skipWhitespace(text, position);
|
||||
expectChar(text, position, ':');
|
||||
JsonValue member = parseJsonValue(text, position);
|
||||
JsonValue member = parseJsonValue(text, position, depth + 1);
|
||||
value.objectValue.emplace_back(std::move(key), std::move(member));
|
||||
skipWhitespace(text, position);
|
||||
const char next = peekChar(text, position);
|
||||
|
|
@ -184,14 +187,15 @@ JsonValue parseJsonObject(const std::string& text, std::size_t& position) {
|
|||
return value;
|
||||
}
|
||||
|
||||
JsonValue parseJsonArray(const std::string& text, std::size_t& position) {
|
||||
JsonValue parseJsonArray(const std::string& text, std::size_t& position, std::size_t depth) {
|
||||
if (depth >= MaxNestingDepth) throw std::runtime_error("param.json is nested too deeply");
|
||||
expectChar(text, position, '[');
|
||||
JsonValue value;
|
||||
value.type = JsonType::Array;
|
||||
skipWhitespace(text, position);
|
||||
if (peekChar(text, position) == ']') { ++position; return value; }
|
||||
while (true) {
|
||||
JsonValue element = parseJsonValue(text, position);
|
||||
JsonValue element = parseJsonValue(text, position, depth + 1);
|
||||
value.arrayValue.push_back(std::move(element));
|
||||
skipWhitespace(text, position);
|
||||
const char next = peekChar(text, position);
|
||||
|
|
@ -230,11 +234,11 @@ JsonValue parseJsonNumber(const std::string& text, std::size_t& position) {
|
|||
return value;
|
||||
}
|
||||
|
||||
JsonValue parseJsonValue(const std::string& text, std::size_t& position) {
|
||||
JsonValue parseJsonValue(const std::string& text, std::size_t& position, std::size_t depth) {
|
||||
skipWhitespace(text, position);
|
||||
const char c = peekChar(text, position);
|
||||
if (c == '{') return parseJsonObject(text, position);
|
||||
if (c == '[') return parseJsonArray(text, position);
|
||||
if (c == '{') return parseJsonObject(text, position, depth);
|
||||
if (c == '[') return parseJsonArray(text, position, depth);
|
||||
if (c == '"') { JsonValue value; value.type = JsonType::String; value.stringValue = parseJsonString(text, position); return value; }
|
||||
if (c == 't') { expectLiteral(text, position, "true"); JsonValue value; value.type = JsonType::Boolean; value.boolValue = true; return value; }
|
||||
if (c == 'f') { expectLiteral(text, position, "false"); JsonValue value; value.type = JsonType::Boolean; value.boolValue = false; return value; }
|
||||
|
|
@ -245,7 +249,7 @@ JsonValue parseJsonValue(const std::string& text, std::size_t& position) {
|
|||
|
||||
JsonValue parseJsonDocument(const std::string& text) {
|
||||
std::size_t position = 0;
|
||||
JsonValue root = parseJsonValue(text, position);
|
||||
JsonValue root = parseJsonValue(text, position, 0);
|
||||
skipWhitespace(text, position);
|
||||
if (position != text.size()) throw std::runtime_error("unexpected trailing data in param.json");
|
||||
return root;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,23 @@ static void CheckUserDefinedParams(const std::filesystem::path& path) {
|
|||
}
|
||||
}
|
||||
|
||||
static void CheckNesting(const std::filesystem::path& path) {
|
||||
const std::string prefix = R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}},"extra":)";
|
||||
const auto nested = [&](std::size_t arrays) {
|
||||
return prefix + std::string(arrays, '[') + std::string(arrays, ']') + "}";
|
||||
};
|
||||
Require(ParseText(path, nested(511)).title == "Example", "Nesting within the limit rejected");
|
||||
for (const std::size_t arrays : {std::size_t{512}, std::size_t{513}, std::size_t{100000}}) {
|
||||
bool rejected = false;
|
||||
try {
|
||||
ParseText(path, nested(arrays));
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::string(error.what()).find("nested too deeply") != std::string::npos;
|
||||
}
|
||||
Require(rejected, "Excessive nesting not rejected: " + std::to_string(arrays));
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckLanguages(const std::filesystem::path& path) {
|
||||
const auto nested = ParseText(path, "{\r\n \"localizedParameters\": {\r\n \"defaultLanguage\": \"en-GB\",\r\n"
|
||||
" \"en-GB\": { \"titleName\": \"British\" }\r\n },\r\n \"titleId\": \"PPSA00001\"\r\n}\r\n");
|
||||
|
|
@ -139,6 +156,7 @@ int main() {
|
|||
}
|
||||
CheckLanguages(path);
|
||||
CheckUserDefinedParams(path);
|
||||
CheckNesting(path);
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
result = 1;
|
||||
|
|
|
|||
Loading…
Reference in New Issue