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