diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index fdac1e450dd..df0bf892975 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -594,6 +594,28 @@ main BEAST_EXPECT(error.empty()); BEAST_EXPECT(c.networkId == 10000); + + // A trailing inline comment on a single-value section must be + // stripped, not passed to lexicalCast. Before the fix this threw + // beast::BadLexicalCast (a std::bad_cast, not a std::runtime_error), + // which escaped as an uncaught terminate at startup. Catch + // std::exception so a regression fails the test rather than aborting + // the test binary. See issue #7545. + error.clear(); + try + { + c.loadFromString(R"xrpldConfig( +[network_id] +1024 # inline comment after the value +)xrpldConfig"); + } + catch (std::exception const& e) + { + error = e.what(); + } + + BEAST_EXPECT(error.empty()); + BEAST_EXPECT(c.networkId == 1024); } void diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 1b2449823e4..b5240a44d7c 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -217,6 +217,38 @@ getIniFileSection(IniFileSections& secSource, std::string const& strSection) return nullptr; } +// Strip a trailing, unescaped '#' comment from a single-line section value. +// parseIniFile() only discards whole-line comments (lines that begin with +// '#'), so a value such as "1 # note" is otherwise passed verbatim to +// lexicalCast and throws beast::BadLexicalCast, aborting startup. This mirrors +// the inline-comment handling BasicConfig::Section::append already performs for +// key=value sections (including escaped "\#"). See issue #7545. +static void +removeTrailingComment(std::string& value) +{ + auto pos = value.find('#'); + while (pos != std::string::npos) + { + if (pos == 0) + { + value.clear(); + return; + } + if (value[pos - 1] == '\\') + { + // escaped '#': drop the backslash, keep the '#', keep scanning + value.erase(pos - 1, 1); + pos = value.find('#', pos); + } + else + { + value.erase(pos); + boost::algorithm::trim(value); + return; + } + } +} + bool getSingleSection( IniFileSections& secSource, @@ -229,6 +261,7 @@ getSingleSection( if ((pmtEntries != nullptr) && pmtEntries->size() == 1) { strValue = (*pmtEntries)[0]; + removeTrailingComment(strValue); return true; }