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