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) + +}}}