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.
4
5 #include "gn/escape.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10
11 #include "base/compiler_specific.h"
12 #include "base/json/string_escape.h"
13 #include "base/logging.h"
14 #include "util/build_config.h"
15
16 namespace {
17
18 constexpr size_t kStackStringBufferSize = 1024;
19 #if defined(OS_WIN)
20 constexpr size_t kMaxEscapedCharsPerChar = 2;
21 #else
22 constexpr size_t kMaxEscapedCharsPerChar = 3;
23 #endif
24
25 // A "1" in this lookup table means that char is valid in the Posix shell.
26 // clang-format off
27 const char kShellValid[0x80] = {
28 // 00-1f: all are invalid
29 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31 // ' ' ! " # $ % & ' ( ) * + , - . /
32 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
33 // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
34 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
35 // @ A B C D E F G H I J K L M N O
36 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
37 // P Q R S T U V W X Y Z [ \ ] ^ _
38 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
39 // ` a b c d e f g h i j k l m n o
40 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
41 // p q r s t u v w x y z { | } ~
42 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
43 // clang-format on
44
EscapeStringToString_Space(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)45 size_t EscapeStringToString_Space(std::string_view str,
46 const EscapeOptions& options,
47 char* dest,
48 bool* needed_quoting) {
49 size_t i = 0;
50 for (const auto& elem : str) {
51 if (elem == ' ')
52 dest[i++] = '\\';
53 dest[i++] = elem;
54 }
55 return i;
56 }
57
58 // Uses the stack if the space needed is small and the heap otherwise.
59 class StackOrHeapBuffer {
60 public:
StackOrHeapBuffer(size_t buf_size)61 explicit StackOrHeapBuffer(size_t buf_size) {
62 if (UNLIKELY(buf_size > kStackStringBufferSize))
63 heap_buf.reset(new char[buf_size]);
64 }
operator char*()65 operator char*() { return heap_buf ? heap_buf.get() : stack_buf; }
66
67 private:
68 char stack_buf[kStackStringBufferSize];
69 std::unique_ptr<char[]> heap_buf;
70 };
71
72 // Ninja's escaping rules are very simple. We always escape colons even
73 // though they're OK in many places, in case the resulting string is used on
74 // the left-hand-side of a rule.
ShouldEscapeCharForNinja(char ch)75 inline bool ShouldEscapeCharForNinja(char ch) {
76 return ch == '$' || ch == ' ' || ch == ':';
77 }
78
EscapeStringToString_Ninja(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)79 size_t EscapeStringToString_Ninja(std::string_view str,
80 const EscapeOptions& options,
81 char* dest,
82 bool* needed_quoting) {
83 size_t i = 0;
84 for (const auto& elem : str) {
85 if (ShouldEscapeCharForNinja(elem))
86 dest[i++] = '$';
87 dest[i++] = elem;
88 }
89 return i;
90 }
91
ShouldEscapeCharForCompilationDatabase(char ch)92 inline bool ShouldEscapeCharForCompilationDatabase(char ch) {
93 return ch == '\\' || ch == '"';
94 }
95
EscapeStringToString_CompilationDatabase(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)96 size_t EscapeStringToString_CompilationDatabase(std::string_view str,
97 const EscapeOptions& options,
98 char* dest,
99 bool* needed_quoting) {
100 size_t i = 0;
101 bool quote = false;
102 for (const auto& elem : str) {
103 if (static_cast<unsigned>(elem) >= 0x80 ||
104 !kShellValid[static_cast<int>(elem)]) {
105 quote = true;
106 break;
107 }
108 }
109 if (quote)
110 dest[i++] = '"';
111
112 for (const auto& elem : str) {
113 if (ShouldEscapeCharForCompilationDatabase(elem))
114 dest[i++] = '\\';
115 dest[i++] = elem;
116 }
117 if (quote)
118 dest[i++] = '"';
119 return i;
120 }
121
EscapeStringToString_Depfile(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)122 size_t EscapeStringToString_Depfile(std::string_view str,
123 const EscapeOptions& options,
124 char* dest,
125 bool* needed_quoting) {
126 size_t i = 0;
127 for (const auto& elem : str) {
128 // Escape all characters that ninja depfile parser can recognize as escaped,
129 // even if some of them can work without escaping.
130 if (elem == ' ' || elem == '\\' || elem == '#' || elem == '*' ||
131 elem == '[' || elem == '|' || elem == ']')
132 dest[i++] = '\\';
133 else if (elem == '$') // Extra rule for $$
134 dest[i++] = '$';
135 dest[i++] = elem;
136 }
137 return i;
138 }
139
EscapeStringToString_NinjaPreformatted(std::string_view str,char * dest)140 size_t EscapeStringToString_NinjaPreformatted(std::string_view str,
141 char* dest) {
142 // Only Ninja-escape $.
143 size_t i = 0;
144 for (const auto& elem : str) {
145 if (elem == '$')
146 dest[i++] = '$';
147 dest[i++] = elem;
148 }
149 return i;
150 }
151
152 // Escape for CommandLineToArgvW and additionally escape Ninja characters.
153 //
154 // The basic algorithm is if the string doesn't contain any parse-affecting
155 // characters, don't do anything (other than the Ninja processing). If it does,
156 // quote the string, and backslash-escape all quotes and backslashes.
157 // See:
158 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
159 // http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
EscapeStringToString_WindowsNinjaFork(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)160 size_t EscapeStringToString_WindowsNinjaFork(std::string_view str,
161 const EscapeOptions& options,
162 char* dest,
163 bool* needed_quoting) {
164 // We assume we don't have any whitespace chars that aren't spaces.
165 DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos);
166
167 size_t i = 0;
168 if (str.find_first_of(" \"") == std::string::npos) {
169 // Simple case, don't quote.
170 return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
171 } else {
172 if (!options.inhibit_quoting)
173 dest[i++] = '"';
174
175 for (size_t j = 0; j < str.size(); j++) {
176 // Count backslashes in case they're followed by a quote.
177 size_t backslash_count = 0;
178 while (j < str.size() && str[j] == '\\') {
179 j++;
180 backslash_count++;
181 }
182 if (j == str.size()) {
183 // Backslashes at end of string. Backslash-escape all of them since
184 // they'll be followed by a quote.
185 memset(dest + i, '\\', backslash_count * 2);
186 i += backslash_count * 2;
187 } else if (str[j] == '"') {
188 // 0 or more backslashes followed by a quote. Backslash-escape the
189 // backslashes, then backslash-escape the quote.
190 memset(dest + i, '\\', backslash_count * 2 + 1);
191 i += backslash_count * 2 + 1;
192 dest[i++] = '"';
193 } else {
194 // Non-special Windows character, just escape for Ninja. Also, add any
195 // backslashes we read previously, these are literals.
196 memset(dest + i, '\\', backslash_count);
197 i += backslash_count;
198 if (ShouldEscapeCharForNinja(str[j]))
199 dest[i++] = '$';
200 dest[i++] = str[j];
201 }
202 }
203
204 if (!options.inhibit_quoting)
205 dest[i++] = '"';
206 if (needed_quoting)
207 *needed_quoting = true;
208 }
209 return i;
210 }
211
EscapeStringToString_PosixNinjaFork(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)212 size_t EscapeStringToString_PosixNinjaFork(std::string_view str,
213 const EscapeOptions& options,
214 char* dest,
215 bool* needed_quoting) {
216 size_t i = 0;
217 for (const auto& elem : str) {
218 if (elem == '$' || elem == ' ') {
219 // Space and $ are special to both Ninja and the shell. '$' escape for
220 // Ninja, then backslash-escape for the shell.
221 dest[i++] = '\\';
222 dest[i++] = '$';
223 dest[i++] = elem;
224 } else if (elem == ':') {
225 // Colon is the only other Ninja special char, which is not special to
226 // the shell.
227 dest[i++] = '$';
228 dest[i++] = ':';
229 } else if (static_cast<unsigned>(elem) >= 0x80 ||
230 !kShellValid[static_cast<int>(elem)]) {
231 // All other invalid shell chars get backslash-escaped.
232 dest[i++] = '\\';
233 dest[i++] = elem;
234 } else {
235 // Everything else is a literal.
236 dest[i++] = elem;
237 }
238 }
239 return i;
240 }
241
242 // Escapes |str| into |dest| and returns the number of characters written.
EscapeStringToString(std::string_view str,const EscapeOptions & options,char * dest,bool * needed_quoting)243 size_t EscapeStringToString(std::string_view str,
244 const EscapeOptions& options,
245 char* dest,
246 bool* needed_quoting) {
247 switch (options.mode) {
248 case ESCAPE_NONE:
249 strncpy(dest, str.data(), str.size());
250 return str.size();
251 case ESCAPE_SPACE:
252 return EscapeStringToString_Space(str, options, dest, needed_quoting);
253 case ESCAPE_NINJA:
254 return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
255 case ESCAPE_DEPFILE:
256 return EscapeStringToString_Depfile(str, options, dest, needed_quoting);
257 case ESCAPE_COMPILATION_DATABASE:
258 return EscapeStringToString_CompilationDatabase(str, options, dest,
259 needed_quoting);
260 case ESCAPE_NINJA_COMMAND:
261 switch (options.platform) {
262 case ESCAPE_PLATFORM_CURRENT:
263 #if defined(OS_WIN)
264 return EscapeStringToString_WindowsNinjaFork(str, options, dest,
265 needed_quoting);
266 #else
267 return EscapeStringToString_PosixNinjaFork(str, options, dest,
268 needed_quoting);
269 #endif
270 case ESCAPE_PLATFORM_WIN:
271 return EscapeStringToString_WindowsNinjaFork(str, options, dest,
272 needed_quoting);
273 case ESCAPE_PLATFORM_POSIX:
274 return EscapeStringToString_PosixNinjaFork(str, options, dest,
275 needed_quoting);
276 default:
277 NOTREACHED();
278 }
279 case ESCAPE_NINJA_PREFORMATTED_COMMAND:
280 return EscapeStringToString_NinjaPreformatted(str, dest);
281 default:
282 NOTREACHED();
283 }
284 return 0;
285 }
286
287 } // namespace
288
EscapeString(std::string_view str,const EscapeOptions & options,bool * needed_quoting)289 std::string EscapeString(std::string_view str,
290 const EscapeOptions& options,
291 bool* needed_quoting) {
292 StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar);
293 return std::string(dest,
294 EscapeStringToString(str, options, dest, needed_quoting));
295 }
296
EscapeStringToStream(std::ostream & out,std::string_view str,const EscapeOptions & options)297 void EscapeStringToStream(std::ostream& out,
298 std::string_view str,
299 const EscapeOptions& options) {
300 StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar);
301 out.write(dest, EscapeStringToString(str, options, dest, nullptr));
302 }
303
EscapeJSONStringToStream(std::ostream & out,std::string_view str,const EscapeOptions & options)304 void EscapeJSONStringToStream(std::ostream& out,
305 std::string_view str,
306 const EscapeOptions& options) {
307 std::string dest;
308 bool needed_quoting = !options.inhibit_quoting;
309 base::EscapeJSONString(str, needed_quoting, &dest);
310
311 EscapeStringToStream(out, dest, options);
312 }
313