1  
//
1  
//
2  
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3  
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
3  
// Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com)
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/boostorg/url
8  
// Official repository: https://github.com/boostorg/url
9  
//
9  
//
10  

10  

11  

11  

12  
#include <boost/url/detail/config.hpp>
12  
#include <boost/url/detail/config.hpp>
13  
#include <boost/url/decode_view.hpp>
13  
#include <boost/url/decode_view.hpp>
14  
#include <boost/url/detail/decode.hpp>
14  
#include <boost/url/detail/decode.hpp>
15  
#include <boost/url/segments_encoded_view.hpp>
15  
#include <boost/url/segments_encoded_view.hpp>
16  
#include <boost/url/grammar/ci_string.hpp>
16  
#include <boost/url/grammar/ci_string.hpp>
17  
#include <boost/url/grammar/lut_chars.hpp>
17  
#include <boost/url/grammar/lut_chars.hpp>
18  
#include <boost/assert.hpp>
18  
#include <boost/assert.hpp>
19  
#include <boost/core/ignore_unused.hpp>
19  
#include <boost/core/ignore_unused.hpp>
20  
#include <cstring>
20  
#include <cstring>
21  
#include "normalize.hpp"
21  
#include "normalize.hpp"
22  

22  

23  
namespace boost {
23  
namespace boost {
24  
namespace urls {
24  
namespace urls {
25  
namespace detail {
25  
namespace detail {
26  

26  

27  
void
27  
void
28  
pop_encoded_front(
28  
pop_encoded_front(
29  
    core::string_view& s,
29  
    core::string_view& s,
30  
    char& c,
30  
    char& c,
31  
    std::size_t& n) noexcept
31  
    std::size_t& n) noexcept
32  
{
32  
{
33  
    if(s.front() != '%')
33  
    if(s.front() != '%')
34  
    {
34  
    {
35  
        c = s.front();
35  
        c = s.front();
36  
        s.remove_prefix(1);
36  
        s.remove_prefix(1);
37  
    }
37  
    }
38  
    else
38  
    else
39  
    {
39  
    {
40  
        detail::decode_unsafe(
40  
        detail::decode_unsafe(
41  
            &c,
41  
            &c,
42  
            &c + 1,
42  
            &c + 1,
43  
            s.substr(0, 3));
43  
            s.substr(0, 3));
44  
        s.remove_prefix(3);
44  
        s.remove_prefix(3);
45  
    }
45  
    }
46  
    ++n;
46  
    ++n;
47  
}
47  
}
48  

48  

49  
int
49  
int
50  
compare_encoded(
50  
compare_encoded(
51  
    core::string_view lhs,
51  
    core::string_view lhs,
52  
    core::string_view rhs) noexcept
52  
    core::string_view rhs) noexcept
53  
{
53  
{
54  
    std::size_t n0 = 0;
54  
    std::size_t n0 = 0;
55  
    std::size_t n1 = 0;
55  
    std::size_t n1 = 0;
56  
    char c0 = 0;
56  
    char c0 = 0;
57  
    char c1 = 0;
57  
    char c1 = 0;
58  
    while(
58  
    while(
59  
        !lhs.empty() &&
59  
        !lhs.empty() &&
60  
        !rhs.empty())
60  
        !rhs.empty())
61  
    {
61  
    {
62  
        pop_encoded_front(lhs, c0, n0);
62  
        pop_encoded_front(lhs, c0, n0);
63  
        pop_encoded_front(rhs, c1, n1);
63  
        pop_encoded_front(rhs, c1, n1);
64  
        if (c0 < c1)
64  
        if (c0 < c1)
65  
            return -1;
65  
            return -1;
66  
        if (c1 < c0)
66  
        if (c1 < c0)
67  
            return 1;
67  
            return 1;
68  
    }
68  
    }
69  
    n0 += detail::decode_bytes_unsafe(lhs);
69  
    n0 += detail::decode_bytes_unsafe(lhs);
70  
    n1 += detail::decode_bytes_unsafe(rhs);
70  
    n1 += detail::decode_bytes_unsafe(rhs);
71  
    if (n0 == n1)
71  
    if (n0 == n1)
72  
        return 0;
72  
        return 0;
73  
    if (n0 < n1)
73  
    if (n0 < n1)
74  
        return -1;
74  
        return -1;
75  
    return 1;
75  
    return 1;
76  
}
76  
}
77  

77  

78  
int
78  
int
79  
compare_encoded_query(
79  
compare_encoded_query(
80  
    core::string_view lhs,
80  
    core::string_view lhs,
81  
    core::string_view rhs) noexcept
81  
    core::string_view rhs) noexcept
82  
{
82  
{
83  
    static constexpr
83  
    static constexpr
84  
    grammar::lut_chars
84  
    grammar::lut_chars
85  
    query_compare_exception_lut = "&=+";
85  
    query_compare_exception_lut = "&=+";
86  

86  

87  
    std::size_t n0 = 0;
87  
    std::size_t n0 = 0;
88  
    std::size_t n1 = 0;
88  
    std::size_t n1 = 0;
89  
    char c0 = 0;
89  
    char c0 = 0;
90  
    char c1 = 0;
90  
    char c1 = 0;
91  
    while(
91  
    while(
92  
        !lhs.empty() &&
92  
        !lhs.empty() &&
93  
        !rhs.empty())
93  
        !rhs.empty())
94  
    {
94  
    {
95  
        bool const lhs_was_decoded = lhs.front() != '%';
95  
        bool const lhs_was_decoded = lhs.front() != '%';
96  
        bool const rhs_was_decoded = rhs.front() != '%';
96  
        bool const rhs_was_decoded = rhs.front() != '%';
97  
        pop_encoded_front(lhs, c0, n0);
97  
        pop_encoded_front(lhs, c0, n0);
98  
        pop_encoded_front(rhs, c1, n1);
98  
        pop_encoded_front(rhs, c1, n1);
99  
        if (c0 < c1)
99  
        if (c0 < c1)
100  
            return -1;
100  
            return -1;
101  
        if (c1 < c0)
101  
        if (c1 < c0)
102  
            return 1;
102  
            return 1;
103  
        // The decoded chars are the same, but
103  
        // The decoded chars are the same, but
104  
        // are these query exceptions that have
104  
        // are these query exceptions that have
105  
        // different meanings when decoded?
105  
        // different meanings when decoded?
106  
        if (query_compare_exception_lut(c0))
106  
        if (query_compare_exception_lut(c0))
107  
        {
107  
        {
108  
            // If so, we only continue if both
108  
            // If so, we only continue if both
109  
            // chars were decoded or encoded
109  
            // chars were decoded or encoded
110  
            // the same way.
110  
            // the same way.
111  
            if (lhs_was_decoded == rhs_was_decoded)
111  
            if (lhs_was_decoded == rhs_was_decoded)
112  
                continue;
112  
                continue;
113  
            // Otherwise, we return a value != 0
113  
            // Otherwise, we return a value != 0
114  
            // because these chars are not equal.
114  
            // because these chars are not equal.
115  
            // If rhs was the decoded one, it contains
115  
            // If rhs was the decoded one, it contains
116  
            // an ascii char higher than '%'
116  
            // an ascii char higher than '%'
117  
            if (rhs_was_decoded)
117  
            if (rhs_was_decoded)
118  
                return -1;
118  
                return -1;
119  
            else
119  
            else
120  
                return 1;
120  
                return 1;
121  
        }
121  
        }
122  
    }
122  
    }
123  
    n0 += detail::decode_bytes_unsafe(lhs);
123  
    n0 += detail::decode_bytes_unsafe(lhs);
124  
    n1 += detail::decode_bytes_unsafe(rhs);
124  
    n1 += detail::decode_bytes_unsafe(rhs);
125  
    if (n0 == n1)
125  
    if (n0 == n1)
126  
        return 0;
126  
        return 0;
127  
    if (n0 < n1)
127  
    if (n0 < n1)
128  
        return -1;
128  
        return -1;
129  
    return 1;
129  
    return 1;
130  
}
130  
}
131  

131  

132  
void
132  
void
133  
digest_encoded(
133  
digest_encoded(
134  
    core::string_view s,
134  
    core::string_view s,
135  
    fnv_1a& hasher) noexcept
135  
    fnv_1a& hasher) noexcept
136  
{
136  
{
137  
    char c = 0;
137  
    char c = 0;
138  
    std::size_t n = 0;
138  
    std::size_t n = 0;
139  
    while(!s.empty())
139  
    while(!s.empty())
140  
    {
140  
    {
141  
        pop_encoded_front(s, c, n);
141  
        pop_encoded_front(s, c, n);
142  
        hasher.put(c);
142  
        hasher.put(c);
143  
    }
143  
    }
144  
}
144  
}
145  

145  

146  
int
146  
int
147  
ci_compare_encoded(
147  
ci_compare_encoded(
148  
    core::string_view lhs,
148  
    core::string_view lhs,
149  
    core::string_view rhs) noexcept
149  
    core::string_view rhs) noexcept
150  
{
150  
{
151  
    std::size_t n0 = 0;
151  
    std::size_t n0 = 0;
152  
    std::size_t n1 = 0;
152  
    std::size_t n1 = 0;
153  
    char c0 = 0;
153  
    char c0 = 0;
154  
    char c1 = 0;
154  
    char c1 = 0;
155  
    while (
155  
    while (
156  
        !lhs.empty() &&
156  
        !lhs.empty() &&
157  
        !rhs.empty())
157  
        !rhs.empty())
158  
    {
158  
    {
159  
        pop_encoded_front(lhs, c0, n0);
159  
        pop_encoded_front(lhs, c0, n0);
160  
        pop_encoded_front(rhs, c1, n1);
160  
        pop_encoded_front(rhs, c1, n1);
161  
        c0 = grammar::to_lower(c0);
161  
        c0 = grammar::to_lower(c0);
162  
        c1 = grammar::to_lower(c1);
162  
        c1 = grammar::to_lower(c1);
163  
        if (c0 < c1)
163  
        if (c0 < c1)
164  
            return -1;
164  
            return -1;
165  
        if (c1 < c0)
165  
        if (c1 < c0)
166  
            return 1;
166  
            return 1;
167  
    }
167  
    }
168  
    n0 += detail::decode_bytes_unsafe(lhs);
168  
    n0 += detail::decode_bytes_unsafe(lhs);
169  
    n1 += detail::decode_bytes_unsafe(rhs);
169  
    n1 += detail::decode_bytes_unsafe(rhs);
170  
    if (n0 == n1)
170  
    if (n0 == n1)
171  
        return 0;
171  
        return 0;
172  
    if (n0 < n1)
172  
    if (n0 < n1)
173  
        return -1;
173  
        return -1;
174  
    return 1;
174  
    return 1;
175  
}
175  
}
176  

176  

177  
void
177  
void
178  
ci_digest_encoded(
178  
ci_digest_encoded(
179  
    core::string_view s,
179  
    core::string_view s,
180  
    fnv_1a& hasher) noexcept
180  
    fnv_1a& hasher) noexcept
181  
{
181  
{
182  
    char c = 0;
182  
    char c = 0;
183  
    std::size_t n = 0;
183  
    std::size_t n = 0;
184  
    while(!s.empty())
184  
    while(!s.empty())
185  
    {
185  
    {
186  
        pop_encoded_front(s, c, n);
186  
        pop_encoded_front(s, c, n);
187  
        c = grammar::to_lower(c);
187  
        c = grammar::to_lower(c);
188  
        hasher.put(c);
188  
        hasher.put(c);
189  
    }
189  
    }
190  
}
190  
}
191  

191  

192  
int
192  
int
193  
compare(
193  
compare(
194  
    core::string_view lhs,
194  
    core::string_view lhs,
195  
    core::string_view rhs) noexcept
195  
    core::string_view rhs) noexcept
196  
{
196  
{
197  
    auto rlen = (std::min)(lhs.size(), rhs.size());
197  
    auto rlen = (std::min)(lhs.size(), rhs.size());
198  
    for (std::size_t i = 0; i < rlen; ++i)
198  
    for (std::size_t i = 0; i < rlen; ++i)
199  
    {
199  
    {
200  
        char c0 = lhs[i];
200  
        char c0 = lhs[i];
201  
        char c1 = rhs[i];
201  
        char c1 = rhs[i];
202  
        if (c0 < c1)
202  
        if (c0 < c1)
203  
            return -1;
203  
            return -1;
204  
        if (c1 < c0)
204  
        if (c1 < c0)
205  
            return 1;
205  
            return 1;
206  
    }
206  
    }
207  
    if ( lhs.size() == rhs.size() )
207  
    if ( lhs.size() == rhs.size() )
208  
        return 0;
208  
        return 0;
209  
    if ( lhs.size() < rhs.size() )
209  
    if ( lhs.size() < rhs.size() )
210  
        return -1;
210  
        return -1;
211  
    return 1;
211  
    return 1;
212  
}
212  
}
213  

213  

214  
int
214  
int
215  
ci_compare(
215  
ci_compare(
216  
    core::string_view lhs,
216  
    core::string_view lhs,
217  
    core::string_view rhs) noexcept
217  
    core::string_view rhs) noexcept
218  
{
218  
{
219  
    auto rlen = (std::min)(lhs.size(), rhs.size());
219  
    auto rlen = (std::min)(lhs.size(), rhs.size());
220  
    for (std::size_t i = 0; i < rlen; ++i)
220  
    for (std::size_t i = 0; i < rlen; ++i)
221  
    {
221  
    {
222  
        char c0 = grammar::to_lower(lhs[i]);
222  
        char c0 = grammar::to_lower(lhs[i]);
223  
        char c1 = grammar::to_lower(rhs[i]);
223  
        char c1 = grammar::to_lower(rhs[i]);
224  
        if (c0 < c1)
224  
        if (c0 < c1)
225  
            return -1;
225  
            return -1;
226  
        if (c1 < c0)
226  
        if (c1 < c0)
227  
            return 1;
227  
            return 1;
228  
    }
228  
    }
229  
    if ( lhs.size() == rhs.size() )
229  
    if ( lhs.size() == rhs.size() )
230  
        return 0;
230  
        return 0;
231  
    if ( lhs.size() < rhs.size() )
231  
    if ( lhs.size() < rhs.size() )
232  
        return -1;
232  
        return -1;
233  
    return 1;
233  
    return 1;
234  
}
234  
}
235  

235  

236  
void
236  
void
237  
ci_digest(
237  
ci_digest(
238  
    core::string_view s,
238  
    core::string_view s,
239  
    fnv_1a& hasher) noexcept
239  
    fnv_1a& hasher) noexcept
240  
{
240  
{
241  
    for (char c: s)
241  
    for (char c: s)
242  
    {
242  
    {
243  
        c = grammar::to_lower(c);
243  
        c = grammar::to_lower(c);
244  
        hasher.put(c);
244  
        hasher.put(c);
245  
    }
245  
    }
246  
}
246  
}
247  

247  

248  
/* Check if a string ends with the specified suffix (decoded comparison)
248  
/* Check if a string ends with the specified suffix (decoded comparison)
249  

249  

250  
   This function determines if a string ends with the specified suffix
250  
   This function determines if a string ends with the specified suffix
251  
   when the string and suffix are compared after percent-decoding.
251  
   when the string and suffix are compared after percent-decoding.
252  

252  

253  
   @param str The string to check (percent-encoded)
253  
   @param str The string to check (percent-encoded)
254  
   @param suffix The suffix to check for (percent-decoded)
254  
   @param suffix The suffix to check for (percent-decoded)
255  
   @return The number of encoded chars consumed in the string
255  
   @return The number of encoded chars consumed in the string
256  
 */
256  
 */
257  
std::size_t
257  
std::size_t
258  
path_ends_with(
258  
path_ends_with(
259  
    core::string_view str,
259  
    core::string_view str,
260  
    core::string_view suffix) noexcept
260  
    core::string_view suffix) noexcept
261  
{
261  
{
262  
    BOOST_ASSERT(!str.empty());
262  
    BOOST_ASSERT(!str.empty());
263  
    BOOST_ASSERT(!suffix.empty());
263  
    BOOST_ASSERT(!suffix.empty());
264  
    BOOST_ASSERT(!suffix.contains("%2F"));
264  
    BOOST_ASSERT(!suffix.contains("%2F"));
265  
    BOOST_ASSERT(!suffix.contains("%2f"));
265  
    BOOST_ASSERT(!suffix.contains("%2f"));
266  
    auto consume_last = [](
266  
    auto consume_last = [](
267  
        core::string_view::iterator& it,
267  
        core::string_view::iterator& it,
268  
        core::string_view::iterator& end,
268  
        core::string_view::iterator& end,
269  
        char& c)
269  
        char& c)
270  
    {
270  
    {
271  
        BOOST_ASSERT(end > it);
271  
        BOOST_ASSERT(end > it);
272  
        BOOST_ASSERT(it != end);
272  
        BOOST_ASSERT(it != end);
273  
        if ((end - it) < 3 ||
273  
        if ((end - it) < 3 ||
274  
            *(std::prev(end, 3)) != '%')
274  
            *(std::prev(end, 3)) != '%')
275  
        {
275  
        {
276  
            c = *--end;
276  
            c = *--end;
277  
            return false;
277  
            return false;
278  
        }
278  
        }
279  
        detail::decode_unsafe(
279  
        detail::decode_unsafe(
280  
            &c,
280  
            &c,
281  
            &c + 1,
281  
            &c + 1,
282  
            core::string_view(std::prev(
282  
            core::string_view(std::prev(
283  
                end, 3), 3));
283  
                end, 3), 3));
284  
        end -= 3;
284  
        end -= 3;
285  
        return true;
285  
        return true;
286  
    };
286  
    };
287  

287  

288  
    auto it0 = str.begin();
288  
    auto it0 = str.begin();
289  
    auto end0 = str.end();
289  
    auto end0 = str.end();
290  
    auto it1 = suffix.begin();
290  
    auto it1 = suffix.begin();
291  
    auto end1 = suffix.end();
291  
    auto end1 = suffix.end();
292  
    char c0 = 0;
292  
    char c0 = 0;
293  
    char c1 = 0;
293  
    char c1 = 0;
294  
    while(
294  
    while(
295  
        it0 < end0 &&
295  
        it0 < end0 &&
296  
        it1 < end1)
296  
        it1 < end1)
297  
    {
297  
    {
298  
        bool const is_encoded = consume_last(it0, end0, c0);
298  
        bool const is_encoded = consume_last(it0, end0, c0);
299  
        // The suffix never contains an encoded slash (%2F), and a decoded
299  
        // The suffix never contains an encoded slash (%2F), and a decoded
300  
        // slash is not equivalent to an encoded slash
300  
        // slash is not equivalent to an encoded slash
301  
        if (is_encoded && c0 == '/')
301  
        if (is_encoded && c0 == '/')
302  
            return 0;
302  
            return 0;
303  
        consume_last(it1, end1, c1);
303  
        consume_last(it1, end1, c1);
304  
        if (c0 != c1)
304  
        if (c0 != c1)
305  
            return 0;
305  
            return 0;
306  
    }
306  
    }
307  
    bool const consumed_suffix = it1 == end1;
307  
    bool const consumed_suffix = it1 == end1;
308  
    if (consumed_suffix)
308  
    if (consumed_suffix)
309  
    {
309  
    {
310  
        std::size_t const consumed_encoded = str.end() - end0;
310  
        std::size_t const consumed_encoded = str.end() - end0;
311  
        return consumed_encoded;
311  
        return consumed_encoded;
312  
    }
312  
    }
313  
    return 0;
313  
    return 0;
314  
}
314  
}
315  

315  

316  
std::size_t
316  
std::size_t
317  
remove_dot_segments(
317  
remove_dot_segments(
318  
    char* dest0,
318  
    char* dest0,
319  
    char const* end,
319  
    char const* end,
320  
    core::string_view input) noexcept
320  
    core::string_view input) noexcept
321  
{
321  
{
322  
    // 1. The input buffer `s` is initialized with
322  
    // 1. The input buffer `s` is initialized with
323  
    // the now-appended path components and the
323  
    // the now-appended path components and the
324  
    // output buffer `dest0` is initialized to
324  
    // output buffer `dest0` is initialized to
325  
    // the empty string.
325  
    // the empty string.
326  
    char* dest = dest0;
326  
    char* dest = dest0;
327  
    bool const is_absolute = input.starts_with('/');
327  
    bool const is_absolute = input.starts_with('/');
328  

328  

329  
    // Step 2 is a loop through 5 production rules:
329  
    // Step 2 is a loop through 5 production rules:
330  
    // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4
330  
    // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4
331  
    //
331  
    //
332  
    // There are no transitions between all rules,
332  
    // There are no transitions between all rules,
333  
    // which enables some optimizations.
333  
    // which enables some optimizations.
334  
    //
334  
    //
335  
    // Initial:
335  
    // Initial:
336  
    // - Rule A: handle initial dots
336  
    // - Rule A: handle initial dots
337  
    // If the input buffer begins with a
337  
    // If the input buffer begins with a
338  
    // prefix of "../" or "./", then remove
338  
    // prefix of "../" or "./", then remove
339  
    // that prefix from the input buffer.
339  
    // that prefix from the input buffer.
340  
    // Rule A can only happen at the beginning.
340  
    // Rule A can only happen at the beginning.
341  
    // Errata 4547: Keep "../" in the beginning
341  
    // Errata 4547: Keep "../" in the beginning
342  
    // https://www.rfc-editor.org/errata/eid4547
342  
    // https://www.rfc-editor.org/errata/eid4547
343  
    //
343  
    //
344  
    // Then:
344  
    // Then:
345  
    // - Rule D: ignore a final ".." or "."
345  
    // - Rule D: ignore a final ".." or "."
346  
    // if the input buffer consists only  of "."
346  
    // if the input buffer consists only  of "."
347  
    // or "..", then remove that from the input
347  
    // or "..", then remove that from the input
348  
    // buffer.
348  
    // buffer.
349  
    // Rule D can only happen after Rule A because:
349  
    // Rule D can only happen after Rule A because:
350  
    // - B and C write "/" to the input
350  
    // - B and C write "/" to the input
351  
    // - E writes "/" to input or returns
351  
    // - E writes "/" to input or returns
352  
    //
352  
    //
353  
    // Then:
353  
    // Then:
354  
    // - Rule B: ignore ".": write "/" to the input
354  
    // - Rule B: ignore ".": write "/" to the input
355  
    // - Rule C: apply "..": remove seg and write "/"
355  
    // - Rule C: apply "..": remove seg and write "/"
356  
    // - Rule E: copy complete segment
356  
    // - Rule E: copy complete segment
357  
    auto append =
357  
    auto append =
358  
        [](char*& first, char const* last, core::string_view in)
358  
        [](char*& first, char const* last, core::string_view in)
359  
    {
359  
    {
360  
        // append `in` to `dest`
360  
        // append `in` to `dest`
361  
        BOOST_ASSERT(in.size() <= std::size_t(last - first));
361  
        BOOST_ASSERT(in.size() <= std::size_t(last - first));
362  
        std::memmove(first, in.data(), in.size());
362  
        std::memmove(first, in.data(), in.size());
363  
        first += in.size();
363  
        first += in.size();
364  
        ignore_unused(last);
364  
        ignore_unused(last);
365  
    };
365  
    };
366  

366  

367  
    auto dot_starts_with = [](
367  
    auto dot_starts_with = [](
368  
        core::string_view str, core::string_view dots, std::size_t& n)
368  
        core::string_view str, core::string_view dots, std::size_t& n)
369  
    {
369  
    {
370  
        // starts_with for encoded/decoded dots
370  
        // starts_with for encoded/decoded dots
371  
        // or decoded otherwise. return how many
371  
        // or decoded otherwise. return how many
372  
        // chars in str match the dots
372  
        // chars in str match the dots
373  
        n = 0;
373  
        n = 0;
374  
        for (char c: dots)
374  
        for (char c: dots)
375  
        {
375  
        {
376  
            if (str.starts_with(c))
376  
            if (str.starts_with(c))
377  
            {
377  
            {
378  
                str.remove_prefix(1);
378  
                str.remove_prefix(1);
379  
                ++n;
379  
                ++n;
380  
                continue;
380  
                continue;
381  
            }
381  
            }
382  

382  

383  
            // In the general case, we would need to
383  
            // In the general case, we would need to
384  
            // check if the next char is an encoded
384  
            // check if the next char is an encoded
385  
            // dot.
385  
            // dot.
386  
            // However, an encoded dot in `str`
386  
            // However, an encoded dot in `str`
387  
            // would have already been decoded in
387  
            // would have already been decoded in
388  
            // url_base::normalize_path().
388  
            // url_base::normalize_path().
389  
            // This needs to be undone if
389  
            // This needs to be undone if
390  
            // `remove_dot_segments` is used in a
390  
            // `remove_dot_segments` is used in a
391  
            // different context.
391  
            // different context.
392  
            // if (str.size() > 2 &&
392  
            // if (str.size() > 2 &&
393  
            //     c == '.'
393  
            //     c == '.'
394  
            //     &&
394  
            //     &&
395  
            //     str[0] == '%' &&
395  
            //     str[0] == '%' &&
396  
            //     str[1] == '2' &&
396  
            //     str[1] == '2' &&
397  
            //     (str[2] == 'e' ||
397  
            //     (str[2] == 'e' ||
398  
            //      str[2] == 'E'))
398  
            //      str[2] == 'E'))
399  
            // {
399  
            // {
400  
            //     str.remove_prefix(3);
400  
            //     str.remove_prefix(3);
401  
            //     n += 3;
401  
            //     n += 3;
402  
            //     continue;
402  
            //     continue;
403  
            // }
403  
            // }
404  

404  

405  
            n = 0;
405  
            n = 0;
406  
            return false;
406  
            return false;
407  
        }
407  
        }
408  
        return true;
408  
        return true;
409  
    };
409  
    };
410  

410  

411  
    auto dot_equal = [&dot_starts_with](
411  
    auto dot_equal = [&dot_starts_with](
412  
        core::string_view str, core::string_view dots)
412  
        core::string_view str, core::string_view dots)
413  
    {
413  
    {
414  
        std::size_t n = 0;
414  
        std::size_t n = 0;
415  
        dot_starts_with(str, dots, n);
415  
        dot_starts_with(str, dots, n);
416  
        return n == str.size();
416  
        return n == str.size();
417  
    };
417  
    };
418  

418  

419  
    // Rule A
419  
    // Rule A
420  
    std::size_t n;
420  
    std::size_t n;
421  
    while (!input.empty())
421  
    while (!input.empty())
422  
    {
422  
    {
423  
        if (dot_starts_with(input, "../", n))
423  
        if (dot_starts_with(input, "../", n))
424  
        {
424  
        {
425  
            // Errata 4547
425  
            // Errata 4547
426  
            append(dest, end, "../");
426  
            append(dest, end, "../");
427  
            input.remove_prefix(n);
427  
            input.remove_prefix(n);
428  
            continue;
428  
            continue;
429  
        }
429  
        }
430  
        else if (!dot_starts_with(input, "./", n))
430  
        else if (!dot_starts_with(input, "./", n))
431  
        {
431  
        {
432  
            break;
432  
            break;
433  
        }
433  
        }
434  
        input.remove_prefix(n);
434  
        input.remove_prefix(n);
435  
    }
435  
    }
436  

436  

437  
    // Rule D
437  
    // Rule D
438  
    if( dot_equal(input, "."))
438  
    if( dot_equal(input, "."))
439  
    {
439  
    {
440  
        input = {};
440  
        input = {};
441  
    }
441  
    }
442  
    else if( dot_equal(input, "..") )
442  
    else if( dot_equal(input, "..") )
443  
    {
443  
    {
444  
        // Errata 4547
444  
        // Errata 4547
445  
        append(dest, end, "..");
445  
        append(dest, end, "..");
446  
        input = {};
446  
        input = {};
447  
    }
447  
    }
448  

448  

449  
    // 2. While the input buffer is not empty,
449  
    // 2. While the input buffer is not empty,
450  
    // loop as follows:
450  
    // loop as follows:
451  
    while (!input.empty())
451  
    while (!input.empty())
452  
    {
452  
    {
453  
        // Rule B
453  
        // Rule B
454  
        bool const is_dot_seg = dot_starts_with(input, "/./", n);
454  
        bool const is_dot_seg = dot_starts_with(input, "/./", n);
455  
        if (is_dot_seg)
455  
        if (is_dot_seg)
456  
        {
456  
        {
457  
            input.remove_prefix(n - 1);
457  
            input.remove_prefix(n - 1);
458  
            continue;
458  
            continue;
459  
        }
459  
        }
460  

460  

461  
        bool const is_final_dot_seg = dot_equal(input, "/.");
461  
        bool const is_final_dot_seg = dot_equal(input, "/.");
462  
        if (is_final_dot_seg)
462  
        if (is_final_dot_seg)
463  
        {
463  
        {
464  
            // We can't remove "." from a core::string_view
464  
            // We can't remove "." from a core::string_view
465  
            // So what we do here is equivalent to
465  
            // So what we do here is equivalent to
466  
            // replacing s with '/' as required
466  
            // replacing s with '/' as required
467  
            // in Rule B and executing the next
467  
            // in Rule B and executing the next
468  
            // iteration, which would append this
468  
            // iteration, which would append this
469  
            // '/' to  the output, as required by
469  
            // '/' to  the output, as required by
470  
            // Rule E
470  
            // Rule E
471  
            append(dest, end, input.substr(0, 1));
471  
            append(dest, end, input.substr(0, 1));
472  
            input = {};
472  
            input = {};
473  
            break;
473  
            break;
474  
        }
474  
        }
475  

475  

476  
        // Rule C
476  
        // Rule C
477  
        bool const is_dotdot_seg = dot_starts_with(input, "/../", n);
477  
        bool const is_dotdot_seg = dot_starts_with(input, "/../", n);
478  
        if (is_dotdot_seg)
478  
        if (is_dotdot_seg)
479  
        {
479  
        {
480  
            core::string_view cur_out(dest0, dest - dest0);
480  
            core::string_view cur_out(dest0, dest - dest0);
481  
            std::size_t p = cur_out.find_last_of('/');
481  
            std::size_t p = cur_out.find_last_of('/');
482  
            bool const has_multiple_segs = p != core::string_view::npos;
482  
            bool const has_multiple_segs = p != core::string_view::npos;
483  
            if (has_multiple_segs)
483  
            if (has_multiple_segs)
484  
            {
484  
            {
485  
                // output has multiple segments
485  
                // output has multiple segments
486  
                // "erase" [p, end] if not "/.."
486  
                // "erase" [p, end] if not "/.."
487  
                core::string_view last_seg(dest0 + p, dest - (dest0 + p));
487  
                core::string_view last_seg(dest0 + p, dest - (dest0 + p));
488  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
488  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
489  
                if (!prev_is_dotdot_seg)
489  
                if (!prev_is_dotdot_seg)
490  
                {
490  
                {
491  
                    dest = dest0 + p;
491  
                    dest = dest0 + p;
492  
                }
492  
                }
493  
                else
493  
                else
494  
                {
494  
                {
495  
                    append(dest, end, "/..");
495  
                    append(dest, end, "/..");
496  
                }
496  
                }
497  
            }
497  
            }
498  
            else if (dest0 != dest)
498  
            else if (dest0 != dest)
499  
            {
499  
            {
500  
                // Only one segment in the output: remove it
500  
                // Only one segment in the output: remove it
501  
                core::string_view last_seg(dest0, dest - dest0);
501  
                core::string_view last_seg(dest0, dest - dest0);
502  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
502  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
503  
                if (!prev_is_dotdot_seg)
503  
                if (!prev_is_dotdot_seg)
504  
                {
504  
                {
505  
                    dest = dest0;
505  
                    dest = dest0;
506  
                    if (!is_absolute)
506  
                    if (!is_absolute)
507  
                    {
507  
                    {
508  
                        input.remove_prefix(1);
508  
                        input.remove_prefix(1);
509  
                    }
509  
                    }
510  
                }
510  
                }
511  
                else
511  
                else
512  
                {
512  
                {
513  
                    append(dest, end, "/..");
513  
                    append(dest, end, "/..");
514  
                }
514  
                }
515  
            }
515  
            }
516  
            else
516  
            else
517  
            {
517  
            {
518  
                // Output is empty
518  
                // Output is empty
519  
                if (is_absolute)
519  
                if (is_absolute)
520  
                {
520  
                {
521  
                    append(dest, end, "/..");
521  
                    append(dest, end, "/..");
522  
                }
522  
                }
523  
                else
523  
                else
524  
                {
524  
                {
525  
                    // AFREITAS: Although we have no formal proof
525  
                    // AFREITAS: Although we have no formal proof
526  
                    // for that, the output can't be relative
526  
                    // for that, the output can't be relative
527  
                    // and empty at this point because relative
527  
                    // and empty at this point because relative
528  
                    // paths will fall in the `dest0 != dest`
528  
                    // paths will fall in the `dest0 != dest`
529  
                    // case above of this rule C and then the
529  
                    // case above of this rule C and then the
530  
                    // general case of rule E for "..".
530  
                    // general case of rule E for "..".
531  
                    append(dest, end, "..");
531  
                    append(dest, end, "..");
532  
                }
532  
                }
533  
            }
533  
            }
534  
            input.remove_prefix(n - 1);
534  
            input.remove_prefix(n - 1);
535  
            continue;
535  
            continue;
536  
        }
536  
        }
537  

537  

538  
        bool const is_final_dotdot_seg = dot_equal(input, "/..");
538  
        bool const is_final_dotdot_seg = dot_equal(input, "/..");
539  
        if (is_final_dotdot_seg)
539  
        if (is_final_dotdot_seg)
540  
        {
540  
        {
541  
            core::string_view cur_out(dest0, dest - dest0);
541  
            core::string_view cur_out(dest0, dest - dest0);
542  
            std::size_t p = cur_out.find_last_of('/');
542  
            std::size_t p = cur_out.find_last_of('/');
543  
            bool const has_multiple_segs = p != core::string_view::npos;
543  
            bool const has_multiple_segs = p != core::string_view::npos;
544  
            if (has_multiple_segs)
544  
            if (has_multiple_segs)
545  
            {
545  
            {
546  
                // output has multiple segments
546  
                // output has multiple segments
547  
                // "erase" [p, end] if not "/.."
547  
                // "erase" [p, end] if not "/.."
548  
                core::string_view last_seg(dest0 + p, dest - (dest0 + p));
548  
                core::string_view last_seg(dest0 + p, dest - (dest0 + p));
549  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
549  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
550  
                if (!prev_is_dotdot_seg)
550  
                if (!prev_is_dotdot_seg)
551  
                {
551  
                {
552  
                    dest = dest0 + p;
552  
                    dest = dest0 + p;
553  
                    append(dest, end, "/");
553  
                    append(dest, end, "/");
554  
                }
554  
                }
555  
                else
555  
                else
556  
                {
556  
                {
557  
                    append(dest, end, "/..");
557  
                    append(dest, end, "/..");
558  
                }
558  
                }
559  
            }
559  
            }
560  
            else if (dest0 != dest)
560  
            else if (dest0 != dest)
561  
            {
561  
            {
562  
                // Only one segment in the output: remove it
562  
                // Only one segment in the output: remove it
563  
                core::string_view last_seg(dest0, dest - dest0);
563  
                core::string_view last_seg(dest0, dest - dest0);
564  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
564  
                bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
565  
                if (!prev_is_dotdot_seg) {
565  
                if (!prev_is_dotdot_seg) {
566  
                    dest = dest0;
566  
                    dest = dest0;
567  
                }
567  
                }
568  
                else
568  
                else
569  
                {
569  
                {
570  
                    append(dest, end, "/..");
570  
                    append(dest, end, "/..");
571  
                }
571  
                }
572  
            }
572  
            }
573  
            else
573  
            else
574  
            {
574  
            {
575  
                // Output is empty: append dotdot
575  
                // Output is empty: append dotdot
576  
                if (is_absolute)
576  
                if (is_absolute)
577  
                {
577  
                {
578  
                    append(dest, end, "/..");
578  
                    append(dest, end, "/..");
579  
                }
579  
                }
580  
                else
580  
                else
581  
                {
581  
                {
582  
                    // AFREITAS: Although we have no formal proof
582  
                    // AFREITAS: Although we have no formal proof
583  
                    // for that, the output can't be relative
583  
                    // for that, the output can't be relative
584  
                    // and empty at this point because relative
584  
                    // and empty at this point because relative
585  
                    // paths will fall in the `dest0 != dest`
585  
                    // paths will fall in the `dest0 != dest`
586  
                    // case above of this rule C and then the
586  
                    // case above of this rule C and then the
587  
                    // general case of rule E for "..".
587  
                    // general case of rule E for "..".
588  
                    append(dest, end, "..");
588  
                    append(dest, end, "..");
589  
                }
589  
                }
590  
            }
590  
            }
591  
            input = {};
591  
            input = {};
592  
            break;
592  
            break;
593  
        }
593  
        }
594  

594  

595  
        // Rule E
595  
        // Rule E
596  
        std::size_t p = input.find_first_of('/', 1);
596  
        std::size_t p = input.find_first_of('/', 1);
597  
        if (p != core::string_view::npos)
597  
        if (p != core::string_view::npos)
598  
        {
598  
        {
599  
            append(dest, end, input.substr(0, p));
599  
            append(dest, end, input.substr(0, p));
600  
            input.remove_prefix(p);
600  
            input.remove_prefix(p);
601  
        }
601  
        }
602  
        else
602  
        else
603  
        {
603  
        {
604  
            append(dest, end, input);
604  
            append(dest, end, input);
605  
            input = {};
605  
            input = {};
606  
        }
606  
        }
607  
    }
607  
    }
608  

608  

609  
    // 3. Finally, the output buffer is set
609  
    // 3. Finally, the output buffer is set
610  
    // as the result of remove_dot_segments,
610  
    // as the result of remove_dot_segments,
611  
    // and we return its size
611  
    // and we return its size
612  
    return dest - dest0;
612  
    return dest - dest0;
613  
}
613  
}
614  

614  

615  
char
615  
char
616  
path_pop_back( core::string_view& s )
616  
path_pop_back( core::string_view& s )
617  
{
617  
{
618  
    if (s.size() < 3 ||
618  
    if (s.size() < 3 ||
619  
        *std::prev(s.end(), 3) != '%')
619  
        *std::prev(s.end(), 3) != '%')
620  
    {
620  
    {
621  
        char c = s.back();
621  
        char c = s.back();
622  
        s.remove_suffix(1);
622  
        s.remove_suffix(1);
623  
        return c;
623  
        return c;
624  
    }
624  
    }
625  
    char c = 0;
625  
    char c = 0;
626  
    detail::decode_unsafe(
626  
    detail::decode_unsafe(
627  
        &c, &c + 1, s.substr(s.size() - 3));
627  
        &c, &c + 1, s.substr(s.size() - 3));
628  
    if (c != '/')
628  
    if (c != '/')
629  
    {
629  
    {
630  
        s.remove_suffix(3);
630  
        s.remove_suffix(3);
631  
        return c;
631  
        return c;
632  
    }
632  
    }
633  
    c = s.back();
633  
    c = s.back();
634  
    s.remove_suffix(1);
634  
    s.remove_suffix(1);
635  
    return c;
635  
    return c;
636  
};
636  
};
637  

637  

638  
void
638  
void
639  
pop_last_segment(
639  
pop_last_segment(
640  
    core::string_view& str,
640  
    core::string_view& str,
641  
    core::string_view& seg,
641  
    core::string_view& seg,
642  
    std::size_t& level,
642  
    std::size_t& level,
643  
    bool remove_unmatched) noexcept
643  
    bool remove_unmatched) noexcept
644  
{
644  
{
645  
    seg = {};
645  
    seg = {};
646  
    std::size_t n = 0;
646  
    std::size_t n = 0;
647  
    while (!str.empty())
647  
    while (!str.empty())
648  
    {
648  
    {
649  
        // B.  if the input buffer begins with a
649  
        // B.  if the input buffer begins with a
650  
        // prefix of "/./" or "/.", where "." is
650  
        // prefix of "/./" or "/.", where "." is
651  
        // a complete path segment, then replace
651  
        // a complete path segment, then replace
652  
        // that prefix with "/" in the input
652  
        // that prefix with "/" in the input
653  
        // buffer; otherwise,
653  
        // buffer; otherwise,
654  
        n = detail::path_ends_with(str, "/./");
654  
        n = detail::path_ends_with(str, "/./");
655  
        if (n)
655  
        if (n)
656  
        {
656  
        {
657  
            seg = str.substr(str.size() - n);
657  
            seg = str.substr(str.size() - n);
658  
            str.remove_suffix(n);
658  
            str.remove_suffix(n);
659  
            continue;
659  
            continue;
660  
        }
660  
        }
661  
        n = detail::path_ends_with(str, "/.");
661  
        n = detail::path_ends_with(str, "/.");
662  
        if (n)
662  
        if (n)
663  
        {
663  
        {
664  
            seg = str.substr(str.size() - n, 1);
664  
            seg = str.substr(str.size() - n, 1);
665  
            str.remove_suffix(n);
665  
            str.remove_suffix(n);
666  
            continue;
666  
            continue;
667  
        }
667  
        }
668  

668  

669  
        // C. if the input buffer begins with a
669  
        // C. if the input buffer begins with a
670  
        // prefix of "/../" or "/..", where ".."
670  
        // prefix of "/../" or "/..", where ".."
671  
        // is a complete path segment, then
671  
        // is a complete path segment, then
672  
        // replace that prefix with "/" in the
672  
        // replace that prefix with "/" in the
673  
        // input buffer and remove the last
673  
        // input buffer and remove the last
674  
        // segment and its preceding "/"
674  
        // segment and its preceding "/"
675  
        // (if any) from the output buffer
675  
        // (if any) from the output buffer
676  
        // otherwise,
676  
        // otherwise,
677  
        n = detail::path_ends_with(str, "/../");
677  
        n = detail::path_ends_with(str, "/../");
678  
        if (n)
678  
        if (n)
679  
        {
679  
        {
680  
            seg = str.substr(str.size() - n);
680  
            seg = str.substr(str.size() - n);
681  
            str.remove_suffix(n);
681  
            str.remove_suffix(n);
682  
            ++level;
682  
            ++level;
683  
            continue;
683  
            continue;
684  
        }
684  
        }
685  
        n = detail::path_ends_with(str, "/..");
685  
        n = detail::path_ends_with(str, "/..");
686  
        if (n)
686  
        if (n)
687  
        {
687  
        {
688  
            seg = str.substr(str.size() - n);
688  
            seg = str.substr(str.size() - n);
689  
            str.remove_suffix(n);
689  
            str.remove_suffix(n);
690  
            ++level;
690  
            ++level;
691  
            continue;
691  
            continue;
692  
        }
692  
        }
693  

693  

694  
        // E.  move the first path segment in the
694  
        // E.  move the first path segment in the
695  
        // input buffer to the end of the output
695  
        // input buffer to the end of the output
696  
        // buffer, including the initial "/"
696  
        // buffer, including the initial "/"
697  
        // character (if any) and any subsequent
697  
        // character (if any) and any subsequent
698  
        // characters up to, but not including,
698  
        // characters up to, but not including,
699  
        // the next "/" character or the end of
699  
        // the next "/" character or the end of
700  
        // the input buffer.
700  
        // the input buffer.
701  
        std::size_t p = str.size() > 1
701  
        std::size_t p = str.size() > 1
702  
            ? str.find_last_of('/', str.size() - 2)
702  
            ? str.find_last_of('/', str.size() - 2)
703  
            : core::string_view::npos;
703  
            : core::string_view::npos;
704  
        if (p != core::string_view::npos)
704  
        if (p != core::string_view::npos)
705  
        {
705  
        {
706  
            seg = str.substr(p + 1);
706  
            seg = str.substr(p + 1);
707  
            str.remove_suffix(seg.size());
707  
            str.remove_suffix(seg.size());
708  
        }
708  
        }
709  
        else
709  
        else
710  
        {
710  
        {
711  
            seg = str;
711  
            seg = str;
712  
            str = {};
712  
            str = {};
713  
        }
713  
        }
714  

714  

715  
        if (level == 0)
715  
        if (level == 0)
716  
            return;
716  
            return;
717  
        if (!str.empty())
717  
        if (!str.empty())
718  
            --level;
718  
            --level;
719  
    }
719  
    }
720  
    // we still need to skip n_skip + 1
720  
    // we still need to skip n_skip + 1
721  
    // but the string is empty
721  
    // but the string is empty
722  
    if (remove_unmatched && level)
722  
    if (remove_unmatched && level)
723  
    {
723  
    {
724  
        seg = "/";
724  
        seg = "/";
725  
        level = 0;
725  
        level = 0;
726  
        return;
726  
        return;
727  
    }
727  
    }
728  
    else if (level)
728  
    else if (level)
729  
    {
729  
    {
730  
        if (!seg.empty())
730  
        if (!seg.empty())
731  
        {
731  
        {
732  
            seg = "/../";
732  
            seg = "/../";
733  
        }
733  
        }
734  
        else
734  
        else
735  
        {
735  
        {
736  
            // AFREITAS: this condition
736  
            // AFREITAS: this condition
737  
            // is correct, but it might
737  
            // is correct, but it might
738  
            // unreachable.
738  
            // unreachable.
739  
            seg = "/..";
739  
            seg = "/..";
740  
        }
740  
        }
741  
        --level;
741  
        --level;
742  
        return;
742  
        return;
743  
    }
743  
    }
744  
    seg = {};
744  
    seg = {};
745  
}
745  
}
746  

746  

747  
void
747  
void
748  
normalized_path_digest(
748  
normalized_path_digest(
749  
    core::string_view str,
749  
    core::string_view str,
750  
    bool remove_unmatched,
750  
    bool remove_unmatched,
751  
    fnv_1a& hasher) noexcept
751  
    fnv_1a& hasher) noexcept
752  
{
752  
{
753  
    core::string_view seg;
753  
    core::string_view seg;
754  
    std::size_t level = 0;
754  
    std::size_t level = 0;
755  
    do
755  
    do
756  
    {
756  
    {
757  
        pop_last_segment(
757  
        pop_last_segment(
758  
            str, seg, level, remove_unmatched);
758  
            str, seg, level, remove_unmatched);
759  
        while (!seg.empty())
759  
        while (!seg.empty())
760  
        {
760  
        {
761  
            char c = path_pop_back(seg);
761  
            char c = path_pop_back(seg);
762  
            hasher.put(c);
762  
            hasher.put(c);
763  
        }
763  
        }
764  
    }
764  
    }
765  
    while (!str.empty());
765  
    while (!str.empty());
766  
}
766  
}
767  

767  

768  
// compare segments as if there were a normalized
768  
// compare segments as if there were a normalized
769  
int
769  
int
770  
segments_compare(
770  
segments_compare(
771  
    segments_encoded_view seg0,
771  
    segments_encoded_view seg0,
772  
    segments_encoded_view seg1) noexcept
772  
    segments_encoded_view seg1) noexcept
773  
{
773  
{
774  
    // calculate path size as if it were normalized
774  
    // calculate path size as if it were normalized
775  
    auto normalized_size =
775  
    auto normalized_size =
776  
        [](segments_encoded_view seg) -> std::size_t
776  
        [](segments_encoded_view seg) -> std::size_t
777  
    {
777  
    {
778  
        if (seg.empty())
778  
        if (seg.empty())
779  
            return seg.is_absolute();
779  
            return seg.is_absolute();
780  

780  

781  
        std::size_t n = 0;
781  
        std::size_t n = 0;
782  
        std::size_t skip = 0;
782  
        std::size_t skip = 0;
783  
        auto begin = seg.begin();
783  
        auto begin = seg.begin();
784  
        auto it = seg.end();
784  
        auto it = seg.end();
785  
        while (it != begin)
785  
        while (it != begin)
786  
        {
786  
        {
787  
            --it;
787  
            --it;
788  
            decode_view dseg = **it;
788  
            decode_view dseg = **it;
789  
            if (dseg == "..")
789  
            if (dseg == "..")
790  
                ++skip;
790  
                ++skip;
791  
            else if (dseg != ".")
791  
            else if (dseg != ".")
792  
            {
792  
            {
793  
                if (skip)
793  
                if (skip)
794  
                    --skip;
794  
                    --skip;
795  
                else
795  
                else
796  
                    n += dseg.size() + 1;
796  
                    n += dseg.size() + 1;
797  
            }
797  
            }
798  
        }
798  
        }
799  
        n += skip * 3;
799  
        n += skip * 3;
800  
        n -= !seg.is_absolute();
800  
        n -= !seg.is_absolute();
801  
        return n;
801  
        return n;
802  
    };
802  
    };
803  

803  

804  
    // find the normalized size for the comparison
804  
    // find the normalized size for the comparison
805  
    std::size_t n0 = normalized_size(seg0);
805  
    std::size_t n0 = normalized_size(seg0);
806  
    std::size_t n1 = normalized_size(seg1);
806  
    std::size_t n1 = normalized_size(seg1);
807  
    std::size_t n00 = n0;
807  
    std::size_t n00 = n0;
808  
    std::size_t n10 = n1;
808  
    std::size_t n10 = n1;
809  

809  

810  
    // consume the last char from a segment range
810  
    // consume the last char from a segment range
811  
    auto consume_last =
811  
    auto consume_last =
812  
        [](
812  
        [](
813  
            std::size_t& n,
813  
            std::size_t& n,
814  
            decode_view& dseg,
814  
            decode_view& dseg,
815  
            segments_encoded_view::iterator& begin,
815  
            segments_encoded_view::iterator& begin,
816  
            segments_encoded_view::iterator& it,
816  
            segments_encoded_view::iterator& it,
817  
            decode_view::iterator& cit,
817  
            decode_view::iterator& cit,
818  
            std::size_t& skip,
818  
            std::size_t& skip,
819  
            bool& at_slash) -> char
819  
            bool& at_slash) -> char
820  
    {
820  
    {
821  
        if (cit != dseg.begin())
821  
        if (cit != dseg.begin())
822  
        {
822  
        {
823  
            // return last char from current segment
823  
            // return last char from current segment
824  
            at_slash = false;
824  
            at_slash = false;
825  
            --cit;
825  
            --cit;
826  
            --n;
826  
            --n;
827  
            return *cit;
827  
            return *cit;
828  
        }
828  
        }
829  

829  

830  
        if (!at_slash)
830  
        if (!at_slash)
831  
        {
831  
        {
832  
            // current segment dseg is over and
832  
            // current segment dseg is over and
833  
            // previous char was not a slash
833  
            // previous char was not a slash
834  
            // so we output one
834  
            // so we output one
835  
            at_slash = true;
835  
            at_slash = true;
836  
            --n;
836  
            --n;
837  
            return '/';
837  
            return '/';
838  
        }
838  
        }
839  

839  

840  
        // current segment dseg is over and
840  
        // current segment dseg is over and
841  
        // last char was already the slash
841  
        // last char was already the slash
842  
        // between segments, so take the
842  
        // between segments, so take the
843  
        // next final segment to consume
843  
        // next final segment to consume
844  
        at_slash = false;
844  
        at_slash = false;
845  
        while (cit == dseg.begin())
845  
        while (cit == dseg.begin())
846  
        {
846  
        {
847  
            // take next segment
847  
            // take next segment
848  
            if (it != begin)
848  
            if (it != begin)
849  
                --it;
849  
                --it;
850  
            else
850  
            else
851  
                break;
851  
                break;
852  
            if (**it == "..")
852  
            if (**it == "..")
853  
            {
853  
            {
854  
                // skip next if this is ".."
854  
                // skip next if this is ".."
855  
                ++skip;
855  
                ++skip;
856  
            }
856  
            }
857  
            else if (**it != ".")
857  
            else if (**it != ".")
858  
            {
858  
            {
859  
                if (skip)
859  
                if (skip)
860  
                {
860  
                {
861  
                    // discount skips
861  
                    // discount skips
862  
                    --skip;
862  
                    --skip;
863  
                }
863  
                }
864  
                else
864  
                else
865  
                {
865  
                {
866  
                    // or update current seg
866  
                    // or update current seg
867  
                    dseg = **it;
867  
                    dseg = **it;
868  
                    cit = dseg.end();
868  
                    cit = dseg.end();
869  
                    break;
869  
                    break;
870  
                }
870  
                }
871  
            }
871  
            }
872  
        }
872  
        }
873  
        // consume from the new current
873  
        // consume from the new current
874  
        // segment
874  
        // segment
875  
        --n;
875  
        --n;
876  
        if (cit != dseg.begin())
876  
        if (cit != dseg.begin())
877  
        {
877  
        {
878  
            // in the general case, we consume
878  
            // in the general case, we consume
879  
            // one more character from the end
879  
            // one more character from the end
880  
            --cit;
880  
            --cit;
881  
            return *cit;
881  
            return *cit;
882  
        }
882  
        }
883  

883  

884  
        // nothing left to consume in the
884  
        // nothing left to consume in the
885  
        // current and new segment
885  
        // current and new segment
886  
        if (it == begin)
886  
        if (it == begin)
887  
        {
887  
        {
888  
            // if this is the first
888  
            // if this is the first
889  
            // segment, the segments are
889  
            // segment, the segments are
890  
            // over and there can only
890  
            // over and there can only
891  
            // be repetitions of "../" to
891  
            // be repetitions of "../" to
892  
            // output
892  
            // output
893  
            return "/.."[n % 3];
893  
            return "/.."[n % 3];
894  
        }
894  
        }
895  
        // at other segments, we need
895  
        // at other segments, we need
896  
        // a slash to transition to the
896  
        // a slash to transition to the
897  
        // next segment
897  
        // next segment
898  
        at_slash = true;
898  
        at_slash = true;
899  
        return '/';
899  
        return '/';
900  
    };
900  
    };
901  

901  

902  
    // consume final segments from seg0 that
902  
    // consume final segments from seg0 that
903  
    // should not influence the comparison
903  
    // should not influence the comparison
904  
    auto begin0 = seg0.begin();
904  
    auto begin0 = seg0.begin();
905  
    auto it0 = seg0.end();
905  
    auto it0 = seg0.end();
906  
    decode_view dseg0;
906  
    decode_view dseg0;
907  
    if (it0 != seg0.begin())
907  
    if (it0 != seg0.begin())
908  
    {
908  
    {
909  
        --it0;
909  
        --it0;
910  
        dseg0 = **it0;
910  
        dseg0 = **it0;
911  
    }
911  
    }
912  
    decode_view::iterator cit0 = dseg0.end();
912  
    decode_view::iterator cit0 = dseg0.end();
913  
    std::size_t skip0 = 0;
913  
    std::size_t skip0 = 0;
914  
    bool at_slash0 = true;
914  
    bool at_slash0 = true;
915  
    while (n0 > n1)
915  
    while (n0 > n1)
916  
    {
916  
    {
917  
        consume_last(n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
917  
        consume_last(n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
918  
    }
918  
    }
919  

919  

920  
    // consume final segments from seg1 that
920  
    // consume final segments from seg1 that
921  
    // should not influence the comparison
921  
    // should not influence the comparison
922  
    auto begin1 = seg1.begin();
922  
    auto begin1 = seg1.begin();
923  
    auto it1 = seg1.end();
923  
    auto it1 = seg1.end();
924  
    decode_view dseg1;
924  
    decode_view dseg1;
925  
    if (it1 != seg1.begin())
925  
    if (it1 != seg1.begin())
926  
    {
926  
    {
927  
        --it1;
927  
        --it1;
928  
        dseg1 = **it1;
928  
        dseg1 = **it1;
929  
    }
929  
    }
930  
    decode_view::iterator cit1 = dseg1.end();
930  
    decode_view::iterator cit1 = dseg1.end();
931  
    std::size_t skip1 = 0;
931  
    std::size_t skip1 = 0;
932  
    bool at_slash1 = true;
932  
    bool at_slash1 = true;
933  
    while (n1 > n0)
933  
    while (n1 > n0)
934  
    {
934  
    {
935  
        consume_last(n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
935  
        consume_last(n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
936  
    }
936  
    }
937  

937  

938  
    int cmp = 0;
938  
    int cmp = 0;
939  
    while (n0)
939  
    while (n0)
940  
    {
940  
    {
941  
        char c0 = consume_last(
941  
        char c0 = consume_last(
942  
            n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
942  
            n0, dseg0, begin0, it0, cit0, skip0, at_slash0);
943  
        char c1 = consume_last(
943  
        char c1 = consume_last(
944  
            n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
944  
            n1, dseg1, begin1, it1, cit1, skip1, at_slash1);
945  
        if (c0 < c1)
945  
        if (c0 < c1)
946  
            cmp = -1;
946  
            cmp = -1;
947  
        else if (c1 < c0)
947  
        else if (c1 < c0)
948  
            cmp = +1;
948  
            cmp = +1;
949  
    }
949  
    }
950  

950  

951  
    if (cmp != 0)
951  
    if (cmp != 0)
952  
        return cmp;
952  
        return cmp;
953  
    if ( n00 == n10 )
953  
    if ( n00 == n10 )
954  
        return 0;
954  
        return 0;
955  
    if ( n00 < n10 )
955  
    if ( n00 < n10 )
956  
        return -1;
956  
        return -1;
957  
    return 1;
957  
    return 1;
958  
}
958  
}
959  

959  

960  
} // detail
960  
} // detail
961  
} // urls
961  
} // urls
962  
} // boost
962  
} // boost
963  

963