// Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com> // 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 <winapi/cmd_line.hpp> #include <winapi/error.hpp> #include <winapi/utf8.hpp> #include <boost/algorithm/string.hpp> #include <boost/config.hpp> // clang-format off #include <windows.h> #include <shellapi.h> // clang-format on #include <cstddef> #include <memory> #include <stdexcept> #include <sstream> #include <string> #include <utility> #include <vector> namespace winapi { namespace { 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<std::string> 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<std::string> CommandLine::escape_args() const { std::vector<std::string> 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) { boost::trim(src); if (src.empty()) return {}; int argc = 0; std::unique_ptr<wchar_t*, LocalDelete> argv{::CommandLineToArgvW(src.c_str(), &argc)}; if (argv.get() == NULL) throw error::windows(GetLastError(), "CommandLineToArgvW"); if (argc == 0) return {}; std::string argv0{narrow(argv.get()[0])}; std::vector<std::string> args; args.reserve(argc - 1); for (int i = 1; i < argc; ++i) args.emplace_back(narrow(argv.get()[i])); 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{"^!\"%&()<>|"}; 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::escape(const std::string& arg) { std::string safe; // Including the quotes: safe.reserve(arg.length() + 2); safe.push_back('"'); for (auto it = arg.cbegin(); it != arg.cend(); ++it) { std::size_t numof_backslashes = 0; for (; it != arg.cend() && *it == '\\'; ++it) ++numof_backslashes; if (it == arg.cend()) { safe.reserve(safe.capacity() + numof_backslashes); safe.append(2 * numof_backslashes, '\\'); break; } switch (*it) { case L'"': safe.reserve(safe.capacity() + numof_backslashes + 1); safe.append(2 * numof_backslashes + 1, '\\'); break; default: safe.append(numof_backslashes, '\\'); break; } safe.push_back(*it); } safe.push_back('"'); return safe; } } // namespace winapi