From f6fb85ba98fdd6601c4b8323c51c8367ccc4b52e Mon Sep 17 00:00:00 2001
From: Edwin Mons <edwin.mons@isode.com>
Date: Mon, 22 Jul 2019 10:54:55 +0200
Subject: Signal namespace declarations to ParserClients

Prior to calling handleStartElement, the ParserClient
handleNamespaceDeclaration will fire for each namespace declared on the
element.

Test-Information:

Unit tests pass on Debian 9 for both expat and libxml2

Change-Id: Ic42e83aee83edfbb2aa5c971997808eb6e133223

diff --git a/Swiften/Parser/ExpatParser.cpp b/Swiften/Parser/ExpatParser.cpp
index e4e66f2..a50949b 100644
--- a/Swiften/Parser/ExpatParser.cpp
+++ b/Swiften/Parser/ExpatParser.cpp
@@ -64,6 +64,10 @@ static void handleCharacterData(void* parser, const XML_Char* data, int len) {
 static void handleXMLDeclaration(void*, const XML_Char*, const XML_Char*, int) {
 }
 
+static void handleNamespaceDeclaration(void* parser, const XML_Char* prefix, const XML_Char* uri) {
+    static_cast<XMLParser*>(parser)->getClient()->handleNamespaceDeclaration(std::string(prefix ? prefix : ""), std::string(uri ? uri : ""));
+}
+
 static void handleEntityDeclaration(void* parser, const XML_Char*, int, const XML_Char*, int, const XML_Char*, const XML_Char*, const XML_Char*, const XML_Char*) {
     static_cast<ExpatParser*>(parser)->stopParser();
 }
@@ -76,6 +80,7 @@ ExpatParser::ExpatParser(XMLParserClient* client) : XMLParser(client), p(new Pri
     XML_SetCharacterDataHandler(p->parser_, handleCharacterData);
     XML_SetXmlDeclHandler(p->parser_, handleXMLDeclaration);
     XML_SetEntityDeclHandler(p->parser_, handleEntityDeclaration);
+    XML_SetNamespaceDeclHandler(p->parser_, handleNamespaceDeclaration, nullptr);
 }
 
 ExpatParser::~ExpatParser() {
diff --git a/Swiften/Parser/LibXMLParser.cpp b/Swiften/Parser/LibXMLParser.cpp
index c9f3a07..192f44b 100644
--- a/Swiften/Parser/LibXMLParser.cpp
+++ b/Swiften/Parser/LibXMLParser.cpp
@@ -24,7 +24,7 @@ struct LibXMLParser::Private {
     xmlParserCtxtPtr context_;
 };
 
-static void handleStartElement(void* parser, const xmlChar* name, const xmlChar*, const xmlChar* xmlns, int, const xmlChar**, int nbAttributes, int nbDefaulted, const xmlChar ** attributes) {
+static void handleStartElement(void* parser, const xmlChar* name, const xmlChar*, const xmlChar* xmlns, int nbNamespaces, const xmlChar** namespaces, int nbAttributes, int nbDefaulted, const xmlChar ** attributes) {
     AttributeMap attributeValues;
     if (nbDefaulted != 0) {
         // Just because i don't understand what this means yet :-)
@@ -42,6 +42,11 @@ static void handleStartElement(void* parser, const xmlChar* name, const xmlChar*
                 std::string(reinterpret_cast<const char*>(attributes[i+3]),
                     static_cast<size_t>(attributes[i+4]-attributes[i+3])));
     }
+    for (auto i = 0; i < nbNamespaces * 2; i += 2) {
+        const auto prefix = namespaces[i] ? std::string(reinterpret_cast<const char*>(namespaces[i])) : "";
+        const auto uri = std::string(reinterpret_cast<const char*>(namespaces[i + 1]));
+        static_cast<XMLParser*>(parser)->getClient()->handleNamespaceDeclaration(prefix, uri);
+    }
     static_cast<XMLParser*>(parser)->getClient()->handleStartElement(reinterpret_cast<const char*>(name), (xmlns ? reinterpret_cast<const char*>(xmlns) : std::string()), attributeValues);
 }
 
diff --git a/Swiften/Parser/UnitTest/XMLParserTest.cpp b/Swiften/Parser/UnitTest/XMLParserTest.cpp
index 9e9012b..c026b4b 100644
--- a/Swiften/Parser/UnitTest/XMLParserTest.cpp
+++ b/Swiften/Parser/UnitTest/XMLParserTest.cpp
@@ -6,6 +6,7 @@
 
 #include <cppunit/extensions/HelperMacros.h>
 #include <cppunit/extensions/TestFactoryRegistry.h>
+#include <unordered_map>
 #include <vector>
 
 #include <string>
@@ -61,6 +62,9 @@ class XMLParserTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[1].data);
             CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[1].attributes.getEntries().size());
             CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns);
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[1].namespaces.size());
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[1].namespaces.count(""));
+            CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].namespaces[""]);
 
             CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type);
             CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[2].data);
@@ -85,10 +89,13 @@ class XMLParserTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[0].data);
             CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[0].attributes.getEntries().size());
             CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[0].ns);
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].namespaces.size());
+            CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[0].namespaces[""]);
 
             CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
             CPPUNIT_ASSERT_EQUAL(std::string("name"), client_.events[1].data);
             CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns);
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[1].namespaces.size());
 
             CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type);
             CPPUNIT_ASSERT_EQUAL(std::string("Swift"), client_.events[2].data);
@@ -161,6 +168,8 @@ class XMLParserTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
             CPPUNIT_ASSERT_EQUAL(std::string("x"), client_.events[0].data);
             CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[0].ns);
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].namespaces.size());
+            CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[0].namespaces["p"]);
 
             CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
             CPPUNIT_ASSERT_EQUAL(std::string("y"), client_.events[1].data);
@@ -262,6 +271,9 @@ class XMLParserTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.getEntries().size());
             CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName());
             CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace());
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events[0].namespaces.size());
+            CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im"), client_.events[0].namespaces[""]);
+            CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].namespaces["f"]);
         }
 
         void testParse_BillionLaughs() {
@@ -301,6 +313,7 @@ class XMLParserTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
             CPPUNIT_ASSERT_EQUAL(std::string("foo:bar"), client_.events[0].data);
             CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].ns);
+            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[0].namespaces.size());
 
             CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
             CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].data);
@@ -328,14 +341,16 @@ class XMLParserTest : public CppUnit::TestFixture {
     private:
         class Client : public XMLParserClient {
             public:
-                enum Type { StartElement, EndElement, CharacterData };
+                using NamespaceMap = std::unordered_map<std::string /* prefix */, std::string /* uri */>;
+                enum Type { StartElement, EndElement, CharacterData, NamespaceDefined };
                 struct Event {
                     Event(
                             Type type,
                             const std::string& data,
                             const std::string& ns,
-                            const AttributeMap& attributes)
-                                : type(type), data(data), ns(ns), attributes(attributes) {}
+                            const AttributeMap& attributes,
+                            NamespaceMap namespaces = {})
+                                : type(type), data(data), ns(ns), attributes(attributes), namespaces(std::move(namespaces)) {}
                     Event(Type type, const std::string& data, const std::string& ns = std::string())
                                 : type(type), data(data), ns(ns) {}
 
@@ -343,23 +358,29 @@ class XMLParserTest : public CppUnit::TestFixture {
                     std::string data;
                     std::string ns;
                     AttributeMap attributes;
+                    NamespaceMap namespaces;
                 };
 
                 Client() {}
 
-                virtual void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
-                    events.push_back(Event(StartElement, element, ns, attributes));
+                void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) override {
+                    events.push_back(Event(StartElement, element, ns, attributes, std::move(namespaces_)));
                 }
 
-                virtual void handleEndElement(const std::string& element, const std::string& ns) {
+                void handleEndElement(const std::string& element, const std::string& ns) override {
                     events.push_back(Event(EndElement, element, ns));
                 }
 
-                virtual void handleCharacterData(const std::string& data) {
+                void handleCharacterData(const std::string& data) override {
                     events.push_back(Event(CharacterData, data));
                 }
 
+                void handleNamespaceDeclaration(const std::string& prefix, const std::string& uri) override {
+                    namespaces_[prefix] = uri;
+                }
                 std::vector<Event> events;
+            private:
+                NamespaceMap namespaces_;
         } client_;
 };
 
diff --git a/Swiften/Parser/XMLParserClient.cpp b/Swiften/Parser/XMLParserClient.cpp
index 6dc6db6..40be4e8 100644
--- a/Swiften/Parser/XMLParserClient.cpp
+++ b/Swiften/Parser/XMLParserClient.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2019 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -11,5 +11,8 @@ namespace Swift {
 XMLParserClient::~XMLParserClient() {
 }
 
+void XMLParserClient::handleNamespaceDeclaration(const std::string&, const std::string&) {
+}
+
 }
 
diff --git a/Swiften/Parser/XMLParserClient.h b/Swiften/Parser/XMLParserClient.h
index e4346f6..0682320 100644
--- a/Swiften/Parser/XMLParserClient.h
+++ b/Swiften/Parser/XMLParserClient.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2019 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -17,5 +17,13 @@ namespace Swift {
             virtual void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) = 0;
             virtual void handleEndElement(const std::string& element, const std::string& ns) = 0;
             virtual void handleCharacterData(const std::string& data) = 0;
+
+            /**
+             * Signal that a namespace prefix has been declared
+             * This callback might be called multiple times for a single element,
+             * and will trigger before the corresponding \ref handleStartElement
+             * is called.
+             */
+            virtual void handleNamespaceDeclaration(const std::string& prefix, const std::string& uri);
     };
 }
-- 
cgit v0.10.2-6-g49f6