From facc103527ea2cfe6b6792c90c82d2ce9844a653 Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Thu, 23 Jul 2020 11:19:47 +0100 Subject: [PATCH] Allow unescaped quoted strings with --config CLI (#2835) * Parsing issue with --config CLI * Allow original behaviour with escaped quotations * Support arrays * Formatting --- nano/core_test/cli.cpp | 26 ++++++++++++++++ nano/lib/CMakeLists.txt | 2 ++ nano/lib/cli.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ nano/lib/cli.hpp | 19 ++++++++++++ nano/nano_node/entry.cpp | 3 +- nano/nano_rpc/entry.cpp | 5 +-- nano/node/cli.cpp | 3 +- 7 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 nano/lib/cli.cpp create mode 100644 nano/lib/cli.hpp diff --git a/nano/core_test/cli.cpp b/nano/core_test/cli.cpp index e5388fc3..27559d05 100644 --- a/nano/core_test/cli.cpp +++ b/nano/core_test/cli.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -43,6 +44,31 @@ TEST (cli, key_create) ASSERT_EQ (vals[2], public_key.to_account ()); } +TEST (cli, config_override_parsing) +{ + std::vector key_value_pairs; + auto config_overrides = nano::config_overrides (key_value_pairs); + ASSERT_TRUE (config_overrides.empty ()); + key_value_pairs.push_back ({ "key", "value" }); + config_overrides = nano::config_overrides (key_value_pairs); + ASSERT_EQ (config_overrides[0], "key=\"value\""); + key_value_pairs.push_back ({ "node.online_weight_minimum", "40000000000000000000000000000000000000" }); + config_overrides = nano::config_overrides (key_value_pairs); + ASSERT_EQ (config_overrides[1], "node.online_weight_minimum=\"40000000000000000000000000000000000000\""); + + // Should add this as it contains escaped quotes, and make sure these are not escaped again + key_value_pairs.push_back ({ "key", "\"value\"" }); + config_overrides = nano::config_overrides (key_value_pairs); + ASSERT_EQ (config_overrides[2], "key=\"value\""); + ASSERT_EQ (config_overrides.size (), 3); + + // Try it with arrays, with and without escaped quotes + key_value_pairs.push_back ({ "node.work_peers", "[127.0.0.1:7000,\"128.0.0.1:50000\"]" }); + config_overrides = nano::config_overrides (key_value_pairs); + ASSERT_EQ (config_overrides[3], "node.work_peers=[\"127.0.0.1:7000\",\"128.0.0.1:50000\"]"); + ASSERT_EQ (config_overrides.size (), 4); +} + namespace { std::string call_cli_command (boost::program_options::variables_map const & vm) diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index a741371e..5ae143fc 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -20,6 +20,8 @@ add_library (nano_lib blockbuilders.cpp blocks.hpp blocks.cpp + cli.hpp + cli.cpp config.hpp config.cpp configbase.hpp diff --git a/nano/lib/cli.cpp b/nano/lib/cli.cpp new file mode 100644 index 00000000..be1f0386 --- /dev/null +++ b/nano/lib/cli.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include + +#include + +std::vector nano::config_overrides (std::vector const & key_value_pairs_a) +{ + std::vector overrides; + auto format (boost::format ("%1%=%2%")); + auto format_add_escaped_quotes (boost::format ("%1%=\"%2%\"")); + for (auto pair : key_value_pairs_a) + { + auto start = pair.value.find ('['); + + std::string value; + auto is_array = (start != std::string::npos); + if (is_array) + { + // Trim off the square brackets [] of the array + auto end = pair.value.find (']'); + auto array_values = pair.value.substr (start + 1, end - start - 1); + + // Split the string by comma + std::vector split_elements; + boost::split (split_elements, array_values, boost::is_any_of (",")); + + auto format (boost::format ("%1%")); + auto format_add_escaped_quotes (boost::format ("\"%1%\"")); + + // Rebuild the array string adding escaped quotes if necessary + std::ostringstream ss; + ss << "["; + for (auto i = 0; i < split_elements.size (); ++i) + { + auto & elem = split_elements[i]; + auto already_escaped = elem.find ('\"') != std::string::npos; + ss << ((!already_escaped ? format_add_escaped_quotes : format) % elem).str (); + if (i != split_elements.size () - 1) + { + ss << ","; + } + } + ss << "]"; + value = ss.str (); + } + else + { + value = pair.value; + } + auto already_escaped = value.find ('\"') != std::string::npos; + overrides.push_back (((!already_escaped ? format_add_escaped_quotes : format) % pair.key % value).str ()); + } + return overrides; +} + +std::istream & nano::operator>> (std::istream & is, nano::config_key_value_pair & into) +{ + char ch; + while (is >> ch && ch != '=') + { + into.key += ch; + } + return is >> into.value; +} diff --git a/nano/lib/cli.hpp b/nano/lib/cli.hpp new file mode 100644 index 00000000..6e47a091 --- /dev/null +++ b/nano/lib/cli.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +namespace nano +{ +class config_key_value_pair +{ +public: + std::string key; + std::string value; +}; + +std::vector config_overrides (std::vector const & key_value_pairs_a); + +std::istream & operator>> (std::istream & is, nano::config_key_value_pair & into); +} diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 579273bf..52d3fa25 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -68,7 +69,7 @@ int main (int argc, char * const * argv) description.add_options () ("help", "Print out options") ("version", "Prints out version") - ("config", boost::program_options::value>()->multitoken(), "Pass node configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.") + ("config", boost::program_options::value>()->multitoken(), "Pass node configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.") ("daemon", "Start node daemon") ("compare_rep_weights", "Display a summarized comparison between the hardcoded bootstrap weights and representative weights from the ledger. Full comparison is output to logs") ("debug_block_count", "Display the number of block") diff --git a/nano/nano_rpc/entry.cpp b/nano/nano_rpc/entry.cpp index f6e31b47..45c46fc9 100644 --- a/nano/nano_rpc/entry.cpp +++ b/nano/nano_rpc/entry.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -90,7 +91,7 @@ int main (int argc, char * const * argv) // clang-format off description.add_options () ("help", "Print out options") - ("config", boost::program_options::value>()->multitoken(), "Pass RPC configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.") + ("config", boost::program_options::value>()->multitoken(), "Pass RPC configuration values. This takes precedence over any values in the configuration file. This option can be repeated multiple times.") ("daemon", "Start RPC daemon") ("data_path", boost::program_options::value (), "Use the supplied path as the data directory") ("network", boost::program_options::value (), "Use the supplied network (live, beta or test)") @@ -139,7 +140,7 @@ int main (int argc, char * const * argv) auto config (vm.find ("config")); if (config != vm.end ()) { - config_overrides = config->second.as> (); + config_overrides = nano::config_overrides (config->second.as> ()); } run (data_path, config_overrides); } diff --git a/nano/node/cli.cpp b/nano/node/cli.cpp index 2a6c6c66..8cc445e3 100644 --- a/nano/node/cli.cpp +++ b/nano/node/cli.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -170,7 +171,7 @@ std::error_code nano::update_flags (nano::node_flags & flags_a, boost::program_o auto config (vm.find ("config")); if (config != vm.end ()) { - flags_a.config_overrides = config->second.as> (); + flags_a.config_overrides = nano::config_overrides (config->second.as> ()); } return ec; }