• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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