summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/xmpp/xmpp_parser.cpp103
-rw-r--r--src/xmpp/xmpp_parser.hpp107
-rw-r--r--src/xmpp/xmpp_stanza.cpp127
-rw-r--r--src/xmpp/xmpp_stanza.hpp106
5 files changed, 444 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f1eef6..1c21b84 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@ add_library(network STATIC ${source_network})
file(GLOB source_irc
src/irc/*.[hc]pp)
add_library(irc STATIC ${source_irc})
-target_link_libraries(irc network ${CRYPTO++_LIBRARIES})
+target_link_libraries(irc network ${CRYPTO++_LIBRARIES} expatpp)
#
## xmpplib
diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp
new file mode 100644
index 0000000..00714b3
--- /dev/null
+++ b/src/xmpp/xmpp_parser.cpp
@@ -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));
+}
diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp
new file mode 100644
index 0000000..2e83bc3
--- /dev/null
+++ b/src/xmpp/xmpp_parser.hpp
@@ -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
diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp
new file mode 100644
index 0000000..2c98acc
--- /dev/null
+++ b/src/xmpp/xmpp_stanza.cpp
@@ -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];
+}
diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp
new file mode 100644
index 0000000..277b0db
--- /dev/null
+++ b/src/xmpp/xmpp_stanza.hpp
@@ -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