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