Line | Branch | Exec | Source |
---|---|---|---|
1 | // | ||
2 | // Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com) | ||
3 | // | ||
4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
6 | // | ||
7 | // Official repository: https://github.com/boostorg/url | ||
8 | // | ||
9 | |||
10 | #ifndef BOOST_URL_DETAIL_ROUTER_IPP | ||
11 | #define BOOST_URL_DETAIL_ROUTER_IPP | ||
12 | |||
13 | #include "../router.hpp" | ||
14 | #include <boost/url/decode_view.hpp> | ||
15 | #include <boost/url/grammar/alnum_chars.hpp> | ||
16 | #include <boost/url/grammar/alpha_chars.hpp> | ||
17 | #include <boost/url/grammar/lut_chars.hpp> | ||
18 | #include <boost/url/grammar/token_rule.hpp> | ||
19 | #include <boost/url/grammar/variant_rule.hpp> | ||
20 | #include <boost/url/rfc/detail/path_rules.hpp> | ||
21 | #include <boost/url/detail/replacement_field_rule.hpp> | ||
22 | #include <vector> | ||
23 | |||
24 | namespace boost { | ||
25 | namespace urls { | ||
26 | namespace detail { | ||
27 | |||
28 | // A path segment template | ||
29 | class segment_template | ||
30 | { | ||
31 | enum class modifier : unsigned char | ||
32 | { | ||
33 | none, | ||
34 | // {id?} | ||
35 | optional, | ||
36 | // {id*} | ||
37 | star, | ||
38 | // {id+} | ||
39 | plus | ||
40 | }; | ||
41 | |||
42 | std::string str_; | ||
43 | bool is_literal_ = true; | ||
44 | modifier modifier_ = modifier::none; | ||
45 | |||
46 | friend struct segment_template_rule_t; | ||
47 | public: | ||
48 | 1271 | segment_template() = default; | |
49 | |||
50 | bool | ||
51 | match(pct_string_view seg) const; | ||
52 | |||
53 | core::string_view | ||
54 | 326 | string() const | |
55 | { | ||
56 | 326 | return str_; | |
57 | } | ||
58 | |||
59 | core::string_view | ||
60 | id() const; | ||
61 | |||
62 | bool | ||
63 | empty() const | ||
64 | { | ||
65 | return str_.empty(); | ||
66 | } | ||
67 | |||
68 | bool | ||
69 | 687 | is_literal() const | |
70 | { | ||
71 | 687 | return is_literal_; | |
72 | } | ||
73 | |||
74 | bool | ||
75 | 163 | has_modifier() const | |
76 | { | ||
77 |
1/2✓ Branch 1 taken 163 times.
✗ Branch 2 not taken.
|
326 | return !is_literal() && |
78 |
2/2✓ Branch 0 taken 62 times.
✓ Branch 1 taken 101 times.
|
326 | modifier_ != modifier::none; |
79 | } | ||
80 | |||
81 | bool | ||
82 | 101 | is_optional() const | |
83 | { | ||
84 | 101 | return modifier_ == modifier::optional; | |
85 | } | ||
86 | |||
87 | bool | ||
88 | 51 | is_star() const | |
89 | { | ||
90 | 51 | return modifier_ == modifier::star; | |
91 | } | ||
92 | |||
93 | bool | ||
94 | 41 | is_plus() const | |
95 | { | ||
96 | 41 | return modifier_ == modifier::plus; | |
97 | } | ||
98 | |||
99 | friend | ||
100 | 83 | bool operator==( | |
101 | segment_template const& a, | ||
102 | segment_template const& b) | ||
103 | { | ||
104 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 75 times.
|
83 | if (a.is_literal_ != b.is_literal_) |
105 | 8 | return false; | |
106 |
2/2✓ Branch 0 taken 73 times.
✓ Branch 1 taken 2 times.
|
75 | if (a.is_literal_) |
107 | 73 | return a.str_ == b.str_; | |
108 | 2 | return a.modifier_ == b.modifier_; | |
109 | } | ||
110 | |||
111 | // segments have precedence: | ||
112 | // - literal | ||
113 | // - unique | ||
114 | // - optional | ||
115 | // - plus | ||
116 | // - star | ||
117 | friend | ||
118 | 18 | bool operator<( | |
119 | segment_template const& a, | ||
120 | segment_template const& b) | ||
121 | { | ||
122 |
2/2✓ Branch 1 taken 15 times.
✓ Branch 2 taken 3 times.
|
18 | if (b.is_literal()) |
123 | 15 | return false; | |
124 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
|
3 | if (a.is_literal()) |
125 | 1 | return !b.is_literal(); | |
126 | 2 | return a.modifier_ < b.modifier_; | |
127 | } | ||
128 | }; | ||
129 | |||
130 | // A segment template is either a literal string | ||
131 | // or a replacement field (as in a format_string). | ||
132 | // Fields cannot contain format specs and might | ||
133 | // have one of the following modifiers: | ||
134 | // - ?: optional segment | ||
135 | // - *: zero or more segments | ||
136 | // - +: one or more segments | ||
137 | struct segment_template_rule_t | ||
138 | { | ||
139 | using value_type = segment_template; | ||
140 | |||
141 | system::result<value_type> | ||
142 | parse( | ||
143 | char const*& it, | ||
144 | char const* end | ||
145 | ) const noexcept; | ||
146 | }; | ||
147 | |||
148 | constexpr auto segment_template_rule = segment_template_rule_t{}; | ||
149 | |||
150 | constexpr auto path_template_rule = | ||
151 | grammar::tuple_rule( | ||
152 | grammar::squelch( | ||
153 | grammar::optional_rule( | ||
154 | grammar::delim_rule('/'))), | ||
155 | grammar::range_rule( | ||
156 | segment_template_rule, | ||
157 | grammar::tuple_rule( | ||
158 | grammar::squelch(grammar::delim_rule('/')), | ||
159 | segment_template_rule))); | ||
160 | |||
161 | bool | ||
162 | 311 | segment_template:: | |
163 | match(pct_string_view seg) const | ||
164 | { | ||
165 |
2/2✓ Branch 0 taken 151 times.
✓ Branch 1 taken 160 times.
|
311 | if (is_literal_) |
166 | 151 | return *seg == str_; | |
167 | |||
168 | // other nodes match any string | ||
169 | 160 | return true; | |
170 | } | ||
171 | |||
172 | core::string_view | ||
173 | 168 | segment_template:: | |
174 | id() const | ||
175 | { | ||
176 | // if (is_literal_) return {}; | ||
177 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 168 times.
|
168 | BOOST_ASSERT(!is_literal()); |
178 | 168 | core::string_view r = {str_}; | |
179 | 168 | r.remove_prefix(1); | |
180 | 168 | r.remove_suffix(1); | |
181 |
2/2✓ Branch 1 taken 118 times.
✓ Branch 2 taken 17 times.
|
303 | if (r.ends_with('?') || |
182 |
6/6✓ Branch 0 taken 135 times.
✓ Branch 1 taken 33 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 96 times.
✓ Branch 5 taken 72 times.
✓ Branch 6 taken 96 times.
|
303 | r.ends_with('+') || |
183 | 118 | r.ends_with('*')) | |
184 | 72 | r.remove_suffix(1); | |
185 | 168 | return r; | |
186 | } | ||
187 | |||
188 | auto | ||
189 | 654 | segment_template_rule_t:: | |
190 | parse( | ||
191 | char const*& it, | ||
192 | char const* end) const noexcept | ||
193 | -> system::result<value_type> | ||
194 | { | ||
195 | 1308 | segment_template t; | |
196 |
2/2✓ Branch 0 taken 646 times.
✓ Branch 1 taken 8 times.
|
654 | if (it != end && |
197 |
2/2✓ Branch 0 taken 323 times.
✓ Branch 1 taken 323 times.
|
646 | *it == '{') |
198 | { | ||
199 | // replacement field | ||
200 | 323 | auto it0 = it; | |
201 | 323 | ++it; | |
202 | auto send = | ||
203 | 323 | grammar::find_if( | |
204 | 323 | it, end, grammar::lut_chars('}')); | |
205 |
2/2✓ Branch 0 taken 322 times.
✓ Branch 1 taken 1 times.
|
323 | if (send != end) |
206 | { | ||
207 | 322 | core::string_view s(it, send); | |
208 | static constexpr auto modifiers_cs = | ||
209 | grammar::lut_chars("?*+"); | ||
210 | static constexpr auto id_rule = | ||
211 | grammar::tuple_rule( | ||
212 | grammar::optional_rule( | ||
213 | arg_id_rule), | ||
214 | grammar::optional_rule( | ||
215 | grammar::delim_rule(modifiers_cs))); | ||
216 |
3/4✓ Branch 1 taken 314 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 314 times.
✗ Branch 4 not taken.
|
636 | if (s.empty() || |
217 |
3/4✓ Branch 2 taken 314 times.
✓ Branch 3 taken 8 times.
✓ Branch 5 taken 322 times.
✗ Branch 6 not taken.
|
636 | grammar::parse(s, id_rule)) |
218 | { | ||
219 | 322 | it = send + 1; | |
220 | |||
221 | 322 | t.str_ = core::string_view(it0, send + 1); | |
222 | 322 | t.is_literal_ = false; | |
223 |
2/2✓ Branch 1 taken 48 times.
✓ Branch 2 taken 274 times.
|
322 | if (s.ends_with('?')) |
224 | 48 | t.modifier_ = | |
225 | segment_template::modifier::optional; | ||
226 |
2/2✓ Branch 1 taken 48 times.
✓ Branch 2 taken 226 times.
|
274 | else if (s.ends_with('*')) |
227 | 48 | t.modifier_ = | |
228 | segment_template::modifier::star; | ||
229 |
2/2✓ Branch 1 taken 58 times.
✓ Branch 2 taken 168 times.
|
226 | else if (s.ends_with('+')) |
230 | 58 | t.modifier_ = | |
231 | segment_template::modifier::plus; | ||
232 | 322 | return t; | |
233 | } | ||
234 | } | ||
235 | 1 | it = it0; | |
236 | } | ||
237 | // literal segment | ||
238 | auto rv = grammar::parse( | ||
239 | 332 | it, end, urls::detail::segment_rule); | |
240 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 332 times.
|
332 | BOOST_ASSERT(rv); |
241 | 332 | rv->decode({}, urls::string_token::assign_to(t.str_)); | |
242 | 332 | t.is_literal_ = true; | |
243 | 332 | return t; | |
244 | } | ||
245 | |||
246 | // a small vector for child nodes... | ||
247 | // we shouldn't expect many children per node, and | ||
248 | // we don't want to allocate for that. But we also | ||
249 | // cannot cap the max number of child nodes because | ||
250 | // especially the root nodes can potentially an | ||
251 | // exponentially higher number of child nodes. | ||
252 | class child_idx_vector | ||
253 | { | ||
254 | static constexpr std::size_t N = 5; | ||
255 | std::size_t static_child_idx_[N]{}; | ||
256 | std::size_t* child_idx{nullptr}; | ||
257 | std::size_t size_{0}; | ||
258 | std::size_t cap_{0}; | ||
259 | |||
260 | public: | ||
261 | 1176 | ~child_idx_vector() | |
262 | 1176 | { | |
263 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1174 times.
|
1176 | delete[] child_idx; |
264 | 1176 | } | |
265 | |||
266 | 393 | child_idx_vector() = default; | |
267 | |||
268 | 390 | child_idx_vector(child_idx_vector const& other) | |
269 | 390 | : size_{other.size_} | |
270 | 390 | , cap_{other.cap_} | |
271 | { | ||
272 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 389 times.
|
390 | if (other.child_idx) |
273 | { | ||
274 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | child_idx = new std::size_t[cap_]; |
275 | 1 | std::memcpy(child_idx, other.child_idx, size_ * sizeof(std::size_t)); | |
276 | 1 | return; | |
277 | } | ||
278 | 389 | std::memcpy(static_child_idx_, other.static_child_idx_, size_ * sizeof(std::size_t)); | |
279 | } | ||
280 | |||
281 | 393 | child_idx_vector(child_idx_vector&& other) | |
282 | 393 | : child_idx{other.child_idx} | |
283 | 393 | , size_{other.size_} | |
284 | 393 | , cap_{other.cap_} | |
285 | { | ||
286 | 393 | std::memcpy(static_child_idx_, other.static_child_idx_, N); | |
287 | 393 | other.child_idx = nullptr; | |
288 | 393 | } | |
289 | |||
290 | bool | ||
291 | 47 | empty() const | |
292 | { | ||
293 | 47 | return size_ == 0; | |
294 | } | ||
295 | |||
296 | std::size_t | ||
297 | 606 | size() const | |
298 | { | ||
299 | 606 | return size_; | |
300 | } | ||
301 | |||
302 | std::size_t* | ||
303 | 1304 | begin() | |
304 | { | ||
305 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 1271 times.
|
1304 | if (child_idx) |
306 | 33 | return child_idx; | |
307 | 1271 | return static_child_idx_; | |
308 | } | ||
309 | |||
310 | std::size_t* | ||
311 | 642 | end() | |
312 | { | ||
313 | 642 | return begin() + size_; | |
314 | } | ||
315 | |||
316 | std::size_t const* | ||
317 | 678 | begin() const | |
318 | { | ||
319 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 674 times.
|
678 | if (child_idx) |
320 | 4 | return child_idx; | |
321 | 674 | return static_child_idx_; | |
322 | } | ||
323 | |||
324 | std::size_t const* | ||
325 | 339 | end() const | |
326 | { | ||
327 | 339 | return begin() + size_; | |
328 | } | ||
329 | |||
330 | void | ||
331 | 5 | erase(std::size_t* it) | |
332 | { | ||
333 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | BOOST_ASSERT(it - begin() >= 0); |
334 | 5 | std::memmove(it - 1, it, end() - it); | |
335 | 5 | --size_; | |
336 | 5 | } | |
337 | |||
338 | void | ||
339 | 298 | push_back(std::size_t v) | |
340 | { | ||
341 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 297 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
298 | if (size_ == N && !child_idx) |
342 | { | ||
343 | 1 | child_idx = new std::size_t[N*2]; | |
344 | 1 | cap_ = N*2; | |
345 | 1 | std::memcpy(child_idx, static_child_idx_, N * sizeof(std::size_t)); | |
346 | } | ||
347 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 292 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 4 times.
|
297 | else if (child_idx && size_ == cap_) |
348 | { | ||
349 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | auto* tmp = new std::size_t[cap_*2]; |
350 | 1 | std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t)); | |
351 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | delete[] child_idx; |
352 | 1 | child_idx = tmp; | |
353 | 1 | cap_ = cap_*2; | |
354 | } | ||
355 | 298 | begin()[size_++] = v; | |
356 | 298 | } | |
357 | }; | ||
358 | |||
359 | // A node in the resource tree | ||
360 | // Each segment in the resource tree might be | ||
361 | // associated with | ||
362 | struct node | ||
363 | { | ||
364 | static constexpr std::size_t npos{std::size_t(-1)}; | ||
365 | |||
366 | // literal segment or replacement field | ||
367 | detail::segment_template seg{}; | ||
368 | |||
369 | // A pointer to the resource | ||
370 | router_base::any_resource const* resource{nullptr}; | ||
371 | |||
372 | // The complete match for the resource | ||
373 | std::string path_template; | ||
374 | |||
375 | // Index of the parent node in the | ||
376 | // implementation pool of nodes | ||
377 | std::size_t parent_idx{npos}; | ||
378 | |||
379 | // Index of child nodes in the pool | ||
380 | detail::child_idx_vector child_idx; | ||
381 | }; | ||
382 | |||
383 | class impl | ||
384 | { | ||
385 | // Pool of nodes in the resource tree | ||
386 | std::vector<node> nodes_; | ||
387 | |||
388 | public: | ||
389 | 95 | impl() | |
390 | 95 | { | |
391 | // root node with no resource | ||
392 |
1/2✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
|
95 | nodes_.push_back(node{}); |
393 | 95 | } | |
394 | |||
395 | 95 | ~impl() | |
396 | 95 | { | |
397 |
2/2✓ Branch 3 taken 388 times.
✓ Branch 4 taken 95 times.
|
483 | for (auto &r: nodes_) |
398 |
2/2✓ Branch 0 taken 111 times.
✓ Branch 1 taken 277 times.
|
388 | delete r.resource; |
399 | 95 | } | |
400 | |||
401 | // include a node for a resource | ||
402 | void | ||
403 | insert_impl( | ||
404 | core::string_view path, | ||
405 | router_base::any_resource const* v); | ||
406 | |||
407 | // match a node and return the element | ||
408 | router_base::any_resource const* | ||
409 | find_impl( | ||
410 | segments_encoded_view path, | ||
411 | core::string_view*& matches, | ||
412 | core::string_view*& ids) const; | ||
413 | |||
414 | private: | ||
415 | // try to match from this root node | ||
416 | node const* | ||
417 | try_match( | ||
418 | segments_encoded_view::const_iterator it, | ||
419 | segments_encoded_view::const_iterator end, | ||
420 | node const* root, | ||
421 | int level, | ||
422 | core::string_view*& matches, | ||
423 | core::string_view*& ids) const; | ||
424 | |||
425 | // check if a node has a resource when we | ||
426 | // also consider optional paths through | ||
427 | // the child nodes. | ||
428 | static | ||
429 | node const* | ||
430 | find_optional_resource( | ||
431 | const node* root, | ||
432 | std::vector<node> const& ns, | ||
433 | core::string_view*& matches, | ||
434 | core::string_view*& ids); | ||
435 | }; | ||
436 | |||
437 | node const* | ||
438 | 55 | impl:: | |
439 | find_optional_resource( | ||
440 | const node* root, | ||
441 | std::vector<node> const& ns, | ||
442 | core::string_view*& matches, | ||
443 | core::string_view*& ids) | ||
444 | { | ||
445 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 55 times.
|
55 | BOOST_ASSERT(root); |
446 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 42 times.
|
55 | if (root->resource) |
447 | 13 | return root; | |
448 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 42 times.
|
42 | BOOST_ASSERT(!root->child_idx.empty()); |
449 |
2/2✓ Branch 2 taken 42 times.
✓ Branch 3 taken 27 times.
|
69 | for (auto i: root->child_idx) |
450 | { | ||
451 | 42 | auto& c = ns[i]; | |
452 |
4/4✓ Branch 1 taken 34 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 16 times.
|
76 | if (!c.seg.is_optional() && |
453 |
2/2✓ Branch 1 taken 26 times.
✓ Branch 2 taken 8 times.
|
34 | !c.seg.is_star()) |
454 | 26 | continue; | |
455 | // Child nodes are also | ||
456 | // potentially optional. | ||
457 | 16 | auto matches0 = matches; | |
458 | 16 | auto ids0 = ids; | |
459 | 16 | *matches++ = {}; | |
460 | 16 | *ids++ = c.seg.id(); | |
461 | 16 | auto n = find_optional_resource( | |
462 | &c, ns, matches, ids); | ||
463 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 times.
|
16 | if (n) |
464 | 15 | return n; | |
465 | 1 | matches = matches0; | |
466 | 1 | ids = ids0; | |
467 | } | ||
468 | 27 | return nullptr; | |
469 | } | ||
470 | |||
471 | void | ||
472 | 113 | impl:: | |
473 | insert_impl( | ||
474 | core::string_view path, | ||
475 | router_base::any_resource const* v) | ||
476 | { | ||
477 | // Parse dynamic route segments | ||
478 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 111 times.
|
113 | if (path.starts_with("/")) |
479 | 2 | path.remove_prefix(1); | |
480 | auto segsr = | ||
481 |
1/2✓ Branch 1 taken 113 times.
✗ Branch 2 not taken.
|
226 | grammar::parse(path, detail::path_template_rule); |
482 |
2/2✓ Branch 1 taken 1 times.
✓ Branch 2 taken 112 times.
|
113 | if (!segsr) |
483 | { | ||
484 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | delete v; |
485 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | segsr.value(); |
486 | } | ||
487 | 224 | auto segs = *segsr; | |
488 | 224 | auto it = segs.begin(); | |
489 | 113 | auto end = segs.end(); | |
490 | |||
491 | // Iterate existing nodes | ||
492 | 112 | node* cur = &nodes_.front(); | |
493 | 112 | int level = 0; | |
494 |
2/2✓ Branch 1 taken 326 times.
✓ Branch 2 taken 112 times.
|
438 | while (it != end) |
495 | { | ||
496 | 326 | core::string_view seg = (*it).string(); | |
497 |
2/2✓ Branch 2 taken 2 times.
✓ Branch 3 taken 324 times.
|
326 | if (seg == ".") |
498 | { | ||
499 | 2 | ++it; | |
500 | 10 | continue; | |
501 | } | ||
502 |
2/2✓ Branch 2 taken 7 times.
✓ Branch 3 taken 317 times.
|
324 | if (seg == "..") |
503 | { | ||
504 | // discount unmatched leaf or | ||
505 | // keep track of levels behind root | ||
506 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 5 times.
|
7 | if (cur == &nodes_.front()) |
507 | { | ||
508 | 2 | --level; | |
509 | 2 | ++it; | |
510 | 2 | continue; | |
511 | } | ||
512 | // move to parent deleting current | ||
513 | // if it carries no resource | ||
514 | 5 | std::size_t p_idx = cur->parent_idx; | |
515 | 5 | if (cur == &nodes_.back() && | |
516 |
4/8✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
|
10 | !cur->resource && |
517 | 5 | cur->child_idx.empty()) | |
518 | { | ||
519 | 5 | node* p = &nodes_[p_idx]; | |
520 | 5 | std::size_t cur_idx = cur - nodes_.data(); | |
521 |
1/2✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
|
5 | p->child_idx.erase( |
522 | std::remove( | ||
523 | p->child_idx.begin(), | ||
524 | p->child_idx.end(), | ||
525 | cur_idx)); | ||
526 | 5 | nodes_.pop_back(); | |
527 | } | ||
528 | 5 | cur = &nodes_[p_idx]; | |
529 | 5 | ++it; | |
530 | 5 | continue; | |
531 | } | ||
532 | // discount unmatched root parent | ||
533 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 316 times.
|
317 | if (level < 0) |
534 | { | ||
535 | 1 | ++level; | |
536 | 1 | ++it; | |
537 | 1 | continue; | |
538 | } | ||
539 | // look for child | ||
540 |
1/2✓ Branch 3 taken 316 times.
✗ Branch 4 not taken.
|
316 | auto cit = std::find_if( |
541 | cur->child_idx.begin(), | ||
542 | cur->child_idx.end(), | ||
543 | 166 | [this, &it](std::size_t ci) -> bool | |
544 | { | ||
545 | 83 | return nodes_[ci].seg == *it; | |
546 | }); | ||
547 |
2/2✓ Branch 1 taken 18 times.
✓ Branch 2 taken 298 times.
|
316 | if (cit != cur->child_idx.end()) |
548 | { | ||
549 | // move to existing child | ||
550 | 18 | cur = &nodes_[*cit]; | |
551 | } | ||
552 | else | ||
553 | { | ||
554 | // create child if it doesn't exist | ||
555 | 596 | node child; | |
556 |
1/2✓ Branch 2 taken 298 times.
✗ Branch 3 not taken.
|
298 | child.seg = *it; |
557 | 298 | std::size_t cur_id = cur - nodes_.data(); | |
558 | 298 | child.parent_idx = cur_id; | |
559 |
1/2✓ Branch 2 taken 298 times.
✗ Branch 3 not taken.
|
298 | nodes_.push_back(std::move(child)); |
560 |
1/2✓ Branch 3 taken 298 times.
✗ Branch 4 not taken.
|
298 | nodes_[cur_id].child_idx.push_back(nodes_.size() - 1); |
561 |
2/2✓ Branch 2 taken 18 times.
✓ Branch 3 taken 280 times.
|
298 | if (nodes_[cur_id].child_idx.size() > 1) |
562 | { | ||
563 | // keep nodes sorted | ||
564 | 18 | auto& cs = nodes_[cur_id].child_idx; | |
565 | 18 | std::size_t n = cs.size() - 1; | |
566 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 times.
|
19 | while (n) |
567 | { | ||
568 |
2/2✓ Branch 5 taken 1 times.
✓ Branch 6 taken 17 times.
|
18 | if (nodes_[cs.begin()[n]].seg < nodes_[cs.begin()[n - 1]].seg) |
569 | 1 | std::swap(cs.begin()[n], cs.begin()[n - 1]); | |
570 | else | ||
571 | 17 | break; | |
572 | 1 | --n; | |
573 | } | ||
574 | } | ||
575 | 298 | cur = &nodes_.back(); | |
576 | } | ||
577 | 316 | ++it; | |
578 | } | ||
579 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 111 times.
|
112 | if (level != 0) |
580 | { | ||
581 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | delete v; |
582 | 1 | urls::detail::throw_invalid_argument(); | |
583 | } | ||
584 | 111 | cur->resource = v; | |
585 |
1/2✓ Branch 1 taken 111 times.
✗ Branch 2 not taken.
|
111 | cur->path_template = path; |
586 | 111 | } | |
587 | |||
588 | node const* | ||
589 | 455 | impl:: | |
590 | try_match( | ||
591 | segments_encoded_view::const_iterator it, | ||
592 | segments_encoded_view::const_iterator end, | ||
593 | node const* cur, | ||
594 | int level, | ||
595 | core::string_view*& matches, | ||
596 | core::string_view*& ids) const | ||
597 | { | ||
598 |
2/2✓ Branch 1 taken 340 times.
✓ Branch 2 taken 115 times.
|
455 | while (it != end) |
599 | { | ||
600 | 340 | pct_string_view s = *it; | |
601 |
2/2✓ Branch 2 taken 5 times.
✓ Branch 3 taken 335 times.
|
340 | if (*s == ".") |
602 | { | ||
603 | // ignore segment | ||
604 | 5 | ++it; | |
605 | 50 | continue; | |
606 | } | ||
607 |
2/2✓ Branch 2 taken 44 times.
✓ Branch 3 taken 291 times.
|
335 | if (*s == "..") |
608 | { | ||
609 | |||
610 | // move back to the parent node | ||
611 | 44 | ++it; | |
612 |
6/6✓ Branch 0 taken 32 times.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 31 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 31 times.
✓ Branch 5 taken 13 times.
|
76 | if (level <= 0 && |
613 | 32 | cur != &nodes_.front()) | |
614 | { | ||
615 |
2/2✓ Branch 1 taken 22 times.
✓ Branch 2 taken 9 times.
|
31 | if (!cur->seg.is_literal()) |
616 | { | ||
617 | 22 | --matches; | |
618 | 22 | --ids; | |
619 | } | ||
620 | 31 | cur = &nodes_[cur->parent_idx]; | |
621 | } | ||
622 | else | ||
623 | // there's no parent, so we | ||
624 | // discount that from the implicit | ||
625 | // tree beyond terminals | ||
626 | 13 | --level; | |
627 | 44 | continue; | |
628 | } | ||
629 | |||
630 | // we are in the implicit tree above the | ||
631 | // root, so discount that as a level | ||
632 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 290 times.
|
291 | if (level < 0) |
633 | { | ||
634 | 1 | ++level; | |
635 | 1 | ++it; | |
636 | 1 | continue; | |
637 | } | ||
638 | |||
639 | // calculate the lower bound on the | ||
640 | // possible number of branches to | ||
641 | // determine if we need to branch. | ||
642 | // We branch when we might have more than | ||
643 | // one child matching node at this level. | ||
644 | // If so, we need to potentially branch | ||
645 | // to find which path leads to a valid | ||
646 | // resource. Otherwise, we can just | ||
647 | // consume the node and input without | ||
648 | // any recursive function calls. | ||
649 | 290 | bool branch = false; | |
650 |
2/2✓ Branch 1 taken 7 times.
✓ Branch 2 taken 283 times.
|
290 | if (cur->child_idx.size() > 1) |
651 | { | ||
652 | 7 | int branches_lb = 0; | |
653 |
2/2✓ Branch 2 taken 25 times.
✓ Branch 3 taken 2 times.
|
27 | for (auto i: cur->child_idx) |
654 | { | ||
655 | 25 | auto& c = nodes_[i]; | |
656 |
4/4✓ Branch 1 taken 8 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 22 times.
✓ Branch 4 taken 3 times.
|
33 | if (c.seg.is_literal() || |
657 |
2/2✓ Branch 1 taken 5 times.
✓ Branch 2 taken 3 times.
|
8 | !c.seg.has_modifier()) |
658 | { | ||
659 | // a literal path counts only | ||
660 | // if it matches | ||
661 |
1/2✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
|
22 | branches_lb += c.seg.match(s); |
662 | } | ||
663 | else | ||
664 | { | ||
665 | // everything not matching | ||
666 | // a single path counts as | ||
667 | // more than one path already | ||
668 | 3 | branches_lb = 2; | |
669 | } | ||
670 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 20 times.
|
25 | if (branches_lb > 1) |
671 | { | ||
672 | // already know we need to | ||
673 | // branch | ||
674 | 5 | branch = true; | |
675 | 5 | break; | |
676 | } | ||
677 | } | ||
678 | } | ||
679 | |||
680 | // attempt to match each child node | ||
681 | 290 | node const* r = nullptr; | |
682 | 290 | bool match_any = false; | |
683 |
2/2✓ Branch 2 taken 289 times.
✓ Branch 3 taken 19 times.
|
308 | for (auto i: cur->child_idx) |
684 | { | ||
685 | 289 | auto& c = nodes_[i]; | |
686 |
3/4✓ Branch 1 taken 289 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 278 times.
✓ Branch 4 taken 11 times.
|
289 | if (c.seg.match(s)) |
687 | { | ||
688 |
2/2✓ Branch 1 taken 123 times.
✓ Branch 2 taken 155 times.
|
278 | if (c.seg.is_literal()) |
689 | { | ||
690 | // just continue from the | ||
691 | // next segment | ||
692 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 121 times.
|
123 | if (branch) |
693 | { | ||
694 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | r = try_match( |
695 | std::next(it), end, | ||
696 | &c, level, | ||
697 | matches, ids); | ||
698 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (r) |
699 | 2 | break; | |
700 | } | ||
701 | else | ||
702 | { | ||
703 | 121 | cur = &c; | |
704 | 121 | match_any = true; | |
705 | 121 | break; | |
706 | } | ||
707 | } | ||
708 |
2/2✓ Branch 1 taken 96 times.
✓ Branch 2 taken 59 times.
|
155 | else if (!c.seg.has_modifier()) |
709 | { | ||
710 | // just continue from the | ||
711 | // next segment | ||
712 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 94 times.
|
96 | if (branch) |
713 | { | ||
714 | 2 | auto matches0 = matches; | |
715 | 2 | auto ids0 = ids; | |
716 | 2 | *matches++ = *it; | |
717 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | *ids++ = c.seg.id(); |
718 |
2/4✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | r = try_match( |
719 | std::next(it), end, &c, | ||
720 | level, matches, ids); | ||
721 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (r) |
722 | { | ||
723 | 1 | break; | |
724 | } | ||
725 | else | ||
726 | { | ||
727 | // rewind | ||
728 | 1 | matches = matches0; | |
729 | 1 | ids = ids0; | |
730 | } | ||
731 | } | ||
732 | else | ||
733 | { | ||
734 | // only path possible | ||
735 | 94 | *matches++ = *it; | |
736 |
1/2✓ Branch 1 taken 94 times.
✗ Branch 2 not taken.
|
94 | *ids++ = c.seg.id(); |
737 | 94 | cur = &c; | |
738 | 94 | match_any = true; | |
739 | 94 | break; | |
740 | } | ||
741 | } | ||
742 |
2/2✓ Branch 1 taken 18 times.
✓ Branch 2 taken 41 times.
|
59 | else if (c.seg.is_optional()) |
743 | { | ||
744 | // attempt to match by ignoring | ||
745 | // and not ignoring the segment. | ||
746 | // we first try the complete | ||
747 | // continuation consuming the | ||
748 | // input, which is the | ||
749 | // longest and most likely | ||
750 | // match | ||
751 | 18 | auto matches0 = matches; | |
752 | 18 | auto ids0 = ids; | |
753 | 18 | *matches++ = *it; | |
754 |
1/2✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
|
18 | *ids++ = c.seg.id(); |
755 |
2/4✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
|
18 | r = try_match( |
756 | std::next(it), end, | ||
757 | &c, level, matches, ids); | ||
758 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 7 times.
|
18 | if (r) |
759 | 11 | break; | |
760 | // rewind | ||
761 | 7 | matches = matches0; | |
762 | 7 | ids = ids0; | |
763 | // try complete continuation | ||
764 | // consuming no segment | ||
765 | 7 | *matches++ = {}; | |
766 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | *ids++ = c.seg.id(); |
767 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | r = try_match( |
768 | it, end, &c, | ||
769 | level, matches, ids); | ||
770 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
|
7 | if (r) |
771 | 5 | break; | |
772 | // rewind | ||
773 | 2 | matches = matches0; | |
774 | 2 | ids = ids0; | |
775 | } | ||
776 | else | ||
777 | { | ||
778 | // check if the next segments | ||
779 | // won't send us to a parent | ||
780 | // directory | ||
781 | 41 | auto first = it; | |
782 | 41 | std::size_t ndotdot = 0; | |
783 | 41 | std::size_t nnondot = 0; | |
784 | 41 | auto it1 = it; | |
785 |
2/2✓ Branch 1 taken 83 times.
✓ Branch 2 taken 31 times.
|
114 | while (it1 != end) |
786 | { | ||
787 |
2/2✓ Branch 2 taken 17 times.
✓ Branch 3 taken 66 times.
|
83 | if (*it1 == "..") |
788 | { | ||
789 | 17 | ++ndotdot; | |
790 |
2/2✓ Branch 1 taken 10 times.
✓ Branch 2 taken 7 times.
|
17 | if (ndotdot >= (nnondot + c.seg.is_star())) |
791 | 10 | break; | |
792 | } | ||
793 |
1/2✓ Branch 2 taken 66 times.
✗ Branch 3 not taken.
|
66 | else if (*it1 != ".") |
794 | { | ||
795 | 66 | ++nnondot; | |
796 | } | ||
797 | 73 | ++it1; | |
798 | } | ||
799 |
2/2✓ Branch 1 taken 10 times.
✓ Branch 2 taken 31 times.
|
41 | if (it1 != end) |
800 | 37 | break; | |
801 | |||
802 | // attempt to match many | ||
803 | // segments | ||
804 | 31 | auto matches0 = matches; | |
805 | 31 | auto ids0 = ids; | |
806 | 31 | *matches++ = *it; | |
807 |
1/2✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
|
31 | *ids++ = c.seg.id(); |
808 | // if this is a plus seg, we | ||
809 | // already consumed the first | ||
810 | // segment | ||
811 |
2/2✓ Branch 1 taken 17 times.
✓ Branch 2 taken 14 times.
|
31 | if (c.seg.is_plus()) |
812 | { | ||
813 | 17 | ++first; | |
814 | } | ||
815 | // {*} is usually the last | ||
816 | // match in a path. | ||
817 | // try complete continuation | ||
818 | // match for every subrange | ||
819 | // from {last, last} to | ||
820 | // {first, last}. | ||
821 | // We also try {last, last} | ||
822 | // first because it is the | ||
823 | // longest match. | ||
824 | 31 | auto start = end; | |
825 |
2/2✓ Branch 1 taken 25 times.
✓ Branch 2 taken 14 times.
|
39 | while (start != first) |
826 | { | ||
827 |
1/2✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
|
25 | r = try_match( |
828 | start, end, &c, | ||
829 | level, matches, ids); | ||
830 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 8 times.
|
25 | if (r) |
831 | { | ||
832 |
1/2✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
|
17 | core::string_view prev = *std::prev(start); |
833 | 34 | *matches0 = { | |
834 | matches0->data(), | ||
835 | 17 | prev.data() + prev.size()}; | |
836 | 17 | break; | |
837 | } | ||
838 | 8 | matches = matches0 + 1; | |
839 | 8 | ids = ids0 + 1; | |
840 | 8 | --start; | |
841 | } | ||
842 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 14 times.
|
31 | if (r) |
843 | { | ||
844 | 17 | break; | |
845 | } | ||
846 | // start == first | ||
847 | 14 | matches = matches0 + 1; | |
848 | 14 | ids = ids0 + 1; | |
849 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | r = try_match( |
850 | start, end, &c, | ||
851 | level, matches, ids); | ||
852 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4 times.
|
14 | if (r) |
853 | { | ||
854 |
2/2✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
|
10 | if (!c.seg.is_plus()) |
855 | 2 | *matches0 = {}; | |
856 | 10 | break; | |
857 | } | ||
858 | } | ||
859 | } | ||
860 | } | ||
861 | // r represent we already found a terminal | ||
862 | // node which is a match | ||
863 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 244 times.
|
290 | if (r) |
864 | 46 | return r; | |
865 | // if we couldn't match anything, we go | ||
866 | // one level up in the implicit tree | ||
867 | // because the path might still have a | ||
868 | // "..". | ||
869 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 215 times.
|
244 | if (!match_any) |
870 | 29 | ++level; | |
871 | 244 | ++it; | |
872 | } | ||
873 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 102 times.
|
115 | if (level != 0) |
874 | { | ||
875 | // the path ended below or above an | ||
876 | // existing node | ||
877 | 13 | return nullptr; | |
878 | } | ||
879 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 63 times.
|
102 | if (!cur->resource) |
880 | { | ||
881 | // we consumed all the input and reached | ||
882 | // a node with no resource, but it might | ||
883 | // still have child optional segments | ||
884 | // with resources we can reach without | ||
885 | // consuming any input | ||
886 | 78 | return find_optional_resource( | |
887 | 39 | cur, nodes_, matches, ids); | |
888 | } | ||
889 | 63 | return cur; | |
890 | } | ||
891 | |||
892 | router_base::any_resource const* | ||
893 | 93 | impl:: | |
894 | find_impl( | ||
895 | segments_encoded_view path, | ||
896 | core::string_view*& matches, | ||
897 | core::string_view*& ids) const | ||
898 | { | ||
899 | // parse_path is inconsistent for empty paths | ||
900 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 89 times.
|
93 | if (path.empty()) |
901 |
1/2✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | path = segments_encoded_view("./"); |
902 | |||
903 | // Iterate nodes from the root | ||
904 | 93 | node const*p = try_match( | |
905 | path.begin(), path.end(), | ||
906 | 93 | &nodes_.front(), 0, | |
907 | matches, ids); | ||
908 |
2/2✓ Branch 0 taken 76 times.
✓ Branch 1 taken 17 times.
|
93 | if (p) |
909 | 76 | return p->resource; | |
910 | 17 | return nullptr; | |
911 | } | ||
912 | |||
913 | 95 | router_base:: | |
914 | 95 | router_base() | |
915 |
1/2✓ Branch 2 taken 95 times.
✗ Branch 3 not taken.
|
95 | : impl_(new impl{}) {} |
916 | |||
917 | 190 | router_base:: | |
918 | 380 | ~router_base() | |
919 | { | ||
920 |
1/2✓ Branch 0 taken 95 times.
✗ Branch 1 not taken.
|
190 | delete reinterpret_cast<impl*>(impl_); |
921 | 190 | } | |
922 | |||
923 | void | ||
924 | 113 | router_base:: | |
925 | insert_impl( | ||
926 | core::string_view s, | ||
927 | any_resource const* v) | ||
928 | { | ||
929 | 113 | reinterpret_cast<impl*>(impl_) | |
930 | 113 | ->insert_impl(s, v); | |
931 | 111 | } | |
932 | |||
933 | auto | ||
934 | 93 | router_base:: | |
935 | find_impl( | ||
936 | segments_encoded_view path, | ||
937 | core::string_view*& matches, | ||
938 | core::string_view*& ids) const noexcept | ||
939 | -> any_resource const* | ||
940 | { | ||
941 | 93 | return reinterpret_cast<impl*>(impl_) | |
942 | 93 | ->find_impl(path, matches, ids); | |
943 | } | ||
944 | |||
945 | } // detail | ||
946 | } // urls | ||
947 | } // boost | ||
948 | |||
949 | #endif | ||
950 |