1 +
//
 
2 +
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@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/cppalliance/http
 
8 +
//
 
9 +

 
10 +
#include "src/server/detail/router_base.hpp"
 
11 +
#include <boost/http/server/detail/router_base.hpp>
 
12 +
#include <boost/http/detail/except.hpp>
 
13 +
#include <boost/http/error.hpp>
 
14 +
#include <boost/http/field.hpp>
 
15 +
#include <boost/http/status.hpp>
 
16 +
#include <boost/url/grammar/ci_string.hpp>
 
17 +
#include <boost/url/grammar/hexdig_chars.hpp>
 
18 +
#include "src/server/detail/pct_decode.hpp"
 
19 +

 
20 +
#include <algorithm>
 
21 +

 
22 +
namespace boost {
 
23 +
namespace http {
 
24 +
namespace detail {
 
25 +

 
26 +
//------------------------------------------------
 
27 +
//
 
28 +
// impl helpers
 
29 +
//
 
30 +
//------------------------------------------------
 
31 +

 
32 +
std::string
 
33 +
router_base::impl::
 
34 +
build_allow_header(
 
35 +
    std::uint64_t methods,
 
36 +
    std::vector<std::string> const& custom)
 
37 +
{
 
38 +
    if(methods == ~0ULL)
 
39 +
        return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT";
 
40 +

 
41 +
    std::string result;
 
42 +
    static constexpr std::pair<http::method, char const*> known[] = {
 
43 +
        {http::method::acl, "ACL"},
 
44 +
        {http::method::bind, "BIND"},
 
45 +
        {http::method::checkout, "CHECKOUT"},
 
46 +
        {http::method::connect, "CONNECT"},
 
47 +
        {http::method::copy, "COPY"},
 
48 +
        {http::method::delete_, "DELETE"},
 
49 +
        {http::method::get, "GET"},
 
50 +
        {http::method::head, "HEAD"},
 
51 +
        {http::method::link, "LINK"},
 
52 +
        {http::method::lock, "LOCK"},
 
53 +
        {http::method::merge, "MERGE"},
 
54 +
        {http::method::mkactivity, "MKACTIVITY"},
 
55 +
        {http::method::mkcalendar, "MKCALENDAR"},
 
56 +
        {http::method::mkcol, "MKCOL"},
 
57 +
        {http::method::move, "MOVE"},
 
58 +
        {http::method::msearch, "M-SEARCH"},
 
59 +
        {http::method::notify, "NOTIFY"},
 
60 +
        {http::method::options, "OPTIONS"},
 
61 +
        {http::method::patch, "PATCH"},
 
62 +
        {http::method::post, "POST"},
 
63 +
        {http::method::propfind, "PROPFIND"},
 
64 +
        {http::method::proppatch, "PROPPATCH"},
 
65 +
        {http::method::purge, "PURGE"},
 
66 +
        {http::method::put, "PUT"},
 
67 +
        {http::method::rebind, "REBIND"},
 
68 +
        {http::method::report, "REPORT"},
 
69 +
        {http::method::search, "SEARCH"},
 
70 +
        {http::method::subscribe, "SUBSCRIBE"},
 
71 +
        {http::method::trace, "TRACE"},
 
72 +
        {http::method::unbind, "UNBIND"},
 
73 +
        {http::method::unlink, "UNLINK"},
 
74 +
        {http::method::unlock, "UNLOCK"},
 
75 +
        {http::method::unsubscribe, "UNSUBSCRIBE"},
 
76 +
    };
 
77 +
    for(auto const& [m, name] : known)
 
78 +
    {
 
79 +
        if(methods & (1ULL << static_cast<unsigned>(m)))
 
80 +
        {
 
81 +
            if(!result.empty())
 
82 +
                result += ", ";
 
83 +
            result += name;
 
84 +
        }
 
85 +
    }
 
86 +
    for(auto const& v : custom)
 
87 +
    {
 
88 +
        if(!result.empty())
 
89 +
            result += ", ";
 
90 +
        result += v;
 
91 +
    }
 
92 +
    return result;
 
93 +
}
 
94 +

 
95 +
router_base::opt_flags
 
96 +
router_base::impl::
 
97 +
compute_effective_opts(
 
98 +
    opt_flags parent,
 
99 +
    opt_flags child)
 
100 +
{
 
101 +
    opt_flags result = parent;
 
102 +

 
103 +
    // case_sensitive: bits 1-2 (2=true, 4=false)
 
104 +
    if(child & 2)
 
105 +
        result = (result & ~6) | 2;
 
106 +
    else if(child & 4)
 
107 +
        result = (result & ~6) | 4;
 
108 +

 
109 +
    // strict: bits 3-4 (8=true, 16=false)
 
110 +
    if(child & 8)
 
111 +
        result = (result & ~24) | 8;
 
112 +
    else if(child & 16)
 
113 +
        result = (result & ~24) | 16;
 
114 +

 
115 +
    return result;
 
116 +
}
 
117 +

 
118 +
void
 
119 +
router_base::impl::
 
120 +
restore_path(
 
121 +
    route_params& p,
 
122 +
    std::size_t base_len)
 
123 +
{
 
124 +
    auto& pv = *route_params_access{p};
 
125 +
    p.base_path = { pv.decoded_path_.data(), base_len };
 
126 +
    auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0);
 
127 +
    if(base_len < path_len)
 
128 +
        p.path = { pv.decoded_path_.data() + base_len,
 
129 +
            path_len - base_len };
 
130 +
    else
 
131 +
        p.path = { pv.decoded_path_.data() +
 
132 +
            pv.decoded_path_.size() - 1, 1 };  // soft slash
 
133 +
}
 
134 +

 
135 +
void
 
136 +
router_base::impl::
 
137 +
update_allow_for_entry(
 
138 +
    matcher& m,
 
139 +
    entry const& e)
 
140 +
{
 
141 +
    if(!m.end_)
 
142 +
        return;
 
143 +

 
144 +
    // Per-matcher collection
 
145 +
    if(e.all)
 
146 +
        m.allowed_methods_ = ~0ULL;
 
147 +
    else if(e.verb != http::method::unknown)
 
148 +
        m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
 
149 +
    else if(!e.verb_str.empty())
 
150 +
        m.custom_verbs_.push_back(e.verb_str);
 
151 +

 
152 +
    // Rebuild per-matcher Allow header eagerly
 
153 +
    m.allow_header_ = build_allow_header(
 
154 +
        m.allowed_methods_, m.custom_verbs_);
 
155 +

 
156 +
    // Global collection (for OPTIONS *)
 
157 +
    if(e.all)
 
158 +
        global_methods_ = ~0ULL;
 
159 +
    else if(e.verb != http::method::unknown)
 
160 +
        global_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
 
161 +
    else if(!e.verb_str.empty())
 
162 +
        global_custom_verbs_.push_back(e.verb_str);
 
163 +
}
 
164 +

 
165 +
void
 
166 +
router_base::impl::
 
167 +
rebuild_global_allow_header()
 
168 +
{
 
169 +
    std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end());
 
170 +
    global_custom_verbs_.erase(
 
171 +
        std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()),
 
172 +
        global_custom_verbs_.end());
 
173 +
    global_allow_header_ = build_allow_header(
 
174 +
        global_methods_, global_custom_verbs_);
 
175 +
}
 
176 +

 
177 +
void
 
178 +
router_base::impl::
 
179 +
finalize_pending()
 
180 +
{
 
181 +
    if(pending_route_ == SIZE_MAX)
 
182 +
        return;
 
183 +
    auto& m = matchers[pending_route_];
 
184 +
    if(entries.size() == m.first_entry_)
 
185 +
    {
 
186 +
        // empty route, remove it
 
187 +
        matchers.pop_back();
 
188 +
    }
 
189 +
    else
 
190 +
    {
 
191 +
        m.skip_ = entries.size();
 
192 +
    }
 
193 +
    pending_route_ = SIZE_MAX;
 
194 +
}
 
195 +

 
196 +
//------------------------------------------------
 
197 +
//
 
198 +
// dispatch
 
199 +
//
 
200 +
//------------------------------------------------
 
201 +

 
202 +
route_task
 
203 +
router_base::impl::
 
204 +
dispatch_loop(route_params& p, bool is_options) const
 
205 +
{
 
206 +
    auto& pv = *route_params_access{p};
 
207 +

 
208 +
    std::size_t last_matched = SIZE_MAX;
 
209 +
    std::uint32_t current_depth = 0;
 
210 +

 
211 +
    std::uint64_t matched_methods = 0;
 
212 +
    std::vector<std::string> matched_custom_verbs;
 
213 +

 
214 +
    std::size_t path_stack[router_base::max_path_depth];
 
215 +
    path_stack[0] = 0;
 
216 +

 
217 +
    std::size_t matched_at_depth[router_base::max_path_depth];
 
218 +
    for(std::size_t d = 0; d < router_base::max_path_depth; ++d)
 
219 +
        matched_at_depth[d] = SIZE_MAX;
 
220 +

 
221 +
    for(std::size_t i = 0; i < entries.size(); )
 
222 +
    {
 
223 +
        auto const& e = entries[i];
 
224 +
        auto const& m = matchers[e.matcher_idx];
 
225 +
        auto const target_depth = m.depth_;
 
226 +

 
227 +
        bool ancestors_ok = true;
 
228 +

 
229 +
        std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
 
230 +

 
231 +
        for(std::size_t check_idx = start_idx;
 
232 +
            check_idx <= e.matcher_idx && ancestors_ok;
 
233 +
            ++check_idx)
 
234 +
        {
 
235 +
            auto const& cm = matchers[check_idx];
 
236 +

 
237 +
            bool is_needed_ancestor = (cm.depth_ < target_depth) &&
 
238 +
                (matched_at_depth[cm.depth_] == SIZE_MAX);
 
239 +
            bool is_self = (check_idx == e.matcher_idx);
 
240 +

 
241 +
            if(!is_needed_ancestor && !is_self)
 
242 +
                continue;
 
243 +

 
244 +
            if(cm.depth_ <= current_depth && current_depth > 0)
 
245 +
            {
 
246 +
                restore_path(p, path_stack[cm.depth_]);
 
247 +
            }
 
248 +

 
249 +
            if(cm.end_ && pv.kind_ != router_base::is_plain)
 
250 +
            {
 
251 +
                i = cm.skip_;
 
252 +
                ancestors_ok = false;
 
253 +
                break;
 
254 +
            }
 
255 +

 
256 +
            pv.case_sensitive = (cm.effective_opts_ & 2) != 0;
 
257 +
            pv.strict = (cm.effective_opts_ & 8) != 0;
 
258 +

 
259 +
            if(cm.depth_ < router_base::max_path_depth)
 
260 +
                path_stack[cm.depth_] = p.base_path.size();
 
261 +

 
262 +
            match_result mr;
 
263 +
            if(!cm(p, mr))
 
264 +
            {
 
265 +
                for(std::size_t d = cm.depth_; d < router_base::max_path_depth; ++d)
 
266 +
                    matched_at_depth[d] = SIZE_MAX;
 
267 +
                i = cm.skip_;
 
268 +
                ancestors_ok = false;
 
269 +
                break;
 
270 +
            }
 
271 +

 
272 +
            if(!mr.params_.empty())
 
273 +
            {
 
274 +
                for(auto& param : mr.params_)
 
275 +
                    p.params.push_back(std::move(param));
 
276 +
            }
 
277 +

 
278 +
            if(cm.depth_ < router_base::max_path_depth)
 
279 +
                matched_at_depth[cm.depth_] = check_idx;
 
280 +

 
281 +
            last_matched = check_idx;
 
282 +
            current_depth = cm.depth_ + 1;
 
283 +

 
284 +
            if(current_depth < router_base::max_path_depth)
 
285 +
                path_stack[current_depth] = p.base_path.size();
 
286 +
        }
 
287 +

 
288 +
        if(!ancestors_ok)
 
289 +
            continue;
 
290 +

 
291 +
        // Collect methods from matching end-route matchers
 
292 +
        if(m.end_)
 
293 +
        {
 
294 +
            matched_methods |= m.allowed_methods_;
 
295 +
            for(auto const& v : m.custom_verbs_)
 
296 +
                matched_custom_verbs.push_back(v);
 
297 +
        }
 
298 +

 
299 +
        if(m.end_ && !e.match_method(
 
300 +
            const_cast<route_params&>(p)))
 
301 +
        {
 
302 +
            ++i;
 
303 +
            continue;
 
304 +
        }
 
305 +

 
306 +
        if(e.h->kind != pv.kind_)
 
307 +
        {
 
308 +
            ++i;
 
309 +
            continue;
 
310 +
        }
 
311 +

 
312 +
        //--------------------------------------------------
 
313 +
        // Invoke handler
 
314 +
        //--------------------------------------------------
 
315 +

 
316 +
        route_result rv;
 
317 +
        try
 
318 +
        {
 
319 +
            rv = co_await e.h->invoke(
 
320 +
                const_cast<route_params&>(p));
 
321 +
        }
 
322 +
        catch(...)
 
323 +
        {
 
324 +
            pv.ep_ = std::current_exception();
 
325 +
            pv.kind_ = router_base::is_exception;
 
326 +
            ++i;
 
327 +
            continue;
 
328 +
        }
 
329 +

 
330 +
        if(rv.what() == route_what::next)
 
331 +
        {
 
332 +
            ++i;
 
333 +
            continue;
 
334 +
        }
 
335 +

 
336 +
        if(rv.what() == route_what::next_route)
 
337 +
        {
 
338 +
            if(!m.end_)
 
339 +
                co_return route_error(error::invalid_route_result);
 
340 +
            i = m.skip_;
 
341 +
            continue;
 
342 +
        }
 
343 +

 
344 +
        if(rv.what() == route_what::done ||
 
345 +
           rv.what() == route_what::close)
 
346 +
        {
 
347 +
            co_return rv;
 
348 +
        }
 
349 +

 
350 +
        // Error - transition to error mode
 
351 +
        pv.ec_ = rv.error();
 
352 +
        pv.kind_ = router_base::is_error;
 
353 +

 
354 +
        if(m.end_)
 
355 +
        {
 
356 +
            i = m.skip_;
 
357 +
            continue;
 
358 +
        }
 
359 +

 
360 +
        ++i;
 
361 +
    }
 
362 +

 
363 +
    if(pv.kind_ == router_base::is_exception)
 
364 +
        co_return route_error(error::unhandled_exception);
 
365 +
    if(pv.kind_ == router_base::is_error)
 
366 +
        co_return route_error(pv.ec_);
 
367 +

 
368 +
    // OPTIONS fallback
 
369 +
    if(is_options && matched_methods != 0 && options_handler_)
 
370 +
    {
 
371 +
        std::string allow = build_allow_header(matched_methods, matched_custom_verbs);
 
372 +
        co_return co_await options_handler_->invoke(p, allow);
 
373 +
    }
 
374 +

 
375 +
    // 405 fallback: path matched but method didn't
 
376 +
    if(!is_options &&
 
377 +
        (matched_methods != 0 || !matched_custom_verbs.empty()))
 
378 +
    {
 
379 +
        std::string allow = build_allow_header(matched_methods, matched_custom_verbs);
 
380 +
        p.res.set(field::allow, allow);
 
381 +
        p.res.set_status(status::method_not_allowed);
 
382 +
        (void)(co_await p.send());
 
383 +
        co_return route_done;
 
384 +
    }
 
385 +

 
386 +
    co_return route_next;
 
387 +
}
 
388 +

 
389 +
//------------------------------------------------
 
390 +
//
 
391 +
// router_base
 
392 +
//
 
393 +
//------------------------------------------------
 
394 +

 
395 +
router_base::
 
396 +
router_base(
 
397 +
    opt_flags opt)
 
398 +
    : impl_(std::make_shared<impl>(opt))
 
399 +
{
 
400 +
}
 
401 +

 
402 +
void
 
403 +
router_base::
 
404 +
add_middleware(
 
405 +
    std::string_view pattern,
 
406 +
    handlers hn)
 
407 +
{
 
408 +
    impl_->finalize_pending();
 
409 +

 
410 +
    if(pattern.empty())
 
411 +
        pattern = "/";
 
412 +

 
413 +
    auto const matcher_idx = impl_->matchers.size();
 
414 +
    impl_->matchers.emplace_back(pattern, false);
 
415 +
    auto& m = impl_->matchers.back();
 
416 +
    if(m.error())
 
417 +
        throw_invalid_argument();
 
418 +
    m.first_entry_ = impl_->entries.size();
 
419 +
    m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
 
420 +
    m.own_opts_ = impl_->opt_;
 
421 +
    m.depth_ = 0;
 
422 +

 
423 +
    for(std::size_t i = 0; i < hn.n; ++i)
 
424 +
    {
 
425 +
        impl_->entries.emplace_back(std::move(hn.p[i]));
 
426 +
        impl_->entries.back().matcher_idx = matcher_idx;
 
427 +
    }
 
428 +

 
429 +
    m.skip_ = impl_->entries.size();
 
430 +
}
 
431 +

 
432 +
void
 
433 +
router_base::
 
434 +
inline_router(
 
435 +
    std::string_view pattern,
 
436 +
    router_base&& sub)
 
437 +
{
 
438 +
    impl_->finalize_pending();
 
439 +

 
440 +
    if(!sub.impl_)
 
441 +
        return;
 
442 +

 
443 +
    sub.impl_->finalize_pending();
 
444 +

 
445 +
    if(pattern.empty())
 
446 +
        pattern = "/";
 
447 +

 
448 +
    // Create parent matcher for the mount point
 
449 +
    auto const parent_matcher_idx = impl_->matchers.size();
 
450 +
    impl_->matchers.emplace_back(pattern, false);
 
451 +
    auto& parent_m = impl_->matchers.back();
 
452 +
    if(parent_m.error())
 
453 +
        throw_invalid_argument();
 
454 +
    parent_m.first_entry_ = impl_->entries.size();
 
455 +

 
456 +
    auto parent_eff = impl::compute_effective_opts(0, impl_->opt_);
 
457 +
    parent_m.effective_opts_ = parent_eff;
 
458 +
    parent_m.own_opts_ = impl_->opt_;
 
459 +
    parent_m.depth_ = 0;
 
460 +

 
461 +
    // Check nesting depth
 
462 +
    std::size_t max_sub_depth = 0;
 
463 +
    for(auto const& sm : sub.impl_->matchers)
 
464 +
        max_sub_depth = (std::max)(max_sub_depth,
 
465 +
            static_cast<std::size_t>(sm.depth_));
 
466 +
    if(max_sub_depth + 1 >= max_path_depth)
 
467 +
        throw_length_error(
 
468 +
            "router nesting depth exceeds max_path_depth");
 
469 +

 
470 +
    // Compute offsets for re-indexing
 
471 +
    auto const matcher_offset = impl_->matchers.size();
 
472 +
    auto const entry_offset = impl_->entries.size();
 
473 +

 
474 +
    // Recompute effective_opts for inlined matchers using depth stack
 
475 +
    auto sub_root_eff = impl::compute_effective_opts(
 
476 +
        parent_eff, sub.impl_->opt_);
 
477 +
    opt_flags eff_stack[max_path_depth];
 
478 +
    eff_stack[0] = sub_root_eff;
 
479 +

 
480 +
    // Inline sub's matchers
 
481 +
    for(auto& sm : sub.impl_->matchers)
 
482 +
    {
 
483 +
        auto d = sm.depth_;
 
484 +
        opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff;
 
485 +
        eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_);
 
486 +
        sm.effective_opts_ = eff_stack[d];
 
487 +
        sm.depth_ += 1;  // increase by 1 (parent is at depth 0)
 
488 +
        sm.first_entry_ += entry_offset;
 
489 +
        sm.skip_ += entry_offset;
 
490 +
        impl_->matchers.push_back(std::move(sm));
 
491 +
    }
 
492 +

 
493 +
    // Inline sub's entries
 
494 +
    for(auto& se : sub.impl_->entries)
 
495 +
    {
 
496 +
        se.matcher_idx += matcher_offset;
 
497 +
        impl_->entries.push_back(std::move(se));
 
498 +
    }
 
499 +

 
500 +
    // Set parent matcher's skip
 
501 +
    // Need to re-fetch since vector may have reallocated
 
502 +
    impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size();
 
503 +

 
504 +
    // Merge global methods
 
505 +
    impl_->global_methods_ |= sub.impl_->global_methods_;
 
506 +
    for(auto& v : sub.impl_->global_custom_verbs_)
 
507 +
        impl_->global_custom_verbs_.push_back(std::move(v));
 
508 +
    impl_->rebuild_global_allow_header();
 
509 +

 
510 +
    // Move options handler if sub has one and parent doesn't
 
511 +
    if(sub.impl_->options_handler_ && !impl_->options_handler_)
 
512 +
        impl_->options_handler_ = std::move(sub.impl_->options_handler_);
 
513 +

 
514 +
    sub.impl_.reset();
 
515 +
}
 
516 +

 
517 +
std::size_t
 
518 +
router_base::
 
519 +
new_route(
 
520 +
    std::string_view pattern)
 
521 +
{
 
522 +
    impl_->finalize_pending();
 
523 +

 
524 +
    if(pattern.empty())
 
525 +
        throw_invalid_argument();
 
526 +

 
527 +
    auto const idx = impl_->matchers.size();
 
528 +
    impl_->matchers.emplace_back(pattern, true);
 
529 +
    auto& m = impl_->matchers.back();
 
530 +
    if(m.error())
 
531 +
        throw_invalid_argument();
 
532 +
    m.first_entry_ = impl_->entries.size();
 
533 +
    m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
 
534 +
    m.own_opts_ = impl_->opt_;
 
535 +
    m.depth_ = 0;
 
536 +

 
537 +
    impl_->pending_route_ = idx;
 
538 +
    return idx;
 
539 +
}
 
540 +

 
541 +
void
 
542 +
router_base::
 
543 +
add_to_route(
 
544 +
    std::size_t idx,
 
545 +
    http::method verb,
 
546 +
    handlers hn)
 
547 +
{
 
548 +
    if(verb == http::method::unknown)
 
549 +
        throw_invalid_argument();
 
550 +

 
551 +
    auto& m = impl_->matchers[idx];
 
552 +
    for(std::size_t i = 0; i < hn.n; ++i)
 
553 +
    {
 
554 +
        impl_->entries.emplace_back(verb, std::move(hn.p[i]));
 
555 +
        impl_->entries.back().matcher_idx = idx;
 
556 +
        impl_->update_allow_for_entry(m, impl_->entries.back());
 
557 +
    }
 
558 +
    impl_->rebuild_global_allow_header();
 
559 +
}
 
560 +

 
561 +
void
 
562 +
router_base::
 
563 +
add_to_route(
 
564 +
    std::size_t idx,
 
565 +
    std::string_view verb,
 
566 +
    handlers hn)
 
567 +
{
 
568 +
    auto& m = impl_->matchers[idx];
 
569 +

 
570 +
    if(verb.empty())
 
571 +
    {
 
572 +
        // all methods
 
573 +
        for(std::size_t i = 0; i < hn.n; ++i)
 
574 +
        {
 
575 +
            impl_->entries.emplace_back(std::move(hn.p[i]));
 
576 +
            impl_->entries.back().matcher_idx = idx;
 
577 +
            impl_->update_allow_for_entry(m, impl_->entries.back());
 
578 +
        }
 
579 +
    }
 
580 +
    else
 
581 +
    {
 
582 +
        // specific method string
 
583 +
        for(std::size_t i = 0; i < hn.n; ++i)
 
584 +
        {
 
585 +
            impl_->entries.emplace_back(verb, std::move(hn.p[i]));
 
586 +
            impl_->entries.back().matcher_idx = idx;
 
587 +
            impl_->update_allow_for_entry(m, impl_->entries.back());
 
588 +
        }
 
589 +
    }
 
590 +
    impl_->rebuild_global_allow_header();
 
591 +
}
 
592 +

 
593 +
void
 
594 +
router_base::
 
595 +
finalize_pending()
 
596 +
{
 
597 +
    if(impl_)
 
598 +
        impl_->finalize_pending();
 
599 +
}
 
600 +

 
601 +
void
 
602 +
router_base::
 
603 +
set_options_handler_impl(
 
604 +
    options_handler_ptr p)
 
605 +
{
 
606 +
    impl_->options_handler_ = std::move(p);
 
607 +
}
 
608 +

 
609 +
//------------------------------------------------
 
610 +
//
 
611 +
// dispatch
 
612 +
//
 
613 +
//------------------------------------------------
 
614 +

 
615 +
route_task
 
616 +
router_base::
 
617 +
dispatch(
 
618 +
    http::method verb,
 
619 +
    urls::url_view const& url,
 
620 +
    route_params& p) const
 
621 +
{
 
622 +
    if(verb == http::method::unknown)
 
623 +
        throw_invalid_argument();
 
624 +

 
625 +
    impl_->ensure_finalized();
 
626 +

 
627 +
    // Handle OPTIONS * before normal dispatch
 
628 +
    if(verb == http::method::options &&
 
629 +
       url.encoded_path() == "*")
 
630 +
    {
 
631 +
        if(impl_->options_handler_)
 
632 +
        {
 
633 +
            return impl_->options_handler_->invoke(
 
634 +
                p, impl_->global_allow_header_);
 
635 +
        }
 
636 +
    }
 
637 +

 
638 +
    // Initialize params
 
639 +
    auto& pv = *route_params_access{p};
 
640 +
    pv.kind_ = is_plain;
 
641 +
    pv.verb_ = verb;
 
642 +
    pv.verb_str_.clear();
 
643 +
    pv.ec_.clear();
 
644 +
    pv.ep_ = nullptr;
 
645 +
    p.params.clear();
 
646 +
    pv.decoded_path_ = pct_decode_path(url.encoded_path());
 
647 +
    if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
 
648 +
    {
 
649 +
        pv.decoded_path_.push_back('/');
 
650 +
        pv.addedSlash_ = true;
 
651 +
    }
 
652 +
    else
 
653 +
    {
 
654 +
        pv.addedSlash_ = false;
 
655 +
    }
 
656 +
    p.base_path = { pv.decoded_path_.data(), 0 };
 
657 +
    auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
 
658 +
    p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
 
659 +

 
660 +
    return impl_->dispatch_loop(p, verb == http::method::options);
 
661 +
}
 
662 +

 
663 +
route_task
 
664 +
router_base::
 
665 +
dispatch(
 
666 +
    std::string_view verb,
 
667 +
    urls::url_view const& url,
 
668 +
    route_params& p) const
 
669 +
{
 
670 +
    if(verb.empty())
 
671 +
        throw_invalid_argument();
 
672 +

 
673 +
    impl_->ensure_finalized();
 
674 +

 
675 +
    auto const method = http::string_to_method(verb);
 
676 +
    bool const is_options = (method == http::method::options);
 
677 +

 
678 +
    // Handle OPTIONS * before normal dispatch
 
679 +
    if(is_options && url.encoded_path() == "*")
 
680 +
    {
 
681 +
        if(impl_->options_handler_)
 
682 +
        {
 
683 +
            return impl_->options_handler_->invoke(
 
684 +
                p, impl_->global_allow_header_);
 
685 +
        }
 
686 +
    }
 
687 +

 
688 +
    // Initialize params
 
689 +
    auto& pv = *route_params_access{p};
 
690 +
    pv.kind_ = is_plain;
 
691 +
    pv.verb_ = method;
 
692 +
    if(pv.verb_ == http::method::unknown)
 
693 +
        pv.verb_str_ = verb;
 
694 +
    else
 
695 +
        pv.verb_str_.clear();
 
696 +
    pv.ec_.clear();
 
697 +
    pv.ep_ = nullptr;
 
698 +
    p.params.clear();
 
699 +
    pv.decoded_path_ = pct_decode_path(url.encoded_path());
 
700 +
    if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
 
701 +
    {
 
702 +
        pv.decoded_path_.push_back('/');
 
703 +
        pv.addedSlash_ = true;
 
704 +
    }
 
705 +
    else
 
706 +
    {
 
707 +
        pv.addedSlash_ = false;
 
708 +
    }
 
709 +
    p.base_path = { pv.decoded_path_.data(), 0 };
 
710 +
    auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
 
711 +
    p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
 
712 +

 
713 +
    return impl_->dispatch_loop(p, is_options);
 
714 +
}
 
715 +

 
716 +
} // detail
 
717 +
} // http
 
718 +
} // boost