From f9c5121f3c3407ea47bd49d491cf243099e265e0 Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Fri, 26 Aug 2022 19:17:33 -0300 Subject: [PATCH] [FOLD] in place algorithm --- include/boost/url/impl/url.ipp | 188 +------------------------- include/boost/url/impl/url_base.ipp | 198 ++++++++++++++++++++++++++++ include/boost/url/url.hpp | 3 +- include/boost/url/url_base.hpp | 65 +++++++++ test/unit/url.cpp | 8 ++ 5 files changed, 274 insertions(+), 188 deletions(-) diff --git a/include/boost/url/impl/url.ipp b/include/boost/url/impl/url.ipp index 8c9fb456e..74ccd7e1e 100644 --- a/include/boost/url/impl/url.ipp +++ b/include/boost/url/impl/url.ipp @@ -145,194 +145,10 @@ relative( { BOOST_ASSERT(&dest != &base); BOOST_ASSERT(&dest != &href); - - // Validate input - if (!href.is_path_absolute() || - !base.is_path_absolute()) - { - // href is already relative or - // cannot calculate a URI relative to another relative URI - BOOST_URL_RETURN_EC(error::not_a_base); - } - - // Resolve scheme - if (href.scheme() == base.scheme() || - !href.has_scheme()) - { - dest.remove_scheme(); - } - else - { - dest.set_scheme(href.scheme()); - } - - // Resolve authority - if (dest.has_scheme() || - href.has_authority() != base.has_authority() || - href.authority() != base.authority() || - href.has_userinfo() || - href.has_password()) - { - // Otherwise, copy all but scheme from href - if (href.has_authority()) - dest.set_encoded_authority(href.encoded_authority()); - else - dest.remove_authority(); - dest.set_encoded_path(href.encoded_path()); - dest.normalize_path(); - if (href.has_query()) - dest.set_encoded_query(href.encoded_query()); - else - dest.remove_query(); - if (href.has_fragment()) - dest.set_encoded_fragment(href.encoded_fragment()); - else - dest.remove_fragment(); - return {}; - } - dest.remove_authority(); - - // Resolve new path - dest.set_encoded_path({}); - - // 0. Get segments - auto segs0 = base.segments(); - auto segs1 = href.segments(); - - // Reference iterators - auto const begin0 = segs0.begin(); - auto it0 = begin0; - auto const end0 = segs0.end(); - auto const last0 = begin0 != end0 ? std::prev(end0) : end0; - auto const begin1 = segs1.begin(); - auto it1 = begin1; - auto const end1 = segs1.end(); - auto const last1 = begin0 != end1 ? std::prev(end1) : end1; - - // Function to advance the dotdot segments - decode_view dotdot(".."); - decode_view dot("."); - auto consume_dots = [dotdot, dot]( - segments_view::iterator& first, - segments_view::iterator last) - { - if (*first == dotdot || - *first == dot) - { - ++first; - return true; - } - auto it = std::next(first); - std::size_t l = 1; - while (it != last) - { - if (*it == dotdot) - { - if (--l == 0) - { - ++it; - first = it; - break; - } - } - else if (*it != dot) - { - ++l; - } - ++it; - } - return first == it; - }; - - // 1. Find the longest common path - while ( - it0 != last0 && - it1 != last1) - { - if (consume_dots(it0, last0)) - continue; - if (consume_dots(it1, last1)) - continue; - if (*it0 == *it1) - { - ++it0; - ++it1; - } - else - { - break; - } - } - - // 1.b Check if parent paths are the same - if (it0 == last0 && - it1 == last1 && - it0 != end0 && - it1 != end1 && - *it0 == *it1) - { - // Return empty path - if (href.has_query()) - dest.set_encoded_query(href.encoded_query()); - else - dest.remove_query(); - if (href.has_fragment()) - dest.set_encoded_fragment(href.encoded_fragment()); - else - dest.remove_fragment(); - return {}; - } - - // 2. Append ".." for each segment left in base - segments_encoded segs = dest.encoded_segments(); - if (it0 != end0) - { - dest.set_path_absolute(false); - while (it0 != last0) - { - if (*it0 == dotdot) - { - if (!segs.empty()) - segs.pop_back(); - } - else if (*it0 != dot) - { - segs.push_back(dotdot.encoded()); - } - ++it0; - } - } - - // 3. Append segments left from the reference - while (it1 != end1) - { - if (*it1 == dotdot) - { - if (!segs.empty()) - segs.pop_back(); - } - else if (*it1 != dot) - { - segs.push_back((*it1).encoded()); - } - ++it1; - } - - // Query and fragment comes from reference - if (href.has_query()) - dest.set_encoded_query(href.encoded_query()); - else - dest.remove_query(); - - if (href.has_fragment()) - dest.set_encoded_fragment(href.encoded_fragment()); - else - dest.remove_fragment(); - - return {}; + dest.copy(base); + return dest.relative(href); } - } // urls } // boost diff --git a/include/boost/url/impl/url_base.ipp b/include/boost/url/impl/url_base.ipp index 49d43718e..f5ea7f94b 100644 --- a/include/boost/url/impl/url_base.ipp +++ b/include/boost/url/impl/url_base.ipp @@ -1992,6 +1992,204 @@ resolve_impl( return {}; } +result +url_base:: +relative( + url_view_base const& href) +{ + BOOST_ASSERT(this != &href); + + // Validate input + if (!href.is_path_absolute() || + !this->is_path_absolute()) + { + // href is already relative or + // cannot calculate a URI relative to another relative URI + BOOST_URL_RETURN_EC(error::not_a_base); + } + + // Resolve scheme + if (href.scheme() == scheme() || + !href.has_scheme()) + { + remove_scheme(); + } + else + { + set_scheme(href.scheme()); + } + + // Resolve authority + if (has_scheme() || + href.has_authority() != has_authority() || + href.authority() != authority() || + href.has_userinfo() || + href.has_password()) + { + // Otherwise, copy all but scheme from href + if (href.has_authority()) + set_encoded_authority(href.encoded_authority()); + else + remove_authority(); + set_encoded_path(href.encoded_path()); + normalize_path(); + if (href.has_query()) + set_encoded_query(href.encoded_query()); + else + remove_query(); + if (href.has_fragment()) + set_encoded_fragment(href.encoded_fragment()); + else + remove_fragment(); + return {}; + } + remove_authority(); + + // Resolve new path + // 0. Get segments + auto segs0 = dynamic_cast(this)->segments(); + auto segs1 = href.segments(); + + // Reference iterators + auto const begin0 = segs0.begin(); + auto it0 = begin0; + auto const end0 = segs0.end(); + auto const last0 = begin0 != end0 ? std::prev(end0) : end0; + auto const begin1 = segs1.begin(); + auto it1 = begin1; + auto const end1 = segs1.end(); + auto const last1 = begin1 != end1 ? std::prev(end1) : end1; + + // Function to advance the dotdot segments + decode_view dotdot(".."); + decode_view dot("."); + auto consume_dots = [dotdot, dot]( + segments_view::iterator& first, + segments_view::iterator last) + { + if (*first == dotdot || + *first == dot) + { + ++first; + return true; + } + auto it = std::next(first); + std::size_t l = 1; + while (it != last) + { + if (*it == dotdot) + { + if (--l == 0) + { + ++it; + first = it; + break; + } + } + else if (*it != dot) + { + ++l; + } + ++it; + } + return first == it; + }; + + // 1. Find the longest common path + while ( + it0 != last0 && + it1 != last1) + { + if (consume_dots(it0, last0)) + continue; + if (consume_dots(it1, last1)) + continue; + if (*it0 == *it1) + { + ++it0; + ++it1; + } + else + { + break; + } + } + + // 1.b Check if parent paths are the same + if (it0 == last0 && + it1 == last1 && + it0 != end0 && + it1 != end1 && + *it0 == *it1) + { + // Return empty path + if (href.has_query()) + set_encoded_query(href.encoded_query()); + else + remove_query(); + set_encoded_path({}); + if (href.has_fragment()) + set_encoded_fragment(href.encoded_fragment()); + else + remove_fragment(); + return {}; + } + + // 2. Append ".." for each segment left in base + std::size_t n = 0; + if (it0 != end0) + { + while (it0 != last0) + { + if (*it0 == dotdot) + { + if (n != 0) + --n; + } + else if (*it0 != dot) + { + ++n; + } + ++it0; + } + } + set_encoded_path({}); + set_path_absolute(false); + segments_encoded segs = encoded_segments(); + while (n--) + { + segs.push_back(dotdot.encoded()); + } + + // 3. Append segments left from the reference + while (it1 != end1) + { + if (*it1 == dotdot) + { + if (!segs.empty()) + segs.pop_back(); + } + else if (*it1 != dot) + { + segs.push_back((*it1).encoded()); + } + ++it1; + } + + // Query and fragment comes from reference + if (href.has_query()) + set_encoded_query(href.encoded_query()); + else + remove_query(); + + if (href.has_fragment()) + set_encoded_fragment(href.encoded_fragment()); + else + remove_fragment(); + + return {}; +} + //------------------------------------------------ // // Normalization diff --git a/include/boost/url/url.hpp b/include/boost/url/url.hpp index 64cb49bcd..8bfcd5696 100644 --- a/include/boost/url/url.hpp +++ b/include/boost/url/url.hpp @@ -355,8 +355,7 @@ resolve( any of the url containers that inherit from @ref url_base. - If an error occurs, the contents of - `dest` is unspecified and `ec` is set. + If the function fails, an error is returned. @par Example @code diff --git a/include/boost/url/url_base.hpp b/include/boost/url/url_base.hpp index 398e3a863..824be3bdb 100644 --- a/include/boost/url/url_base.hpp +++ b/include/boost/url/url_base.hpp @@ -1447,6 +1447,64 @@ class BOOST_SYMBOL_VISIBLE // //-------------------------------------------- + /** Compares absolute paths and this URL relative to the other + + This function compares two absolute paths. + It sets this URL to a relative path that + references the target path relative + to the base path represented by this URL. + + Unlike @ref resolve, this function takes + two absolute paths to create a relative + path. + + If the input URLs contain schemes and + authorities, these are resolved. If the + schemes and authorities are the same, they + are removed before the relative path is + calculated. If they are different, only + the relative path of the reference URL + is returned. + + Given this object as a base URL, this function + resolves the reference as if performing + the following steps: + + @li Normalize both URLs + @li Remove the longest common path from both paths + @li Replace each segment left in this base path with ".." + @li Append the reference path + @li Normalize the resulting URL + + This function places the result of the + resolution into this same object in place, + which can be any of the url containers + that inherit from @ref url_base. + + If the function fails, an error is returned. + + @par Example + @code + url u( "/relative/sub/foo/sub/file" ); + relative( "/relative/path" ); + assert( u.str() == "../../../path" ); + @endcode + + @par Exception Safety + Basic guarantee. + Calls to allocate may throw. + + @param href The target URL the relative URL should point to + + @see + @ref url, + @ref url_view. + */ + BOOST_URL_DECL + result + relative( + url_view_base const& ref); + friend result resolve( @@ -1454,6 +1512,13 @@ class BOOST_SYMBOL_VISIBLE url_view_base const& ref, url_base& dest); + friend + result + relative( + url_view_base const& base, + url_view_base const& ref, + url_base& dest); + private: //-------------------------------------------- // diff --git a/test/unit/url.cpp b/test/unit/url.cpp index 904b8ed15..d228974bc 100644 --- a/test/unit/url.cpp +++ b/test/unit/url.cpp @@ -2079,10 +2079,18 @@ class url_test parse_uri_reference(b).value(); auto ur = parse_uri_reference(r).value(); + + // free function url dest = parse_uri_reference("x/y" ).value(); auto rv = relative(ub, ur, dest); BOOST_TEST(!rv.has_error()); BOOST_TEST_EQ(dest.string(), e); + + // in place + url base( ub ); + rv = base.relative( ur ); + BOOST_TEST(!rv.has_error()); + BOOST_TEST_EQ(base.string(), e); }; // relative URL / absolute paths