• 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/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