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