Commit f0e07bea authored by louiz’'s avatar louiz’

Forgot to remove some XMPP files that are now in louloulibs instead

parent 649ebaf1
......@@ -76,9 +76,6 @@ include_directories(${ICONV_INCLUDE_DIRS})
include_directories(${LIBUUID_INCLUDE_DIRS})
# If they are found in louloulibs CMakeLists.txt, we inherite these values
if(LIBIDN_FOUND)
include_directories(${LIBIDN_INCLUDE_DIRS})
endif()
if(SYSTEMD_FOUND)
include_directories(${SYSTEMD_INCLUDE_DIRS})
endif()
......
#include <xmpp/jid.hpp>
#include <cstring>
#include <map>
#include <louloulibs.h>
#ifdef LIBIDN_FOUND
#include <stringprep.h>
#endif
#include <logger/logger.hpp>
Jid::Jid(const std::string& jid)
{
std::string::size_type slash = jid.find('/');
if (slash != std::string::npos)
{
this->resource = jid.substr(slash + 1);
}
std::string::size_type at = jid.find('@');
if (at != std::string::npos && at < slash)
{
this->local = jid.substr(0, at);
at++;
}
else
at = 0;
this->domain = jid.substr(at, slash - at);
}
#include <iostream>
static constexpr size_t max_jid_part_len = 1023;
std::string jidprep(const std::string& original)
{
#ifdef LIBIDN_FOUND
using CacheType = std::map<std::string, std::string>;
static CacheType cache;
std::pair<CacheType::iterator, bool> cached = cache.insert({original, {}});
if (std::get<1>(cached) == false)
{ // Insertion failed: the result is already in the cache, return it
return std::get<0>(cached)->second;
}
const std::string error_msg("Failed to convert " + original + " into a valid JID:");
Jid jid(original);
char local[max_jid_part_len] = {};
memcpy(local, jid.local.data(), jid.local.size());
Stringprep_rc rc = static_cast<Stringprep_rc>(::stringprep(local, max_jid_part_len,
static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_nodeprep));
if (rc != STRINGPREP_OK)
{
log_error(error_msg + stringprep_strerror(rc));
return "";
}
char domain[max_jid_part_len] = {};
memcpy(domain, jid.domain.data(), jid.domain.size());
rc = static_cast<Stringprep_rc>(::stringprep(domain, max_jid_part_len,
static_cast<Stringprep_profile_flags>(0), stringprep_nameprep));
if (rc != STRINGPREP_OK)
{
log_error(error_msg + stringprep_strerror(rc));
return "";
}
// If there is no resource, stop here
if (jid.resource.empty())
{
std::get<0>(cached)->second = std::string(local) + "@" + domain;
return std::get<0>(cached)->second;
}
// Otherwise, also process the resource part
char resource[max_jid_part_len] = {};
memcpy(resource, jid.resource.data(), jid.resource.size());
rc = static_cast<Stringprep_rc>(::stringprep(resource, max_jid_part_len,
static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_resourceprep));
if (rc != STRINGPREP_OK)
{
log_error(error_msg + stringprep_strerror(rc));
return "";
}
std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource;
return std::get<0>(cached)->second;
#else
(void)original;
return "";
#endif
}
#ifndef JID_INCLUDED
# define JID_INCLUDED
#include <string>
/**
* Parse a JID into its different subart
*/
class Jid
{
public:
explicit Jid(const std::string& jid);
std::string domain;
std::string local;
std::string resource;
private:
Jid(const Jid&) = delete;
Jid(Jid&&) = delete;
Jid& operator=(const Jid&) = delete;
Jid& operator=(Jid&&) = delete;
};
/**
* Prepare the given UTF-8 string according to the XMPP node stringprep
* identifier profile. This is used to send properly-formed JID to the XMPP
* server.
*
* If the stringprep library is not found, we return an empty string. When
* this function is used, the result must always be checked for an empty
* value, and if this is the case it must not be used as a JID.
*/
std::string jidprep(const std::string& original);
#endif // JID_INCLUDED
#include <xmpp/xmpp_parser.hpp>
#include <xmpp/xmpp_stanza.hpp>
#include <logger/logger.hpp>
/**
* Expat handlers. Called by the Expat library, never by ourself.
* They just forward the call to the XmppParser corresponding methods.
*/
static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts)
{
static_cast<XmppParser*>(user_data)->start_element(name, atts);
}
static void end_element_handler(void* user_data, const XML_Char* name)
{
static_cast<XmppParser*>(user_data)->end_element(name);
}
static void character_data_handler(void *user_data, const XML_Char *s, int len)
{
static_cast<XmppParser*>(user_data)->char_data(s, len);
}
/**
* XmppParser class
*/
XmppParser::XmppParser():
level(0),
current_node(nullptr)
{
this->init_xml_parser();
}
void XmppParser::init_xml_parser()
{
// Create the expat parser
this->parser = XML_ParserCreateNS("UTF-8", ':');
XML_SetUserData(this->parser, static_cast<void*>(this));
// Install Expat handlers
XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler);
XML_SetCharacterDataHandler(this->parser, &character_data_handler);
}
XmppParser::~XmppParser()
{
if (this->current_node)
delete this->current_node;
XML_ParserFree(this->parser);
}
int XmppParser::feed(const char* data, const int len, const bool is_final)
{
int res = XML_Parse(this->parser, data, len, is_final);
if (res == XML_STATUS_ERROR &&
(XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED))
log_error("Xml_Parse encountered an error: " <<
XML_ErrorString(XML_GetErrorCode(this->parser)))
return res;
}
int XmppParser::parse(const int len, const bool is_final)
{
int res = XML_ParseBuffer(this->parser, len, is_final);
if (res == XML_STATUS_ERROR)
log_error("Xml_Parsebuffer encountered an error: " <<
XML_ErrorString(XML_GetErrorCode(this->parser)));
return res;
}
void XmppParser::reset()
{
XML_ParserFree(this->parser);
this->init_xml_parser();
if (this->current_node)
delete this->current_node;
this->current_node = nullptr;
this->level = 0;
}
void* XmppParser::get_buffer(const size_t size) const
{
return XML_GetBuffer(this->parser, static_cast<int>(size));
}
void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute)
{
level++;
XmlNode* new_node = new XmlNode(name, this->current_node);
if (this->current_node)
this->current_node->add_child(new_node);
this->current_node = new_node;
for (size_t i = 0; attribute[i]; i += 2)
this->current_node->set_attribute(attribute[i], attribute[i+1]);
if (this->level == 1)
this->stream_open_event(*this->current_node);
}
void XmppParser::end_element(const XML_Char* name)
{
(void)name;
level--;
this->current_node->close();
if (level == 1)
{
this->stanza_event(*this->current_node);
}
if (level == 0)
{
this->stream_close_event(*this->current_node);
delete this->current_node;
this->current_node = nullptr;
}
else
this->current_node = this->current_node->get_parent();
if (level == 1)
this->current_node->delete_all_children();
}
void XmppParser::char_data(const XML_Char* data, int len)
{
if (this->current_node->has_children())
this->current_node->get_last_child()->add_to_tail(std::string(data, len));
else
this->current_node->add_to_inner(std::string(data, len));
}
void XmppParser::stanza_event(const Stanza& stanza) const
{
for (const auto& callback: this->stanza_callbacks)
{
try {
callback(stanza);
} catch (const std::exception& e) {
log_debug("Unhandled exception: " << e.what());
}
}
}
void XmppParser::stream_open_event(const XmlNode& node) const
{
for (const auto& callback: this->stream_open_callbacks)
callback(node);
}
void XmppParser::stream_close_event(const XmlNode& node) const
{
for (const auto& callback: this->stream_close_callbacks)
callback(node);
}
void XmppParser::add_stanza_callback(std::function<void(const Stanza&)>&& callback)
{
this->stanza_callbacks.emplace_back(std::move(callback));
}
void XmppParser::add_stream_open_callback(std::function<void(const XmlNode&)>&& callback)
{
this->stream_open_callbacks.emplace_back(std::move(callback));
}
void XmppParser::add_stream_close_callback(std::function<void(const XmlNode&)>&& callback)
{
this->stream_close_callbacks.emplace_back(std::move(callback));
}
#ifndef XMPP_PARSER_INCLUDED
# define XMPP_PARSER_INCLUDED
#include <xmpp/xmpp_stanza.hpp>
#include <functional>
#include <expat.h>
/**
* A SAX XML parser that builds XML nodes and spawns events when a complete
* stanza is received (an element of level 2), or when the document is
* opened/closed (an element of level 1)
*
* After a stanza_event has been spawned, we delete the whole stanza. This
* means that even with a very long document (in XMPP the document is
* potentially infinite), the memory is never exhausted as long as each
* stanza is reasonnably short.
*
* The element names generated by expat contain the namespace of the
* element, a colon (':') and then the actual name of the element. To get
* an element "x" with a namespace of "http://jabber.org/protocol/muc", you
* just look for an XmlNode named "http://jabber.org/protocol/muc:x"
*
* TODO: enforce the size-limit for the stanza (limit the number of childs
* it can contain). For example forbid the parser going further than level
* 20 (arbitrary number here), and each XML node to have more than 15 childs
* (arbitrary number again).
*/
class XmppParser
{
public:
explicit XmppParser();
~XmppParser();
public:
/**
* Init the XML parser and install the callbacks
*/
void init_xml_parser();
/**
* Feed the parser with some XML data
*/
int feed(const char* data, const int len, const bool is_final);
/**
* Parse the data placed in the parser buffer
*/
int parse(const int size, const bool is_final);
/**
* Reset the parser, so it can be used from scratch afterward
*/
void reset();
/**
* Get a buffer provided by the xml parser.
*/
void* get_buffer(const size_t size) const;
/**
* Add one callback for the various events that this parser can spawn.
*/
void add_stanza_callback(std::function<void(const Stanza&)>&& callback);
void add_stream_open_callback(std::function<void(const XmlNode&)>&& callback);
void add_stream_close_callback(std::function<void(const XmlNode&)>&& callback);
/**
* Called when a new XML element has been opened. We instanciate a new
* XmlNode and set it as our current node. The parent of this new node is
* the previous "current" node. We have all the element's attributes in
* this event.
*
* We spawn a stream_event with this node if this is a level-1 element.
*/
void start_element(const XML_Char* name, const XML_Char** attribute);
/**
* Called when an XML element has been closed. We close the current_node,
* set our current_node as the parent of the current_node, and if that was
* a level-2 element we spawn a stanza_event with this node.
*
* And we then delete the stanza (and everything under it, its children,
* attribute, etc).
*/
void end_element(const XML_Char* name);
/**
* Some inner or tail data has been parsed
*/
void char_data(const XML_Char* data, int len);
/**
* Calls all the stanza_callbacks one by one.
*/
void stanza_event(const Stanza& stanza) const;
/**
* Calls all the stream_open_callbacks one by one. Note: the passed node is not
* closed yet.
*/
void stream_open_event(const XmlNode& node) const;
/**
* Calls all the stream_close_callbacks one by one.
*/
void stream_close_event(const XmlNode& node) const;
private:
/**
* Expat structure.
*/
XML_Parser parser;
/**
* The current depth in the XML document
*/
size_t level;
/**
* The deepest XML node opened but not yet closed (to which we are adding
* new children, inner or tail)
*/
XmlNode* current_node;
/**
* A list of callbacks to be called on an *_event, receiving the
* concerned Stanza/XmlNode.
*/
std::vector<std::function<void(const Stanza&)>> stanza_callbacks;
std::vector<std::function<void(const XmlNode&)>> stream_open_callbacks;
std::vector<std::function<void(const XmlNode&)>> stream_close_callbacks;
XmppParser(const XmppParser&) = delete;
XmppParser& operator=(const XmppParser&) = delete;
};
#endif // XMPP_PARSER_INCLUDED
#include <xmpp/xmpp_stanza.hpp>
#include <utils/encoding.hpp>
#include <utils/split.hpp>
#include <stdexcept>
#include <iostream>
#include <string.h>
std::string xml_escape(const std::string& data)
{
std::string res;
res.reserve(data.size());
for (size_t pos = 0; pos != data.size(); ++pos)
{
switch(data[pos])
{
case '&':
res += "&amp;";
break;
case '<':
res += "&lt;";
break;
case '>':
res += "&gt;";
break;
case '\"':
res += "&quot;";
break;
case '\'':
res += "&apos;";
break;
default:
res += data[pos];
break;
}
}
return res;
}
std::string xml_unescape(const std::string& data)
{
std::string res;
res.reserve(data.size());
const char* str = data.c_str();
while (str && *str && static_cast<size_t>(str - data.c_str()) < data.size())
{
if (*str == '&')
{
if (strncmp(str+1, "amp;", 4) == 0)
{
res += "&";
str += 4;
}
else if (strncmp(str+1, "lt;", 3) == 0)
{
res += "<";
str += 3;
}
else if (strncmp(str+1, "gt;", 3) == 0)
{
res += ">";
str += 3;
}
else if (strncmp(str+1, "quot;", 5) == 0)
{
res += "\"";
str += 5;
}
else if (strncmp(str+1, "apos;", 5) == 0)
{
res += "'";
str += 5;
}
else
res += "&";
}
else
res += *str;
str++;
}
return res;
}
XmlNode::XmlNode(const std::string& name, XmlNode* parent):
parent(parent),
closed(false)
{
// split the namespace and the name
auto n = name.rfind(":");
if (n == std::string::npos)
this->name = name;
else
{
this->name = name.substr(n+1);
this->attributes["xmlns"] = name.substr(0, n);
}
}
XmlNode::XmlNode(const std::string& name):
XmlNode(name, nullptr)
{
}
XmlNode::~XmlNode()
{
this->delete_all_children();
}
void XmlNode::delete_all_children()
{
for (auto& child: this->children)
{
delete child;
}
this->children.clear();
}
void XmlNode::set_attribute(const std::string& name, const std::string& value)
{
this->attributes[name] = value;
}
void XmlNode::set_tail(const std::string& data)
{
this->tail = xml_escape(data);
}
void XmlNode::add_to_tail(const std::string& data)
{
this->tail += xml_escape(data);
}
void XmlNode::set_inner(const std::string& data)
{
this->inner = xml_escape(data);
}
void XmlNode::add_to_inner(const std::string& data)
{
this->inner += xml_escape(data);
}
std::string XmlNode::get_inner() const
{
return xml_unescape(this->inner);
}
std::string XmlNode::get_tail() const
{
return xml_unescape(this->tail);
}
XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const
{
for (auto& child: this->children)
{
if (child->name == name && child->get_tag("xmlns") == xmlns)
return child;
}
return nullptr;
}
std::vector<XmlNode*> XmlNode::get_children(const std::string& name, const std::string& xmlns) const
{
std::vector<XmlNode*> res;
for (auto& child: this->children)
{
if (child->name == name && child->get_tag("xmlns") == xmlns)
res.push_back(child);
}
return res;
}
XmlNode* XmlNode::add_child(XmlNode* child)
{
child->parent = this;
this->children.push_back(child);
return child;
}
XmlNode* XmlNode::add_child(XmlNode&& child)
{
XmlNode* new_node = new XmlNode(std::move(child));
return this->add_child(new_node);
}
XmlNode* XmlNode::get_last_child() const
{
return this->children.back();
}
void XmlNode::close()
{
if (this->closed)
throw std::runtime_error("Closing an already closed XmlNode");
this->closed = true;
}
XmlNode* XmlNode::get_parent() const
{
return this->parent;