xref: /openbmc/bmcweb/http/routing.hpp (revision 93cf0ac2)
1 #pragma once
2 
3 #include "async_resp.hpp"
4 #include "common.hpp"
5 #include "dbus_privileges.hpp"
6 #include "dbus_utility.hpp"
7 #include "error_messages.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "logging.hpp"
11 #include "privileges.hpp"
12 #include "routing/baserule.hpp"
13 #include "routing/dynamicrule.hpp"
14 #include "routing/sserule.hpp"
15 #include "routing/taggedrule.hpp"
16 #include "routing/websocketrule.hpp"
17 #include "sessions.hpp"
18 #include "utility.hpp"
19 #include "utils/dbus_utils.hpp"
20 #include "verb.hpp"
21 #include "websocket.hpp"
22 
23 #include <boost/beast/ssl/ssl_stream.hpp>
24 #include <boost/container/flat_map.hpp>
25 #include <boost/url/format.hpp>
26 #include <sdbusplus/unpack_properties.hpp>
27 
28 #include <cerrno>
29 #include <cstdint>
30 #include <cstdlib>
31 #include <limits>
32 #include <memory>
33 #include <optional>
34 #include <tuple>
35 #include <utility>
36 #include <vector>
37 
38 namespace crow
39 {
40 
41 class Trie
42 {
43   public:
44     struct Node
45     {
46         unsigned ruleIndex{};
47         std::array<size_t, static_cast<size_t>(ParamType::MAX)>
48             paramChildrens{};
49         using ChildMap = boost::container::flat_map<
50             std::string, unsigned, std::less<>,
51             std::vector<std::pair<std::string, unsigned>>>;
52         ChildMap children;
53 
54         bool isSimpleNode() const
55         {
56             return ruleIndex == 0 &&
57                    std::all_of(std::begin(paramChildrens),
58                                std::end(paramChildrens),
59                                [](size_t x) { return x == 0U; });
60         }
61     };
62 
63     Trie() : nodes(1) {}
64 
65   private:
66     void optimizeNode(Node* node)
67     {
68         for (size_t x : node->paramChildrens)
69         {
70             if (x == 0U)
71             {
72                 continue;
73             }
74             Node* child = &nodes[x];
75             optimizeNode(child);
76         }
77         if (node->children.empty())
78         {
79             return;
80         }
81         bool mergeWithChild = true;
82         for (const Node::ChildMap::value_type& kv : node->children)
83         {
84             Node* child = &nodes[kv.second];
85             if (!child->isSimpleNode())
86             {
87                 mergeWithChild = false;
88                 break;
89             }
90         }
91         if (mergeWithChild)
92         {
93             Node::ChildMap merged;
94             for (const Node::ChildMap::value_type& kv : node->children)
95             {
96                 Node* child = &nodes[kv.second];
97                 for (const Node::ChildMap::value_type& childKv :
98                      child->children)
99                 {
100                     merged[kv.first + childKv.first] = childKv.second;
101                 }
102             }
103             node->children = std::move(merged);
104             optimizeNode(node);
105         }
106         else
107         {
108             for (const Node::ChildMap::value_type& kv : node->children)
109             {
110                 Node* child = &nodes[kv.second];
111                 optimizeNode(child);
112             }
113         }
114     }
115 
116     void optimize()
117     {
118         optimizeNode(head());
119     }
120 
121   public:
122     void validate()
123     {
124         optimize();
125     }
126 
127     void findRouteIndexes(const std::string& reqUrl,
128                           std::vector<unsigned>& routeIndexes,
129                           const Node* node = nullptr, unsigned pos = 0) const
130     {
131         if (node == nullptr)
132         {
133             node = head();
134         }
135         for (const Node::ChildMap::value_type& kv : node->children)
136         {
137             const std::string& fragment = kv.first;
138             const Node* child = &nodes[kv.second];
139             if (pos >= reqUrl.size())
140             {
141                 if (child->ruleIndex != 0 && fragment != "/")
142                 {
143                     routeIndexes.push_back(child->ruleIndex);
144                 }
145                 findRouteIndexes(reqUrl, routeIndexes, child,
146                                  static_cast<unsigned>(pos + fragment.size()));
147             }
148             else
149             {
150                 if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
151                 {
152                     findRouteIndexes(
153                         reqUrl, routeIndexes, child,
154                         static_cast<unsigned>(pos + fragment.size()));
155                 }
156             }
157         }
158     }
159 
160     std::pair<unsigned, std::vector<std::string>>
161         find(const std::string_view reqUrl, const Node* node = nullptr,
162              size_t pos = 0, std::vector<std::string>* params = nullptr) const
163     {
164         std::vector<std::string> empty;
165         if (params == nullptr)
166         {
167             params = &empty;
168         }
169 
170         unsigned found{};
171         std::vector<std::string> matchParams;
172 
173         if (node == nullptr)
174         {
175             node = head();
176         }
177         if (pos == reqUrl.size())
178         {
179             return {node->ruleIndex, *params};
180         }
181 
182         auto updateFound =
183             [&found,
184              &matchParams](std::pair<unsigned, std::vector<std::string>>& ret) {
185             if (ret.first != 0U && (found == 0U || found > ret.first))
186             {
187                 found = ret.first;
188                 matchParams = std::move(ret.second);
189             }
190         };
191 
192         if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)] != 0U)
193         {
194             size_t epos = pos;
195             for (; epos < reqUrl.size(); epos++)
196             {
197                 if (reqUrl[epos] == '/')
198                 {
199                     break;
200                 }
201             }
202 
203             if (epos != pos)
204             {
205                 params->emplace_back(reqUrl.substr(pos, epos - pos));
206                 std::pair<unsigned, std::vector<std::string>> ret =
207                     find(reqUrl,
208                          &nodes[node->paramChildrens[static_cast<size_t>(
209                              ParamType::STRING)]],
210                          epos, params);
211                 updateFound(ret);
212                 params->pop_back();
213             }
214         }
215 
216         if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)] != 0U)
217         {
218             size_t epos = reqUrl.size();
219 
220             if (epos != pos)
221             {
222                 params->emplace_back(reqUrl.substr(pos, epos - pos));
223                 std::pair<unsigned, std::vector<std::string>> ret =
224                     find(reqUrl,
225                          &nodes[node->paramChildrens[static_cast<size_t>(
226                              ParamType::PATH)]],
227                          epos, params);
228                 updateFound(ret);
229                 params->pop_back();
230             }
231         }
232 
233         for (const Node::ChildMap::value_type& kv : node->children)
234         {
235             const std::string& fragment = kv.first;
236             const Node* child = &nodes[kv.second];
237 
238             if (reqUrl.compare(pos, fragment.size(), fragment) == 0)
239             {
240                 std::pair<unsigned, std::vector<std::string>> ret =
241                     find(reqUrl, child, pos + fragment.size(), params);
242                 updateFound(ret);
243             }
244         }
245 
246         return {found, matchParams};
247     }
248 
249     void add(const std::string& url, unsigned ruleIndex)
250     {
251         size_t idx = 0;
252 
253         for (unsigned i = 0; i < url.size(); i++)
254         {
255             char c = url[i];
256             if (c == '<')
257             {
258                 constexpr static std::array<
259                     std::pair<ParamType, std::string_view>, 3>
260                     paramTraits = {{
261                         {ParamType::STRING, "<str>"},
262                         {ParamType::STRING, "<string>"},
263                         {ParamType::PATH, "<path>"},
264                     }};
265 
266                 for (const std::pair<ParamType, std::string_view>& x :
267                      paramTraits)
268                 {
269                     if (url.compare(i, x.second.size(), x.second) == 0)
270                     {
271                         size_t index = static_cast<size_t>(x.first);
272                         if (nodes[idx].paramChildrens[index] == 0U)
273                         {
274                             unsigned newNodeIdx = newNode();
275                             nodes[idx].paramChildrens[index] = newNodeIdx;
276                         }
277                         idx = nodes[idx].paramChildrens[index];
278                         i += static_cast<unsigned>(x.second.size());
279                         break;
280                     }
281                 }
282 
283                 i--;
284             }
285             else
286             {
287                 std::string piece(&c, 1);
288                 if (nodes[idx].children.count(piece) == 0U)
289                 {
290                     unsigned newNodeIdx = newNode();
291                     nodes[idx].children.emplace(piece, newNodeIdx);
292                 }
293                 idx = nodes[idx].children[piece];
294             }
295         }
296         if (nodes[idx].ruleIndex != 0U)
297         {
298             throw std::runtime_error("handler already exists for " + url);
299         }
300         nodes[idx].ruleIndex = ruleIndex;
301     }
302 
303   private:
304     void debugNodePrint(Node* n, size_t level)
305     {
306         for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++)
307         {
308             if (n->paramChildrens[i] != 0U)
309             {
310                 BMCWEB_LOG_DEBUG(
311                     "{}({}{}",
312                     std::string(2U * level,
313                                 ' ') /*, n->paramChildrens[i], ") "*/);
314                 switch (static_cast<ParamType>(i))
315                 {
316                     case ParamType::STRING:
317                         BMCWEB_LOG_DEBUG("<str>");
318                         break;
319                     case ParamType::PATH:
320                         BMCWEB_LOG_DEBUG("<path>");
321                         break;
322                     case ParamType::MAX:
323                         BMCWEB_LOG_DEBUG("<ERROR>");
324                         break;
325                 }
326 
327                 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1);
328             }
329         }
330         for (const Node::ChildMap::value_type& kv : n->children)
331         {
332             BMCWEB_LOG_DEBUG("{}({}{}{}",
333                              std::string(2U * level, ' ') /*, kv.second, ") "*/,
334                              kv.first);
335             debugNodePrint(&nodes[kv.second], level + 1);
336         }
337     }
338 
339   public:
340     void debugPrint()
341     {
342         debugNodePrint(head(), 0U);
343     }
344 
345   private:
346     const Node* head() const
347     {
348         return &nodes.front();
349     }
350 
351     Node* head()
352     {
353         return &nodes.front();
354     }
355 
356     unsigned newNode()
357     {
358         nodes.resize(nodes.size() + 1);
359         return static_cast<unsigned>(nodes.size() - 1);
360     }
361 
362     std::vector<Node> nodes;
363 };
364 
365 class Router
366 {
367   public:
368     Router() = default;
369 
370     DynamicRule& newRuleDynamic(const std::string& rule)
371     {
372         std::unique_ptr<DynamicRule> ruleObject =
373             std::make_unique<DynamicRule>(rule);
374         DynamicRule* ptr = ruleObject.get();
375         allRules.emplace_back(std::move(ruleObject));
376 
377         return *ptr;
378     }
379 
380     template <uint64_t N>
381     auto& newRuleTagged(const std::string& rule)
382     {
383         constexpr size_t numArgs = utility::numArgsFromTag(N);
384         if constexpr (numArgs == 0)
385         {
386             using RuleT = TaggedRule<>;
387             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
388             RuleT* ptr = ruleObject.get();
389             allRules.emplace_back(std::move(ruleObject));
390             return *ptr;
391         }
392         else if constexpr (numArgs == 1)
393         {
394             using RuleT = TaggedRule<std::string>;
395             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
396             RuleT* ptr = ruleObject.get();
397             allRules.emplace_back(std::move(ruleObject));
398             return *ptr;
399         }
400         else if constexpr (numArgs == 2)
401         {
402             using RuleT = TaggedRule<std::string, std::string>;
403             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
404             RuleT* ptr = ruleObject.get();
405             allRules.emplace_back(std::move(ruleObject));
406             return *ptr;
407         }
408         else if constexpr (numArgs == 3)
409         {
410             using RuleT = TaggedRule<std::string, std::string, std::string>;
411             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
412             RuleT* ptr = ruleObject.get();
413             allRules.emplace_back(std::move(ruleObject));
414             return *ptr;
415         }
416         else if constexpr (numArgs == 4)
417         {
418             using RuleT =
419                 TaggedRule<std::string, std::string, std::string, std::string>;
420             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
421             RuleT* ptr = ruleObject.get();
422             allRules.emplace_back(std::move(ruleObject));
423             return *ptr;
424         }
425         else
426         {
427             using RuleT = TaggedRule<std::string, std::string, std::string,
428                                      std::string, std::string>;
429             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
430             RuleT* ptr = ruleObject.get();
431             allRules.emplace_back(std::move(ruleObject));
432             return *ptr;
433         }
434         static_assert(numArgs <= 5, "Max number of args supported is 5");
435     }
436 
437     void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
438     {
439         if (ruleObject == nullptr)
440         {
441             return;
442         }
443         for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex;
444              method++, methodBit <<= 1)
445         {
446             if ((ruleObject->methodsBitfield & methodBit) > 0U)
447             {
448                 perMethods[method].rules.emplace_back(ruleObject);
449                 perMethods[method].trie.add(
450                     rule, static_cast<unsigned>(
451                               perMethods[method].rules.size() - 1U));
452                 // directory case:
453                 //   request to `/about' url matches `/about/' rule
454                 if (rule.size() > 2 && rule.back() == '/')
455                 {
456                     perMethods[method].trie.add(
457                         rule.substr(0, rule.size() - 1),
458                         static_cast<unsigned>(perMethods[method].rules.size() -
459                                               1));
460                 }
461             }
462         }
463     }
464 
465     void validate()
466     {
467         for (std::unique_ptr<BaseRule>& rule : allRules)
468         {
469             if (rule)
470             {
471                 std::unique_ptr<BaseRule> upgraded = rule->upgrade();
472                 if (upgraded)
473                 {
474                     rule = std::move(upgraded);
475                 }
476                 rule->validate();
477                 internalAddRuleObject(rule->rule, rule.get());
478             }
479         }
480         for (PerMethod& perMethod : perMethods)
481         {
482             perMethod.trie.validate();
483         }
484     }
485 
486     struct FindRoute
487     {
488         BaseRule* rule = nullptr;
489         std::vector<std::string> params;
490     };
491 
492     struct FindRouteResponse
493     {
494         std::string allowHeader;
495         FindRoute route;
496     };
497 
498     FindRoute findRouteByIndex(std::string_view url, size_t index) const
499     {
500         FindRoute route;
501         if (index >= perMethods.size())
502         {
503             BMCWEB_LOG_CRITICAL("Bad index???");
504             return route;
505         }
506         const PerMethod& perMethod = perMethods[index];
507         std::pair<unsigned, std::vector<std::string>> found =
508             perMethod.trie.find(url);
509         if (found.first >= perMethod.rules.size())
510         {
511             throw std::runtime_error("Trie internal structure corrupted!");
512         }
513         // Found a 404 route, switch that in
514         if (found.first != 0U)
515         {
516             route.rule = perMethod.rules[found.first];
517             route.params = std::move(found.second);
518         }
519         return route;
520     }
521 
522     FindRouteResponse findRoute(Request& req) const
523     {
524         FindRouteResponse findRoute;
525 
526         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
527         if (!verb)
528         {
529             return findRoute;
530         }
531         size_t reqMethodIndex = static_cast<size_t>(*verb);
532         // Check to see if this url exists at any verb
533         for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex;
534              perMethodIndex++)
535         {
536             // Make sure it's safe to deference the array at that index
537             static_assert(maxVerbIndex <
538                           std::tuple_size_v<decltype(perMethods)>);
539             FindRoute route = findRouteByIndex(req.url().encoded_path(),
540                                                perMethodIndex);
541             if (route.rule == nullptr)
542             {
543                 continue;
544             }
545             if (!findRoute.allowHeader.empty())
546             {
547                 findRoute.allowHeader += ", ";
548             }
549             HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex);
550             findRoute.allowHeader += httpVerbToString(thisVerb);
551             if (perMethodIndex == reqMethodIndex)
552             {
553                 findRoute.route = route;
554             }
555         }
556         return findRoute;
557     }
558 
559     template <typename Adaptor>
560     void handleUpgrade(Request& req,
561                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
562                        Adaptor&& adaptor)
563     {
564         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
565         if (!verb || static_cast<size_t>(*verb) >= perMethods.size())
566         {
567             asyncResp->res.result(boost::beast::http::status::not_found);
568             return;
569         }
570         PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)];
571         Trie& trie = perMethod.trie;
572         std::vector<BaseRule*>& rules = perMethod.rules;
573 
574         const std::pair<unsigned, std::vector<std::string>>& found =
575             trie.find(req.url().encoded_path());
576         unsigned ruleIndex = found.first;
577         if (ruleIndex == 0U)
578         {
579             BMCWEB_LOG_DEBUG("Cannot match rules {}", req.url().encoded_path());
580             asyncResp->res.result(boost::beast::http::status::not_found);
581             return;
582         }
583 
584         if (ruleIndex >= rules.size())
585         {
586             throw std::runtime_error("Trie internal structure corrupted!");
587         }
588 
589         BaseRule& rule = *rules[ruleIndex];
590         size_t methods = rule.getMethods();
591         if ((methods & (1U << static_cast<size_t>(*verb))) == 0)
592         {
593             BMCWEB_LOG_DEBUG(
594                 "Rule found but method mismatch: {} with {}({}) / {}",
595                 req.url().encoded_path(), req.methodString(),
596                 static_cast<uint32_t>(*verb), methods);
597             asyncResp->res.result(boost::beast::http::status::not_found);
598             return;
599         }
600 
601         BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}' {} / {}", rule.rule,
602                          static_cast<uint32_t>(*verb), methods);
603 
604         // TODO(ed) This should be able to use std::bind_front, but it doesn't
605         // appear to work with the std::move on adaptor.
606         validatePrivilege(
607             req, asyncResp, rule,
608             [&rule, asyncResp, adaptor(std::forward<Adaptor>(adaptor))](
609                 Request& thisReq) mutable {
610             rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor));
611         });
612     }
613 
614     void handle(Request& req,
615                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
616     {
617         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
618         if (!verb || static_cast<size_t>(*verb) >= perMethods.size())
619         {
620             asyncResp->res.result(boost::beast::http::status::not_found);
621             return;
622         }
623 
624         FindRouteResponse foundRoute = findRoute(req);
625 
626         if (foundRoute.route.rule == nullptr)
627         {
628             // Couldn't find a normal route with any verb, try looking for a 404
629             // route
630             if (foundRoute.allowHeader.empty())
631             {
632                 foundRoute.route = findRouteByIndex(req.url().encoded_path(),
633                                                     notFoundIndex);
634             }
635             else
636             {
637                 // See if we have a method not allowed (405) handler
638                 foundRoute.route = findRouteByIndex(req.url().encoded_path(),
639                                                     methodNotAllowedIndex);
640             }
641         }
642 
643         // Fill in the allow header if it's valid
644         if (!foundRoute.allowHeader.empty())
645         {
646             asyncResp->res.addHeader(boost::beast::http::field::allow,
647                                      foundRoute.allowHeader);
648         }
649 
650         // If we couldn't find a real route or a 404 route, return a generic
651         // response
652         if (foundRoute.route.rule == nullptr)
653         {
654             if (foundRoute.allowHeader.empty())
655             {
656                 asyncResp->res.result(boost::beast::http::status::not_found);
657             }
658             else
659             {
660                 asyncResp->res.result(
661                     boost::beast::http::status::method_not_allowed);
662             }
663             return;
664         }
665 
666         BaseRule& rule = *foundRoute.route.rule;
667         std::vector<std::string> params = std::move(foundRoute.route.params);
668 
669         BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule,
670                          static_cast<uint32_t>(*verb), rule.getMethods());
671 
672         if (req.session == nullptr)
673         {
674             rule.handle(req, asyncResp, params);
675             return;
676         }
677         validatePrivilege(req, asyncResp, rule,
678                           [&rule, asyncResp, params](Request& thisReq) mutable {
679             rule.handle(thisReq, asyncResp, params);
680         });
681     }
682 
683     void debugPrint()
684     {
685         for (size_t i = 0; i < perMethods.size(); i++)
686         {
687             BMCWEB_LOG_DEBUG("{}",
688                              boost::beast::http::to_string(
689                                  static_cast<boost::beast::http::verb>(i)));
690             perMethods[i].trie.debugPrint();
691         }
692     }
693 
694     std::vector<const std::string*> getRoutes(const std::string& parent)
695     {
696         std::vector<const std::string*> ret;
697 
698         for (const PerMethod& pm : perMethods)
699         {
700             std::vector<unsigned> x;
701             pm.trie.findRouteIndexes(parent, x);
702             for (unsigned index : x)
703             {
704                 ret.push_back(&pm.rules[index]->rule);
705             }
706         }
707         return ret;
708     }
709 
710   private:
711     struct PerMethod
712     {
713         std::vector<BaseRule*> rules;
714         Trie trie;
715         // rule index 0 has special meaning; preallocate it to avoid
716         // duplication.
717         PerMethod() : rules(1) {}
718     };
719 
720     std::array<PerMethod, methodNotAllowedIndex + 1> perMethods;
721     std::vector<std::unique_ptr<BaseRule>> allRules;
722 };
723 } // namespace crow
724