Add an XmppParser, and Stanza classes
Generate events on stanza and stream open/close. Create Stanza and serialize them. Note: XML namespaces are not handled yet.
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ add_library(network STATIC ${source_network})
|
|||||||
file(GLOB source_irc
|
file(GLOB source_irc
|
||||||
src/irc/*.[hc]pp)
|
src/irc/*.[hc]pp)
|
||||||
add_library(irc STATIC ${source_irc})
|
add_library(irc STATIC ${source_irc})
|
||||||
target_link_libraries(irc network ${CRYPTO++_LIBRARIES})
|
target_link_libraries(irc network ${CRYPTO++_LIBRARIES} expatpp)
|
||||||
|
|
||||||
#
|
#
|
||||||
## xmpplib
|
## xmpplib
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#include <xmpp/xmpp_parser.hpp>
|
||||||
|
#include <xmpp/xmpp_stanza.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
XmppParser::XmppParser():
|
||||||
|
level(0),
|
||||||
|
current_node(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
XmppParser::~XmppParser()
|
||||||
|
{
|
||||||
|
if (this->current_node)
|
||||||
|
delete this->current_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmppParser::startElement(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::endElement(const XML_Char* name)
|
||||||
|
{
|
||||||
|
assert(name == this->current_node->get_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::charData(const XML_Char* data, int len)
|
||||||
|
{
|
||||||
|
if (this->current_node->has_children())
|
||||||
|
this->current_node->get_last_child()->set_tail(std::string(data, len));
|
||||||
|
else
|
||||||
|
this->current_node->set_inner(std::string(data, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmppParser::startNamespace(const XML_Char* prefix, const XML_Char* uri)
|
||||||
|
{
|
||||||
|
std::cout << "startNamespace: " << prefix << ":" << uri << std::endl;
|
||||||
|
this->namespaces.emplace(std::make_pair(prefix, uri));
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmppParser::stanza_event(const Stanza& stanza) const
|
||||||
|
{
|
||||||
|
for (const auto& callback: this->stanza_callbacks)
|
||||||
|
callback(stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
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::endNamespace(const XML_Char* coucou)
|
||||||
|
{
|
||||||
|
std::cout << "endNamespace: " << coucou << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#ifndef XMPP_PARSER_INCLUDED
|
||||||
|
# define XMPP_PARSER_INCLUDED
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#include <xmpp/xmpp_stanza.hpp>
|
||||||
|
|
||||||
|
#include <expatpp.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 (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 then is never exhausted as long as each
|
||||||
|
* stanza is reasonnably short.
|
||||||
|
*
|
||||||
|
* 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 expatpp
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit XmppParser();
|
||||||
|
~XmppParser();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* 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 startElement(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 endElement(const XML_Char* name);
|
||||||
|
/**
|
||||||
|
* Some inner or tail data has been parsed
|
||||||
|
*/
|
||||||
|
void charData(const XML_Char* data, int len);
|
||||||
|
/**
|
||||||
|
* TODO use that.
|
||||||
|
*/
|
||||||
|
void startNamespace(const XML_Char* prefix, const XML_Char* uri);
|
||||||
|
/**
|
||||||
|
* TODO and that.
|
||||||
|
*/
|
||||||
|
void endNamespace(const XML_Char* prefix);
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
* TODO: also use that.
|
||||||
|
*/
|
||||||
|
std::stack<std::pair<std::string, std::string>> namespaces;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // XMPP_PARSER_INCLUDED
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
#include <xmpp/xmpp_stanza.hpp>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
XmlNode::XmlNode(const std::string& name, XmlNode* parent):
|
||||||
|
name(name),
|
||||||
|
parent(parent),
|
||||||
|
closed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmlNode::set_inner(const std::string& data)
|
||||||
|
{
|
||||||
|
this->inner = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmlNode::add_child(XmlNode* child)
|
||||||
|
{
|
||||||
|
this->children.push_back(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmlNode::add_child(XmlNode&& child)
|
||||||
|
{
|
||||||
|
XmlNode* new_node = new XmlNode(std::move(child));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& XmlNode::get_name() const
|
||||||
|
{
|
||||||
|
return this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string XmlNode::to_string() const
|
||||||
|
{
|
||||||
|
std::string res("<");
|
||||||
|
res += this->name;
|
||||||
|
for (const auto& it: this->attributes)
|
||||||
|
res += " " + it.first + "='" + it.second + "'";
|
||||||
|
if (this->closed && !this->has_children() && this->inner.empty())
|
||||||
|
res += "/>";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res += ">" + this->inner;
|
||||||
|
for (const auto& child: this->children)
|
||||||
|
res += child->to_string();
|
||||||
|
if (this->closed)
|
||||||
|
{
|
||||||
|
res += "</" + this->name + ">";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += this->tail;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XmlNode::display() const
|
||||||
|
{
|
||||||
|
std::cout << this->to_string() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XmlNode::has_children() const
|
||||||
|
{
|
||||||
|
return !this->children.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& XmlNode::operator[](const std::string& name) const
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto& value = this->attributes.at(name);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
catch (const std::out_of_range& e)
|
||||||
|
{
|
||||||
|
throw AttributeNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string& XmlNode::operator[](const std::string& name)
|
||||||
|
{
|
||||||
|
return this->attributes[name];
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#ifndef XMPP_STANZA_INCLUDED
|
||||||
|
# define XMPP_STANZA_INCLUDED
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <expatpp.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raised on operator[] when the attribute does not exist
|
||||||
|
*/
|
||||||
|
class AttributeNotFound: public std::exception
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent an XML node. It has
|
||||||
|
* - A parent XML node (in the case of the first-level nodes, the parent is
|
||||||
|
nullptr)
|
||||||
|
* - zero, one or more children XML nodes
|
||||||
|
* - A name
|
||||||
|
* - attributes
|
||||||
|
* - inner data (inside the node)
|
||||||
|
* - tail data (just after the node)
|
||||||
|
*/
|
||||||
|
class XmlNode
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit XmlNode(const std::string& name, XmlNode* parent);
|
||||||
|
explicit XmlNode(const std::string& name);
|
||||||
|
XmlNode(XmlNode&& node):
|
||||||
|
name(std::move(node.name)),
|
||||||
|
parent(std::move(node.parent)),
|
||||||
|
closed(std::move(node.closed)),
|
||||||
|
attributes(std::move(node.attributes)),
|
||||||
|
children(std::move(node.children)),
|
||||||
|
inner(std::move(node.inner)),
|
||||||
|
tail(std::move(node.tail))
|
||||||
|
{
|
||||||
|
node.parent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~XmlNode();
|
||||||
|
|
||||||
|
void delete_all_children();
|
||||||
|
void set_attribute(const std::string& name, const std::string& value);
|
||||||
|
/**
|
||||||
|
* Set the content of the tail, that is the text just after this node
|
||||||
|
*/
|
||||||
|
void set_tail(const std::string& data);
|
||||||
|
/**
|
||||||
|
* Set the content of the inner, that is the text inside this node
|
||||||
|
*/
|
||||||
|
void set_inner(const std::string& data);
|
||||||
|
void add_child(XmlNode* child);
|
||||||
|
void add_child(XmlNode&& child);
|
||||||
|
XmlNode* get_last_child() const;
|
||||||
|
/**
|
||||||
|
* Mark this node as closed, nothing else
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
XmlNode* get_parent() const;
|
||||||
|
const std::string& get_name() const;
|
||||||
|
/**
|
||||||
|
* Serialize the stanza into a string
|
||||||
|
*/
|
||||||
|
std::string to_string() const;
|
||||||
|
void display() const;
|
||||||
|
/**
|
||||||
|
* Whether or not this node has at least one child (if not, this is a leaf
|
||||||
|
* node)
|
||||||
|
*/
|
||||||
|
bool has_children() const;
|
||||||
|
/**
|
||||||
|
* Gets the value for the given attribute, raises AttributeNotFound if the
|
||||||
|
* node as no such attribute.
|
||||||
|
*/
|
||||||
|
const std::string& operator[](const std::string& name) const;
|
||||||
|
/**
|
||||||
|
* Use this to set an attribute's value, like node["id"] = "12";
|
||||||
|
*/
|
||||||
|
std::string& operator[](const std::string& name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name;
|
||||||
|
XmlNode* parent;
|
||||||
|
bool closed;
|
||||||
|
std::unordered_map<std::string, std::string> attributes;
|
||||||
|
std::vector<XmlNode*> children;
|
||||||
|
std::string inner;
|
||||||
|
std::string tail;
|
||||||
|
|
||||||
|
XmlNode(const XmlNode&) = delete;
|
||||||
|
XmlNode& operator=(const XmlNode&) = delete;
|
||||||
|
XmlNode& operator=(XmlNode&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An XMPP stanza is just an XML node of level 2 in the XMPP document (the
|
||||||
|
* level 1 ones are the <stream::stream/>, and the ones about 2 are just the
|
||||||
|
* content of the stanzas)
|
||||||
|
*/
|
||||||
|
typedef XmlNode Stanza;
|
||||||
|
|
||||||
|
#endif // XMPP_STANZA_INCLUDED
|
||||||
Reference in New Issue
Block a user