From 69ed8e0c82e4965c9cdd96f6847e163f7a032842 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 27 Aug 2019 13:32:24 +0300 Subject: Fix backslash escaping in windows process arguments --- libbutl/process.cxx | 28 +++++++++++++++++-- tests/process/driver.cxx | 72 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/libbutl/process.cxx b/libbutl/process.cxx index 3b18355..8b558ff 100644 --- a/libbutl/process.cxx +++ b/libbutl/process.cxx @@ -1301,16 +1301,38 @@ namespace butl if (q) s += '"'; + // Note that backslashes don't need escaping, unless they immediately + // precede the double quote (see `Parsing C Command-Line Arguments` MSDN + // article for more details). For example: + // + // -DPATH="C:\\foo\\" -> -DPATH=\"C:\\foo\\\\\" + // -DPATH=C:\foo bar\ -> "-DPATH=C:\foo bar\\" + // + size_t nbs (0); // Number of consecutive backslashes. for (size_t i (0), n (strlen (a)); i != n; ++i) { - if (a[i] != '"') - s += a[i]; + char c (a[i]); + + if (c != '"') + s += c; else - s += "\\\""; + { + if (nbs != 0) + s.append (nbs, '\\'); // Escape backslashes. + + s += "\\\""; // Escape quote. + } + + nbs = c == '\\' ? nbs + 1 : 0; } if (q) + { + if (nbs != 0) + s.append (nbs, '\\'); // Escape backslashes. + s += '"'; + } return s.c_str (); } diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index f2dd337..a235f05 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -38,6 +38,36 @@ using namespace butl; static const char* envvars[] = {"ABC=1", "DEF", nullptr}; +using cstrings = vector; + +bool +roundtrip_arg (const path& p, const string& a) +{ + string r; + + try + { + cstrings args {p.string ().c_str (), "-a", a.c_str (), nullptr}; + process pr (args.data (), 0 /* in */, -1 /* out */, 2 /* err */); + + ifdstream is (move (pr.in_ofd)); + + assert (getline (is, r)); + assert (pr.wait ()); + } + catch (const process_error& e) + { + //cerr << args[0] << ": " << e << endl; + + if (e.child) + exit (1); + + assert (false); + } + + return r == a; +} + static bool exec (const path& p, vector in = vector (), @@ -48,7 +78,6 @@ exec (const path& p, dir_path wd = dir_path (), // Set the working dir for the child process. bool env = false) // Set environment variables for the child. { - using cstrings = vector; using butl::optional; assert (!in.empty () || (!out && !err)); // Nothing to output if no input. @@ -197,6 +226,26 @@ exec (const path& p, p, vector (i.begin (), i.end ()), o, e, pipeline, false, wd, env); } +// Usages: +// +// argv[0] +// argv[0] -a +// argv[0] -c [-b] [-e] [] +// +// In the first form run some basic process execution/communication tests. +// +// In the second form print the arguments to STDOUT one per line. +// +// In the third form read the data from STDIN and print it to STDOUT and +// STDERR. Also check if the working directory argument matches the current +// directory, if specified. +// +// -b +// Set binary mode for the standard streams. +// +// -e +// Check some environment variable values. +// int main (int argc, const char* argv[]) { @@ -210,6 +259,14 @@ main (int argc, const char* argv[]) assert (argc > 0); + if (argc > 1 && string (argv[1]) == "-a") + { + for (int i (2); i != argc; ++i) + cout << argv[i] << endl; + + return 0; + } + int i (1); for (; i != argc; ++i) { @@ -237,13 +294,7 @@ main (int argc, const char* argv[]) } } - if (i != argc) - { - if (!child) - cerr << "usage: " << argv[0] << " [-c] [-b] []" << endl; - - return 1; - } + assert (i == argc); path p; @@ -328,6 +379,11 @@ main (int argc, const char* argv[]) assert (!p.wait ()); // "Exited" with an error. } + assert (roundtrip_arg (p, "-DPATH=\"C:\\\\foo\\\\\"")); // -DPATH="C:\\foo\\" + assert (roundtrip_arg (p, "C:\\\\f oo\\\\")); + assert (roundtrip_arg (p, "C:\\\"f oo\\\\")); + assert (roundtrip_arg (p, "C:\\f oo\\")); + const char* s ("ABC\nXYZ"); assert (exec (p)); -- cgit v1.1