GCC Code Coverage Report


Directory: libs/beast2/
File: src/server/detail/any_router.cpp
Date: 2025-11-13 15:50:44
Exec Total Coverage
Lines: 77 147 52.4%
Functions: 10 17 58.8%
Branches: 37 126 29.4%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/beast2
8 //
9
10 #include "src/server/route_rule.hpp"
11 #include <boost/beast2/server/basic_router.hpp>
12 #include <boost/beast2/server/route_handler.hpp>
13 #include <boost/beast2/server/detail/any_router.hpp>
14 #include <boost/beast2/error.hpp>
15 #include <boost/beast2/detail/except.hpp>
16 #include <boost/url/grammar/hexdig_chars.hpp>
17 #include <boost/assert.hpp>
18 #include <atomic>
19 #include <string>
20 #include <vector>
21
22 namespace boost {
23 namespace beast2 {
24 namespace detail {
25
26 18 any_router::any_handler::~any_handler() = default;
27
28 //------------------------------------------------
29
30 namespace {
31
32 // decode all percent escapes
33 std::string
34 9 pct_decode(
35 urls::pct_string_view s)
36 {
37
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
18 return core::string_view(s); // for now
38 }
39
40 // decode all percent escapes except slashes '/' and '\'
41 std::string
42 7 pct_decode_path(
43 urls::pct_string_view s)
44 {
45 7 std::string result;
46 7 core::string_view sv(s);
47
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 result.reserve(s.size());
48 7 auto it = sv.data();
49 7 auto const end = it + sv.size();
50 for(;;)
51 {
52
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 16 times.
23 if(it == end)
53 14 return result;
54
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if(*it != '%')
55 {
56
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 result.push_back(*it++);
57 16 continue;
58 }
59 if(++it == end)
60 break;
61 auto d0 = urls::grammar::hexdig_value(*it++);
62 if( d0 < 0 ||
63 it == end)
64 break;
65 auto d1 = urls::grammar::hexdig_value(*it++);
66 if(d1 < 0)
67 break;
68 char c = d0 * 4 + d1;
69 if( c != '/' &&
70 c != '\\')
71 {
72 result.push_back(c);
73 continue;
74 }
75 result.append(it - 3, 3);
76 16 }
77 detail::throw_invalid_argument(
78 "bad percent encoding");
79 }
80
81 struct match_result
82 {
83 std::size_t n = 0; // chars moved from path to base_path
84 };
85
86 } // (anon)
87
88 //------------------------------------------------
89
90 struct any_router::layer
91 {
92 std::string pat_; // prefix to match
93 handler_ptr handler_;
94 bool end_; // if an exact match is required
95 //bool ignore_case = false;
96 //bool strict = false; // trailing slashes are significant
97 // route r;
98 path_rule_t::value_type pv_;
99
100 9 layer(
101 core::string_view pat,
102 handler_ptr handler,
103 bool end)
104
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
9 : pat_(pct_decode(pat))
105 9 , handler_(std::move(handler))
106 9 , end_(end)
107
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
18 , pv_(grammar::parse(
108 9 pat, path_rule).value())
109 {
110 9 }
111
112 /** Return true if path matches this layer
113
114 Caller is responsible for selectively percent
115 decoding the path. Slashes should not be decoded.
116 */
117 bool
118 8 match(
119 core::string_view path,
120 match_result& mr,
121 req_info const& ri) const
122 {
123
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 BOOST_ASSERT(! path.empty());
124 8 auto it = path.data();
125 8 auto const end = it + path.size();
126 8 auto pit = pv_.segs.begin();
127 8 auto const pend = pv_.segs.end();
128
6/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
✓ Branch 3 taken 8 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 7 times.
15 while(it != end && pit != pend)
129 {
130
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 7 times.
16 if(! core::string_view(
131 8 it, end).starts_with(pit->prefix))
132 1 return false;
133 7 it += pit->prefix.size();
134 7 ++pit;
135 }
136
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
7 if( end_ && (
137 it != end ||
138 pit != pend))
139 return false;
140
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
14 if( ! end_ &&
141 7 pit != pend)
142 return false;
143 7 mr.n = it - path.data();
144 21 ri.base_path = {
145 7 ri.base_path.data(),
146 7 ri.base_path.size() + mr.n };
147 7 ri.path.remove_prefix(mr.n);
148 7 return true;
149 }
150 };
151
152 struct any_router::impl
153 {
154 std::atomic<std::size_t> refs{1};
155 std::vector<layer> layers;
156 req_info(*get_req_info)(void*);
157 };
158
159 //------------------------------------------------
160
161 7 any_router::
162 7 ~any_router()
163 {
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if(! impl_)
165 return;
166
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 if(--impl_->refs == 0)
167
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 delete impl_;
168 7 }
169
170 any_router::
171 any_router(any_router&& other) noexcept
172 :impl_(other.impl_)
173 {
174 other.impl_ = nullptr;
175 }
176
177 any_router::
178 any_router(any_router const& other) noexcept
179 {
180 impl_ = other.impl_;
181 ++impl_->refs;
182 }
183
184 any_router&
185 any_router::
186 operator=(any_router&& other) noexcept
187 {
188 auto p = impl_;
189 impl_ = other.impl_;
190 other.impl_ = nullptr;
191 if(p && --p->refs == 0)
192 delete p;
193 return *this;
194 }
195
196 any_router&
197 any_router::
198 operator=(any_router const& other) noexcept
199 {
200 auto p = impl_;
201 impl_ = other.impl_;
202 ++impl_->refs;
203 if(p && --p->refs == 0)
204 delete p;
205 return *this;
206 }
207
208 7 any_router::
209 any_router(
210 7 req_info(*get_req_info)(void*))
211 7 : impl_(new impl)
212 {
213 7 impl_->get_req_info = get_req_info;
214 7 }
215
216 std::size_t
217 any_router::
218 count() const noexcept
219 {
220 std::size_t n = 1;
221 for(auto const& e : impl_->layers)
222 n += e.handler_->count();
223 return n;
224 }
225
226 // top-level dispatch gets called first
227 route_result
228 7 any_router::
229 dispatch_impl(
230 http_proto::method verb,
231 urls::url_view const& url,
232 void* req, void*res, route_state& st) const
233 {
234 7 st = {};
235 7 st.verb = verb;
236 7 st.verb_str = {};
237 // VFALCO use reusing-StringToken
238 st.decoded_path =
239
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 pct_decode_path(url.encoded_path());
240
241
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 auto ri = impl_->get_req_info(req);
242 7 ri.base_path = { st.decoded_path.data(), 0 };
243 7 ri.path = st.decoded_path;
244
245
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
14 return dispatch_impl(req, res, st);
246 }
247
248 // recursive dispatch
249 route_result
250 7 any_router::
251 dispatch_impl(
252 void* req, void* res, route_state& st) const
253 {
254
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 auto ri = impl_->get_req_info(req);
255 //system::error_code ec;
256
257
1/2
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
8 for(auto const& e : impl_->layers)
258 {
259 8 match_result mr;
260
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
8 if(e.match(ri.path, mr, ri))
261 {
262
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 auto rv = e.handler_->invoke(req, res, st, nullptr);
263
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
7 if( rv.failed() ||
264 rv == route::send ||
265
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
7 rv == route::done ||
266
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 rv == route::close
267 // || rv == route::detach // VFALCO this would be best
268 )
269 7 return rv;
270 if(rv == route::detach)
271 {
272 // do detach
273 return rv;
274 }
275 // must be a non-success error code
276 if(&rv.category() != &detail::route_cat)
277 detail::throw_invalid_argument();
278
279 }
280 }
281
282 return route::next;
283
284 #if 0
285 route_result rv;
286 system::error_code ec;
287 auto ri = impl_->get_req_info(req);
288 std::string path_str = std::string(ri.path->buffer());
289
290 auto it = impl_->layers.begin();
291 auto const end = impl_->layers.end();
292
293 if(st.resume != 0)
294 {
295 // st.resume is how many we have to skip
296 while(it != end)
297 {
298 auto const n = it->handler->count();
299 if(st.pos + n <= st.resume)
300 {
301 // skip route and children
302 st.pos += n;
303 ++it;
304 continue;
305 }
306
307 ++st.pos;
308
309 if(st.pos == st.resume)
310 {
311 st.resume = 0;
312 return st.ec;
313 }
314 break;
315 }
316 }
317
318 for(;it != end;++it)
319 {
320 // 1-based index of last route we checked
321 ++st.pos;
322
323 if( it->method != http_proto::method::unknown &&
324 ri.method != it->method)
325 continue;
326 if(! it->exact_prefix.empty())
327 {
328 if(it->prefix)
329 {
330 // only the prefix has to match
331 if(! core::string_view(*ri.suffix_path).starts_with(
332 core::string_view(it->exact_prefix)))
333 continue;
334 }
335 else
336 {
337 // full match
338 if( core::string_view(*ri.suffix_path) !=
339 core::string_view(it->exact_prefix))
340 continue;
341 }
342 }
343
344 auto const base_path0 = *ri.base_path;
345 auto const suffix_path0 = *ri.suffix_path;
346 ri.base_path->append(
347 ri.suffix_path->data(), it->exact_prefix.size());
348 ri.suffix_path->erase(0, it->exact_prefix.size());
349 // invoke the handler
350 rv = it->handler->invoke(req, res, st, nullptr);
351 *ri.base_path = base_path0;
352 *ri.suffix_path = suffix_path0;
353
354 if(rv == route::next)
355 continue;
356 if(&rv.category() != &detail::route_cat)
357 {
358 // must indicate failure
359 if(! ec.failed())
360 detail::throw_invalid_argument();
361 ec = rv;
362 goto do_error;
363 }
364 if(rv == route::detach)
365 {
366 // VFALCO this statement is broken, because a foreign
367 // thread could be resuming and race with st.resume
368 if( st.resume == 0)
369 st.resume = st.pos;
370 return rv;
371 }
372 BOOST_ASSERT(
373 rv == route::send ||
374 rv == route::close |
375 rv == route::done);
376 return rv;
377 }
378 return route::next;
379
380 do_error:
381 while(++it != end)
382 {
383 // invoke error handlers
384 rv = it->handler->invoke(req, res, st, &ec);
385 if(&ec.category() == &detail::route_cat)
386 {
387 // can't change ec to a route result
388 detail::throw_invalid_argument();
389 }
390 if(! ec.failed())
391 {
392 // can't change ec to success
393 detail::throw_invalid_argument();
394 }
395 if(rv == route::next)
396 continue;
397 if( rv == route::send ||
398 rv == route::close)
399 return rv;
400 if(rv == route::done)
401 {
402 // can't complete the response in handler
403 detail::throw_invalid_argument();
404 }
405 if(rv == route::detach)
406 {
407 // can't detach in error handler
408 detail::throw_invalid_argument();
409 }
410 // can't get here
411 detail::throw_logic_error();
412 }
413 return ec;
414 #endif
415 }
416
417 auto
418 any_router::
419 resume(
420 void* req, void* res, route_state& st,
421 route_result const& ec) const ->
422 route_result
423 {
424 BOOST_ASSERT(st.resume > 0);
425 if( ec == route::send ||
426 ec == route::close ||
427 ec == route::done)
428 return ec;
429 if(ec == route::detach)
430 {
431 // can't detach on resume
432 detail::throw_invalid_argument();
433 }
434 if(&ec.category() != &detail::route_cat)
435 {
436 // must indicate failure
437 if(! ec.failed())
438 detail::throw_invalid_argument();
439 }
440 st.ec = ec;
441 st.pos = 0;
442 return dispatch_impl(req, res, st);
443 }
444
445 void
446 9 any_router::
447 append(
448 bool end,
449 http_proto::method method,
450 core::string_view pat,
451 handler_ptr h)
452 {
453 // delete the last layer if it is empty, to handle the case where
454 // the user calls route() without actually adding anything after.
455 /*
456 if( ! impl_->layers.empty() &&
457 ! impl_->layers.back().handler)
458 {
459 --impl_->size;
460 impl_->layers.pop_back();
461 }*/
462 (void)method;
463 9 impl_->layers.emplace_back(
464 9 pat, std::move(h), end);
465 9 }
466
467 void
468 any_router::
469 append(
470 core::string_view,
471 any_router&)
472 {
473 }
474
475 } // detail
476 } // beast2
477 } // boost
478