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/any_router.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/url/grammar/ci_string.hpp>
 
15 -
#include <boost/url/grammar/hexdig_chars.hpp>
 
16 -
#include "src/server/detail/pct_decode.hpp"
 
17 -

 
18 -
#include <algorithm>
 
19 -

 
20 -
namespace boost {
 
21 -
namespace http {
 
22 -
namespace detail {
 
23 -

 
24 -
//------------------------------------------------
 
25 -
//
 
26 -
// impl helpers
 
27 -
//
 
28 -
//------------------------------------------------
 
29 -

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

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

 
93 -
router_base::opt_flags
 
94 -
router_base::impl::
 
95 -
compute_effective_opts(
 
96 -
    opt_flags parent,
 
97 -
    opt_flags child)
 
98 -
{
 
99 -
    opt_flags result = parent;
 
100 -

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

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

 
113 -
    return result;
 
114 -
}
 
115 -

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

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

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

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

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

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

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

 
194 -
//------------------------------------------------
 
195 -
//
 
196 -
// dispatch
 
197 -
//
 
198 -
//------------------------------------------------
 
199 -

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

 
206 -
    std::size_t last_matched = SIZE_MAX;
 
207 -
    std::uint32_t current_depth = 0;
 
208 -

 
209 -
    std::uint64_t options_methods = 0;
 
210 -
    std::vector<std::string> options_custom_verbs;
 
211 -

 
212 -
    std::size_t path_stack[router_base::max_path_depth];
 
213 -
    path_stack[0] = 0;
 
214 -

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

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

 
225 -
        bool ancestors_ok = true;
 
226 -

 
227 -
        std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
 
228 -

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

 
235 -
            bool is_needed_ancestor = (cm.depth_ < target_depth) &&
 
236 -
                (matched_at_depth[cm.depth_] == SIZE_MAX);
 
237 -
            bool is_self = (check_idx == e.matcher_idx);
 
238 -

 
239 -
            if(!is_needed_ancestor && !is_self)
 
240 -
                continue;
 
241 -

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

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

 
254 -
            pv.case_sensitive = (cm.effective_opts_ & 2) != 0;
 
255 -
            pv.strict = (cm.effective_opts_ & 8) != 0;
 
256 -

 
257 -
            if(cm.depth_ < router_base::max_path_depth)
 
258 -
                path_stack[cm.depth_] = p.base_path.size();
 
259 -

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

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

 
276 -
            if(cm.depth_ < router_base::max_path_depth)
 
277 -
                matched_at_depth[cm.depth_] = check_idx;
 
278 -

 
279 -
            last_matched = check_idx;
 
280 -
            current_depth = cm.depth_ + 1;
 
281 -

 
282 -
            if(current_depth < router_base::max_path_depth)
 
283 -
                path_stack[current_depth] = p.base_path.size();
 
284 -
        }
 
285 -

 
286 -
        if(!ancestors_ok)
 
287 -
            continue;
 
288 -

 
289 -
        // Collect methods from matching end-route matchers for OPTIONS
 
290 -
        if(is_options && m.end_)
 
291 -
        {
 
292 -
            options_methods |= m.allowed_methods_;
 
293 -
            for(auto const& v : m.custom_verbs_)
 
294 -
                options_custom_verbs.push_back(v);
 
295 -
        }
 
296 -

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

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

 
310 -
        //--------------------------------------------------
 
311 -
        // Invoke handler
 
312 -
        //--------------------------------------------------
 
313 -

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

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

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

 
342 -
        if(rv.what() == route_what::done ||
 
343 -
           rv.what() == route_what::close)
 
344 -
        {
 
345 -
            co_return rv;
 
346 -
        }
 
347 -

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

 
352 -
        if(m.end_)
 
353 -
        {
 
354 -
            i = m.skip_;
 
355 -
            continue;
 
356 -
        }
 
357 -

 
358 -
        ++i;
 
359 -
    }
 
360 -

 
361 -
    if(pv.kind_ == router_base::is_exception)
 
362 -
        co_return route_error(error::unhandled_exception);
 
363 -
    if(pv.kind_ == router_base::is_error)
 
364 -
        co_return route_error(pv.ec_);
 
365 -

 
366 -
    // OPTIONS fallback
 
367 -
    if(is_options && options_methods != 0 && options_handler_)
 
368 -
    {
 
369 -
        std::string allow = build_allow_header(options_methods, options_custom_verbs);
 
370 -
        co_return co_await options_handler_->invoke(p, allow);
 
371 -
    }
 
372 -

 
373 -
    co_return route_next;
 
374 -
}
 
375 -

 
376 -
//------------------------------------------------
 
377 -
//
 
378 -
// router_base
 
379 -
//
 
380 -
//------------------------------------------------
 
381 -

 
382 -
router_base::
 
383 -
router_base(
 
384 -
    opt_flags opt)
 
385 -
    : impl_(std::make_shared<impl>(opt))
 
386 -
{
 
387 -
}
 
388 -

 
389 -
void
 
390 -
router_base::
 
391 -
add_middleware(
 
392 -
    std::string_view pattern,
 
393 -
    handlers hn)
 
394 -
{
 
395 -
    impl_->finalize_pending();
 
396 -

 
397 -
    if(pattern.empty())
 
398 -
        pattern = "/";
 
399 -

 
400 -
    auto const matcher_idx = impl_->matchers.size();
 
401 -
    impl_->matchers.emplace_back(pattern, false);
 
402 -
    auto& m = impl_->matchers.back();
 
403 -
    if(m.error())
 
404 -
        throw_invalid_argument();
 
405 -
    m.first_entry_ = impl_->entries.size();
 
406 -
    m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
 
407 -
    m.own_opts_ = impl_->opt_;
 
408 -
    m.depth_ = 0;
 
409 -

 
410 -
    for(std::size_t i = 0; i < hn.n; ++i)
 
411 -
    {
 
412 -
        impl_->entries.emplace_back(std::move(hn.p[i]));
 
413 -
        impl_->entries.back().matcher_idx = matcher_idx;
 
414 -
    }
 
415 -

 
416 -
    m.skip_ = impl_->entries.size();
 
417 -
}
 
418 -

 
419 -
void
 
420 -
router_base::
 
421 -
inline_router(
 
422 -
    std::string_view pattern,
 
423 -
    router_base&& sub)
 
424 -
{
 
425 -
    impl_->finalize_pending();
 
426 -

 
427 -
    if(!sub.impl_)
 
428 -
        return;
 
429 -

 
430 -
    sub.impl_->finalize_pending();
 
431 -

 
432 -
    if(pattern.empty())
 
433 -
        pattern = "/";
 
434 -

 
435 -
    // Create parent matcher for the mount point
 
436 -
    auto const parent_matcher_idx = impl_->matchers.size();
 
437 -
    impl_->matchers.emplace_back(pattern, false);
 
438 -
    auto& parent_m = impl_->matchers.back();
 
439 -
    if(parent_m.error())
 
440 -
        throw_invalid_argument();
 
441 -
    parent_m.first_entry_ = impl_->entries.size();
 
442 -

 
443 -
    auto parent_eff = impl::compute_effective_opts(0, impl_->opt_);
 
444 -
    parent_m.effective_opts_ = parent_eff;
 
445 -
    parent_m.own_opts_ = impl_->opt_;
 
446 -
    parent_m.depth_ = 0;
 
447 -

 
448 -
    // Check nesting depth
 
449 -
    std::size_t max_sub_depth = 0;
 
450 -
    for(auto const& sm : sub.impl_->matchers)
 
451 -
        max_sub_depth = (std::max)(max_sub_depth,
 
452 -
            static_cast<std::size_t>(sm.depth_));
 
453 -
    if(max_sub_depth + 1 >= max_path_depth)
 
454 -
        throw_length_error(
 
455 -
            "router nesting depth exceeds max_path_depth");
 
456 -

 
457 -
    // Compute offsets for re-indexing
 
458 -
    auto const matcher_offset = impl_->matchers.size();
 
459 -
    auto const entry_offset = impl_->entries.size();
 
460 -

 
461 -
    // Recompute effective_opts for inlined matchers using depth stack
 
462 -
    auto sub_root_eff = impl::compute_effective_opts(
 
463 -
        parent_eff, sub.impl_->opt_);
 
464 -
    opt_flags eff_stack[max_path_depth];
 
465 -
    eff_stack[0] = sub_root_eff;
 
466 -

 
467 -
    // Inline sub's matchers
 
468 -
    for(auto& sm : sub.impl_->matchers)
 
469 -
    {
 
470 -
        auto d = sm.depth_;
 
471 -
        opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff;
 
472 -
        eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_);
 
473 -
        sm.effective_opts_ = eff_stack[d];
 
474 -
        sm.depth_ += 1;  // increase by 1 (parent is at depth 0)
 
475 -
        sm.first_entry_ += entry_offset;
 
476 -
        sm.skip_ += entry_offset;
 
477 -
        impl_->matchers.push_back(std::move(sm));
 
478 -
    }
 
479 -

 
480 -
    // Inline sub's entries
 
481 -
    for(auto& se : sub.impl_->entries)
 
482 -
    {
 
483 -
        se.matcher_idx += matcher_offset;
 
484 -
        impl_->entries.push_back(std::move(se));
 
485 -
    }
 
486 -

 
487 -
    // Set parent matcher's skip
 
488 -
    // Need to re-fetch since vector may have reallocated
 
489 -
    impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size();
 
490 -

 
491 -
    // Merge global methods
 
492 -
    impl_->global_methods_ |= sub.impl_->global_methods_;
 
493 -
    for(auto& v : sub.impl_->global_custom_verbs_)
 
494 -
        impl_->global_custom_verbs_.push_back(std::move(v));
 
495 -
    impl_->rebuild_global_allow_header();
 
496 -

 
497 -
    // Move options handler if sub has one and parent doesn't
 
498 -
    if(sub.impl_->options_handler_ && !impl_->options_handler_)
 
499 -
        impl_->options_handler_ = std::move(sub.impl_->options_handler_);
 
500 -

 
501 -
    sub.impl_.reset();
 
502 -
}
 
503 -

 
504 -
std::size_t
 
505 -
router_base::
 
506 -
new_route(
 
507 -
    std::string_view pattern)
 
508 -
{
 
509 -
    impl_->finalize_pending();
 
510 -

 
511 -
    if(pattern.empty())
 
512 -
        throw_invalid_argument();
 
513 -

 
514 -
    auto const idx = impl_->matchers.size();
 
515 -
    impl_->matchers.emplace_back(pattern, true);
 
516 -
    auto& m = impl_->matchers.back();
 
517 -
    if(m.error())
 
518 -
        throw_invalid_argument();
 
519 -
    m.first_entry_ = impl_->entries.size();
 
520 -
    m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
 
521 -
    m.own_opts_ = impl_->opt_;
 
522 -
    m.depth_ = 0;
 
523 -

 
524 -
    impl_->pending_route_ = idx;
 
525 -
    return idx;
 
526 -
}
 
527 -

 
528 -
void
 
529 -
router_base::
 
530 -
add_to_route(
 
531 -
    std::size_t idx,
 
532 -
    http::method verb,
 
533 -
    handlers hn)
 
534 -
{
 
535 -
    if(verb == http::method::unknown)
 
536 -
        throw_invalid_argument();
 
537 -

 
538 -
    auto& m = impl_->matchers[idx];
 
539 -
    for(std::size_t i = 0; i < hn.n; ++i)
 
540 -
    {
 
541 -
        impl_->entries.emplace_back(verb, std::move(hn.p[i]));
 
542 -
        impl_->entries.back().matcher_idx = idx;
 
543 -
        impl_->update_allow_for_entry(m, impl_->entries.back());
 
544 -
    }
 
545 -
    impl_->rebuild_global_allow_header();
 
546 -
}
 
547 -

 
548 -
void
 
549 -
router_base::
 
550 -
add_to_route(
 
551 -
    std::size_t idx,
 
552 -
    std::string_view verb,
 
553 -
    handlers hn)
 
554 -
{
 
555 -
    auto& m = impl_->matchers[idx];
 
556 -

 
557 -
    if(verb.empty())
 
558 -
    {
 
559 -
        // all methods
 
560 -
        for(std::size_t i = 0; i < hn.n; ++i)
 
561 -
        {
 
562 -
            impl_->entries.emplace_back(std::move(hn.p[i]));
 
563 -
            impl_->entries.back().matcher_idx = idx;
 
564 -
            impl_->update_allow_for_entry(m, impl_->entries.back());
 
565 -
        }
 
566 -
    }
 
567 -
    else
 
568 -
    {
 
569 -
        // specific method string
 
570 -
        for(std::size_t i = 0; i < hn.n; ++i)
 
571 -
        {
 
572 -
            impl_->entries.emplace_back(verb, std::move(hn.p[i]));
 
573 -
            impl_->entries.back().matcher_idx = idx;
 
574 -
            impl_->update_allow_for_entry(m, impl_->entries.back());
 
575 -
        }
 
576 -
    }
 
577 -
    impl_->rebuild_global_allow_header();
 
578 -
}
 
579 -

 
580 -
void
 
581 -
router_base::
 
582 -
finalize_pending()
 
583 -
{
 
584 -
    if(impl_)
 
585 -
        impl_->finalize_pending();
 
586 -
}
 
587 -

 
588 -
void
 
589 -
router_base::
 
590 -
set_options_handler_impl(
 
591 -
    options_handler_ptr p)
 
592 -
{
 
593 -
    impl_->options_handler_ = std::move(p);
 
594 -
}
 
595 -

 
596 -
//------------------------------------------------
 
597 -
//
 
598 -
// dispatch
 
599 -
//
 
600 -
//------------------------------------------------
 
601 -

 
602 -
route_task
 
603 -
router_base::
 
604 -
dispatch(
 
605 -
    http::method verb,
 
606 -
    urls::url_view const& url,
 
607 -
    route_params& p) const
 
608 -
{
 
609 -
    if(verb == http::method::unknown)
 
610 -
        throw_invalid_argument();
 
611 -

 
612 -
    impl_->ensure_finalized();
 
613 -

 
614 -
    // Handle OPTIONS * before normal dispatch
 
615 -
    if(verb == http::method::options &&
 
616 -
       url.encoded_path() == "*")
 
617 -
    {
 
618 -
        if(impl_->options_handler_)
 
619 -
        {
 
620 -
            return impl_->options_handler_->invoke(
 
621 -
                p, impl_->global_allow_header_);
 
622 -
        }
 
623 -
    }
 
624 -

 
625 -
    // Initialize params
 
626 -
    auto& pv = *route_params_access{p};
 
627 -
    pv.kind_ = is_plain;
 
628 -
    pv.verb_ = verb;
 
629 -
    pv.verb_str_.clear();
 
630 -
    pv.ec_.clear();
 
631 -
    pv.ep_ = nullptr;
 
632 -
    p.params.clear();
 
633 -
    pv.decoded_path_ = pct_decode_path(url.encoded_path());
 
634 -
    if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
 
635 -
    {
 
636 -
        pv.decoded_path_.push_back('/');
 
637 -
        pv.addedSlash_ = true;
 
638 -
    }
 
639 -
    else
 
640 -
    {
 
641 -
        pv.addedSlash_ = false;
 
642 -
    }
 
643 -
    p.base_path = { pv.decoded_path_.data(), 0 };
 
644 -
    auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
 
645 -
    p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
 
646 -

 
647 -
    return impl_->dispatch_loop(p, verb == http::method::options);
 
648 -
}
 
649 -

 
650 -
route_task
 
651 -
router_base::
 
652 -
dispatch(
 
653 -
    std::string_view verb,
 
654 -
    urls::url_view const& url,
 
655 -
    route_params& p) const
 
656 -
{
 
657 -
    if(verb.empty())
 
658 -
        throw_invalid_argument();
 
659 -

 
660 -
    impl_->ensure_finalized();
 
661 -

 
662 -
    auto const method = http::string_to_method(verb);
 
663 -
    bool const is_options = (method == http::method::options);
 
664 -

 
665 -
    // Handle OPTIONS * before normal dispatch
 
666 -
    if(is_options && url.encoded_path() == "*")
 
667 -
    {
 
668 -
        if(impl_->options_handler_)
 
669 -
        {
 
670 -
            return impl_->options_handler_->invoke(
 
671 -
                p, impl_->global_allow_header_);
 
672 -
        }
 
673 -
    }
 
674 -

 
675 -
    // Initialize params
 
676 -
    auto& pv = *route_params_access{p};
 
677 -
    pv.kind_ = is_plain;
 
678 -
    pv.verb_ = method;
 
679 -
    if(pv.verb_ == http::method::unknown)
 
680 -
        pv.verb_str_ = verb;
 
681 -
    else
 
682 -
        pv.verb_str_.clear();
 
683 -
    pv.ec_.clear();
 
684 -
    pv.ep_ = nullptr;
 
685 -
    p.params.clear();
 
686 -
    pv.decoded_path_ = pct_decode_path(url.encoded_path());
 
687 -
    if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
 
688 -
    {
 
689 -
        pv.decoded_path_.push_back('/');
 
690 -
        pv.addedSlash_ = true;
 
691 -
    }
 
692 -
    else
 
693 -
    {
 
694 -
        pv.addedSlash_ = false;
 
695 -
    }
 
696 -
    p.base_path = { pv.decoded_path_.data(), 0 };
 
697 -
    auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
 
698 -
    p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
 
699 -

 
700 -
    return impl_->dispatch_loop(p, is_options);
 
701 -
}
 
702 -

 
703 -
} // detail
 
704 -
} // http
 
705 -
} // boost