From f4b6bfbf4c1573e9914185e2ef170f47838ea11a Mon Sep 17 00:00:00 2001
From: Joanna Hulboj <joanna.hulboj@isode.com>
Date: Fri, 24 May 2019 10:30:14 +0100
Subject: Add check if IPv4, IPv6 are valid JID domain part

When creating a JID we were not checking if a
domain part is a valid IPv4, IPv6 addresses. We were
only checking if the domain is correct according to
internationalized domain name rules which was failing
for IPv6 addresses.

Test-Information:
Unit tests pass on Windows 10 and Ubuntu 18.04.1 LTS

Change-Id: Ia1b67089f6edfdc6a0ebf2d26a7eaab9ce8171c0

diff --git a/Swiften/JID/JID.cpp b/Swiften/JID/JID.cpp
index fff88e9..5c6ea9d 100644
--- a/Swiften/JID/JID.cpp
+++ b/Swiften/JID/JID.cpp
@@ -13,6 +13,7 @@
 #include <Swiften/Base/String.h>
 #include <Swiften/IDN/IDNConverter.h>
 #include <Swiften/JID/JID.h>
+#include <Swiften/Network/HostAddress.h>
 
 #ifndef SWIFTEN_JID_NO_DEFAULT_IDN_CONVERTER
 #include <memory>
@@ -97,14 +98,47 @@ void JID::initializeFromString(const std::string& jid) {
     }
 }
 
+void JID::setComponents(const std::string& node, const std::string& domain, const std::string& resource) {
+    domain_ = domain;
+    try {
+        node_ = idnConverter->getStringPrepared(node, IDNConverter::XMPPNodePrep);
+        resource_ = idnConverter->getStringPrepared(resource, IDNConverter::XMPPResourcePrep);
+    }
+    catch (...) {
+        valid_ = false;
+        return;
+    }
+}
 
 void JID::nameprepAndSetComponents(const std::string& node, const std::string& domain, const std::string& resource) {
-    if (domain.empty() || !idnConverter->getIDNAEncoded(domain)) {
+    if (domain.empty() || (hasResource_ && resource.empty())) {
         valid_ = false;
         return;
     }
 
-    if (hasResource_ && resource.empty()) {
+    // Handling IPv6 addresses according to RFC 3986 rules
+    // saying that they are enclosed in square brackets
+    // which we have to remove when passing to HostAddress
+    if (domain.size() > 2 && domain.front() == '[' && domain.back() == ']') {
+        auto inner = std::string(domain.begin() + 1, domain.end() - 1);
+        auto hostAddress = HostAddress::fromString(inner);
+        if (hostAddress && hostAddress->isValid()) {
+            setComponents(node, domain, resource);
+            return;
+        }
+    }
+
+    const auto isAnyOfNonNumericAndNotDot = std::any_of(std::begin(domain), std::end(domain), [](char c) {return !::isdigit(c) && c != '.'; });
+
+    if (!isAnyOfNonNumericAndNotDot) {
+        auto hostAddress = HostAddress::fromString(domain);
+        if (hostAddress && hostAddress->isValid()) {
+            setComponents(node, domain, resource);
+            return;
+        }
+    }
+
+    if (!isAnyOfNonNumericAndNotDot || !idnConverter->getIDNAEncoded(domain)) {
         valid_ = false;
         return;
     }
@@ -118,7 +152,8 @@ void JID::nameprepAndSetComponents(const std::string& node, const std::string& d
             domain_ = idnConverter->getStringPrepared(domain, IDNConverter::NamePrep);
         }
         resource_ = idnConverter->getStringPrepared(resource, IDNConverter::XMPPResourcePrep);
-    } catch (...) {
+    }
+    catch (...) {
         valid_ = false;
         return;
     }
diff --git a/Swiften/JID/JID.h b/Swiften/JID/JID.h
index dc92f53..e98b796 100644
--- a/Swiften/JID/JID.h
+++ b/Swiften/JID/JID.h
@@ -184,6 +184,7 @@ namespace Swift {
 
         private:
             void nameprepAndSetComponents(const std::string& node, const std::string& domain, const std::string& resource);
+            void setComponents(const std::string& node, const std::string& domain, const std::string& resource);
             void initializeFromString(const std::string&);
 
         private:
diff --git a/Swiften/JID/UnitTest/JIDTest.cpp b/Swiften/JID/UnitTest/JIDTest.cpp
index 0753fb5..894378d 100644
--- a/Swiften/JID/UnitTest/JIDTest.cpp
+++ b/Swiften/JID/UnitTest/JIDTest.cpp
@@ -71,6 +71,12 @@ class JIDTest : public CppUnit::TestFixture
         CPPUNIT_TEST(testGetUnescapedNode);
         CPPUNIT_TEST(testGetUnescapedNode_XEP106Examples);
         CPPUNIT_TEST(testStringPrepFailures);
+        CPPUNIT_TEST(testConstructorWithString_DomainIPv4);
+        CPPUNIT_TEST(testConstructorWithString_DomainNOTIPv4);
+        CPPUNIT_TEST(testConstructorWithString_ValidDomainNOTIPv4);
+        CPPUNIT_TEST(testConstructorWithString_DomainIPv6);
+        CPPUNIT_TEST(testConstructorWithString_DomainInvalidIPv6);
+        CPPUNIT_TEST(testConstructorWithString_DomainIPv6NoBrackets);
         CPPUNIT_TEST_SUITE_END();
 
     public:
@@ -492,6 +498,46 @@ class JIDTest : public CppUnit::TestFixture
             CPPUNIT_ASSERT_EQUAL(std::string("c:\\cool stuff"), JID("c\\3a\\cool\\20stuff@example.com").getUnescapedNode());
             CPPUNIT_ASSERT_EQUAL(std::string("c:\\5commas"), JID("c\\3a\\5c5commas@example.com").getUnescapedNode());
         }
+
+        void testConstructorWithString_DomainIPv4() {
+            JID testling("foo@192.34.12.1/resource");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("foo"), testling.getNode());
+            CPPUNIT_ASSERT_EQUAL(std::string("192.34.12.1"), testling.getDomain());
+            CPPUNIT_ASSERT_EQUAL(std::string("resource"), testling.getResource());
+            CPPUNIT_ASSERT(!testling.isBare());
+            CPPUNIT_ASSERT(testling.isValid());
+        }
+
+        void testConstructorWithString_DomainNOTIPv4() {
+            JID testling("foo@500.34.12.1/resource");
+            CPPUNIT_ASSERT(!testling.isValid());
+        }
+
+        void testConstructorWithString_ValidDomainNOTIPv4() {
+            JID testling("foo@500.34.12.1a/resource");
+            CPPUNIT_ASSERT(testling.isValid());
+        }
+
+        void testConstructorWithString_DomainIPv6() {
+            JID testling("foo@[fe80::a857:33ff:febd:3580]/resource");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("foo"), testling.getNode());
+            CPPUNIT_ASSERT_EQUAL(std::string("[fe80::a857:33ff:febd:3580]"), testling.getDomain());
+            CPPUNIT_ASSERT_EQUAL(std::string("resource"), testling.getResource());
+            CPPUNIT_ASSERT(!testling.isBare());
+            CPPUNIT_ASSERT(testling.isValid());
+        }
+
+        void testConstructorWithString_DomainInvalidIPv6() {
+            JID testling("foo@[1111::a1111:1111:111!:!!!!]/resource");
+            CPPUNIT_ASSERT(!testling.isValid());
+        }
+
+        void testConstructorWithString_DomainIPv6NoBrackets() {
+            JID testling("foo@fe80::a857:33ff:febd:3580/resource");
+            CPPUNIT_ASSERT(!testling.isValid());
+        }
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(JIDTest);
-- 
cgit v0.10.2-6-g49f6