buffet: Add proper XML parsing for XMPP streams

For large command notifications, a single XMPP stanza can be split
into a number of TCP packets. In order to handle large stanzas and
in order to help with implementing TLS support for XMPP, added an
expat-based XML parser on top of XMPP stream, to make sure that
the stanzas are processed when all the data for a complete XML tag
has arrived.

BUG=brillo:458
TEST=`FEATURES=test emerge-link buffet`

Change-Id: I560f40dafb31c6e6b9e645d232453338ee4fbbef
Reviewed-on: https://chromium-review.googlesource.com/271592
Trybot-Ready: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/notification/xmpp_stream_parser.h b/buffet/notification/xmpp_stream_parser.h
new file mode 100644
index 0000000..6bb39c7
--- /dev/null
+++ b/buffet/notification/xmpp_stream_parser.h
@@ -0,0 +1,86 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+#define BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+
+#include <expat.h>
+
+#include <map>
+#include <memory>
+#include <stack>
+#include <string>
+
+#include <base/macros.h>
+
+namespace buffet {
+
+class XmlNode;
+
+// A simple XML stream parser. As the XML data is being read from a data source
+// (for example, a socket), XmppStreamParser::ParseData() should be called.
+// This method parses the provided XML data chunk and if it finds complete
+// XML elements, it will call internal OnOpenElement(), OnCloseElement() and
+// OnCharData() member functions. These will track the element nesting level.
+// When a top-level element starts, the parser will call Delegate::OnStreamStart
+// method. Once this happens, every complete XML element (including its children
+// if they are present) will trigger Delegate::OnStanze() callback.
+// Finally, when top-level element is closed, Delegate::OnStreamEnd() is called.
+// This class is specifically tailored to XMPP streams which look like this:
+// B:  <stream:stream to='example.com' xmlns='jabber:client' version='1.0'>
+// S:    <presence><show/></presence>
+// S:    <message to='foo'><body/></message>
+// S:    <iq to='bar'><query/></iq>
+// S:    ...
+// E:  </stream:stream>
+// Here, "B:" will trigger OnStreamStart(), "S:" will result in OnStanza() and
+// "E:" will result in OnStreamEnd().
+class XmppStreamParser final {
+ public:
+  // Delegate interface that interested parties implement to receive
+  // notifications of stream opening/closing and on new stanzas arriving.
+  class Delegate {
+   public:
+    virtual void OnStreamStart(
+        const std::string& node_name,
+        std::map<std::string, std::string> attributes) = 0;
+    virtual void OnStreamEnd(const std::string& node_name) = 0;
+    virtual void OnStanza(std::unique_ptr<XmlNode> stanza) = 0;
+  };
+
+  explicit XmppStreamParser(Delegate* delegate);
+  ~XmppStreamParser();
+
+  // Parses additional XML data received from an input stream.
+  void ParseData(const std::string& data);
+
+  // Resets the parser to expect the top-level stream node again.
+  void Reset();
+
+ private:
+  // Raw expat callbacks.
+  static void HandleElementStart(void* user_data,
+                                 const XML_Char* element,
+                                 const XML_Char** attr);
+  static void HandleElementEnd(void* user_data, const XML_Char* element);
+  static void HandleCharData(void* user_data, const char* content, int length);
+
+  // Reinterpreted callbacks from expat with some data pre-processed.
+  void OnOpenElement(const std::string& node_name,
+                     std::map<std::string, std::string> attributes);
+  void OnCloseElement(const std::string& node_name);
+  void OnCharData(const std::string& text);
+
+  Delegate* delegate_;
+  XML_Parser parser_{nullptr};
+  bool started_{false};
+  std::stack<std::unique_ptr<XmlNode>> node_stack_;
+
+  DISALLOW_COPY_AND_ASSIGN(XmppStreamParser);
+};
+
+}  // namespace buffet
+
+#endif  // BUFFET_NOTIFICATION_XMPP_STREAM_PARSER_H_
+