Line data Source code
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 326 : return !is_literal() &&
78 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 83 : if (a.is_literal_ != b.is_literal_)
105 8 : return false;
106 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 18 : if (b.is_literal())
123 15 : return false;
124 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 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 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 303 : if (r.ends_with('?') ||
182 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 654 : if (it != end &&
197 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 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 636 : if (s.empty() ||
217 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 322 : if (s.ends_with('?'))
224 48 : t.modifier_ =
225 : segment_template::modifier::optional;
226 274 : else if (s.ends_with('*'))
227 48 : t.modifier_ =
228 : segment_template::modifier::star;
229 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 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 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 390 : if (other.child_idx)
273 : {
274 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 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 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 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 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 297 : else if (child_idx && size_ == cap_)
348 : {
349 1 : auto* tmp = new std::size_t[cap_*2];
350 1 : std::memcpy(tmp, child_idx, cap_ * sizeof(std::size_t));
351 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 95 : nodes_.push_back(node{});
393 95 : }
394 :
395 95 : ~impl()
396 95 : {
397 483 : for (auto &r: nodes_)
398 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 55 : BOOST_ASSERT(root);
446 55 : if (root->resource)
447 13 : return root;
448 42 : BOOST_ASSERT(!root->child_idx.empty());
449 69 : for (auto i: root->child_idx)
450 : {
451 42 : auto& c = ns[i];
452 76 : if (!c.seg.is_optional() &&
453 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 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 113 : if (path.starts_with("/"))
479 2 : path.remove_prefix(1);
480 : auto segsr =
481 226 : grammar::parse(path, detail::path_template_rule);
482 113 : if (!segsr)
483 : {
484 1 : delete v;
485 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 438 : while (it != end)
495 : {
496 326 : core::string_view seg = (*it).string();
497 326 : if (seg == ".")
498 : {
499 2 : ++it;
500 10 : continue;
501 : }
502 324 : if (seg == "..")
503 : {
504 : // discount unmatched leaf or
505 : // keep track of levels behind root
506 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 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 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 317 : if (level < 0)
534 : {
535 1 : ++level;
536 1 : ++it;
537 1 : continue;
538 : }
539 : // look for child
540 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 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 298 : child.seg = *it;
557 298 : std::size_t cur_id = cur - nodes_.data();
558 298 : child.parent_idx = cur_id;
559 298 : nodes_.push_back(std::move(child));
560 298 : nodes_[cur_id].child_idx.push_back(nodes_.size() - 1);
561 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 19 : while (n)
567 : {
568 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 112 : if (level != 0)
580 : {
581 1 : delete v;
582 1 : urls::detail::throw_invalid_argument();
583 : }
584 111 : cur->resource = v;
585 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 455 : while (it != end)
599 : {
600 340 : pct_string_view s = *it;
601 340 : if (*s == ".")
602 : {
603 : // ignore segment
604 5 : ++it;
605 50 : continue;
606 : }
607 335 : if (*s == "..")
608 : {
609 :
610 : // move back to the parent node
611 44 : ++it;
612 76 : if (level <= 0 &&
613 32 : cur != &nodes_.front())
614 : {
615 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 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 290 : if (cur->child_idx.size() > 1)
651 : {
652 7 : int branches_lb = 0;
653 27 : for (auto i: cur->child_idx)
654 : {
655 25 : auto& c = nodes_[i];
656 33 : if (c.seg.is_literal() ||
657 8 : !c.seg.has_modifier())
658 : {
659 : // a literal path counts only
660 : // if it matches
661 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 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 308 : for (auto i: cur->child_idx)
684 : {
685 289 : auto& c = nodes_[i];
686 289 : if (c.seg.match(s))
687 : {
688 278 : if (c.seg.is_literal())
689 : {
690 : // just continue from the
691 : // next segment
692 123 : if (branch)
693 : {
694 2 : r = try_match(
695 : std::next(it), end,
696 : &c, level,
697 : matches, ids);
698 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 155 : else if (!c.seg.has_modifier())
709 : {
710 : // just continue from the
711 : // next segment
712 96 : if (branch)
713 : {
714 2 : auto matches0 = matches;
715 2 : auto ids0 = ids;
716 2 : *matches++ = *it;
717 2 : *ids++ = c.seg.id();
718 2 : r = try_match(
719 : std::next(it), end, &c,
720 : level, matches, ids);
721 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 94 : *ids++ = c.seg.id();
737 94 : cur = &c;
738 94 : match_any = true;
739 94 : break;
740 : }
741 : }
742 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 18 : *ids++ = c.seg.id();
755 18 : r = try_match(
756 : std::next(it), end,
757 : &c, level, matches, ids);
758 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 7 : *ids++ = c.seg.id();
767 7 : r = try_match(
768 : it, end, &c,
769 : level, matches, ids);
770 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 114 : while (it1 != end)
786 : {
787 83 : if (*it1 == "..")
788 : {
789 17 : ++ndotdot;
790 17 : if (ndotdot >= (nnondot + c.seg.is_star()))
791 10 : break;
792 : }
793 66 : else if (*it1 != ".")
794 : {
795 66 : ++nnondot;
796 : }
797 73 : ++it1;
798 : }
799 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 31 : *ids++ = c.seg.id();
808 : // if this is a plus seg, we
809 : // already consumed the first
810 : // segment
811 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 39 : while (start != first)
826 : {
827 25 : r = try_match(
828 : start, end, &c,
829 : level, matches, ids);
830 25 : if (r)
831 : {
832 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 31 : if (r)
843 : {
844 17 : break;
845 : }
846 : // start == first
847 14 : matches = matches0 + 1;
848 14 : ids = ids0 + 1;
849 14 : r = try_match(
850 : start, end, &c,
851 : level, matches, ids);
852 14 : if (r)
853 : {
854 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 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 244 : if (!match_any)
870 29 : ++level;
871 244 : ++it;
872 : }
873 115 : if (level != 0)
874 : {
875 : // the path ended below or above an
876 : // existing node
877 13 : return nullptr;
878 : }
879 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 93 : if (path.empty())
901 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 93 : if (p)
909 76 : return p->resource;
910 17 : return nullptr;
911 : }
912 :
913 95 : router_base::
914 95 : router_base()
915 95 : : impl_(new impl{}) {}
916 :
917 95 : router_base::
918 190 : ~router_base()
919 : {
920 95 : delete reinterpret_cast<impl*>(impl_);
921 95 : }
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
|