Skip to content

Commit fc892c3

Browse files
author
MarcoFalke
committed
rpc: Fail to return undocumented or misdocumented JSON
1 parent f4bc4a7 commit fc892c3

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

src/rpc/util.cpp

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
774774
// Elements in a JSON structure (dictionary or array) are separated by a comma
775775
const std::string maybe_separator{outer_type != OuterType::NONE ? "," : ""};
776776

777-
// The key name if recursed into an dictionary
777+
// The key name if recursed into a dictionary
778778
const std::string maybe_key{
779779
outer_type == OuterType::OBJ ?
780780
"\"" + this->m_key_name + "\" : " :
@@ -865,10 +865,11 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
865865

866866
bool RPCResult::MatchesType(const UniValue& result) const
867867
{
868-
switch (m_type) {
869-
case Type::ELISION: {
870-
return false;
868+
if (m_skip_type_check) {
869+
return true;
871870
}
871+
switch (m_type) {
872+
case Type::ELISION:
872873
case Type::ANY: {
873874
return true;
874875
}
@@ -889,11 +890,52 @@ bool RPCResult::MatchesType(const UniValue& result) const
889890
}
890891
case Type::ARR_FIXED:
891892
case Type::ARR: {
892-
return UniValue::VARR == result.getType();
893+
if (UniValue::VARR != result.getType()) return false;
894+
for (size_t i{0}; i < result.get_array().size(); ++i) {
895+
// If there are more results than documented, re-use the last doc_inner.
896+
const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
897+
if (!doc_inner.MatchesType(result.get_array()[i])) return false;
898+
}
899+
return true; // empty result array is valid
893900
}
894901
case Type::OBJ_DYN:
895902
case Type::OBJ: {
896-
return UniValue::VOBJ == result.getType();
903+
if (UniValue::VOBJ != result.getType()) return false;
904+
if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
905+
if (m_type == Type::OBJ_DYN) {
906+
const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
907+
for (size_t i{0}; i < result.get_obj().size(); ++i) {
908+
if (!doc_inner.MatchesType(result.get_obj()[i])) {
909+
return false;
910+
}
911+
}
912+
return true; // empty result obj is valid
913+
}
914+
std::set<std::string> doc_keys;
915+
for (const auto& doc_entry : m_inner) {
916+
doc_keys.insert(doc_entry.m_key_name);
917+
}
918+
std::map<std::string, UniValue> result_obj;
919+
result.getObjMap(result_obj);
920+
for (const auto& result_entry : result_obj) {
921+
if (doc_keys.find(result_entry.first) == doc_keys.end()) {
922+
return false; // missing documentation
923+
}
924+
}
925+
926+
for (const auto& doc_entry : m_inner) {
927+
const auto result_it{result_obj.find(doc_entry.m_key_name)};
928+
if (result_it == result_obj.end()) {
929+
if (!doc_entry.m_optional) {
930+
return false; // result is missing a required key
931+
}
932+
continue;
933+
}
934+
if (!doc_entry.MatchesType(result_it->second)) {
935+
return false; // wrong type
936+
}
937+
}
938+
return true;
897939
}
898940
} // no default case, so the compiler can warn about missing cases
899941
CHECK_NONFATAL(false);

src/wallet/rpc/wallet.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static RPCHelpMan getwalletinfo()
5656
{
5757
{RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
5858
{RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
59-
}},
59+
}, /*skip_type_check=*/true},
6060
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
6161
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
6262
}},

0 commit comments

Comments
 (0)