diff options
-rw-r--r-- | include/Uri/Uri.hpp | 17 | ||||
-rw-r--r-- | src/Uri.cpp | 71 | ||||
-rw-r--r-- | test/src/UriTests.cpp | 58 |
3 files changed, 139 insertions, 7 deletions
diff --git a/include/Uri/Uri.hpp b/include/Uri/Uri.hpp index 43e4ac3..1288a52 100644 --- a/include/Uri/Uri.hpp +++ b/include/Uri/Uri.hpp @@ -195,6 +195,23 @@ namespace Uri { */ void NormalizePath(); + /** + * This method resolves the given relative reference, based on the given + * base URI, returning the resolved target URI. + * + * @param[in] relativeReference + * This describes how to get to the target starting at the base. + * + * @return + * The resolved target URI is returned. + * + * @note + * It only makes sense to call this method on an absolute URI + * (in which I mean, the base URI should be absolute, + * as in IsRelativeReference() should return false). + */ + Uri Resolve(const Uri& relativeReference) const; + // Private properties private: /** diff --git a/src/Uri.cpp b/src/Uri.cpp index 01f5141..e29e517 100644 --- a/src/Uri.cpp +++ b/src/Uri.cpp @@ -10,6 +10,7 @@ #include "NormalizeCaseInsensitiveString.hpp" #include "PercentEncodedCharacterDecoder.hpp" +#include <algorithm> #include <functional> #include <inttypes.h> #include <memory> @@ -673,10 +674,10 @@ namespace Uri { return false; } - // Handle special case of absolute URI with empty + // Handle special case of URI with authority and empty // path -- treat the same as "/" path. if ( - !impl_->scheme.empty() + !impl_->host.empty() && impl_->path.empty() ) { impl_->path.push_back(""); @@ -789,9 +790,7 @@ namespace Uri { // Step 2E { if (oldPath[0] == "") { - if (impl_->path.empty()) { - impl_->path.push_back(""); - } + impl_->path.push_back(""); oldPath.erase(oldPath.begin()); } if (!oldPath.empty()) { @@ -806,4 +805,66 @@ namespace Uri { } } + Uri Uri::Resolve(const Uri& relativeReference) const { + // Resolve the reference by following the algorithm + // from section 5.2.2 in + // RFC 3986 (https://tools.ietf.org/html/rfc3986). + Uri target; + if (!relativeReference.impl_->scheme.empty()) { + target.impl_->scheme = relativeReference.impl_->scheme; + target.impl_->host = relativeReference.impl_->host; + target.impl_->userInfo = relativeReference.impl_->userInfo; + target.impl_->hasPort = relativeReference.impl_->hasPort; + target.impl_->port = relativeReference.impl_->port; + target.impl_->path = relativeReference.impl_->path; + target.NormalizePath(); + target.impl_->query = relativeReference.impl_->query; + } else { + if (!relativeReference.impl_->host.empty()) { + target.impl_->host = relativeReference.impl_->host; + target.impl_->userInfo = relativeReference.impl_->userInfo; + target.impl_->hasPort = relativeReference.impl_->hasPort; + target.impl_->port = relativeReference.impl_->port; + target.impl_->path = relativeReference.impl_->path; + target.NormalizePath(); + target.impl_->query = relativeReference.impl_->query; + } else { + if (relativeReference.impl_->path.empty()) { + target.impl_->path = impl_->path; + if (!relativeReference.impl_->query.empty()) { + target.impl_->query = relativeReference.impl_->query; + } else { + target.impl_->query = impl_->query; + } + } else { + if ( + !relativeReference.impl_->path.empty() + && (relativeReference.impl_->path[0] == "") + ) { + target.impl_->path = relativeReference.impl_->path; + target.NormalizePath(); + } else { + target.impl_->path = impl_->path; + if (target.impl_->path.size() > 1) { + target.impl_->path.pop_back(); + } + std::copy( + relativeReference.impl_->path.begin(), + relativeReference.impl_->path.end(), + std::back_inserter(target.impl_->path) + ); + target.NormalizePath(); + } + target.impl_->query = relativeReference.impl_->query; + } + target.impl_->host = impl_->host; + target.impl_->userInfo = impl_->userInfo; + target.impl_->hasPort = impl_->hasPort; + target.impl_->port = impl_->port; + } + target.impl_->scheme = impl_->scheme; + } + target.impl_->fragment = relativeReference.impl_->fragment; + return target; + } } diff --git a/test/src/UriTests.cpp b/test/src/UriTests.cpp index 897047d..f42dd5a 100644 --- a/test/src/UriTests.cpp +++ b/test/src/UriTests.cpp @@ -647,12 +647,66 @@ TEST(UriTests, ConstructNormalizeAndCompareEquivalentUris) { ASSERT_EQ(uri1, uri2); } +TEST(UriTests, ReferenceResolution) { + struct TestVector { + std::string baseString; + std::string relativeReferenceString; + std::string targetString; + }; + const std::vector< TestVector > testVectors{ + // These are all taken from section 5.4.1 + // of RFC 3986 (https://tools.ietf.org/html/rfc3986). + {"http://a/b/c/d;p?q", "g:h", "g:h"}, + {"http://a/b/c/d;p?q", "g", "http://a/b/c/g"}, + {"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"}, + {"http://a/b/c/d;p?q", "g/", "http://a/b/c/g/"}, + {"http://a/b/c/d;p?q", "//g", "http://g"}, + {"http://a/b/c/d;p?q", "?y", "http://a/b/c/d;p?y"}, + {"http://a/b/c/d;p?q", "g?y", "http://a/b/c/g?y"}, + {"http://a/b/c/d;p?q", "#s", "http://a/b/c/d;p?q#s"}, + {"http://a/b/c/d;p?q", "g#s", "http://a/b/c/g#s"}, + {"http://a/b/c/d;p?q", "g?y#s", "http://a/b/c/g?y#s"}, + {"http://a/b/c/d;p?q", ";x", "http://a/b/c/;x"}, + {"http://a/b/c/d;p?q", "g;x", "http://a/b/c/g;x"}, + {"http://a/b/c/d;p?q", "g;x?y#s", "http://a/b/c/g;x?y#s"}, + {"http://a/b/c/d;p?q", "", "http://a/b/c/d;p?q"}, + {"http://a/b/c/d;p?q", ".", "http://a/b/c/"}, + {"http://a/b/c/d;p?q", "./", "http://a/b/c/"}, + {"http://a/b/c/d;p?q", "..", "http://a/b/"}, + {"http://a/b/c/d;p?q", "../", "http://a/b/"}, + {"http://a/b/c/d;p?q", "../g", "http://a/b/g"}, + {"http://a/b/c/d;p?q", "../..", "http://a"}, + {"http://a/b/c/d;p?q", "../../", "http://a"}, + {"http://a/b/c/d;p?q", "../../g", "http://a/g"}, + + // Here are some examples of our own. + {"http://example.com", "foo", "http://example.com/foo"}, + {"http://example.com/", "foo", "http://example.com/foo"}, + {"http://example.com", "foo/", "http://example.com/foo/"}, + {"http://example.com/", "foo/", "http://example.com/foo/"}, + {"http://example.com", "/foo", "http://example.com/foo"}, + {"http://example.com/", "/foo", "http://example.com/foo"}, + {"http://example.com", "/foo/", "http://example.com/foo/"}, + {"http://example.com/", "/foo/", "http://example.com/foo/"}, + }; + size_t index = 0; + for (const auto& testVector : testVectors) { + Uri::Uri baseUri, relativeReferenceUri, expectedTargetUri; + ASSERT_TRUE(baseUri.ParseFromString(testVector.baseString)); + ASSERT_TRUE(relativeReferenceUri.ParseFromString(testVector.relativeReferenceString)) << index; + ASSERT_TRUE(expectedTargetUri.ParseFromString(testVector.targetString)) << index; + const auto actualTargetUri = baseUri.Resolve(relativeReferenceUri); + ASSERT_EQ(expectedTargetUri, actualTargetUri) << index; + ++index; + } +} + TEST(UriTests, EmptyPathInUriWithAuthorityIsEquivalentToSlashOnlyPath) { Uri::Uri uri1, uri2; ASSERT_TRUE(uri1.ParseFromString("http://example.com")); ASSERT_TRUE(uri2.ParseFromString("http://example.com/")); ASSERT_EQ(uri1, uri2); - ASSERT_TRUE(uri1.ParseFromString("urn:")); - ASSERT_TRUE(uri2.ParseFromString("urn:/")); + ASSERT_TRUE(uri1.ParseFromString("//example.com")); + ASSERT_TRUE(uri2.ParseFromString("//example.com/")); ASSERT_EQ(uri1, uri2); } |