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