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