1313#include " llbuild/Basic/ShellUtility.h"
1414#include " llvm/ADT/SmallString.h"
1515
16+ using namespace llvm ;
1617namespace llbuild {
1718namespace basic {
1819
19- void appendShellEscapedString (llvm::raw_ostream& os, StringRef string) {
2020
21+ #if defined(_WIN32)
22+ std::string formatWindowsCommandArg (StringRef string) {
23+ // Windows escaping, adapted from Daniel Colascione's "Everyone quotes
24+ // command line arguments the wrong way" - Microsoft Developer Blog
25+ const std::string needsQuote = " \t\n\v\" " ;
26+ if (string.find_first_of (needsQuote) == std::string::npos) {
27+ return string;
28+ }
29+
30+ // To escape the command line, we surround the argument with quotes. However
31+ // the complication comes due to how the Windows command line parser treats
32+ // backslashes (\) and quotes (")
33+ //
34+ // - \ is normally treated as a literal backslash
35+ // - e.g. foo\bar\baz => foo\bar\baz
36+ // - However, the sequence \" is treated as a literal "
37+ // - e.g. foo\"bar => foo"bar
38+ //
39+ // But then what if we are given a path that ends with a \? Surrounding
40+ // foo\bar\ with " would be "foo\bar\" which would be an unterminated string
41+ // since it ends on a literal quote. To allow this case the parser treats:
42+ //
43+ // - \\" as \ followed by the " metachar
44+ // - \\\" as \ followed by a literal "
45+ // - In general:
46+ // - 2n \ followed by " => n \ followed by the " metachar
47+ // - 2n+1 \ followed by " => n \ followed by a literal "
48+ std::string escaped = " \" " ;
49+ for (auto i = std::begin (string); i != std::end (string); ++i) {
50+ int numBackslashes = 0 ;
51+ while (i != string.end () && *i == ' \\ ' ) {
52+ ++i;
53+ ++numBackslashes;
54+ }
55+
56+ if (i == string.end ()) {
57+ // String ends with a backslash e.g. foo\bar\, escape all the backslashes
58+ // then add the metachar " below
59+ escaped.append (numBackslashes * 2 , ' \\ ' );
60+ break ;
61+ } else if (*i == ' "' ) {
62+ // This is a string of \ followed by a " e.g. foo\"bar. Escape the
63+ // backslashes and the quote
64+ escaped.append (numBackslashes * 2 + 1 , ' \\ ' );
65+ escaped.push_back (*i);
66+ } else {
67+ // These are just literal backslashes
68+ escaped.append (numBackslashes, ' \\ ' );
69+ escaped.push_back (*i);
70+ }
71+ }
72+ escaped.push_back (' "' );
73+
74+ return escaped;
75+ }
76+
77+ std::string formatWindowsCommandString (std::vector<std::string> args) {
78+ std::string commandLine;
79+ for (auto & arg : args)
80+ commandLine += formatWindowsCommandArg (arg) + " " ;
81+ if (commandLine.size ())
82+ commandLine.pop_back ();
83+ return commandLine;
84+ }
85+ #endif
86+
87+ void appendShellEscapedString (llvm::raw_ostream& os, StringRef string) {
88+ #if defined(_WIN32)
89+ os << formatWindowsCommandArg (string);
90+ return ;
91+ #else
2192 static const std::string whitelist = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_/:@#%+=.," ;
2293 auto pos = string.find_first_not_of (whitelist);
2394
@@ -27,32 +98,27 @@ void appendShellEscapedString(llvm::raw_ostream& os, StringRef string) {
2798 return ;
2899 }
29100
30- #if defined(_WIN32)
31- std::string escQuote = " \" " ;
32- #else
33101 std::string escQuote = " '" ;
34- #endif
35102 // We only need to escape the single quote, if it isn't present we can
36103 // escape using single quotes.
37- auto singleQuotePos = string.find_first_of (escQuote , pos);
104+ auto singleQuotePos = string.find_first_of (" ' " , pos);
38105 if (singleQuotePos == std::string::npos) {
39- os << escQuote;
40- os << string;
41- os << escQuote;
106+ os << " '" << string << " '" ;
42107 return ;
43108 }
44109
45110 // Otherwise iterate and escape all the single quotes.
46- os << escQuote ;
111+ os << " ' " ;
47112 os << string.slice (0 , singleQuotePos);
48113 for (auto idx = singleQuotePos; idx < string.size (); idx++) {
49- if (string[idx] == escQuote[ 0 ] ) {
50- os << escQuote << " \\ " << escQuote << escQuote ;
114+ if (string[idx] == ' \' ' ) {
115+ os << " ' \\ '' " ;
51116 } else {
52117 os << string[idx];
53118 }
54119 }
55- os << escQuote;
120+ os << " '" ;
121+ #endif
56122}
57123
58124std::string shellEscaped (StringRef string) {
0 commit comments