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