1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "tools/gn/escape.h"
7 #include "base/containers/stack_container.h"
8 #include "base/logging.h"
12 // A "1" in this lookup table means that char is valid in the Posix shell.
13 const char kShellValid
[0x80] = {
14 // 00-1f: all are invalid
15 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
16 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17 // ' ' ! " # $ % & ' ( ) * + , - . /
18 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
19 // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
20 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
21 // @ A B C D E F G H I J K L M N O
22 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
23 // P Q R S T U V W X Y Z [ \ ] ^ _
24 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
25 // ` a b c d e f g h i j k l m n o
26 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
27 // p q r s t u v w x y z { | } ~
28 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0 };
30 // Append one character to the given string, escaping it for Ninja.
32 // Ninja's escaping rules are very simple. We always escape colons even
33 // though they're OK in many places, in case the resulting string is used on
34 // the left-hand-side of a rule.
35 template<typename DestString
>
36 inline void NinjaEscapeChar(char ch
, DestString
* dest
) {
37 if (ch
== '$' || ch
== ' ' || ch
== ':')
42 template<typename DestString
>
43 void EscapeStringToString_Ninja(const base::StringPiece
& str
,
44 const EscapeOptions
& options
,
46 bool* needed_quoting
) {
47 for (size_t i
= 0; i
< str
.size(); i
++)
48 NinjaEscapeChar(str
[i
], dest
);
51 template<typename DestString
>
52 void EscapeStringToString_NinjaPreformatted(const base::StringPiece
& str
,
54 // Only Ninja-escape $.
55 for (size_t i
= 0; i
< str
.size(); i
++) {
58 dest
->push_back(str
[i
]);
62 // Escape for CommandLineToArgvW and additionally escape Ninja characters.
64 // The basic algorithm is if the string doesn't contain any parse-affecting
65 // characters, don't do anything (other than the Ninja processing). If it does,
66 // quote the string, and backslash-escape all quotes and backslashes.
68 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
69 // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
70 template<typename DestString
>
71 void EscapeStringToString_WindowsNinjaFork(const base::StringPiece
& str
,
72 const EscapeOptions
& options
,
74 bool* needed_quoting
) {
75 // We assume we don't have any whitespace chars that aren't spaces.
76 DCHECK(str
.find_first_of("\r\n\v\t") == std::string::npos
);
78 if (str
.find_first_of(" \"") == std::string::npos
) {
79 // Simple case, don't quote.
80 EscapeStringToString_Ninja(str
, options
, dest
, needed_quoting
);
82 if (!options
.inhibit_quoting
)
85 for (size_t i
= 0; i
< str
.size(); i
++) {
86 // Count backslashes in case they're followed by a quote.
87 size_t backslash_count
= 0;
88 while (i
< str
.size() && str
[i
] == '\\') {
92 if (i
== str
.size()) {
93 // Backslashes at end of string. Backslash-escape all of them since
94 // they'll be followed by a quote.
95 dest
->append(backslash_count
* 2, '\\');
96 } else if (str
[i
] == '"') {
97 // 0 or more backslashes followed by a quote. Backslash-escape the
98 // backslashes, then backslash-escape the quote.
99 dest
->append(backslash_count
* 2 + 1, '\\');
100 dest
->push_back('"');
102 // Non-special Windows character, just escape for Ninja. Also, add any
103 // backslashes we read previously, these are literals.
104 dest
->append(backslash_count
, '\\');
105 NinjaEscapeChar(str
[i
], dest
);
109 if (!options
.inhibit_quoting
)
110 dest
->push_back('"');
112 *needed_quoting
= true;
116 template<typename DestString
>
117 void EscapeStringToString_PosixNinjaFork(const base::StringPiece
& str
,
118 const EscapeOptions
& options
,
120 bool* needed_quoting
) {
121 for (size_t i
= 0; i
< str
.size(); i
++) {
122 if (str
[i
] == '$' || str
[i
] == ' ') {
123 // Space and $ are special to both Ninja and the shell. '$' escape for
124 // Ninja, then backslash-escape for the shell.
125 dest
->push_back('\\');
126 dest
->push_back('$');
127 dest
->push_back(str
[i
]);
128 } else if (str
[i
] == ':') {
129 // Colon is the only other Ninja special char, which is not special to
131 dest
->push_back('$');
132 dest
->push_back(':');
133 } else if (static_cast<unsigned>(str
[i
]) >= 0x80 ||
134 !kShellValid
[static_cast<int>(str
[i
])]) {
135 // All other invalid shell chars get backslash-escaped.
136 dest
->push_back('\\');
137 dest
->push_back(str
[i
]);
139 // Everything else is a literal.
140 dest
->push_back(str
[i
]);
145 template<typename DestString
>
146 void EscapeStringToString(const base::StringPiece
& str
,
147 const EscapeOptions
& options
,
149 bool* needed_quoting
) {
150 switch (options
.mode
) {
152 dest
->append(str
.data(), str
.size());
155 EscapeStringToString_Ninja(str
, options
, dest
, needed_quoting
);
157 case ESCAPE_NINJA_COMMAND
:
158 switch (options
.platform
) {
159 case ESCAPE_PLATFORM_CURRENT
:
161 EscapeStringToString_WindowsNinjaFork(str
, options
, dest
,
164 EscapeStringToString_PosixNinjaFork(str
, options
, dest
,
168 case ESCAPE_PLATFORM_WIN
:
169 EscapeStringToString_WindowsNinjaFork(str
, options
, dest
,
172 case ESCAPE_PLATFORM_POSIX
:
173 EscapeStringToString_PosixNinjaFork(str
, options
, dest
,
180 case ESCAPE_NINJA_PREFORMATTED_COMMAND
:
181 EscapeStringToString_NinjaPreformatted(str
, dest
);
190 std::string
EscapeString(const base::StringPiece
& str
,
191 const EscapeOptions
& options
,
192 bool* needed_quoting
) {
194 result
.reserve(str
.size() + 4); // Guess we'll add a couple of extra chars.
195 EscapeStringToString(str
, options
, &result
, needed_quoting
);
199 void EscapeStringToStream(std::ostream
& out
,
200 const base::StringPiece
& str
,
201 const EscapeOptions
& options
) {
202 base::StackString
<256> escaped
;
203 EscapeStringToString(str
, options
, &escaped
.container(), NULL
);
204 if (!escaped
->empty())
205 out
.write(escaped
->data(), escaped
->size());