summaryrefslogtreecommitdiff
path: root/src/xmpp/xmpp_parser.hpp
blob: ec42f9a326e132508772f51a343804fb6b30abad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#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;
};