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