From 0d5dcec01a396ab64c559a5b0cd04eb38f2f1954 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 7 Jun 2016 21:40:15 +0200
Subject: Add support for URLs with IPv6 addresses in Swift::URL

Test-Information:

Added unit tests and test cases from RFC 2732.

All tests pass on OS X 10.11.5.

Change-Id: Ic76e57985109912871c05d12253f73d3653bd7a3

diff --git a/Swiften/Base/URL.cpp b/Swiften/Base/URL.cpp
index a9a1140..4a47a11 100644
--- a/Swiften/Base/URL.cpp
+++ b/Swiften/Base/URL.cpp
@@ -1,11 +1,12 @@
 /*
- * Copyright (c) 2010 Isode Limited.
+ * Copyright (c) 2010-2016 Isode Limited.
  * All rights reserved.
  * See the COPYING file for more information.
  */
 
 #include <Swiften/Base/URL.h>
 
+#include <algorithm>
 #include <iostream>
 
 namespace Swift {
@@ -62,18 +63,39 @@ URL URL::fromString(const std::string& urlString) {
 
         std::string host;
         boost::optional<int> port;
-        colonIndex = hostAndPort.find(':');
-        if (colonIndex != std::string::npos) {
-            host = unescape(hostAndPort.substr(0, colonIndex));
-            try {
-                port = boost::lexical_cast<int>(hostAndPort.substr(colonIndex + 1));
+        if (hostAndPort[0] == '[') {
+            // handle IPv6 address literals
+            size_t addressEndIndex = hostAndPort.find(']');
+            if (addressEndIndex != std::string::npos) {
+                host = hostAndPort.substr(1, addressEndIndex - 1);
+                colonIndex = hostAndPort.find(':', addressEndIndex);
+                if (colonIndex != std::string::npos) {
+                    try {
+                        port = boost::lexical_cast<int>(hostAndPort.substr(colonIndex + 1));
+                    }
+                    catch (const boost::bad_lexical_cast&) {
+                        return URL();
+                    }
+                }
             }
-            catch (const boost::bad_lexical_cast&) {
+            else {
                 return URL();
             }
         }
         else {
-            host = unescape(hostAndPort);
+            colonIndex = hostAndPort.find(':');
+            if (colonIndex != std::string::npos) {
+                host = unescape(hostAndPort.substr(0, colonIndex));
+                try {
+                    port = boost::lexical_cast<int>(hostAndPort.substr(colonIndex + 1));
+                }
+                catch (const boost::bad_lexical_cast&) {
+                    return URL();
+                }
+            }
+            else {
+                host = unescape(hostAndPort);
+            }
         }
 
         if (port) {
@@ -102,7 +124,12 @@ std::string URL::toString() const {
         }
         result += "@";
     }
-    result += host;
+    if (host.find(':') != std::string::npos) {
+        result += "[" + host + "]";
+    }
+    else {
+        result += host;
+    }
     if (port) {
         result += ":";
         result += boost::lexical_cast<std::string>(*port);
diff --git a/Swiften/Base/UnitTest/URLTest.cpp b/Swiften/Base/UnitTest/URLTest.cpp
index 2d2ceba..c38398a 100644
--- a/Swiften/Base/UnitTest/URLTest.cpp
+++ b/Swiften/Base/UnitTest/URLTest.cpp
@@ -24,8 +24,13 @@ class URLTest : public CppUnit::TestFixture {
         CPPUNIT_TEST(testFromString_WithUserInfo);
         CPPUNIT_TEST(testFromString_NonASCIIHost);
         CPPUNIT_TEST(testFromString_NonASCIIPath);
+        CPPUNIT_TEST(testFromString_IPv4Address);
+        CPPUNIT_TEST(testFromString_IPv4AddressWithPort);
+        CPPUNIT_TEST(testFromString_IPv6Address);
+        CPPUNIT_TEST(testFromString_IPv6AddressWithPort);
         CPPUNIT_TEST(testToString);
         CPPUNIT_TEST(testToString_WithPort);
+        CPPUNIT_TEST(test_FromString_ToString_IPv6RFC2732);
         CPPUNIT_TEST_SUITE_END();
 
     public:
@@ -103,6 +108,124 @@ class URLTest : public CppUnit::TestFixture {
             CPPUNIT_ASSERT_EQUAL(std::string("/baz/tron\xc3\xa7on/bam"), url.getPath());
         }
 
+        void testFromString_IPv4Address() {
+            URL url = URL::fromString("http://127.0.0.1/foobar");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+            CPPUNIT_ASSERT_EQUAL(std::string("127.0.0.1"), url.getHost());
+            CPPUNIT_ASSERT_EQUAL(std::string("/foobar"), url.getPath());
+        }
+
+        void testFromString_IPv4AddressWithPort() {
+            URL url = URL::fromString("http://127.0.0.1:12345/foobar");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+            CPPUNIT_ASSERT_EQUAL(std::string("127.0.0.1"), url.getHost());
+            CPPUNIT_ASSERT_EQUAL(12345, url.getPort().get_value_or(0));
+            CPPUNIT_ASSERT_EQUAL(std::string("/foobar"), url.getPath());
+        }
+
+        void testFromString_IPv6Address() {
+            URL url = URL::fromString("http://[fdf8:f53b:82e4::53]/foobar");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+            CPPUNIT_ASSERT_EQUAL(std::string("fdf8:f53b:82e4::53"), url.getHost());
+        }
+
+        void testFromString_IPv6AddressWithPort() {
+            URL url = URL::fromString("http://[fdf8:f53b:82e4::53]:12435/foobar");
+
+            CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+            CPPUNIT_ASSERT_EQUAL(std::string("fdf8:f53b:82e4::53"), url.getHost());
+            CPPUNIT_ASSERT_EQUAL(12435, url.getPort().get_value_or(0));
+        }
+
+        void test_FromString_ToString_IPv6RFC2732() {
+            {
+                const char* testVector = "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(80, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string("/index.html"), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[1080:0:0:0:8:800:200C:417A]/index.html";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("1080:0:0:0:8:800:200C:417A"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(2, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string("/index.html"), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[3ffe:2a00:100:7031::1]";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("3ffe:2a00:100:7031::1"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(2, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string(""), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[1080::8:800:200C:417A]/foo";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("1080::8:800:200C:417A"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(2, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string("/foo"), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[::192.9.5.5]/ipng";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("::192.9.5.5"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(2, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string("/ipng"), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[::FFFF:129.144.52.38]:80/index.html";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("::FFFF:129.144.52.38"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(80, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string("/index.html"), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+
+            {
+                const char* testVector = "http://[2010:836B:4179::836B:4179]";
+                URL url = URL::fromString(testVector);
+
+                CPPUNIT_ASSERT_EQUAL(std::string("http"), url.getScheme());
+                CPPUNIT_ASSERT_EQUAL(std::string("2010:836B:4179::836B:4179"), url.getHost());
+                CPPUNIT_ASSERT_EQUAL(2, url.getPort().get_value_or(2));
+                CPPUNIT_ASSERT_EQUAL(std::string(), url.getPath());
+
+                CPPUNIT_ASSERT_EQUAL(std::string(testVector), url.toString());
+            }
+        }
+
         void testToString() {
             CPPUNIT_ASSERT_EQUAL(std::string("http://foo.bar/baz/bam"), URL("http", "foo.bar", "/baz/bam").toString());
         }
-- 
cgit v0.10.2-6-g49f6