aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Walters <rwalters@digitalstirling.com>2018-07-02 23:15:11 -0700
committerRichard Walters <rwalters@digitalstirling.com>2018-07-02 23:15:11 -0700
commiteed4afa6005e7359c0807d35f0e2fc80af544c0c (patch)
treeba2c700751df1c3524bbf797a92585bd982701c1
parente6d8e650b591e1e495822de356661e37a729f745 (diff)
Add reference resolution and attempt to fix path normalization
Path normalization is hideously broken for now.
-rw-r--r--include/Uri/Uri.hpp17
-rw-r--r--src/Uri.cpp71
-rw-r--r--test/src/UriTests.cpp58
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);
}