xref: /openbmc/bmcweb/http/routing.hpp (revision 6fde95fa)
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                 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                 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(crow::sse_socket::Connection&)> openHandler;
436     std::function<void(crow::sse_socket::Connection&)> closeHandler;
437 };
438 
439 template <typename T>
440 struct RuleParameterTraits
441 {
442     using self_t = T;
443     WebSocketRule& websocket()
444     {
445         self_t* self = static_cast<self_t*>(this);
446         WebSocketRule* p = new WebSocketRule(self->rule);
447         p->privilegesSet = self->privilegesSet;
448         self->ruleToUpgrade.reset(p);
449         return *p;
450     }
451 
452     SseSocketRule& serverSentEvent()
453     {
454         self_t* self = static_cast<self_t*>(this);
455         SseSocketRule* p = new SseSocketRule(self->rule);
456         self->ruleToUpgrade.reset(p);
457         return *p;
458     }
459 
460     self_t& methods(boost::beast::http::verb method)
461     {
462         self_t* self = static_cast<self_t*>(this);
463         std::optional<HttpVerb> verb = httpVerbFromBoost(method);
464         if (verb)
465         {
466             self->methodsBitfield = 1U << static_cast<size_t>(*verb);
467         }
468         return *self;
469     }
470 
471     template <typename... MethodArgs>
472     self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod)
473     {
474         self_t* self = static_cast<self_t*>(this);
475         methods(argsMethod...);
476         std::optional<HttpVerb> verb = httpVerbFromBoost(method);
477         if (verb)
478         {
479             self->methodsBitfield |= 1U << static_cast<size_t>(*verb);
480         }
481         return *self;
482     }
483 
484     self_t& notFound()
485     {
486         self_t* self = static_cast<self_t*>(this);
487         self->methodsBitfield = 1U << notFoundIndex;
488         return *self;
489     }
490 
491     self_t& methodNotAllowed()
492     {
493         self_t* self = static_cast<self_t*>(this);
494         self->methodsBitfield = 1U << methodNotAllowedIndex;
495         return *self;
496     }
497 
498     self_t& privileges(
499         const std::initializer_list<std::initializer_list<const char*>>& p)
500     {
501         self_t* self = static_cast<self_t*>(this);
502         for (const std::initializer_list<const char*>& privilege : p)
503         {
504             self->privilegesSet.emplace_back(privilege);
505         }
506         return *self;
507     }
508 
509     template <size_t N, typename... MethodArgs>
510     self_t& privileges(const std::array<redfish::Privileges, N>& p)
511     {
512         self_t* self = static_cast<self_t*>(this);
513         for (const redfish::Privileges& privilege : p)
514         {
515             self->privilegesSet.emplace_back(privilege);
516         }
517         return *self;
518     }
519 };
520 
521 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule>
522 {
523   public:
524     explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) {}
525 
526     void validate() override
527     {
528         if (!erasedHandler)
529         {
530             throw std::runtime_error("no handler for url " + rule);
531         }
532     }
533 
534     void handle(const Request& req,
535                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
536                 const std::vector<std::string>& params) override
537     {
538         erasedHandler(req, asyncResp, params);
539     }
540 
541     template <typename Func>
542     void operator()(Func f)
543     {
544         using boost::callable_traits::args_t;
545         constexpr size_t arity = std::tuple_size<args_t<Func>>::value;
546         constexpr auto is = std::make_integer_sequence<unsigned, arity>{};
547         erasedHandler = wrap(std::move(f), is);
548     }
549 
550     // enable_if Arg1 == request && Arg2 == Response
551     // enable_if Arg1 == request && Arg2 != response
552     // enable_if Arg1 != request
553 
554     template <typename Func, unsigned... Indices>
555     std::function<void(const Request&,
556                        const std::shared_ptr<bmcweb::AsyncResp>&,
557                        const std::vector<std::string>&)>
558         wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/)
559     {
560         using function_t = crow::utility::FunctionTraits<Func>;
561 
562         auto ret = detail::routing_handler_call_helper::Wrapped<
563             Func, typename function_t::template arg<Indices>...>();
564         ret.template set<typename function_t::template arg<Indices>...>(
565             std::move(f));
566         return ret;
567     }
568 
569   private:
570     std::function<void(const Request&,
571                        const std::shared_ptr<bmcweb::AsyncResp>&,
572                        const std::vector<std::string>&)>
573         erasedHandler;
574 };
575 
576 template <typename... Args>
577 class TaggedRule :
578     public BaseRule,
579     public RuleParameterTraits<TaggedRule<Args...>>
580 {
581   public:
582     using self_t = TaggedRule<Args...>;
583 
584     explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {}
585 
586     void validate() override
587     {
588         if (!handler)
589         {
590             throw std::runtime_error("no handler for url " + rule);
591         }
592     }
593 
594     template <typename Func>
595     void operator()(Func&& f)
596     {
597         static_assert(
598             black_magic::CallHelper<
599                 Func, black_magic::S<crow::Request,
600                                      std::shared_ptr<bmcweb::AsyncResp>&,
601                                      Args...>>::value,
602             "Handler type is mismatched with URL parameters");
603         static_assert(
604             std::is_same<
605                 void,
606                 decltype(f(std::declval<crow::Request>(),
607                            std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(),
608                            std::declval<Args>()...))>::value,
609             "Handler function with response argument should have void return type");
610 
611         handler = std::forward<Func>(f);
612     }
613 
614     void handle(const Request& req,
615                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
616                 const std::vector<std::string>& params) override
617     {
618         detail::routing_handler_call_helper::Call<
619             detail::routing_handler_call_helper::CallParams<decltype(handler)>,
620             0, black_magic::S<Args...>, black_magic::S<>>()(
621             detail::routing_handler_call_helper::CallParams<decltype(handler)>{
622                 handler, params, req, asyncResp});
623     }
624 
625   private:
626     std::function<void(const crow::Request&,
627                        const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>
628         handler;
629 };
630 
631 class Trie
632 {
633   public:
634     struct Node
635     {
636         unsigned ruleIndex{};
637         std::array<size_t, static_cast<size_t>(ParamType::MAX)>
638             paramChildrens{};
639         using ChildMap = boost::container::flat_map<
640             std::string, unsigned, std::less<>,
641             std::vector<std::pair<std::string, unsigned>>>;
642         ChildMap children;
643 
644         bool isSimpleNode() const
645         {
646             return ruleIndex == 0 &&
647                    std::all_of(std::begin(paramChildrens),
648                                std::end(paramChildrens),
649                                [](size_t x) { return x == 0U; });
650         }
651     };
652 
653     Trie() : nodes(1) {}
654 
655   private:
656     void optimizeNode(Node* node)
657     {
658         for (size_t x : node->paramChildrens)
659         {
660             if (x == 0U)
661             {
662                 continue;
663             }
664             Node* child = &nodes[x];
665             optimizeNode(child);
666         }
667         if (node->children.empty())
668         {
669             return;
670         }
671         bool mergeWithChild = true;
672         for (const Node::ChildMap::value_type& kv : node->children)
673         {
674             Node* child = &nodes[kv.second];
675             if (!child->isSimpleNode())
676             {
677                 mergeWithChild = false;
678                 break;
679             }
680         }
681         if (mergeWithChild)
682         {
683             Node::ChildMap merged;
684             for (const Node::ChildMap::value_type& kv : node->children)
685             {
686                 Node* child = &nodes[kv.second];
687                 for (const Node::ChildMap::value_type& childKv :
688                      child->children)
689                 {
690                     merged[kv.first + childKv.first] = childKv.second;
691                 }
692             }
693             node->children = std::move(merged);
694             optimizeNode(node);
695         }
696         else
697         {
698             for (const Node::ChildMap::value_type& kv : node->children)
699             {
700                 Node* child = &nodes[kv.second];
701                 optimizeNode(child);
702             }
703         }
704     }
705 
706     void optimize()
707     {
708         optimizeNode(head());
709     }
710 
711   public:
712     void validate()
713     {
714         optimize();
715     }
716 
717     void findRouteIndexes(const std::string& reqUrl,
718                           std::vector<unsigned>& routeIndexes,
719                           const Node* node = nullptr, unsigned pos = 0) const
720     {
721         if (node == nullptr)
722         {
723             node = head();
724         }
725         for (const Node::ChildMap::value_type& kv : node->children)
726         {
727             const std::string& fragment = kv.first;
728             const Node* child = &nodes[kv.second];
729             if (pos >= reqUrl.size())
730             {
731                 if (child->ruleIndex != 0 && fragment != "/")
732                 {
733                     routeIndexes.push_back(child->ruleIndex);
734                 }
735                 findRouteIndexes(reqUrl, routeIndexes, child,
736                                  static_cast<unsigned>(pos + fragment.size()));
737             }
738             else
739             {
740                 if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
741                 {
742                     findRouteIndexes(
743                         reqUrl, routeIndexes, child,
744                         static_cast<unsigned>(pos + fragment.size()));
745                 }
746             }
747         }
748     }
749 
750     std::pair<unsigned, std::vector<std::string>>
751         find(const std::string_view reqUrl, const Node* node = nullptr,
752              size_t pos = 0, std::vector<std::string>* params = nullptr) const
753     {
754         std::vector<std::string> empty;
755         if (params == nullptr)
756         {
757             params = &empty;
758         }
759 
760         unsigned found{};
761         std::vector<std::string> matchParams;
762 
763         if (node == nullptr)
764         {
765             node = head();
766         }
767         if (pos == reqUrl.size())
768         {
769             return {node->ruleIndex, *params};
770         }
771 
772         auto updateFound =
773             [&found,
774              &matchParams](std::pair<unsigned, std::vector<std::string>>& ret) {
775             if (ret.first != 0U && (found == 0U || found > ret.first))
776             {
777                 found = ret.first;
778                 matchParams = std::move(ret.second);
779             }
780         };
781 
782         if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)] != 0U)
783         {
784             size_t epos = pos;
785             for (; epos < reqUrl.size(); epos++)
786             {
787                 if (reqUrl[epos] == '/')
788                 {
789                     break;
790                 }
791             }
792 
793             if (epos != pos)
794             {
795                 params->emplace_back(reqUrl.substr(pos, epos - pos));
796                 std::pair<unsigned, std::vector<std::string>> ret =
797                     find(reqUrl,
798                          &nodes[node->paramChildrens[static_cast<size_t>(
799                              ParamType::STRING)]],
800                          epos, params);
801                 updateFound(ret);
802                 params->pop_back();
803             }
804         }
805 
806         if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)] != 0U)
807         {
808             size_t epos = reqUrl.size();
809 
810             if (epos != pos)
811             {
812                 params->emplace_back(reqUrl.substr(pos, epos - pos));
813                 std::pair<unsigned, std::vector<std::string>> ret =
814                     find(reqUrl,
815                          &nodes[node->paramChildrens[static_cast<size_t>(
816                              ParamType::PATH)]],
817                          epos, params);
818                 updateFound(ret);
819                 params->pop_back();
820             }
821         }
822 
823         for (const Node::ChildMap::value_type& kv : node->children)
824         {
825             const std::string& fragment = kv.first;
826             const Node* child = &nodes[kv.second];
827 
828             if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
829             {
830                 std::pair<unsigned, std::vector<std::string>> ret =
831                     find(reqUrl, child, pos + fragment.size(), params);
832                 updateFound(ret);
833             }
834         }
835 
836         return {found, matchParams};
837     }
838 
839     void add(const std::string& url, unsigned ruleIndex)
840     {
841         size_t idx = 0;
842 
843         for (unsigned i = 0; i < url.size(); i++)
844         {
845             char c = url[i];
846             if (c == '<')
847             {
848                 constexpr static std::array<
849                     std::pair<ParamType, std::string_view>, 3>
850                     paramTraits = {{
851                         {ParamType::STRING, "<str>"},
852                         {ParamType::STRING, "<string>"},
853                         {ParamType::PATH, "<path>"},
854                     }};
855 
856                 for (const std::pair<ParamType, std::string_view>& x :
857                      paramTraits)
858                 {
859                     if (url.compare(i, x.second.size(), x.second) == 0)
860                     {
861                         size_t index = static_cast<size_t>(x.first);
862                         if (nodes[idx].paramChildrens[index] == 0U)
863                         {
864                             unsigned newNodeIdx = newNode();
865                             nodes[idx].paramChildrens[index] = newNodeIdx;
866                         }
867                         idx = nodes[idx].paramChildrens[index];
868                         i += static_cast<unsigned>(x.second.size());
869                         break;
870                     }
871                 }
872 
873                 i--;
874             }
875             else
876             {
877                 std::string piece(&c, 1);
878                 if (nodes[idx].children.count(piece) == 0U)
879                 {
880                     unsigned newNodeIdx = newNode();
881                     nodes[idx].children.emplace(piece, newNodeIdx);
882                 }
883                 idx = nodes[idx].children[piece];
884             }
885         }
886         if (nodes[idx].ruleIndex != 0U)
887         {
888             throw std::runtime_error("handler already exists for " + url);
889         }
890         nodes[idx].ruleIndex = ruleIndex;
891     }
892 
893   private:
894     void debugNodePrint(Node* n, size_t level)
895     {
896         for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++)
897         {
898             if (n->paramChildrens[i] != 0U)
899             {
900                 BMCWEB_LOG_DEBUG << std::string(
901                     2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/;
902                 switch (static_cast<ParamType>(i))
903                 {
904                     case ParamType::STRING:
905                         BMCWEB_LOG_DEBUG << "<str>";
906                         break;
907                     case ParamType::PATH:
908                         BMCWEB_LOG_DEBUG << "<path>";
909                         break;
910                     case ParamType::MAX:
911                         BMCWEB_LOG_DEBUG << "<ERROR>";
912                         break;
913                 }
914 
915                 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1);
916             }
917         }
918         for (const Node::ChildMap::value_type& kv : n->children)
919         {
920             BMCWEB_LOG_DEBUG
921                 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/
922                 << kv.first;
923             debugNodePrint(&nodes[kv.second], level + 1);
924         }
925     }
926 
927   public:
928     void debugPrint()
929     {
930         debugNodePrint(head(), 0U);
931     }
932 
933   private:
934     const Node* head() const
935     {
936         return &nodes.front();
937     }
938 
939     Node* head()
940     {
941         return &nodes.front();
942     }
943 
944     unsigned newNode()
945     {
946         nodes.resize(nodes.size() + 1);
947         return static_cast<unsigned>(nodes.size() - 1);
948     }
949 
950     std::vector<Node> nodes;
951 };
952 
953 class Router
954 {
955   public:
956     Router() = default;
957 
958     DynamicRule& newRuleDynamic(const std::string& rule)
959     {
960         std::unique_ptr<DynamicRule> ruleObject =
961             std::make_unique<DynamicRule>(rule);
962         DynamicRule* ptr = ruleObject.get();
963         allRules.emplace_back(std::move(ruleObject));
964 
965         return *ptr;
966     }
967 
968     template <uint64_t N>
969     typename black_magic::Arguments<N>::type::template rebind<TaggedRule>&
970         newRuleTagged(const std::string& rule)
971     {
972         using RuleT = typename black_magic::Arguments<N>::type::template rebind<
973             TaggedRule>;
974         std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
975         RuleT* ptr = ruleObject.get();
976         allRules.emplace_back(std::move(ruleObject));
977 
978         return *ptr;
979     }
980 
981     void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
982     {
983         if (ruleObject == nullptr)
984         {
985             return;
986         }
987         for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex;
988              method++, methodBit <<= 1)
989         {
990             if ((ruleObject->methodsBitfield & methodBit) > 0U)
991             {
992                 perMethods[method].rules.emplace_back(ruleObject);
993                 perMethods[method].trie.add(
994                     rule, static_cast<unsigned>(
995                               perMethods[method].rules.size() - 1U));
996                 // directory case:
997                 //   request to `/about' url matches `/about/' rule
998                 if (rule.size() > 2 && rule.back() == '/')
999                 {
1000                     perMethods[method].trie.add(
1001                         rule.substr(0, rule.size() - 1),
1002                         static_cast<unsigned>(perMethods[method].rules.size() -
1003                                               1));
1004                 }
1005             }
1006         }
1007     }
1008 
1009     void validate()
1010     {
1011         for (std::unique_ptr<BaseRule>& rule : allRules)
1012         {
1013             if (rule)
1014             {
1015                 std::unique_ptr<BaseRule> upgraded = rule->upgrade();
1016                 if (upgraded)
1017                 {
1018                     rule = std::move(upgraded);
1019                 }
1020                 rule->validate();
1021                 internalAddRuleObject(rule->rule, rule.get());
1022             }
1023         }
1024         for (PerMethod& perMethod : perMethods)
1025         {
1026             perMethod.trie.validate();
1027         }
1028     }
1029 
1030     struct FindRoute
1031     {
1032         BaseRule* rule = nullptr;
1033         std::vector<std::string> params;
1034     };
1035 
1036     struct FindRouteResponse
1037     {
1038         std::string allowHeader;
1039         FindRoute route;
1040     };
1041 
1042     FindRoute findRouteByIndex(std::string_view url, size_t index) const
1043     {
1044         FindRoute route;
1045         if (index >= perMethods.size())
1046         {
1047             BMCWEB_LOG_CRITICAL << "Bad index???";
1048             return route;
1049         }
1050         const PerMethod& perMethod = perMethods[index];
1051         std::pair<unsigned, std::vector<std::string>> found =
1052             perMethod.trie.find(url);
1053         if (found.first >= perMethod.rules.size())
1054         {
1055             throw std::runtime_error("Trie internal structure corrupted!");
1056         }
1057         // Found a 404 route, switch that in
1058         if (found.first != 0U)
1059         {
1060             route.rule = perMethod.rules[found.first];
1061             route.params = std::move(found.second);
1062         }
1063         return route;
1064     }
1065 
1066     FindRouteResponse findRoute(Request& req) const
1067     {
1068         FindRouteResponse findRoute;
1069 
1070         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
1071         if (!verb)
1072         {
1073             return findRoute;
1074         }
1075         size_t reqMethodIndex = static_cast<size_t>(*verb);
1076         // Check to see if this url exists at any verb
1077         for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex;
1078              perMethodIndex++)
1079         {
1080             // Make sure it's safe to deference the array at that index
1081             static_assert(maxVerbIndex <
1082                           std::tuple_size_v<decltype(perMethods)>);
1083             FindRoute route = findRouteByIndex(req.url().encoded_path(),
1084                                                perMethodIndex);
1085             if (route.rule == nullptr)
1086             {
1087                 continue;
1088             }
1089             if (!findRoute.allowHeader.empty())
1090             {
1091                 findRoute.allowHeader += ", ";
1092             }
1093             HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex);
1094             findRoute.allowHeader += httpVerbToString(thisVerb);
1095             if (perMethodIndex == reqMethodIndex)
1096             {
1097                 findRoute.route = route;
1098             }
1099         }
1100         return findRoute;
1101     }
1102 
1103     // Populate session with user information.
1104     static bool
1105         populateUserInfo(Request& req,
1106                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1107                          const dbus::utility::DBusPropertiesMap& userInfoMap)
1108     {
1109         const std::string* userRolePtr = nullptr;
1110         const bool* remoteUser = nullptr;
1111         const bool* passwordExpired = nullptr;
1112         const std::vector<std::string>* userGroups = nullptr;
1113 
1114         const bool success = sdbusplus::unpackPropertiesNoThrow(
1115             redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap,
1116             "UserPrivilege", userRolePtr, "RemoteUser", remoteUser,
1117             "UserPasswordExpired", passwordExpired, "UserGroups", userGroups);
1118 
1119         if (!success)
1120         {
1121             BMCWEB_LOG_ERROR << "Failed to unpack user properties.";
1122             asyncResp->res.result(
1123                 boost::beast::http::status::internal_server_error);
1124             return false;
1125         }
1126 
1127         if (userRolePtr != nullptr)
1128         {
1129             req.session->userRole = *userRolePtr;
1130             BMCWEB_LOG_DEBUG << "userName = " << req.session->username
1131                              << " userRole = " << *userRolePtr;
1132         }
1133 
1134         if (remoteUser == nullptr)
1135         {
1136             BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type";
1137             asyncResp->res.result(
1138                 boost::beast::http::status::internal_server_error);
1139             return false;
1140         }
1141         bool expired = false;
1142         if (passwordExpired == nullptr)
1143         {
1144             if (!*remoteUser)
1145             {
1146                 BMCWEB_LOG_ERROR
1147                     << "UserPasswordExpired property is expected for"
1148                        " local user but is missing or wrong type";
1149                 asyncResp->res.result(
1150                     boost::beast::http::status::internal_server_error);
1151                 return false;
1152             }
1153         }
1154         else
1155         {
1156             expired = *passwordExpired;
1157         }
1158 
1159         // Set isConfigureSelfOnly based on D-Bus results.  This
1160         // ignores the results from both pamAuthenticateUser and the
1161         // value from any previous use of this session.
1162         req.session->isConfigureSelfOnly = expired;
1163 
1164         if (userGroups != nullptr)
1165         {
1166             // Populate session with user groups.
1167             for (const auto& userGroup : *userGroups)
1168             {
1169                 req.session->userGroups.emplace_back(userGroup);
1170             }
1171         }
1172 
1173         return true;
1174     }
1175 
1176     static bool
1177         isUserPrivileged(Request& req,
1178                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1179                          BaseRule& rule)
1180     {
1181         // Get the user's privileges from the role
1182         redfish::Privileges userPrivileges =
1183             redfish::getUserPrivileges(*req.session);
1184 
1185         // Modify privileges if isConfigureSelfOnly.
1186         if (req.session->isConfigureSelfOnly)
1187         {
1188             // Remove all privileges except ConfigureSelf
1189             userPrivileges = userPrivileges.intersection(
1190                 redfish::Privileges{"ConfigureSelf"});
1191             BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf";
1192         }
1193 
1194         if (!rule.checkPrivileges(userPrivileges))
1195         {
1196             asyncResp->res.result(boost::beast::http::status::forbidden);
1197             if (req.session->isConfigureSelfOnly)
1198             {
1199                 redfish::messages::passwordChangeRequired(
1200                     asyncResp->res,
1201                     boost::urls::format(
1202                         "/redfish/v1/AccountService/Accounts/{}",
1203                         req.session->username));
1204             }
1205             return false;
1206         }
1207 
1208         req.userRole = req.session->userRole;
1209         return true;
1210     }
1211 
1212     template <typename CallbackFn>
1213     void afterGetUserInfo(Request& req,
1214                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1215                           BaseRule& rule, CallbackFn&& callback,
1216                           const boost::system::error_code& ec,
1217                           const dbus::utility::DBusPropertiesMap& userInfoMap)
1218     {
1219         if (ec)
1220         {
1221             BMCWEB_LOG_ERROR << "GetUserInfo failed...";
1222             asyncResp->res.result(
1223                 boost::beast::http::status::internal_server_error);
1224             return;
1225         }
1226 
1227         if (!populateUserInfo(req, asyncResp, userInfoMap))
1228         {
1229             BMCWEB_LOG_ERROR << "Failed to populate user information";
1230             asyncResp->res.result(
1231                 boost::beast::http::status::internal_server_error);
1232             return;
1233         }
1234 
1235         if (!Router::isUserPrivileged(req, asyncResp, rule))
1236         {
1237             // User is not privileged
1238             BMCWEB_LOG_ERROR << "Insufficient Privilege";
1239             asyncResp->res.result(boost::beast::http::status::forbidden);
1240             return;
1241         }
1242         callback(req);
1243     }
1244 
1245     template <typename CallbackFn>
1246     void validatePrivilege(Request& req,
1247                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1248                            BaseRule& rule, CallbackFn&& callback)
1249     {
1250         if (req.session == nullptr)
1251         {
1252             return;
1253         }
1254         std::string username = req.session->username;
1255         crow::connections::systemBus->async_method_call(
1256             [this, &req, asyncResp, &rule,
1257              callback(std::forward<CallbackFn>(callback))](
1258                 const boost::system::error_code& ec,
1259                 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable {
1260             afterGetUserInfo(req, asyncResp, rule,
1261                              std::forward<CallbackFn>(callback), ec,
1262                              userInfoMap);
1263             },
1264             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1265             "xyz.openbmc_project.User.Manager", "GetUserInfo", username);
1266     }
1267 
1268     template <typename Adaptor>
1269     void handleUpgrade(Request& req,
1270                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1271                        Adaptor&& adaptor)
1272     {
1273         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
1274         if (!verb || static_cast<size_t>(*verb) >= perMethods.size())
1275         {
1276             asyncResp->res.result(boost::beast::http::status::not_found);
1277             return;
1278         }
1279         PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)];
1280         Trie& trie = perMethod.trie;
1281         std::vector<BaseRule*>& rules = perMethod.rules;
1282 
1283         const std::pair<unsigned, std::vector<std::string>>& found =
1284             trie.find(req.url().encoded_path());
1285         unsigned ruleIndex = found.first;
1286         if (ruleIndex == 0U)
1287         {
1288             BMCWEB_LOG_DEBUG << "Cannot match rules "
1289                              << req.url().encoded_path();
1290             asyncResp->res.result(boost::beast::http::status::not_found);
1291             return;
1292         }
1293 
1294         if (ruleIndex >= rules.size())
1295         {
1296             throw std::runtime_error("Trie internal structure corrupted!");
1297         }
1298 
1299         BaseRule& rule = *rules[ruleIndex];
1300         size_t methods = rule.getMethods();
1301         if ((methods & (1U << static_cast<size_t>(*verb))) == 0)
1302         {
1303             BMCWEB_LOG_DEBUG
1304                 << "Rule found but method mismatch: "
1305                 << req.url().encoded_path() << " with " << req.methodString()
1306                 << "(" << static_cast<uint32_t>(*verb) << ") / " << methods;
1307             asyncResp->res.result(boost::beast::http::status::not_found);
1308             return;
1309         }
1310 
1311         BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rule.rule << "' "
1312                          << static_cast<uint32_t>(*verb) << " / " << methods;
1313 
1314         // TODO(ed) This should be able to use std::bind_front, but it doesn't
1315         // appear to work with the std::move on adaptor.
1316         validatePrivilege(
1317             req, asyncResp, rule,
1318             [&rule, asyncResp, adaptor(std::forward<Adaptor>(adaptor))](
1319                 Request& thisReq) mutable {
1320             rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor));
1321             });
1322     }
1323 
1324     void handle(Request& req,
1325                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1326     {
1327         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
1328         if (!verb || static_cast<size_t>(*verb) >= perMethods.size())
1329         {
1330             asyncResp->res.result(boost::beast::http::status::not_found);
1331             return;
1332         }
1333 
1334         FindRouteResponse foundRoute = findRoute(req);
1335 
1336         if (foundRoute.route.rule == nullptr)
1337         {
1338             // Couldn't find a normal route with any verb, try looking for a 404
1339             // route
1340             if (foundRoute.allowHeader.empty())
1341             {
1342                 foundRoute.route = findRouteByIndex(req.url().encoded_path(),
1343                                                     notFoundIndex);
1344             }
1345             else
1346             {
1347                 // See if we have a method not allowed (405) handler
1348                 foundRoute.route = findRouteByIndex(req.url().encoded_path(),
1349                                                     methodNotAllowedIndex);
1350             }
1351         }
1352 
1353         // Fill in the allow header if it's valid
1354         if (!foundRoute.allowHeader.empty())
1355         {
1356             asyncResp->res.addHeader(boost::beast::http::field::allow,
1357                                      foundRoute.allowHeader);
1358         }
1359 
1360         // If we couldn't find a real route or a 404 route, return a generic
1361         // response
1362         if (foundRoute.route.rule == nullptr)
1363         {
1364             if (foundRoute.allowHeader.empty())
1365             {
1366                 asyncResp->res.result(boost::beast::http::status::not_found);
1367             }
1368             else
1369             {
1370                 asyncResp->res.result(
1371                     boost::beast::http::status::method_not_allowed);
1372             }
1373             return;
1374         }
1375 
1376         BaseRule& rule = *foundRoute.route.rule;
1377         std::vector<std::string> params = std::move(foundRoute.route.params);
1378 
1379         BMCWEB_LOG_DEBUG << "Matched rule '" << rule.rule << "' "
1380                          << static_cast<uint32_t>(*verb) << " / "
1381                          << rule.getMethods();
1382 
1383         if (req.session == nullptr)
1384         {
1385             rule.handle(req, asyncResp, params);
1386             return;
1387         }
1388         validatePrivilege(req, asyncResp, rule,
1389                           [&rule, asyncResp, params](Request& thisReq) mutable {
1390             rule.handle(thisReq, asyncResp, params);
1391         });
1392     }
1393 
1394     void debugPrint()
1395     {
1396         for (size_t i = 0; i < perMethods.size(); i++)
1397         {
1398             BMCWEB_LOG_DEBUG << boost::beast::http::to_string(
1399                 static_cast<boost::beast::http::verb>(i));
1400             perMethods[i].trie.debugPrint();
1401         }
1402     }
1403 
1404     std::vector<const std::string*> getRoutes(const std::string& parent)
1405     {
1406         std::vector<const std::string*> ret;
1407 
1408         for (const PerMethod& pm : perMethods)
1409         {
1410             std::vector<unsigned> x;
1411             pm.trie.findRouteIndexes(parent, x);
1412             for (unsigned index : x)
1413             {
1414                 ret.push_back(&pm.rules[index]->rule);
1415             }
1416         }
1417         return ret;
1418     }
1419 
1420   private:
1421     struct PerMethod
1422     {
1423         std::vector<BaseRule*> rules;
1424         Trie trie;
1425         // rule index 0 has special meaning; preallocate it to avoid
1426         // duplication.
1427         PerMethod() : rules(1) {}
1428     };
1429 
1430     std::array<PerMethod, methodNotAllowedIndex + 1> perMethods;
1431     std::vector<std::unique_ptr<BaseRule>> allRules;
1432 };
1433 } // namespace crow
1434