From fe03b65c8bd952ac26a516e23f4c04bb014455e9 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Fri, 16 Oct 2020 23:43:11 +0300 Subject: CommandLine: saner API --- include/winapi/cmd_line.hpp | 36 +++++------- src/cmd_line.cpp | 130 +++++++++++++++++++++---------------------- src/process.cpp | 2 +- test/unit_tests/cmd_line.cpp | 6 +- 4 files changed, 84 insertions(+), 90 deletions(-) diff --git a/include/winapi/cmd_line.hpp b/include/winapi/cmd_line.hpp index 60bc56a..176cf3d 100644 --- a/include/winapi/cmd_line.hpp +++ b/include/winapi/cmd_line.hpp @@ -17,7 +17,9 @@ class CommandLine { public: static CommandLine query(); - static CommandLine build_from_main(int argc, wchar_t* argv[]); + static CommandLine parse(const std::string&); + + static CommandLine from_main(int argc, wchar_t* argv[]); CommandLine() = default; @@ -27,37 +29,29 @@ public: CommandLine(std::string&& argv0, std::vector&& args = {}) : argv0(std::move(argv0)), args(std::move(args)) {} - bool has_argv0() const { return !argv0.empty(); } - - std::string get_argv0() const { return argv0; } - - std::string escape_argv0() const { return escape(get_argv0()); } + static std::string escape(const std::string&); - bool has_args() const { return !get_args().empty(); } + static std::string escape_cmd(const std::string&); - const std::vector& get_args() const { return args; } + std::string to_string() const; - std::vector escape_args() const; + std::string args_to_string() const; - static BOOST_CONSTEXPR char sep() { return ' '; } + std::string get_argv0() const { return argv0; } - std::string join_args() const; + bool has_args() const { return !get_args().empty(); } - std::string join() const; + const std::vector& get_args() const { return args; } private: - static CommandLine build_from_string(const std::string&); - - static CommandLine build_from_string(std::wstring); + static BOOST_CONSTEXPR char token_sep() { return ' '; } - static std::string escape_for_cmd(const std::string&); - - static std::string escape(const std::string&); + std::string escape_argv0() const { return escape(get_argv0()); } - CommandLine(std::vector&& args) : args(std::move(args)) {} + std::vector escape_args() const; - const std::string argv0; - const std::vector args; + std::string argv0; + std::vector args; }; } // namespace winapi diff --git a/src/cmd_line.cpp b/src/cmd_line.cpp index b771299..468bb96 100644 --- a/src/cmd_line.cpp +++ b/src/cmd_line.cpp @@ -18,8 +18,8 @@ #include #include #include -#include #include +#include #include #include @@ -30,59 +30,11 @@ struct LocalDelete { void operator()(wchar_t* argv[]) const { ::LocalFree(argv); } }; -} // namespace - -CommandLine CommandLine::query() { - return build_from_string(::GetCommandLineW()); -} - -CommandLine CommandLine::build_from_main(int argc, wchar_t* argv[]) { - if (argc < 1) - throw std::range_error{"invalid argc value"}; - - std::string argv0{narrow(argv[0])}; - --argc; - ++argv; - - std::vector args; - args.reserve(argc); - - for (int i = 0; i < argc; ++i) - args.emplace_back(narrow(argv[i])); - - return {std::move(argv0), std::move(args)}; -} - -std::vector CommandLine::escape_args() const { - std::vector safe; - safe.reserve(args.size()); - for (const auto& arg : args) - safe.emplace_back(escape(arg)); - return safe; -} - -std::string CommandLine::join_args() const { - return boost::algorithm::join(escape_args(), std::string{sep()}); -} - -std::string CommandLine::join() const { - if (!has_argv0()) - throw std::logic_error{"argv[0] isn't defined"}; - std::ostringstream oss; - oss << escape_argv0(); - if (has_args()) - oss << sep() << join_args(); - return oss.str(); -} - -CommandLine CommandLine::build_from_string(const std::string& src) { - return build_from_string(widen(src)); -} - -CommandLine CommandLine::build_from_string(std::wstring src) { +CommandLine do_parse(std::wstring src) { boost::trim(src); - if (src.empty()) - return {}; + if (src.empty()) { + throw std::runtime_error{"Command line cannot be an empty string"}; + } int argc = 0; std::unique_ptr argv{::CommandLineToArgvW(src.c_str(), &argc)}; @@ -90,8 +42,9 @@ CommandLine CommandLine::build_from_string(std::wstring src) { if (argv.get() == NULL) throw error::windows(GetLastError(), "CommandLineToArgvW"); - if (argc == 0) - return {}; + if (argc == 0) { + throw std::runtime_error{"Command line must contain at least one token"}; + } std::string argv0{narrow(argv.get()[0])}; @@ -104,17 +57,31 @@ CommandLine CommandLine::build_from_string(std::wstring src) { return {std::move(argv0), std::move(args)}; } -std::string CommandLine::escape_for_cmd(const std::string& arg) { - BOOST_STATIC_CONSTEXPR auto escape_symbol = '^'; - static const std::string dangerous_symbols{"^!\"%&()<>|"}; +} // namespace - auto safe = escape(arg); - for (const auto danger : dangerous_symbols) { - std::ostringstream replacement; - replacement << escape_symbol << danger; - boost::replace_all(safe, std::string{danger}, replacement.str()); - } - return safe; +CommandLine CommandLine::query() { + return do_parse(::GetCommandLineW()); +} + +CommandLine CommandLine::parse(const std::string& src) { + return do_parse(widen(src)); +} + +CommandLine CommandLine::from_main(int argc, wchar_t* argv[]) { + if (argc < 1) + throw std::range_error{"argc must be a positive number"}; + + std::string argv0{narrow(argv[0])}; + --argc; + ++argv; + + std::vector args; + args.reserve(argc); + + for (int i = 0; i < argc; ++i) + args.emplace_back(narrow(argv[i])); + + return {std::move(argv0), std::move(args)}; } std::string CommandLine::escape(const std::string& arg) { @@ -154,4 +121,37 @@ std::string CommandLine::escape(const std::string& arg) { return safe; } +std::string CommandLine::escape_cmd(const std::string& arg) { + BOOST_STATIC_CONSTEXPR auto escape_symbol = '^'; + static const std::string dangerous_symbols{"^!\"%&()<>|"}; + + auto safe = escape(arg); + for (const auto danger : dangerous_symbols) { + std::ostringstream replacement; + replacement << escape_symbol << danger; + boost::replace_all(safe, std::string{danger}, replacement.str()); + } + return safe; +} + +std::string CommandLine::to_string() const { + std::ostringstream oss; + oss << escape(get_argv0()); + if (has_args()) + oss << token_sep() << args_to_string(); + return oss.str(); +} + +std::string CommandLine::args_to_string() const { + return boost::algorithm::join(escape_args(), std::string{token_sep()}); +} + +std::vector CommandLine::escape_args() const { + std::vector safe; + safe.reserve(args.size()); + for (const auto& arg : args) + safe.emplace_back(escape(arg)); + return safe; +} + } // namespace winapi diff --git a/src/process.cpp b/src/process.cpp index 508f9e1..f95bc56 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -52,7 +52,7 @@ Handle create_process(EscapedCommandLine cmd_line, Process::IO& io) { } EscapedCommandLine escape_command_line(const CommandLine& cmd_line) { - const auto unicode_cmd_line = widen(cmd_line.join()); + const auto unicode_cmd_line = widen(cmd_line.to_string()); EscapedCommandLine buffer; buffer.reserve(unicode_cmd_line.size() + 1); buffer.assign(unicode_cmd_line.cbegin(), unicode_cmd_line.cend()); diff --git a/test/unit_tests/cmd_line.cpp b/test/unit_tests/cmd_line.cpp index 4506304..99646ff 100644 --- a/test/unit_tests/cmd_line.cpp +++ b/test/unit_tests/cmd_line.cpp @@ -13,7 +13,7 @@ BOOST_AUTO_TEST_SUITE(cmd_line_tests) BOOST_AUTO_TEST_CASE(query) { const auto cmd_line = winapi::CommandLine::query(); - BOOST_TEST(cmd_line.has_argv0()); + BOOST_TEST(!cmd_line.get_argv0().empty()); BOOST_TEST_MESSAGE(cmd_line.get_argv0()); } @@ -25,10 +25,10 @@ BOOST_AUTO_TEST_CASE(escape) { LR"(path\to\dir\)", LR"(weird\\argument)", }; - const auto cmd_line = winapi::CommandLine::build_from_main(5, argv); + const auto cmd_line = winapi::CommandLine::from_main(5, argv); const auto expected = R"("test.exe" "arg1 arg2" "path\to\file" "path\to\dir\\" "weird\\argument")"; - BOOST_TEST(cmd_line.join() == expected); + BOOST_TEST(cmd_line.to_string() == expected); } BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3