| // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "tools/gn/escape.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include "base/containers/stack_container.h" | 
 | #include "base/logging.h" | 
 | #include "build/build_config.h" | 
 |  | 
 | namespace { | 
 |  | 
 | // A "1" in this lookup table means that char is valid in the Posix shell. | 
 | const char kShellValid[0x80] = { | 
 | // 00-1f: all are invalid | 
 |     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | 
 |     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | 
 | // ' ' !  "  #  $  %  &  '  (  )  *  +  ,  -  .  / | 
 |     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, | 
 | //  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ? | 
 |     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, | 
 | //  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O | 
 |     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | 
 | //  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _ | 
 |     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, | 
 | //  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o | 
 |     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | 
 | //  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~ | 
 |     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0 }; | 
 |  | 
 | // Append one character to the given string, escaping it for Ninja. | 
 | // | 
 | // Ninja's escaping rules are very simple. We always escape colons even | 
 | // though they're OK in many places, in case the resulting string is used on | 
 | // the left-hand-side of a rule. | 
 | template<typename DestString> | 
 | inline void NinjaEscapeChar(char ch, DestString* dest) { | 
 |   if (ch == '$' || ch == ' ' || ch == ':') | 
 |     dest->push_back('$'); | 
 |   dest->push_back(ch); | 
 | } | 
 |  | 
 | template<typename DestString> | 
 | void EscapeStringToString_Ninja(const base::StringPiece& str, | 
 |                                 const EscapeOptions& options, | 
 |                                 DestString* dest, | 
 |                                 bool* needed_quoting) { | 
 |   for (const auto& elem : str) | 
 |     NinjaEscapeChar(elem, dest); | 
 | } | 
 |  | 
 | template<typename DestString> | 
 | void EscapeStringToString_NinjaPreformatted(const base::StringPiece& str, | 
 |                                             DestString* dest) { | 
 |   // Only Ninja-escape $. | 
 |   for (const auto& elem : str) { | 
 |     if (elem == '$') | 
 |       dest->push_back('$'); | 
 |     dest->push_back(elem); | 
 |   } | 
 | } | 
 |  | 
 | // Escape for CommandLineToArgvW and additionally escape Ninja characters. | 
 | // | 
 | // The basic algorithm is if the string doesn't contain any parse-affecting | 
 | // characters, don't do anything (other than the Ninja processing). If it does, | 
 | // quote the string, and backslash-escape all quotes and backslashes. | 
 | // See: | 
 | //   http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx | 
 | //   http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx | 
 | template<typename DestString> | 
 | void EscapeStringToString_WindowsNinjaFork(const base::StringPiece& str, | 
 |                                            const EscapeOptions& options, | 
 |                                            DestString* dest, | 
 |                                            bool* needed_quoting) { | 
 |   // We assume we don't have any whitespace chars that aren't spaces. | 
 |   DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos); | 
 |  | 
 |   if (str.find_first_of(" \"") == std::string::npos) { | 
 |     // Simple case, don't quote. | 
 |     EscapeStringToString_Ninja(str, options, dest, needed_quoting); | 
 |   } else { | 
 |     if (!options.inhibit_quoting) | 
 |       dest->push_back('"'); | 
 |  | 
 |     for (size_t i = 0; i < str.size(); i++) { | 
 |       // Count backslashes in case they're followed by a quote. | 
 |       size_t backslash_count = 0; | 
 |       while (i < str.size() && str[i] == '\\') { | 
 |         i++; | 
 |         backslash_count++; | 
 |       } | 
 |       if (i == str.size()) { | 
 |         // Backslashes at end of string. Backslash-escape all of them since | 
 |         // they'll be followed by a quote. | 
 |         dest->append(backslash_count * 2, '\\'); | 
 |       } else if (str[i] == '"') { | 
 |         // 0 or more backslashes followed by a quote. Backslash-escape the | 
 |         // backslashes, then backslash-escape the quote. | 
 |         dest->append(backslash_count * 2 + 1, '\\'); | 
 |         dest->push_back('"'); | 
 |       } else { | 
 |         // Non-special Windows character, just escape for Ninja. Also, add any | 
 |         // backslashes we read previously, these are literals. | 
 |         dest->append(backslash_count, '\\'); | 
 |         NinjaEscapeChar(str[i], dest); | 
 |       } | 
 |     } | 
 |  | 
 |     if (!options.inhibit_quoting) | 
 |       dest->push_back('"'); | 
 |     if (needed_quoting) | 
 |       *needed_quoting = true; | 
 |   } | 
 | } | 
 |  | 
 | template<typename DestString> | 
 | void EscapeStringToString_PosixNinjaFork(const base::StringPiece& str, | 
 |                                          const EscapeOptions& options, | 
 |                                          DestString* dest, | 
 |                                          bool* needed_quoting) { | 
 |   for (const auto& elem : str) { | 
 |     if (elem == '$' || elem == ' ') { | 
 |       // Space and $ are special to both Ninja and the shell. '$' escape for | 
 |       // Ninja, then backslash-escape for the shell. | 
 |       dest->push_back('\\'); | 
 |       dest->push_back('$'); | 
 |       dest->push_back(elem); | 
 |     } else if (elem == ':') { | 
 |       // Colon is the only other Ninja special char, which is not special to | 
 |       // the shell. | 
 |       dest->push_back('$'); | 
 |       dest->push_back(':'); | 
 |     } else if (static_cast<unsigned>(elem) >= 0x80 || | 
 |                !kShellValid[static_cast<int>(elem)]) { | 
 |       // All other invalid shell chars get backslash-escaped. | 
 |       dest->push_back('\\'); | 
 |       dest->push_back(elem); | 
 |     } else { | 
 |       // Everything else is a literal. | 
 |       dest->push_back(elem); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | template<typename DestString> | 
 | void EscapeStringToString(const base::StringPiece& str, | 
 |                           const EscapeOptions& options, | 
 |                           DestString* dest, | 
 |                           bool* needed_quoting) { | 
 |   switch (options.mode) { | 
 |     case ESCAPE_NONE: | 
 |       dest->append(str.data(), str.size()); | 
 |       break; | 
 |     case ESCAPE_NINJA: | 
 |       EscapeStringToString_Ninja(str, options, dest, needed_quoting); | 
 |       break; | 
 |     case ESCAPE_NINJA_COMMAND: | 
 |       switch (options.platform) { | 
 |         case ESCAPE_PLATFORM_CURRENT: | 
 | #if defined(OS_WIN) | 
 |           EscapeStringToString_WindowsNinjaFork(str, options, dest, | 
 |                                                 needed_quoting); | 
 | #else | 
 |           EscapeStringToString_PosixNinjaFork(str, options, dest, | 
 |                                               needed_quoting); | 
 | #endif | 
 |           break; | 
 |         case ESCAPE_PLATFORM_WIN: | 
 |           EscapeStringToString_WindowsNinjaFork(str, options, dest, | 
 |                                                 needed_quoting); | 
 |           break; | 
 |         case ESCAPE_PLATFORM_POSIX: | 
 |           EscapeStringToString_PosixNinjaFork(str, options, dest, | 
 |                                               needed_quoting); | 
 |           break; | 
 |         default: | 
 |           NOTREACHED(); | 
 |       } | 
 |       break; | 
 |     case ESCAPE_NINJA_PREFORMATTED_COMMAND: | 
 |       EscapeStringToString_NinjaPreformatted(str, dest); | 
 |       break; | 
 |     default: | 
 |       NOTREACHED(); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | std::string EscapeString(const base::StringPiece& str, | 
 |                          const EscapeOptions& options, | 
 |                          bool* needed_quoting) { | 
 |   std::string result; | 
 |   result.reserve(str.size() + 4);  // Guess we'll add a couple of extra chars. | 
 |   EscapeStringToString(str, options, &result, needed_quoting); | 
 |   return result; | 
 | } | 
 |  | 
 | void EscapeStringToStream(std::ostream& out, | 
 |                           const base::StringPiece& str, | 
 |                           const EscapeOptions& options) { | 
 |   base::StackString<256> escaped; | 
 |   EscapeStringToString(str, options, &escaped.container(), nullptr); | 
 |   if (!escaped->empty()) | 
 |     out.write(escaped->data(), escaped->size()); | 
 | } |