/*
* Copyright (c) 2010 Remko Tronçon
* Licensed under the GNU General Public License v3.
* See Documentation/Licenses/GPLv3.txt for more information.
*/
#define SWIFTEN_CACHE_JID_PREP
#include <vector>
#include <list>
#include <iostream>
#include <string>
#ifdef SWIFTEN_CACHE_JID_PREP
#include <boost/thread/mutex.hpp>
#include <boost/unordered_map.hpp>
#endif
#include <boost/assign/list_of.hpp>
#include <boost/algorithm/string/find_format.hpp>
#include <boost/algorithm/string/finder.hpp>
#include <sstream>
#include <Swiften/Base/String.h>
#include <Swiften/JID/JID.h>
#include <Swiften/IDN/StringPrep.h>
using namespace Swift;
#ifdef SWIFTEN_CACHE_JID_PREP
typedef boost::unordered_map<std::string, std::string> PrepCache;
static boost::mutex namePrepCacheMutex;
static PrepCache nodePrepCache;
static PrepCache domainPrepCache;
static PrepCache resourcePrepCache;
#endif
static const std::list<char> escapedChars = boost::assign::list_of(' ')('"')('&')('\'')('/')('<')('>')('@')(':');
static std::string getEscaped(char c) {
return makeString() << '\\' << std::hex << static_cast<int>(c);
}
static bool getEscapeSequenceValue(const std::string& sequence, unsigned char& value) {
std::stringstream s;
unsigned int v;
s << std::hex << sequence;
s >> v;
value = static_cast<unsigned char>(v);
return (!s.fail() && !s.bad() && (value == 0x5C || std::find(escapedChars.begin(), escapedChars.end(), value) != escapedChars.end()));
}
// Disabling this code for now, since GCC4.5+boost1.42 (on ubuntu) seems to
// result in a bug. Replacing it with naive code.
#if 0
struct UnescapedCharacterFinder {
template<typename Iterator> boost::iterator_range<Iterator> operator()(Iterator begin, Iterator end) {
for (; begin != end; ++begin) {
if (std::find(escapedChars.begin(), escapedChars.end(), *begin) != escapedChars.end()) {
return boost::iterator_range<Iterator>(begin, begin + 1);
}
else if (*begin == '\\') {
// Check if we have an escaped dissalowed character sequence
Iterator innerBegin = begin + 1;
if (innerBegin != end && innerBegin + 1 != end) {
Iterator innerEnd = innerBegin + 2;
unsigned char value;
if (getEscapeSequenceValue(std::string(innerBegin, innerEnd), value)) {
return boost::iterator_range<Iterator>(begin, begin + 1);
}
}
}
}
return boost::iterator_range<Iterator>(end, end);
}
};
struct UnescapedCharacterFormatter {
template<typename FindResult> std::string operator()(const FindResult& match) const {
std::ostringstream s;
s << '\\' << std::hex << static_cast<int>(*match.begin());
return s.str();
}
};
struct EscapedCharacterFinder {
template<typename Iterator> boost::iterator_range<Iterator> operator()(Iterator begin, Iterator end) {
for (; begin != end; ++begin) {
if (*begin == '\\') {
Iterator innerEnd = begin + 1;
for (size_t i = 0; i < 2 && innerEnd != end; ++i, ++innerEnd) {
}
unsigned char value;
if (getEscapeSequenceValue(std::string(begin + 1, innerEnd), value)) {
return boost::iterator_range<Iterator>(begin, innerEnd);
}
}
}
return boost::iterator_range<Iterator>(end, end);
}
};
struct EscapedCharacterFormatter {
template<typename FindResult> std::string operator()(const FindResult& match) const {
unsigned char value;
if (getEscapeSequenceValue(std::string(match.begin() + 1, match.end()), value)) {
return std::string(reinterpret_cast<const char*>(&value), 1);
}
return boost::copy_range<std::string>(match);
}
};
#endif
JID::JID(const char* jid) : valid_(true) {
initializeFromString(std::string(jid));
}
JID::JID(const std::string& jid) : valid_(true) {
initializeFromString(jid);
}
JID::JID(const std::string& node, const std::string& domain) : valid_(true), hasResource_(false) {
nameprepAndSetComponents(node, domain, "");
}
JID::JID(const std::string& node, const std::string& domain, const std::string& resource) : valid_(true), hasResource_(true) {
nameprepAndSetComponents(node, domain, resource);
}
void JID::initializeFromString(const std::string& jid) {
if (String::beginsWith(jid, '@')) {
valid_ = false;
return;
}
std::string bare, resource;
size_t slashIndex = jid.find('/');
if (slashIndex != jid.npos) {
hasResource_ = true;
bare = jid.substr(0, slashIndex);
resource = jid.substr(slashIndex + 1, jid.npos);
}
else {
hasResource_ = false;
bare = jid;
}
std::pair<std::string,std::string> nodeAndDomain = String::getSplittedAtFirst(bare, '@');
if (nodeAndDomain.second.empty()) {
nameprepAndSetComponents("", nodeAndDomain.first, resource);
}
else {
nameprepAndSetComponents(nodeAndDomain.first, nodeAndDomain.second, resource);
}
}
void JID::nameprepAndSetComponents(const std::string& node, const std::string& domain, const std::string& resource) {
if (domain.empty()) {
valid_ = false;
return;
}
try {
#ifndef SWIFTEN_CACHE_JID_PREP
node_ = StringPrep::getPrepared(node, StringPrep::NamePrep);
domain_ = StringPrep::getPrepared(domain, StringPrep::XMPPNodePrep);
resource_ = StringPrep::getPrepared(resource, StringPrep::XMPPResourcePrep);
#else
boost::mutex::scoped_lock lock(namePrepCacheMutex);
std::pair<PrepCache::iterator, bool> r;
r = nodePrepCache.insert(std::make_pair(node, std::string()));
if (r.second) {
r.first->second = StringPrep::getPrepared(node, StringPrep::NamePrep);
}
node_ = r.first->second;
r = domainPrepCache.insert(std::make_pair(domain, std::string()));
if (r.second) {
r.first->second = StringPrep::getPrepared(domain, StringPrep::XMPPNodePrep);
}
domain_ = r.first->second;
r = resourcePrepCache.insert(std::make_pair(resource, std::string()));
if (r.second) {
r.first->second = StringPrep::getPrepared(resource, StringPrep::XMPPResourcePrep);
}
resource_ = r.first->second;
#endif
if (domain_.empty()) {
valid_ = false;
return;
}
}
catch (const std::exception&) {
valid_ = false;
}
}
std::string JID::toString() const {
std::string string;
if (!node_.empty()) {
string += node_ + "@";
}
string += domain_;
if (!isBare()) {
string += "/" + resource_;
}
return string;
}
int JID::compare(const Swift::JID& o, CompareType compareType) const {
if (node_ < o.node_) { return -1; }
if (node_ > o.node_) { return 1; }
if (domain_ < o.domain_) { return -1; }
if (domain_ > o.domain_) { return 1; }
if (compareType == WithResource) {
if (hasResource_ != o.hasResource_) {
return hasResource_ ? 1 : -1;
}
if (resource_ < o.resource_) { return -1; }
if (resource_ > o.resource_) { return 1; }
}
return 0;
}
std::string JID::getEscapedNode(const std::string& node) {
std::string result;
for (std::string::const_iterator i = node.begin(); i != node.end(); ++i) {
if (std::find(escapedChars.begin(), escapedChars.end(), *i) != escapedChars.end()) {
result += getEscaped(*i);
continue;
}
else if (*i == '\\') {
// Check if we have an escaped dissalowed character sequence
std::string::const_iterator innerBegin = i + 1;
if (innerBegin != node.end() && innerBegin + 1 != node.end()) {
std::string::const_iterator innerEnd = innerBegin + 2;
unsigned char value;
if (getEscapeSequenceValue(std::string(innerBegin, innerEnd), value)) {
result += getEscaped(*i);
continue;
}
}
}
result += *i;
}
return result;
//return boost::find_format_all_copy(node, UnescapedCharacterFinder(), UnescapedCharacterFormatter());
}
std::string JID::getUnescapedNode() const {
std::string result;
for (std::string::const_iterator j = node_.begin(); j != node_.end();) {
if (*j == '\\') {
std::string::const_iterator innerEnd = j + 1;
for (size_t i = 0; i < 2 && innerEnd != node_.end(); ++i, ++innerEnd) {
}
unsigned char value;
if (getEscapeSequenceValue(std::string(j + 1, innerEnd), value)) {
result += std::string(reinterpret_cast<const char*>(&value), 1);
j = innerEnd;
continue;
}
}
result += *j;
++j;
}
return result;
//return boost::find_format_all_copy(node_, EscapedCharacterFinder(), EscapedCharacterFormatter());
}