xref: /openbmc/bmcweb/http/routing.hpp (revision 10f270b4)
1 #pragma once
2 
3 #include "common.hpp"
4 #include "error_messages.hpp"
5 #include "http_request.hpp"
6 #include "http_response.hpp"
7 #include "logging.hpp"
8 #include "privileges.hpp"
9 #include "sessions.hpp"
10 #include "utility.hpp"
11 #include "websocket.hpp"
12 
13 #include <async_resp.hpp>
14 #include <boost/container/flat_map.hpp>
15 #include <boost/container/small_vector.hpp>
16 #include <boost/lexical_cast.hpp>
17 
18 #include <cerrno>
19 #include <cstdint>
20 #include <cstdlib>
21 #include <limits>
22 #include <memory>
23 #include <tuple>
24 #include <utility>
25 #include <vector>
26 
27 namespace crow
28 {
29 
30 class BaseRule
31 {
32   public:
33     BaseRule(const std::string& thisRule) : rule(thisRule)
34     {}
35 
36     virtual ~BaseRule() = default;
37 
38     virtual void validate() = 0;
39     std::unique_ptr<BaseRule> upgrade()
40     {
41         if (ruleToUpgrade)
42         {
43             return std::move(ruleToUpgrade);
44         }
45         return {};
46     }
47 
48     virtual void handle(const Request&,
49                         const std::shared_ptr<bmcweb::AsyncResp>&,
50                         const RoutingParams&) = 0;
51     virtual void handleUpgrade(const Request&, Response& res,
52                                boost::asio::ip::tcp::socket&&)
53     {
54         res.result(boost::beast::http::status::not_found);
55         res.end();
56     }
57 #ifdef BMCWEB_ENABLE_SSL
58     virtual void
59         handleUpgrade(const Request&, Response& res,
60                       boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&)
61     {
62         res.result(boost::beast::http::status::not_found);
63         res.end();
64     }
65 #endif
66 
67     size_t getMethods()
68     {
69         return methodsBitfield;
70     }
71 
72     bool checkPrivileges(const redfish::Privileges& userPrivileges)
73     {
74         // If there are no privileges assigned, assume no privileges
75         // required
76         if (privilegesSet.empty())
77         {
78             return true;
79         }
80 
81         for (const redfish::Privileges& requiredPrivileges : privilegesSet)
82         {
83             if (userPrivileges.isSupersetOf(requiredPrivileges))
84             {
85                 return true;
86             }
87         }
88         return false;
89     }
90 
91     size_t methodsBitfield{
92         1 << static_cast<size_t>(boost::beast::http::verb::get)};
93 
94     std::vector<redfish::Privileges> privilegesSet;
95 
96     std::string rule;
97     std::string nameStr;
98 
99     std::unique_ptr<BaseRule> ruleToUpgrade;
100 
101     friend class Router;
102     template <typename T>
103     friend struct RuleParameterTraits;
104 };
105 
106 namespace detail
107 {
108 namespace routing_handler_call_helper
109 {
110 template <typename T, int Pos>
111 struct CallPair
112 {
113     using type = T;
114     static const int pos = Pos;
115 };
116 
117 template <typename H1>
118 struct CallParams
119 {
120     H1& handler;
121     const RoutingParams& params;
122     const Request& req;
123     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp;
124 };
125 
126 template <typename F, int NInt, int NUint, int NDouble, int NString,
127           typename S1, typename S2>
128 struct Call
129 {};
130 
131 template <typename F, int NInt, int NUint, int NDouble, int NString,
132           typename... Args1, typename... Args2>
133 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>,
134             black_magic::S<Args2...>>
135 {
136     void operator()(F cparams)
137     {
138         using pushed = typename black_magic::S<Args2...>::template push_back<
139             CallPair<int64_t, NInt>>;
140         Call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>,
141              pushed>()(cparams);
142     }
143 };
144 
145 template <typename F, int NInt, int NUint, int NDouble, int NString,
146           typename... Args1, typename... Args2>
147 struct Call<F, NInt, NUint, NDouble, NString,
148             black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>>
149 {
150     void operator()(F cparams)
151     {
152         using pushed = typename black_magic::S<Args2...>::template push_back<
153             CallPair<uint64_t, NUint>>;
154         Call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>,
155              pushed>()(cparams);
156     }
157 };
158 
159 template <typename F, int NInt, int NUint, int NDouble, int NString,
160           typename... Args1, typename... Args2>
161 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>,
162             black_magic::S<Args2...>>
163 {
164     void operator()(F cparams)
165     {
166         using pushed = typename black_magic::S<Args2...>::template push_back<
167             CallPair<double, NDouble>>;
168         Call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>,
169              pushed>()(cparams);
170     }
171 };
172 
173 template <typename F, int NInt, int NUint, int NDouble, int NString,
174           typename... Args1, typename... Args2>
175 struct Call<F, NInt, NUint, NDouble, NString,
176             black_magic::S<std::string, Args1...>, black_magic::S<Args2...>>
177 {
178     void operator()(F cparams)
179     {
180         using pushed = typename black_magic::S<Args2...>::template push_back<
181             CallPair<std::string, NString>>;
182         Call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>,
183              pushed>()(cparams);
184     }
185 };
186 
187 template <typename F, int NInt, int NUint, int NDouble, int NString,
188           typename... Args1>
189 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>,
190             black_magic::S<Args1...>>
191 {
192     void operator()(F cparams)
193     {
194         cparams.handler(
195             cparams.req, cparams.asyncResp,
196             cparams.params.template get<typename Args1::type>(Args1::pos)...);
197     }
198 };
199 
200 template <typename Func, typename... ArgsWrapped>
201 struct Wrapped
202 {
203     template <typename... Args>
204     void set(
205         Func f,
206         typename std::enable_if<
207             !std::is_same<
208                 typename std::tuple_element<0, std::tuple<Args..., void>>::type,
209                 const Request&>::value,
210             int>::type = 0)
211     {
212         handler = [f = std::move(f)](
213                       const Request&,
214                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
215                       Args... args) { asyncResp->res.result(f(args...)); };
216     }
217 
218     template <typename Req, typename... Args>
219     struct ReqHandlerWrapper
220     {
221         ReqHandlerWrapper(Func fIn) : f(std::move(fIn))
222         {}
223 
224         void operator()(const Request& req,
225                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
226                         Args... args)
227         {
228             asyncResp->res.result(f(req, args...));
229         }
230 
231         Func f;
232     };
233 
234     template <typename... Args>
235     void set(
236         Func f,
237         typename std::enable_if<
238             std::is_same<
239                 typename std::tuple_element<0, std::tuple<Args..., void>>::type,
240                 const Request&>::value &&
241                 !std::is_same<typename std::tuple_element<
242                                   1, std::tuple<Args..., void, void>>::type,
243                               const std::shared_ptr<bmcweb::AsyncResp>&>::value,
244             int>::type = 0)
245     {
246         handler = ReqHandlerWrapper<Args...>(std::move(f));
247         /*handler = (
248             [f = std::move(f)]
249             (const Request& req, Response& res, Args... args){
250                  res.result(f(req, args...));
251                  res.end();
252             });*/
253     }
254 
255     template <typename... Args>
256     void set(
257         Func f,
258         typename std::enable_if<
259             std::is_same<
260                 typename std::tuple_element<0, std::tuple<Args..., void>>::type,
261                 const Request&>::value &&
262                 std::is_same<typename std::tuple_element<
263                                  1, std::tuple<Args..., void, void>>::type,
264                              const std::shared_ptr<bmcweb::AsyncResp>&>::value,
265             int>::type = 0)
266     {
267         handler = std::move(f);
268     }
269 
270     template <typename... Args>
271     struct HandlerTypeHelper
272     {
273         using type = std::function<void(
274             const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&,
275             Args...)>;
276         using args_type =
277             black_magic::S<typename black_magic::PromoteT<Args>...>;
278     };
279 
280     template <typename... Args>
281     struct HandlerTypeHelper<const Request&, Args...>
282     {
283         using type = std::function<void(
284             const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&,
285             Args...)>;
286         using args_type =
287             black_magic::S<typename black_magic::PromoteT<Args>...>;
288     };
289 
290     template <typename... Args>
291     struct HandlerTypeHelper<const Request&,
292                              const std::shared_ptr<bmcweb::AsyncResp>&, Args...>
293     {
294         using type = std::function<void(
295             const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&,
296             Args...)>;
297         using args_type =
298             black_magic::S<typename black_magic::PromoteT<Args>...>;
299     };
300 
301     typename HandlerTypeHelper<ArgsWrapped...>::type handler;
302 
303     void operator()(const Request& req,
304                     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
305                     const RoutingParams& params)
306     {
307         detail::routing_handler_call_helper::Call<
308             detail::routing_handler_call_helper::CallParams<decltype(handler)>,
309             0, 0, 0, 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type,
310             black_magic::S<>>()(
311             detail::routing_handler_call_helper::CallParams<decltype(handler)>{
312                 handler, params, req, asyncResp});
313     }
314 };
315 } // namespace routing_handler_call_helper
316 } // namespace detail
317 
318 class WebSocketRule : public BaseRule
319 {
320     using self_t = WebSocketRule;
321 
322   public:
323     WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn)
324     {}
325 
326     void validate() override
327     {}
328 
329     void handle(const Request&,
330                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
331                 const RoutingParams&) override
332     {
333         asyncResp->res.result(boost::beast::http::status::not_found);
334     }
335 
336     void handleUpgrade(const Request& req, Response&,
337                        boost::asio::ip::tcp::socket&& adaptor) override
338     {
339         std::shared_ptr<
340             crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>
341             myConnection = std::make_shared<
342                 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>(
343                 req, std::move(adaptor), openHandler, messageHandler,
344                 closeHandler, errorHandler);
345         myConnection->start();
346     }
347 #ifdef BMCWEB_ENABLE_SSL
348     void handleUpgrade(const Request& req, Response&,
349                        boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&
350                            adaptor) override
351     {
352         std::shared_ptr<crow::websocket::ConnectionImpl<
353             boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>
354             myConnection = std::make_shared<crow::websocket::ConnectionImpl<
355                 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>(
356                 req, std::move(adaptor), openHandler, messageHandler,
357                 closeHandler, errorHandler);
358         myConnection->start();
359     }
360 #endif
361 
362     template <typename Func>
363     self_t& onopen(Func f)
364     {
365         openHandler = f;
366         return *this;
367     }
368 
369     template <typename Func>
370     self_t& onmessage(Func f)
371     {
372         messageHandler = f;
373         return *this;
374     }
375 
376     template <typename Func>
377     self_t& onclose(Func f)
378     {
379         closeHandler = f;
380         return *this;
381     }
382 
383     template <typename Func>
384     self_t& onerror(Func f)
385     {
386         errorHandler = f;
387         return *this;
388     }
389 
390   protected:
391     std::function<void(crow::websocket::Connection&,
392                        std::shared_ptr<bmcweb::AsyncResp>)>
393         openHandler;
394     std::function<void(crow::websocket::Connection&, const std::string&, bool)>
395         messageHandler;
396     std::function<void(crow::websocket::Connection&, const std::string&)>
397         closeHandler;
398     std::function<void(crow::websocket::Connection&)> errorHandler;
399 };
400 
401 template <typename T>
402 struct RuleParameterTraits
403 {
404     using self_t = T;
405     WebSocketRule& websocket()
406     {
407         self_t* self = static_cast<self_t*>(this);
408         WebSocketRule* p = new WebSocketRule(self->rule);
409         self->ruleToUpgrade.reset(p);
410         return *p;
411     }
412 
413     self_t& name(const std::string_view name) noexcept
414     {
415         self_t* self = static_cast<self_t*>(this);
416         self->nameStr = name;
417         return *self;
418     }
419 
420     self_t& methods(boost::beast::http::verb method)
421     {
422         self_t* self = static_cast<self_t*>(this);
423         self->methodsBitfield = 1U << static_cast<size_t>(method);
424         return *self;
425     }
426 
427     template <typename... MethodArgs>
428     self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod)
429     {
430         self_t* self = static_cast<self_t*>(this);
431         methods(argsMethod...);
432         self->methodsBitfield |= 1U << static_cast<size_t>(method);
433         return *self;
434     }
435 
436     template <typename... MethodArgs>
437     self_t& privileges(std::initializer_list<const char*> l)
438     {
439         self_t* self = static_cast<self_t*>(this);
440         self->privilegesSet.emplace_back(l);
441         return *self;
442     }
443 
444     template <typename... MethodArgs>
445     self_t& privileges(const std::vector<redfish::Privileges>& p)
446     {
447         self_t* self = static_cast<self_t*>(this);
448         for (const redfish::Privileges& privilege : p)
449         {
450             self->privilegesSet.emplace_back(privilege);
451         }
452         return *self;
453     }
454 };
455 
456 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule>
457 {
458   public:
459     DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn)
460     {}
461 
462     void validate() override
463     {
464         if (!erasedHandler)
465         {
466             throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") +
467                                      "no handler for url " + rule);
468         }
469     }
470 
471     void handle(const Request& req,
472                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
473                 const RoutingParams& params) override
474     {
475         erasedHandler(req, asyncResp, params);
476     }
477 
478     template <typename Func>
479     void operator()(Func f)
480     {
481         using function_t = utility::function_traits<Func>;
482         erasedHandler =
483             wrap(std::move(f),
484                  std::make_integer_sequence<unsigned, function_t::arity>{});
485     }
486 
487     // enable_if Arg1 == request && Arg2 == Response
488     // enable_if Arg1 == request && Arg2 != response
489     // enable_if Arg1 != request
490 
491     template <typename Func, unsigned... Indices>
492     std::function<void(const Request&,
493                        const std::shared_ptr<bmcweb::AsyncResp>&,
494                        const RoutingParams&)>
495         wrap(Func f, std::integer_sequence<unsigned, Indices...>)
496     {
497         using function_t = crow::utility::function_traits<Func>;
498 
499         if (!black_magic::isParameterTagCompatible(
500                 black_magic::getParameterTag(rule.c_str()),
501                 black_magic::computeParameterTagFromArgsList<
502                     typename function_t::template arg<Indices>...>::value))
503         {
504             throw std::runtime_error("routeDynamic: Handler type is mismatched "
505                                      "with URL parameters: " +
506                                      rule);
507         }
508         auto ret = detail::routing_handler_call_helper::Wrapped<
509             Func, typename function_t::template arg<Indices>...>();
510         ret.template set<typename function_t::template arg<Indices>...>(
511             std::move(f));
512         return ret;
513     }
514 
515     template <typename Func>
516     void operator()(std::string name, Func&& f)
517     {
518         nameStr = std::move(name);
519         (*this).template operator()<Func>(std::forward(f));
520     }
521 
522   private:
523     std::function<void(const Request&,
524                        const std::shared_ptr<bmcweb::AsyncResp>&,
525                        const RoutingParams&)>
526         erasedHandler;
527 };
528 
529 template <typename... Args>
530 class TaggedRule :
531     public BaseRule,
532     public RuleParameterTraits<TaggedRule<Args...>>
533 {
534   public:
535     using self_t = TaggedRule<Args...>;
536 
537     TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn)
538     {}
539 
540     void validate() override
541     {
542         if (!handler)
543         {
544             throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") +
545                                      "no handler for url " + rule);
546         }
547     }
548 
549     template <typename Func>
550     typename std::enable_if<
551         black_magic::CallHelper<Func, black_magic::S<Args...>>::value,
552         void>::type
553         operator()(Func&& f)
554     {
555         static_assert(
556             black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
557                 black_magic::CallHelper<
558                     Func, black_magic::S<crow::Request, Args...>>::value,
559             "Handler type is mismatched with URL parameters");
560         static_assert(
561             !std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
562             "Handler function cannot have void return type; valid return "
563             "types: "
564             "string, int, crow::response, nlohmann::json");
565 
566         handler = [f = std::move(f)](
567                       const Request&,
568                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
569                       Args... args) { asyncResp->res.result(f(args...)); };
570     }
571 
572     template <typename Func>
573     typename std::enable_if<
574         !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
575             black_magic::CallHelper<
576                 Func, black_magic::S<crow::Request, Args...>>::value,
577         void>::type
578         operator()(Func&& f)
579     {
580         static_assert(
581             black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
582                 black_magic::CallHelper<
583                     Func, black_magic::S<crow::Request, Args...>>::value,
584             "Handler type is mismatched with URL parameters");
585         static_assert(
586             !std::is_same<void, decltype(f(std::declval<crow::Request>(),
587                                            std::declval<Args>()...))>::value,
588             "Handler function cannot have void return type; valid return "
589             "types: "
590             "string, int, crow::response,nlohmann::json");
591 
592         handler = [f = std::move(f)](
593                       const crow::Request& req,
594                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
595                       Args... args) { asyncResp->res.result(f(req, args...)); };
596     }
597 
598     template <typename Func>
599     typename std::enable_if<
600         !black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
601             !black_magic::CallHelper<
602                 Func, black_magic::S<crow::Request, Args...>>::value,
603         void>::type
604         operator()(Func&& f)
605     {
606         static_assert(
607             black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
608                 black_magic::CallHelper<
609                     Func, black_magic::S<crow::Request, Args...>>::value ||
610                 black_magic::CallHelper<
611                     Func, black_magic::S<crow::Request,
612                                          std::shared_ptr<bmcweb::AsyncResp>&,
613                                          Args...>>::value,
614             "Handler type is mismatched with URL parameters");
615         static_assert(
616             std::is_same<
617                 void,
618                 decltype(f(std::declval<crow::Request>(),
619                            std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(),
620                            std::declval<Args>()...))>::value,
621             "Handler function with response argument should have void "
622             "return "
623             "type");
624 
625         handler = std::move(f);
626     }
627 
628     template <typename Func>
629     void operator()(const std::string_view name, Func&& f)
630     {
631         nameStr = name;
632         (*this).template operator()<Func>(std::forward(f));
633     }
634 
635     void handle(const Request& req,
636                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
637                 const RoutingParams& params) override
638     {
639         detail::routing_handler_call_helper::Call<
640             detail::routing_handler_call_helper::CallParams<decltype(handler)>,
641             0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()(
642             detail::routing_handler_call_helper::CallParams<decltype(handler)>{
643                 handler, params, req, asyncResp});
644     }
645 
646   private:
647     std::function<void(const crow::Request&,
648                        const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>
649         handler;
650 };
651 
652 const int ruleSpecialRedirectSlash = 1;
653 
654 class Trie
655 {
656   public:
657     struct Node
658     {
659         unsigned ruleIndex{};
660         std::array<size_t, static_cast<size_t>(ParamType::MAX)>
661             paramChildrens{};
662         boost::container::flat_map<std::string, unsigned> children;
663 
664         bool isSimpleNode() const
665         {
666             return !ruleIndex && std::all_of(std::begin(paramChildrens),
667                                              std::end(paramChildrens),
668                                              [](size_t x) { return !x; });
669         }
670     };
671 
672     Trie() : nodes(1)
673     {}
674 
675   private:
676     void optimizeNode(Node* node)
677     {
678         for (size_t x : node->paramChildrens)
679         {
680             if (!x)
681             {
682                 continue;
683             }
684             Node* child = &nodes[x];
685             optimizeNode(child);
686         }
687         if (node->children.empty())
688         {
689             return;
690         }
691         bool mergeWithChild = true;
692         for (const std::pair<std::string, unsigned>& kv : node->children)
693         {
694             Node* child = &nodes[kv.second];
695             if (!child->isSimpleNode())
696             {
697                 mergeWithChild = false;
698                 break;
699             }
700         }
701         if (mergeWithChild)
702         {
703             decltype(node->children) merged;
704             for (const std::pair<std::string, unsigned>& kv : node->children)
705             {
706                 Node* child = &nodes[kv.second];
707                 for (const std::pair<std::string, unsigned>& childKv :
708                      child->children)
709                 {
710                     merged[kv.first + childKv.first] = childKv.second;
711                 }
712             }
713             node->children = std::move(merged);
714             optimizeNode(node);
715         }
716         else
717         {
718             for (const std::pair<std::string, unsigned>& kv : node->children)
719             {
720                 Node* child = &nodes[kv.second];
721                 optimizeNode(child);
722             }
723         }
724     }
725 
726     void optimize()
727     {
728         optimizeNode(head());
729     }
730 
731   public:
732     void validate()
733     {
734         optimize();
735     }
736 
737     void findRouteIndexes(const std::string& reqUrl,
738                           std::vector<unsigned>& routeIndexes,
739                           const Node* node = nullptr, unsigned pos = 0) const
740     {
741         if (node == nullptr)
742         {
743             node = head();
744         }
745         for (const std::pair<std::string, unsigned>& kv : node->children)
746         {
747             const std::string& fragment = kv.first;
748             const Node* child = &nodes[kv.second];
749             if (pos >= reqUrl.size())
750             {
751                 if (child->ruleIndex != 0 && fragment != "/")
752                 {
753                     routeIndexes.push_back(child->ruleIndex);
754                 }
755                 findRouteIndexes(reqUrl, routeIndexes, child,
756                                  static_cast<unsigned>(pos + fragment.size()));
757             }
758             else
759             {
760                 if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
761                 {
762                     findRouteIndexes(
763                         reqUrl, routeIndexes, child,
764                         static_cast<unsigned>(pos + fragment.size()));
765                 }
766             }
767         }
768     }
769 
770     std::pair<unsigned, RoutingParams>
771         find(const std::string_view reqUrl, const Node* node = nullptr,
772              size_t pos = 0, RoutingParams* params = nullptr) const
773     {
774         RoutingParams empty;
775         if (params == nullptr)
776         {
777             params = &empty;
778         }
779 
780         unsigned found{};
781         RoutingParams matchParams;
782 
783         if (node == nullptr)
784         {
785             node = head();
786         }
787         if (pos == reqUrl.size())
788         {
789             return {node->ruleIndex, *params};
790         }
791 
792         auto updateFound =
793             [&found, &matchParams](std::pair<unsigned, RoutingParams>& ret) {
794                 if (ret.first && (!found || found > ret.first))
795                 {
796                     found = ret.first;
797                     matchParams = std::move(ret.second);
798                 }
799             };
800 
801         if (node->paramChildrens[static_cast<size_t>(ParamType::INT)])
802         {
803             char c = reqUrl[pos];
804             if ((c >= '0' && c <= '9') || c == '+' || c == '-')
805             {
806                 char* eptr;
807                 errno = 0;
808                 long long int value =
809                     std::strtoll(reqUrl.data() + pos, &eptr, 10);
810                 if (errno != ERANGE && eptr != reqUrl.data() + pos)
811                 {
812                     params->intParams.push_back(value);
813                     std::pair<unsigned, RoutingParams> ret =
814                         find(reqUrl,
815                              &nodes[node->paramChildrens[static_cast<size_t>(
816                                  ParamType::INT)]],
817                              static_cast<size_t>(eptr - reqUrl.data()), params);
818                     updateFound(ret);
819                     params->intParams.pop_back();
820                 }
821             }
822         }
823 
824         if (node->paramChildrens[static_cast<size_t>(ParamType::UINT)])
825         {
826             char c = reqUrl[pos];
827             if ((c >= '0' && c <= '9') || c == '+')
828             {
829                 char* eptr;
830                 errno = 0;
831                 unsigned long long int value =
832                     std::strtoull(reqUrl.data() + pos, &eptr, 10);
833                 if (errno != ERANGE && eptr != reqUrl.data() + pos)
834                 {
835                     params->uintParams.push_back(value);
836                     std::pair<unsigned, RoutingParams> ret =
837                         find(reqUrl,
838                              &nodes[node->paramChildrens[static_cast<size_t>(
839                                  ParamType::UINT)]],
840                              static_cast<size_t>(eptr - reqUrl.data()), params);
841                     updateFound(ret);
842                     params->uintParams.pop_back();
843                 }
844             }
845         }
846 
847         if (node->paramChildrens[static_cast<size_t>(ParamType::DOUBLE)])
848         {
849             char c = reqUrl[pos];
850             if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')
851             {
852                 char* eptr;
853                 errno = 0;
854                 double value = std::strtod(reqUrl.data() + pos, &eptr);
855                 if (errno != ERANGE && eptr != reqUrl.data() + pos)
856                 {
857                     params->doubleParams.push_back(value);
858                     std::pair<unsigned, RoutingParams> ret =
859                         find(reqUrl,
860                              &nodes[node->paramChildrens[static_cast<size_t>(
861                                  ParamType::DOUBLE)]],
862                              static_cast<size_t>(eptr - reqUrl.data()), params);
863                     updateFound(ret);
864                     params->doubleParams.pop_back();
865                 }
866             }
867         }
868 
869         if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)])
870         {
871             size_t epos = pos;
872             for (; epos < reqUrl.size(); epos++)
873             {
874                 if (reqUrl[epos] == '/')
875                 {
876                     break;
877                 }
878             }
879 
880             if (epos != pos)
881             {
882                 params->stringParams.emplace_back(
883                     reqUrl.substr(pos, epos - pos));
884                 std::pair<unsigned, RoutingParams> ret =
885                     find(reqUrl,
886                          &nodes[node->paramChildrens[static_cast<size_t>(
887                              ParamType::STRING)]],
888                          epos, params);
889                 updateFound(ret);
890                 params->stringParams.pop_back();
891             }
892         }
893 
894         if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)])
895         {
896             size_t epos = reqUrl.size();
897 
898             if (epos != pos)
899             {
900                 params->stringParams.emplace_back(
901                     reqUrl.substr(pos, epos - pos));
902                 std::pair<unsigned, RoutingParams> ret =
903                     find(reqUrl,
904                          &nodes[node->paramChildrens[static_cast<size_t>(
905                              ParamType::PATH)]],
906                          epos, params);
907                 updateFound(ret);
908                 params->stringParams.pop_back();
909             }
910         }
911 
912         for (const std::pair<std::string, unsigned>& kv : node->children)
913         {
914             const std::string& fragment = kv.first;
915             const Node* child = &nodes[kv.second];
916 
917             if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
918             {
919                 std::pair<unsigned, RoutingParams> ret =
920                     find(reqUrl, child, pos + fragment.size(), params);
921                 updateFound(ret);
922             }
923         }
924 
925         return {found, matchParams};
926     }
927 
928     void add(const std::string& url, unsigned ruleIndex)
929     {
930         size_t idx = 0;
931 
932         for (unsigned i = 0; i < url.size(); i++)
933         {
934             char c = url[i];
935             if (c == '<')
936             {
937                 const static std::array<std::pair<ParamType, std::string>, 7>
938                     paramTraits = {{
939                         {ParamType::INT, "<int>"},
940                         {ParamType::UINT, "<uint>"},
941                         {ParamType::DOUBLE, "<float>"},
942                         {ParamType::DOUBLE, "<double>"},
943                         {ParamType::STRING, "<str>"},
944                         {ParamType::STRING, "<string>"},
945                         {ParamType::PATH, "<path>"},
946                     }};
947 
948                 for (const std::pair<ParamType, std::string>& x : paramTraits)
949                 {
950                     if (url.compare(i, x.second.size(), x.second) == 0)
951                     {
952                         size_t index = static_cast<size_t>(x.first);
953                         if (!nodes[idx].paramChildrens[index])
954                         {
955                             unsigned newNodeIdx = newNode();
956                             nodes[idx].paramChildrens[index] = newNodeIdx;
957                         }
958                         idx = nodes[idx].paramChildrens[index];
959                         i += static_cast<unsigned>(x.second.size());
960                         break;
961                     }
962                 }
963 
964                 i--;
965             }
966             else
967             {
968                 std::string piece(&c, 1);
969                 if (!nodes[idx].children.count(piece))
970                 {
971                     unsigned newNodeIdx = newNode();
972                     nodes[idx].children.emplace(piece, newNodeIdx);
973                 }
974                 idx = nodes[idx].children[piece];
975             }
976         }
977         if (nodes[idx].ruleIndex)
978         {
979             throw std::runtime_error("handler already exists for " + url);
980         }
981         nodes[idx].ruleIndex = ruleIndex;
982     }
983 
984   private:
985     void debugNodePrint(Node* n, size_t level)
986     {
987         for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++)
988         {
989             if (n->paramChildrens[i])
990             {
991                 BMCWEB_LOG_DEBUG << std::string(
992                     2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/;
993                 switch (static_cast<ParamType>(i))
994                 {
995                     case ParamType::INT:
996                         BMCWEB_LOG_DEBUG << "<int>";
997                         break;
998                     case ParamType::UINT:
999                         BMCWEB_LOG_DEBUG << "<uint>";
1000                         break;
1001                     case ParamType::DOUBLE:
1002                         BMCWEB_LOG_DEBUG << "<float>";
1003                         break;
1004                     case ParamType::STRING:
1005                         BMCWEB_LOG_DEBUG << "<str>";
1006                         break;
1007                     case ParamType::PATH:
1008                         BMCWEB_LOG_DEBUG << "<path>";
1009                         break;
1010                     case ParamType::MAX:
1011                         BMCWEB_LOG_DEBUG << "<ERROR>";
1012                         break;
1013                 }
1014 
1015                 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1);
1016             }
1017         }
1018         for (const std::pair<std::string, unsigned>& kv : n->children)
1019         {
1020             BMCWEB_LOG_DEBUG
1021                 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/
1022                 << kv.first;
1023             debugNodePrint(&nodes[kv.second], level + 1);
1024         }
1025     }
1026 
1027   public:
1028     void debugPrint()
1029     {
1030         debugNodePrint(head(), 0U);
1031     }
1032 
1033   private:
1034     const Node* head() const
1035     {
1036         return &nodes.front();
1037     }
1038 
1039     Node* head()
1040     {
1041         return &nodes.front();
1042     }
1043 
1044     unsigned newNode()
1045     {
1046         nodes.resize(nodes.size() + 1);
1047         return static_cast<unsigned>(nodes.size() - 1);
1048     }
1049 
1050     std::vector<Node> nodes;
1051 };
1052 
1053 class Router
1054 {
1055   public:
1056     Router() = default;
1057 
1058     DynamicRule& newRuleDynamic(const std::string& rule)
1059     {
1060         std::unique_ptr<DynamicRule> ruleObject =
1061             std::make_unique<DynamicRule>(rule);
1062         DynamicRule* ptr = ruleObject.get();
1063         allRules.emplace_back(std::move(ruleObject));
1064 
1065         return *ptr;
1066     }
1067 
1068     template <uint64_t N>
1069     typename black_magic::Arguments<N>::type::template rebind<TaggedRule>&
1070         newRuleTagged(const std::string& rule)
1071     {
1072         using RuleT = typename black_magic::Arguments<N>::type::template rebind<
1073             TaggedRule>;
1074         std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
1075         RuleT* ptr = ruleObject.get();
1076         allRules.emplace_back(std::move(ruleObject));
1077 
1078         return *ptr;
1079     }
1080 
1081     void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
1082     {
1083         if (ruleObject == nullptr)
1084         {
1085             return;
1086         }
1087         for (size_t method = 0, methodBit = 1; method < maxHttpVerbCount;
1088              method++, methodBit <<= 1)
1089         {
1090             if (ruleObject->methodsBitfield & methodBit)
1091             {
1092                 perMethods[method].rules.emplace_back(ruleObject);
1093                 perMethods[method].trie.add(
1094                     rule, static_cast<unsigned>(
1095                               perMethods[method].rules.size() - 1U));
1096                 // directory case:
1097                 //   request to `/about' url matches `/about/' rule
1098                 if (rule.size() > 2 && rule.back() == '/')
1099                 {
1100                     perMethods[method].trie.add(
1101                         rule.substr(0, rule.size() - 1),
1102                         static_cast<unsigned>(perMethods[method].rules.size() -
1103                                               1));
1104                 }
1105             }
1106         }
1107     }
1108 
1109     void validate()
1110     {
1111         for (std::unique_ptr<BaseRule>& rule : allRules)
1112         {
1113             if (rule)
1114             {
1115                 std::unique_ptr<BaseRule> upgraded = rule->upgrade();
1116                 if (upgraded)
1117                 {
1118                     rule = std::move(upgraded);
1119                 }
1120                 rule->validate();
1121                 internalAddRuleObject(rule->rule, rule.get());
1122             }
1123         }
1124         for (PerMethod& perMethod : perMethods)
1125         {
1126             perMethod.trie.validate();
1127         }
1128     }
1129 
1130     template <typename Adaptor>
1131     void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor)
1132     {
1133         if (static_cast<size_t>(req.method()) >= perMethods.size())
1134         {
1135             res.result(boost::beast::http::status::not_found);
1136             res.end();
1137             return;
1138         }
1139 
1140         PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())];
1141         Trie& trie = perMethod.trie;
1142         std::vector<BaseRule*>& rules = perMethod.rules;
1143 
1144         const std::pair<unsigned, RoutingParams>& found = trie.find(req.url);
1145         unsigned ruleIndex = found.first;
1146         if (!ruleIndex)
1147         {
1148             BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url;
1149             res.result(boost::beast::http::status::not_found);
1150             res.end();
1151             return;
1152         }
1153 
1154         if (ruleIndex >= rules.size())
1155         {
1156             throw std::runtime_error("Trie internal structure corrupted!");
1157         }
1158 
1159         if (ruleIndex == ruleSpecialRedirectSlash)
1160         {
1161             BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: "
1162                             << req.url;
1163             res.result(boost::beast::http::status::moved_permanently);
1164 
1165             // TODO absolute url building
1166             if (req.getHeaderValue("Host").empty())
1167             {
1168                 res.addHeader("Location", std::string(req.url) + "/");
1169             }
1170             else
1171             {
1172                 res.addHeader(
1173                     "Location",
1174                     req.isSecure
1175                         ? "https://"
1176                         : "http://" + std::string(req.getHeaderValue("Host")) +
1177                               std::string(req.url) + "/");
1178             }
1179             res.end();
1180             return;
1181         }
1182 
1183         if ((rules[ruleIndex]->getMethods() &
1184              (1U << static_cast<size_t>(req.method()))) == 0)
1185         {
1186             BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
1187                              << " with " << req.methodString() << "("
1188                              << static_cast<uint32_t>(req.method()) << ") / "
1189                              << rules[ruleIndex]->getMethods();
1190             res.result(boost::beast::http::status::not_found);
1191             res.end();
1192             return;
1193         }
1194 
1195         BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule
1196                          << "' " << static_cast<uint32_t>(req.method()) << " / "
1197                          << rules[ruleIndex]->getMethods();
1198 
1199         // any uncaught exceptions become 500s
1200         try
1201         {
1202             rules[ruleIndex]->handleUpgrade(req, res, std::move(adaptor));
1203         }
1204         catch (std::exception& e)
1205         {
1206             BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what();
1207             res.result(boost::beast::http::status::internal_server_error);
1208             res.end();
1209             return;
1210         }
1211         catch (...)
1212         {
1213             BMCWEB_LOG_ERROR
1214                 << "An uncaught exception occurred. The type was unknown "
1215                    "so no information was available.";
1216             res.result(boost::beast::http::status::internal_server_error);
1217             res.end();
1218             return;
1219         }
1220     }
1221 
1222     void handle(Request& req,
1223                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1224     {
1225         if (static_cast<size_t>(req.method()) >= perMethods.size())
1226         {
1227             asyncResp->res.result(boost::beast::http::status::not_found);
1228             return;
1229         }
1230         PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())];
1231         Trie& trie = perMethod.trie;
1232         std::vector<BaseRule*>& rules = perMethod.rules;
1233 
1234         const std::pair<unsigned, RoutingParams>& found = trie.find(req.url);
1235 
1236         unsigned ruleIndex = found.first;
1237 
1238         if (!ruleIndex)
1239         {
1240             // Check to see if this url exists at any verb
1241             for (const PerMethod& p : perMethods)
1242             {
1243                 const std::pair<unsigned, RoutingParams>& found2 =
1244                     p.trie.find(req.url);
1245                 if (found2.first > 0)
1246                 {
1247                     asyncResp->res.result(
1248                         boost::beast::http::status::method_not_allowed);
1249                     return;
1250                 }
1251             }
1252             BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url;
1253             asyncResp->res.result(boost::beast::http::status::not_found);
1254             return;
1255         }
1256 
1257         if (ruleIndex >= rules.size())
1258         {
1259             throw std::runtime_error("Trie internal structure corrupted!");
1260         }
1261 
1262         if (ruleIndex == ruleSpecialRedirectSlash)
1263         {
1264             BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: "
1265                             << req.url;
1266             asyncResp->res.result(
1267                 boost::beast::http::status::moved_permanently);
1268 
1269             // TODO absolute url building
1270             if (req.getHeaderValue("Host").empty())
1271             {
1272                 asyncResp->res.addHeader("Location",
1273                                          std::string(req.url) + "/");
1274             }
1275             else
1276             {
1277                 asyncResp->res.addHeader(
1278                     "Location", (req.isSecure ? "https://" : "http://") +
1279                                     std::string(req.getHeaderValue("Host")) +
1280                                     std::string(req.url) + "/");
1281             }
1282             return;
1283         }
1284 
1285         if ((rules[ruleIndex]->getMethods() &
1286              (1U << static_cast<uint32_t>(req.method()))) == 0)
1287         {
1288             BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url
1289                              << " with " << req.methodString() << "("
1290                              << static_cast<uint32_t>(req.method()) << ") / "
1291                              << rules[ruleIndex]->getMethods();
1292             asyncResp->res.result(
1293                 boost::beast::http::status::method_not_allowed);
1294             return;
1295         }
1296 
1297         BMCWEB_LOG_DEBUG << "Matched rule '" << rules[ruleIndex]->rule << "' "
1298                          << static_cast<uint32_t>(req.method()) << " / "
1299                          << rules[ruleIndex]->getMethods();
1300 
1301         if (req.session == nullptr)
1302         {
1303             rules[ruleIndex]->handle(req, asyncResp, found.second);
1304             return;
1305         }
1306 
1307         crow::connections::systemBus->async_method_call(
1308             [&req, asyncResp, &rules, ruleIndex, found](
1309                 const boost::system::error_code ec,
1310                 std::map<std::string, std::variant<bool, std::string,
1311                                                    std::vector<std::string>>>
1312                     userInfo) {
1313                 if (ec)
1314                 {
1315                     BMCWEB_LOG_ERROR << "GetUserInfo failed...";
1316                     asyncResp->res.result(
1317                         boost::beast::http::status::internal_server_error);
1318                     return;
1319                 }
1320 
1321                 const std::string* userRolePtr = nullptr;
1322                 auto userInfoIter = userInfo.find("UserPrivilege");
1323                 if (userInfoIter != userInfo.end())
1324                 {
1325                     userRolePtr =
1326                         std::get_if<std::string>(&userInfoIter->second);
1327                 }
1328 
1329                 std::string userRole{};
1330                 if (userRolePtr != nullptr)
1331                 {
1332                     userRole = *userRolePtr;
1333                     BMCWEB_LOG_DEBUG << "userName = " << req.session->username
1334                                      << " userRole = " << *userRolePtr;
1335                 }
1336 
1337                 bool* remoteUserPtr = nullptr;
1338                 auto remoteUserIter = userInfo.find("RemoteUser");
1339                 if (remoteUserIter != userInfo.end())
1340                 {
1341                     remoteUserPtr = std::get_if<bool>(&remoteUserIter->second);
1342                 }
1343                 if (remoteUserPtr == nullptr)
1344                 {
1345                     BMCWEB_LOG_ERROR
1346                         << "RemoteUser property missing or wrong type";
1347                     asyncResp->res.result(
1348                         boost::beast::http::status::internal_server_error);
1349                     return;
1350                 }
1351                 bool remoteUser = *remoteUserPtr;
1352 
1353                 bool passwordExpired = false; // default for remote user
1354                 if (!remoteUser)
1355                 {
1356                     bool* passwordExpiredPtr = nullptr;
1357                     auto passwordExpiredIter =
1358                         userInfo.find("UserPasswordExpired");
1359                     if (passwordExpiredIter != userInfo.end())
1360                     {
1361                         passwordExpiredPtr =
1362                             std::get_if<bool>(&passwordExpiredIter->second);
1363                     }
1364                     if (passwordExpiredPtr != nullptr)
1365                     {
1366                         passwordExpired = *passwordExpiredPtr;
1367                     }
1368                     else
1369                     {
1370                         BMCWEB_LOG_ERROR
1371                             << "UserPasswordExpired property is expected for"
1372                                " local user but is missing or wrong type";
1373                         asyncResp->res.result(
1374                             boost::beast::http::status::internal_server_error);
1375                         return;
1376                     }
1377                 }
1378 
1379                 // Get the userprivileges from the role
1380                 redfish::Privileges userPrivileges =
1381                     redfish::getUserPrivileges(userRole);
1382 
1383                 // Set isConfigureSelfOnly based on D-Bus results.  This
1384                 // ignores the results from both pamAuthenticateUser and the
1385                 // value from any previous use of this session.
1386                 req.session->isConfigureSelfOnly = passwordExpired;
1387 
1388                 // Modifyprivileges if isConfigureSelfOnly.
1389                 if (req.session->isConfigureSelfOnly)
1390                 {
1391                     // Remove allprivileges except ConfigureSelf
1392                     userPrivileges = userPrivileges.intersection(
1393                         redfish::Privileges{"ConfigureSelf"});
1394                     BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf";
1395                 }
1396 
1397                 if (!rules[ruleIndex]->checkPrivileges(userPrivileges))
1398                 {
1399                     asyncResp->res.result(
1400                         boost::beast::http::status::forbidden);
1401                     if (req.session->isConfigureSelfOnly)
1402                     {
1403                         redfish::messages::passwordChangeRequired(
1404                             asyncResp->res,
1405                             "/redfish/v1/AccountService/Accounts/" +
1406                                 req.session->username);
1407                     }
1408                     return;
1409                 }
1410 
1411                 req.userRole = userRole;
1412                 rules[ruleIndex]->handle(req, asyncResp, found.second);
1413             },
1414             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1415             "xyz.openbmc_project.User.Manager", "GetUserInfo",
1416             req.session->username);
1417     }
1418 
1419     void debugPrint()
1420     {
1421         for (size_t i = 0; i < perMethods.size(); i++)
1422         {
1423             BMCWEB_LOG_DEBUG << boost::beast::http::to_string(
1424                 static_cast<boost::beast::http::verb>(i));
1425             perMethods[i].trie.debugPrint();
1426         }
1427     }
1428 
1429     std::vector<const std::string*> getRoutes(const std::string& parent)
1430     {
1431         std::vector<const std::string*> ret;
1432 
1433         for (const PerMethod& pm : perMethods)
1434         {
1435             std::vector<unsigned> x;
1436             pm.trie.findRouteIndexes(parent, x);
1437             for (unsigned index : x)
1438             {
1439                 ret.push_back(&pm.rules[index]->rule);
1440             }
1441         }
1442         return ret;
1443     }
1444 
1445   private:
1446     struct PerMethod
1447     {
1448         std::vector<BaseRule*> rules;
1449         Trie trie;
1450         // rule index 0, 1 has special meaning; preallocate it to avoid
1451         // duplication.
1452         PerMethod() : rules(2)
1453         {}
1454     };
1455 
1456     const static size_t maxHttpVerbCount =
1457         static_cast<size_t>(boost::beast::http::verb::unlink);
1458 
1459     std::array<PerMethod, maxHttpVerbCount> perMethods;
1460     std::vector<std::unique_ptr<BaseRule>> allRules;
1461 };
1462 } // namespace crow
1463