Home | History | Annotate | Download | only in gn
      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 "tools/gn/escape.h"
      6 
      7 #include "base/containers/stack_container.h"
      8 #include "base/logging.h"
      9 
     10 namespace {
     11 
     12 // A "1" in this lookup table means that char is valid in the Posix shell.
     13 const char kShellValid[0x80] = {
     14 // 00-1f: all are invalid
     15     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     16     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     17 // ' ' !  "  #  $  %  &  '  (  )  *  +  ,  -  .  /
     18     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
     19 //  0  1  2  3  4  5  6  7  8  9  :  ;  <  =  >  ?
     20     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
     21 //  @  A  B  C  D  E  F  G  H  I  J  K  L  M  N  O
     22     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
     23 //  P  Q  R  S  T  U  V  W  X  Y  Z  [  \  ]  ^  _
     24     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
     25 //  `  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o
     26     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
     27 //  p  q  r  s  t  u  v  w  x  y  z  {  |  }  ~
     28     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0 };
     29 
     30 // Append one character to the given string, escaping it for Ninja.
     31 //
     32 // Ninja's escaping rules are very simple. We always escape colons even
     33 // though they're OK in many places, in case the resulting string is used on
     34 // the left-hand-side of a rule.
     35 template<typename DestString>
     36 inline void NinjaEscapeChar(char ch, DestString* dest) {
     37   if (ch == '$' || ch == ' ' || ch == ':')
     38     dest->push_back('$');
     39   dest->push_back(ch);
     40 }
     41 
     42 template<typename DestString>
     43 void EscapeStringToString_Ninja(const base::StringPiece& str,
     44                                 const EscapeOptions& options,
     45                                 DestString* dest,
     46                                 bool* needed_quoting) {
     47   for (size_t i = 0; i < str.size(); i++)
     48     NinjaEscapeChar(str[i], dest);
     49   if (needed_quoting)
     50     *needed_quoting = false;
     51 }
     52 
     53 // Escape for CommandLineToArgvW and additionally escape Ninja characters.
     54 //
     55 // The basic algorithm is if the string doesn't contain any parse-affecting
     56 // characters, don't do anything (other than the Ninja processing). If it does,
     57 // quote the string, and backslash-escape all quotes and backslashes.
     58 // See:
     59 //   http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
     60 //   http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
     61 template<typename DestString>
     62 void EscapeStringToString_WindowsNinjaFork(const base::StringPiece& str,
     63                                            const EscapeOptions& options,
     64                                            DestString* dest,
     65                                            bool* needed_quoting) {
     66   // We assume we don't have any whitespace chars that aren't spaces.
     67   DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos);
     68 
     69   if (str.find_first_of(" \"") == std::string::npos) {
     70     // Simple case, don't quote.
     71     EscapeStringToString_Ninja(str, options, dest, needed_quoting);
     72   } else {
     73     if (!options.inhibit_quoting)
     74       dest->push_back('"');
     75 
     76     for (size_t i = 0; i < str.size(); i++) {
     77       // Count backslashes in case they're followed by a quote.
     78       size_t backslash_count = 0;
     79       while (i < str.size() && str[i] == '\\') {
     80         i++;
     81         backslash_count++;
     82       }
     83       if (i == str.size()) {
     84         // Backslashes at end of string. Backslash-escape all of them since
     85         // they'll be followed by a quote.
     86         dest->append(backslash_count * 2, '\\');
     87       } else if (str[i] == '"') {
     88         // 0 or more backslashes followed by a quote. Backslash-escape the
     89         // backslashes, then backslash-escape the quote.
     90         dest->append(backslash_count * 2 + 1, '\\');
     91         dest->push_back('"');
     92       } else {
     93         // Non-special Windows character, just escape for Ninja. Also, add any
     94         // backslashes we read previously, these are literals.
     95         dest->append(backslash_count, '\\');
     96         NinjaEscapeChar(str[i], dest);
     97       }
     98     }
     99 
    100     if (!options.inhibit_quoting)
    101       dest->push_back('"');
    102     if (needed_quoting)
    103       *needed_quoting = true;
    104   }
    105 }
    106 
    107 template<typename DestString>
    108 void EscapeStringToString_PosixNinjaFork(const base::StringPiece& str,
    109                                          const EscapeOptions& options,
    110                                          DestString* dest,
    111                                          bool* needed_quoting) {
    112   for (size_t i = 0; i < str.size(); i++) {
    113     if (str[i] == '$' || str[i] == ' ') {
    114       // Space and $ are special to both Ninja and the shell. '$' escape for
    115       // Ninja, then backslash-escape for the shell.
    116       dest->push_back('\\');
    117       dest->push_back('$');
    118       dest->push_back(str[i]);
    119     } else if (str[i] == ':') {
    120       // Colon is the only other Ninja special char, which is not special to
    121       // the shell.
    122       dest->push_back('$');
    123       dest->push_back(':');
    124     } else if (static_cast<unsigned>(str[i]) >= 0x80 ||
    125                !kShellValid[static_cast<int>(str[i])]) {
    126       // All other invalid shell chars get backslash-escaped.
    127       dest->push_back('\\');
    128       dest->push_back(str[i]);
    129     } else {
    130       // Everything else is a literal.
    131       dest->push_back(str[i]);
    132     }
    133   }
    134 }
    135 
    136 template<typename DestString>
    137 void EscapeStringToString(const base::StringPiece& str,
    138                           const EscapeOptions& options,
    139                           DestString* dest,
    140                           bool* needed_quoting) {
    141   switch (options.mode) {
    142     case ESCAPE_NONE:
    143       dest->append(str.data(), str.size());
    144       break;
    145     case ESCAPE_NINJA:
    146       EscapeStringToString_Ninja(str, options, dest, needed_quoting);
    147       break;
    148     case ESCAPE_NINJA_COMMAND:
    149       switch (options.platform) {
    150         case ESCAPE_PLATFORM_CURRENT:
    151 #if defined(OS_WIN)
    152           EscapeStringToString_WindowsNinjaFork(str, options, dest,
    153                                                 needed_quoting);
    154 #else
    155           EscapeStringToString_PosixNinjaFork(str, options, dest,
    156                                               needed_quoting);
    157 #endif
    158           break;
    159         case ESCAPE_PLATFORM_WIN:
    160           EscapeStringToString_WindowsNinjaFork(str, options, dest,
    161                                                 needed_quoting);
    162           break;
    163         case ESCAPE_PLATFORM_POSIX:
    164           EscapeStringToString_PosixNinjaFork(str, options, dest,
    165                                               needed_quoting);
    166           break;
    167         default:
    168           NOTREACHED();
    169       }
    170       break;
    171     default:
    172       NOTREACHED();
    173   }
    174 }
    175 
    176 }  // namespace
    177 
    178 std::string EscapeString(const base::StringPiece& str,
    179                          const EscapeOptions& options,
    180                          bool* needed_quoting) {
    181   std::string result;
    182   result.reserve(str.size() + 4);  // Guess we'll add a couple of extra chars.
    183   EscapeStringToString(str, options, &result, needed_quoting);
    184   return result;
    185 }
    186 
    187 void EscapeStringToStream(std::ostream& out,
    188                           const base::StringPiece& str,
    189                           const EscapeOptions& options) {
    190   base::StackString<256> escaped;
    191   EscapeStringToString(str, options, &escaped.container(), NULL);
    192   if (!escaped->empty())
    193     out.write(escaped->data(), escaped->size());
    194 }
    195