command line: use % as escape character for luids

The command line, like a lot of other code, used the escape/unescape
code in SafeConfigNode. For historic reasons, that code used ! as
escape character, which is awkward for the command line, because
that is a special character.

Instead of further overloading the SafeConfigNode, this patch moves
the escape/unescape code into its own utility class. This is the
cleaner approach anyway. It also adds unit testing for the code.

All other users of the old code are updated. Care must be taken here
to not accidentally switch to a different escape mechanism, because
the mechanism must remain compatible with the old implementation.
This commit is contained in:
Patrick Ohly 2010-09-01 15:59:48 +02:00
parent 21f76cff07
commit 7071f941cd
7 changed files with 205 additions and 94 deletions

View file

@ -22,6 +22,7 @@
#include <syncevo/SyncConfig.h>
#include <syncevo/FilterConfigNode.h>
#include <syncevo/util.h>
#include <set>
using namespace std;
@ -54,13 +55,13 @@ public:
/** return original LUID */
string toLUID() const { return toLUID(m_encodedLUID); }
static string toLUID(const string &encoded) { return SafeConfigNode::unescape(encoded); }
static string toLUID(const string &encoded) { return StringEscape::unescape(encoded, '%'); }
/** fill with unencoded LUID */
void setLUID(const string &luid) { m_encodedLUID = fromLUID(luid); }
/** convert from unencoded LUID */
static string fromLUID(const string &luid) { return SafeConfigNode::escape(luid, true, true); }
static string fromLUID(const string &luid) { return StringEscape::escape(luid, '%', StringEscape::STRICT); }
};
class Cmdline {

View file

@ -80,73 +80,4 @@ void SafeConfigNode::flush()
m_node->flush();
}
string SafeConfigNode::escape(const string &str, bool allSpaces, bool strictMode)
{
string res;
char buffer[4];
bool isLeadingSpace = true;
res.reserve(str.size() * 3);
BOOST_FOREACH(char c, str) {
if(strictMode ?
(isalnum(c) ||
c == '-' ||
c == '_') :
!(((isLeadingSpace || allSpaces) && isspace(c)) ||
c == '=' ||
c == '!' ||
c == '\r' ||
c == '\n')) {
res += c;
if (!isspace(c)) {
isLeadingSpace = false;
}
} else {
sprintf(buffer, "!%02x",
(unsigned int)(unsigned char)c);
res += buffer;
}
}
// also encode trailing space?
if (!strictMode || allSpaces) {
size_t numspaces = 0;
ssize_t off = res.size() - 1;
while (off >= 0 && isspace(res[off])) {
off--;
numspaces++;
}
res.resize(res.size() - numspaces);
BOOST_FOREACH(char c, str.substr(str.size() - numspaces)) {
sprintf(buffer, "!%02x",
(unsigned int)(unsigned char)c);
res += buffer;
}
}
return res;
}
string SafeConfigNode::unescape(const string &str)
{
string res;
size_t curr;
res.reserve(str.size());
curr = 0;
while (curr < str.size()) {
if (str[curr] == '!') {
string hex = str.substr(curr + 1, 2);
res += (char)strtol(hex.c_str(), NULL, 16);
curr += 3;
} else {
res += str[curr];
curr++;
}
}
return res;
}
SE_END_CXX

View file

@ -62,18 +62,6 @@ class SafeConfigNode : public ConfigNode {
/* keep underlying methods visible; our own setProperty() would hide them */
using ConfigNode::setProperty;
/**
* turn str into something which can be used as key or value in ConfigNode
* @param allSpaces escape all spaces, not just those at the start and end
* @paramm strictMode same as in setMode()
*/
static string escape(const string &str, bool allSpaces, bool strictMode);
string escape(const string &str) const { return escape(str, false, m_strictMode); }
/** inverse operation for escape() */
static string unescape(const string &str);
/* ConfigNode API */
virtual void flush();
virtual string readProperty(const string &property) const;
@ -90,6 +78,17 @@ class SafeConfigNode : public ConfigNode {
boost::shared_ptr<ConfigNode> m_node;
boost::shared_ptr<const ConfigNode> m_readOnlyNode;
bool m_strictMode;
/**
* turn str into something which can be used as key or value in ConfigNode
*/
string escape(const string &str) const {
return StringEscape::escape(str, '!', m_strictMode ? StringEscape::STRICT : StringEscape::INI_VALUE);
}
static string unescape(const string &str) {
return StringEscape::unescape(str, '!');
}
};

View file

@ -742,11 +742,11 @@ class SafeConfigProperty : public ConfigProperty {
{}
void setProperty(ConfigNode &node, const string &value) {
ConfigProperty::setProperty(node, SafeConfigNode::escape(value, true, false));
ConfigProperty::setProperty(node, StringEscape::escape(value, '!', StringEscape::INI_WORD));
}
virtual string getProperty(const ConfigNode &node, bool *isDefault = NULL) const {
string res = ConfigProperty::getProperty(node, isDefault);
res = SafeConfigNode::unescape(res);
res = StringEscape::unescape(res, '!');
return res;
}
};

View file

@ -26,7 +26,6 @@
#include <syncevo/SyncSource.h>
#include <syncevo/SyncContext.h>
#include <syncevo/util.h>
#include <syncevo/SafeConfigNode.h>
#include <syncevo/SynthesisEngine.h>
#include <synthesis/SDK_util.h>
@ -1087,7 +1086,7 @@ sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
char **adminData)
{
std::string data = m_configNode->readProperty(m_adminPropertyName);
*adminData = StrAlloc(SafeConfigNode::unescape(data).c_str());
*adminData = StrAlloc(StringEscape::unescape(data, '!').c_str());
resetMap();
return sysync::LOCERR_OK;
}
@ -1095,7 +1094,7 @@ sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
{
m_configNode->setProperty(m_adminPropertyName,
SafeConfigNode::escape(adminData, false, false));
StringEscape::escape(adminData, '!', StringEscape::INI_VALUE));
// Flush here, because some calls to saveAdminData() happend
// after SyncSourceAdmin::flush() (= session end).
@ -1197,12 +1196,12 @@ void SyncSourceAdmin::resetMap()
void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
{
key = StringPrintf ("%s-%x",
SafeConfigNode::escape(mID->localID ? mID->localID : "", true, false).c_str(),
mID->ident);
key = StringPrintf("%s-%x",
StringEscape::escape(mID->localID ? mID->localID : "", '!', StringEscape::INI_WORD).c_str(),
mID->ident);
if (mID->remoteID && mID->remoteID[0]) {
value = StringPrintf("%s %x",
SafeConfigNode::escape(mID->remoteID ? mID->remoteID : "", true, false).c_str(),
StringEscape::escape(mID->remoteID ? mID->remoteID : "", '!', StringEscape::INI_WORD).c_str(),
mID->flags);
} else {
value = StringPrintf("%x", mID->flags);
@ -1212,7 +1211,7 @@ void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value
void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
{
size_t found = key.rfind('-');
mID->localID = StrAlloc(SafeConfigNode::unescape(key.substr(0,found)).c_str());
mID->localID = StrAlloc(StringEscape::unescape(key.substr(0,found), '!').c_str());
if (found != key.npos) {
mID->ident = strtol(key.substr(found+1).c_str(), NULL, 16);
} else {
@ -1222,7 +1221,7 @@ void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync
boost::split(tokens, value, boost::is_from_range(' ', ' '));
if (tokens.size() >= 2) {
// if branch from mapid2entry above
mID->remoteID = StrAlloc(SafeConfigNode::unescape(tokens[0]).c_str());
mID->remoteID = StrAlloc(StringEscape::unescape(tokens[0], '!').c_str());
mID->flags = strtol(tokens[1].c_str(), NULL, 16);
} else {
// else branch from above

View file

@ -459,6 +459,126 @@ std::string SHA_256(const std::string &data)
#endif
}
string StringEscape::escape(const string &str, char escapeChar, Mode mode)
{
string res;
char buffer[4];
bool isLeadingSpace = true;
res.reserve(str.size() * 3);
BOOST_FOREACH(char c, str) {
if(c != escapeChar &&
(mode == STRICT ?
(isalnum(c) ||
c == '-' ||
c == '_') :
!(((isLeadingSpace || mode == INI_WORD) && isspace(c)) ||
c == '=' ||
c == '\r' ||
c == '\n'))) {
res += c;
if (!isspace(c)) {
isLeadingSpace = false;
}
} else {
sprintf(buffer, "%c%02x",
escapeChar,
(unsigned int)(unsigned char)c);
res += buffer;
}
}
// also encode trailing space?
if (mode == INI_VALUE) {
size_t numspaces = 0;
ssize_t off = res.size() - 1;
while (off >= 0 && isspace(res[off])) {
off--;
numspaces++;
}
res.resize(res.size() - numspaces);
BOOST_FOREACH(char c, str.substr(str.size() - numspaces)) {
sprintf(buffer, "%c%02x",
escapeChar,
(unsigned int)(unsigned char)c);
res += buffer;
}
}
return res;
}
string StringEscape::unescape(const string &str, char escapeChar)
{
string res;
size_t curr;
res.reserve(str.size());
curr = 0;
while (curr < str.size()) {
if (str[curr] == escapeChar) {
string hex = str.substr(curr + 1, 2);
res += (char)strtol(hex.c_str(), NULL, 16);
curr += 3;
} else {
res += str[curr];
curr++;
}
}
return res;
}
#ifdef ENABLE_UNIT_TESTS
class StringEscapeTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(StringEscapeTest);
CPPUNIT_TEST(escape);
CPPUNIT_TEST(unescape);
CPPUNIT_TEST_SUITE_END();
void escape() {
const string test = " _-%\rfoo bar?! \n ";
StringEscape def;
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar%3f%21%20%0a%20"), def.escape(test));
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar%3f%21%20%0a%20"),
StringEscape::escape(test, '%', StringEscape::STRICT));
StringEscape word('%', StringEscape::INI_WORD);
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar?!%20%0a%20"), word.escape(test));
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo%20bar?!%20%0a%20"),
StringEscape::escape(test, '%', StringEscape::INI_WORD));
StringEscape ini('%', StringEscape::INI_VALUE);
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo bar?! %0a%20"), ini.escape(test));
CPPUNIT_ASSERT_EQUAL(string("%20_-%25%0dfoo bar?! %0a%20"),
StringEscape::escape(test, '%', StringEscape::INI_VALUE));
StringEscape alt('!', StringEscape::INI_VALUE);
CPPUNIT_ASSERT_EQUAL(string("!20_-%!0dfoo bar?!21 !0a!20"), alt.escape(test));
CPPUNIT_ASSERT_EQUAL(string("!20_-%!0dfoo bar?!21 !0a!20"),
StringEscape::escape(test, '!', StringEscape::INI_VALUE));
}
void unescape() {
const string escaped = "%20_-%25foo%20bar%3F%21%20%0A";
const string plain = " _-%foo bar?! \n";
StringEscape def;
CPPUNIT_ASSERT_EQUAL(plain, def.unescape(escaped));
CPPUNIT_ASSERT_EQUAL(plain, StringEscape::unescape(escaped, '%'));
CPPUNIT_ASSERT_EQUAL(string("%41B"), StringEscape::unescape("%41!42", '!'));
CPPUNIT_ASSERT_EQUAL(string("A!42"), StringEscape::unescape("%41!42", '%'));
}
};
SYNCEVOLUTION_TEST_SUITE_REGISTRATION(StringEscapeTest);
#endif // ENABLE_UNIT_TESTS
std::string StringPrintf(const char *format, ...)
{

View file

@ -168,6 +168,67 @@ unsigned long Hash(const std::string &str);
*/
std::string SHA_256(const std::string &in);
/**
* escape/unescape code
*
* Escaping is done URL-like, with a configurable escape
* character. The exact set of characters to replace (besides the
* special escape character) is configurable, too.
*
* The code used to be in SafeConfigNode, but is of general value.
*/
class StringEscape
{
public:
enum Mode {
INI_VALUE, /**< right hand side of .ini assignment:
escape all spaces at start and end (but not in the middle) and the equal sign */
INI_WORD, /**< same as before, but keep it one word:
escape all spaces and the equal sign = */
STRICT /**< general purpose:
escape all characters besides alphanumeric and -_ */
};
private:
char m_escapeChar;
Mode m_mode;
public:
/**
* default constructor, using % as escape character, escaping all spaces (including
* leading and trailing ones), and all characters besides alphanumeric and -_
*/
StringEscape(char escapeChar = '%', Mode mode = STRICT) :
m_escapeChar(escapeChar),
m_mode(mode)
{}
/** special character which introduces two-char hex encoded original character */
char getEscapeChar() const { return m_escapeChar; }
void setEscapeChar(char escapeChar) { m_escapeChar = escapeChar; }
Mode getMode() const { return m_mode; }
void setMode(Mode mode) { m_mode = mode; }
/**
* escape string according to current settings
*/
string escape(const string &str) const { return escape(str, m_escapeChar, m_mode); }
/** escape string with the given settings */
static string escape(const string &str, char escapeChar, Mode mode);
/**
* unescape string, with escape character as currently set
*/
string unescape(const string &str) const { return unescape(str, m_escapeChar); }
/**
* unescape string, with escape character as given
*/
static string unescape(const string &str, char escapeChar);
};
/**
* This is a simplified implementation of a class representing and calculating
* UUIDs v4 inspired from RFC 4122. We do not use cryptographic pseudo-random