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