Allow parsing urls with extra elements

Sometimes it is desirable to only parse a portion of a URL, and allow
any elements at the end.  This comes up significantly in aggregation
where parsing "/redfish/v1/<collection>/anything is pretty common.

This commit adds a new class, OrMorePaths, that can be used as a
placeholder to indicate that more paths should be accepted.

Tested: Unit tests pass.

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: If4fb3991a91560fd3b8b838f912aa36e79ddd2b3
diff --git a/http/ut/utility_test.cpp b/http/ut/utility_test.cpp
index 4d9bd91..8eef93f 100644
--- a/http/ut/utility_test.cpp
+++ b/http/ut/utility_test.cpp
@@ -147,6 +147,12 @@
 
     parsed = boost::urls::parse_relative_ref("not/absolute/url");
     EXPECT_FALSE(readUrlSegments(*parsed, "not", "absolute", "url"));
+
+    parsed = boost::urls::parse_relative_ref("/excellent/path");
+
+    EXPECT_TRUE(readUrlSegments(*parsed, "excellent", "path", OrMorePaths()));
+    EXPECT_TRUE(readUrlSegments(*parsed, "excellent", OrMorePaths()));
+    EXPECT_TRUE(readUrlSegments(*parsed, OrMorePaths()));
 }
 
 TEST(Utility, ValidateAndSplitUrlPositive)
diff --git a/http/utility.hpp b/http/utility.hpp
index e08e565..2ba9b0e 100644
--- a/http/utility.hpp
+++ b/http/utility.hpp
@@ -573,6 +573,9 @@
 }
 } // namespace details
 
+class OrMorePaths
+{};
+
 template <typename... AV>
 inline boost::urls::url urlFromPieces(const AV... args)
 {
@@ -584,21 +587,37 @@
 
 // std::reference_wrapper<std::string> - extracts segment to variable
 //                    std::string_view - checks if segment is equal to variable
-using UrlSegment =
-    std::variant<std::reference_wrapper<std::string>, std::string_view>;
+using UrlSegment = std::variant<std::reference_wrapper<std::string>,
+                                std::string_view, OrMorePaths>;
+
+enum class UrlParseResult
+{
+    Continue,
+    Fail,
+    Done,
+};
 
 class UrlSegmentMatcherVisitor
 {
   public:
-    bool operator()(std::string& output)
+    UrlParseResult operator()(std::string& output)
     {
         output = std::string_view(segment.data(), segment.size());
-        return true;
+        return UrlParseResult::Continue;
     }
 
-    bool operator()(std::string_view expected)
+    UrlParseResult operator()(std::string_view expected)
     {
-        return std::string_view(segment.data(), segment.size()) == expected;
+        if (std::string_view(segment.data(), segment.size()) == expected)
+        {
+            return UrlParseResult::Continue;
+        }
+        return UrlParseResult::Fail;
+    }
+
+    UrlParseResult operator()(OrMorePaths /*unused*/)
+    {
+        return UrlParseResult::Done;
     }
 
     explicit UrlSegmentMatcherVisitor(
@@ -615,7 +634,7 @@
 {
     const boost::urls::segments_view& urlSegments = urlView.segments();
 
-    if (!urlSegments.is_absolute() || segments.size() != urlSegments.size())
+    if (!urlSegments.is_absolute())
     {
         return false;
     }
@@ -625,13 +644,23 @@
 
     for (const auto& segment : segments)
     {
-        if (!std::visit(UrlSegmentMatcherVisitor(*it), segment))
+        if (it == end)
+        {
+            // If the request ends with an "any" path, this was successful
+            return std::holds_alternative<OrMorePaths>(segment);
+        }
+        UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
+        if (res == UrlParseResult::Done)
+        {
+            return true;
+        }
+        if (res == UrlParseResult::Fail)
         {
             return false;
         }
         it++;
     }
-    return true;
+    return it == end;
 }
 
 } // namespace details