Add function strutils::escape()
This function complements the existing strutils::unescape(). It backslash-escapes a string for C/C++ string literal syntax. For every std::string s, the following holds: unescape(escape(s)) == s (not the other way around, because there are many ways to escape a given character in a string literal)
This commit is contained in:
@@ -632,6 +632,59 @@ std::string encodeHex(const unsigned char* rawBytes, unsigned int length)
|
||||
return hex;
|
||||
}
|
||||
|
||||
// Write an octal backslash-escaped respresentation of 'val' to 'buf'.
|
||||
//
|
||||
// At least 4 write positions must be available at 'buf'. The result is *not*
|
||||
// null-terminated. Only the 8 least significant bits of 'val' are used;
|
||||
// higher-order bits have no influence on the chars written to 'buf'.
|
||||
static void writeOctalBackslashEscapedRepr(char *buf, unsigned char val)
|
||||
{
|
||||
buf[0] = '\\';
|
||||
buf[1] = '0' + ((val >> 6) & 3); // 2 bits
|
||||
buf[2] = '0' + ((val >> 3) & 7); // 3 bits
|
||||
buf[3] = '0' + (val & 7); // 3 bits
|
||||
}
|
||||
|
||||
// Backslash-escape a string for C/C++ string literal syntax.
|
||||
std::string escape(const std::string& s) {
|
||||
string res;
|
||||
char buf[4];
|
||||
|
||||
for (const char c: s) {
|
||||
// We don't really *need* to special-case \a, \b, \f, \n, \r, \t and \v,
|
||||
// because they could be handled like the other non-ASCII or non-printable
|
||||
// characters. However, doing so will make the output string both shorter
|
||||
// and more readable.
|
||||
if (c == '\a') {
|
||||
res += "\\a";
|
||||
} else if (c == '\b') {
|
||||
res += "\\b";
|
||||
} else if (c == '\f') {
|
||||
res += "\\f";
|
||||
} else if (c == '\n') {
|
||||
res += "\\n";
|
||||
} else if (c == '\r') {
|
||||
res += "\\r";
|
||||
} else if (c == '\t') {
|
||||
res += "\\t";
|
||||
} else if (c == '\v') {
|
||||
res += "\\v";
|
||||
} else if (c < 0x20 || c > 0x7e) { // non-ASCII or non-printable character
|
||||
// This is fast (no memory allocation nor IOStreams needed)
|
||||
writeOctalBackslashEscapedRepr(buf, static_cast<unsigned char>(c));
|
||||
res.append(buf, 4);
|
||||
} else if (c == '\\') {
|
||||
res += "\\\\";
|
||||
} else if (c == '"') {
|
||||
res += "\\\"";
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
std::string unescape(const char* s)
|
||||
{
|
||||
|
||||
@@ -230,26 +230,43 @@ namespace simgear {
|
||||
* malformed
|
||||
*/
|
||||
void decodeBase64(const std::string& a, std::vector<unsigned char>& output);
|
||||
|
||||
|
||||
/**
|
||||
* convert bytes to hexadecimal equivalent
|
||||
*/
|
||||
std::string encodeHex(const std::string& bytes);
|
||||
|
||||
|
||||
std::string encodeHex(const unsigned char* rawBytes, unsigned int length);
|
||||
|
||||
/**
|
||||
* Backslash-escape a string for C/C++ string literal syntax.
|
||||
*
|
||||
* @param s Input string.
|
||||
* @return a copy of the input string with proper escaping, so that if the
|
||||
* result is part of a C or C++ file and enclosed in double
|
||||
* quotes, it can be used to represent a string literal that is
|
||||
* equal to the input string.
|
||||
*
|
||||
* @note For every std::string s: unescape(escape(s)) == s
|
||||
* @see unescape()
|
||||
*/
|
||||
std::string escape(const std::string& s);
|
||||
|
||||
/**
|
||||
* Unescape string.
|
||||
*
|
||||
* @param str String possibly containing escaped characters.
|
||||
* @return string with escaped characters replaced by single character
|
||||
* values.
|
||||
*
|
||||
* @note For every std::string s: unescape(escape(s)) == s
|
||||
* @see escape()
|
||||
*/
|
||||
std::string unescape(const char* str);
|
||||
|
||||
inline std::string unescape(const std::string& str)
|
||||
{ return unescape(str.c_str()); }
|
||||
|
||||
|
||||
/**
|
||||
* Check a printf-style format string for dangerous (buffer-overflowing,
|
||||
* memory re-writing) format tokens. If a problematic token is
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// Unit tests for functions inside the strutils package
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h> // _set_errno() on Windows
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility> // std::move()
|
||||
#include <fstream> // std::ifstream
|
||||
#include <cerrno>
|
||||
#include <cstdlib> // _set_errno() on Windows
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace strutils = simgear::strutils;
|
||||
|
||||
@@ -139,6 +144,40 @@ void test_split()
|
||||
}
|
||||
}
|
||||
|
||||
void test_escape()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::escape(""), "");
|
||||
SG_CHECK_EQUAL(strutils::escape("\\"), "\\\\");
|
||||
SG_CHECK_EQUAL(strutils::escape("\""), "\\\"");
|
||||
SG_CHECK_EQUAL(strutils::escape("\\n"), "\\\\n");
|
||||
SG_CHECK_EQUAL(strutils::escape("n\\"), "n\\\\");
|
||||
SG_CHECK_EQUAL(strutils::escape(" ab\nc \\def\t\r \\ ghi\\"),
|
||||
" ab\\nc \\\\def\\t\\r \\\\ ghi\\\\");
|
||||
// U+0152 is LATIN CAPITAL LIGATURE OE. The last word is Egg translated in
|
||||
// French and encoded in UTF-8 ('Œuf' if you can read UTF-8).
|
||||
SG_CHECK_EQUAL(strutils::escape("Un \"Bel\" '\u0152uf'"),
|
||||
"Un \\\"Bel\\\" '\\305\\222uf'");
|
||||
SG_CHECK_EQUAL(strutils::escape("\a\b\f\n\r\t\v"),
|
||||
"\\a\\b\\f\\n\\r\\t\\v");
|
||||
|
||||
// Test with non-printable characters
|
||||
//
|
||||
// - 'prefix' is an std::string that *contains* a NUL character.
|
||||
// - \012 is \n (LINE FEED).
|
||||
// - \037 (\x1F) is the last non-printable ASCII character before \040 (\x20),
|
||||
// which is the space.
|
||||
// - \176 (\x7E) is '~', the last printable ASCII character.
|
||||
// - \377 is \xFF. Higher char values (> 255) are not faithfully encoded by
|
||||
// strutils::escape(): only the lowest 8 bits are used; higher-order bits
|
||||
// are ignored (for people who use chars with more than 8 bits...).
|
||||
const string prefix = string("abc") + '\000';
|
||||
SG_CHECK_EQUAL(strutils::escape(prefix +
|
||||
"\003def\012\037\040\176\177\376\377"),
|
||||
"abc\\000\\003def\\n\\037 ~\\177\\376\\377");
|
||||
|
||||
SG_CHECK_EQUAL(strutils::escape(" \n\tAOa"), " \\n\\tAOa");
|
||||
}
|
||||
|
||||
void test_unescape()
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\ \\n\\t\\x41\\117a"), " \n\tAOa");
|
||||
@@ -151,6 +190,28 @@ void test_unescape()
|
||||
SG_CHECK_EQUAL(strutils::unescape("\\xA-"), "\n-");
|
||||
}
|
||||
|
||||
void aux_escapeAndUnescapeRoundTripTest(const string& testString)
|
||||
{
|
||||
SG_CHECK_EQUAL(strutils::unescape(strutils::escape(testString)), testString);
|
||||
}
|
||||
|
||||
void test_escapeAndUnescapeRoundTrips()
|
||||
{
|
||||
// "\0332" contains two chars: '\033' (ESC) followed by '2'.
|
||||
// Ditto for "\0402": it's a space ('\040') followed by a '2'.
|
||||
vector<string> stringsToTest(
|
||||
{"", "\\", "\n", "\\\\", "\"\'\?\t\rAG\v\a \b\f\\", "\x23\xf8",
|
||||
"\0332", "\0402", "\uab42", "\U12345678"});
|
||||
|
||||
const string withBinary = (string("abc") + '\000' +
|
||||
"\003def\012\037\040\176\177\376\377");
|
||||
stringsToTest.push_back(std::move(withBinary));
|
||||
|
||||
for (const string& s: stringsToTest) {
|
||||
aux_escapeAndUnescapeRoundTripTest(s);
|
||||
}
|
||||
}
|
||||
|
||||
void test_compare_versions()
|
||||
{
|
||||
SG_CHECK_LT(strutils::compare_versions("1.0.12", "1.1"), 0);
|
||||
@@ -239,7 +300,9 @@ int main(int argc, char* argv[])
|
||||
test_simplify();
|
||||
test_to_int();
|
||||
test_split();
|
||||
test_escape();
|
||||
test_unescape();
|
||||
test_escapeAndUnescapeRoundTrips();
|
||||
test_compare_versions();
|
||||
test_md5_hex();
|
||||
test_error_string();
|
||||
|
||||
Reference in New Issue
Block a user