| // 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 "gn/escape.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/compiler_specific.h" |
| #include "base/json/string_escape.h" |
| #include "base/logging.h" |
| #include "util/build_config.h" |
| |
| namespace { |
| |
| constexpr size_t kStackStringBufferSize = 1024; |
| #if defined(OS_WIN) |
| constexpr size_t kMaxEscapedCharsPerChar = 2; |
| #else |
| constexpr size_t kMaxEscapedCharsPerChar = 3; |
| #endif |
| |
| // A "1" in this lookup table means that char is valid in the Posix shell. |
| // clang-format off |
| 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}; |
| // clang-format on |
| |
| size_t EscapeStringToString_Space(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| size_t i = 0; |
| for (const auto& elem : str) { |
| if (elem == ' ') |
| dest[i++] = '\\'; |
| dest[i++] = elem; |
| } |
| return i; |
| } |
| |
| // Uses the stack if the space needed is small and the heap otherwise. |
| class StackOrHeapBuffer { |
| public: |
| explicit StackOrHeapBuffer(size_t buf_size) { |
| if (UNLIKELY(buf_size > kStackStringBufferSize)) |
| heap_buf.reset(new char[buf_size]); |
| } |
| operator char*() { return heap_buf ? heap_buf.get() : stack_buf; } |
| |
| private: |
| char stack_buf[kStackStringBufferSize]; |
| std::unique_ptr<char[]> heap_buf; |
| }; |
| |
| // 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 bool ShouldEscapeCharForNinja(char ch) { |
| return ch == '$' || ch == ' ' || ch == ':'; |
| } |
| |
| size_t EscapeStringToString_Ninja(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| size_t i = 0; |
| for (const auto& elem : str) { |
| if (ShouldEscapeCharForNinja(elem)) |
| dest[i++] = '$'; |
| dest[i++] = elem; |
| } |
| return i; |
| } |
| |
| inline bool ShouldEscapeCharForCompilationDatabase(char ch) { |
| return ch == '\\' || ch == '"'; |
| } |
| |
| size_t EscapeStringToString_CompilationDatabase(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| size_t i = 0; |
| bool quote = false; |
| for (const auto& elem : str) { |
| if (static_cast<unsigned>(elem) >= 0x80 || |
| !kShellValid[static_cast<int>(elem)]) { |
| quote = true; |
| break; |
| } |
| } |
| if (quote) |
| dest[i++] = '"'; |
| |
| for (const auto& elem : str) { |
| if (ShouldEscapeCharForCompilationDatabase(elem)) |
| dest[i++] = '\\'; |
| dest[i++] = elem; |
| } |
| if (quote) |
| dest[i++] = '"'; |
| return i; |
| } |
| |
| size_t EscapeStringToString_Depfile(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| size_t i = 0; |
| for (const auto& elem : str) { |
| // Escape all characters that ninja depfile parser can recognize as escaped, |
| // even if some of them can work without escaping. |
| if (elem == ' ' || elem == '\\' || elem == '#' || elem == '*' || |
| elem == '[' || elem == '|' || elem == ']') |
| dest[i++] = '\\'; |
| else if (elem == '$') // Extra rule for $$ |
| dest[i++] = '$'; |
| dest[i++] = elem; |
| } |
| return i; |
| } |
| |
| size_t EscapeStringToString_NinjaPreformatted(const std::string_view& str, |
| char* dest) { |
| // Only Ninja-escape $. |
| size_t i = 0; |
| for (const auto& elem : str) { |
| if (elem == '$') |
| dest[i++] = '$'; |
| dest[i++] = elem; |
| } |
| return i; |
| } |
| |
| // 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 |
| size_t EscapeStringToString_WindowsNinjaFork(const std::string_view& str, |
| const EscapeOptions& options, |
| char* 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); |
| |
| size_t i = 0; |
| if (str.find_first_of(" \"") == std::string::npos) { |
| // Simple case, don't quote. |
| return EscapeStringToString_Ninja(str, options, dest, needed_quoting); |
| } else { |
| if (!options.inhibit_quoting) |
| dest[i++] = '"'; |
| |
| for (size_t j = 0; j < str.size(); j++) { |
| // Count backslashes in case they're followed by a quote. |
| size_t backslash_count = 0; |
| while (j < str.size() && str[j] == '\\') { |
| j++; |
| backslash_count++; |
| } |
| if (j == str.size()) { |
| // Backslashes at end of string. Backslash-escape all of them since |
| // they'll be followed by a quote. |
| memset(dest + i, '\\', backslash_count * 2); |
| i += backslash_count * 2; |
| } else if (str[j] == '"') { |
| // 0 or more backslashes followed by a quote. Backslash-escape the |
| // backslashes, then backslash-escape the quote. |
| memset(dest + i, '\\', backslash_count * 2 + 1); |
| i += backslash_count * 2 + 1; |
| dest[i++] = '"'; |
| } else { |
| // Non-special Windows character, just escape for Ninja. Also, add any |
| // backslashes we read previously, these are literals. |
| memset(dest + i, '\\', backslash_count); |
| i += backslash_count; |
| if (ShouldEscapeCharForNinja(str[j])) |
| dest[i++] = '$'; |
| dest[i++] = str[j]; |
| } |
| } |
| |
| if (!options.inhibit_quoting) |
| dest[i++] = '"'; |
| if (needed_quoting) |
| *needed_quoting = true; |
| } |
| return i; |
| } |
| |
| size_t EscapeStringToString_PosixNinjaFork(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| size_t i = 0; |
| 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[i++] = '\\'; |
| dest[i++] = '$'; |
| dest[i++] = elem; |
| } else if (elem == ':') { |
| // Colon is the only other Ninja special char, which is not special to |
| // the shell. |
| dest[i++] = '$'; |
| dest[i++] = ':'; |
| } else if (static_cast<unsigned>(elem) >= 0x80 || |
| !kShellValid[static_cast<int>(elem)]) { |
| // All other invalid shell chars get backslash-escaped. |
| dest[i++] = '\\'; |
| dest[i++] = elem; |
| } else { |
| // Everything else is a literal. |
| dest[i++] = elem; |
| } |
| } |
| return i; |
| } |
| |
| // Escapes |str| into |dest| and returns the number of characters written. |
| size_t EscapeStringToString(const std::string_view& str, |
| const EscapeOptions& options, |
| char* dest, |
| bool* needed_quoting) { |
| switch (options.mode) { |
| case ESCAPE_NONE: |
| strncpy(dest, str.data(), str.size()); |
| return str.size(); |
| case ESCAPE_SPACE: |
| return EscapeStringToString_Space(str, options, dest, needed_quoting); |
| case ESCAPE_NINJA: |
| return EscapeStringToString_Ninja(str, options, dest, needed_quoting); |
| case ESCAPE_DEPFILE: |
| return EscapeStringToString_Depfile(str, options, dest, needed_quoting); |
| case ESCAPE_COMPILATION_DATABASE: |
| return EscapeStringToString_CompilationDatabase(str, options, dest, |
| needed_quoting); |
| case ESCAPE_NINJA_COMMAND: |
| switch (options.platform) { |
| case ESCAPE_PLATFORM_CURRENT: |
| #if defined(OS_WIN) |
| return EscapeStringToString_WindowsNinjaFork(str, options, dest, |
| needed_quoting); |
| #else |
| return EscapeStringToString_PosixNinjaFork(str, options, dest, |
| needed_quoting); |
| #endif |
| case ESCAPE_PLATFORM_WIN: |
| return EscapeStringToString_WindowsNinjaFork(str, options, dest, |
| needed_quoting); |
| case ESCAPE_PLATFORM_POSIX: |
| return EscapeStringToString_PosixNinjaFork(str, options, dest, |
| needed_quoting); |
| default: |
| NOTREACHED(); |
| } |
| case ESCAPE_NINJA_PREFORMATTED_COMMAND: |
| return EscapeStringToString_NinjaPreformatted(str, dest); |
| default: |
| NOTREACHED(); |
| } |
| return 0; |
| } |
| |
| } // namespace |
| |
| std::string EscapeString(const std::string_view& str, |
| const EscapeOptions& options, |
| bool* needed_quoting) { |
| StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar); |
| return std::string(dest, |
| EscapeStringToString(str, options, dest, needed_quoting)); |
| } |
| |
| void EscapeStringToStream(std::ostream& out, |
| const std::string_view& str, |
| const EscapeOptions& options) { |
| StackOrHeapBuffer dest(str.size() * kMaxEscapedCharsPerChar); |
| out.write(dest, EscapeStringToString(str, options, dest, nullptr)); |
| } |
| |
| void EscapeJSONStringToStream(std::ostream& out, |
| const std::string_view& str, |
| const EscapeOptions& options) { |
| std::string dest; |
| bool needed_quoting = !options.inhibit_quoting; |
| base::EscapeJSONString(str, needed_quoting, &dest); |
| |
| EscapeStringToStream(out, dest, options); |
| } |