#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
const char kShellValid[0x80] = {
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,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
size_t EscapeStringToString_Space(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;
}
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;
};
inline bool ShouldEscapeCharForNinja(char ch) {
return ch == '$' || ch == ' ' || ch == ':';
}
size_t EscapeStringToString_Ninja(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(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(std::string_view str,
const EscapeOptions& options,
char* dest,
bool* needed_quoting) {
size_t i = 0;
for (const auto& elem : str) {
if (elem == ' ' || elem == '\\' || elem == '#' || elem == '*' ||
elem == '[' || elem == '|' || elem == ']')
dest[i++] = '\\';
else if (elem == '$')
dest[i++] = '$';
dest[i++] = elem;
}
return i;
}
size_t EscapeStringToString_NinjaPreformatted(std::string_view str,
char* dest) {
size_t i = 0;
for (const auto& elem : str) {
if (elem == '$')
dest[i++] = '$';
dest[i++] = elem;
}
return i;
}
size_t EscapeStringToString_WindowsNinjaFork(std::string_view str,
const EscapeOptions& options,
char* dest,
bool* needed_quoting) {
DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos);
size_t i = 0;
if (str.find_first_of(" \"") == std::string::npos) {
return EscapeStringToString_Ninja(str, options, dest, needed_quoting);
} else {
if (!options.inhibit_quoting)
dest[i++] = '"';
for (size_t j = 0; j < str.size(); j++) {
size_t backslash_count = 0;
while (j < str.size() && str[j] == '\\') {
j++;
backslash_count++;
}
if (j == str.size()) {
memset(dest + i, '\\', backslash_count * 2);
i += backslash_count * 2;
} else if (str[j] == '"') {
memset(dest + i, '\\', backslash_count * 2 + 1);
i += backslash_count * 2 + 1;
dest[i++] = '"';
} else {
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(std::string_view str,
const EscapeOptions& options,
char* dest,
bool* needed_quoting) {
size_t i = 0;
for (const auto& elem : str) {
if (elem == '$' || elem == ' ') {
dest[i++] = '\\';
dest[i++] = '$';
dest[i++] = elem;
} else if (elem == ':') {
dest[i++] = '$';
dest[i++] = ':';
} else if (static_cast<unsigned>(elem) >= 0x80 ||
!kShellValid[static_cast<int>(elem)]) {
dest[i++] = '\\';
dest[i++] = elem;
} else {
dest[i++] = elem;
}
}
return i;
}
size_t EscapeStringToString(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;
}
}
std::string EscapeString(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,
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,
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);
}