This commit is contained in:
pablomartin4btc 2025-10-08 02:00:47 +02:00 committed by GitHub
commit fc39f66e50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 147 additions and 38 deletions

View File

@ -1207,16 +1207,21 @@ static void SetGenerateToAddressArgs(const std::string& address, std::vector<std
args.emplace(args.begin() + 1, address);
}
static int CommandLineRPC(int argc, char *argv[])
std::vector<std::string> GetCommandArgs() {
std::optional<const ArgsManager::Command> command = gArgs.GetCommand();
// Return command args if command is present, otherwise return an empty vector
if (command.has_value()) {
return command->args;
}
return {};
}
static int CommandLineRPC()
{
std::string strPrint;
int nRet = 0;
try {
// Skip switches
while (argc > 1 && IsSwitchChar(argv[1][0])) {
argc--;
argv++;
}
std::string rpcPass;
if (gArgs.GetBoolArg("-stdinrpcpass", false)) {
NO_STDIN_ECHO();
@ -1232,7 +1237,7 @@ static int CommandLineRPC(int argc, char *argv[])
}
gArgs.ForceSetArg("-rpcpassword", rpcPass);
}
std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
std::vector<std::string> args = GetCommandArgs();
if (gArgs.GetBoolArg("-stdinwalletpassphrase", false)) {
NO_STDIN_ECHO();
std::string walletPass;
@ -1354,7 +1359,7 @@ MAIN_FUNCTION
int ret = EXIT_FAILURE;
try {
ret = CommandLineRPC(argc, argv);
ret = CommandLineRPC();
}
catch (const std::exception& e) {
PrintExceptionContinue(&e, "CommandLineRPC()");

View File

@ -176,6 +176,42 @@ void ArgsManager::SelectConfigNetwork(const std::string& network)
m_network = network;
}
bool IsArgNumber(const char* arg) {
double dummy{};
return ParseDouble(arg, &dummy);
}
bool ArgsManager::ProcessOptionKey(std::string& key, std::optional<std::string>& val, std::string& error) {
std::string original_input = key; // Capture the original key
if (val) original_input += "=" + *val; // Append =value if it exists
// Transform --foo to -foo
if (key.length() > 1 && key[1] == '-')
key.erase(0, 1);
// Transform -foo to foo
key.erase(0, 1);
KeyInfo keyinfo = InterpretKey(key);
std::optional<unsigned int> flags = GetArgFlags('-' + keyinfo.name);
// Unknown command line options and command line options with dot characters
// (which are returned from InterpretKey with nonempty section strings)are not valid.
if (!flags || !keyinfo.section.empty()) {
error = strprintf("Invalid parameter %s", original_input);
return false;
}
std::optional<common::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error);
if (!value) return false;
// Store the option
LOCK(cs_args);
m_settings.command_line_options[keyinfo.name].push_back(*value);
return true;
}
bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error)
{
LOCK(cs_args);
@ -217,32 +253,28 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin
m_command.push_back(key);
while (++i < argc) {
// The remaining args are command args
m_command.emplace_back(argv[i]);
if (argv[i][0] == '-' && !IsArgNumber(argv[i])) {
// except it starts with dash "-" (and it's not a number) then will check if it's a valid option
key = argv[i];
val.reset();
is_index = key.find('=');
if (is_index != std::string::npos) {
val = key.substr(is_index + 1);
key.erase(is_index);
}
if (!ProcessOptionKey(key, val, error)) {
return false;
}
} else {
m_command.emplace_back(argv[i]);
}
}
break;
}
// Transform --foo to -foo
if (key.length() > 1 && key[1] == '-')
key.erase(0, 1);
// Transform -foo to foo
key.erase(0, 1);
KeyInfo keyinfo = InterpretKey(key);
std::optional<unsigned int> flags = GetArgFlags('-' + keyinfo.name);
// Unknown command line options and command line options with dot
// characters (which are returned from InterpretKey with nonempty
// section strings) are not valid.
if (!flags || !keyinfo.section.empty()) {
error = strprintf("Invalid parameter %s", argv[i]);
if (!ProcessOptionKey(key, val, error)) {
return false;
}
std::optional<common::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error);
if (!value) return false;
m_settings.command_line_options[keyinfo.name].push_back(*value);
}
// we do not allow -includeconf from command line, only -noincludeconf

View File

@ -446,6 +446,8 @@ private:
const std::string& prefix,
const std::string& section,
const std::map<std::string, std::vector<common::SettingsValue>>& args) const;
bool ProcessOptionKey(std::string& key, std::optional<std::string>& val, std::string& error);
};
extern ArgsManager gArgs;

View File

@ -1,4 +1,4 @@
// Copyright (c) 2011-2022 The Bitcoin Core developers
// Copyright (c) 2011-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -227,13 +227,13 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters)
BOOST_CHECK(testArgs.ParseParameters(7, argv_test, error));
// expectation: -ignored is ignored (program name argument),
// -a, -b and -ccc end up in map, -d ignored because it is after
// a non-option argument (non-GNU option parsing)
BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty());
// -a, -b and -ccc end up in map, also -d since the parser detects
// [options] after a non-option argument (GNU-style option parsing)
BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 4 && testArgs.m_settings.ro_config.empty());
BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc")
&& !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d"));
&& !testArgs.IsArgSet("f") && testArgs.IsArgSet("-d"));
BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc")
&& !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d"));
&& !testArgs.m_settings.command_line_options.count("f") && testArgs.m_settings.command_line_options.count("d"));
BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1);
BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == "");

View File

@ -1,4 +1,4 @@
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Copyright (c) 2009-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -55,6 +55,9 @@ FUZZ_TARGET(parse_numbers)
(void)ParseMoney(random_string);
double d;
(void)ParseDouble(random_string, &d);
(void)LocaleIndependentAtoi<int>(random_string);
int64_t i64;

View File

@ -835,6 +835,32 @@ BOOST_AUTO_TEST_CASE(test_LocaleIndependentAtoi)
BOOST_CHECK_EQUAL(LocaleIndependentAtoi<uint8_t>("256"), 255U);
}
BOOST_AUTO_TEST_CASE(test_ParseDouble)
{
double n;
// Valid values
BOOST_CHECK(ParseDouble("1234", nullptr));
BOOST_CHECK(ParseDouble("0", &n) && n == 0.0);
BOOST_CHECK(ParseDouble("1234", &n) && n == 1234.0);
BOOST_CHECK(ParseDouble("01234", &n) && n == 1234.0); // no octal
BOOST_CHECK(ParseDouble("2147483647", &n) && n == 2147483647.0);
BOOST_CHECK(ParseDouble("-2147483648", &n) && n == -2147483648.0);
BOOST_CHECK(ParseDouble("-1234", &n) && n == -1234.0);
BOOST_CHECK(ParseDouble("1e6", &n) && n == 1e6);
BOOST_CHECK(ParseDouble("-1e6", &n) && n == -1e6);
// Invalid values
BOOST_CHECK(!ParseDouble("", &n));
BOOST_CHECK(!ParseDouble(" 1", &n)); // no padding inside
BOOST_CHECK(!ParseDouble("1 ", &n));
BOOST_CHECK(!ParseDouble("1a", &n));
BOOST_CHECK(!ParseDouble("aap", &n));
BOOST_CHECK(!ParseDouble("0x1", &n)); // no hex
BOOST_CHECK(!ParseDouble(STRING_WITH_EMBEDDED_NULL_CHAR, &n));
// Overflow and underflow
BOOST_CHECK(!ParseDouble("-1e10000", nullptr));
BOOST_CHECK(!ParseDouble("1e10000", nullptr));
}
BOOST_AUTO_TEST_CASE(test_FormatParagraph)
{
BOOST_CHECK_EQUAL(FormatParagraph("", 79, 0), "");

View File

@ -17,6 +17,8 @@
#include <string>
#include <vector>
using util::ContainsNoNUL;
static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static const std::string SAFE_CHARS[] =
@ -199,6 +201,31 @@ std::optional<std::vector<unsigned char>> DecodeBase32(std::string_view str)
return ret;
}
[[nodiscard]] static bool ParsePrechecks(const std::string& str)
{
if (str.empty()) // No empty string allowed
return false;
if (str.size() >= 1 && (IsSpace(str[0]) || IsSpace(str[str.size()-1]))) // No padding allowed
return false;
if (!ContainsNoNUL(str)) // No embedded NUL characters allowed
return false;
return true;
}
bool ParseDouble(const std::string& str, double *out)
{
if (!ParsePrechecks(str))
return false;
if (str.size() >= 2 && str[0] == '0' && str[1] == 'x') // No hexadecimal floats allowed
return false;
std::istringstream text(str);
text.imbue(std::locale::classic());
double result;
text >> result;
if(out) *out = result;
return text.eof() && !text.fail();
}
std::string FormatParagraph(std::string_view in, size_t width, size_t indent)
{
assert(width >= indent);

View File

@ -186,6 +186,13 @@ std::optional<T> ToIntegral(std::string_view str)
return result;
}
/**
* Convert string to double with strict parse error feedback.
* @returns true if the entire string could be parsed as valid double,
* false if not the entire string could be parsed or when overflow or underflow occurred.
*/
[[nodiscard]] bool ParseDouble(const std::string& str, double *out);
/**
* Format a paragraph of text to a fixed width, adding spaces for
* indentation to any added line.

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2022 The Bitcoin Core developers
# Copyright (c) 2017-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test bitcoin-cli"""
@ -352,6 +352,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.nodes[0].loadwallet(wallets[2])
n3 = 4
n4 = 10
n5 = 5
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate -rpcwallet=<filename> raise RPC error')
@ -386,9 +387,15 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 'foo').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 0).echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 1, 2, 3).echo)
self.log.info('Test -generate passing -rpcwallet after the nblocks arg')
generate = self.nodes[0].cli('-generate', n5, rpcwallet2).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n5)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4 + n5)
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
self.generate(self.nodes[0], 25) # maintain block parity with the wallet_compiled conditional branch
self.generate(self.nodes[0], 30) # maintain block parity with the wallet_compiled conditional branch
self.test_netinfo()
@ -402,7 +409,7 @@ class TestBitcoinCli(BitcoinTestFramework):
self.nodes[0].wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
assert_equal(blocks, BLOCKS + 25)
assert_equal(blocks, BLOCKS + 30)
self.log.info("Test -rpcwait option waits at most -rpcwaittimeout seconds for startup")
self.stop_node(0) # stop the node so we time out