99.50% Lines (396/398) 100.00% Functions (25/25)
TLA Baseline Branch
Line Hits Code Line Hits Code
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 <boost/url/detail/normalize.hpp> 21   #include <boost/url/detail/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
HITCBC 28   7864 pop_encoded_front( 28   8508 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   {
HITCBC 33   7864 if(s.front() != '%') 33   8508 if(s.front() != '%')
34   { 34   {
HITCBC 35   7698 c = s.front(); 35   8288 c = s.front();
HITCBC 36   7698 s.remove_prefix(1); 36   8288 s.remove_prefix(1);
37   } 37   }
38   else 38   else
39   { 39   {
HITCBC 40   166 detail::decode_unsafe( 40   220 detail::decode_unsafe(
41   &c, 41   &c,
42   &c + 1, 42   &c + 1,
43   s.substr(0, 3)); 43   s.substr(0, 3));
HITCBC 44   166 s.remove_prefix(3); 44   220 s.remove_prefix(3);
45   } 45   }
HITCBC 46   7864 ++n; 46   8508 ++n;
HITCBC 47   7864 } 47   8508 }
48   48  
49   int 49   int
HITCBC 50   64 compare_encoded( 50   70 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   {
HITCBC 54   64 std::size_t n0 = 0; 54   70 std::size_t n0 = 0;
HITCBC 55   64 std::size_t n1 = 0; 55   70 std::size_t n1 = 0;
HITCBC 56   64 char c0 = 0; 56   70 char c0 = 0;
HITCBC 57   64 char c1 = 0; 57   70 char c1 = 0;
HITCBC 58   64 while( 58   70 while(
HITCBC 59   486 !lhs.empty() && 59   528 !lhs.empty() &&
HITCBC 60   228 !rhs.empty()) 60   246 !rhs.empty())
61   { 61   {
HITCBC 62   215 pop_encoded_front(lhs, c0, n0); 62   233 pop_encoded_front(lhs, c0, n0);
HITCBC 63   215 pop_encoded_front(rhs, c1, n1); 63   233 pop_encoded_front(rhs, c1, n1);
HITCBC 64   215 if (c0 < c1) 64   233 if (c0 < c1)
HITCBC 65   18 return -1; 65   18 return -1;
HITCBC 66   197 if (c1 < c0) 66   215 if (c1 < c0)
HITCBC 67   3 return 1; 67   3 return 1;
68   } 68   }
HITCBC 69   43 n0 += detail::decode_bytes_unsafe(lhs); 69   49 n0 += detail::decode_bytes_unsafe(lhs);
HITCBC 70   43 n1 += detail::decode_bytes_unsafe(rhs); 70   49 n1 += detail::decode_bytes_unsafe(rhs);
HITCBC 71   43 if (n0 == n1) 71   49 if (n0 == n1)
HITCBC 72   22 return 0; 72   28 return 0;
HITCBC 73   21 if (n0 < n1) 73   21 if (n0 < n1)
HITCBC 74   8 return -1; 74   8 return -1;
HITCBC 75   13 return 1; 75   13 return 1;
76   } 76   }
77   77  
78   int 78   int
HITCBC 79   28 compare_encoded_query( 79   32 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  
HITCBC 87   28 std::size_t n0 = 0; 87   32 std::size_t n0 = 0;
HITCBC 88   28 std::size_t n1 = 0; 88   32 std::size_t n1 = 0;
HITCBC 89   28 char c0 = 0; 89   32 char c0 = 0;
HITCBC 90   28 char c1 = 0; 90   32 char c1 = 0;
HITCBC 91   28 while( 91   32 while(
HITCBC 92   254 !lhs.empty() && 92   294 !lhs.empty() &&
HITCBC 93   122 !rhs.empty()) 93   140 !rhs.empty())
94   { 94   {
HITCBC 95   121 bool const lhs_was_decoded = lhs.front() != '%'; 95   139 bool const lhs_was_decoded = lhs.front() != '%';
HITCBC 96   121 bool const rhs_was_decoded = rhs.front() != '%'; 96   139 bool const rhs_was_decoded = rhs.front() != '%';
HITCBC 97   121 pop_encoded_front(lhs, c0, n0); 97   139 pop_encoded_front(lhs, c0, n0);
HITCBC 98   121 pop_encoded_front(rhs, c1, n1); 98   139 pop_encoded_front(rhs, c1, n1);
HITCBC 99   121 if (c0 < c1) 99   139 if (c0 < c1)
HITCBC 100   2 return -1; 100   2 return -1;
HITCBC 101   119 if (c1 < c0) 101   137 if (c1 < c0)
HITCBC 102   12 return 1; 102   12 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?
HITCBC 106   107 if (query_compare_exception_lut(c0)) 106   125 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.
HITCBC 111   40 if (lhs_was_decoded == rhs_was_decoded) 111   44 if (lhs_was_decoded == rhs_was_decoded)
HITCBC 112   37 continue; 112   41 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 '%'
HITCBC 117   3 if (rhs_was_decoded) 117   3 if (rhs_was_decoded)
HITCBC 118   2 return -1; 118   2 return -1;
119   else 119   else
HITCBC 120   1 return 1; 120   1 return 1;
121   } 121   }
122   } 122   }
HITCBC 123   11 n0 += detail::decode_bytes_unsafe(lhs); 123   15 n0 += detail::decode_bytes_unsafe(lhs);
HITCBC 124   11 n1 += detail::decode_bytes_unsafe(rhs); 124   15 n1 += detail::decode_bytes_unsafe(rhs);
HITCBC 125   11 if (n0 == n1) 125   15 if (n0 == n1)
HITCBC 126   9 return 0; 126   13 return 0;
HITCBC 127   2 if (n0 < n1) 127   2 if (n0 < n1)
HITCBC 128   1 return -1; 128   1 return -1;
HITCBC 129   1 return 1; 129   1 return 1;
130   } 130   }
131   131  
132   void 132   void
HITCBC 133   1552 digest_encoded( 133   1632 digest_encoded(
134   core::string_view s, 134   core::string_view s,
135   fnv_1a& hasher) noexcept 135   fnv_1a& hasher) noexcept
136   { 136   {
HITCBC 137   1552 char c = 0; 137   1632 char c = 0;
HITCBC 138   1552 std::size_t n = 0; 138   1632 std::size_t n = 0;
HITCBC 139   2060 while(!s.empty()) 139   2272 while(!s.empty())
140   { 140   {
HITCBC 141   508 pop_encoded_front(s, c, n); 141   640 pop_encoded_front(s, c, n);
HITCBC 142   508 hasher.put(c); 142   640 hasher.put(c);
143   } 143   }
HITCBC 144   1552 } 144   1632 }
145   145  
146   int 146   int
HITCBC 147   183 ci_compare_encoded( 147   193 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   {
HITCBC 151   183 std::size_t n0 = 0; 151   193 std::size_t n0 = 0;
HITCBC 152   183 std::size_t n1 = 0; 152   193 std::size_t n1 = 0;
HITCBC 153   183 char c0 = 0; 153   193 char c0 = 0;
HITCBC 154   183 char c1 = 0; 154   193 char c1 = 0;
HITCBC 155   183 while ( 155   193 while (
HITCBC 156   4799 !lhs.empty() && 156   5029 !lhs.empty() &&
HITCBC 157   2317 !rhs.empty()) 157   2427 !rhs.empty())
158   { 158   {
HITCBC 159   2311 pop_encoded_front(lhs, c0, n0); 159   2421 pop_encoded_front(lhs, c0, n0);
HITCBC 160   2311 pop_encoded_front(rhs, c1, n1); 160   2421 pop_encoded_front(rhs, c1, n1);
HITCBC 161   2311 c0 = grammar::to_lower(c0); 161   2421 c0 = grammar::to_lower(c0);
HITCBC 162   2311 c1 = grammar::to_lower(c1); 162   2421 c1 = grammar::to_lower(c1);
HITCBC 163   2311 if (c0 < c1) 163   2421 if (c0 < c1)
HITCBC 164   10 return -1; 164   10 return -1;
HITCBC 165   2301 if (c1 < c0) 165   2411 if (c1 < c0)
HITCBC 166   2 return 1; 166   2 return 1;
167   } 167   }
HITCBC 168   171 n0 += detail::decode_bytes_unsafe(lhs); 168   181 n0 += detail::decode_bytes_unsafe(lhs);
HITCBC 169   171 n1 += detail::decode_bytes_unsafe(rhs); 169   181 n1 += detail::decode_bytes_unsafe(rhs);
HITCBC 170   171 if (n0 == n1) 170   181 if (n0 == n1)
HITCBC 171   164 return 0; 171   174 return 0;
HITCBC 172   7 if (n0 < n1) 172   7 if (n0 < n1)
HITCBC 173   1 return -1; 173   1 return -1;
HITCBC 174   6 return 1; 174   6 return 1;
175   } 175   }
176   176  
177   void 177   void
HITCBC 178   388 ci_digest_encoded( 178   408 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   {
HITCBC 182   388 char c = 0; 182   408 char c = 0;
HITCBC 183   388 std::size_t n = 0; 183   408 std::size_t n = 0;
HITCBC 184   2450 while(!s.empty()) 184   2690 while(!s.empty())
185   { 185   {
HITCBC 186   2062 pop_encoded_front(s, c, n); 186   2282 pop_encoded_front(s, c, n);
HITCBC 187   2062 c = grammar::to_lower(c); 187   2282 c = grammar::to_lower(c);
HITCBC 188   2062 hasher.put(c); 188   2282 hasher.put(c);
189   } 189   }
HITCBC 190   388 } 190   408 }
191   191  
192   int 192   int
HITCBC 193   46 compare( 193   46 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   {
HITCBC 197   46 auto rlen = (std::min)(lhs.size(), rhs.size()); 197   46 auto rlen = (std::min)(lhs.size(), rhs.size());
HITCBC 198   104 for (std::size_t i = 0; i < rlen; ++i) 198   104 for (std::size_t i = 0; i < rlen; ++i)
199   { 199   {
HITCBC 200   79 char c0 = lhs[i]; 200   79 char c0 = lhs[i];
HITCBC 201   79 char c1 = rhs[i]; 201   79 char c1 = rhs[i];
HITCBC 202   79 if (c0 < c1) 202   79 if (c0 < c1)
HITCBC 203   13 return -1; 203   13 return -1;
HITCBC 204   66 if (c1 < c0) 204   66 if (c1 < c0)
HITCBC 205   8 return 1; 205   8 return 1;
206   } 206   }
HITCBC 207   25 if ( lhs.size() == rhs.size() ) 207   25 if ( lhs.size() == rhs.size() )
HITCBC 208   4 return 0; 208   4 return 0;
HITCBC 209   21 if ( lhs.size() < rhs.size() ) 209   21 if ( lhs.size() < rhs.size() )
HITCBC 210   8 return -1; 210   8 return -1;
HITCBC 211   13 return 1; 211   13 return 1;
212   } 212   }
213   213  
214   int 214   int
HITCBC 215   226 ci_compare( 215   236 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   {
HITCBC 219   226 auto rlen = (std::min)(lhs.size(), rhs.size()); 219   236 auto rlen = (std::min)(lhs.size(), rhs.size());
HITCBC 220   1155 for (std::size_t i = 0; i < rlen; ++i) 220   1213 for (std::size_t i = 0; i < rlen; ++i)
221   { 221   {
HITCBC 222   936 char c0 = grammar::to_lower(lhs[i]); 222   984 char c0 = grammar::to_lower(lhs[i]);
HITCBC 223   936 char c1 = grammar::to_lower(rhs[i]); 223   984 char c1 = grammar::to_lower(rhs[i]);
HITCBC 224   936 if (c0 < c1) 224   984 if (c0 < c1)
HITCBC 225   6 return -1; 225   6 return -1;
HITCBC 226   930 if (c1 < c0) 226   978 if (c1 < c0)
HITCBC 227   1 return 1; 227   1 return 1;
228   } 228   }
HITCBC 229   219 if ( lhs.size() == rhs.size() ) 229   229 if ( lhs.size() == rhs.size() )
HITCBC 230   211 return 0; 230   221 return 0;
HITCBC 231   8 if ( lhs.size() < rhs.size() ) 231   8 if ( lhs.size() < rhs.size() )
HITCBC 232   6 return -1; 232   6 return -1;
HITCBC 233   2 return 1; 233   2 return 1;
234   } 234   }
235   235  
236   void 236   void
HITCBC 237   388 ci_digest( 237   408 ci_digest(
238   core::string_view s, 238   core::string_view s,
239   fnv_1a& hasher) noexcept 239   fnv_1a& hasher) noexcept
240   { 240   {
HITCBC 241   1118 for (char c: s) 241   1254 for (char c: s)
242   { 242   {
HITCBC 243   730 c = grammar::to_lower(c); 243   846 c = grammar::to_lower(c);
HITCBC 244   730 hasher.put(c); 244   846 hasher.put(c);
245   } 245   }
HITCBC 246   388 } 246   408 }
247   247  
248   std::size_t 248   std::size_t
HITCBC 249   1107 remove_dot_segments( 249   1112 remove_dot_segments(
250   char* dest0, 250   char* dest0,
251   char const* end, 251   char const* end,
252   core::string_view input) noexcept 252   core::string_view input) noexcept
253   { 253   {
254   // 1. The input buffer `s` is initialized with 254   // 1. The input buffer `s` is initialized with
255   // the now-appended path components and the 255   // the now-appended path components and the
256   // output buffer `dest0` is initialized to 256   // output buffer `dest0` is initialized to
257   // the empty string. 257   // the empty string.
HITCBC 258   1107 char* dest = dest0; 258   1112 char* dest = dest0;
HITCBC 259   1107 bool const is_absolute = input.starts_with('/'); 259   1112 bool const is_absolute = input.starts_with('/');
260   260  
261   // Step 2 is a loop through 5 production rules: 261   // Step 2 is a loop through 5 production rules:
262   // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4 262   // https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4
263   // 263   //
264   // There are no transitions between all rules, 264   // There are no transitions between all rules,
265   // which enables some optimizations. 265   // which enables some optimizations.
266   // 266   //
267   // Initial: 267   // Initial:
268   // - Rule A: handle initial dots 268   // - Rule A: handle initial dots
269   // If the input buffer begins with a 269   // If the input buffer begins with a
270   // prefix of "../" or "./", then remove 270   // prefix of "../" or "./", then remove
271   // that prefix from the input buffer. 271   // that prefix from the input buffer.
272   // Rule A can only happen at the beginning. 272   // Rule A can only happen at the beginning.
273   // Errata 4547: Keep "../" in the beginning 273   // Errata 4547: Keep "../" in the beginning
274   // https://www.rfc-editor.org/errata/eid4547 274   // https://www.rfc-editor.org/errata/eid4547
275   // 275   //
276   // Then: 276   // Then:
277   // - Rule D: ignore a final ".." or "." 277   // - Rule D: ignore a final ".." or "."
278   // if the input buffer consists only of "." 278   // if the input buffer consists only of "."
279   // or "..", then remove that from the input 279   // or "..", then remove that from the input
280   // buffer. 280   // buffer.
281   // Rule D can only happen after Rule A because: 281   // Rule D can only happen after Rule A because:
282   // - B and C write "/" to the input 282   // - B and C write "/" to the input
283   // - E writes "/" to input or returns 283   // - E writes "/" to input or returns
284   // 284   //
285   // Then: 285   // Then:
286   // - Rule B: ignore ".": write "/" to the input 286   // - Rule B: ignore ".": write "/" to the input
287   // - Rule C: apply "..": remove seg and write "/" 287   // - Rule C: apply "..": remove seg and write "/"
288   // - Rule E: copy complete segment 288   // - Rule E: copy complete segment
289   auto append = 289   auto append =
HITCBC 290   1945 [](char*& first, char const* last, core::string_view in) 290   1950 [](char*& first, char const* last, core::string_view in)
291   { 291   {
292   // append `in` to `dest` 292   // append `in` to `dest`
HITCBC 293   1945 BOOST_ASSERT(in.size() <= std::size_t(last - first)); 293   1950 BOOST_ASSERT(in.size() <= std::size_t(last - first));
HITCBC 294   1945 std::memmove(first, in.data(), in.size()); 294   1950 std::memmove(first, in.data(), in.size());
HITCBC 295   1945 first += in.size(); 295   1950 first += in.size();
296   ignore_unused(last); 296   ignore_unused(last);
HITCBC 297   1945 }; 297   1950 };
298   298  
HITCBC 299   12503 auto dot_starts_with = []( 299   12543 auto dot_starts_with = [](
300   core::string_view str, core::string_view dots, std::size_t& n) 300   core::string_view str, core::string_view dots, std::size_t& n)
301   { 301   {
302   // starts_with for encoded/decoded dots 302   // starts_with for encoded/decoded dots
303   // or decoded otherwise. return how many 303   // or decoded otherwise. return how many
304   // chars in str match the dots 304   // chars in str match the dots
HITCBC 305   12503 n = 0; 305   12543 n = 0;
HITCBC 306   21903 for (char c: dots) 306   21963 for (char c: dots)
307   { 307   {
HITCBC 308   21257 if (str.starts_with(c)) 308   21317 if (str.starts_with(c))
309   { 309   {
HITCBC 310   9400 str.remove_prefix(1); 310   9420 str.remove_prefix(1);
HITCBC 311   9400 ++n; 311   9420 ++n;
HITCBC 312   9400 continue; 312   9420 continue;
313   } 313   }
314   314  
315   // In the general case, we would need to 315   // In the general case, we would need to
316   // check if the next char is an encoded 316   // check if the next char is an encoded
317   // dot. 317   // dot.
318   // However, an encoded dot in `str` 318   // However, an encoded dot in `str`
319   // would have already been decoded in 319   // would have already been decoded in
320   // url_base::normalize_path(). 320   // url_base::normalize_path().
321   // This needs to be undone if 321   // This needs to be undone if
322   // `remove_dot_segments` is used in a 322   // `remove_dot_segments` is used in a
323   // different context. 323   // different context.
324   // if (str.size() > 2 && 324   // if (str.size() > 2 &&
325   // c == '.' 325   // c == '.'
326   // && 326   // &&
327   // str[0] == '%' && 327   // str[0] == '%' &&
328   // str[1] == '2' && 328   // str[1] == '2' &&
329   // (str[2] == 'e' || 329   // (str[2] == 'e' ||
330   // str[2] == 'E')) 330   // str[2] == 'E'))
331   // { 331   // {
332   // str.remove_prefix(3); 332   // str.remove_prefix(3);
333   // n += 3; 333   // n += 3;
334   // continue; 334   // continue;
335   // } 335   // }
336   336  
HITCBC 337   11857 n = 0; 337   11897 n = 0;
HITCBC 338   11857 return false; 338   11897 return false;
339   } 339   }
HITCBC 340   646 return true; 340   646 return true;
341   }; 341   };
342   342  
HITCBC 343   6260 auto dot_equal = [&dot_starts_with]( 343   6280 auto dot_equal = [&dot_starts_with](
344   core::string_view str, core::string_view dots) 344   core::string_view str, core::string_view dots)
345   { 345   {
HITCBC 346   6260 std::size_t n = 0; 346   6280 std::size_t n = 0;
HITCBC 347   6260 dot_starts_with(str, dots, n); 347   6280 dot_starts_with(str, dots, n);
HITCBC 348   6260 return n == str.size(); 348   6280 return n == str.size();
HITCBC 349   1107 }; 349   1112 };
350   350  
351   // Rule A 351   // Rule A
352   std::size_t n; 352   std::size_t n;
HITCBC 353   1129 while (!input.empty()) 353   1134 while (!input.empty())
354   { 354   {
HITCBC 355   1003 if (dot_starts_with(input, "../", n)) 355   1008 if (dot_starts_with(input, "../", n))
356   { 356   {
357   // Errata 4547 357   // Errata 4547
HITCBC 358   4 append(dest, end, "../"); 358   4 append(dest, end, "../");
HITCBC 359   4 input.remove_prefix(n); 359   4 input.remove_prefix(n);
HITCBC 360   4 continue; 360   4 continue;
361   } 361   }
HITCBC 362   999 else if (!dot_starts_with(input, "./", n)) 362   1004 else if (!dot_starts_with(input, "./", n))
363   { 363   {
HITCBC 364   981 break; 364   986 break;
365   } 365   }
HITCBC 366   18 input.remove_prefix(n); 366   18 input.remove_prefix(n);
367   } 367   }
368   368  
369   // Rule D 369   // Rule D
HITCBC 370   1107 if( dot_equal(input, ".")) 370   1112 if( dot_equal(input, "."))
371   { 371   {
HITCBC 372   127 input = {}; 372   127 input = {};
373   } 373   }
HITCBC 374   980 else if( dot_equal(input, "..") ) 374   985 else if( dot_equal(input, "..") )
375   { 375   {
376   // Errata 4547 376   // Errata 4547
HITCBC 377   3 append(dest, end, ".."); 377   3 append(dest, end, "..");
HITCBC 378   3 input = {}; 378   3 input = {};
379   } 379   }
380   380  
381   // 2. While the input buffer is not empty, 381   // 2. While the input buffer is not empty,
382   // loop as follows: 382   // loop as follows:
HITCBC 383   3209 while (!input.empty()) 383   3219 while (!input.empty())
384   { 384   {
385   // Rule B 385   // Rule B
HITCBC 386   2147 bool const is_dot_seg = dot_starts_with(input, "/./", n); 386   2152 bool const is_dot_seg = dot_starts_with(input, "/./", n);
HITCBC 387   2147 if (is_dot_seg) 387   2152 if (is_dot_seg)
388   { 388   {
HITCBC 389   43 input.remove_prefix(n - 1); 389   43 input.remove_prefix(n - 1);
HITCBC 390   43 continue; 390   43 continue;
391   } 391   }
392   392  
HITCBC 393   2104 bool const is_final_dot_seg = dot_equal(input, "/."); 393   2109 bool const is_final_dot_seg = dot_equal(input, "/.");
HITCBC 394   2104 if (is_final_dot_seg) 394   2109 if (is_final_dot_seg)
395   { 395   {
396   // We can't remove "." from a core::string_view 396   // We can't remove "." from a core::string_view
397   // So what we do here is equivalent to 397   // So what we do here is equivalent to
398   // replacing s with '/' as required 398   // replacing s with '/' as required
399   // in Rule B and executing the next 399   // in Rule B and executing the next
400   // iteration, which would append this 400   // iteration, which would append this
401   // '/' to the output, as required by 401   // '/' to the output, as required by
402   // Rule E 402   // Rule E
HITCBC 403   10 append(dest, end, input.substr(0, 1)); 403   10 append(dest, end, input.substr(0, 1));
HITCBC 404   10 input = {}; 404   10 input = {};
HITCBC 405   10 break; 405   10 break;
406   } 406   }
407   407  
408   // Rule C 408   // Rule C
HITCBC 409   2094 bool const is_dotdot_seg = dot_starts_with(input, "/../", n); 409   2099 bool const is_dotdot_seg = dot_starts_with(input, "/../", n);
HITCBC 410   2094 if (is_dotdot_seg) 410   2099 if (is_dotdot_seg)
411   { 411   {
HITCBC 412   227 core::string_view cur_out(dest0, dest - dest0); 412   227 core::string_view cur_out(dest0, dest - dest0);
HITCBC 413   227 std::size_t p = cur_out.find_last_of('/'); 413   227 std::size_t p = cur_out.find_last_of('/');
HITCBC 414   227 bool const has_multiple_segs = p != core::string_view::npos; 414   227 bool const has_multiple_segs = p != core::string_view::npos;
HITCBC 415   227 if (has_multiple_segs) 415   227 if (has_multiple_segs)
416   { 416   {
417   // output has multiple segments 417   // output has multiple segments
418   // "erase" [p, end] if not "/.." 418   // "erase" [p, end] if not "/.."
HITCBC 419   147 core::string_view last_seg(dest0 + p, dest - (dest0 + p)); 419   147 core::string_view last_seg(dest0 + p, dest - (dest0 + p));
HITCBC 420   147 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/.."); 420   147 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
HITCBC 421   147 if (!prev_is_dotdot_seg) 421   147 if (!prev_is_dotdot_seg)
422   { 422   {
HITCBC 423   136 dest = dest0 + p; 423   136 dest = dest0 + p;
424   } 424   }
425   else 425   else
426   { 426   {
HITCBC 427   11 append(dest, end, "/.."); 427   11 append(dest, end, "/..");
428   } 428   }
429   } 429   }
HITCBC 430   80 else if (dest0 != dest) 430   80 else if (dest0 != dest)
431   { 431   {
432   // Only one segment in the output: remove it 432   // Only one segment in the output: remove it
HITCBC 433   30 core::string_view last_seg(dest0, dest - dest0); 433   30 core::string_view last_seg(dest0, dest - dest0);
HITCBC 434   30 bool const prev_is_dotdot_seg = dot_equal(last_seg, ".."); 434   30 bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
HITCBC 435   30 if (!prev_is_dotdot_seg) 435   30 if (!prev_is_dotdot_seg)
436   { 436   {
HITCBC 437   28 dest = dest0; 437   28 dest = dest0;
HITCBC 438   28 if (!is_absolute) 438   28 if (!is_absolute)
439   { 439   {
HITCBC 440   28 input.remove_prefix(1); 440   28 input.remove_prefix(1);
441   } 441   }
442   } 442   }
443   else 443   else
444   { 444   {
HITCBC 445   2 append(dest, end, "/.."); 445   2 append(dest, end, "/..");
446   } 446   }
447   } 447   }
448   else 448   else
449   { 449   {
450   // Output is empty 450   // Output is empty
HITCBC 451   50 if (is_absolute) 451   50 if (is_absolute)
452   { 452   {
HITCBC 453   50 append(dest, end, "/.."); 453   50 append(dest, end, "/..");
454   } 454   }
455   else 455   else
456   { 456   {
457   // AFREITAS: Although we have no formal proof 457   // AFREITAS: Although we have no formal proof
458   // for that, the output can't be relative 458   // for that, the output can't be relative
459   // and empty at this point because relative 459   // and empty at this point because relative
460   // paths will fall in the `dest0 != dest` 460   // paths will fall in the `dest0 != dest`
461   // case above of this rule C and then the 461   // case above of this rule C and then the
462   // general case of rule E for "..". 462   // general case of rule E for "..".
MISUBC 463   ✗ append(dest, end, ".."); 463   ✗ append(dest, end, "..");
464   } 464   }
465   } 465   }
HITCBC 466   227 input.remove_prefix(n - 1); 466   227 input.remove_prefix(n - 1);
HITCBC 467   227 continue; 467   227 continue;
HITCBC 468   227 } 468   227 }
469   469  
HITCBC 470   1867 bool const is_final_dotdot_seg = dot_equal(input, "/.."); 470   1872 bool const is_final_dotdot_seg = dot_equal(input, "/..");
HITCBC 471   1867 if (is_final_dotdot_seg) 471   1872 if (is_final_dotdot_seg)
472   { 472   {
HITCBC 473   35 core::string_view cur_out(dest0, dest - dest0); 473   35 core::string_view cur_out(dest0, dest - dest0);
HITCBC 474   35 std::size_t p = cur_out.find_last_of('/'); 474   35 std::size_t p = cur_out.find_last_of('/');
HITCBC 475   35 bool const has_multiple_segs = p != core::string_view::npos; 475   35 bool const has_multiple_segs = p != core::string_view::npos;
HITCBC 476   35 if (has_multiple_segs) 476   35 if (has_multiple_segs)
477   { 477   {
478   // output has multiple segments 478   // output has multiple segments
479   // "erase" [p, end] if not "/.." 479   // "erase" [p, end] if not "/.."
HITCBC 480   21 core::string_view last_seg(dest0 + p, dest - (dest0 + p)); 480   21 core::string_view last_seg(dest0 + p, dest - (dest0 + p));
HITCBC 481   21 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/.."); 481   21 bool const prev_is_dotdot_seg = dot_equal(last_seg, "/..");
HITCBC 482   21 if (!prev_is_dotdot_seg) 482   21 if (!prev_is_dotdot_seg)
483   { 483   {
HITCBC 484   17 dest = dest0 + p; 484   17 dest = dest0 + p;
HITCBC 485   17 append(dest, end, "/"); 485   17 append(dest, end, "/");
486   } 486   }
487   else 487   else
488   { 488   {
HITCBC 489   4 append(dest, end, "/.."); 489   4 append(dest, end, "/..");
490   } 490   }
491   } 491   }
HITCBC 492   14 else if (dest0 != dest) 492   14 else if (dest0 != dest)
493   { 493   {
494   // Only one segment in the output: remove it 494   // Only one segment in the output: remove it
HITCBC 495   4 core::string_view last_seg(dest0, dest - dest0); 495   4 core::string_view last_seg(dest0, dest - dest0);
HITCBC 496   4 bool const prev_is_dotdot_seg = dot_equal(last_seg, ".."); 496   4 bool const prev_is_dotdot_seg = dot_equal(last_seg, "..");
HITCBC 497   4 if (!prev_is_dotdot_seg) { 497   4 if (!prev_is_dotdot_seg) {
HITCBC 498   2 dest = dest0; 498   2 dest = dest0;
499   } 499   }
500   else 500   else
501   { 501   {
HITCBC 502   2 append(dest, end, "/.."); 502   2 append(dest, end, "/..");
503   } 503   }
504   } 504   }
505   else 505   else
506   { 506   {
507   // Output is empty: append dotdot 507   // Output is empty: append dotdot
HITCBC 508   10 if (is_absolute) 508   10 if (is_absolute)
509   { 509   {
HITCBC 510   10 append(dest, end, "/.."); 510   10 append(dest, end, "/..");
511   } 511   }
512   else 512   else
513   { 513   {
514   // AFREITAS: Although we have no formal proof 514   // AFREITAS: Although we have no formal proof
515   // for that, the output can't be relative 515   // for that, the output can't be relative
516   // and empty at this point because relative 516   // and empty at this point because relative
517   // paths will fall in the `dest0 != dest` 517   // paths will fall in the `dest0 != dest`
518   // case above of this rule C and then the 518   // case above of this rule C and then the
519   // general case of rule E for "..". 519   // general case of rule E for "..".
MISUBC 520   ✗ append(dest, end, ".."); 520   ✗ append(dest, end, "..");
521   } 521   }
522   } 522   }
HITCBC 523   35 input = {}; 523   35 input = {};
HITCBC 524   35 break; 524   35 break;
525   } 525   }
526   526  
527   // Rule E 527   // Rule E
HITCBC 528   1832 std::size_t p = input.find_first_of('/', 1); 528   1837 std::size_t p = input.find_first_of('/', 1);
HITCBC 529   1832 if (p != core::string_view::npos) 529   1837 if (p != core::string_view::npos)
530   { 530   {
HITCBC 531   900 append(dest, end, input.substr(0, p)); 531   900 append(dest, end, input.substr(0, p));
HITCBC 532   900 input.remove_prefix(p); 532   900 input.remove_prefix(p);
533   } 533   }
534   else 534   else
535   { 535   {
HITCBC 536   932 append(dest, end, input); 536   937 append(dest, end, input);
HITCBC 537   932 input = {}; 537   937 input = {};
538   } 538   }
539   } 539   }
540   540  
541   // 3. Finally, the output buffer is set 541   // 3. Finally, the output buffer is set
542   // as the result of remove_dot_segments, 542   // as the result of remove_dot_segments,
543   // and we return its size 543   // and we return its size
HITCBC 544   1107 return dest - dest0; 544   1112 return dest - dest0;
545   } 545   }
546   546  
547   namespace { 547   namespace {
548   548  
549   /* The chars of a normalized path, from the end 549   /* The chars of a normalized path, from the end
550   550  
551   segments_compare() 551   segments_compare()
552   and normalized_path_digest() 552   and normalized_path_digest()
553   must see the path that url_base::normalize() 553   must see the path that url_base::normalize()
554   would produce, but they cannot allocate, so 554   would produce, but they cannot allocate, so
555   that path is never materialized. This class 555   that path is never materialized. This class
556   produces its path chars one at a time in 556   produces its path chars one at a time in
557   reverse order instead, reading the encoded 557   reverse order instead, reading the encoded
558   segments in place: 558   segments in place:
559   559  
560   @code 560   @code
561   input: /a/./b/../c/ 561   input: /a/./b/../c/
562   normalized: /a/c/ 562   normalized: /a/c/
563   next(): '/' 'c' '/' 'a' '/' 563   next(): '/' 'c' '/' 'a' '/'
564   @endcode 564   @endcode
565   565  
566   @par Why from the end 566   @par Why from the end
567   567  
568   Doing it in reverse order ensures we 568   Doing it in reverse order ensures we
569   don't have to allocate memory. 569   don't have to allocate memory.
570   570  
571   Whether a segment survives depends on the 571   Whether a segment survives depends on the
572   segments after it. In "a/b/.." the ".." 572   segments after it. In "a/b/.." the ".."
573   removes "b", so "b" cannot be emitted, or 573   removes "b", so "b" cannot be emitted, or
574   skipped, before the ".." is seen. Reading 574   skipped, before the ".." is seen. Reading
575   from the end, the ".." always comes first, 575   from the end, the ".." always comes first,
576   and a count of pending ".." is enough to 576   and a count of pending ".." is enough to
577   decide each segment the moment it is read: 577   decide each segment the moment it is read:
578   578  
579   @code 579   @code
580   input: a/b/.. 580   input: a/b/..
581   read pending result 581   read pending result
582   ".." 1 582   ".." 1
583   "b" 0 removed by the ".." 583   "b" 0 removed by the ".."
584   "a" 0 emitted 584   "a" 0 emitted
585   @endcode 585   @endcode
586   586  
587   Reading from the front would need to keep 587   Reading from the front would need to keep
588   every segment read so far until it is known 588   every segment read so far until it is known
589   that no later ".." removes it, which is the 589   that no later ".." removes it, which is the
590   buffer remove_dot_segments() writes to. 590   buffer remove_dot_segments() writes to.
591   However, in this algorithm we have no buffer 591   However, in this algorithm we have no buffer
592   to write to and cannot allocate memory. 592   to write to and cannot allocate memory.
593   593  
594   Producing the chars backward still gives a 594   Producing the chars backward still gives a
595   lexicographic comparison in O(n). 595   lexicographic comparison in O(n).
596   segments_compare() aligns both paths at 596   segments_compare() aligns both paths at
597   their first char, reads the pairs of chars 597   their first char, reads the pairs of chars
598   from the end, and keeps the last difference 598   from the end, and keeps the last difference
599   it sees, which is the first difference from 599   it sees, which is the first difference from
600   the front. For that it needs the size of 600   the front. For that it needs the size of
601   each path before the first char is produced, 601   each path before the first char is produced,
602   so it can skip the extra chars at the end 602   so it can skip the extra chars at the end
603   of the longer path. 603   of the longer path.
604   604  
605   The only extra cost is comparing O(n) chars 605   The only extra cost is comparing O(n) chars
606   even in cases where the distinction between 606   even in cases where the distinction between
607   paths would be evident by comparing the first 607   paths would be evident by comparing the first
608   normalized chars. The only way to implement 608   normalized chars. The only way to implement
609   this as an optimization would be to have another 609   this as an optimization would be to have another
610   algorithm that doesn't operate in reverse but 610   algorithm that doesn't operate in reverse but
611   covers a few limited cases where we can do it 611   covers a few limited cases where we can do it
612   in regular order, like paths whose size is 612   in regular order, like paths whose size is
613   limited by a constant (in which case we can 613   limited by a constant (in which case we can
614   use a buffer) or paths without dotdot 614   use a buffer) or paths without dotdot
615   segments. Another possible optimization is not 615   segments. Another possible optimization is not
616   using `compare` as the implementation for 616   using `compare` as the implementation for
617   `operator==` and having a faster bool version 617   `operator==` and having a faster bool version
618   of compare segments that fails on the first 618   of compare segments that fails on the first
619   difference, in which case the order doesn't 619   difference, in which case the order doesn't
620   matter. 620   matter.
621   621  
622   @par How 622   @par How
623   623  
624   advance() reads the segments once, from the 624   advance() reads the segments once, from the
625   end, and decides each one as it is read. Two 625   end, and decides each one as it is read. Two
626   decisions depend on other segments, and both 626   decisions depend on other segments, and both
627   are made only when they are needed: 627   are made only when they are needed:
628   628  
629   @li Whether the path ends with "/" is decided 629   @li Whether the path ends with "/" is decided
630   by the first call to advance(). When the 630   by the first call to advance(). When the
631   last segment is "..", this reads ahead until 631   last segment is "..", this reads ahead until
632   that ".." removes a segment or the front is 632   that ".." removes a segment or the front is
633   reached: "/a/b/.." ends with "/" after 633   reached: "/a/b/.." ends with "/" after
634   reading "b", and "/x/../.." does not, after 634   reading "b", and "/x/../.." does not, after
635   reading every segment. 635   reading every segment.
636   636  
637   @li Whether the path gets a "./" prefix is 637   @li Whether the path gets a "./" prefix is
638   decided after the segments that survive were 638   decided after the segments that survive were
639   produced, because it depends on whether the 639   produced, because it depends on whether the
640   first of them is empty. 640   first of them is empty.
641   641  
642   The size of the normalized path is not known 642   The size of the normalized path is not known
643   in advance. normalized_path_size() counts it 643   in advance. normalized_path_size() counts it
644   by reading the path once more. 644   by reading the path once more.
645   645  
646   @par Rules 646   @par Rules
647   647  
648   The rules are the ones of remove_dot_segments() 648   The rules are the ones of remove_dot_segments()
649   and url_base::normalize_path(): 649   and url_base::normalize_path():
650   650  
651   @li "." segments are removed and ".." segments 651   @li "." segments are removed and ".." segments
652   remove the closest preceding segment: 652   remove the closest preceding segment:
653   "/a/./b/../c" becomes "/a/c". 653   "/a/./b/../c" becomes "/a/c".
654   654  
655   @li ".." segments with nothing left to remove 655   @li ".." segments with nothing left to remove
656   are kept at the front, as errata 4547 656   are kept at the front, as errata 4547
657   requires: "/a/../../b" becomes "/../b" and 657   requires: "/a/../../b" becomes "/../b" and
658   "a/../.." becomes "..". 658   "a/../.." becomes "..".
659   659  
660   @li A path whose last segment is "." or a ".." 660   @li A path whose last segment is "." or a ".."
661   that removed a segment ends with "/" (RFC 3986 661   that removed a segment ends with "/" (RFC 3986
662   section 5.2.4, rules B and C): "/a/.." becomes 662   section 5.2.4, rules B and C): "/a/.." becomes
663   "/", "/." becomes "/" and "x/./y/.." becomes 663   "/", "/." becomes "/" and "x/./y/.." becomes
664   "x/". 664   "x/".
665   665  
666   @li A relative path left with nothing but one 666   @li A relative path left with nothing but one
667   empty segment is the empty path: "a/.." and 667   empty segment is the empty path: "a/.." and
668   "." become "". 668   "." become "".
669   669  
670   @li A relative path whose first segment is 670   @li A relative path whose first segment is
671   empty would serialize with a leading "//", 671   empty would serialize with a leading "//",
672   which reads as an authority, so normalize() 672   which reads as an authority, so normalize()
673   adds a "./" prefix that this class emits too: 673   adds a "./" prefix that this class emits too:
674   "a/..//b" and ".//b" both become ".//b". 674   "a/..//b" and ".//b" both become ".//b".
675   675  
676   @li Segments are compared after decoding, so 676   @li Segments are compared after decoding, so
677   "%2E%2E" is "..". An encoded slash ("%2F") is 677   "%2E%2E" is "..". An encoded slash ("%2F") is
678   not decoded by normalize() and is not a 678   not decoded by normalize() and is not a
679   separator, so it is produced as '/' with a 679   separator, so it is produced as '/' with a
680   flag that lets callers tell it apart. 680   flag that lets callers tell it apart.
681   */ 681   */
682   class normalized_path_reverse 682   class normalized_path_reverse
683   { 683   {
684   /* The types of segment advance() produces 684   /* The types of segment advance() produces
685   685  
686   advance() goes through these types in 686   advance() goes through these types in
687   the order they are declared, which is the 687   the order they are declared, which is the
688   order these types of segments appear when 688   order these types of segments appear when
689   the normalized path is read from its end 689   the normalized path is read from its end
690   to its front: 690   to its front:
691   691  
692   @code 692   @code
693   path: ../a/b/. 693   path: ../a/b/.
694   normalized: ../a/b/ 694   normalized: ../a/b/
695   advance(): "" trailing_empty_segment 695   advance(): "" trailing_empty_segment
696   "b", "a" surviving_segment 696   "b", "a" surviving_segment
697   ".." dotdot_prefix 697   ".." dotdot_prefix
698   @endcode 698   @endcode
699   699  
700   dot_prefix comes last because it is the 700   dot_prefix comes last because it is the
701   first segment of the normalized path. A 701   first segment of the normalized path. A
702   path never has both a dot_prefix and a 702   path never has both a dot_prefix and a
703   dotdot_prefix: the "./" prefix is only 703   dotdot_prefix: the "./" prefix is only
704   needed when nothing is left over at the 704   needed when nothing is left over at the
705   front. 705   front.
706   */ 706   */
707   enum class segment_type 707   enum class segment_type
708   { 708   {
709   // The empty last segment of a path that 709   // The empty last segment of a path that
710   // ends with "/". For "/a/b/..", which 710   // ends with "/". For "/a/b/..", which
711   // normalizes to "/a/", this produces "". 711   // normalizes to "/a/", this produces "".
712   trailing_empty_segment, 712   trailing_empty_segment,
713   713  
714   // The segments that survive, last first. 714   // The segments that survive, last first.
715   // For "/a/./b/../c", which normalizes to 715   // For "/a/./b/../c", which normalizes to
716   // "/a/c", this produces "c", then "a". 716   // "/a/c", this produces "c", then "a".
717   surviving_segment, 717   surviving_segment,
718   718  
719   // The ".." left over at the front. For 719   // The ".." left over at the front. For
720   // "a/../../..", which normalizes to 720   // "a/../../..", which normalizes to
721   // "../..", this produces "..", then "..". 721   // "../..", this produces "..", then "..".
722   dotdot_prefix, 722   dotdot_prefix,
723   723  
724   // The "." of the "./" prefix. For 724   // The "." of the "./" prefix. For
725   // "a/..//b", which normalizes to ".//b", 725   // "a/..//b", which normalizes to ".//b",
726   // this produces ".". 726   // this produces ".".
727   dot_prefix, 727   dot_prefix,
728   728  
729   // Nothing is left, so advance() returns 729   // Nothing is left, so advance() returns
730   // false. 730   // false.
731   end 731   end
732   }; 732   };
733   733  
734   // The type of segment advance() produces 734   // The type of segment advance() produces
735   // next. It starts at trailing_empty_segment, 735   // next. It starts at trailing_empty_segment,
736   // the type that comes first when reading 736   // the type that comes first when reading
737   // from the end, and only moves forward. For 737   // from the end, and only moves forward. For
738   // "../a/b/." it stays at surviving_segment 738   // "../a/b/." it stays at surviving_segment
739   // while producing "b" and "a", then moves 739   // while producing "b" and "a", then moves
740   // to dotdot_prefix to produce "..". 740   // to dotdot_prefix to produce "..".
741   segment_type cur_segment_type_ = 741   segment_type cur_segment_type_ =
742   segment_type::trailing_empty_segment; 742   segment_type::trailing_empty_segment;
743   743  
744   // The segments, and the next one to read 744   // The segments, and the next one to read
745   // from the end 745   // from the end
746   segments_encoded_view::iterator begin_; 746   segments_encoded_view::iterator begin_;
747   segments_encoded_view::iterator it_; 747   segments_encoded_view::iterator it_;
748   748  
749   // What is left of the segment being produced 749   // What is left of the segment being produced
750   core::string_view seg_; 750   core::string_view seg_;
751   751  
752   // Number of ".." read by advance() that have 752   // Number of ".." read by advance() that have
753   // not removed a segment yet. While it is not 753   // not removed a segment yet. While it is not
754   // zero, each segment read is removed instead 754   // zero, each segment read is removed instead
755   // of produced: in "a/b/../..", both "b" and 755   // of produced: in "a/b/../..", both "b" and
756   // "a" are removed. 756   // "a" are removed.
757   std::size_t pending_dotdots_ = 0; 757   std::size_t pending_dotdots_ = 0;
758   758  
759   // The path starts with "/". next() produces 759   // The path starts with "/". next() produces
760   // that "/" as its very last char, after all 760   // that "/" as its very last char, after all
761   // segments: "/a" produces 'a', then '/'. 761   // segments: "/a" produces 'a', then '/'.
762   bool absolute_ = false; 762   bool absolute_ = false;
763   763  
764   // Number of segments advance() returned so 764   // Number of segments advance() returned so
765   // far. next() puts no '/' after the first 765   // far. next() puts no '/' after the first
766   // one, and the "./" prefix needs at least 766   // one, and the "./" prefix needs at least
767   // two: "a/..//b" produces "b" and "" before 767   // two: "a/..//b" produces "b" and "" before
768   // the ".". 768   // the ".".
769   std::size_t n_produced_ = 0; 769   std::size_t n_produced_ = 0;
770   770  
771   // The last segment produced as a 771   // The last segment produced as a
772   // surviving_segment was empty. They come 772   // surviving_segment was empty. They come
773   // last first, so this is the first segment 773   // last first, so this is the first segment
774   // of the normalized path, and an empty one 774   // of the normalized path, and an empty one
775   // needs the "./" prefix: in "a/..//b" it is 775   // needs the "./" prefix: in "a/..//b" it is
776   // the "" before "b". 776   // the "" before "b".
777   bool front_surviving_empty_ = false; 777   bool front_surviving_empty_ = false;
778   778  
779   // advance() produced a leftover "..". The 779   // advance() produced a leftover "..". The
780   // path then starts with "..", so it never 780   // path then starts with "..", so it never
781   // needs the "./" prefix: "a/../..//b" 781   // needs the "./" prefix: "a/../..//b"
782   // normalizes to "..//b". 782   // normalizes to "..//b".
783   bool produced_dotdot_ = false; 783   bool produced_dotdot_ = false;
784   784  
785   // Nothing is left to produce, so next() 785   // Nothing is left to produce, so next()
786   // returns false. 786   // returns false.
787   bool done_ = false; 787   bool done_ = false;
788   788  
789   /* A char of a segment after decoding 789   /* A char of a segment after decoding
790   790  
791   The decoded value, and whether it was an 791   The decoded value, and whether it was an
792   encoded slash ("%2F"). An encoded slash 792   encoded slash ("%2F"). An encoded slash
793   decodes to '/' but is not a separator, so 793   decodes to '/' but is not a separator, so
794   callers need to tell it apart. 794   callers need to tell it apart.
795   795  
796   @code 796   @code
797   "b" -> { 'b', false } 797   "b" -> { 'b', false }
798   "%41" -> { 'A', false } 798   "%41" -> { 'A', false }
799   "%2F" -> { '/', true } 799   "%2F" -> { '/', true }
800   @endcode 800   @endcode
801   */ 801   */
802   struct decoded_char 802   struct decoded_char
803   { 803   {
804   char value; 804   char value;
805   bool encoded_slash; 805   bool encoded_slash;
806   }; 806   };
807   807  
808   /* Remove the decoded last char of a segment 808   /* Remove the decoded last char of a segment
809   809  
810   Returns the decoded last char and removes 810   Returns the decoded last char and removes
811   it from the encoded segment. 811   it from the encoded segment.
812   812  
813   @code 813   @code
814   "ab" -> { 'b', false }, leaves "a" 814   "ab" -> { 'b', false }, leaves "a"
815   "a%41" -> { 'A', false }, leaves "a" 815   "a%41" -> { 'A', false }, leaves "a"
816   "a%2F" -> { '/', true }, leaves "a" 816   "a%2F" -> { '/', true }, leaves "a"
817   @endcode 817   @endcode
818   */ 818   */
819   static 819   static
820   decoded_char 820   decoded_char
HITCBC 821   4876 pop_last_decoded_char( 821   5020 pop_last_decoded_char(
822   core::string_view& s) noexcept 822   core::string_view& s) noexcept
823   { 823   {
HITCBC 824   4876 BOOST_ASSERT(!s.empty()); 824   5020 BOOST_ASSERT(!s.empty());
HITCBC 825   7060 if (s.size() >= 3 && 825   7316 if (s.size() >= 3 &&
HITCBC 826   2184 s[s.size() - 3] == '%') 826   2296 s[s.size() - 3] == '%')
827   { 827   {
HITCBC 828   182 char c = 0; 828   214 char c = 0;
HITCBC 829   364 detail::decode_unsafe( 829   428 detail::decode_unsafe(
HITCBC 830   364 &c, &c + 1, s.substr(s.size() - 3)); 830   428 &c, &c + 1, s.substr(s.size() - 3));
HITCBC 831   182 s.remove_suffix(3); 831   214 s.remove_suffix(3);
HITCBC 832   182 return { c, c == '/' }; 832   214 return { c, c == '/' };
833   } 833   }
HITCBC 834   4694 char c = s.back(); 834   4806 char c = s.back();
HITCBC 835   4694 s.remove_suffix(1); 835   4806 s.remove_suffix(1);
HITCBC 836   4694 return { c, false }; 836   4806 return { c, false };
837   } 837   }
838   838  
839   /* Whether a segment is "." after decoding 839   /* Whether a segment is "." after decoding
840   840  
841   @code 841   @code
842   is_encoded_dot(".") -> true 842   is_encoded_dot(".") -> true
843   is_encoded_dot("%2E") -> true 843   is_encoded_dot("%2E") -> true
844   is_encoded_dot("..") -> false 844   is_encoded_dot("..") -> false
845   @endcode 845   @endcode
846   */ 846   */
847   static 847   static
848   bool 848   bool
HITCBC 849   3760 is_encoded_dot(pct_string_view s) noexcept 849   3808 is_encoded_dot(pct_string_view s) noexcept
850   { 850   {
HITCBC 851   3760 return *s == "."; 851   3808 return *s == ".";
852   } 852   }
853   853  
854   /* Whether a segment is ".." after decoding 854   /* Whether a segment is ".." after decoding
855   855  
856   @code 856   @code
857   is_encoded_dotdot("..") -> true 857   is_encoded_dotdot("..") -> true
858   is_encoded_dotdot(".%2E") -> true 858   is_encoded_dotdot(".%2E") -> true
859   is_encoded_dotdot("...") -> false 859   is_encoded_dotdot("...") -> false
860   @endcode 860   @endcode
861   */ 861   */
862   static 862   static
863   bool 863   bool
HITCBC 864   4468 is_encoded_dotdot(pct_string_view s) noexcept 864   4516 is_encoded_dotdot(pct_string_view s) noexcept
865   { 865   {
HITCBC 866   4468 return *s == ".."; 866   4516 return *s == "..";
867   } 867   }
868   868  
869   /* Whether the normalized path ends with "/" 869   /* Whether the normalized path ends with "/"
870   870  
871   It does when the last segment is "." or a 871   It does when the last segment is "." or a
872   ".." that removes a segment. For a "..", 872   ".." that removes a segment. For a "..",
873   this reads ahead, without moving it_, until 873   this reads ahead, without moving it_, until
874   that ".." removes a segment or the front is 874   that ".." removes a segment or the front is
875   reached: 875   reached:
876   876  
877   @code 877   @code
878   path normalized result 878   path normalized result
879   /a/b/. /a/b/ true: the last segment is "." 879   /a/b/. /a/b/ true: the last segment is "."
880   /a/b/.. /a/ true: reads "b", which the 880   /a/b/.. /a/ true: reads "b", which the
881   ".." removes 881   ".." removes
882   /x/../.. /.. false: reads every segment, and 882   /x/../.. /.. false: reads every segment, and
883   the last ".." removes 883   the last ".." removes
884   nothing 884   nothing
885   /a/b /a/b false: the last segment is 885   /a/b /a/b false: the last segment is
886   neither "." nor ".." 886   neither "." nor ".."
887   @endcode 887   @endcode
888   */ 888   */
889   bool 889   bool
HITCBC 890   1736 ends_with_slash() const noexcept 890   1796 ends_with_slash() const noexcept
891   { 891   {
HITCBC 892   1736 if (it_ == begin_) 892   1796 if (it_ == begin_)
893   { 893   {
HITCBC 894   375 return false; 894   411 return false;
895   } 895   }
HITCBC 896   1361 auto it = it_; 896   1385 auto it = it_;
HITCBC 897   1361 --it; 897   1385 --it;
HITCBC 898   1361 if (is_encoded_dot(*it)) 898   1385 if (is_encoded_dot(*it))
899   { 899   {
HITCBC 900   46 return true; 900   46 return true;
901   } 901   }
HITCBC 902   1315 if (!is_encoded_dotdot(*it)) 902   1339 if (!is_encoded_dotdot(*it))
903   { 903   {
HITCBC 904   1126 return false; 904   1150 return false;
905   } 905   }
HITCBC 906   189 std::size_t pending = 1; 906   189 std::size_t pending = 1;
HITCBC 907   232 while (it != begin_) 907   232 while (it != begin_)
908   { 908   {
HITCBC 909   130 --it; 909   130 --it;
HITCBC 910   130 pct_string_view seg = *it; 910   130 pct_string_view seg = *it;
HITCBC 911   130 if (is_encoded_dotdot(seg)) 911   130 if (is_encoded_dotdot(seg))
912   { 912   {
HITCBC 913   22 ++pending; 913   22 ++pending;
914   } 914   }
HITCBC 915   108 else if ( 915   108 else if (
HITCBC 916   108 !is_encoded_dot(seg) && 916   108 !is_encoded_dot(seg) &&
917   --pending == 0) 917   --pending == 0)
918   { 918   {
HITCBC 919   87 return true; 919   87 return true;
920   } 920   }
921   } 921   }
HITCBC 922   102 return false; 922   102 return false;
923   } 923   }
924   924  
925   /* Move to the next segment of the output 925   /* Move to the next segment of the output
926   926  
927   Sets seg_ to the next segment of the 927   Sets seg_ to the next segment of the
928   normalized path, from the last to the 928   normalized path, from the last to the
929   first, and returns false when there are 929   first, and returns false when there are
930   no more. The segments come in the order 930   no more. The segments come in the order
931   of the types declared in segment_type: 931   of the types declared in segment_type:
932   932  
933   @li trailing_empty_segment: the empty 933   @li trailing_empty_segment: the empty
934   segment of a trailing "/" 934   segment of a trailing "/"
935   @li surviving_segment: the segments that 935   @li surviving_segment: the segments that
936   survive, last first 936   survive, last first
937   @li dotdot_prefix: the ".." left over at 937   @li dotdot_prefix: the ".." left over at
938   the front 938   the front
939   @li dot_prefix: the "." of the "./" prefix 939   @li dot_prefix: the "." of the "./" prefix
940   940  
941   @code 941   @code
942   path: ../a/b/.. 942   path: ../a/b/..
943   normalized: ../a/ 943   normalized: ../a/
944   advance(): "", "a", "..", false 944   advance(): "", "a", "..", false
945   @endcode 945   @endcode
946   */ 946   */
947   bool 947   bool
HITCBC 948   3814 advance() noexcept 948   3898 advance() noexcept
949   { 949   {
950   // next() only asks for another segment 950   // next() only asks for another segment
951   // when the previous one was fully 951   // when the previous one was fully
952   // produced and the output is not over 952   // produced and the output is not over
HITCBC 953   3814 BOOST_ASSERT(seg_.empty()); 953   3898 BOOST_ASSERT(seg_.empty());
HITCBC 954   3814 BOOST_ASSERT(!done_); 954   3898 BOOST_ASSERT(!done_);
HITCBC 955   3814 if (cur_segment_type_ == segment_type::trailing_empty_segment) 955   3898 if (cur_segment_type_ == segment_type::trailing_empty_segment)
956   { 956   {
957   // no segment was read yet 957   // no segment was read yet
HITCBC 958   1736 BOOST_ASSERT(pending_dotdots_ == 0); 958   1796 BOOST_ASSERT(pending_dotdots_ == 0);
HITCBC 959   1736 BOOST_ASSERT(n_produced_ == 0); 959   1796 BOOST_ASSERT(n_produced_ == 0);
HITCBC 960   1736 cur_segment_type_ = segment_type::surviving_segment; 960   1796 cur_segment_type_ = segment_type::surviving_segment;
HITCBC 961   1736 if (ends_with_slash()) 961   1796 if (ends_with_slash())
962   { 962   {
HITCBC 963   133 ++n_produced_; 963   133 ++n_produced_;
HITCBC 964   133 seg_ = {}; 964   133 seg_ = {};
HITCBC 965   133 return true; 965   133 return true;
966   } 966   }
967   } 967   }
HITCBC 968   3681 if (cur_segment_type_ == segment_type::surviving_segment) 968   3765 if (cur_segment_type_ == segment_type::surviving_segment)
969   { 969   {
HITCBC 970   4601 while (it_ != begin_) 970   4685 while (it_ != begin_)
971   { 971   {
HITCBC 972   3023 --it_; 972   3047 --it_;
HITCBC 973   3023 pct_string_view s = *it_; 973   3047 pct_string_view s = *it_;
HITCBC 974   3023 if (is_encoded_dotdot(s)) 974   3047 if (is_encoded_dotdot(s))
975   { 975   {
HITCBC 976   732 ++pending_dotdots_; 976   732 ++pending_dotdots_;
977   } 977   }
HITCBC 978   2291 else if (is_encoded_dot(s)) 978   2315 else if (is_encoded_dot(s))
979   { 979   {
HITCBC 980   145 continue; 980   145 continue;
981   } 981   }
HITCBC 982   2146 else if (pending_dotdots_) 982   2170 else if (pending_dotdots_)
983   { 983   {
HITCBC 984   392 --pending_dotdots_; 984   392 --pending_dotdots_;
985   } 985   }
986   else 986   else
987   { 987   {
HITCBC 988   1754 ++n_produced_; 988   1778 ++n_produced_;
HITCBC 989   1754 front_surviving_empty_ = s.empty(); 989   1778 front_surviving_empty_ = s.empty();
HITCBC 990   1754 seg_ = s; 990   1778 seg_ = s;
HITCBC 991   1754 return true; 991   1778 return true;
992   } 992   }
993   } 993   }
HITCBC 994   1578 cur_segment_type_ = segment_type::dotdot_prefix; 994   1638 cur_segment_type_ = segment_type::dotdot_prefix;
995   } 995   }
HITCBC 996   1927 if (cur_segment_type_ == segment_type::dotdot_prefix) 996   1987 if (cur_segment_type_ == segment_type::dotdot_prefix)
997   { 997   {
998   // every regular segment was read 998   // every regular segment was read
HITCBC 999   1860 BOOST_ASSERT(it_ == begin_); 999   1920 BOOST_ASSERT(it_ == begin_);
HITCBC 1000   1860 if (pending_dotdots_) 1000   1920 if (pending_dotdots_)
1001   { 1001   {
HITCBC 1002   340 --pending_dotdots_; 1002   340 --pending_dotdots_;
HITCBC 1003   340 ++n_produced_; 1003   340 ++n_produced_;
HITCBC 1004   340 produced_dotdot_ = true; 1004   340 produced_dotdot_ = true;
HITCBC 1005   340 seg_ = ".."; 1005   340 seg_ = "..";
HITCBC 1006   340 return true; 1006   340 return true;
1007   } 1007   }
HITCBC 1008   1520 cur_segment_type_ = segment_type::dot_prefix; 1008   1580 cur_segment_type_ = segment_type::dot_prefix;
1009   } 1009   }
HITCBC 1010   1587 if (cur_segment_type_ == segment_type::dot_prefix) 1010   1647 if (cur_segment_type_ == segment_type::dot_prefix)
1011   { 1011   {
1012   // every segment was read and every 1012   // every segment was read and every
1013   // leftover ".." was produced 1013   // leftover ".." was produced
HITCBC 1014   1520 BOOST_ASSERT(it_ == begin_); 1014   1580 BOOST_ASSERT(it_ == begin_);
HITCBC 1015   1520 BOOST_ASSERT(pending_dotdots_ == 0); 1015   1580 BOOST_ASSERT(pending_dotdots_ == 0);
HITCBC 1016   1520 cur_segment_type_ = segment_type::end; 1016   1580 cur_segment_type_ = segment_type::end;
1017   // A relative path whose first segment 1017   // A relative path whose first segment
1018   // is empty, with more after it, would 1018   // is empty, with more after it, would
1019   // start with "//" and read as an 1019   // start with "//" and read as an
1020   // authority: "a/..//b" produced "b" 1020   // authority: "a/..//b" produced "b"
1021   // and "", so it gets a ".". 1021   // and "", so it gets a ".".
HITCBC 1022   1520 bool const needs_dot_prefix = 1022   1580 bool const needs_dot_prefix =
HITCBC 1023   2260 !absolute_ && 1023   2320 !absolute_ &&
HITCBC 1024   740 !produced_dotdot_ && 1024   740 !produced_dotdot_ &&
HITCBC 1025   2396 front_surviving_empty_ && 1025   2456 front_surviving_empty_ &&
HITCBC 1026   136 n_produced_ >= 2; 1026   136 n_produced_ >= 2;
HITCBC 1027   1520 if (needs_dot_prefix) 1027   1580 if (needs_dot_prefix)
1028   { 1028   {
HITCBC 1029   126 ++n_produced_; 1029   126 ++n_produced_;
HITCBC 1030   126 seg_ = "."; 1030   126 seg_ = ".";
HITCBC 1031   126 return true; 1031   126 return true;
1032   } 1032   }
1033   } 1033   }
1034   // every path to here went through the 1034   // every path to here went through the
1035   // transition to end 1035   // transition to end
HITCBC 1036   1461 BOOST_ASSERT( 1036   1521 BOOST_ASSERT(
1037   cur_segment_type_ == segment_type::end); 1037   cur_segment_type_ == segment_type::end);
HITCBC 1038   1461 return false; 1038   1521 return false;
1039   } 1039   }
1040   1040  
1041   public: 1041   public:
1042   /* Prepare to read a path from its end 1042   /* Prepare to read a path from its end
1043   1043  
1044   Nothing is read until the first call to 1044   Nothing is read until the first call to
1045   next(). 1045   next().
1046   */ 1046   */
1047   explicit 1047   explicit
HITCBC 1048   1896 normalized_path_reverse( 1048   1956 normalized_path_reverse(
1049   segments_encoded_view segs) noexcept 1049   segments_encoded_view segs) noexcept
HITCBC 1050   1896 : begin_(segs.begin()) 1050   1956 : begin_(segs.begin())
HITCBC 1051   1896 , it_(segs.end()) 1051   1956 , it_(segs.end())
HITCBC 1052   3792 , absolute_(segs.is_absolute()) 1052   3912 , absolute_(segs.is_absolute())
1053   { 1053   {
HITCBC 1054   1896 } 1054   1956 }
1055   1055  
1056   /* Produce the next char from the end 1056   /* Produce the next char from the end
1057   1057  
1058   Returns false when all chars were 1058   Returns false when all chars were
1059   produced. The flag is set when the char 1059   produced. The flag is set when the char
1060   is an encoded slash. 1060   is an encoded slash.
1061   1061  
1062   @code 1062   @code
1063   path: x/./y/.. 1063   path: x/./y/..
1064   next(): '/', 'x', false ("x/") 1064   next(): '/', 'x', false ("x/")
1065   @endcode 1065   @endcode
1066   */ 1066   */
1067   bool 1067   bool
HITCBC 1068   7790 next( 1068   8034 next(
1069   char& c, 1069   char& c,
1070   bool& encoded_slash) noexcept 1070   bool& encoded_slash) noexcept
1071   { 1071   {
HITCBC 1072   7790 encoded_slash = false; 1072   8034 encoded_slash = false;
1073   for (;;) 1073   for (;;)
1074   { 1074   {
HITCBC 1075   9151 if (!seg_.empty()) 1075   9419 if (!seg_.empty())
1076   { 1076   {
1077   decoded_char const d = 1077   decoded_char const d =
HITCBC 1078   4876 pop_last_decoded_char(seg_); 1078   5020 pop_last_decoded_char(seg_);
HITCBC 1079   4876 c = d.value; 1079   5020 c = d.value;
HITCBC 1080   4876 encoded_slash = d.encoded_slash; 1080   5020 encoded_slash = d.encoded_slash;
HITCBC 1081   4876 return true; 1081   5020 return true;
1082   } 1082   }
HITCBC 1083   4275 if (done_) 1083   4399 if (done_)
1084   { 1084   {
HITCBC 1085   461 return false; 1085   501 return false;
1086   } 1086   }
HITCBC 1087   3814 if (advance()) 1087   3898 if (advance())
1088   { 1088   {
HITCBC 1089   2353 if (n_produced_ == 1) 1089   2377 if (n_produced_ == 1)
1090   { 1090   {
1091   // nothing follows the last segment 1091   // nothing follows the last segment
HITCBC 1092   1361 continue; 1092   1385 continue;
1093   } 1093   }
HITCBC 1094   992 c = '/'; 1094   992 c = '/';
HITCBC 1095   992 return true; 1095   992 return true;
1096   } 1096   }
HITCBC 1097   1461 done_ = true; 1097   1521 done_ = true;
HITCBC 1098   1461 if (absolute_) 1098   1521 if (absolute_)
1099   { 1099   {
HITCBC 1100   780 c = '/'; 1100   840 c = '/';
HITCBC 1101   780 return true; 1101   840 return true;
1102   } 1102   }
HITCBC 1103   681 return false; 1103   681 return false;
HITCBC 1104   1361 } 1104   1385 }
1105   } 1105   }
1106   1106  
1107   /* Produce the next char from the end 1107   /* Produce the next char from the end
1108   1108  
1109   Like the other overload, for callers that 1109   Like the other overload, for callers that
1110   only count or skip chars and do not need 1110   only count or skip chars and do not need
1111   to know whether it was an encoded slash. 1111   to know whether it was an encoded slash.
1112   1112  
1113   @code 1113   @code
1114   path: x/./y/.. 1114   path: x/./y/..
1115   next(): '/', 'x', false ("x/") 1115   next(): '/', 'x', false ("x/")
1116   @endcode 1116   @endcode
1117   */ 1117   */
1118   bool 1118   bool
HITCBC 1119   3702 next(char& c) noexcept 1119   3790 next(char& c) noexcept
1120   { 1120   {
HITCBC 1121   3702 bool encoded_slash = false; 1121   3790 bool encoded_slash = false;
HITCBC 1122   3702 return next(c, encoded_slash); 1122   3790 return next(c, encoded_slash);
1123   } 1123   }
1124   1124  
1125   /* Skip the next n chars from the end 1125   /* Skip the next n chars from the end
1126   1126  
1127   The path must have at least n chars left. 1127   The path must have at least n chars left.
1128   1128  
1129   @code 1129   @code
1130   path: /a/b/ 1130   path: /a/b/
1131   skip(2): skips '/' and 'b' 1131   skip(2): skips '/' and 'b'
1132   next(): '/', 'a', '/', false 1132   next(): '/', 'a', '/', false
1133   @endcode 1133   @endcode
1134   */ 1134   */
1135   void 1135   void
HITCBC 1136   377 skip(std::size_t n) noexcept 1136   387 skip(std::size_t n) noexcept
1137   { 1137   {
HITCBC 1138   377 char c = 0; 1138   387 char c = 0;
HITCBC 1139   687 for (; n != 0; --n) 1139   697 for (; n != 0; --n)
1140   { 1140   {
HITCBC 1141   310 bool const ok = next(c); 1141   310 bool const ok = next(c);
HITCBC 1142   310 BOOST_ASSERT(ok); 1142   310 BOOST_ASSERT(ok);
1143   ignore_unused(ok); 1143   ignore_unused(ok);
1144   } 1144   }
HITCBC 1145   377 } 1145   387 }
1146   }; 1146   };
1147   1147  
1148   /* The size of a path after normalization 1148   /* The size of a path after normalization
1149   1149  
1150   Counts the chars normalized_path_reverse 1150   Counts the chars normalized_path_reverse
1151   produces, reading the path once. 1151   produces, reading the path once.
1152   1152  
1153   @code 1153   @code
1154   "/a/b/.." -> 3 ("/a/") 1154   "/a/b/.." -> 3 ("/a/")
1155   "a/../.." -> 2 ("..") 1155   "a/../.." -> 2 ("..")
1156   "a/.." -> 0 ("") 1156   "a/.." -> 0 ("")
1157   @endcode 1157   @endcode
1158   */ 1158   */
1159   std::size_t 1159   std::size_t
HITCBC 1160   754 normalized_path_size( 1160   774 normalized_path_size(
1161   segments_encoded_view segs) noexcept 1161   segments_encoded_view segs) noexcept
1162   { 1162   {
HITCBC 1163   754 normalized_path_reverse r(segs); 1163   774 normalized_path_reverse r(segs);
HITCBC 1164   754 std::size_t n = 0; 1164   774 std::size_t n = 0;
HITCBC 1165   754 char c = 0; 1165   774 char c = 0;
HITCBC 1166   3392 while (r.next(c)) 1166   3480 while (r.next(c))
1167   { 1167   {
HITCBC 1168   2638 ++n; 1168   2706 ++n;
1169   } 1169   }
HITCBC 1170   754 return n; 1170   774 return n;
1171   } 1171   }
1172   1172  
1173   } // (anon) 1173   } // (anon)
1174   1174  
1175   /* Hash a path as if it were normalized 1175   /* Hash a path as if it were normalized
1176   1176  
1177   Hashes the chars of the path that 1177   Hashes the chars of the path that
1178   url_base::normalize() would produce, so 1178   url_base::normalize() would produce, so
1179   paths that segments_compare() finds equal 1179   paths that segments_compare() finds equal
1180   always hash equal. The chars are hashed 1180   always hash equal. The chars are hashed
1181   from the end of the path, in the order 1181   from the end of the path, in the order
1182   normalized_path_reverse produces them. An 1182   normalized_path_reverse produces them. An
1183   encoded slash is written "%2F" in 1183   encoded slash is written "%2F" in
1184   uppercase, as normalize() writes it, and 1184   uppercase, as normalize() writes it, and
1185   those three chars are hashed from the end 1185   those three chars are hashed from the end
1186   too, like any others: 1186   too, like any others:
1187   1187  
1188   @code 1188   @code
1189   path normalized hashed 1189   path normalized hashed
1190   /. / '/' 1190   /. / '/'
1191   a/../.. .. '.' '.' 1191   a/../.. .. '.' '.'
1192   a%2fb a%2Fb 'b' 'F' '2' '%' 'a' 1192   a%2fb a%2Fb 'b' 'F' '2' '%' 'a'
1193   @endcode 1193   @endcode
1194   */ 1194   */
1195   void 1195   void
HITCBC 1196   388 normalized_path_digest( 1196   408 normalized_path_digest(
1197   segments_encoded_view segs, 1197   segments_encoded_view segs,
1198   fnv_1a& hasher) noexcept 1198   fnv_1a& hasher) noexcept
1199   { 1199   {
HITCBC 1200   388 normalized_path_reverse r(segs); 1200   408 normalized_path_reverse r(segs);
HITCBC 1201   388 char c = 0; 1201   408 char c = 0;
HITCBC 1202   388 bool encoded_slash = false; 1202   408 bool encoded_slash = false;
HITCBC 1203   1760 while (r.next(c, encoded_slash)) 1203   1848 while (r.next(c, encoded_slash))
1204   { 1204   {
HITCBC 1205   1372 if (!encoded_slash) 1205   1440 if (!encoded_slash)
1206   { 1206   {
HITCBC 1207   1360 hasher.put(c); 1207   1428 hasher.put(c);
HITCBC 1208   1360 continue; 1208   1428 continue;
1209   } 1209   }
1210   // an encoded slash stays encoded in 1210   // an encoded slash stays encoded in
1211   // the normalized path, and its chars 1211   // the normalized path, and its chars
1212   // are hashed from the end as well 1212   // are hashed from the end as well
HITCBC 1213   12 hasher.put('F'); 1213   12 hasher.put('F');
HITCBC 1214   12 hasher.put('2'); 1214   12 hasher.put('2');
HITCBC 1215   12 hasher.put('%'); 1215   12 hasher.put('%');
1216   } 1216   }
HITCBC 1217   388 } 1217   408 }
1218   1218  
1219   /* Compare two paths as if they were normalized 1219   /* Compare two paths as if they were normalized
1220   1220  
1221   The result is the order of the paths that 1221   The result is the order of the paths that
1222   url_base::normalize() would produce, without 1222   url_base::normalize() would produce, without
1223   producing them, so that the function does not 1223   producing them, so that the function does not
1224   allocate. Paths compare equal exactly when 1224   allocate. Paths compare equal exactly when
1225   they normalize to the same path: 1225   they normalize to the same path:
1226   1226  
1227   @code 1227   @code
1228   compare("/a/b/..", "/a/") -> 0 ("/a/" and "/a/") 1228   compare("/a/b/..", "/a/") -> 0 ("/a/" and "/a/")
1229   compare("a/../..", "..") -> 0 (".." and "..") 1229   compare("a/../..", "..") -> 0 (".." and "..")
1230   compare("/a/b/..", "/a") -> +1 ("/a/" and "/a") 1230   compare("/a/b/..", "/a") -> +1 ("/a/" and "/a")
1231   compare("a%2Fb", "a/b") -> -1 ("%2F" is not "/") 1231   compare("a%2Fb", "a/b") -> -1 ("%2F" is not "/")
1232   @endcode 1232   @endcode
1233   1233  
1234   Both paths are read from the end with 1234   Both paths are read from the end with
1235   normalized_path_reverse, which explains why 1235   normalized_path_reverse, which explains why
1236   the chars come in that order. The paths are 1236   the chars come in that order. The paths are
1237   aligned at their first common char: the extra chars 1237   aligned at their first common char: the extra chars
1238   at the end of the longer path are skipped, 1238   at the end of the longer path are skipped,
1239   then the remaining chars are compared in 1239   then the remaining chars are compared in
1240   pairs. Each difference overwrites the 1240   pairs. Each difference overwrites the
1241   previous one, so the result is the last 1241   previous one, so the result is the last
1242   difference read, which is the first one from 1242   difference read, which is the first one from
1243   the front. If there is none, the shorter path 1243   the front. If there is none, the shorter path
1244   comes first: 1244   comes first:
1245   1245  
1246   @code 1246   @code
1247   lhs: /a/c/ (size 5) 1247   lhs: /a/c/ (size 5)
1248   rhs: /b/c (size 4) 1248   rhs: /b/c (size 4)
1249   skip: '/' of lhs 1249   skip: '/' of lhs
1250   pairs: ('c','c') ('/','/') ('a','b') ('/','/') 1250   pairs: ('c','c') ('/','/') ('a','b') ('/','/')
1251   result: -1, from ('a','b') 1251   result: -1, from ('a','b')
1252   @endcode 1252   @endcode
1253   1253  
1254   An encoded slash and a '/' are the same char 1254   An encoded slash and a '/' are the same char
1255   with a different meaning. The encoded slash 1255   with a different meaning. The encoded slash
1256   sorts first, as the '%' it is written with 1256   sorts first, as the '%' it is written with
1257   would. 1257   would.
1258   */ 1258   */
1259   int 1259   int
HITCBC 1260   377 segments_compare( 1260   387 segments_compare(
1261   segments_encoded_view seg0, 1261   segments_encoded_view seg0,
1262   segments_encoded_view seg1) noexcept 1262   segments_encoded_view seg1) noexcept
1263   { 1263   {
HITCBC 1264   377 normalized_path_reverse r0(seg0); 1264   387 normalized_path_reverse r0(seg0);
HITCBC 1265   377 normalized_path_reverse r1(seg1); 1265   387 normalized_path_reverse r1(seg1);
HITCBC 1266   377 std::size_t const n0 = normalized_path_size(seg0); 1266   387 std::size_t const n0 = normalized_path_size(seg0);
HITCBC 1267   377 std::size_t const n1 = normalized_path_size(seg1); 1267   387 std::size_t const n1 = normalized_path_size(seg1);
HITCBC 1268   377 std::size_t const n = (std::min)(n0, n1); 1268   387 std::size_t const n = (std::min)(n0, n1);
1269   1269  
1270   // skip the chars at the end of the longer 1270   // skip the chars at the end of the longer
1271   // path that have nothing to be compared 1271   // path that have nothing to be compared
1272   // with 1272   // with
HITCBC 1273   377 normalized_path_reverse& longer = 1273   387 normalized_path_reverse& longer =
HITCBC 1274   377 n0 > n1 ? r0 : r1; 1274   387 n0 > n1 ? r0 : r1;
HITCBC 1275   377 longer.skip((std::max)(n0, n1) - n); 1275   387 longer.skip((std::max)(n0, n1) - n);
1276   1276  
1277   1277  
1278   // compare the common prefix from the end, 1278   // compare the common prefix from the end,
1279   // keeping the first difference 1279   // keeping the first difference
HITCBC 1280   377 int cmp = 0; 1280   387 int cmp = 0;
HITCBC 1281   377 char c0 = 0; 1281   387 char c0 = 0;
HITCBC 1282   377 char c1 = 0; 1282   387 char c1 = 0;
HITCBC 1283   377 bool e0 = false; 1283   387 bool e0 = false;
HITCBC 1284   377 bool e1 = false; 1284   387 bool e1 = false;
HITCBC 1285   1541 for (std::size_t i = 0; i < n; ++i) 1285   1585 for (std::size_t i = 0; i < n; ++i)
1286   { 1286   {
HITCBC 1287   1164 bool const ok0 = r0.next(c0, e0); 1287   1198 bool const ok0 = r0.next(c0, e0);
HITCBC 1288   1164 bool const ok1 = r1.next(c1, e1); 1288   1198 bool const ok1 = r1.next(c1, e1);
HITCBC 1289   1164 BOOST_ASSERT(ok0 && ok1); 1289   1198 BOOST_ASSERT(ok0 && ok1);
1290   ignore_unused(ok0, ok1); 1290   ignore_unused(ok0, ok1);
HITCBC 1291   1164 if (c0 < c1) 1291   1198 if (c0 < c1)
1292   { 1292   {
HITCBC 1293   87 cmp = -1; 1293   87 cmp = -1;
1294   } 1294   }
HITCBC 1295   1077 else if (c1 < c0) 1295   1111 else if (c1 < c0)
1296   { 1296   {
HITCBC 1297   72 cmp = +1; 1297   72 cmp = +1;
1298   } 1298   }
HITCBC 1299   1005 else if (e0 != e1) 1299   1039 else if (e0 != e1)
1300   { 1300   {
1301   // same char, but only one of them 1301   // same char, but only one of them
1302   // is an encoded slash, which is 1302   // is an encoded slash, which is
1303   // not a path separator: it sorts 1303   // not a path separator: it sorts
1304   // like the '%' it keeps 1304   // like the '%' it keeps
HITCBC 1305   10 cmp = e0 ? -1 : +1; 1305   10 cmp = e0 ? -1 : +1;
1306   } 1306   }
1307   } 1307   }
HITCBC 1308   377 if (cmp != 0 || 1308   387 if (cmp != 0 ||
HITCBC 1309   272 n0 == n1) 1309   282 n0 == n1)
1310   { 1310   {
HITCBC 1311   356 return cmp; 1311   366 return cmp;
1312   } 1312   }
HITCBC 1313   21 if (n0 < n1) 1313   21 if (n0 < n1)
1314   { 1314   {
HITCBC 1315   11 return -1; 1315   11 return -1;
1316   } 1316   }
HITCBC 1317   10 return 1; 1317   10 return 1;
1318   } 1318   }
1319   1319  
1320   } // detail 1320   } // detail
1321   } // urls 1321   } // urls
1322   } // boost 1322   } // boost
1323   1323