From 53c959a6795d098d800d85750f21afa721750c56 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Wed, 14 Oct 2020 19:51:42 +0300 Subject: cmd_line.hpp: rework for this project --- CMakeLists.txt | 2 +- include/winapi/cmd_line.hpp | 101 ++++++++++++++++++++++++++------------------ test/CMakeLists.txt | 2 +- test/cmd_line.cpp | 34 +++++++++++++++ 4 files changed, 95 insertions(+), 44 deletions(-) create mode 100644 test/cmd_line.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ee72ccb..4d965c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ add_library(winapi_common ${winapi_common_include} ${winapi_common_src}) target_include_directories(winapi_common PUBLIC include/) add_subdirectory(3rdparty/winapi/utf8 EXCLUDE_FROM_ALL) -target_link_libraries(winapi_common PRIVATE winapi_utf8) +target_link_libraries(winapi_common PUBLIC winapi_utf8) find_package(Boost REQUIRED) target_link_libraries(winapi_common PUBLIC Boost::boost) diff --git a/include/winapi/cmd_line.hpp b/include/winapi/cmd_line.hpp index 4d652e9..f19d6b9 100644 --- a/include/winapi/cmd_line.hpp +++ b/include/winapi/cmd_line.hpp @@ -6,10 +6,16 @@ #pragma once #include "error.hpp" -#include "string.hpp" -#include +#include + +#include +#include + +// clang-format off #include +#include +// clang-format on #include #include @@ -23,21 +29,21 @@ namespace winapi { class CommandLine { public: - static CommandLine query() { return build_from_string(GetCommandLine()); } + static CommandLine query() { return build_from_string(::GetCommandLineW()); } static CommandLine build_from_main(int argc, wchar_t* argv[]) { if (argc < 1) throw std::range_error{"invalid argc value"}; - std::wstring argv0{argv[0]}; + std::string argv0{narrow(argv[0])}; --argc; ++argv; - std::vector args; + std::vector args; args.reserve(argc); for (int i = 0; i < argc; ++i) - args.emplace_back(argv[i]); + args.emplace_back(narrow(argv[i])); return {std::move(argv0), std::move(args)}; } @@ -46,118 +52,129 @@ public: bool has_argv0() const { return !argv0.empty(); } - std::wstring get_argv0() const { return argv0; } + std::string get_argv0() const { return argv0; } - std::wstring escape_argv0() const { return escape(get_argv0()); } + std::string escape_argv0() const { return escape(get_argv0()); } bool has_args() const { return !get_args().empty(); } - const std::vector& get_args() const { return args; } + const std::vector& get_args() const { return args; } - std::vector escape_args() const { - std::vector safe; + std::vector escape_args() const { + std::vector safe; safe.reserve(args.size()); for (const auto& arg : args) safe.emplace_back(escape(arg)); return safe; } - static constexpr wchar_t sep() { return L' '; } + static BOOST_CONSTEXPR char sep() { return ' '; } - std::wstring join_args() const { return string::join(sep(), escape_args()); } + std::string join_args() const { + return boost::algorithm::join(escape_args(), std::string{sep()}); + } - std::wstring join() const { + std::string join() const { if (!has_argv0()) throw std::logic_error{"argv[0] isn't defined"}; - std::wostringstream oss; + std::ostringstream oss; oss << escape_argv0(); if (has_args()) - oss << sep() << string::join(sep(), escape_args()); + oss << sep() << join_args(); return oss.str(); } private: + static CommandLine build_from_string(const std::string& src) { + return build_from_string(widen(src)); + } + static CommandLine build_from_string(std::wstring src) { - string::trim(src); + boost::trim(src); if (src.empty()) return {}; int argc = 0; - std::unique_ptr argv{CommandLineToArgvW(src.c_str(), &argc)}; + std::unique_ptr argv{::CommandLineToArgvW(src.c_str(), &argc)}; if (argv.get() == NULL) - error::raise("CommandLineToArgvW"); + throw error::windows(GetLastError(), "CommandLineToArgvW"); if (argc == 0) return {}; - std::wstring argv0{argv.get()[0]}; + std::string argv0{narrow(argv.get()[0])}; - std::vector args; + std::vector args; args.reserve(argc - 1); for (int i = 1; i < argc; ++i) - args.emplace_back(argv.get()[i]); + args.emplace_back(narrow(argv.get()[i])); return {std::move(argv0), std::move(args)}; } - inline std::wstring escape_for_cmd(const std::wstring& arg) { - static constexpr auto escape_symbol = L'^'; - static constexpr auto dangerous_symbols = L"!\"%&()<>^|"; + struct LocalDelete { + void operator()(wchar_t* argv[]) const { ::LocalFree(argv); } + }; + + static std::string escape_for_cmd(const std::string& arg) { + BOOST_STATIC_CONSTEXPR auto escape_symbol = '^'; + static const std::string dangerous_symbols{"^!\"%&()<>|"}; auto safe = escape(arg); - string::prefix_with(safe, dangerous_symbols, escape_symbol); + for (const auto danger : dangerous_symbols) { + std::ostringstream replacement; + replacement << escape_symbol << danger; + boost::replace_all(safe, std::string{danger}, replacement.str()); + } return safe; } - static std::wstring escape(const std::wstring& arg) { - std::wstring safe; + static std::string escape(const std::string& arg) { + std::string safe; + // Including the quotes: safe.reserve(arg.length() + 2); - safe.push_back(L'"'); + safe.push_back('"'); for (auto it = arg.cbegin(); it != arg.cend(); ++it) { std::size_t numof_backslashes = 0; - for (; it != arg.cend() && *it == L'\\'; ++it) + for (; it != arg.cend() && *it == '\\'; ++it) ++numof_backslashes; if (it == arg.cend()) { safe.reserve(safe.capacity() + numof_backslashes); - safe.append(2 * numof_backslashes, L'\\'); + safe.append(2 * numof_backslashes, '\\'); break; } switch (*it) { case L'"': safe.reserve(safe.capacity() + numof_backslashes + 1); - safe.append(2 * numof_backslashes + 1, L'\\'); + safe.append(2 * numof_backslashes + 1, '\\'); break; default: - safe.append(numof_backslashes, L'\\'); + safe.append(numof_backslashes, '\\'); break; } safe.push_back(*it); } - safe.push_back(L'"'); + safe.push_back('"'); return safe; } - struct LocalDelete { - void operator()(wchar_t* argv[]) const { LocalFree(argv); } - }; - - CommandLine(std::vector&& args) : args{std::move(args)} {} + CommandLine(std::vector&& args) : args{std::move(args)} {} - CommandLine(std::wstring&& argv0, std::vector&& args = {}) + CommandLine(std::string&& argv0, std::vector&& args = {}) : argv0{std::move(argv0)}, args{std::move(args)} {} - const std::wstring argv0; - const std::vector args; + const std::string argv0; + const std::vector args; }; } // namespace winapi diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4c09bcd..55cdf34 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(Boost REQUIRED COMPONENTS unit_test_framework) -add_executable(unit_tests main.cpp error.cpp handle.cpp) +add_executable(unit_tests main.cpp cmd_line.cpp error.cpp handle.cpp) target_link_libraries(unit_tests PRIVATE winapi_common) target_link_libraries(unit_tests PRIVATE Boost::disable_autolinking Boost::unit_test_framework) set_target_properties(unit_tests PROPERTIES OUTPUT_NAME winapi-common-unit-tests) diff --git a/test/cmd_line.cpp b/test/cmd_line.cpp new file mode 100644 index 0000000..4506304 --- /dev/null +++ b/test/cmd_line.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2020 Egor Tensin +// This file is part of the "winapi-common" project. +// For details, see https://github.com/egor-tensin/winapi-common. +// Distributed under the MIT License. + +#include + +#include + +#include + +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_MESSAGE(cmd_line.get_argv0()); +} + +BOOST_AUTO_TEST_CASE(escape) { + wchar_t* argv[] = { + L"test.exe", + L"arg1 arg2", + LR"(path\to\file)", + LR"(path\to\dir\)", + LR"(weird\\argument)", + }; + const auto cmd_line = winapi::CommandLine::build_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_AUTO_TEST_SUITE_END() -- cgit v1.2.3