Line data Source code
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 9 : 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 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 7 : result.reserve(s.size());
48 7 : auto it = sv.data();
49 7 : auto const end = it + sv.size();
50 : for(;;)
51 : {
52 23 : if(it == end)
53 14 : return result;
54 16 : if(*it != '%')
55 : {
56 16 : result.push_back(*it++);
57 16 : continue;
58 : }
59 0 : if(++it == end)
60 0 : break;
61 0 : auto d0 = urls::grammar::hexdig_value(*it++);
62 0 : if( d0 < 0 ||
63 : it == end)
64 : break;
65 0 : auto d1 = urls::grammar::hexdig_value(*it++);
66 0 : if(d1 < 0)
67 0 : break;
68 0 : char c = d0 * 4 + d1;
69 0 : if( c != '/' &&
70 : c != '\\')
71 : {
72 0 : result.push_back(c);
73 0 : continue;
74 : }
75 0 : result.append(it - 3, 3);
76 16 : }
77 0 : detail::throw_invalid_argument(
78 : "bad percent encoding");
79 0 : }
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 9 : : pat_(pct_decode(pat))
105 9 : , handler_(std::move(handler))
106 9 : , end_(end)
107 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 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 15 : while(it != end && pit != pend)
129 : {
130 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 7 : if( end_ && (
137 0 : it != end ||
138 0 : pit != pend))
139 0 : return false;
140 14 : if( ! end_ &&
141 7 : pit != pend)
142 0 : 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 7 : if(! impl_)
165 0 : return;
166 7 : if(--impl_->refs == 0)
167 7 : delete impl_;
168 7 : }
169 :
170 0 : any_router::
171 0 : any_router(any_router&& other) noexcept
172 0 : :impl_(other.impl_)
173 : {
174 0 : other.impl_ = nullptr;
175 0 : }
176 :
177 0 : any_router::
178 0 : any_router(any_router const& other) noexcept
179 : {
180 0 : impl_ = other.impl_;
181 0 : ++impl_->refs;
182 0 : }
183 :
184 : any_router&
185 0 : any_router::
186 : operator=(any_router&& other) noexcept
187 : {
188 0 : auto p = impl_;
189 0 : impl_ = other.impl_;
190 0 : other.impl_ = nullptr;
191 0 : if(p && --p->refs == 0)
192 0 : delete p;
193 0 : return *this;
194 : }
195 :
196 : any_router&
197 0 : any_router::
198 : operator=(any_router const& other) noexcept
199 : {
200 0 : auto p = impl_;
201 0 : impl_ = other.impl_;
202 0 : ++impl_->refs;
203 0 : if(p && --p->refs == 0)
204 0 : delete p;
205 0 : 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 0 : any_router::
218 : count() const noexcept
219 : {
220 0 : std::size_t n = 1;
221 0 : for(auto const& e : impl_->layers)
222 0 : n += e.handler_->count();
223 0 : 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 7 : pct_decode_path(url.encoded_path());
240 :
241 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 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 7 : auto ri = impl_->get_req_info(req);
255 : //system::error_code ec;
256 :
257 8 : for(auto const& e : impl_->layers)
258 : {
259 8 : match_result mr;
260 8 : if(e.match(ri.path, mr, ri))
261 : {
262 7 : auto rv = e.handler_->invoke(req, res, st, nullptr);
263 7 : if( rv.failed() ||
264 0 : rv == route::send ||
265 7 : rv == route::done ||
266 7 : rv == route::close
267 : // || rv == route::detach // VFALCO this would be best
268 : )
269 7 : return rv;
270 0 : if(rv == route::detach)
271 : {
272 : // do detach
273 0 : return rv;
274 : }
275 : // must be a non-success error code
276 0 : if(&rv.category() != &detail::route_cat)
277 0 : detail::throw_invalid_argument();
278 :
279 : }
280 : }
281 :
282 0 : 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 0 : any_router::
419 : resume(
420 : void* req, void* res, route_state& st,
421 : route_result const& ec) const ->
422 : route_result
423 : {
424 0 : BOOST_ASSERT(st.resume > 0);
425 0 : if( ec == route::send ||
426 0 : ec == route::close ||
427 0 : ec == route::done)
428 0 : return ec;
429 0 : if(ec == route::detach)
430 : {
431 : // can't detach on resume
432 0 : detail::throw_invalid_argument();
433 : }
434 0 : if(&ec.category() != &detail::route_cat)
435 : {
436 : // must indicate failure
437 0 : if(! ec.failed())
438 0 : detail::throw_invalid_argument();
439 : }
440 0 : st.ec = ec;
441 0 : st.pos = 0;
442 0 : 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 0 : any_router::
469 : append(
470 : core::string_view,
471 : any_router&)
472 : {
473 0 : }
474 :
475 : } // detail
476 : } // beast2
477 : } // boost
|