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