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 10namespace { 11 12// A "1" in this lookup table means that char is valid in the Posix shell. 13const 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. 35template<typename DestString> 36inline void NinjaEscapeChar(char ch, DestString* dest) { 37 if (ch == '$' || ch == ' ' || ch == ':') 38 dest->push_back('$'); 39 dest->push_back(ch); 40} 41 42template<typename DestString> 43void 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} 50 51template<typename DestString> 52void EscapeStringToString_NinjaPreformatted(const base::StringPiece& str, 53 DestString* dest) { 54 // Only Ninja-escape $. 55 for (size_t i = 0; i < str.size(); i++) { 56 if (str[i] == '$') 57 dest->push_back('$'); 58 dest->push_back(str[i]); 59 } 60} 61 62// Escape for CommandLineToArgvW and additionally escape Ninja characters. 63// 64// The basic algorithm is if the string doesn't contain any parse-affecting 65// characters, don't do anything (other than the Ninja processing). If it does, 66// quote the string, and backslash-escape all quotes and backslashes. 67// See: 68// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx 69// http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx 70template<typename DestString> 71void EscapeStringToString_WindowsNinjaFork(const base::StringPiece& str, 72 const EscapeOptions& options, 73 DestString* dest, 74 bool* needed_quoting) { 75 // We assume we don't have any whitespace chars that aren't spaces. 76 DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos); 77 78 if (str.find_first_of(" \"") == std::string::npos) { 79 // Simple case, don't quote. 80 EscapeStringToString_Ninja(str, options, dest, needed_quoting); 81 } else { 82 if (!options.inhibit_quoting) 83 dest->push_back('"'); 84 85 for (size_t i = 0; i < str.size(); i++) { 86 // Count backslashes in case they're followed by a quote. 87 size_t backslash_count = 0; 88 while (i < str.size() && str[i] == '\\') { 89 i++; 90 backslash_count++; 91 } 92 if (i == str.size()) { 93 // Backslashes at end of string. Backslash-escape all of them since 94 // they'll be followed by a quote. 95 dest->append(backslash_count * 2, '\\'); 96 } else if (str[i] == '"') { 97 // 0 or more backslashes followed by a quote. Backslash-escape the 98 // backslashes, then backslash-escape the quote. 99 dest->append(backslash_count * 2 + 1, '\\'); 100 dest->push_back('"'); 101 } else { 102 // Non-special Windows character, just escape for Ninja. Also, add any 103 // backslashes we read previously, these are literals. 104 dest->append(backslash_count, '\\'); 105 NinjaEscapeChar(str[i], dest); 106 } 107 } 108 109 if (!options.inhibit_quoting) 110 dest->push_back('"'); 111 if (needed_quoting) 112 *needed_quoting = true; 113 } 114} 115 116template<typename DestString> 117void EscapeStringToString_PosixNinjaFork(const base::StringPiece& str, 118 const EscapeOptions& options, 119 DestString* dest, 120 bool* needed_quoting) { 121 for (size_t i = 0; i < str.size(); i++) { 122 if (str[i] == '$' || str[i] == ' ') { 123 // Space and $ are special to both Ninja and the shell. '$' escape for 124 // Ninja, then backslash-escape for the shell. 125 dest->push_back('\\'); 126 dest->push_back('$'); 127 dest->push_back(str[i]); 128 } else if (str[i] == ':') { 129 // Colon is the only other Ninja special char, which is not special to 130 // the shell. 131 dest->push_back('$'); 132 dest->push_back(':'); 133 } else if (static_cast<unsigned>(str[i]) >= 0x80 || 134 !kShellValid[static_cast<int>(str[i])]) { 135 // All other invalid shell chars get backslash-escaped. 136 dest->push_back('\\'); 137 dest->push_back(str[i]); 138 } else { 139 // Everything else is a literal. 140 dest->push_back(str[i]); 141 } 142 } 143} 144 145template<typename DestString> 146void EscapeStringToString(const base::StringPiece& str, 147 const EscapeOptions& options, 148 DestString* dest, 149 bool* needed_quoting) { 150 switch (options.mode) { 151 case ESCAPE_NONE: 152 dest->append(str.data(), str.size()); 153 break; 154 case ESCAPE_NINJA: 155 EscapeStringToString_Ninja(str, options, dest, needed_quoting); 156 break; 157 case ESCAPE_NINJA_COMMAND: 158 switch (options.platform) { 159 case ESCAPE_PLATFORM_CURRENT: 160#if defined(OS_WIN) 161 EscapeStringToString_WindowsNinjaFork(str, options, dest, 162 needed_quoting); 163#else 164 EscapeStringToString_PosixNinjaFork(str, options, dest, 165 needed_quoting); 166#endif 167 break; 168 case ESCAPE_PLATFORM_WIN: 169 EscapeStringToString_WindowsNinjaFork(str, options, dest, 170 needed_quoting); 171 break; 172 case ESCAPE_PLATFORM_POSIX: 173 EscapeStringToString_PosixNinjaFork(str, options, dest, 174 needed_quoting); 175 break; 176 default: 177 NOTREACHED(); 178 } 179 break; 180 case ESCAPE_NINJA_PREFORMATTED_COMMAND: 181 EscapeStringToString_NinjaPreformatted(str, dest); 182 break; 183 default: 184 NOTREACHED(); 185 } 186} 187 188} // namespace 189 190std::string EscapeString(const base::StringPiece& str, 191 const EscapeOptions& options, 192 bool* needed_quoting) { 193 std::string result; 194 result.reserve(str.size() + 4); // Guess we'll add a couple of extra chars. 195 EscapeStringToString(str, options, &result, needed_quoting); 196 return result; 197} 198 199void EscapeStringToStream(std::ostream& out, 200 const base::StringPiece& str, 201 const EscapeOptions& options) { 202 base::StackString<256> escaped; 203 EscapeStringToString(str, options, &escaped.container(), NULL); 204 if (!escaped->empty()) 205 out.write(escaped->data(), escaped->size()); 206} 207