From 019c16a0771662c9b0e60dca96de57d24e3dfb63 Mon Sep 17 00:00:00 2001 From: xirvik Date: Sat, 19 Sep 2026 04:19:41 +0000 Subject: [PATCH] rpc: multicall requires methodName as the struct's first member Faults clearly instead of the confusing positional parse error it replaces. --- src/rpc/xmlrpc_tinyxml2.cc | 13 ++++++++---- test/rpc/test_xmlrpc.cc | 42 ++++++++++++++++++++++++++++++++++++++ test/rpc/test_xmlrpc.h | 2 ++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/rpc/xmlrpc_tinyxml2.cc b/src/rpc/xmlrpc_tinyxml2.cc index 083da9ae..681c5b8c 100644 --- a/src/rpc/xmlrpc_tinyxml2.cc +++ b/src/rpc/xmlrpc_tinyxml2.cc @@ -326,15 +326,20 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer auto& result_list = result.as_list(); auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"}); for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) { - auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText(); + auto method_name_member = element_access(child, {"struct", "member"}); + auto member_name = method_name_member->FirstChildElement("name"); + + if (member_name == nullptr || member_name->GetText() == nullptr || + std::strncmp(member_name->GetText(), "methodName", sizeof("methodName")) != 0) + throw rpc_error(XMLRPC_PARSE_ERROR, "multicall struct's first member must be methodName"); + + auto sub_method_name = element_access(method_name_member, {"value", "string"})->GetText(); if (sub_method_name == nullptr) throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty"); // If sub_params ends up a nullptr at the end of this if-chian, // execute_command will turn it into an empty list - auto sub_params = element_access(child, {"struct", "member"}); - if (sub_params != nullptr) - sub_params = sub_params->NextSiblingElement("member"); + auto sub_params = method_name_member->NextSiblingElement("member"); if (sub_params != nullptr) sub_params = sub_params->FirstChildElement("value"); if (sub_params != nullptr) diff --git a/test/rpc/test_xmlrpc.cc b/test/rpc/test_xmlrpc.cc index b3d68ead..44706fb9 100644 --- a/test/rpc/test_xmlrpc.cc +++ b/test/rpc/test_xmlrpc.cc @@ -192,12 +192,54 @@ TestXmlrpc::test_response_size_limit() { CPPUNIT_ASSERT_EQUAL(expected, output); } +namespace { + +const std::string multicall_method_name = + "methodNamexmlrpc_reflect"; +const std::string multicall_params = + "params" + "a" + ""; + +std::string +multicall_request(const std::string& members) { + return "system.multicall" + "" + members + + ""; +} + +} + +void +TestXmlrpc::test_multicall_member_order() { + auto call = [this](const std::string& input) { + std::string output; + m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;}); + return output; + }; + + // methodName first is the only order accepted; the positive control proves + // the harness drives the real code path rather than a stub. + std::string ordered = call(multicall_request(multicall_method_name + multicall_params)); + CPPUNIT_ASSERT(ordered.find("faultCode") == std::string::npos); + + // params before methodName is rejected with a clear top-level fault, + // instead of the "could not find expected element string" of a positional read. + std::string expected_fault = + "" + "faultCode-503" + "faultStringmulticall struct's first member must be methodName" + ""; + CPPUNIT_ASSERT_EQUAL(expected_fault, call(multicall_request(multicall_params + multicall_method_name))); +} + #else void TestXmlrpc::test_invalid_utf8() {} void TestXmlrpc::test_basics() {} void TestXmlrpc::test_size_limit() {} void TestXmlrpc::test_response_size_limit() {} +void TestXmlrpc::test_multicall_member_order() {} void TestXmlrpc::setUp() {} void TestXmlrpc::tearDown() {} diff --git a/test/rpc/test_xmlrpc.h b/test/rpc/test_xmlrpc.h index 7e788b52..d41788e1 100644 --- a/test/rpc/test_xmlrpc.h +++ b/test/rpc/test_xmlrpc.h @@ -11,6 +11,7 @@ class TestXmlrpc : public test_fixture { CPPUNIT_TEST(test_invalid_utf8); CPPUNIT_TEST(test_size_limit); CPPUNIT_TEST(test_response_size_limit); + CPPUNIT_TEST(test_multicall_member_order); CPPUNIT_TEST_SUITE_END(); @@ -24,6 +25,7 @@ public: void test_invalid_utf8(); void test_size_limit(); void test_response_size_limit(); + void test_multicall_member_order(); private: std::unique_ptr m_test_main_thread;