winapi_common
cmd_line.cpp
1 // Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
2 // This file is part of the "winapi-common" project.
3 // For details, see https://github.com/egor-tensin/winapi-common.
4 // Distributed under the MIT License.
5 
6 #include <winapi/cmd_line.hpp>
7 #include <winapi/error.hpp>
8 #include <winapi/utf8.hpp>
9 #include <winapi/utils.hpp>
10 
11 #include <boost/algorithm/string.hpp>
12 
13 // clang-format off
14 #include <windows.h>
15 #include <shellapi.h>
16 // clang-format on
17 
18 #include <cstddef>
19 #include <memory>
20 #include <sstream>
21 #include <stdexcept>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 namespace winapi {
27 namespace {
28 
29 std::vector<std::string> narrow_all(int argc, wchar_t** argv) {
30  std::vector<std::string> utf;
31  utf.reserve(argc);
32  for (int i = 0; i < argc; ++i)
33  utf.emplace_back(narrow(argv[i]));
34  return utf;
35 }
36 
37 CommandLine do_parse(std::wstring src) {
38  boost::trim(src);
39  if (src.empty()) {
40  throw std::runtime_error{"Command line cannot be an empty string"};
41  }
42 
43  int argc = 0;
44  std::unique_ptr<wchar_t*, LocalDelete> argv{::CommandLineToArgvW(src.c_str(), &argc)};
45 
46  if (argv.get() == NULL) {
47  throw error::windows(GetLastError(), "CommandLineToArgvW");
48  }
49  if (argc == 0) {
50  throw std::runtime_error{"Command line must contain at least one token"};
51  }
52 
53  return CommandLine{narrow_all(argc, argv.get())};
54 }
55 
56 std::string split_argv0(std::vector<std::string>& argv) {
57  if (argv.empty()) {
58  throw std::range_error{"argv must contain at least one element"};
59  }
60  auto argv0 = argv[0];
61  argv.erase(argv.begin());
62  return argv0;
63 }
64 
65 std::vector<std::string> escape_all(const std::vector<std::string>& xs) {
66  std::vector<std::string> escaped;
67  escaped.reserve(xs.size());
68  for (const auto& x : xs)
69  escaped.emplace_back(CommandLine::escape(x));
70  return escaped;
71 }
72 
73 } // namespace
74 
76  return do_parse(::GetCommandLineW());
77 }
78 
79 CommandLine CommandLine::parse(const std::string& src) {
80  return do_parse(widen(src));
81 }
82 
83 CommandLine CommandLine::from_main(int argc, wchar_t* argv[]) {
84  if (argc < 1)
85  throw std::range_error{"argc must be a positive number"};
86  return CommandLine{narrow_all(argc, argv)};
87 }
88 
89 CommandLine::CommandLine(std::vector<std::string> argv) : m_args{std::move(argv)} {
90  m_argv0 = split_argv0(m_args);
91 }
92 
93 std::string CommandLine::escape(const std::string& arg) {
94  std::ostringstream safe;
95  safe << '"';
96 
97  for (auto it = arg.cbegin(); it != arg.cend(); ++it) {
98  std::size_t numof_backslashes = 0;
99 
100  for (; it != arg.cend() && *it == '\\'; ++it)
101  ++numof_backslashes;
102 
103  if (it == arg.cend()) {
104  safe << std::string(2 * numof_backslashes, '\\');
105  break;
106  }
107 
108  switch (*it) {
109  case L'"':
110  safe << std::string(2 * numof_backslashes + 1, '\\');
111  break;
112 
113  default:
114  safe << std::string(numof_backslashes, '\\');
115  break;
116  }
117 
118  safe << *it;
119  }
120 
121  safe << '"';
122  return safe.str();
123 }
124 
125 std::string CommandLine::escape_cmd(const std::string& arg) {
126  static constexpr auto escape_symbol = '^';
127  static const std::string dangerous_symbols{"^!\"%&()<>|"};
128 
129  auto safe = escape(arg);
130  for (const auto danger : dangerous_symbols) {
131  std::ostringstream replacement;
132  replacement << escape_symbol << danger;
133  boost::replace_all(safe, std::string{danger}, replacement.str());
134  }
135  return safe;
136 }
137 
138 std::string CommandLine::to_string() const {
139  return boost::algorithm::join(escape_argv(), std::string{token_sep()});
140 }
141 
142 std::string CommandLine::args_to_string() const {
143  return boost::algorithm::join(escape_args(), std::string{token_sep()});
144 }
145 
146 std::vector<std::string> CommandLine::get_argv() const {
147  auto argv = get_args();
148  argv.emplace(argv.begin(), get_argv0());
149  return argv;
150 }
151 
152 std::vector<std::string> CommandLine::escape_args() const {
153  return escape_all(get_args());
154 }
155 
156 std::vector<std::string> CommandLine::escape_argv() const {
157  return escape_all(get_argv());
158 }
159 
160 } // namespace winapi
Command line for the current process or for launching new processes.
Definition: cmd_line.hpp:21
static CommandLine from_main(int argc, wchar_t *argv[])
Definition: cmd_line.cpp:83
std::string get_argv0() const
Definition: cmd_line.hpp:87
static CommandLine query()
Definition: cmd_line.cpp:75
static CommandLine parse(const std::string &src)
Definition: cmd_line.cpp:79
std::vector< std::string > get_argv() const
Definition: cmd_line.cpp:146
std::string to_string() const
Definition: cmd_line.cpp:138
const std::vector< std::string > & get_args() const
Definition: cmd_line.hpp:96
std::string args_to_string() const
Definition: cmd_line.cpp:142
Make std::system_error work with GetLastError().