LCOV - code coverage report
Current view: top level - libs/url/src/detail - normalize.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 399 402 99.3 %
Date: 2024-04-08 19:38:36 Functions: 20 20 100.0 %

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

Generated by: LCOV version 1.15