#pragma once #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(); XmppParser(const XmppParser&) = delete; XmppParser& operator=(const XmppParser&) = delete; XmppParser(XmppParser&&) = delete; XmppParser& operator=(XmppParser&&) = delete; public: /** * 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, const size_t 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: /** * Init the XML parser and install the callbacks */ void init_xml_parser(); /** * 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; /** * The root node has no parent, so we keep it here: the XmppParser object * is its owner. */ std::unique_ptr<XmlNode> root; /** * 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; };