Windows: Stout: Reimplemented `stringify_args`. This was an unused function that ended up being the correct place to implement proper `argv` concatenation and escaping. It returns a `std::wstring` for use (speifically) by `::CreateProcessW`. This brings us a bit closer to Unicode support within Mesos.
Review: https://reviews.apache.org/r/58126/ Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/bce6c05c Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/bce6c05c Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/bce6c05c Branch: refs/heads/master Commit: bce6c05c7c84e4b93881dd9780fe724197f62520 Parents: 718d3c9 Author: Andrew Schwartzmeyer <[email protected]> Authored: Tue Apr 4 13:25:09 2017 -0700 Committer: Joseph Wu <[email protected]> Committed: Tue Apr 4 16:45:17 2017 -0700 ---------------------------------------------------------------------- .../stout/include/stout/os/windows/shell.hpp | 75 ++++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/bce6c05c/3rdparty/stout/include/stout/os/windows/shell.hpp ---------------------------------------------------------------------- diff --git a/3rdparty/stout/include/stout/os/windows/shell.hpp b/3rdparty/stout/include/stout/os/windows/shell.hpp index fdce93c..b93f337 100644 --- a/3rdparty/stout/include/stout/os/windows/shell.hpp +++ b/3rdparty/stout/include/stout/os/windows/shell.hpp @@ -170,22 +170,69 @@ inline int execvpe(const char* file, char* const argv[], char* const envp[]) // Concatenates multiple command-line arguments and escapes the values. -// If `arg` is not specified (or takes the value `0`), the function will -// scan `argv` until a `nullptr` is encountered. -inline std::string stringify_args(char** argv, unsigned long argc = 0) +// NOTE: This is necessary even when using Windows APIs that "appear" +// to take arguments as a list, because those APIs will themselves +// concatenate command-line arguments *without* escaping them. +// +// This function escapes arguments with the following rules: +// 1) Any argument with a space, tab, newline, vertical tab, +// or double-quote must be surrounded in double-quotes. +// 2) Backslashes at the very end of an argument must be escaped. +// 3) Backslashes that precede a double-quote must be escaped. +// The double-quote must also be escaped. +// +// NOTE: The below algorithm is adapted from Daniel Colascione's public domain +// algorithm for quoting command line arguments on Windows for `CreateProcess`. +// +// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +// NOLINT(whitespace/line_length) +inline std::wstring stringify_args(const std::vector<std::string>& argv) { - std::string arg_line = ""; - unsigned long index = 0; - while ((argc == 0 || index < argc) && argv[index] != nullptr) { - // TODO(dpravat): (MESOS-5522) Format these args for all cases. - // Specifically, we need to: - // (1) Add double quotes around arguments that contain special - // characters, like spaces and tabs. - // (2) Escape any existing double quotes and backslashes. - arg_line = strings::join(" ", arg_line, argv[index++]); + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter; + std::wstring command; + for (auto argit = argv.cbegin(); argit != argv.cend(); ++argit) { + std::wstring arg = converter.from_bytes(*argit); + // Don't quote empty arguments or those without troublesome characters. + if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == arg.npos) { + command.append(arg); + } else { + // Beginning double quotation mark. + command.push_back(L'"'); + for (auto it = arg.cbegin(); it != arg.cend(); ++it) { + // Count existent backslashes in argument. + unsigned int backslashes = 0; + while (it != arg.cend() && *it == L'\\') { + ++it; + ++backslashes; + } + + if (it == arg.cend()) { + // Escape all backslashes, but let the terminating double quotation + // mark we add below be interpreted as a metacharacter. + command.append(backslashes * 2, L'\\'); + break; + } else if (*it == L'"') { + // Escape all backslashes and the following double quotation mark. + command.append(backslashes * 2 + 1, L'\\'); + command.push_back(*it); + } else { + // Backslashes aren't special here. + command.append(backslashes, L'\\'); + command.push_back(*it); + } + } + + // Terminating double quotation mark. + command.push_back(L'"'); + } + // Space separate arguments (but don't append at end). + if (argit != argv.cend() - 1) { + command.push_back(L' '); + } } - - return arg_line; + // Append final null terminating character. + command.push_back(L'\0'); + return command; } } // namespace os {
