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