LCOV - code coverage report
Current view: top level - libs/url/src/detail - normalize.cpp (source / functions) Hit Total Coverage
Test: coverage_filtered.info Lines: 391 428 91.4 %
Date: 2024-01-19 15:42:53 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 "decode.hpp"
      17             : #include <boost/url/segments_encoded_view.hpp>
      18             : #include <boost/url/grammar/ci_string.hpp>
      19             : #include <boost/assert.hpp>
      20             : #include <boost/core/ignore_unused.hpp>
      21             : #include <cstring>
      22             : #include "normalize.hpp"
      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         831 : remove_dot_segments(
     294             :     char* dest0,
     295             :     char const* end,
     296             :     core::string_view input) 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         831 :     char* dest = dest0;
     303         831 :     bool const is_absolute = input.starts_with('/');
     304             : 
     305             :     // Step 2 is a loop through 5 production rules:
     306             :     // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4
     307             :     //
     308             :     // There are no transitions between all rules,
     309             :     // which enables some optimizations.
     310             :     //
     311             :     // Initial:
     312             :     // - Rule A: handle initial dots
     313             :     // If the input buffer begins with a
     314             :     // prefix of "../" or "./", then remove
     315             :     // that prefix from the input buffer.
     316             :     // Rule A can only happen at the beginning.
     317             :     // Errata 4547: Keep "../" in the beginning
     318             :     // https://www.rfc-editor.org/errata/eid4547
     319             :     //
     320             :     // Then:
     321             :     // - Rule D: ignore a final ".." or "."
     322             :     // if the input buffer consists only  of "."
     323             :     // or "..", then remove that from the input
     324             :     // buffer.
     325             :     // Rule D can only happen after Rule A because:
     326             :     // - B and C write "/" to the input
     327             :     // - E writes "/" to input or returns
     328             :     //
     329             :     // Then:
     330             :     // - Rule B: ignore ".": write "/" to the input
     331             :     // - Rule C: apply "..": remove seg and write "/"
     332             :     // - Rule E: copy complete segment
     333             :     auto append =
     334        1491 :         [](char*& first, char const* last, core::string_view in)
     335             :     {
     336             :         // append `in` to `dest`
     337        1491 :         BOOST_ASSERT(in.size() <= std::size_t(last - first));
     338        1491 :         std::memmove(first, in.data(), in.size());
     339        1491 :         first += in.size();
     340             :         ignore_unused(last);
     341        1491 :     };
     342             : 
     343        9555 :     auto dot_starts_with = [](
     344             :         core::string_view str, core::string_view dots, std::size_t& n)
     345             :     {
     346             :         // starts_with for encoded/decoded dots
     347             :         // or decoded otherwise. return how many
     348             :         // chars in str match the dots
     349        9555 :         n = 0;
     350       16906 :         for (char c: dots)
     351             :         {
     352       16356 :             if (str.starts_with(c))
     353             :             {
     354        7351 :                 str.remove_prefix(1);
     355        7351 :                 ++n;
     356             :             }
     357        9005 :             else if (str.size() > 2 &&
     358        6243 :                      str[0] == '%' &&
     359       15264 :                      str[1] == '2' &&
     360          16 :                      (str[2] == 'e' ||
     361          16 :                       str[2] == 'E'))
     362             :             {
     363           0 :                 str.remove_prefix(3);
     364           0 :                 n += 3;
     365             :             }
     366             :             else
     367             :             {
     368        9005 :                 n = 0;
     369        9005 :                 return false;
     370             :             }
     371             :         }
     372         550 :         return true;
     373             :     };
     374             : 
     375        4773 :     auto dot_equal = [&dot_starts_with](
     376        4773 :         core::string_view str, core::string_view dots)
     377             :     {
     378        4773 :         std::size_t n = 0;
     379        4773 :         dot_starts_with(str, dots, n);
     380        4773 :         return n == str.size();
     381         831 :     };
     382             : 
     383             :     // Rule A
     384             :     std::size_t n;
     385         847 :     while (!input.empty())
     386             :     {
     387         766 :         if (dot_starts_with(input, "../", n))
     388             :         {
     389             :             // Errata 4547
     390           4 :             append(dest, end, "../");
     391           4 :             input.remove_prefix(n);
     392           4 :             continue;
     393             :         }
     394         762 :         else if (!dot_starts_with(input, "./", n))
     395             :         {
     396         750 :             break;
     397             :         }
     398          12 :         input.remove_prefix(n);
     399             :     }
     400             : 
     401             :     // Rule D
     402         831 :     if( dot_equal(input, "."))
     403             :     {
     404          82 :         input = {};
     405             :     }
     406         749 :     else if( dot_equal(input, "..") )
     407             :     {
     408             :         // Errata 4547
     409           3 :         append(dest, end, "..");
     410           3 :         input = {};
     411             :     }
     412             : 
     413             :     // 2. While the input buffer is not empty,
     414             :     // loop as follows:
     415        2439 :     while (!input.empty())
     416             :     {
     417             :         // Rule B
     418        1647 :         bool const is_dot_seg = dot_starts_with(input, "/./", n);
     419        1647 :         if (is_dot_seg)
     420             :         {
     421          32 :             input.remove_prefix(n - 1);
     422          32 :             continue;
     423             :         }
     424             : 
     425        1615 :         bool const is_final_dot_seg = dot_equal(input, "/.");
     426        1615 :         if (is_final_dot_seg)
     427             :         {
     428             :             // We can't remove "." from a core::string_view
     429             :             // So what we do here is equivalent to
     430             :             // replacing s with '/' as required
     431             :             // in Rule B and executing the next
     432             :             // iteration, which would append this
     433             :             // '/' to  the output, as required by
     434             :             // Rule E
     435           8 :             append(dest, end, input.substr(0, 1));
     436           8 :             input = {};
     437           8 :             break;
     438             :         }
     439             : 
     440             :         // Rule C
     441        1607 :         bool const is_dotdot_seg = dot_starts_with(input, "/../", n);
     442        1607 :         if (is_dotdot_seg)
     443             :         {
     444         193 :             core::string_view cur_out(dest0, dest - dest0);
     445         193 :             std::size_t p = cur_out.find_last_of('/');
     446         193 :             bool const has_multiple_segs = p != core::string_view::npos;
     447         193 :             if (has_multiple_segs)
     448             :             {
     449             :                 // output has multiple segments
     450             :                 // "erase" [p, end] if not "/.."
     451         132 :                 core::string_view last_seg(dest0 + p, dest - (dest0 + p));
     452         132 :                 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
     453         132 :                 if (!prev_is_dotdot_seg)
     454             :                 {
     455         121 :                     dest = dest0 + p;
     456             :                 }
     457             :                 else
     458             :                 {
     459          11 :                     append(dest, end, "/..");
     460             :                 }
     461             :             }
     462          61 :             else if (dest0 != dest)
     463             :             {
     464             :                 // Only one segment in the output: remove it
     465          11 :                 core::string_view last_seg(dest0, dest - dest0);
     466          11 :                 bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
     467          11 :                 if (!prev_is_dotdot_seg)
     468             :                 {
     469           9 :                     dest = dest0;
     470           9 :                     if (!is_absolute)
     471             :                     {
     472           9 :                         input.remove_prefix(1);
     473             :                     }
     474             :                 }
     475             :                 else
     476             :                 {
     477           2 :                     append(dest, end, "/..");
     478             :                 }
     479             :             }
     480             :             else
     481             :             {
     482             :                 // Output is empty
     483          50 :                 if (is_absolute)
     484             :                 {
     485          50 :                     append(dest, end, "/..");
     486             :                 }
     487             :                 else
     488             :                 {
     489           0 :                     append(dest, end, "..");
     490             :                 }
     491             :             }
     492         193 :             input.remove_prefix(n - 1);
     493         193 :             continue;
     494             :         }
     495             : 
     496        1414 :         bool const is_final_dotdot_seg = dot_equal(input, "/..");
     497        1414 :         if (is_final_dotdot_seg)
     498             :         {
     499          31 :             core::string_view cur_out(dest0, dest - dest0);
     500          31 :             std::size_t p = cur_out.find_last_of('/');
     501          31 :             bool const has_multiple_segs = p != core::string_view::npos;
     502          31 :             if (has_multiple_segs)
     503             :             {
     504             :                 // output has multiple segments
     505             :                 // "erase" [p, end] if not "/.."
     506          18 :                 core::string_view last_seg(dest0 + p, dest - (dest0 + p));
     507          18 :                 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
     508          18 :                 if (!prev_is_dotdot_seg)
     509             :                 {
     510          14 :                     dest = dest0 + p;
     511          14 :                     append(dest, end, "/");
     512             :                 }
     513             :                 else
     514             :                 {
     515           4 :                     append(dest, end, "/..");
     516             :                 }
     517             :             }
     518          13 :             else if (dest0 != dest)
     519             :             {
     520             :                 // Only one segment in the output: remove it
     521           3 :                 core::string_view last_seg(dest0, dest - dest0);
     522           3 :                 bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
     523           3 :                 if (!prev_is_dotdot_seg) {
     524           1 :                     dest = dest0;
     525             :                 }
     526             :                 else
     527             :                 {
     528           2 :                     append(dest, end, "/..");
     529             :                 }
     530             :             }
     531             :             else
     532             :             {
     533             :                 // Output is empty: append dotdot
     534          10 :                 if (is_absolute)
     535             :                 {
     536          10 :                     append(dest, end, "/..");
     537             :                 }
     538             :                 else
     539             :                 {
     540           0 :                     append(dest, end, "..");
     541             :                 }
     542             :             }
     543          31 :             input = {};
     544          31 :             break;
     545             :         }
     546             : 
     547             :         // Rule E
     548        1383 :         std::size_t p = input.find_first_of('/', 1);
     549        1383 :         if (p != core::string_view::npos)
     550             :         {
     551         676 :             append(dest, end, input.substr(0, p));
     552         676 :             input.remove_prefix(p);
     553             :         }
     554             :         else
     555             :         {
     556         707 :             append(dest, end, input);
     557         707 :             input = {};
     558             :         }
     559             :     }
     560             : 
     561             :     // 3. Finally, the output buffer is set
     562             :     // as the result of remove_dot_segments,
     563             :     // and we return its size
     564         831 :     return dest - dest0;
     565             : }
     566             : 
     567             : char
     568        1138 : path_pop_back( core::string_view& s )
     569             : {
     570        1656 :     if (s.size() < 3 ||
     571         518 :         *std::prev(s.end(), 3) != '%')
     572             :     {
     573        1090 :         char c = s.back();
     574        1090 :         s.remove_suffix(1);
     575        1090 :         return c;
     576             :     }
     577          48 :     char c = 0;
     578          96 :     detail::decode_unsafe(
     579          96 :         &c, &c + 1, s.substr(s.size() - 3));
     580          48 :     if (c != '/')
     581             :     {
     582          44 :         s.remove_suffix(3);
     583          44 :         return c;
     584             :     }
     585           4 :     c = s.back();
     586           4 :     s.remove_suffix(1);
     587           4 :     return c;
     588             : };
     589             : 
     590             : void
     591         506 : pop_last_segment(
     592             :     core::string_view& s,
     593             :     core::string_view& c,
     594             :     std::size_t& level,
     595             :     bool r) noexcept
     596             : {
     597         506 :     c = {};
     598         506 :     std::size_t n = 0;
     599         668 :     while (!s.empty())
     600             :     {
     601             :         // B.  if the input buffer begins with a
     602             :         // prefix of "/./" or "/.", where "." is
     603             :         // a complete path segment, then replace
     604             :         // that prefix with "/" in the input
     605             :         // buffer; otherwise,
     606         550 :         n = detail::path_ends_with(s, "/./");
     607         550 :         if (n)
     608             :         {
     609          10 :             c = s.substr(s.size() - n);
     610          10 :             s.remove_suffix(n);
     611          10 :             continue;
     612             :         }
     613         540 :         n = detail::path_ends_with(s, "/.");
     614         540 :         if (n)
     615             :         {
     616          12 :             c = s.substr(s.size() - n, 1);
     617          12 :             s.remove_suffix(n);
     618          12 :             continue;
     619             :         }
     620             : 
     621             :         // C. if the input buffer begins with a
     622             :         // prefix of "/../" or "/..", where ".."
     623             :         // is a complete path segment, then
     624             :         // replace that prefix with "/" in the
     625             :         // input buffer and remove the last
     626             :         // segment and its preceding "/"
     627             :         // (if any) from the output buffer
     628             :         // otherwise,
     629         528 :         n = detail::path_ends_with(s, "/../");
     630         528 :         if (n)
     631             :         {
     632          42 :             c = s.substr(s.size() - n);
     633          42 :             s.remove_suffix(n);
     634          42 :             ++level;
     635          42 :             continue;
     636             :         }
     637         486 :         n = detail::path_ends_with(s, "/..");
     638         486 :         if (n)
     639             :         {
     640          46 :             c = s.substr(s.size() - n);
     641          46 :             s.remove_suffix(n);
     642          46 :             ++level;
     643          46 :             continue;
     644             :         }
     645             : 
     646             :         // E.  move the first path segment in the
     647             :         // input buffer to the end of the output
     648             :         // buffer, including the initial "/"
     649             :         // character (if any) and any subsequent
     650             :         // characters up to, but not including,
     651             :         // the next "/" character or the end of
     652             :         // the input buffer.
     653         440 :         std::size_t p = s.size() > 1
     654         440 :             ? s.find_last_of('/', s.size() - 2)
     655         440 :             : core::string_view::npos;
     656         440 :         if (p != core::string_view::npos)
     657             :         {
     658         272 :             c = s.substr(p + 1);
     659         272 :             s.remove_suffix(c.size());
     660             :         }
     661             :         else
     662             :         {
     663         168 :             c = s;
     664         168 :             s = {};
     665             :         }
     666             : 
     667         440 :         if (level == 0)
     668         388 :             return;
     669          52 :         if (!s.empty())
     670          42 :             --level;
     671             :     }
     672             :     // we still need to skip n_skip + 1
     673             :     // but the string is empty
     674         118 :     if (r && level)
     675             :     {
     676          34 :         c = "/";
     677          34 :         level = 0;
     678          34 :         return;
     679             :     }
     680          84 :     else if (level)
     681             :     {
     682           4 :         if (c.empty())
     683           0 :             c = "/..";
     684             :         else
     685           4 :             c = "/../";
     686           4 :         --level;
     687           4 :         return;
     688             :     }
     689          80 :     c = {};
     690             : }
     691             : 
     692             : void
     693         276 : normalized_path_digest(
     694             :     core::string_view s,
     695             :     bool remove_unmatched,
     696             :     fnv_1a& hasher) noexcept
     697             : {
     698         276 :     core::string_view child;
     699         276 :     std::size_t level = 0;
     700         230 :     do
     701             :     {
     702         506 :         pop_last_segment(
     703             :             s, child, level, remove_unmatched);
     704        1644 :         while (!child.empty())
     705             :         {
     706        1138 :             char c = path_pop_back(child);
     707        1138 :             hasher.put(c);
     708             :         }
     709             :     }
     710         506 :     while (!s.empty());
     711         276 : }
     712             : 
     713             : // compare segments as if there were a normalized
     714             : int
     715         168 : 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         336 :         [](segments_encoded_view seg) -> std::size_t
     722             :     {
     723         336 :         if (seg.empty())
     724         102 :             return seg.is_absolute();
     725             : 
     726         234 :         std::size_t n = 0;
     727         234 :         std::size_t skip = 0;
     728         234 :         auto begin = seg.begin();
     729         234 :         auto it = seg.end();
     730         892 :         while (it != begin)
     731             :         {
     732         658 :             --it;
     733         658 :             decode_view dseg = **it;
     734         658 :             if (dseg == "..")
     735         167 :                 ++skip;
     736         491 :             else if (dseg != ".")
     737             :             {
     738         453 :                 if (skip)
     739          85 :                     --skip;
     740             :                 else
     741         368 :                     n += dseg.size() + 1;
     742             :             }
     743             :         }
     744         234 :         n += skip * 3;
     745         234 :         n -= !seg.is_absolute();
     746         234 :         return n;
     747             :     };
     748             : 
     749             :     // find the normalized size for the comparison
     750         168 :     std::size_t n0 = normalized_size(seg0);
     751         168 :     std::size_t n1 = normalized_size(seg1);
     752         168 :     std::size_t n00 = n0;
     753         168 :     std::size_t n10 = n1;
     754             : 
     755             :     // consume the last char from a segment range
     756             :     auto consume_last =
     757        1632 :         [](
     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        1632 :         if (cit != dseg.begin())
     767             :         {
     768             :             // return last char from current segment
     769        1005 :             at_slash = false;
     770        1005 :             --cit;
     771        1005 :             --n;
     772        1005 :             return *cit;
     773             :         }
     774             : 
     775         627 :         if (!at_slash)
     776             :         {
     777             :             // current segment dseg is over and
     778             :             // previous char was not a slash
     779             :             // so we output one
     780         367 :             at_slash = true;
     781         367 :             --n;
     782         367 :             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         168 :     auto begin0 = seg0.begin();
     850         168 :     auto it0 = seg0.end();
     851         168 :     decode_view dseg0;
     852         168 :     if (it0 != seg0.begin())
     853             :     {
     854         117 :         --it0;
     855         117 :         dseg0 = **it0;
     856             :     }
     857         168 :     decode_view::iterator cit0 = dseg0.end();
     858         168 :     std::size_t skip0 = 0;
     859         168 :     bool at_slash0 = true;
     860         302 :     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         168 :     auto begin1 = seg1.begin();
     868         168 :     auto it1 = seg1.end();
     869         168 :     decode_view dseg1;
     870         168 :     if (it1 != seg1.begin())
     871             :     {
     872         117 :         --it1;
     873         117 :         dseg1 = **it1;
     874             :     }
     875         168 :     decode_view::iterator cit1 = dseg1.end();
     876         168 :     std::size_t skip1 = 0;
     877         168 :     bool at_slash1 = true;
     878         202 :     while (n1 > n0)
     879             :     {
     880          34 :         consume_last(n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
     881             :     }
     882             : 
     883         168 :     int cmp = 0;
     884         900 :     while (n0)
     885             :     {
     886         732 :         char c0 = consume_last(
     887             :             n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
     888         732 :         char c1 = consume_last(
     889             :             n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
     890         732 :         if (c0 < c1)
     891          36 :             cmp = -1;
     892         696 :         else if (c1 < c0)
     893          41 :             cmp = +1;
     894             :     }
     895             : 
     896         168 :     if (cmp != 0)
     897          41 :         return cmp;
     898         127 :     if ( n00 == n10 )
     899         125 :         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             : #endif

Generated by: LCOV version 1.15