LCOV - code coverage report
Current view: top level - libs/url/src/detail - normalize.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 373 406 91.9 %
Date: 2023-12-15 15:31:48 Functions: 20 22 90.9 %

          Line data    Source code
       1             : //
       2             : // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
       3             : // Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
       4             : //
       5             : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6             : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7             : //
       8             : // Official repository: https://github.com/boostorg/url
       9             : //
      10             : 
      11             : #ifndef BOOST_URL_DETAIL_IMPL_NORMALIZE_IPP
      12             : #define BOOST_URL_DETAIL_IMPL_NORMALIZE_IPP
      13             : 
      14             : #include <boost/url/detail/config.hpp>
      15             : #include <boost/url/decode_view.hpp>
      16             : #include <boost/url/detail/decode.hpp>
      17             : #include <boost/url/segments_encoded_view.hpp>
      18             : #include <boost/url/detail/normalize.hpp>
      19             : #include <boost/url/grammar/ci_string.hpp>
      20             : #include <boost/assert.hpp>
      21             : #include <boost/core/ignore_unused.hpp>
      22             : #include <cstring>
      23             : 
      24             : namespace boost {
      25             : namespace urls {
      26             : namespace detail {
      27             : 
      28             : void
      29        5604 : pop_encoded_front(
      30             :     core::string_view& s,
      31             :     char& c,
      32             :     std::size_t& n) noexcept
      33             : {
      34        5604 :     if(s.front() != '%')
      35             :     {
      36        5514 :         c = s.front();
      37        5514 :         s.remove_prefix(1);
      38             :     }
      39             :     else
      40             :     {
      41          90 :         detail::decode_unsafe(
      42             :             &c,
      43             :             &c + 1,
      44             :             s.substr(0, 3));
      45          90 :         s.remove_prefix(3);
      46             :     }
      47        5604 :     ++n;
      48        5604 : }
      49             : 
      50             : int
      51          77 : compare_encoded(
      52             :     core::string_view lhs,
      53             :     core::string_view rhs) noexcept
      54             : {
      55          77 :     std::size_t n0 = 0;
      56          77 :     std::size_t n1 = 0;
      57          77 :     char c0 = 0;
      58          77 :     char c1 = 0;
      59         205 :     while(
      60         535 :         !lhs.empty() &&
      61         253 :         !rhs.empty())
      62             :     {
      63         240 :         pop_encoded_front(lhs, c0, n0);
      64         240 :         pop_encoded_front(rhs, c1, n1);
      65         240 :         if (c0 < c1)
      66          20 :             return -1;
      67         220 :         if (c1 < c0)
      68          15 :             return 1;
      69             :     }
      70          42 :     n0 += detail::decode_bytes_unsafe(lhs);
      71          42 :     n1 += detail::decode_bytes_unsafe(rhs);
      72          42 :     if (n0 == n1)
      73          21 :         return 0;
      74          21 :     if (n0 < n1)
      75           8 :         return -1;
      76          13 :     return 1;
      77             : }
      78             : 
      79             : void
      80        1104 : digest_encoded(
      81             :     core::string_view s,
      82             :     fnv_1a& hasher) noexcept
      83             : {
      84        1104 :     char c = 0;
      85        1104 :     std::size_t n = 0;
      86        1556 :     while(!s.empty())
      87             :     {
      88         452 :         pop_encoded_front(s, c, n);
      89         452 :         hasher.put(c);
      90             :     }
      91        1104 : }
      92             : 
      93             : int
      94         119 : ci_compare_encoded(
      95             :     core::string_view lhs,
      96             :     core::string_view rhs) noexcept
      97             : {
      98         119 :     std::size_t n0 = 0;
      99         119 :     std::size_t n1 = 0;
     100         119 :     char c0 = 0;
     101         119 :     char c1 = 0;
     102        1505 :     while (
     103        3145 :         !lhs.empty() &&
     104        1521 :         !rhs.empty())
     105             :     {
     106        1515 :         pop_encoded_front(lhs, c0, n0);
     107        1515 :         pop_encoded_front(rhs, c1, n1);
     108        1515 :         c0 = grammar::to_lower(c0);
     109        1515 :         c1 = grammar::to_lower(c1);
     110        1515 :         if (c0 < c1)
     111           8 :             return -1;
     112        1507 :         if (c1 < c0)
     113           2 :             return 1;
     114             :     }
     115         109 :     n0 += detail::decode_bytes_unsafe(lhs);
     116         109 :     n1 += detail::decode_bytes_unsafe(rhs);
     117         109 :     if (n0 == n1)
     118         102 :         return 0;
     119           7 :     if (n0 < n1)
     120           1 :         return -1;
     121           6 :     return 1;
     122             : }
     123             : 
     124             : void
     125         276 : ci_digest_encoded(
     126             :     core::string_view s,
     127             :     fnv_1a& hasher) noexcept
     128             : {
     129         276 :     char c = 0;
     130         276 :     std::size_t n = 0;
     131        1918 :     while(!s.empty())
     132             :     {
     133        1642 :         pop_encoded_front(s, c, n);
     134        1642 :         c = grammar::to_lower(c);
     135        1642 :         hasher.put(c);
     136             :     }
     137         276 : }
     138             : 
     139             : int
     140           8 : compare(
     141             :     core::string_view lhs,
     142             :     core::string_view rhs) noexcept
     143             : {
     144           8 :     auto rlen = (std::min)(lhs.size(), rhs.size());
     145          18 :     for (std::size_t i = 0; i < rlen; ++i)
     146             :     {
     147          17 :         char c0 = lhs[i];
     148          17 :         char c1 = rhs[i];
     149          17 :         if (c0 < c1)
     150           6 :             return -1;
     151          11 :         if (c1 < c0)
     152           1 :             return 1;
     153             :     }
     154           1 :     if ( lhs.size() == rhs.size() )
     155           1 :         return 0;
     156           0 :     if ( lhs.size() < rhs.size() )
     157           0 :         return -1;
     158           0 :     return 1;
     159             : }
     160             : 
     161             : int
     162         156 : ci_compare(
     163             :     core::string_view lhs,
     164             :     core::string_view rhs) noexcept
     165             : {
     166         156 :     auto rlen = (std::min)(lhs.size(), rhs.size());
     167         789 :     for (std::size_t i = 0; i < rlen; ++i)
     168             :     {
     169         640 :         char c0 = grammar::to_lower(lhs[i]);
     170         640 :         char c1 = grammar::to_lower(rhs[i]);
     171         640 :         if (c0 < c1)
     172           6 :             return -1;
     173         634 :         if (c1 < c0)
     174           1 :             return 1;
     175             :     }
     176         149 :     if ( lhs.size() == rhs.size() )
     177         142 :         return 0;
     178           7 :     if ( lhs.size() < rhs.size() )
     179           6 :         return -1;
     180           1 :     return 1;
     181             : }
     182             : 
     183             : void
     184         276 : ci_digest(
     185             :     core::string_view s,
     186             :     fnv_1a& hasher) noexcept
     187             : {
     188         866 :     for (char c: s)
     189             :     {
     190         590 :         c = grammar::to_lower(c);
     191         590 :         hasher.put(c);
     192             :     }
     193         276 : }
     194             : 
     195             : std::size_t
     196           0 : path_starts_with(
     197             :     core::string_view lhs,
     198             :     core::string_view rhs) noexcept
     199             : {
     200           0 :     auto consume_one = [](
     201             :         core::string_view::iterator& it,
     202             :         char &c)
     203             :     {
     204           0 :         if(*it != '%')
     205             :         {
     206           0 :             c = *it;
     207           0 :             ++it;
     208           0 :             return;
     209             :         }
     210           0 :         detail::decode_unsafe(
     211             :             &c,
     212             :             &c + 1,
     213             :             core::string_view(it, 3));
     214           0 :         if (c != '/')
     215             :         {
     216           0 :             it += 3;
     217           0 :             return;
     218             :         }
     219           0 :         c = *it;
     220           0 :         ++it;
     221             :     };
     222             : 
     223           0 :     auto it0 = lhs.begin();
     224           0 :     auto it1 = rhs.begin();
     225           0 :     auto end0 = lhs.end();
     226           0 :     auto end1 = rhs.end();
     227           0 :     char c0 = 0;
     228           0 :     char c1 = 0;
     229           0 :     while (
     230           0 :         it0 < end0 &&
     231           0 :         it1 < end1)
     232             :     {
     233           0 :         consume_one(it0, c0);
     234           0 :         consume_one(it1, c1);
     235           0 :         if (c0 != c1)
     236           0 :             return 0;
     237             :     }
     238           0 :     if (it1 == end1)
     239           0 :         return it0 - lhs.begin();
     240           0 :     return 0;
     241             : }
     242             : 
     243             : std::size_t
     244        2104 : path_ends_with(
     245             :     core::string_view lhs,
     246             :     core::string_view rhs) noexcept
     247             : {
     248        5800 :     auto consume_last = [](
     249             :         core::string_view::iterator& it,
     250             :         core::string_view::iterator& end,
     251             :         char& c)
     252             :     {
     253        9732 :         if ((end - it) < 3 ||
     254        3932 :             *(std::prev(end, 3)) != '%')
     255             :         {
     256        5768 :             c = *--end;
     257        5768 :             return;
     258             :         }
     259          32 :         detail::decode_unsafe(
     260             :             &c,
     261             :             &c + 1,
     262             :             core::string_view(std::prev(
     263             :                 end, 3), 3));
     264          32 :         if (c != '/')
     265             :         {
     266          32 :             end -= 3;
     267          32 :             return;
     268             :         }
     269           0 :         c = *--end;
     270             :     };
     271             : 
     272        2104 :     auto it0 = lhs.begin();
     273        2104 :     auto it1 = rhs.begin();
     274        2104 :     auto end0 = lhs.end();
     275        2104 :     auto end1 = rhs.end();
     276        2104 :     char c0 = 0;
     277        2104 :     char c1 = 0;
     278        1104 :     while(
     279        3208 :         it0 < end0 &&
     280        2974 :         it1 < end1)
     281             :     {
     282        2900 :         consume_last(it0, end0, c0);
     283        2900 :         consume_last(it1, end1, c1);
     284        2900 :         if (c0 != c1)
     285        1796 :             return 0;
     286             :     }
     287         308 :     if (it1 == end1)
     288         110 :         return lhs.end() - end0;
     289         198 :     return 0;
     290             : }
     291             : 
     292             : std::size_t
     293         833 : remove_dot_segments(
     294             :     char* dest0,
     295             :     char const* end,
     296             :     core::string_view s) noexcept
     297             : {
     298             :     // 1. The input buffer `s` is initialized with
     299             :     // the now-appended path components and the
     300             :     // output buffer `dest0` is initialized to
     301             :     // the empty string.
     302         833 :     char* dest = dest0;
     303             : 
     304             :     // Step 2 is a loop through 5 production rules:
     305             :     // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4
     306             :     //
     307             :     // There are no transitions between all rules,
     308             :     // which enables some optimizations.
     309             :     //
     310             :     // Initial:
     311             :     // - Rule A: handle initial dots
     312             :     // If the input buffer begins with a
     313             :     // prefix of "../" or "./", then remove
     314             :     // that prefix from the input buffer.
     315             :     // Rule A can only happen at the beginning.
     316             :     // Errata 4547: Keep "../" in the beginning
     317             :     // https://www.rfc-editor.org/errata/eid4547
     318             :     //
     319             :     // Then:
     320             :     // - Rule D: ignore a final ".." or "."
     321             :     // if the input buffer consists only  of "."
     322             :     // or "..", then remove that from the input
     323             :     // buffer.
     324             :     // Rule D can only happen after Rule A because:
     325             :     // - B and C write "/" to the input
     326             :     // - E writes "/" to input or returns
     327             :     //
     328             :     // Then:
     329             :     // - Rule B: ignore ".": write "/" to the input
     330             :     // - Rule C: apply "..": remove seg and write "/"
     331             :     // - Rule E: copy complete segment
     332             :     auto append =
     333        1473 :         [](char*& first, char const* last, core::string_view in)
     334             :     {
     335             :         // append `in` to `dest`
     336        1473 :         BOOST_ASSERT(in.size() <= std::size_t(last - first));
     337        1473 :         std::memmove(first, in.data(), in.size());
     338        1473 :         first += in.size();
     339             :         ignore_unused(last);
     340        1473 :     };
     341             : 
     342        9399 :     auto dot_starts_with = [](
     343             :         core::string_view str, core::string_view dots, std::size_t& n)
     344             :     {
     345             :         // starts_with for encoded/decoded dots
     346             :         // or decoded otherwise. return how many
     347             :         // chars in str match the dots
     348        9399 :         n = 0;
     349       16477 :         for (char c: dots)
     350             :         {
     351       15982 :             if (str.empty())
     352             :             {
     353        1219 :                 n = 0;
     354        1219 :                 return false;
     355             :             }
     356       14763 :             else if (str.starts_with(c))
     357             :             {
     358        7074 :                 str.remove_prefix(1);
     359        7074 :                 ++n;
     360             :             }
     361        7689 :             else if (str.size() > 2 &&
     362        6177 :                      str[0] == '%' &&
     363       13886 :                      str[1] == '2' &&
     364          20 :                      (str[2] == 'e' ||
     365          19 :                       str[2] == 'E'))
     366             :             {
     367           4 :                 str.remove_prefix(3);
     368           4 :                 n += 3;
     369             :             }
     370             :             else
     371             :             {
     372        7685 :                 n = 0;
     373        7685 :                 return false;
     374             :             }
     375             :         }
     376         495 :         return true;
     377             :     };
     378             : 
     379        4677 :     auto dot_equal = [&dot_starts_with](
     380        4677 :         core::string_view str, core::string_view dots)
     381             :     {
     382        4677 :         std::size_t n = 0;
     383        4677 :         dot_starts_with(str, dots, n);
     384        4677 :         return n == str.size();
     385         833 :     };
     386             : 
     387             :     // Rule A
     388             :     std::size_t n;
     389         857 :     while (!s.empty())
     390             :     {
     391         775 :         if (dot_starts_with(s, "../", n))
     392             :         {
     393             :             // Errata 4547
     394           8 :             append(dest, end, "../");
     395           8 :             s.remove_prefix(n);
     396           8 :             continue;
     397             :         }
     398         767 :         else if (!dot_starts_with(s, "./", n))
     399             :         {
     400         751 :             break;
     401             :         }
     402          16 :         s.remove_prefix(n);
     403             :     }
     404             : 
     405             :     // Rule D
     406         833 :     if( dot_equal(s, "."))
     407             :     {
     408          84 :         s = {};
     409             :     }
     410         749 :     else if( dot_equal(s, "..") )
     411             :     {
     412             :         // Errata 4547
     413           4 :         append(dest, end, "..");
     414           4 :         s = {};
     415             :     }
     416             : 
     417             :     // 2. While the input buffer is not empty,
     418             :     // loop as follows:
     419        2411 :     while (!s.empty())
     420             :     {
     421             :         // Rule B
     422        1614 :         if (dot_starts_with(s, "/./", n))
     423             :         {
     424          39 :             s.remove_prefix(n - 1);
     425          39 :             continue;
     426             :         }
     427        1575 :         if (dot_equal(s, "/."))
     428             :         {
     429             :             // We can't remove "." from a core::string_view
     430             :             // So what we do here is equivalent to
     431             :             // replacing s with '/' as required
     432             :             // in Rule B and executing the next
     433             :             // iteration, which would append this
     434             :             // '/' to  the output, as required by
     435             :             // Rule E
     436           9 :             append(dest, end, s.substr(0, 1));
     437           9 :             s = {};
     438           9 :             break;
     439             :         }
     440             : 
     441             :         // Rule C
     442        1566 :         if (dot_starts_with(s, "/../", n))
     443             :         {
     444         330 :             std::size_t p = core::string_view(
     445         165 :                 dest0, dest - dest0).find_last_of('/');
     446         165 :             if (p != core::string_view::npos)
     447             :             {
     448             :                 // output has multiple segments
     449             :                 // "erase" [p, end] if not "/.."
     450         119 :                 core::string_view last_seg(dest0 + p, dest - (dest0 + p));
     451         119 :                 if (!dot_equal(last_seg, "/.."))
     452         110 :                     dest = dest0 + p;
     453             :                 else
     454           9 :                     append(dest, end, "/..");
     455             :             }
     456          46 :             else if (dest0 != dest)
     457             :             {
     458             :                 // one segment in the output
     459           2 :                 dest = dest0;
     460           2 :                 s.remove_prefix(1);
     461             :             }
     462             :             else
     463             :             {
     464             :                 // output is empty
     465          44 :                 append(dest, end, "/..");
     466             :             }
     467         165 :             s.remove_prefix(n-1);
     468         165 :             continue;
     469             :         }
     470        1401 :         if (dot_equal(s, "/.."))
     471             :         {
     472          54 :             std::size_t p = core::string_view(
     473          27 :                 dest0, dest - dest0).find_last_of('/');
     474          27 :             if (p != core::string_view::npos)
     475             :             {
     476             :                 // erase [p, end]
     477          16 :                 dest = dest0 + p;
     478          16 :                 append(dest, end, "/");
     479             :             }
     480          11 :             else if (dest0 != dest)
     481             :             {
     482           2 :                 dest = dest0;
     483             :             }
     484             :             else
     485             :             {
     486           9 :                 append(dest, end, "/..");
     487             :             }
     488          27 :             s = {};
     489          27 :             break;
     490             :         }
     491             : 
     492             :         // Rule E
     493        1374 :         std::size_t p = s.find_first_of('/', 1);
     494        1374 :         if (p != core::string_view::npos)
     495             :         {
     496         665 :             append(dest, end, s.substr(0, p));
     497         665 :             s.remove_prefix(p);
     498             :         }
     499             :         else
     500             :         {
     501         709 :             append(dest, end, s);
     502         709 :             s = {};
     503             :         }
     504             :     }
     505             : 
     506             :     // 3. Finally, the output buffer is set
     507             :     // as the result of remove_dot_segments,
     508             :     // and we return its size
     509         833 :     return dest - dest0;
     510             : }
     511             : 
     512             : char
     513        1138 : path_pop_back( core::string_view& s )
     514             : {
     515        1656 :     if (s.size() < 3 ||
     516         518 :         *std::prev(s.end(), 3) != '%')
     517             :     {
     518        1090 :         char c = s.back();
     519        1090 :         s.remove_suffix(1);
     520        1090 :         return c;
     521             :     }
     522          48 :     char c = 0;
     523          96 :     detail::decode_unsafe(
     524          96 :         &c, &c + 1, s.substr(s.size() - 3));
     525          48 :     if (c != '/')
     526             :     {
     527          44 :         s.remove_suffix(3);
     528          44 :         return c;
     529             :     }
     530           4 :     c = s.back();
     531           4 :     s.remove_suffix(1);
     532           4 :     return c;
     533             : };
     534             : 
     535             : void
     536         506 : pop_last_segment(
     537             :     core::string_view& s,
     538             :     core::string_view& c,
     539             :     std::size_t& level,
     540             :     bool r) noexcept
     541             : {
     542         506 :     c = {};
     543         506 :     std::size_t n = 0;
     544         668 :     while (!s.empty())
     545             :     {
     546             :         // B.  if the input buffer begins with a
     547             :         // prefix of "/./" or "/.", where "." is
     548             :         // a complete path segment, then replace
     549             :         // that prefix with "/" in the input
     550             :         // buffer; otherwise,
     551         550 :         n = detail::path_ends_with(s, "/./");
     552         550 :         if (n)
     553             :         {
     554          10 :             c = s.substr(s.size() - n);
     555          10 :             s.remove_suffix(n);
     556          10 :             continue;
     557             :         }
     558         540 :         n = detail::path_ends_with(s, "/.");
     559         540 :         if (n)
     560             :         {
     561          12 :             c = s.substr(s.size() - n, 1);
     562          12 :             s.remove_suffix(n);
     563          12 :             continue;
     564             :         }
     565             : 
     566             :         // C. if the input buffer begins with a
     567             :         // prefix of "/../" or "/..", where ".."
     568             :         // is a complete path segment, then
     569             :         // replace that prefix with "/" in the
     570             :         // input buffer and remove the last
     571             :         // segment and its preceding "/"
     572             :         // (if any) from the output buffer
     573             :         // otherwise,
     574         528 :         n = detail::path_ends_with(s, "/../");
     575         528 :         if (n)
     576             :         {
     577          42 :             c = s.substr(s.size() - n);
     578          42 :             s.remove_suffix(n);
     579          42 :             ++level;
     580          42 :             continue;
     581             :         }
     582         486 :         n = detail::path_ends_with(s, "/..");
     583         486 :         if (n)
     584             :         {
     585          46 :             c = s.substr(s.size() - n);
     586          46 :             s.remove_suffix(n);
     587          46 :             ++level;
     588          46 :             continue;
     589             :         }
     590             : 
     591             :         // E.  move the first path segment in the
     592             :         // input buffer to the end of the output
     593             :         // buffer, including the initial "/"
     594             :         // character (if any) and any subsequent
     595             :         // characters up to, but not including,
     596             :         // the next "/" character or the end of
     597             :         // the input buffer.
     598         440 :         std::size_t p = s.size() > 1
     599         440 :             ? s.find_last_of('/', s.size() - 2)
     600         440 :             : core::string_view::npos;
     601         440 :         if (p != core::string_view::npos)
     602             :         {
     603         272 :             c = s.substr(p + 1);
     604         272 :             s.remove_suffix(c.size());
     605             :         }
     606             :         else
     607             :         {
     608         168 :             c = s;
     609         168 :             s = {};
     610             :         }
     611             : 
     612         440 :         if (level == 0)
     613         388 :             return;
     614          52 :         if (!s.empty())
     615          42 :             --level;
     616             :     }
     617             :     // we still need to skip n_skip + 1
     618             :     // but the string is empty
     619         118 :     if (r && level)
     620             :     {
     621          34 :         c = "/";
     622          34 :         level = 0;
     623          34 :         return;
     624             :     }
     625          84 :     else if (level)
     626             :     {
     627           4 :         if (c.empty())
     628           0 :             c = "/..";
     629             :         else
     630           4 :             c = "/../";
     631           4 :         --level;
     632           4 :         return;
     633             :     }
     634          80 :     c = {};
     635             : }
     636             : 
     637             : void
     638         276 : normalized_path_digest(
     639             :     core::string_view s,
     640             :     bool remove_unmatched,
     641             :     fnv_1a& hasher) noexcept
     642             : {
     643         276 :     core::string_view child;
     644         276 :     std::size_t level = 0;
     645         230 :     do
     646             :     {
     647         506 :         pop_last_segment(
     648             :             s, child, level, remove_unmatched);
     649        1644 :         while (!child.empty())
     650             :         {
     651        1138 :             char c = path_pop_back(child);
     652        1138 :             hasher.put(c);
     653             :         }
     654             :     }
     655         506 :     while (!s.empty());
     656         276 : }
     657             : 
     658             : // compare segments as if there were a normalized
     659             : int
     660         168 : segments_compare(
     661             :     segments_encoded_view seg0,
     662             :     segments_encoded_view seg1) noexcept
     663             : {
     664             :     // calculate path size as if it were normalized
     665             :     auto normalized_size =
     666         336 :         [](segments_encoded_view seg) -> std::size_t
     667             :     {
     668         336 :         if (seg.empty())
     669         102 :             return seg.is_absolute();
     670             : 
     671         234 :         std::size_t n = 0;
     672         234 :         std::size_t skip = 0;
     673         234 :         auto begin = seg.begin();
     674         234 :         auto it = seg.end();
     675         892 :         while (it != begin)
     676             :         {
     677         658 :             --it;
     678         658 :             decode_view dseg = **it;
     679         658 :             if (dseg == "..")
     680         167 :                 ++skip;
     681         491 :             else if (dseg != ".")
     682             :             {
     683         453 :                 if (skip)
     684          85 :                     --skip;
     685             :                 else
     686         368 :                     n += dseg.size() + 1;
     687             :             }
     688             :         }
     689         234 :         n += skip * 3;
     690         234 :         n -= !seg.is_absolute();
     691         234 :         return n;
     692             :     };
     693             : 
     694             :     // find the normalized size for the comparison
     695         168 :     std::size_t n0 = normalized_size(seg0);
     696         168 :     std::size_t n1 = normalized_size(seg1);
     697         168 :     std::size_t n00 = n0;
     698         168 :     std::size_t n10 = n1;
     699             : 
     700             :     // consume the last char from a segment range
     701             :     auto consume_last =
     702        1632 :         [](
     703             :             std::size_t& n,
     704             :             decode_view& dseg,
     705             :             segments_encoded_view::iterator& begin,
     706             :             segments_encoded_view::iterator& it,
     707             :             decode_view::iterator& cit,
     708             :             std::size_t& skip,
     709             :             bool& at_slash) -> char
     710             :     {
     711        1632 :         if (cit != dseg.begin())
     712             :         {
     713             :             // return last char from current segment
     714        1005 :             at_slash = false;
     715        1005 :             --cit;
     716        1005 :             --n;
     717        1005 :             return *cit;
     718             :         }
     719             : 
     720         627 :         if (!at_slash)
     721             :         {
     722             :             // current segment dseg is over and
     723             :             // previous char was not a slash
     724             :             // so we output one
     725         367 :             at_slash = true;
     726         367 :             --n;
     727         367 :             return '/';
     728             :         }
     729             : 
     730             :         // current segment dseg is over and
     731             :         // last char was already the slash
     732             :         // between segments, so take the
     733             :         // next final segment to consume
     734         260 :         at_slash = false;
     735         498 :         while (cit == dseg.begin())
     736             :         {
     737             :             // take next segment
     738         498 :             if (it != begin)
     739         376 :                 --it;
     740             :             else
     741         122 :                 break;
     742         376 :             if (**it == "..")
     743             :             {
     744             :                 // skip next if this is ".."
     745         140 :                 ++skip;
     746             :             }
     747         236 :             else if (**it != ".")
     748             :             {
     749         208 :                 if (skip)
     750             :                 {
     751             :                     // discount skips
     752          70 :                     --skip;
     753             :                 }
     754             :                 else
     755             :                 {
     756             :                     // or update current seg
     757         138 :                     dseg = **it;
     758         138 :                     cit = dseg.end();
     759         138 :                     break;
     760             :                 }
     761             :             }
     762             :         }
     763             :         // consume from the new current
     764             :         // segment
     765         260 :         --n;
     766         260 :         if (cit != dseg.begin())
     767             :         {
     768             :             // in the general case, we consume
     769             :             // one more character from the end
     770         123 :             --cit;
     771         123 :             return *cit;
     772             :         }
     773             : 
     774             :         // nothing left to consume in the
     775             :         // current and new segment
     776         137 :         if (it == begin)
     777             :         {
     778             :             // if this is the first
     779             :             // segment, the segments are
     780             :             // over and there can only
     781             :             // be repetitions of "../" to
     782             :             // output
     783         128 :             return "/.."[n % 3];
     784             :         }
     785             :         // at other segments, we need
     786             :         // a slash to transition to the
     787             :         // next segment
     788           9 :         at_slash = true;
     789           9 :         return '/';
     790             :     };
     791             : 
     792             :     // consume final segments from seg0 that
     793             :     // should not influence the comparison
     794         168 :     auto begin0 = seg0.begin();
     795         168 :     auto it0 = seg0.end();
     796         168 :     decode_view dseg0;
     797         168 :     if (it0 != seg0.begin())
     798             :     {
     799         117 :         --it0;
     800         117 :         dseg0 = **it0;
     801             :     }
     802         168 :     decode_view::iterator cit0 = dseg0.end();
     803         168 :     std::size_t skip0 = 0;
     804         168 :     bool at_slash0 = true;
     805         302 :     while (n0 > n1)
     806             :     {
     807         134 :         consume_last(n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
     808             :     }
     809             : 
     810             :     // consume final segments from seg1 that
     811             :     // should not influence the comparison
     812         168 :     auto begin1 = seg1.begin();
     813         168 :     auto it1 = seg1.end();
     814         168 :     decode_view dseg1;
     815         168 :     if (it1 != seg1.begin())
     816             :     {
     817         117 :         --it1;
     818         117 :         dseg1 = **it1;
     819             :     }
     820         168 :     decode_view::iterator cit1 = dseg1.end();
     821         168 :     std::size_t skip1 = 0;
     822         168 :     bool at_slash1 = true;
     823         202 :     while (n1 > n0)
     824             :     {
     825          34 :         consume_last(n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
     826             :     }
     827             : 
     828         168 :     int cmp = 0;
     829         900 :     while (n0)
     830             :     {
     831         732 :         char c0 = consume_last(
     832             :             n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
     833         732 :         char c1 = consume_last(
     834             :             n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
     835         732 :         if (c0 < c1)
     836          36 :             cmp = -1;
     837         696 :         else if (c1 < c0)
     838          41 :             cmp = +1;
     839             :     }
     840             : 
     841         168 :     if (cmp != 0)
     842          41 :         return cmp;
     843         127 :     if ( n00 == n10 )
     844         125 :         return 0;
     845           2 :     if ( n00 < n10 )
     846           1 :         return -1;
     847           1 :     return 1;
     848             : }
     849             : 
     850             : } // detail
     851             : } // urls
     852             : } // boost
     853             : 
     854             : #endif

Generated by: LCOV version 1.15