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:
parent
21f76cff07
commit
7071f941cd
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, '!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ...)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue