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