diff --git a/Release/include/cpprest/base_uri.h b/Release/include/cpprest/base_uri.h
index b5fc8fcfd0..91a4d47bd0 100644
--- a/Release/include/cpprest/base_uri.h
+++ b/Release/include/cpprest/base_uri.h
@@ -379,12 +379,20 @@ namespace web {
///
/// Returns the full (encoded) URI as a string.
///
- /// The full encoded URI string.
+ /// The full encoded URI string.
utility::string_t to_string() const
{
return m_uri;
}
+ ///
+ /// Returns an URI resolved against this as the base URI
+ /// according to RFC3986, Section 5 (https://tools.ietf.org/html/rfc3986#section-5).
+ ///
+ /// The relative URI to be resolved against this as base.
+ /// The new resolved URI string.
+ _ASYNCRTIMP utility::string_t resolve_uri(const utility::string_t &relativeUri) const;
+
_ASYNCRTIMP bool operator == (const uri &other) const;
bool operator < (const uri &other) const
@@ -413,4 +421,4 @@ namespace web {
details::uri_components m_components;
};
-} // namespace web
+} // namespace web
diff --git a/Release/src/uri/uri.cpp b/Release/src/uri/uri.cpp
index 737ed12e1b..1953564a9f 100644
--- a/Release/src/uri/uri.cpp
+++ b/Release/src/uri/uri.cpp
@@ -19,6 +19,9 @@ namespace web { namespace details
{
namespace
{
+ const ::utility::string_t dotSegment = _XPLATSTR(".");
+ const ::utility::string_t dotDotSegment = _XPLATSTR("..");
+
///
/// Unreserved characters are those that are allowed in a URI but do not have a reserved purpose. They include:
/// - A-Z
@@ -423,7 +426,60 @@ namespace
return encoded;
}
-}
+ // 5.2.3. Merge Paths https://tools.ietf.org/html/rfc3986#section-5.2.3
+ utility::string_t mergePaths(const utility::string_t &base, const utility::string_t &relative)
+ {
+ const auto lastSlash = base.rfind(_XPLATSTR('/'));
+ if (lastSlash == utility::string_t::npos)
+ {
+ return base + _XPLATSTR('/') + relative;
+ }
+ else if (lastSlash == base.size() - 1)
+ {
+ return base + relative;
+ }
+ // path contains and does not end with '/', we remove segment after last '/'
+ return base.substr(0, lastSlash + 1) + relative;
+ }
+
+ // 5.2.4. Remove Dot Segments https://tools.ietf.org/html/rfc3986#section-5.2.4
+ void removeDotSegments(uri_builder &builder)
+ {
+ if (builder.path().find(_XPLATSTR('.')) == utility::string_t::npos)
+ return;
+
+ const auto segments = uri::split_path(builder.path());
+ std::vector> result;
+ for (auto& segment : segments)
+ {
+ if (segment == dotSegment)
+ continue;
+ else if (segment != dotDotSegment)
+ result.push_back(segment);
+ else if (!result.empty())
+ result.pop_back();
+ }
+ if (result.empty())
+ {
+ builder.set_path(utility::string_t());
+ return;
+ }
+ utility::string_t path = result.front().get();
+ for (size_t i = 1; i != result.size(); ++i)
+ {
+ path += _XPLATSTR('/');
+ path += result[i].get();
+ }
+ if (segments.back() == dotDotSegment
+ || segments.back() == dotSegment
+ || builder.path().back() == _XPLATSTR('/'))
+ {
+ path += _XPLATSTR('/');
+ }
+
+ builder.set_path(std::move(path));
+ }
+} // namespace
utility::string_t uri_components::join()
{
@@ -448,7 +504,8 @@ utility::string_t uri_components::join()
if (!m_scheme.empty())
{
- ret.append(m_scheme).append({ _XPLATSTR(':') });
+ ret.append(m_scheme);
+ ret.push_back(_XPLATSTR(':'));
}
if (!m_host.empty())
@@ -473,7 +530,7 @@ utility::string_t uri_components::join()
// only add the leading slash when the host is present
if (!m_host.empty() && m_path.front() != _XPLATSTR('/'))
{
- ret.append({ _XPLATSTR('/') });
+ ret.push_back(_XPLATSTR('/'));
}
ret.append(m_path);
@@ -481,17 +538,19 @@ utility::string_t uri_components::join()
if (!m_query.empty())
{
- ret.append({ _XPLATSTR('?') }).append(m_query);
+ ret.push_back(_XPLATSTR('?'));
+ ret.append(m_query);
}
if (!m_fragment.empty())
{
- ret.append({ _XPLATSTR('#') }).append(m_fragment);
+ ret.push_back(_XPLATSTR('#'));
+ ret.append(m_fragment);
}
return ret;
}
-}
+} // namespace details
uri::uri(const details::uri_components &components) : m_components(components)
{
@@ -715,7 +774,7 @@ std::map uri::split_query(const utility::s
utility::string_t key(key_value_pair.begin(), key_value_pair.begin() + equals_index);
utility::string_t value(key_value_pair.begin() + equals_index + 1, key_value_pair.end());
results[key] = value;
- }
+ }
}
return results;
@@ -784,4 +843,54 @@ bool uri::operator == (const uri &other) const
return true;
}
+//resolving URI according to RFC3986, Section 5 https://tools.ietf.org/html/rfc3986#section-5
+utility::string_t uri::resolve_uri(const utility::string_t &relativeUri) const
+{
+ if (relativeUri.empty())
+ {
+ return to_string();
+ }
+
+ if (relativeUri[0] == _XPLATSTR('/')) // starts with '/'
+ {
+ if (relativeUri.size() >= 2 && relativeUri[1] == _XPLATSTR('/')) // starts with '//'
+ {
+ return this->scheme() + _XPLATSTR(':') + relativeUri;
+ }
+
+ // otherwise relative to root
+ auto builder = uri_builder(this->authority());
+ builder.append(relativeUri);
+ details::removeDotSegments(builder);
+ return builder.to_string();
+ }
+
+ const auto url = uri(relativeUri);
+ if (!url.scheme().empty())
+ return relativeUri;
+
+ if (!url.authority().is_empty())
+ {
+ return uri_builder(url).set_scheme(this->scheme()).to_string();
+ }
+
+ // relative url
+ auto builder = uri_builder(*this);
+ if (url.path() == _XPLATSTR("/") || url.path().empty()) // web::uri considers empty path as '/'
+ {
+ if (!url.query().empty())
+ {
+ builder.set_query(url.query());
+ }
+ }
+ else if (!this->path().empty())
+ {
+ builder.set_path(details::mergePaths(this->path(), url.path()));
+ details::removeDotSegments(builder);
+ builder.set_query(url.query());
+ }
+
+ return builder.set_fragment(url.fragment()).to_string();
}
+
+} // namespace web
diff --git a/Release/tests/functional/uri/CMakeLists.txt b/Release/tests/functional/uri/CMakeLists.txt
index f869e24d18..54d80ac254 100644
--- a/Release/tests/functional/uri/CMakeLists.txt
+++ b/Release/tests/functional/uri/CMakeLists.txt
@@ -8,6 +8,7 @@ set(SOURCES
operator_tests.cpp
splitting_tests.cpp
uri_builder_tests.cpp
+ resolve_uri_tests.cpp
stdafx.cpp
)
diff --git a/Release/tests/functional/uri/resolve_uri_tests.cpp b/Release/tests/functional/uri/resolve_uri_tests.cpp
new file mode 100644
index 0000000000..963be8656f
--- /dev/null
+++ b/Release/tests/functional/uri/resolve_uri_tests.cpp
@@ -0,0 +1,68 @@
+#include "stdafx.h"
+
+using namespace web;
+using namespace utility;
+
+namespace tests { namespace functional { namespace uri_tests {
+
+//testing resolution against examples from Section 5.4 https://tools.ietf.org/html/rfc3986#section-5.4
+SUITE(resolve_uri_tests)
+{
+//5.4.1. Normal Examples https://tools.ietf.org/html/rfc3986#section-5.4.1
+TEST(resolve_uri_normal)
+{
+ const uri baseUri = U("http://a/b/c/d;p?q");
+
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g:h")), U("g:h"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g")), U("http://a/b/c/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("./g")), U("http://a/b/c/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g/")), U("http://a/b/c/g/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("/g")), U("http://a/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("//g")), U("http://g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("?y")), U("http://a/b/c/d;p?y"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g?y")), U("http://a/b/c/g?y"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("#s")), U("http://a/b/c/d;p?q#s"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g#s")), U("http://a/b/c/g#s"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g?y#s")), U("http://a/b/c/g?y#s"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U(";x")), U("http://a/b/c/;x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g;x")), U("http://a/b/c/g;x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g;x?y#s")), U("http://a/b/c/g;x?y#s"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("")), U("http://a/b/c/d;p?q"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U(".")), U("http://a/b/c/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("./")), U("http://a/b/c/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("..")), U("http://a/b/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../")), U("http://a/b/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../g")), U("http://a/b/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../..")), U("http://a/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../../")), U("http://a/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../../g")), U("http://a/g"));
+}
+//5.4.2. Abnormal Examples https://tools.ietf.org/html/rfc3986#section-5.4.2
+TEST(resolve_uri_abnormal)
+{
+ const uri baseUri = U("http://a/b/c/d;p?q");
+
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../../../g")), U("http://a/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("../../../../g")), U("http://a/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("/./g")), U("http://a/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("/../g")), U("http://a/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g.")), U("http://a/b/c/g."));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U(".g")), U("http://a/b/c/.g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g..")), U("http://a/b/c/g.."));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("..g")), U("http://a/b/c/..g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("./../g")), U("http://a/b/g"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("./g/.")), U("http://a/b/c/g/"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g/./h")), U("http://a/b/c/g/h"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g/../h")), U("http://a/b/c/h"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g;x=1/./y")), U("http://a/b/c/g;x=1/y"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g;x=1/../y")), U("http://a/b/c/y"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g?y/./x")), U("http://a/b/c/g?y/./x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g?y/../x")), U("http://a/b/c/g?y/../x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g#s/./x")), U("http://a/b/c/g#s/./x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("g#s/../x")), U("http://a/b/c/g#s/../x"));
+ VERIFY_ARE_EQUAL(baseUri.resolve_uri(U("http:g")), U("http:g"));
+}
+
+} // SUITE(resolve_uri_tests)
+
+}}}