From a43820d0b4014878e4bbfede6acde25f5830faa7 Mon Sep 17 00:00:00 2001
From: Richard Walters <rwalters@digitalstirling.com>
Date: Sat, 30 Jun 2018 21:20:37 -0700
Subject: Add support for port and hasPort elements

---
 include/Uri/Uri.hpp   | 24 ++++++++++++++++++++++++
 src/Uri.cpp           | 46 +++++++++++++++++++++++++++++++++++++++++++++-
 test/src/UriTests.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/include/Uri/Uri.hpp b/include/Uri/Uri.hpp
index fb1e5c8..4907aaf 100644
--- a/include/Uri/Uri.hpp
+++ b/include/Uri/Uri.hpp
@@ -10,6 +10,7 @@
  */
 
 #include <memory>
+#include <stdint.h>
 #include <string>
 #include <vector>
 
@@ -84,6 +85,29 @@ namespace Uri {
          */
         std::vector< std::string > GetPath() const;
 
+        /**
+         * This method returns an indication of whether or not the
+         * URI includes a port number.
+         *
+         * @return
+         *     An indication of whether or not the
+         *     URI includes a port number is returned.
+         */
+        bool HasPort() const;
+
+        /**
+         * This method returns the port number element of the URI,
+         * if it has one.
+         *
+         * @return
+         *     The port number element of the URI is returned.
+         *
+         * @note
+         *     The returned port number is only valid if the
+         *     HasPort method returns true.
+         */
+        uint16_t GetPort() const;
+
         // Private properties
     private:
         /**
diff --git a/src/Uri.cpp b/src/Uri.cpp
index c028443..78f3b4d 100644
--- a/src/Uri.cpp
+++ b/src/Uri.cpp
@@ -6,6 +6,7 @@
  * © 2018 by Richard Walters
  */
 
+#include <inttypes.h>
 #include <string>
 #include <Uri/Uri.hpp>
 #include <vector>
@@ -25,6 +26,17 @@ namespace Uri {
          */
         std::string host;
 
+        /**
+         * This flag indicates whether or not the
+         * URI includes a port number.
+         */
+        bool hasPort = false;
+
+        /**
+         * This is the port number element of the URI.
+         */
+        uint16_t port = 0;
+
         /**
          * This is the "path" element of the URI,
          * as a sequence of segments.
@@ -46,9 +58,33 @@ namespace Uri {
         auto rest = uriString.substr(schemeEnd + 1);
 
         // Next parse the host.
+        impl_->hasPort = false;
         if (rest.substr(0, 2) == "//") {
             const auto authorityEnd = rest.find('/', 2);
-            impl_->host = rest.substr(2, authorityEnd - 2);
+            const auto portDelimiter = rest.find(':');
+            if (portDelimiter == std::string::npos) {
+                impl_->host = rest.substr(2, authorityEnd - 2);
+            } else {
+                impl_->host = rest.substr(2, portDelimiter - 2);
+                uint32_t newPort = 0;
+                for (auto c: rest.substr(portDelimiter + 1, authorityEnd - portDelimiter - 1)) {
+                    if (
+                        (c < '0')
+                        || (c > '9')
+                    ) {
+                        return false;
+                    }
+                    newPort *= 10;
+                    newPort += (uint16_t)(c - '0');
+                    if (
+                        (newPort & ~((1 << 16) - 1)) != 0
+                    ) {
+                        return false;
+                    }
+                }
+                impl_->port = (uint16_t)newPort;
+                impl_->hasPort = true;
+            }
             rest = rest.substr(authorityEnd);
         } else {
             impl_->host.clear();
@@ -90,4 +126,12 @@ namespace Uri {
         return impl_->path;
     }
 
+    bool Uri::HasPort() const {
+        return impl_->hasPort;
+    }
+
+    uint16_t Uri::GetPort() const {
+        return impl_->port;
+    }
+
 }
diff --git a/test/src/UriTests.cpp b/test/src/UriTests.cpp
index 2f8f28e..2be24b6 100644
--- a/test/src/UriTests.cpp
+++ b/test/src/UriTests.cpp
@@ -57,3 +57,52 @@ TEST(UriTests, ParseFromStringPathCornerCases) {
         ++index;
     }
 }
+
+TEST(UriTests, ParseFromStringHasAPortNumber) {
+    Uri::Uri uri;
+    ASSERT_TRUE(uri.ParseFromString("http://www.example.com:8080/foo/bar"));
+    ASSERT_EQ("www.example.com", uri.GetHost());
+    ASSERT_TRUE(uri.HasPort());
+    ASSERT_EQ(8080, uri.GetPort());
+}
+
+TEST(UriTests, ParseFromStringDoesNotHaveAPortNumber) {
+    Uri::Uri uri;
+    ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar"));
+    ASSERT_EQ("www.example.com", uri.GetHost());
+    ASSERT_FALSE(uri.HasPort());
+}
+
+TEST(UriTests, ParseFromStringTwiceFirstWithPortNumberThenWithout) {
+    Uri::Uri uri;
+    ASSERT_TRUE(uri.ParseFromString("http://www.example.com:8080/foo/bar"));
+    ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar"));
+    ASSERT_FALSE(uri.HasPort());
+}
+
+TEST(UriTests, ParseFromStringBadPortNumberPurelyAlphabetic) {
+    Uri::Uri uri;
+    ASSERT_FALSE(uri.ParseFromString("http://www.example.com:spam/foo/bar"));
+}
+
+TEST(UriTests, ParseFromStringBadPortNumberStartsNumericEndsAlphabetic) {
+    Uri::Uri uri;
+    ASSERT_FALSE(uri.ParseFromString("http://www.example.com:8080spam/foo/bar"));
+}
+
+TEST(UriTests, ParseFromStringLargestValidPortNumber) {
+    Uri::Uri uri;
+    ASSERT_TRUE(uri.ParseFromString("http://www.example.com:65535/foo/bar"));
+    ASSERT_TRUE(uri.HasPort());
+    ASSERT_EQ(65535, uri.GetPort());
+}
+
+TEST(UriTests, ParseFromStringBadPortNumberTooBig) {
+    Uri::Uri uri;
+    ASSERT_FALSE(uri.ParseFromString("http://www.example.com:65536/foo/bar"));
+}
+
+TEST(UriTests, ParseFromStringBadPortNumberNegative) {
+    Uri::Uri uri;
+    ASSERT_FALSE(uri.ParseFromString("http://www.example.com:-1234/foo/bar"));
+}
-- 
cgit v1.2.3