91.60% Lines (218/238) 94.12% Functions (16/17)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/http 7   // Official repository: https://github.com/cppalliance/http
8   // 8   //
9   9  
10   #include "src/server/detail/any_router.hpp" 10   #include "src/server/detail/any_router.hpp"
11   #include <boost/http/server/detail/router_base.hpp> 11   #include <boost/http/server/detail/router_base.hpp>
12   #include <boost/http/detail/except.hpp> 12   #include <boost/http/detail/except.hpp>
13   #include <boost/http/error.hpp> 13   #include <boost/http/error.hpp>
14   #include <boost/url/grammar/ci_string.hpp> 14   #include <boost/url/grammar/ci_string.hpp>
15   #include <boost/url/grammar/hexdig_chars.hpp> 15   #include <boost/url/grammar/hexdig_chars.hpp>
16   #include "src/server/detail/pct_decode.hpp" 16   #include "src/server/detail/pct_decode.hpp"
17   17  
18   #include <algorithm> 18   #include <algorithm>
19   19  
20   namespace boost { 20   namespace boost {
21   namespace http { 21   namespace http {
22   namespace detail { 22   namespace detail {
23   23  
24   //------------------------------------------------ 24   //------------------------------------------------
25   // 25   //
26   // impl helpers 26   // impl helpers
27   // 27   //
28   //------------------------------------------------ 28   //------------------------------------------------
29   29  
30   std::string 30   std::string
HITCBC 31   187 router_base::impl:: 31   187 router_base::impl::
32   build_allow_header( 32   build_allow_header(
33   std::uint64_t methods, 33   std::uint64_t methods,
34   std::vector<std::string> const& custom) 34   std::vector<std::string> const& custom)
35   { 35   {
HITCBC 36   187 if(methods == ~0ULL) 36   187 if(methods == ~0ULL)
HITCBC 37   34 return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT"; 37   34 return "DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT";
38   38  
HITCBC 39   170 std::string result; 39   170 std::string result;
40   static constexpr std::pair<http::method, char const*> known[] = { 40   static constexpr std::pair<http::method, char const*> known[] = {
41   {http::method::acl, "ACL"}, 41   {http::method::acl, "ACL"},
42   {http::method::bind, "BIND"}, 42   {http::method::bind, "BIND"},
43   {http::method::checkout, "CHECKOUT"}, 43   {http::method::checkout, "CHECKOUT"},
44   {http::method::connect, "CONNECT"}, 44   {http::method::connect, "CONNECT"},
45   {http::method::copy, "COPY"}, 45   {http::method::copy, "COPY"},
46   {http::method::delete_, "DELETE"}, 46   {http::method::delete_, "DELETE"},
47   {http::method::get, "GET"}, 47   {http::method::get, "GET"},
48   {http::method::head, "HEAD"}, 48   {http::method::head, "HEAD"},
49   {http::method::link, "LINK"}, 49   {http::method::link, "LINK"},
50   {http::method::lock, "LOCK"}, 50   {http::method::lock, "LOCK"},
51   {http::method::merge, "MERGE"}, 51   {http::method::merge, "MERGE"},
52   {http::method::mkactivity, "MKACTIVITY"}, 52   {http::method::mkactivity, "MKACTIVITY"},
53   {http::method::mkcalendar, "MKCALENDAR"}, 53   {http::method::mkcalendar, "MKCALENDAR"},
54   {http::method::mkcol, "MKCOL"}, 54   {http::method::mkcol, "MKCOL"},
55   {http::method::move, "MOVE"}, 55   {http::method::move, "MOVE"},
56   {http::method::msearch, "M-SEARCH"}, 56   {http::method::msearch, "M-SEARCH"},
57   {http::method::notify, "NOTIFY"}, 57   {http::method::notify, "NOTIFY"},
58   {http::method::options, "OPTIONS"}, 58   {http::method::options, "OPTIONS"},
59   {http::method::patch, "PATCH"}, 59   {http::method::patch, "PATCH"},
60   {http::method::post, "POST"}, 60   {http::method::post, "POST"},
61   {http::method::propfind, "PROPFIND"}, 61   {http::method::propfind, "PROPFIND"},
62   {http::method::proppatch, "PROPPATCH"}, 62   {http::method::proppatch, "PROPPATCH"},
63   {http::method::purge, "PURGE"}, 63   {http::method::purge, "PURGE"},
64   {http::method::put, "PUT"}, 64   {http::method::put, "PUT"},
65   {http::method::rebind, "REBIND"}, 65   {http::method::rebind, "REBIND"},
66   {http::method::report, "REPORT"}, 66   {http::method::report, "REPORT"},
67   {http::method::search, "SEARCH"}, 67   {http::method::search, "SEARCH"},
68   {http::method::subscribe, "SUBSCRIBE"}, 68   {http::method::subscribe, "SUBSCRIBE"},
69   {http::method::trace, "TRACE"}, 69   {http::method::trace, "TRACE"},
70   {http::method::unbind, "UNBIND"}, 70   {http::method::unbind, "UNBIND"},
71   {http::method::unlink, "UNLINK"}, 71   {http::method::unlink, "UNLINK"},
72   {http::method::unlock, "UNLOCK"}, 72   {http::method::unlock, "UNLOCK"},
73   {http::method::unsubscribe, "UNSUBSCRIBE"}, 73   {http::method::unsubscribe, "UNSUBSCRIBE"},
74   }; 74   };
HITCBC 75   5780 for(auto const& [m, name] : known) 75   5780 for(auto const& [m, name] : known)
76   { 76   {
HITCBC 77   5610 if(methods & (1ULL << static_cast<unsigned>(m))) 77   5610 if(methods & (1ULL << static_cast<unsigned>(m)))
78   { 78   {
HITCBC 79   128 if(!result.empty()) 79   128 if(!result.empty())
HITCBC 80   8 result += ", "; 80   8 result += ", ";
HITCBC 81   128 result += name; 81   128 result += name;
82   } 82   }
83   } 83   }
HITCBC 84   174 for(auto const& v : custom) 84   174 for(auto const& v : custom)
85   { 85   {
HITCBC 86   4 if(!result.empty()) 86   4 if(!result.empty())
MISUBC 87   result += ", "; 87   result += ", ";
HITCBC 88   4 result += v; 88   4 result += v;
89   } 89   }
HITCBC 90   170 return result; 90   170 return result;
HITCBC 91   170 } 91   170 }
92   92  
93   router_base::opt_flags 93   router_base::opt_flags
HITCBC 94   495 router_base::impl:: 94   495 router_base::impl::
95   compute_effective_opts( 95   compute_effective_opts(
96   opt_flags parent, 96   opt_flags parent,
97   opt_flags child) 97   opt_flags child)
98   { 98   {
HITCBC 99   495 opt_flags result = parent; 99   495 opt_flags result = parent;
100   100  
101   // case_sensitive: bits 1-2 (2=true, 4=false) 101   // case_sensitive: bits 1-2 (2=true, 4=false)
HITCBC 102   495 if(child & 2) 102   495 if(child & 2)
HITCBC 103   4 result = (result & ~6) | 2; 103   4 result = (result & ~6) | 2;
HITCBC 104   491 else if(child & 4) 104   491 else if(child & 4)
HITCBC 105   4 result = (result & ~6) | 4; 105   4 result = (result & ~6) | 4;
106   106  
107   // strict: bits 3-4 (8=true, 16=false) 107   // strict: bits 3-4 (8=true, 16=false)
HITCBC 108   495 if(child & 8) 108   495 if(child & 8)
HITCBC 109   1 result = (result & ~24) | 8; 109   1 result = (result & ~24) | 8;
HITCBC 110   494 else if(child & 16) 110   494 else if(child & 16)
HITCBC 111   1 result = (result & ~24) | 16; 111   1 result = (result & ~24) | 16;
112   112  
HITCBC 113   495 return result; 113   495 return result;
114   } 114   }
115   115  
116   void 116   void
HITCBC 117   34 router_base::impl:: 117   34 router_base::impl::
118   restore_path( 118   restore_path(
119   route_params& p, 119   route_params& p,
120   std::size_t base_len) 120   std::size_t base_len)
121   { 121   {
HITCBC 122   34 auto& pv = *route_params_access{p}; 122   34 auto& pv = *route_params_access{p};
HITCBC 123   34 p.base_path = { pv.decoded_path_.data(), base_len }; 123   34 p.base_path = { pv.decoded_path_.data(), base_len };
HITCBC 124   34 auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0); 124   34 auto const path_len = pv.decoded_path_.size() - (pv.addedSlash_ ? 1 : 0);
HITCBC 125   34 if(base_len < path_len) 125   34 if(base_len < path_len)
HITCBC 126   33 p.path = { pv.decoded_path_.data() + base_len, 126   33 p.path = { pv.decoded_path_.data() + base_len,
127   path_len - base_len }; 127   path_len - base_len };
128   else 128   else
HITCBC 129   2 p.path = { pv.decoded_path_.data() + 129   2 p.path = { pv.decoded_path_.data() +
HITCBC 130   1 pv.decoded_path_.size() - 1, 1 }; // soft slash 130   1 pv.decoded_path_.size() - 1, 1 }; // soft slash
HITCBC 131   34 } 131   34 }
132   132  
133   void 133   void
HITCBC 134   68 router_base::impl:: 134   68 router_base::impl::
135   update_allow_for_entry( 135   update_allow_for_entry(
136   matcher& m, 136   matcher& m,
137   entry const& e) 137   entry const& e)
138   { 138   {
HITCBC 139   68 if(!m.end_) 139   68 if(!m.end_)
MISUBC 140   return; 140   return;
141   141  
142   // Per-matcher collection 142   // Per-matcher collection
HITCBC 143   68 if(e.all) 143   68 if(e.all)
HITCBC 144   8 m.allowed_methods_ = ~0ULL; 144   8 m.allowed_methods_ = ~0ULL;
HITCBC 145   60 else if(e.verb != http::method::unknown) 145   60 else if(e.verb != http::method::unknown)
HITCBC 146   58 m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); 146   58 m.allowed_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
HITCBC 147   2 else if(!e.verb_str.empty()) 147   2 else if(!e.verb_str.empty())
HITCBC 148   2 m.custom_verbs_.push_back(e.verb_str); 148   2 m.custom_verbs_.push_back(e.verb_str);
149   149  
150   // Rebuild per-matcher Allow header eagerly 150   // Rebuild per-matcher Allow header eagerly
HITCBC 151   68 m.allow_header_ = build_allow_header( 151   68 m.allow_header_ = build_allow_header(
HITCBC 152   68 m.allowed_methods_, m.custom_verbs_); 152   68 m.allowed_methods_, m.custom_verbs_);
153   153  
154   // Global collection (for OPTIONS *) 154   // Global collection (for OPTIONS *)
HITCBC 155   68 if(e.all) 155   68 if(e.all)
HITCBC 156   8 global_methods_ = ~0ULL; 156   8 global_methods_ = ~0ULL;
HITCBC 157   60 else if(e.verb != http::method::unknown) 157   60 else if(e.verb != http::method::unknown)
HITCBC 158   58 global_methods_ |= (1ULL << static_cast<unsigned>(e.verb)); 158   58 global_methods_ |= (1ULL << static_cast<unsigned>(e.verb));
HITCBC 159   2 else if(!e.verb_str.empty()) 159   2 else if(!e.verb_str.empty())
HITCBC 160   2 global_custom_verbs_.push_back(e.verb_str); 160   2 global_custom_verbs_.push_back(e.verb_str);
161   } 161   }
162   162  
163   void 163   void
HITCBC 164   117 router_base::impl:: 164   117 router_base::impl::
165   rebuild_global_allow_header() 165   rebuild_global_allow_header()
166   { 166   {
HITCBC 167   117 std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end()); 167   117 std::sort(global_custom_verbs_.begin(), global_custom_verbs_.end());
HITCBC 168   234 global_custom_verbs_.erase( 168   234 global_custom_verbs_.erase(
HITCBC 169   117 std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()), 169   117 std::unique(global_custom_verbs_.begin(), global_custom_verbs_.end()),
HITCBC 170   117 global_custom_verbs_.end()); 170   117 global_custom_verbs_.end());
HITCBC 171   117 global_allow_header_ = build_allow_header( 171   117 global_allow_header_ = build_allow_header(
HITCBC 172   117 global_methods_, global_custom_verbs_); 172   117 global_methods_, global_custom_verbs_);
HITCBC 173   117 } 173   117 }
174   174  
175   void 175   void
HITCBC 176   341 router_base::impl:: 176   341 router_base::impl::
177   finalize_pending() 177   finalize_pending()
178   { 178   {
HITCBC 179   341 if(pending_route_ == SIZE_MAX) 179   341 if(pending_route_ == SIZE_MAX)
HITCBC 180   277 return; 180   277 return;
HITCBC 181   64 auto& m = matchers[pending_route_]; 181   64 auto& m = matchers[pending_route_];
HITCBC 182   64 if(entries.size() == m.first_entry_) 182   64 if(entries.size() == m.first_entry_)
183   { 183   {
184   // empty route, remove it 184   // empty route, remove it
MISUBC 185   matchers.pop_back(); 185   matchers.pop_back();
186   } 186   }
187   else 187   else
188   { 188   {
HITCBC 189   64 m.skip_ = entries.size(); 189   64 m.skip_ = entries.size();
190   } 190   }
HITCBC 191   64 pending_route_ = SIZE_MAX; 191   64 pending_route_ = SIZE_MAX;
192   } 192   }
193   193  
194   //------------------------------------------------ 194   //------------------------------------------------
195   // 195   //
196   // dispatch 196   // dispatch
197   // 197   //
198   //------------------------------------------------ 198   //------------------------------------------------
199   199  
200   route_task 200   route_task
HITCBC 201   112 router_base::impl:: 201   112 router_base::impl::
202   dispatch_loop(route_params& p, bool is_options) const 202   dispatch_loop(route_params& p, bool is_options) const
203   { 203   {
204   auto& pv = *route_params_access{p}; 204   auto& pv = *route_params_access{p};
205   205  
206   std::size_t last_matched = SIZE_MAX; 206   std::size_t last_matched = SIZE_MAX;
207   std::uint32_t current_depth = 0; 207   std::uint32_t current_depth = 0;
208   208  
209   std::uint64_t options_methods = 0; 209   std::uint64_t options_methods = 0;
210   std::vector<std::string> options_custom_verbs; 210   std::vector<std::string> options_custom_verbs;
211   211  
212   std::size_t path_stack[router_base::max_path_depth]; 212   std::size_t path_stack[router_base::max_path_depth];
213   path_stack[0] = 0; 213   path_stack[0] = 0;
214   214  
215   std::size_t matched_at_depth[router_base::max_path_depth]; 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) 216   for(std::size_t d = 0; d < router_base::max_path_depth; ++d)
217   matched_at_depth[d] = SIZE_MAX; 217   matched_at_depth[d] = SIZE_MAX;
218   218  
219   for(std::size_t i = 0; i < entries.size(); ) 219   for(std::size_t i = 0; i < entries.size(); )
220   { 220   {
221   auto const& e = entries[i]; 221   auto const& e = entries[i];
222   auto const& m = matchers[e.matcher_idx]; 222   auto const& m = matchers[e.matcher_idx];
223   auto const target_depth = m.depth_; 223   auto const target_depth = m.depth_;
224   224  
225   bool ancestors_ok = true; 225   bool ancestors_ok = true;
226   226  
227   std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1; 227   std::size_t start_idx = (last_matched == SIZE_MAX) ? 0 : last_matched + 1;
228   228  
229   for(std::size_t check_idx = start_idx; 229   for(std::size_t check_idx = start_idx;
230   check_idx <= e.matcher_idx && ancestors_ok; 230   check_idx <= e.matcher_idx && ancestors_ok;
231   ++check_idx) 231   ++check_idx)
232   { 232   {
233   auto const& cm = matchers[check_idx]; 233   auto const& cm = matchers[check_idx];
234   234  
235   bool is_needed_ancestor = (cm.depth_ < target_depth) && 235   bool is_needed_ancestor = (cm.depth_ < target_depth) &&
236   (matched_at_depth[cm.depth_] == SIZE_MAX); 236   (matched_at_depth[cm.depth_] == SIZE_MAX);
237   bool is_self = (check_idx == e.matcher_idx); 237   bool is_self = (check_idx == e.matcher_idx);
238   238  
239   if(!is_needed_ancestor && !is_self) 239   if(!is_needed_ancestor && !is_self)
240   continue; 240   continue;
241   241  
242   if(cm.depth_ <= current_depth && current_depth > 0) 242   if(cm.depth_ <= current_depth && current_depth > 0)
243   { 243   {
244   restore_path(p, path_stack[cm.depth_]); 244   restore_path(p, path_stack[cm.depth_]);
245   } 245   }
246   246  
247   if(cm.end_ && pv.kind_ != router_base::is_plain) 247   if(cm.end_ && pv.kind_ != router_base::is_plain)
248   { 248   {
249   i = cm.skip_; 249   i = cm.skip_;
250   ancestors_ok = false; 250   ancestors_ok = false;
251   break; 251   break;
252   } 252   }
253   253  
254   pv.case_sensitive = (cm.effective_opts_ & 2) != 0; 254   pv.case_sensitive = (cm.effective_opts_ & 2) != 0;
255   pv.strict = (cm.effective_opts_ & 8) != 0; 255   pv.strict = (cm.effective_opts_ & 8) != 0;
256   256  
257   if(cm.depth_ < router_base::max_path_depth) 257   if(cm.depth_ < router_base::max_path_depth)
258   path_stack[cm.depth_] = p.base_path.size(); 258   path_stack[cm.depth_] = p.base_path.size();
259   259  
260   match_result mr; 260   match_result mr;
261   if(!cm(p, mr)) 261   if(!cm(p, mr))
262   { 262   {
263   for(std::size_t d = cm.depth_; d < router_base::max_path_depth; ++d) 263   for(std::size_t d = cm.depth_; d < router_base::max_path_depth; ++d)
264   matched_at_depth[d] = SIZE_MAX; 264   matched_at_depth[d] = SIZE_MAX;
265   i = cm.skip_; 265   i = cm.skip_;
266   ancestors_ok = false; 266   ancestors_ok = false;
267   break; 267   break;
268   } 268   }
269   269  
270   if(!mr.params_.empty()) 270   if(!mr.params_.empty())
271   { 271   {
272   for(auto& param : mr.params_) 272   for(auto& param : mr.params_)
273   p.params.push_back(std::move(param)); 273   p.params.push_back(std::move(param));
274   } 274   }
275   275  
276   if(cm.depth_ < router_base::max_path_depth) 276   if(cm.depth_ < router_base::max_path_depth)
277   matched_at_depth[cm.depth_] = check_idx; 277   matched_at_depth[cm.depth_] = check_idx;
278   278  
279   last_matched = check_idx; 279   last_matched = check_idx;
280   current_depth = cm.depth_ + 1; 280   current_depth = cm.depth_ + 1;
281   281  
282   if(current_depth < router_base::max_path_depth) 282   if(current_depth < router_base::max_path_depth)
283   path_stack[current_depth] = p.base_path.size(); 283   path_stack[current_depth] = p.base_path.size();
284   } 284   }
285   285  
286   if(!ancestors_ok) 286   if(!ancestors_ok)
287   continue; 287   continue;
288   288  
289   // Collect methods from matching end-route matchers for OPTIONS 289   // Collect methods from matching end-route matchers for OPTIONS
290   if(is_options && m.end_) 290   if(is_options && m.end_)
291   { 291   {
292   options_methods |= m.allowed_methods_; 292   options_methods |= m.allowed_methods_;
293   for(auto const& v : m.custom_verbs_) 293   for(auto const& v : m.custom_verbs_)
294   options_custom_verbs.push_back(v); 294   options_custom_verbs.push_back(v);
295   } 295   }
296   296  
297   if(m.end_ && !e.match_method( 297   if(m.end_ && !e.match_method(
298   const_cast<route_params&>(p))) 298   const_cast<route_params&>(p)))
299   { 299   {
300   ++i; 300   ++i;
301   continue; 301   continue;
302   } 302   }
303   303  
304   if(e.h->kind != pv.kind_) 304   if(e.h->kind != pv.kind_)
305   { 305   {
306   ++i; 306   ++i;
307   continue; 307   continue;
308   } 308   }
309   309  
310   //-------------------------------------------------- 310   //--------------------------------------------------
311   // Invoke handler 311   // Invoke handler
312   //-------------------------------------------------- 312   //--------------------------------------------------
313   313  
314   route_result rv; 314   route_result rv;
315   try 315   try
316   { 316   {
317   rv = co_await e.h->invoke( 317   rv = co_await e.h->invoke(
318   const_cast<route_params&>(p)); 318   const_cast<route_params&>(p));
319   } 319   }
320   catch(...) 320   catch(...)
321   { 321   {
322   pv.ep_ = std::current_exception(); 322   pv.ep_ = std::current_exception();
323   pv.kind_ = router_base::is_exception; 323   pv.kind_ = router_base::is_exception;
324   ++i; 324   ++i;
325   continue; 325   continue;
326   } 326   }
327   327  
328   if(rv.what() == route_what::next) 328   if(rv.what() == route_what::next)
329   { 329   {
330   ++i; 330   ++i;
331   continue; 331   continue;
332   } 332   }
333   333  
334   if(rv.what() == route_what::next_route) 334   if(rv.what() == route_what::next_route)
335   { 335   {
336   if(!m.end_) 336   if(!m.end_)
337   co_return route_error(error::invalid_route_result); 337   co_return route_error(error::invalid_route_result);
338   i = m.skip_; 338   i = m.skip_;
339   continue; 339   continue;
340   } 340   }
341   341  
342   if(rv.what() == route_what::done || 342   if(rv.what() == route_what::done ||
343   rv.what() == route_what::close) 343   rv.what() == route_what::close)
344   { 344   {
345   co_return rv; 345   co_return rv;
346   } 346   }
347   347  
348   // Error - transition to error mode 348   // Error - transition to error mode
349   pv.ec_ = rv.error(); 349   pv.ec_ = rv.error();
350   pv.kind_ = router_base::is_error; 350   pv.kind_ = router_base::is_error;
351   351  
352   if(m.end_) 352   if(m.end_)
353   { 353   {
354   i = m.skip_; 354   i = m.skip_;
355   continue; 355   continue;
356   } 356   }
357   357  
358   ++i; 358   ++i;
359   } 359   }
360   360  
361   if(pv.kind_ == router_base::is_exception) 361   if(pv.kind_ == router_base::is_exception)
362   co_return route_error(error::unhandled_exception); 362   co_return route_error(error::unhandled_exception);
363   if(pv.kind_ == router_base::is_error) 363   if(pv.kind_ == router_base::is_error)
364   co_return route_error(pv.ec_); 364   co_return route_error(pv.ec_);
365   365  
366   // OPTIONS fallback 366   // OPTIONS fallback
367   if(is_options && options_methods != 0 && options_handler_) 367   if(is_options && options_methods != 0 && options_handler_)
368   { 368   {
369   std::string allow = build_allow_header(options_methods, options_custom_verbs); 369   std::string allow = build_allow_header(options_methods, options_custom_verbs);
370   co_return co_await options_handler_->invoke(p, allow); 370   co_return co_await options_handler_->invoke(p, allow);
371   } 371   }
372   372  
373   co_return route_next; 373   co_return route_next;
HITCBC 374   224 } 374   224 }
375   375  
376   //------------------------------------------------ 376   //------------------------------------------------
377   // 377   //
378   // router_base 378   // router_base
379   // 379   //
380   //------------------------------------------------ 380   //------------------------------------------------
381   381  
HITCBC 382   168 router_base:: 382   168 router_base::
383   router_base( 383   router_base(
HITCBC 384   168 opt_flags opt) 384   168 opt_flags opt)
HITCBC 385   168 : impl_(std::make_shared<impl>(opt)) 385   168 : impl_(std::make_shared<impl>(opt))
386   { 386   {
HITCBC 387   168 } 387   168 }
388   388  
389   void 389   void
HITCBC 390   65 router_base:: 390   65 router_base::
391   add_middleware( 391   add_middleware(
392   std::string_view pattern, 392   std::string_view pattern,
393   handlers hn) 393   handlers hn)
394   { 394   {
HITCBC 395   65 impl_->finalize_pending(); 395   65 impl_->finalize_pending();
396   396  
HITCBC 397   65 if(pattern.empty()) 397   65 if(pattern.empty())
HITCBC 398   34 pattern = "/"; 398   34 pattern = "/";
399   399  
HITCBC 400   65 auto const matcher_idx = impl_->matchers.size(); 400   65 auto const matcher_idx = impl_->matchers.size();
HITCBC 401   65 impl_->matchers.emplace_back(pattern, false); 401   65 impl_->matchers.emplace_back(pattern, false);
HITCBC 402   65 auto& m = impl_->matchers.back(); 402   65 auto& m = impl_->matchers.back();
HITCBC 403   65 if(m.error()) 403   65 if(m.error())
MISUBC 404   throw_invalid_argument(); 404   throw_invalid_argument();
HITCBC 405   65 m.first_entry_ = impl_->entries.size(); 405   65 m.first_entry_ = impl_->entries.size();
HITCBC 406   65 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); 406   65 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
HITCBC 407   65 m.own_opts_ = impl_->opt_; 407   65 m.own_opts_ = impl_->opt_;
HITCBC 408   65 m.depth_ = 0; 408   65 m.depth_ = 0;
409   409  
HITCBC 410   139 for(std::size_t i = 0; i < hn.n; ++i) 410   139 for(std::size_t i = 0; i < hn.n; ++i)
411   { 411   {
HITCBC 412   74 impl_->entries.emplace_back(std::move(hn.p[i])); 412   74 impl_->entries.emplace_back(std::move(hn.p[i]));
HITCBC 413   74 impl_->entries.back().matcher_idx = matcher_idx; 413   74 impl_->entries.back().matcher_idx = matcher_idx;
414   } 414   }
415   415  
HITCBC 416   65 m.skip_ = impl_->entries.size(); 416   65 m.skip_ = impl_->entries.size();
HITCBC 417   65 } 417   65 }
418   418  
419   void 419   void
HITCBC 420   50 router_base:: 420   50 router_base::
421   inline_router( 421   inline_router(
422   std::string_view pattern, 422   std::string_view pattern,
423   router_base&& sub) 423   router_base&& sub)
424   { 424   {
HITCBC 425   50 impl_->finalize_pending(); 425   50 impl_->finalize_pending();
426   426  
HITCBC 427   50 if(!sub.impl_) 427   50 if(!sub.impl_)
MISUBC 428   return; 428   return;
429   429  
HITCBC 430   50 sub.impl_->finalize_pending(); 430   50 sub.impl_->finalize_pending();
431   431  
HITCBC 432   50 if(pattern.empty()) 432   50 if(pattern.empty())
MISUBC 433   pattern = "/"; 433   pattern = "/";
434   434  
435   // Create parent matcher for the mount point 435   // Create parent matcher for the mount point
HITCBC 436   50 auto const parent_matcher_idx = impl_->matchers.size(); 436   50 auto const parent_matcher_idx = impl_->matchers.size();
HITCBC 437   50 impl_->matchers.emplace_back(pattern, false); 437   50 impl_->matchers.emplace_back(pattern, false);
HITCBC 438   50 auto& parent_m = impl_->matchers.back(); 438   50 auto& parent_m = impl_->matchers.back();
HITCBC 439   50 if(parent_m.error()) 439   50 if(parent_m.error())
MISUBC 440   throw_invalid_argument(); 440   throw_invalid_argument();
HITCBC 441   50 parent_m.first_entry_ = impl_->entries.size(); 441   50 parent_m.first_entry_ = impl_->entries.size();
442   442  
HITCBC 443   50 auto parent_eff = impl::compute_effective_opts(0, impl_->opt_); 443   50 auto parent_eff = impl::compute_effective_opts(0, impl_->opt_);
HITCBC 444   50 parent_m.effective_opts_ = parent_eff; 444   50 parent_m.effective_opts_ = parent_eff;
HITCBC 445   50 parent_m.own_opts_ = impl_->opt_; 445   50 parent_m.own_opts_ = impl_->opt_;
HITCBC 446   50 parent_m.depth_ = 0; 446   50 parent_m.depth_ = 0;
447   447  
448   // Check nesting depth 448   // Check nesting depth
HITCBC 449   50 std::size_t max_sub_depth = 0; 449   50 std::size_t max_sub_depth = 0;
HITCBC 450   332 for(auto const& sm : sub.impl_->matchers) 450   332 for(auto const& sm : sub.impl_->matchers)
HITCBC 451   564 max_sub_depth = (std::max)(max_sub_depth, 451   564 max_sub_depth = (std::max)(max_sub_depth,
HITCBC 452   282 static_cast<std::size_t>(sm.depth_)); 452   282 static_cast<std::size_t>(sm.depth_));
HITCBC 453   50 if(max_sub_depth + 1 >= max_path_depth) 453   50 if(max_sub_depth + 1 >= max_path_depth)
HITCBC 454   1 throw_length_error( 454   1 throw_length_error(
455   "router nesting depth exceeds max_path_depth"); 455   "router nesting depth exceeds max_path_depth");
456   456  
457   // Compute offsets for re-indexing 457   // Compute offsets for re-indexing
HITCBC 458   49 auto const matcher_offset = impl_->matchers.size(); 458   49 auto const matcher_offset = impl_->matchers.size();
HITCBC 459   49 auto const entry_offset = impl_->entries.size(); 459   49 auto const entry_offset = impl_->entries.size();
460   460  
461   // Recompute effective_opts for inlined matchers using depth stack 461   // Recompute effective_opts for inlined matchers using depth stack
HITCBC 462   49 auto sub_root_eff = impl::compute_effective_opts( 462   49 auto sub_root_eff = impl::compute_effective_opts(
HITCBC 463   49 parent_eff, sub.impl_->opt_); 463   49 parent_eff, sub.impl_->opt_);
464   opt_flags eff_stack[max_path_depth]; 464   opt_flags eff_stack[max_path_depth];
HITCBC 465   49 eff_stack[0] = sub_root_eff; 465   49 eff_stack[0] = sub_root_eff;
466   466  
467   // Inline sub's matchers 467   // Inline sub's matchers
HITCBC 468   315 for(auto& sm : sub.impl_->matchers) 468   315 for(auto& sm : sub.impl_->matchers)
469   { 469   {
HITCBC 470   266 auto d = sm.depth_; 470   266 auto d = sm.depth_;
HITCBC 471   266 opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff; 471   266 opt_flags parent = (d > 0) ? eff_stack[d - 1] : parent_eff;
HITCBC 472   266 eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_); 472   266 eff_stack[d] = impl::compute_effective_opts(parent, sm.own_opts_);
HITCBC 473   266 sm.effective_opts_ = eff_stack[d]; 473   266 sm.effective_opts_ = eff_stack[d];
HITCBC 474   266 sm.depth_ += 1; // increase by 1 (parent is at depth 0) 474   266 sm.depth_ += 1; // increase by 1 (parent is at depth 0)
HITCBC 475   266 sm.first_entry_ += entry_offset; 475   266 sm.first_entry_ += entry_offset;
HITCBC 476   266 sm.skip_ += entry_offset; 476   266 sm.skip_ += entry_offset;
HITCBC 477   266 impl_->matchers.push_back(std::move(sm)); 477   266 impl_->matchers.push_back(std::move(sm));
478   } 478   }
479   479  
480   // Inline sub's entries 480   // Inline sub's entries
HITCBC 481   98 for(auto& se : sub.impl_->entries) 481   98 for(auto& se : sub.impl_->entries)
482   { 482   {
HITCBC 483   49 se.matcher_idx += matcher_offset; 483   49 se.matcher_idx += matcher_offset;
HITCBC 484   49 impl_->entries.push_back(std::move(se)); 484   49 impl_->entries.push_back(std::move(se));
485   } 485   }
486   486  
487   // Set parent matcher's skip 487   // Set parent matcher's skip
488   // Need to re-fetch since vector may have reallocated 488   // Need to re-fetch since vector may have reallocated
HITCBC 489   49 impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size(); 489   49 impl_->matchers[parent_matcher_idx].skip_ = impl_->entries.size();
490   490  
491   // Merge global methods 491   // Merge global methods
HITCBC 492   49 impl_->global_methods_ |= sub.impl_->global_methods_; 492   49 impl_->global_methods_ |= sub.impl_->global_methods_;
HITCBC 493   49 for(auto& v : sub.impl_->global_custom_verbs_) 493   49 for(auto& v : sub.impl_->global_custom_verbs_)
MISUBC 494   impl_->global_custom_verbs_.push_back(std::move(v)); 494   impl_->global_custom_verbs_.push_back(std::move(v));
HITCBC 495   49 impl_->rebuild_global_allow_header(); 495   49 impl_->rebuild_global_allow_header();
496   496  
497   // Move options handler if sub has one and parent doesn't 497   // Move options handler if sub has one and parent doesn't
HITCBC 498   49 if(sub.impl_->options_handler_ && !impl_->options_handler_) 498   49 if(sub.impl_->options_handler_ && !impl_->options_handler_)
MISUBC 499   impl_->options_handler_ = std::move(sub.impl_->options_handler_); 499   impl_->options_handler_ = std::move(sub.impl_->options_handler_);
500   500  
HITCBC 501   49 sub.impl_.reset(); 501   49 sub.impl_.reset();
502   } 502   }
503   503  
504   std::size_t 504   std::size_t
HITCBC 505   77 router_base:: 505   77 router_base::
506   new_route( 506   new_route(
507   std::string_view pattern) 507   std::string_view pattern)
508   { 508   {
HITCBC 509   77 impl_->finalize_pending(); 509   77 impl_->finalize_pending();
510   510  
HITCBC 511   77 if(pattern.empty()) 511   77 if(pattern.empty())
MISUBC 512   throw_invalid_argument(); 512   throw_invalid_argument();
513   513  
HITCBC 514   77 auto const idx = impl_->matchers.size(); 514   77 auto const idx = impl_->matchers.size();
HITCBC 515   77 impl_->matchers.emplace_back(pattern, true); 515   77 impl_->matchers.emplace_back(pattern, true);
HITCBC 516   77 auto& m = impl_->matchers.back(); 516   77 auto& m = impl_->matchers.back();
HITCBC 517   77 if(m.error()) 517   77 if(m.error())
HITCBC 518   12 throw_invalid_argument(); 518   12 throw_invalid_argument();
HITCBC 519   65 m.first_entry_ = impl_->entries.size(); 519   65 m.first_entry_ = impl_->entries.size();
HITCBC 520   65 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_); 520   65 m.effective_opts_ = impl::compute_effective_opts(0, impl_->opt_);
HITCBC 521   65 m.own_opts_ = impl_->opt_; 521   65 m.own_opts_ = impl_->opt_;
HITCBC 522   65 m.depth_ = 0; 522   65 m.depth_ = 0;
523   523  
HITCBC 524   65 impl_->pending_route_ = idx; 524   65 impl_->pending_route_ = idx;
HITCBC 525   65 return idx; 525   65 return idx;
526   } 526   }
527   527  
528   void 528   void
HITCBC 529   58 router_base:: 529   58 router_base::
530   add_to_route( 530   add_to_route(
531   std::size_t idx, 531   std::size_t idx,
532   http::method verb, 532   http::method verb,
533   handlers hn) 533   handlers hn)
534   { 534   {
HITCBC 535   58 if(verb == http::method::unknown) 535   58 if(verb == http::method::unknown)
MISUBC 536   throw_invalid_argument(); 536   throw_invalid_argument();
537   537  
HITCBC 538   58 auto& m = impl_->matchers[idx]; 538   58 auto& m = impl_->matchers[idx];
HITCBC 539   116 for(std::size_t i = 0; i < hn.n; ++i) 539   116 for(std::size_t i = 0; i < hn.n; ++i)
540   { 540   {
HITCBC 541   58 impl_->entries.emplace_back(verb, std::move(hn.p[i])); 541   58 impl_->entries.emplace_back(verb, std::move(hn.p[i]));
HITCBC 542   58 impl_->entries.back().matcher_idx = idx; 542   58 impl_->entries.back().matcher_idx = idx;
HITCBC 543   58 impl_->update_allow_for_entry(m, impl_->entries.back()); 543   58 impl_->update_allow_for_entry(m, impl_->entries.back());
544   } 544   }
HITCBC 545   58 impl_->rebuild_global_allow_header(); 545   58 impl_->rebuild_global_allow_header();
HITCBC 546   58 } 546   58 }
547   547  
548   void 548   void
HITCBC 549   10 router_base:: 549   10 router_base::
550   add_to_route( 550   add_to_route(
551   std::size_t idx, 551   std::size_t idx,
552   std::string_view verb, 552   std::string_view verb,
553   handlers hn) 553   handlers hn)
554   { 554   {
HITCBC 555   10 auto& m = impl_->matchers[idx]; 555   10 auto& m = impl_->matchers[idx];
556   556  
HITCBC 557   10 if(verb.empty()) 557   10 if(verb.empty())
558   { 558   {
559   // all methods 559   // all methods
HITCBC 560   16 for(std::size_t i = 0; i < hn.n; ++i) 560   16 for(std::size_t i = 0; i < hn.n; ++i)
561   { 561   {
HITCBC 562   8 impl_->entries.emplace_back(std::move(hn.p[i])); 562   8 impl_->entries.emplace_back(std::move(hn.p[i]));
HITCBC 563   8 impl_->entries.back().matcher_idx = idx; 563   8 impl_->entries.back().matcher_idx = idx;
HITCBC 564   8 impl_->update_allow_for_entry(m, impl_->entries.back()); 564   8 impl_->update_allow_for_entry(m, impl_->entries.back());
565   } 565   }
566   } 566   }
567   else 567   else
568   { 568   {
569   // specific method string 569   // specific method string
HITCBC 570   4 for(std::size_t i = 0; i < hn.n; ++i) 570   4 for(std::size_t i = 0; i < hn.n; ++i)
571   { 571   {
HITCBC 572   2 impl_->entries.emplace_back(verb, std::move(hn.p[i])); 572   2 impl_->entries.emplace_back(verb, std::move(hn.p[i]));
HITCBC 573   2 impl_->entries.back().matcher_idx = idx; 573   2 impl_->entries.back().matcher_idx = idx;
HITCBC 574   2 impl_->update_allow_for_entry(m, impl_->entries.back()); 574   2 impl_->update_allow_for_entry(m, impl_->entries.back());
575   } 575   }
576   } 576   }
HITCBC 577   10 impl_->rebuild_global_allow_header(); 577   10 impl_->rebuild_global_allow_header();
HITCBC 578   10 } 578   10 }
579   579  
580   void 580   void
MISUBC 581   router_base:: 581   router_base::
582   finalize_pending() 582   finalize_pending()
583   { 583   {
MISUBC 584   if(impl_) 584   if(impl_)
MISUBC 585   impl_->finalize_pending(); 585   impl_->finalize_pending();
MISUBC 586   } 586   }
587   587  
588   void 588   void
HITCBC 589   4 router_base:: 589   4 router_base::
590   set_options_handler_impl( 590   set_options_handler_impl(
591   options_handler_ptr p) 591   options_handler_ptr p)
592   { 592   {
HITCBC 593   4 impl_->options_handler_ = std::move(p); 593   4 impl_->options_handler_ = std::move(p);
HITCBC 594   4 } 594   4 }
595   595  
596   //------------------------------------------------ 596   //------------------------------------------------
597   // 597   //
598   // dispatch 598   // dispatch
599   // 599   //
600   //------------------------------------------------ 600   //------------------------------------------------
601   601  
602   route_task 602   route_task
HITCBC 603   109 router_base:: 603   109 router_base::
604   dispatch( 604   dispatch(
605   http::method verb, 605   http::method verb,
606   urls::url_view const& url, 606   urls::url_view const& url,
607   route_params& p) const 607   route_params& p) const
608   { 608   {
HITCBC 609   109 if(verb == http::method::unknown) 609   109 if(verb == http::method::unknown)
HITCBC 610   1 throw_invalid_argument(); 610   1 throw_invalid_argument();
611   611  
HITCBC 612   108 impl_->ensure_finalized(); 612   108 impl_->ensure_finalized();
613   613  
614   // Handle OPTIONS * before normal dispatch 614   // Handle OPTIONS * before normal dispatch
HITCBC 615   113 if(verb == http::method::options && 615   113 if(verb == http::method::options &&
HITCBC 616   113 url.encoded_path() == "*") 616   113 url.encoded_path() == "*")
617   { 617   {
HITCBC 618   1 if(impl_->options_handler_) 618   1 if(impl_->options_handler_)
619   { 619   {
HITCBC 620   1 return impl_->options_handler_->invoke( 620   1 return impl_->options_handler_->invoke(
HITCBC 621   1 p, impl_->global_allow_header_); 621   1 p, impl_->global_allow_header_);
622   } 622   }
623   } 623   }
624   624  
625   // Initialize params 625   // Initialize params
HITCBC 626   107 auto& pv = *route_params_access{p}; 626   107 auto& pv = *route_params_access{p};
HITCBC 627   107 pv.kind_ = is_plain; 627   107 pv.kind_ = is_plain;
HITCBC 628   107 pv.verb_ = verb; 628   107 pv.verb_ = verb;
HITCBC 629   107 pv.verb_str_.clear(); 629   107 pv.verb_str_.clear();
HITCBC 630   107 pv.ec_.clear(); 630   107 pv.ec_.clear();
HITCBC 631   107 pv.ep_ = nullptr; 631   107 pv.ep_ = nullptr;
HITCBC 632   107 p.params.clear(); 632   107 p.params.clear();
HITCBC 633   107 pv.decoded_path_ = pct_decode_path(url.encoded_path()); 633   107 pv.decoded_path_ = pct_decode_path(url.encoded_path());
HITCBC 634   107 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/') 634   107 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
635   { 635   {
HITCBC 636   70 pv.decoded_path_.push_back('/'); 636   70 pv.decoded_path_.push_back('/');
HITCBC 637   70 pv.addedSlash_ = true; 637   70 pv.addedSlash_ = true;
638   } 638   }
639   else 639   else
640   { 640   {
HITCBC 641   37 pv.addedSlash_ = false; 641   37 pv.addedSlash_ = false;
642   } 642   }
HITCBC 643   107 p.base_path = { pv.decoded_path_.data(), 0 }; 643   107 p.base_path = { pv.decoded_path_.data(), 0 };
HITCBC 644   107 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; 644   107 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
HITCBC 645   107 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; 645   107 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
646   646  
HITCBC 647   107 return impl_->dispatch_loop(p, verb == http::method::options); 647   107 return impl_->dispatch_loop(p, verb == http::method::options);
648   } 648   }
649   649  
650   route_task 650   route_task
HITCBC 651   6 router_base:: 651   6 router_base::
652   dispatch( 652   dispatch(
653   std::string_view verb, 653   std::string_view verb,
654   urls::url_view const& url, 654   urls::url_view const& url,
655   route_params& p) const 655   route_params& p) const
656   { 656   {
HITCBC 657   6 if(verb.empty()) 657   6 if(verb.empty())
HITCBC 658   1 throw_invalid_argument(); 658   1 throw_invalid_argument();
659   659  
HITCBC 660   5 impl_->ensure_finalized(); 660   5 impl_->ensure_finalized();
661   661  
HITCBC 662   5 auto const method = http::string_to_method(verb); 662   5 auto const method = http::string_to_method(verb);
HITCBC 663   5 bool const is_options = (method == http::method::options); 663   5 bool const is_options = (method == http::method::options);
664   664  
665   // Handle OPTIONS * before normal dispatch 665   // Handle OPTIONS * before normal dispatch
HITCBC 666   5 if(is_options && url.encoded_path() == "*") 666   5 if(is_options && url.encoded_path() == "*")
667   { 667   {
MISUBC 668   if(impl_->options_handler_) 668   if(impl_->options_handler_)
669   { 669   {
MISUBC 670   return impl_->options_handler_->invoke( 670   return impl_->options_handler_->invoke(
MISUBC 671   p, impl_->global_allow_header_); 671   p, impl_->global_allow_header_);
672   } 672   }
673   } 673   }
674   674  
675   // Initialize params 675   // Initialize params
HITCBC 676   5 auto& pv = *route_params_access{p}; 676   5 auto& pv = *route_params_access{p};
HITCBC 677   5 pv.kind_ = is_plain; 677   5 pv.kind_ = is_plain;
HITCBC 678   5 pv.verb_ = method; 678   5 pv.verb_ = method;
HITCBC 679   5 if(pv.verb_ == http::method::unknown) 679   5 if(pv.verb_ == http::method::unknown)
HITCBC 680   4 pv.verb_str_ = verb; 680   4 pv.verb_str_ = verb;
681   else 681   else
HITCBC 682   1 pv.verb_str_.clear(); 682   1 pv.verb_str_.clear();
HITCBC 683   5 pv.ec_.clear(); 683   5 pv.ec_.clear();
HITCBC 684   5 pv.ep_ = nullptr; 684   5 pv.ep_ = nullptr;
HITCBC 685   5 p.params.clear(); 685   5 p.params.clear();
HITCBC 686   5 pv.decoded_path_ = pct_decode_path(url.encoded_path()); 686   5 pv.decoded_path_ = pct_decode_path(url.encoded_path());
HITCBC 687   5 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/') 687   5 if(pv.decoded_path_.empty() || pv.decoded_path_.back() != '/')
688   { 688   {
MISUBC 689   pv.decoded_path_.push_back('/'); 689   pv.decoded_path_.push_back('/');
MISUBC 690   pv.addedSlash_ = true; 690   pv.addedSlash_ = true;
691   } 691   }
692   else 692   else
693   { 693   {
HITCBC 694   5 pv.addedSlash_ = false; 694   5 pv.addedSlash_ = false;
695   } 695   }
HITCBC 696   5 p.base_path = { pv.decoded_path_.data(), 0 }; 696   5 p.base_path = { pv.decoded_path_.data(), 0 };
HITCBC 697   5 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0; 697   5 auto const subtract = (pv.addedSlash_ && pv.decoded_path_.size() > 1) ? 1 : 0;
HITCBC 698   5 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract }; 698   5 p.path = { pv.decoded_path_.data(), pv.decoded_path_.size() - subtract };
699   699  
HITCBC 700   5 return impl_->dispatch_loop(p, is_options); 700   5 return impl_->dispatch_loop(p, is_options);
701   } 701   }
702   702  
703   } // detail 703   } // detail
704   } // http 704   } // http
705   } // boost 705   } // boost