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