From 860ef54501e8c32d91160f798c8f9ecf811f2501 Mon Sep 17 00:00:00 2001
From: Peter Burgess <pete.burgess@isode.com>
Date: Mon, 26 Mar 2018 12:17:24 +0100
Subject: Add new ReferencePayload element class, parser and serializer

Added a new element object ReferencePayload, and created the parser
and serializer to handle this element. Currently no functionality
to send references directly in swift, nor to render their contents.

Test-Information:
Unit tests written and passed for serializer and parser, testing
various types of valid and invalid references, and testing
references with embedded payloads.

Change-Id: I81fd5d9e020fac1729640f297705806af97f6388

diff --git a/Sluift/Examples/SendReference.lua b/Sluift/Examples/SendReference.lua
index b705172..2e60182 100644
--- a/Sluift/Examples/SendReference.lua
+++ b/Sluift/Examples/SendReference.lua
@@ -37,7 +37,7 @@ function get_password(prompt)
     if stty_ret then
         io.write("\027[m")
     else
-        os.execute("stty sane")
+        os.execute("stty echo")
     end
     io.write("\n");
     if ok then
@@ -55,6 +55,7 @@ datauri = #arg > 2 and arg[3] or DEFAULT_URI
 
 sluift.debug = 1
 sluift.with(sluift.new_client(jid, password), function()
+    options = { host=os.getenv("XMPP_HOST"), port=os.getenv("XMPP_PORT") }
     connect(options)
     reference = {
         ['_type'] = 'dom',
@@ -63,6 +64,9 @@ sluift.with(sluift.new_client(jid, password), function()
         ['attributes'] = {
             { ['name'] = 'type', ['value'] = 'data' },
             { ['name'] = 'uri', ['value'] = datauri },
+            { ['name'] = 'mimeType', ['value'] = arg[4] },
+            { ['name'] = 'begin', ['value'] = arg[5] },
+            { ['name'] = 'end', ['value'] = arg[6] }
         },
     }
     send_message{to=tojid, body="Check out this data!", payloads={reference}}
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp
index f3d23e7..6a7018e 100644
--- a/Swift/QtUI/QtWebKitChatView.cpp
+++ b/Swift/QtUI/QtWebKitChatView.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2017 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
diff --git a/Swiften/Elements/DiscoInfo.cpp b/Swiften/Elements/DiscoInfo.cpp
index 11f0623..701ed40 100644
--- a/Swiften/Elements/DiscoInfo.cpp
+++ b/Swiften/Elements/DiscoInfo.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2015 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -26,6 +26,7 @@ const std::string DiscoInfo::MessageDeliveryReceiptsFeature = std::string("urn:x
 const std::string DiscoInfo::WhiteboardFeature = std::string("http://swift.im/whiteboard");
 const std::string DiscoInfo::BlockingCommandFeature = std::string("urn:xmpp:blocking");
 const std::string DiscoInfo::MessageCarbonsFeature = std::string("urn:xmpp:carbons:2");
+const std::string DiscoInfo::ReferencesFeature = std::string("urn:xmpp:references:0");
 
 bool DiscoInfo::Identity::operator<(const Identity& other) const {
     if (category_ == other.category_) {
diff --git a/Swiften/Elements/DiscoInfo.h b/Swiften/Elements/DiscoInfo.h
index c8009ee..713eaba 100644
--- a/Swiften/Elements/DiscoInfo.h
+++ b/Swiften/Elements/DiscoInfo.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2016 Isode Limited.
+ * Copyright (c) 2010-2018 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
@@ -37,6 +37,7 @@ namespace Swift {
             static const std::string WhiteboardFeature;
             static const std::string BlockingCommandFeature;
             static const std::string MessageCarbonsFeature;
+            static const std::string ReferencesFeature;
 
             class Identity {
                 public:
diff --git a/Swiften/Elements/ReferencePayload.cpp b/Swiften/Elements/ReferencePayload.cpp
new file mode 100644
index 0000000..288f28f
--- /dev/null
+++ b/Swiften/Elements/ReferencePayload.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Elements/ReferencePayload.h>
+
+namespace Swift {
+
+ReferencePayload::ReferencePayload()
+    : type_(Type::Data) {
+}
+
+const ReferencePayload::Type& ReferencePayload::getType() const {
+    return type_;
+}
+
+void ReferencePayload::setType(const ReferencePayload::Type& type) {
+    type_ = type;
+}
+
+const boost::optional<std::string>& ReferencePayload::getUri() const {
+    return uri_;
+}
+
+void ReferencePayload::setUri(const boost::optional<std::string>& uri) {
+    uri_ = uri;
+}
+
+const boost::optional<std::string>& ReferencePayload::getBegin() const {
+    return begin_;
+}
+
+void ReferencePayload::setBegin(const boost::optional<std::string>& begin) {
+    begin_ = begin;
+}
+
+const boost::optional<std::string>& ReferencePayload::getEnd() const {
+    return end_;
+}
+
+void ReferencePayload::setEnd(const boost::optional<std::string>& end) {
+    end_ = end;
+}
+
+const boost::optional<std::string>& ReferencePayload::getAnchor() const {
+    return anchor_;
+}
+
+void ReferencePayload::setAnchor(const boost::optional<std::string>& anchor) {
+    anchor_ = anchor;
+}
+
+const std::vector<std::shared_ptr<Payload>>& ReferencePayload::getPayloads() const {
+    return payloads_;
+}
+
+void ReferencePayload::addPayload(const std::shared_ptr<Payload>& payload) {
+    payloads_.push_back(payload);
+}
+
+}
diff --git a/Swiften/Elements/ReferencePayload.h b/Swiften/Elements/ReferencePayload.h
new file mode 100644
index 0000000..b9a394e
--- /dev/null
+++ b/Swiften/Elements/ReferencePayload.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <boost/optional.hpp>
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/Payload.h>
+
+namespace Swift {
+    /**
+     * reference from XEP-0372
+     */
+    class SWIFTEN_API ReferencePayload : public Payload {
+
+    public:
+
+        typedef std::shared_ptr<ReferencePayload> ref;
+
+        enum class Type {
+            Data,
+            Mention,
+            PubSub,
+            Unknown
+        };
+
+        ReferencePayload();
+
+        const Type& getType() const;
+        const boost::optional<std::string>& getUri() const;
+        const boost::optional<std::string>& getBegin() const;
+        const boost::optional<std::string>& getEnd() const;
+        const boost::optional<std::string>& getAnchor() const;
+
+        const std::vector<std::shared_ptr<Payload>>& getPayloads() const;
+
+        void setType(const Type& type);
+        void setUri(const boost::optional<std::string>& uri);
+        void setBegin(const boost::optional<std::string>& begin);
+        void setEnd(const boost::optional<std::string>& end);
+        void setAnchor(const boost::optional<std::string>& anchor);
+
+        void addPayload(const std::shared_ptr<Payload>& payload);
+
+    private:
+
+        Type type_;
+        boost::optional<std::string> uri_;
+        boost::optional<std::string> begin_;
+        boost::optional<std::string> end_;
+        boost::optional<std::string> anchor_;
+
+        std::vector<std::shared_ptr<Payload>> payloads_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 43c4ebb..9e56b63 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -75,6 +75,7 @@
 #include <Swiften/Parser/PayloadParsers/PubSubOwnerPubSubParser.h>
 #include <Swiften/Parser/PayloadParsers/PubSubParser.h>
 #include <Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/ReferencePayloadParser.h>
 #include <Swiften/Parser/PayloadParsers/ReplaceParser.h>
 #include <Swiften/Parser/PayloadParsers/ResourceBindParser.h>
 #include <Swiften/Parser/PayloadParsers/ResultSetParser.h>
@@ -187,6 +188,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
     factories_.push_back(std::make_shared<GenericPayloadParserFactory2<CarbonsSentParser> >("sent", "urn:xmpp:carbons:2", this));
     factories_.push_back(std::make_shared<GenericPayloadParserFactory<CarbonsPrivateParser> >("private", "urn:xmpp:carbons:2"));
     factories_.push_back(std::make_shared<MIXJoinParserFactory>());
+    factories_.push_back(std::make_shared<GenericPayloadParserFactory2<ReferencePayloadParser> >("reference", "urn:xmpp:reference:0", this));
 
     for (auto& factory : factories_) {
         addFactory(factory.get());
diff --git a/Swiften/Parser/PayloadParsers/ReferencePayloadParser.cpp b/Swiften/Parser/PayloadParsers/ReferencePayloadParser.cpp
new file mode 100644
index 0000000..a337a29
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ReferencePayloadParser.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/ReferencePayloadParser.h>
+
+#include <cassert>
+#include <iostream>
+
+#include <Swiften/Parser/PayloadParserFactory.h>
+#include <Swiften/Parser/PayloadParserFactoryCollection.h>
+
+namespace Swift {
+
+ReferencePayloadParser::ReferencePayloadParser(PayloadParserFactoryCollection* factories) : factories_(factories) {
+}
+
+ReferencePayload::Type ReferencePayloadParser::getTypeFromString(const std::string& typeString) const {
+    if (typeString == "data") {
+        return ReferencePayload::Type::Data;
+    }
+    else if (typeString == "mention") {
+        return ReferencePayload::Type::Mention;
+    }
+    else if (typeString == "pubsub") {
+        return ReferencePayload::Type::PubSub;
+    }
+    else {
+        return ReferencePayload::Type::Unknown;
+    }
+}
+
+void ReferencePayloadParser::handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
+    if (level_ == topLevel_) {
+        if (element == "reference") {
+            getPayloadInternal()->setType(getTypeFromString(attributes.getAttribute("type")));
+            getPayloadInternal()->setUri(attributes.getAttributeValue("uri"));
+            getPayloadInternal()->setBegin(attributes.getAttributeValue("begin"));
+            getPayloadInternal()->setEnd(attributes.getAttributeValue("end"));
+            getPayloadInternal()->setAnchor(attributes.getAttributeValue("anchor"));
+        }
+    }
+    else if (level_ == payloadLevel_)   {
+        PayloadParserFactory* payloadParserFactory = factories_->getPayloadParserFactory(element, ns, attributes);
+        if (payloadParserFactory) {
+            currentPayloadParser_.reset(payloadParserFactory->createPayloadParser());
+        }
+    }
+
+    if (level_ >= payloadLevel_ && currentPayloadParser_) {
+        currentPayloadParser_->handleStartElement(element, ns, attributes);
+    }
+
+    ++level_;
+}
+
+void ReferencePayloadParser::handleEndElement(const std::string& element, const std::string& ns) {
+    --level_;
+    if (currentPayloadParser_) {
+        if (level_ >= payloadLevel_) {
+            currentPayloadParser_->handleEndElement(element, ns);
+        }
+
+        if (level_ == payloadLevel_) {
+            getPayloadInternal()->addPayload(currentPayloadParser_->getPayload());
+            currentPayloadParser_.reset();
+        }
+    }
+}
+
+void ReferencePayloadParser::handleCharacterData(const std::string& data) {
+    if (level_ > payloadLevel_ && currentPayloadParser_) {
+        currentPayloadParser_->handleCharacterData(data);
+    }
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/ReferencePayloadParser.h b/Swiften/Parser/PayloadParsers/ReferencePayloadParser.h
new file mode 100644
index 0000000..3afd181
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/ReferencePayloadParser.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/ReferencePayload.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+
+    class PayloadParserFactoryCollection;
+
+    class SWIFTEN_API ReferencePayloadParser : public GenericPayloadParser<ReferencePayload> {
+        public:
+
+            ReferencePayloadParser(PayloadParserFactoryCollection* factories);
+
+            virtual void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes);
+            virtual void handleEndElement(const std::string& element, const std::string& ns);
+            virtual void handleCharacterData(const std::string& data);
+
+        private:
+
+            ReferencePayload::Type getTypeFromString(const std::string& typeString) const;
+            int level_ = 0;
+            const int topLevel_ = 0;
+            const int payloadLevel_ = 1;
+            PayloadParserFactoryCollection* factories_;
+            std::shared_ptr<PayloadParser> currentPayloadParser_;
+    };
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/ReferencePayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/ReferencePayloadParserTest.cpp
new file mode 100644
index 0000000..ca7b280
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/UnitTest/ReferencePayloadParserTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Elements/Body.h>
+#include <Swiften/Elements/Delay.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Parser/PayloadParsers/ReferencePayloadParser.h>
+#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h>
+
+using namespace Swift;
+
+TEST(ReferencePayloadParserTest, testParse) {
+    PayloadsParserTester parser;
+
+    ASSERT_TRUE(parser.parse(
+        "<reference xmlns='urn:xmpp:reference:0' "
+            "type='data' "
+            "uri='https://www.example.com/mindBlowingImage.jpeg' "
+            "begin='11' "
+            "end='22' "
+            "anchor='xmpp:data@localhost.example.test'>"
+        "</reference>"));
+
+    auto payload = std::dynamic_pointer_cast<ReferencePayload>(parser.getPayload());
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(payload->getType()));
+    ASSERT_EQ(std::string("https://www.example.com/mindBlowingImage.jpeg"), *payload->getUri());
+    ASSERT_EQ(std::string("11"), *payload->getBegin());
+    ASSERT_EQ(std::string("22"), *payload->getEnd());
+    ASSERT_EQ(std::string("xmpp:data@localhost.example.test"), *payload->getAnchor());
+}
+
+TEST(ReferencePayloadParserTest, testParseNoType) {
+    PayloadsParserTester parser;
+
+    ASSERT_TRUE(parser.parse(
+        "<reference xmlns='urn:xmpp:reference:0' "
+            "uri='https://www.example.com/mindBlowingImage.jpeg' "
+            "begin='11' "
+            "end='22' "
+            "anchor='xmpp:data@localhost.example.test'>"
+        "</reference>"));
+
+    auto payload = std::dynamic_pointer_cast<ReferencePayload>(parser.getPayload());
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Unknown), static_cast<int>(payload->getType()));
+    ASSERT_EQ(std::string("https://www.example.com/mindBlowingImage.jpeg"), *payload->getUri());
+    ASSERT_EQ(std::string("11"), *payload->getBegin());
+    ASSERT_EQ(std::string("22"), *payload->getEnd());
+    ASSERT_EQ(std::string("xmpp:data@localhost.example.test"), *payload->getAnchor());
+}
+
+TEST(ReferencePayloadParserTest, testParseEmbeddedPayloads) {
+    PayloadsParserTester parser;
+
+    ASSERT_TRUE(parser.parse(
+        "<reference xmlns='urn:xmpp:reference:0' type='data'> "
+            "<error type=\"modify\">"
+                "<bad-request xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+                "<delay xmlns='urn:xmpp:delay' from='juliet@capulet.com/balcony' stamp='2002-09-10T23:41:07Z'/>"
+                "<text xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">boo</text>"
+            "</error>"
+        "</reference>"));
+
+    auto payload = std::dynamic_pointer_cast<ReferencePayload>(parser.getPayload());
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(payload->getType()));
+    ASSERT_FALSE(payload->getUri());
+    ASSERT_FALSE(payload->getBegin());
+    ASSERT_FALSE(payload->getEnd());
+    ASSERT_FALSE(payload->getAnchor());
+    auto childPayloadList = payload->getPayloads();
+    auto errorPayload = std::dynamic_pointer_cast<ErrorPayload>(childPayloadList[0]);
+    ASSERT_TRUE(errorPayload);
+    ASSERT_EQ("boo", errorPayload->getText());
+    auto delayPayload = std::dynamic_pointer_cast<Delay>(errorPayload->getPayload());
+    ASSERT_TRUE(delayPayload);
+}
+
+TEST(ReferencePayloadParserTest, testParseEmbeddedPayloadWithText) {
+    PayloadsParserTester parser;
+
+    ASSERT_TRUE(parser.parse(
+        "<reference xmlns='urn:xmpp:reference:0' type='data'> "
+            "<body>Example Text</body>"
+        "</reference>"));
+
+    auto payload = std::dynamic_pointer_cast<ReferencePayload>(parser.getPayload());
+    auto childPayloadList = payload->getPayloads();
+    auto bodyPayload = std::dynamic_pointer_cast<Body>(childPayloadList[0]);
+    ASSERT_EQ("Example Text", bodyPayload->getText());
+}
+
+TEST(ReferencePayloadParserTest, testParseRecursive) {
+    PayloadsParserTester parser;
+
+    ASSERT_TRUE(parser.parse(
+        "<reference xmlns='urn:xmpp:reference:0' type='data'> "
+            "<reference xmlns='urn:xmpp:reference:0' type='data' uri='https://download.montague.lit/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/summit.jpg' /> "
+            "<reference xmlns='urn:xmpp:reference:0' type='data' uri='xmpp:romeo@montague.lit/resource?jingle;id=9559976B-3FBF-4E7E-B457-2DAA225972BB' /> "
+            "<reference xmlns='urn:xmpp:reference:0' type='data'> "
+                "<reference xmlns='urn:xmpp:reference:0' type='data' uri='https://www.example.com/mindBlowingImage.jpeg' /> "
+            "</reference>"
+        "</reference>"));
+
+    auto payload = std::dynamic_pointer_cast<ReferencePayload>(parser.getPayload());
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(payload->getType()));
+    auto childPayloadList = payload->getPayloads();
+    auto childPayloadA = std::dynamic_pointer_cast<ReferencePayload>(childPayloadList[0]);
+    auto childPayloadB = std::dynamic_pointer_cast<ReferencePayload>(childPayloadList[1]);
+    auto childPayloadC = std::dynamic_pointer_cast<ReferencePayload>(childPayloadList[2]);
+    ASSERT_TRUE(childPayloadA);
+    ASSERT_TRUE(childPayloadB);
+    ASSERT_TRUE(childPayloadC);
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(childPayloadA->getType()));
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(childPayloadB->getType()));
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(childPayloadC->getType()));
+    ASSERT_EQ(std::string("https://download.montague.lit/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/summit.jpg"), *childPayloadA->getUri());
+    ASSERT_EQ(std::string("xmpp:romeo@montague.lit/resource?jingle;id=9559976B-3FBF-4E7E-B457-2DAA225972BB"), *childPayloadB->getUri());
+    ASSERT_FALSE(childPayloadC->getUri());
+    ASSERT_FALSE(childPayloadC->getBegin());
+    ASSERT_FALSE(childPayloadC->getEnd());
+    ASSERT_FALSE(childPayloadC->getAnchor());
+    auto grandChildPayloadList = childPayloadC->getPayloads();
+    auto grandChildPayload = std::dynamic_pointer_cast<ReferencePayload>(grandChildPayloadList[0]);
+    ASSERT_TRUE(grandChildPayload);
+    ASSERT_EQ(static_cast<int>(ReferencePayload::Type::Data), static_cast<int>(grandChildPayload->getType()));
+    ASSERT_EQ(std::string("https://www.example.com/mindBlowingImage.jpeg"), *grandChildPayload->getUri());
+    ASSERT_FALSE(grandChildPayload->getBegin());
+    ASSERT_FALSE(grandChildPayload->getEnd());
+    ASSERT_FALSE(grandChildPayload->getAnchor());
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 30c61a7..4ac5aa4 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -53,6 +53,7 @@ sources = [
         "PayloadParsers/PriorityParser.cpp",
         "PayloadParsers/PrivateStorageParser.cpp",
         "PayloadParsers/RawXMLPayloadParser.cpp",
+        "PayloadParsers/ReferencePayloadParser.cpp",
         "PayloadParsers/ResourceBindParser.cpp",
         "PayloadParsers/RosterItemExchangeParser.cpp",
         "PayloadParsers/RosterParser.cpp",
diff --git a/Swiften/QA/TLSTest/SConscript b/Swiften/QA/TLSTest/SConscript
index 7811b50..81e2471 100644
--- a/Swiften/QA/TLSTest/SConscript
+++ b/Swiften/QA/TLSTest/SConscript
@@ -24,4 +24,4 @@ if env["TEST"] :
             # Reenable if either http://www.tls-o-matic.com/ is fixed or we have setup a replacement.
             #"CertificateErrorTest.cpp"
         ])
-    myenv.Test(tester, "system")
\ No newline at end of file
+    myenv.Test(tester, "system")
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 5fb3b47..db08225 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -142,6 +142,7 @@ if env["SCONS_STAGE"] == "build" :
             "Elements/PubSubPayload.cpp",
             "Elements/PubSubOwnerPayload.cpp",
             "Elements/PubSubEventPayload.cpp",
+            "Elements/ReferencePayload.cpp",
             "Elements/RosterItemExchangePayload.cpp",
             "Elements/RosterPayload.cpp",
             "Elements/SecurityLabel.cpp",
@@ -225,6 +226,7 @@ if env["SCONS_STAGE"] == "build" :
             "Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCDestroyPayloadSerializer.cpp",
             "Serializer/PayloadSerializers/MUCInvitationPayloadSerializer.cpp",
+            "Serializer/PayloadSerializers/ReferencePayloadSerializer.cpp",
             "Serializer/PayloadSerializers/ResourceBindSerializer.cpp",
             "Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp",
             "Serializer/PayloadSerializers/RosterSerializer.cpp",
@@ -437,6 +439,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Parser/PayloadParsers/UnitTest/CommandParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"),
+            File("Parser/PayloadParsers/UnitTest/ReferencePayloadParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/RosterItemExchangeParserTest.cpp"),
             File("Parser/PayloadParsers/UnitTest/RosterParserTest.cpp"),
@@ -514,6 +517,7 @@ if env["SCONS_STAGE"] == "build" :
             File("Serializer/PayloadSerializers/UnitTest/ErrorSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/IBBSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/PrioritySerializerTest.cpp"),
+            File("Serializer/PayloadSerializers/UnitTest/ReferencePayloadSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/ResourceBindSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/RosterItemExchangeSerializerTest.cpp"),
             File("Serializer/PayloadSerializers/UnitTest/RosterSerializerTest.cpp"),
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 428ce76..31294f1 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -69,6 +69,7 @@
 #include <Swiften/Serializer/PayloadSerializers/PubSubOwnerPubSubSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/PubSubSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/RawXMLPayloadSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/ReplaceSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/ResultSetSerializer.h>
@@ -186,6 +187,8 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 
     serializers_.push_back(new IsodeIQDelegationSerializer(this));
 
+    serializers_.push_back(new ReferencePayloadSerializer(this));
+
     for (auto serializer : serializers_) {
         addSerializer(serializer);
     }
diff --git a/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.cpp
new file mode 100644
index 0000000..6e78a8d
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h>
+
+#include <memory>
+
+#include <Swiften/Base/Log.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+#include <Swiften/Serializer/XML/XMLRawTextNode.h>
+#include <Swiften/Serializer/PayloadSerializerCollection.h>
+
+namespace Swift {
+
+ReferencePayloadSerializer::ReferencePayloadSerializer(PayloadSerializerCollection* payloadSerializers) : GenericPayloadSerializer<ReferencePayload>(), payloadSerializers_(payloadSerializers) {
+}
+
+std::string ReferencePayloadSerializer::serializePayload(ReferencePayload::ref reference)  const {
+    XMLElement element("reference", "urn:xmpp:reference:0");
+
+    auto type = reference->getType();
+    if (type != ReferencePayload::Type::Unknown) {
+        element.setAttribute("type", getTypeString(type));
+
+        if (auto uri = reference->getUri()) {
+            element.setAttribute("uri", *uri);
+        }
+        if (auto begin = reference->getBegin()) {
+            element.setAttribute("begin", *begin);
+        }
+        if (auto end = reference->getEnd()) {
+            element.setAttribute("end", *end);
+        }
+        if (auto anchor = reference->getAnchor()) {
+            element.setAttribute("anchor", *anchor);
+        }
+
+        std::string serializedPayloads;
+        for (const auto& payload : reference->getPayloads()) {
+            if (auto serializer = payloadSerializers_->getPayloadSerializer(payload)) {
+                element.addNode(std::make_shared<XMLRawTextNode>(serializer->serialize(payload)));
+            }
+            else {
+                SWIFT_LOG(warning) << "Could not find serializer for " << typeid(*(payload.get())).name() << std::endl;
+            }
+        }
+    }
+    return element.serialize();
+}
+
+const std::string ReferencePayloadSerializer::getTypeString(const ReferencePayload::Type type) const {
+    switch(type) {
+        case ReferencePayload::Type::Data:    return "data";
+        case ReferencePayload::Type::Mention: return "mention";
+        case ReferencePayload::Type::PubSub:  return "pubsub";
+        case ReferencePayload::Type::Unknown: return "unknown";
+    }
+    return "";
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h
new file mode 100644
index 0000000..2af6045
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/API.h>
+#include <Swiften/Elements/ReferencePayload.h>
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+
+namespace Swift {
+    class PayloadSerializerCollection;
+
+    class SWIFTEN_API ReferencePayloadSerializer : public GenericPayloadSerializer<ReferencePayload> {
+        public:
+            ReferencePayloadSerializer(PayloadSerializerCollection* payloadSerializers);
+            virtual std::string serializePayload(ReferencePayload::ref reference)  const;
+
+        private:
+            const std::string getTypeString(const ReferencePayload::Type type) const;
+
+            PayloadSerializerCollection* payloadSerializers_;
+    };
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/ReferencePayloadSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/ReferencePayloadSerializerTest.cpp
new file mode 100644
index 0000000..82465d7
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/ReferencePayloadSerializerTest.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <gtest/gtest.h>
+
+#include <Swiften/Serializer/PayloadSerializers/ReferencePayloadSerializer.h>
+
+#include <Swiften/Elements/Body.h>
+#include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
+
+using namespace Swift;
+
+static FullPayloadSerializerCollection serializers;
+
+TEST(ReferencePayloadSerializerTest, testSerialize) {
+    ReferencePayloadSerializer testling(&serializers);
+    auto reference = std::make_shared<ReferencePayload>();
+    reference->setType(ReferencePayload::Type::Data);
+    reference->setUri(boost::optional<std::string>("https://www.example.com/mindBlowingImage.jpeg"));
+    reference->setBegin(boost::optional<std::string>("11"));
+    reference->setEnd(boost::optional<std::string>("22"));
+    reference->setAnchor(boost::optional<std::string>("xmpp:data@localhost.example.test"));
+
+    std::string expectedResult =
+        "<reference "
+            "anchor=\"xmpp:data@localhost.example.test\" "
+            "begin=\"11\" "
+            "end=\"22\" "
+            "type=\"data\" "
+            "uri=\"https://www.example.com/mindBlowingImage.jpeg\" "
+            "xmlns=\"urn:xmpp:reference:0\"/>";
+
+    ASSERT_EQ(expectedResult, testling.serialize(reference));
+}
+
+TEST(ReferencePayloadSerializerTest, testSerializeNoType) {
+    ReferencePayloadSerializer testling(&serializers);
+    auto reference = std::make_shared<ReferencePayload>();
+    reference->setUri(boost::optional<std::string>("https://www.example.com/mindBlowingImage.jpeg"));
+    reference->setBegin(boost::optional<std::string>("11"));
+    reference->setEnd(boost::optional<std::string>("22"));
+    reference->setAnchor(boost::optional<std::string>("xmpp:data@localhost.example.test"));
+
+    std::string expectedResult =
+        "<reference "
+            "anchor=\"xmpp:data@localhost.example.test\" "
+            "begin=\"11\" "
+            "end=\"22\" "
+            "type=\"data\" "
+            "uri=\"https://www.example.com/mindBlowingImage.jpeg\" "
+            "xmlns=\"urn:xmpp:reference:0\"/>";
+
+    ASSERT_EQ(expectedResult, testling.serialize(reference));
+}
+
+TEST(ReferencePayloadSerializerTest, testSerializeWithEmbeddedPayload) {
+    ReferencePayloadSerializer testling(&serializers);
+    auto reference = std::make_shared<ReferencePayload>();
+    reference->setUri(boost::optional<std::string>("https://www.example.com/mindBlowingImage.jpeg"));
+    reference->setBegin(boost::optional<std::string>("11"));
+    reference->setEnd(boost::optional<std::string>("22"));
+    reference->setAnchor(boost::optional<std::string>("xmpp:data@localhost.example.test"));
+    auto payload = std::make_shared<Body>(std::string("Look, I'm in a reference"));
+    reference->addPayload(payload);
+
+    std::string expectedResult =
+        "<reference "
+            "anchor=\"xmpp:data@localhost.example.test\" "
+            "begin=\"11\" "
+            "end=\"22\" "
+            "type=\"data\" "
+            "uri=\"https://www.example.com/mindBlowingImage.jpeg\" "
+            "xmlns=\"urn:xmpp:reference:0\">"
+            "<body>Look, I'm in a reference</body>"
+        "</reference>";
+
+    ASSERT_EQ(expectedResult, testling.serialize(reference));
+}
-- 
cgit v0.10.2-6-g49f6