| // 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/logging.h" | |
| #include "util/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, 0, 0, 0, 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. | |
| inline void NinjaEscapeChar(char ch, std::string* dest) { | |
| if (ch == '$' || ch == ' ' || ch == ':') | |
| dest->push_back('$'); | |
| dest->push_back(ch); | |
| } | |
| void EscapeStringToString_Ninja(const base::StringPiece& str, | |
| const EscapeOptions& options, | |
| std::string* dest, | |
| bool* needed_quoting) { | |
| for (const auto& elem : str) | |
| NinjaEscapeChar(elem, dest); | |
| } | |
| void EscapeStringToString_NinjaPreformatted(const base::StringPiece& str, | |
| std::string* 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 | |
| void EscapeStringToString_WindowsNinjaFork(const base::StringPiece& str, | |
| const EscapeOptions& options, | |
| std::string* 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; | |
| } | |
| } | |
| void EscapeStringToString_PosixNinjaFork(const base::StringPiece& str, | |
| const EscapeOptions& options, | |
| std::string* 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); | |
| } | |
| } | |
| } | |
| void EscapeStringToString(const base::StringPiece& str, | |
| const EscapeOptions& options, | |
| std::string* 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) { | |
| std::string escaped; | |
| EscapeStringToString(str, options, &escaped, nullptr); | |
| if (!escaped.empty()) | |
| out.write(escaped.data(), escaped.size()); | |
| } |