xref: /openbmc/bmcweb/http/routing.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
304e438cbSEd Tanous #pragma once
404e438cbSEd Tanous 
53ccb3adbSEd Tanous #include "async_resp.hpp"
608bbe119SEd Tanous #include "dbus_privileges.hpp"
704e438cbSEd Tanous #include "http_request.hpp"
804e438cbSEd Tanous #include "http_response.hpp"
904e438cbSEd Tanous #include "logging.hpp"
1008bbe119SEd Tanous #include "routing/baserule.hpp"
1108bbe119SEd Tanous #include "routing/dynamicrule.hpp"
1208bbe119SEd Tanous #include "routing/taggedrule.hpp"
132c9efc3cSEd Tanous #include "verb.hpp"
1404e438cbSEd Tanous 
15*d7857201SEd Tanous #include <boost/beast/http/field.hpp>
16*d7857201SEd Tanous #include <boost/beast/http/status.hpp>
1704e438cbSEd Tanous #include <boost/container/flat_map.hpp>
18d9e89dfdSEd Tanous #include <boost/container/small_vector.hpp>
1904e438cbSEd Tanous 
20d9e89dfdSEd Tanous #include <algorithm>
21*d7857201SEd Tanous #include <array>
2204e438cbSEd Tanous #include <cerrno>
2304e438cbSEd Tanous #include <cstdint>
2404e438cbSEd Tanous #include <cstdlib>
25*d7857201SEd Tanous #include <format>
26*d7857201SEd Tanous #include <functional>
2704e438cbSEd Tanous #include <memory>
282c9efc3cSEd Tanous #include <optional>
29*d7857201SEd Tanous #include <stdexcept>
30*d7857201SEd Tanous #include <string>
31a3b9eb98SEd Tanous #include <string_view>
3204e438cbSEd Tanous #include <tuple>
3304e438cbSEd Tanous #include <utility>
3404e438cbSEd Tanous #include <vector>
3504e438cbSEd Tanous 
3604e438cbSEd Tanous namespace crow
3704e438cbSEd Tanous {
3804e438cbSEd Tanous 
3904e438cbSEd Tanous class Trie
4004e438cbSEd Tanous {
4104e438cbSEd Tanous   public:
4204e438cbSEd Tanous     struct Node
4304e438cbSEd Tanous     {
44d9e89dfdSEd Tanous         unsigned ruleIndex = 0U;
45d9e89dfdSEd Tanous 
46d9e89dfdSEd Tanous         size_t stringParamChild = 0U;
47d9e89dfdSEd Tanous         size_t pathParamChild = 0U;
48d9e89dfdSEd Tanous 
49a94ac61fSEd Tanous         using ChildMap = boost::container::flat_map<
50a94ac61fSEd Tanous             std::string, unsigned, std::less<>,
51d9e89dfdSEd Tanous             boost::container::small_vector<std::pair<std::string, unsigned>,
52d9e89dfdSEd Tanous                                            1>>;
53a94ac61fSEd Tanous         ChildMap children;
5404e438cbSEd Tanous 
isSimpleNodecrow::Trie::Node5504e438cbSEd Tanous         bool isSimpleNode() const
5604e438cbSEd Tanous         {
57d9e89dfdSEd Tanous             return ruleIndex == 0 && stringParamChild == 0 &&
58d9e89dfdSEd Tanous                    pathParamChild == 0;
5904e438cbSEd Tanous         }
6004e438cbSEd Tanous     };
6104e438cbSEd Tanous 
Trie()6289492a15SPatrick Williams     Trie() : nodes(1) {}
6304e438cbSEd Tanous 
6404e438cbSEd Tanous   private:
optimizeNode(Node & node)65d9e89dfdSEd Tanous     void optimizeNode(Node& node)
6604e438cbSEd Tanous     {
67d9e89dfdSEd Tanous         if (node.stringParamChild != 0U)
6804e438cbSEd Tanous         {
69d9e89dfdSEd Tanous             optimizeNode(nodes[node.stringParamChild]);
7004e438cbSEd Tanous         }
71d9e89dfdSEd Tanous         if (node.pathParamChild != 0U)
72d9e89dfdSEd Tanous         {
73d9e89dfdSEd Tanous             optimizeNode(nodes[node.pathParamChild]);
7404e438cbSEd Tanous         }
75d9e89dfdSEd Tanous 
76d9e89dfdSEd Tanous         if (node.children.empty())
7704e438cbSEd Tanous         {
7804e438cbSEd Tanous             return;
7904e438cbSEd Tanous         }
80d9e89dfdSEd Tanous         while (true)
8104e438cbSEd Tanous         {
82d9e89dfdSEd Tanous             bool didMerge = false;
83a94ac61fSEd Tanous             Node::ChildMap merged;
84d9e89dfdSEd Tanous             for (const Node::ChildMap::value_type& kv : node.children)
8504e438cbSEd Tanous             {
86d9e89dfdSEd Tanous                 Node& child = nodes[kv.second];
87d9e89dfdSEd Tanous                 if (child.isSimpleNode())
88d9e89dfdSEd Tanous                 {
89a94ac61fSEd Tanous                     for (const Node::ChildMap::value_type& childKv :
90d9e89dfdSEd Tanous                          child.children)
9104e438cbSEd Tanous                     {
9204e438cbSEd Tanous                         merged[kv.first + childKv.first] = childKv.second;
93d9e89dfdSEd Tanous                         didMerge = true;
9404e438cbSEd Tanous                     }
9504e438cbSEd Tanous                 }
9604e438cbSEd Tanous                 else
9704e438cbSEd Tanous                 {
98d9e89dfdSEd Tanous                     merged[kv.first] = kv.second;
9904e438cbSEd Tanous                 }
10004e438cbSEd Tanous             }
101d9e89dfdSEd Tanous             node.children = std::move(merged);
102d9e89dfdSEd Tanous             if (!didMerge)
103d9e89dfdSEd Tanous             {
104d9e89dfdSEd Tanous                 break;
105d9e89dfdSEd Tanous             }
106d9e89dfdSEd Tanous         }
107d9e89dfdSEd Tanous 
108d9e89dfdSEd Tanous         for (const Node::ChildMap::value_type& kv : node.children)
109d9e89dfdSEd Tanous         {
110d9e89dfdSEd Tanous             optimizeNode(nodes[kv.second]);
111d9e89dfdSEd Tanous         }
11204e438cbSEd Tanous     }
11304e438cbSEd Tanous 
optimize()11404e438cbSEd Tanous     void optimize()
11504e438cbSEd Tanous     {
11604e438cbSEd Tanous         optimizeNode(head());
11704e438cbSEd Tanous     }
11804e438cbSEd Tanous 
11904e438cbSEd Tanous   public:
validate()12004e438cbSEd Tanous     void validate()
12104e438cbSEd Tanous     {
12204e438cbSEd Tanous         optimize();
12304e438cbSEd Tanous     }
12404e438cbSEd Tanous 
findRouteIndexesHelper(std::string_view reqUrl,std::vector<unsigned> & routeIndexes,const Node & node) const125d9e89dfdSEd Tanous     void findRouteIndexesHelper(std::string_view reqUrl,
12681ce609eSEd Tanous                                 std::vector<unsigned>& routeIndexes,
127d9e89dfdSEd Tanous                                 const Node& node) const
12804e438cbSEd Tanous     {
129d9e89dfdSEd Tanous         for (const Node::ChildMap::value_type& kv : node.children)
13004e438cbSEd Tanous         {
13104e438cbSEd Tanous             const std::string& fragment = kv.first;
132d9e89dfdSEd Tanous             const Node& child = nodes[kv.second];
133d9e89dfdSEd Tanous             if (reqUrl.empty())
13404e438cbSEd Tanous             {
135d9e89dfdSEd Tanous                 if (child.ruleIndex != 0 && fragment != "/")
13604e438cbSEd Tanous                 {
137d9e89dfdSEd Tanous                     routeIndexes.push_back(child.ruleIndex);
13804e438cbSEd Tanous                 }
139d9e89dfdSEd Tanous                 findRouteIndexesHelper(reqUrl, routeIndexes, child);
14004e438cbSEd Tanous             }
14104e438cbSEd Tanous             else
14204e438cbSEd Tanous             {
143d9e89dfdSEd Tanous                 if (reqUrl.starts_with(fragment))
14404e438cbSEd Tanous                 {
145d9e89dfdSEd Tanous                     findRouteIndexesHelper(reqUrl.substr(fragment.size()),
146d9e89dfdSEd Tanous                                            routeIndexes, child);
14704e438cbSEd Tanous                 }
14804e438cbSEd Tanous             }
14904e438cbSEd Tanous         }
15004e438cbSEd Tanous     }
15104e438cbSEd Tanous 
findRouteIndexes(const std::string & reqUrl,std::vector<unsigned> & routeIndexes) const152d9e89dfdSEd Tanous     void findRouteIndexes(const std::string& reqUrl,
153d9e89dfdSEd Tanous                           std::vector<unsigned>& routeIndexes) const
15404e438cbSEd Tanous     {
155d9e89dfdSEd Tanous         findRouteIndexesHelper(reqUrl, routeIndexes, head());
15604e438cbSEd Tanous     }
15704e438cbSEd Tanous 
158d9e89dfdSEd Tanous     struct FindResult
15904e438cbSEd Tanous     {
160d9e89dfdSEd Tanous         unsigned ruleIndex;
161d9e89dfdSEd Tanous         std::vector<std::string> params;
16204e438cbSEd Tanous     };
16304e438cbSEd Tanous 
164d9e89dfdSEd Tanous   private:
findHelper(const std::string_view reqUrl,const Node & node,std::vector<std::string> & params) const165d9e89dfdSEd Tanous     FindResult findHelper(const std::string_view reqUrl, const Node& node,
166d9e89dfdSEd Tanous                           std::vector<std::string>& params) const
16704e438cbSEd Tanous     {
168d9e89dfdSEd Tanous         if (reqUrl.empty())
169d9e89dfdSEd Tanous         {
170d9e89dfdSEd Tanous             return {node.ruleIndex, params};
171d9e89dfdSEd Tanous         }
172d9e89dfdSEd Tanous 
173d9e89dfdSEd Tanous         if (node.stringParamChild != 0U)
174d9e89dfdSEd Tanous         {
175d9e89dfdSEd Tanous             size_t epos = 0;
17681ce609eSEd Tanous             for (; epos < reqUrl.size(); epos++)
17704e438cbSEd Tanous             {
17881ce609eSEd Tanous                 if (reqUrl[epos] == '/')
17904e438cbSEd Tanous                 {
18004e438cbSEd Tanous                     break;
18104e438cbSEd Tanous                 }
18204e438cbSEd Tanous             }
18304e438cbSEd Tanous 
184d9e89dfdSEd Tanous             if (epos != 0)
18504e438cbSEd Tanous             {
186d9e89dfdSEd Tanous                 params.emplace_back(reqUrl.substr(0, epos));
187d9e89dfdSEd Tanous                 FindResult ret = findHelper(
188d9e89dfdSEd Tanous                     reqUrl.substr(epos), nodes[node.stringParamChild], params);
189d9e89dfdSEd Tanous                 if (ret.ruleIndex != 0U)
190d9e89dfdSEd Tanous                 {
191d9e89dfdSEd Tanous                     return {ret.ruleIndex, std::move(ret.params)};
192d9e89dfdSEd Tanous                 }
193d9e89dfdSEd Tanous                 params.pop_back();
19404e438cbSEd Tanous             }
19504e438cbSEd Tanous         }
19604e438cbSEd Tanous 
197d9e89dfdSEd Tanous         if (node.pathParamChild != 0U)
19804e438cbSEd Tanous         {
199d9e89dfdSEd Tanous             params.emplace_back(reqUrl);
200d9e89dfdSEd Tanous             FindResult ret = findHelper("", nodes[node.pathParamChild], params);
201d9e89dfdSEd Tanous             if (ret.ruleIndex != 0U)
20204e438cbSEd Tanous             {
203d9e89dfdSEd Tanous                 return {ret.ruleIndex, std::move(ret.params)};
20404e438cbSEd Tanous             }
205d9e89dfdSEd Tanous             params.pop_back();
20604e438cbSEd Tanous         }
20704e438cbSEd Tanous 
208d9e89dfdSEd Tanous         for (const Node::ChildMap::value_type& kv : node.children)
20904e438cbSEd Tanous         {
21004e438cbSEd Tanous             const std::string& fragment = kv.first;
211d9e89dfdSEd Tanous             const Node& child = nodes[kv.second];
21204e438cbSEd Tanous 
213d9e89dfdSEd Tanous             if (reqUrl.starts_with(fragment))
21404e438cbSEd Tanous             {
215bd79bce8SPatrick Williams                 FindResult ret =
216bd79bce8SPatrick Williams                     findHelper(reqUrl.substr(fragment.size()), child, params);
217d9e89dfdSEd Tanous                 if (ret.ruleIndex != 0U)
218d9e89dfdSEd Tanous                 {
219d9e89dfdSEd Tanous                     return {ret.ruleIndex, std::move(ret.params)};
220d9e89dfdSEd Tanous                 }
22104e438cbSEd Tanous             }
22204e438cbSEd Tanous         }
22304e438cbSEd Tanous 
224d9e89dfdSEd Tanous         return {0U, std::vector<std::string>()};
22504e438cbSEd Tanous     }
22604e438cbSEd Tanous 
227d9e89dfdSEd Tanous   public:
find(const std::string_view reqUrl) const228d9e89dfdSEd Tanous     FindResult find(const std::string_view reqUrl) const
229d9e89dfdSEd Tanous     {
230d9e89dfdSEd Tanous         std::vector<std::string> start;
231d9e89dfdSEd Tanous         return findHelper(reqUrl, head(), start);
232d9e89dfdSEd Tanous     }
233d9e89dfdSEd Tanous 
add(std::string_view urlIn,unsigned ruleIndex)234a3b9eb98SEd Tanous     void add(std::string_view urlIn, unsigned ruleIndex)
23504e438cbSEd Tanous     {
23604e438cbSEd Tanous         size_t idx = 0;
23704e438cbSEd Tanous 
238a3b9eb98SEd Tanous         std::string_view url = urlIn;
239a3b9eb98SEd Tanous 
240d9e89dfdSEd Tanous         while (!url.empty())
24104e438cbSEd Tanous         {
242d9e89dfdSEd Tanous             char c = url[0];
24304e438cbSEd Tanous             if (c == '<')
24404e438cbSEd Tanous             {
245d9e89dfdSEd Tanous                 bool found = false;
246d9e89dfdSEd Tanous                 for (const std::string_view str1 :
247d9e89dfdSEd Tanous                      {"<str>", "<string>", "<path>"})
24804e438cbSEd Tanous                 {
249d9e89dfdSEd Tanous                     if (!url.starts_with(str1))
25004e438cbSEd Tanous                     {
251d9e89dfdSEd Tanous                         continue;
25204e438cbSEd Tanous                     }
253d9e89dfdSEd Tanous                     found = true;
254d9e89dfdSEd Tanous                     Node& node = nodes[idx];
255d9e89dfdSEd Tanous                     size_t* param = &node.stringParamChild;
256d9e89dfdSEd Tanous                     if (str1 == "<path>")
257d9e89dfdSEd Tanous                     {
258d9e89dfdSEd Tanous                         param = &node.pathParamChild;
259d9e89dfdSEd Tanous                     }
260d9e89dfdSEd Tanous                     if (*param == 0U)
261d9e89dfdSEd Tanous                     {
262d9e89dfdSEd Tanous                         *param = newNode();
263d9e89dfdSEd Tanous                     }
264d9e89dfdSEd Tanous                     idx = *param;
265d9e89dfdSEd Tanous 
266d9e89dfdSEd Tanous                     url.remove_prefix(str1.size());
26704e438cbSEd Tanous                     break;
26804e438cbSEd Tanous                 }
269d9e89dfdSEd Tanous                 if (found)
270d9e89dfdSEd Tanous                 {
271d9e89dfdSEd Tanous                     continue;
27204e438cbSEd Tanous                 }
27304e438cbSEd Tanous 
274efff2b5dSManojkiran Eda                 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
275d9e89dfdSEd Tanous                 return;
27604e438cbSEd Tanous             }
27704e438cbSEd Tanous             std::string piece(&c, 1);
278d9e89dfdSEd Tanous             if (!nodes[idx].children.contains(piece))
27904e438cbSEd Tanous             {
28004e438cbSEd Tanous                 unsigned newNodeIdx = newNode();
28104e438cbSEd Tanous                 nodes[idx].children.emplace(piece, newNodeIdx);
28204e438cbSEd Tanous             }
28304e438cbSEd Tanous             idx = nodes[idx].children[piece];
284d9e89dfdSEd Tanous             url.remove_prefix(1);
28504e438cbSEd Tanous         }
286a3b9eb98SEd Tanous         Node& node = nodes[idx];
287a3b9eb98SEd Tanous         if (node.ruleIndex != 0U)
28804e438cbSEd Tanous         {
289a3b9eb98SEd Tanous             BMCWEB_LOG_CRITICAL("handler already exists for \"{}\"", urlIn);
290d9e89dfdSEd Tanous             throw std::runtime_error(
291a3b9eb98SEd Tanous                 std::format("handler already exists for \"{}\"", urlIn));
29204e438cbSEd Tanous         }
293a3b9eb98SEd Tanous         node.ruleIndex = ruleIndex;
29404e438cbSEd Tanous     }
29504e438cbSEd Tanous 
29604e438cbSEd Tanous   private:
debugNodePrint(Node & n,size_t level)297d9e89dfdSEd Tanous     void debugNodePrint(Node& n, size_t level)
29804e438cbSEd Tanous     {
299d9e89dfdSEd Tanous         std::string spaces(level, ' ');
300d9e89dfdSEd Tanous         if (n.stringParamChild != 0U)
30104e438cbSEd Tanous         {
302d9e89dfdSEd Tanous             BMCWEB_LOG_DEBUG("{}<str>", spaces);
303d9e89dfdSEd Tanous             debugNodePrint(nodes[n.stringParamChild], level + 5);
30404e438cbSEd Tanous         }
305d9e89dfdSEd Tanous         if (n.pathParamChild != 0U)
30604e438cbSEd Tanous         {
307d9e89dfdSEd Tanous             BMCWEB_LOG_DEBUG("{} <path>", spaces);
308d9e89dfdSEd Tanous             debugNodePrint(nodes[n.pathParamChild], level + 6);
309d9e89dfdSEd Tanous         }
310d9e89dfdSEd Tanous         for (const Node::ChildMap::value_type& kv : n.children)
311d9e89dfdSEd Tanous         {
312d9e89dfdSEd Tanous             BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
313d9e89dfdSEd Tanous             debugNodePrint(nodes[kv.second], level + kv.first.size());
31404e438cbSEd Tanous         }
31504e438cbSEd Tanous     }
31604e438cbSEd Tanous 
31704e438cbSEd Tanous   public:
debugPrint()31804e438cbSEd Tanous     void debugPrint()
31904e438cbSEd Tanous     {
32004e438cbSEd Tanous         debugNodePrint(head(), 0U);
32104e438cbSEd Tanous     }
32204e438cbSEd Tanous 
32304e438cbSEd Tanous   private:
head() const324d9e89dfdSEd Tanous     const Node& head() const
32504e438cbSEd Tanous     {
326d9e89dfdSEd Tanous         return nodes.front();
32704e438cbSEd Tanous     }
32804e438cbSEd Tanous 
head()329d9e89dfdSEd Tanous     Node& head()
33004e438cbSEd Tanous     {
331d9e89dfdSEd Tanous         return nodes.front();
33204e438cbSEd Tanous     }
33304e438cbSEd Tanous 
newNode()33404e438cbSEd Tanous     unsigned newNode()
33504e438cbSEd Tanous     {
33604e438cbSEd Tanous         nodes.resize(nodes.size() + 1);
33704e438cbSEd Tanous         return static_cast<unsigned>(nodes.size() - 1);
33804e438cbSEd Tanous     }
33904e438cbSEd Tanous 
34004e438cbSEd Tanous     std::vector<Node> nodes;
34104e438cbSEd Tanous };
34204e438cbSEd Tanous 
34304e438cbSEd Tanous class Router
34404e438cbSEd Tanous {
34504e438cbSEd Tanous   public:
34604e438cbSEd Tanous     Router() = default;
34704e438cbSEd Tanous 
newRuleDynamic(const std::string & rule)34804e438cbSEd Tanous     DynamicRule& newRuleDynamic(const std::string& rule)
34904e438cbSEd Tanous     {
35004e438cbSEd Tanous         std::unique_ptr<DynamicRule> ruleObject =
35104e438cbSEd Tanous             std::make_unique<DynamicRule>(rule);
35204e438cbSEd Tanous         DynamicRule* ptr = ruleObject.get();
35304e438cbSEd Tanous         allRules.emplace_back(std::move(ruleObject));
35404e438cbSEd Tanous 
35504e438cbSEd Tanous         return *ptr;
35604e438cbSEd Tanous     }
35704e438cbSEd Tanous 
358d9e89dfdSEd Tanous     template <uint64_t NumArgs>
newRuleTagged(const std::string & rule)359cfe3bc0aSEd Tanous     auto& newRuleTagged(const std::string& rule)
36004e438cbSEd Tanous     {
361d9e89dfdSEd Tanous         if constexpr (NumArgs == 0)
362cfe3bc0aSEd Tanous         {
363cfe3bc0aSEd Tanous             using RuleT = TaggedRule<>;
36404e438cbSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
36504e438cbSEd Tanous             RuleT* ptr = ruleObject.get();
36604e438cbSEd Tanous             allRules.emplace_back(std::move(ruleObject));
36704e438cbSEd Tanous             return *ptr;
36804e438cbSEd Tanous         }
369d9e89dfdSEd Tanous         else if constexpr (NumArgs == 1)
370cfe3bc0aSEd Tanous         {
371cfe3bc0aSEd Tanous             using RuleT = TaggedRule<std::string>;
372cfe3bc0aSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
373cfe3bc0aSEd Tanous             RuleT* ptr = ruleObject.get();
374cfe3bc0aSEd Tanous             allRules.emplace_back(std::move(ruleObject));
375cfe3bc0aSEd Tanous             return *ptr;
376cfe3bc0aSEd Tanous         }
377d9e89dfdSEd Tanous         else if constexpr (NumArgs == 2)
378cfe3bc0aSEd Tanous         {
379cfe3bc0aSEd Tanous             using RuleT = TaggedRule<std::string, std::string>;
380cfe3bc0aSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
381cfe3bc0aSEd Tanous             RuleT* ptr = ruleObject.get();
382cfe3bc0aSEd Tanous             allRules.emplace_back(std::move(ruleObject));
383cfe3bc0aSEd Tanous             return *ptr;
384cfe3bc0aSEd Tanous         }
385d9e89dfdSEd Tanous         else if constexpr (NumArgs == 3)
386cfe3bc0aSEd Tanous         {
387cfe3bc0aSEd Tanous             using RuleT = TaggedRule<std::string, std::string, std::string>;
388cfe3bc0aSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
389cfe3bc0aSEd Tanous             RuleT* ptr = ruleObject.get();
390cfe3bc0aSEd Tanous             allRules.emplace_back(std::move(ruleObject));
391cfe3bc0aSEd Tanous             return *ptr;
392cfe3bc0aSEd Tanous         }
393d9e89dfdSEd Tanous         else if constexpr (NumArgs == 4)
394cfe3bc0aSEd Tanous         {
395cfe3bc0aSEd Tanous             using RuleT =
396cfe3bc0aSEd Tanous                 TaggedRule<std::string, std::string, std::string, std::string>;
397cfe3bc0aSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
398cfe3bc0aSEd Tanous             RuleT* ptr = ruleObject.get();
399cfe3bc0aSEd Tanous             allRules.emplace_back(std::move(ruleObject));
400cfe3bc0aSEd Tanous             return *ptr;
401cfe3bc0aSEd Tanous         }
402cfe3bc0aSEd Tanous         else
403cfe3bc0aSEd Tanous         {
404cfe3bc0aSEd Tanous             using RuleT = TaggedRule<std::string, std::string, std::string,
405cfe3bc0aSEd Tanous                                      std::string, std::string>;
406cfe3bc0aSEd Tanous             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
407cfe3bc0aSEd Tanous             RuleT* ptr = ruleObject.get();
408cfe3bc0aSEd Tanous             allRules.emplace_back(std::move(ruleObject));
409cfe3bc0aSEd Tanous             return *ptr;
410cfe3bc0aSEd Tanous         }
411d9e89dfdSEd Tanous         static_assert(NumArgs <= 5, "Max number of args supported is 5");
412cfe3bc0aSEd Tanous     }
41304e438cbSEd Tanous 
414a3b9eb98SEd Tanous     struct PerMethod
415a3b9eb98SEd Tanous     {
416a3b9eb98SEd Tanous         std::vector<BaseRule*> rules;
417a3b9eb98SEd Tanous         Trie trie;
418a3b9eb98SEd Tanous         // rule index 0 has special meaning; preallocate it to avoid
419a3b9eb98SEd Tanous         // duplication.
PerMethodcrow::Router::PerMethod420a3b9eb98SEd Tanous         PerMethod() : rules(1) {}
421a3b9eb98SEd Tanous 
internalAddcrow::Router::PerMethod422a3b9eb98SEd Tanous         void internalAdd(std::string_view rule, BaseRule* ruleObject)
423a3b9eb98SEd Tanous         {
424a3b9eb98SEd Tanous             rules.emplace_back(ruleObject);
425a3b9eb98SEd Tanous             trie.add(rule, static_cast<unsigned>(rules.size() - 1U));
426a3b9eb98SEd Tanous             // directory case:
427a3b9eb98SEd Tanous             //   request to `/about' url matches `/about/' rule
428a3b9eb98SEd Tanous             if (rule.size() > 2 && rule.back() == '/')
429a3b9eb98SEd Tanous             {
430a3b9eb98SEd Tanous                 trie.add(rule.substr(0, rule.size() - 1),
431a3b9eb98SEd Tanous                          static_cast<unsigned>(rules.size() - 1));
432a3b9eb98SEd Tanous             }
433a3b9eb98SEd Tanous         }
434a3b9eb98SEd Tanous     };
435a3b9eb98SEd Tanous 
internalAddRuleObject(const std::string & rule,BaseRule * ruleObject)43604e438cbSEd Tanous     void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject)
43704e438cbSEd Tanous     {
43804e438cbSEd Tanous         if (ruleObject == nullptr)
43904e438cbSEd Tanous         {
44004e438cbSEd Tanous             return;
44104e438cbSEd Tanous         }
442a3b9eb98SEd Tanous         for (size_t method = 0; method <= maxVerbIndex; method++)
44304e438cbSEd Tanous         {
444a3b9eb98SEd Tanous             size_t methodBit = 1 << method;
445e662eae8SEd Tanous             if ((ruleObject->methodsBitfield & methodBit) > 0U)
44604e438cbSEd Tanous             {
447a3b9eb98SEd Tanous                 perMethods[method].internalAdd(rule, ruleObject);
448a3b9eb98SEd Tanous             }
449a3b9eb98SEd Tanous         }
450a3b9eb98SEd Tanous 
451a3b9eb98SEd Tanous         if (ruleObject->isNotFound)
45204e438cbSEd Tanous         {
453a3b9eb98SEd Tanous             notFoundRoutes.internalAdd(rule, ruleObject);
45404e438cbSEd Tanous         }
455a3b9eb98SEd Tanous 
456a3b9eb98SEd Tanous         if (ruleObject->isMethodNotAllowed)
457a3b9eb98SEd Tanous         {
458a3b9eb98SEd Tanous             methodNotAllowedRoutes.internalAdd(rule, ruleObject);
45904e438cbSEd Tanous         }
460a3b9eb98SEd Tanous 
461a3b9eb98SEd Tanous         if (ruleObject->isUpgrade)
462a3b9eb98SEd Tanous         {
463a3b9eb98SEd Tanous             upgradeRoutes.internalAdd(rule, ruleObject);
46404e438cbSEd Tanous         }
46504e438cbSEd Tanous     }
46604e438cbSEd Tanous 
validate()46704e438cbSEd Tanous     void validate()
46804e438cbSEd Tanous     {
46904e438cbSEd Tanous         for (std::unique_ptr<BaseRule>& rule : allRules)
47004e438cbSEd Tanous         {
47104e438cbSEd Tanous             if (rule)
47204e438cbSEd Tanous             {
47304e438cbSEd Tanous                 std::unique_ptr<BaseRule> upgraded = rule->upgrade();
47404e438cbSEd Tanous                 if (upgraded)
47504e438cbSEd Tanous                 {
47604e438cbSEd Tanous                     rule = std::move(upgraded);
47704e438cbSEd Tanous                 }
47804e438cbSEd Tanous                 rule->validate();
47904e438cbSEd Tanous                 internalAddRuleObject(rule->rule, rule.get());
48004e438cbSEd Tanous             }
48104e438cbSEd Tanous         }
48204e438cbSEd Tanous         for (PerMethod& perMethod : perMethods)
48304e438cbSEd Tanous         {
48404e438cbSEd Tanous             perMethod.trie.validate();
48504e438cbSEd Tanous         }
48604e438cbSEd Tanous     }
48704e438cbSEd Tanous 
48844e4518bSEd Tanous     struct FindRoute
48944e4518bSEd Tanous     {
49044e4518bSEd Tanous         BaseRule* rule = nullptr;
49115a42df0SEd Tanous         std::vector<std::string> params;
49244e4518bSEd Tanous     };
49344e4518bSEd Tanous 
49444e4518bSEd Tanous     struct FindRouteResponse
49544e4518bSEd Tanous     {
49644e4518bSEd Tanous         std::string allowHeader;
49744e4518bSEd Tanous         FindRoute route;
49844e4518bSEd Tanous     };
49944e4518bSEd Tanous 
findRouteByPerMethod(std::string_view url,const PerMethod & perMethod)500a3b9eb98SEd Tanous     static FindRoute findRouteByPerMethod(std::string_view url,
501a3b9eb98SEd Tanous                                           const PerMethod& perMethod)
502759cf105SEd Tanous     {
503759cf105SEd Tanous         FindRoute route;
504a3b9eb98SEd Tanous 
505d9e89dfdSEd Tanous         Trie::FindResult found = perMethod.trie.find(url);
506d9e89dfdSEd Tanous         if (found.ruleIndex >= perMethod.rules.size())
507759cf105SEd Tanous         {
508759cf105SEd Tanous             throw std::runtime_error("Trie internal structure corrupted!");
509759cf105SEd Tanous         }
510759cf105SEd Tanous         // Found a 404 route, switch that in
511d9e89dfdSEd Tanous         if (found.ruleIndex != 0U)
512759cf105SEd Tanous         {
513d9e89dfdSEd Tanous             route.rule = perMethod.rules[found.ruleIndex];
514d9e89dfdSEd Tanous             route.params = std::move(found.params);
515759cf105SEd Tanous         }
516759cf105SEd Tanous         return route;
517759cf105SEd Tanous     }
518759cf105SEd Tanous 
findRoute(const Request & req) const519102a4cdaSJonathan Doman     FindRouteResponse findRoute(const Request& req) const
52044e4518bSEd Tanous     {
52144e4518bSEd Tanous         FindRouteResponse findRoute;
52244e4518bSEd Tanous 
52344e4518bSEd Tanous         // Check to see if this url exists at any verb
52444e4518bSEd Tanous         for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex;
52544e4518bSEd Tanous              perMethodIndex++)
52644e4518bSEd Tanous         {
52744e4518bSEd Tanous             // Make sure it's safe to deference the array at that index
528bd79bce8SPatrick Williams             static_assert(
529bd79bce8SPatrick Williams                 maxVerbIndex < std::tuple_size_v<decltype(perMethods)>);
530a3b9eb98SEd Tanous             FindRoute route = findRouteByPerMethod(req.url().encoded_path(),
531a3b9eb98SEd Tanous                                                    perMethods[perMethodIndex]);
532759cf105SEd Tanous             if (route.rule == nullptr)
53344e4518bSEd Tanous             {
53444e4518bSEd Tanous                 continue;
53544e4518bSEd Tanous             }
53644e4518bSEd Tanous             if (!findRoute.allowHeader.empty())
53744e4518bSEd Tanous             {
53844e4518bSEd Tanous                 findRoute.allowHeader += ", ";
53944e4518bSEd Tanous             }
5402c9efc3cSEd Tanous             HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex);
5412c9efc3cSEd Tanous             findRoute.allowHeader += httpVerbToString(thisVerb);
54250bfc917SEd Tanous         }
54350bfc917SEd Tanous 
54450bfc917SEd Tanous         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
54550bfc917SEd Tanous         if (!verb)
54650bfc917SEd Tanous         {
54750bfc917SEd Tanous             return findRoute;
54850bfc917SEd Tanous         }
54950bfc917SEd Tanous         size_t reqMethodIndex = static_cast<size_t>(*verb);
55050bfc917SEd Tanous         if (reqMethodIndex >= perMethods.size())
55150bfc917SEd Tanous         {
55250bfc917SEd Tanous             return findRoute;
55350bfc917SEd Tanous         }
55450bfc917SEd Tanous 
55550bfc917SEd Tanous         FindRoute route = findRouteByPerMethod(req.url().encoded_path(),
55650bfc917SEd Tanous                                                perMethods[reqMethodIndex]);
55750bfc917SEd Tanous         if (route.rule != nullptr)
55844e4518bSEd Tanous         {
559759cf105SEd Tanous             findRoute.route = route;
56044e4518bSEd Tanous         }
56150bfc917SEd Tanous 
56244e4518bSEd Tanous         return findRoute;
56344e4518bSEd Tanous     }
56444e4518bSEd Tanous 
56504e438cbSEd Tanous     template <typename Adaptor>
handleUpgrade(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,Adaptor && adaptor)566102a4cdaSJonathan Doman     void handleUpgrade(const std::shared_ptr<Request>& req,
567a9f076e5SP Dheeraj Srujan Kumar                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
568a9f076e5SP Dheeraj Srujan Kumar                        Adaptor&& adaptor)
56904e438cbSEd Tanous     {
570a3b9eb98SEd Tanous         PerMethod& perMethod = upgradeRoutes;
57104e438cbSEd Tanous         Trie& trie = perMethod.trie;
57204e438cbSEd Tanous         std::vector<BaseRule*>& rules = perMethod.rules;
57304e438cbSEd Tanous 
574102a4cdaSJonathan Doman         Trie::FindResult found = trie.find(req->url().encoded_path());
575d9e89dfdSEd Tanous         unsigned ruleIndex = found.ruleIndex;
576e662eae8SEd Tanous         if (ruleIndex == 0U)
57704e438cbSEd Tanous         {
578102a4cdaSJonathan Doman             BMCWEB_LOG_DEBUG("Cannot match rules {}",
579102a4cdaSJonathan Doman                              req->url().encoded_path());
580a9f076e5SP Dheeraj Srujan Kumar             asyncResp->res.result(boost::beast::http::status::not_found);
58104e438cbSEd Tanous             return;
58204e438cbSEd Tanous         }
58304e438cbSEd Tanous 
58404e438cbSEd Tanous         if (ruleIndex >= rules.size())
58504e438cbSEd Tanous         {
58604e438cbSEd Tanous             throw std::runtime_error("Trie internal structure corrupted!");
58704e438cbSEd Tanous         }
58804e438cbSEd Tanous 
5897e9093e6SP Dheeraj Srujan Kumar         BaseRule& rule = *rules[ruleIndex];
59004e438cbSEd Tanous 
591a3b9eb98SEd Tanous         BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}'", rule.rule);
59204e438cbSEd Tanous 
5937e9093e6SP Dheeraj Srujan Kumar         // TODO(ed) This should be able to use std::bind_front, but it doesn't
5947e9093e6SP Dheeraj Srujan Kumar         // appear to work with the std::move on adaptor.
595bd79bce8SPatrick Williams         validatePrivilege(
596bd79bce8SPatrick Williams             req, asyncResp, rule,
597102a4cdaSJonathan Doman             [req, &rule, asyncResp,
598102a4cdaSJonathan Doman              adaptor = std::forward<Adaptor>(adaptor)]() mutable {
599102a4cdaSJonathan Doman                 rule.handleUpgrade(*req, asyncResp, std::move(adaptor));
6007e9093e6SP Dheeraj Srujan Kumar             });
60104e438cbSEd Tanous     }
60204e438cbSEd Tanous 
handle(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)603102a4cdaSJonathan Doman     void handle(const std::shared_ptr<Request>& req,
6048d1b46d7Szhanghch05                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
60504e438cbSEd Tanous     {
606102a4cdaSJonathan Doman         FindRouteResponse foundRoute = findRoute(*req);
60744e4518bSEd Tanous 
608759cf105SEd Tanous         if (foundRoute.route.rule == nullptr)
609759cf105SEd Tanous         {
61044e4518bSEd Tanous             // Couldn't find a normal route with any verb, try looking for a 404
61144e4518bSEd Tanous             // route
61244e4518bSEd Tanous             if (foundRoute.allowHeader.empty())
61388a03c55SEd Tanous             {
614a3b9eb98SEd Tanous                 foundRoute.route = findRouteByPerMethod(
615a3b9eb98SEd Tanous                     req->url().encoded_path(), notFoundRoutes);
61644e4518bSEd Tanous             }
61744e4518bSEd Tanous             else
61844e4518bSEd Tanous             {
619759cf105SEd Tanous                 // See if we have a method not allowed (405) handler
620a3b9eb98SEd Tanous                 foundRoute.route = findRouteByPerMethod(
621a3b9eb98SEd Tanous                     req->url().encoded_path(), methodNotAllowedRoutes);
622759cf105SEd Tanous             }
623759cf105SEd Tanous         }
624759cf105SEd Tanous 
625759cf105SEd Tanous         // Fill in the allow header if it's valid
626759cf105SEd Tanous         if (!foundRoute.allowHeader.empty())
627759cf105SEd Tanous         {
62888a03c55SEd Tanous             asyncResp->res.addHeader(boost::beast::http::field::allow,
62944e4518bSEd Tanous                                      foundRoute.allowHeader);
63088a03c55SEd Tanous         }
63104e438cbSEd Tanous 
63244e4518bSEd Tanous         // If we couldn't find a real route or a 404 route, return a generic
63344e4518bSEd Tanous         // response
63444e4518bSEd Tanous         if (foundRoute.route.rule == nullptr)
63504e438cbSEd Tanous         {
63644e4518bSEd Tanous             if (foundRoute.allowHeader.empty())
63704e438cbSEd Tanous             {
6388d1b46d7Szhanghch05                 asyncResp->res.result(boost::beast::http::status::not_found);
63904e438cbSEd Tanous             }
64044e4518bSEd Tanous             else
64104e438cbSEd Tanous             {
6428d1b46d7Szhanghch05                 asyncResp->res.result(
6438d1b46d7Szhanghch05                     boost::beast::http::status::method_not_allowed);
64444e4518bSEd Tanous             }
64504e438cbSEd Tanous             return;
64604e438cbSEd Tanous         }
64704e438cbSEd Tanous 
64844e4518bSEd Tanous         BaseRule& rule = *foundRoute.route.rule;
64915a42df0SEd Tanous         std::vector<std::string> params = std::move(foundRoute.route.params);
65044e4518bSEd Tanous 
65162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule,
65250bfc917SEd Tanous                          req->methodString(), rule.getMethods());
65304e438cbSEd Tanous 
654102a4cdaSJonathan Doman         if (req->session == nullptr)
65504e438cbSEd Tanous         {
656102a4cdaSJonathan Doman             rule.handle(*req, asyncResp, params);
65704e438cbSEd Tanous             return;
65804e438cbSEd Tanous         }
659102a4cdaSJonathan Doman         validatePrivilege(
660102a4cdaSJonathan Doman             req, asyncResp, rule,
661102a4cdaSJonathan Doman             [req, asyncResp, &rule, params = std::move(params)]() {
662102a4cdaSJonathan Doman                 rule.handle(*req, asyncResp, params);
663915d2d4eSEd Tanous             });
66404e438cbSEd Tanous     }
66504e438cbSEd Tanous 
debugPrint()66604e438cbSEd Tanous     void debugPrint()
66704e438cbSEd Tanous     {
66804e438cbSEd Tanous         for (size_t i = 0; i < perMethods.size(); i++)
66904e438cbSEd Tanous         {
670d9e89dfdSEd Tanous             BMCWEB_LOG_DEBUG("{}", httpVerbToString(static_cast<HttpVerb>(i)));
67104e438cbSEd Tanous             perMethods[i].trie.debugPrint();
67204e438cbSEd Tanous         }
67304e438cbSEd Tanous     }
67404e438cbSEd Tanous 
getRoutes(const std::string & parent)67504e438cbSEd Tanous     std::vector<const std::string*> getRoutes(const std::string& parent)
67604e438cbSEd Tanous     {
67704e438cbSEd Tanous         std::vector<const std::string*> ret;
67804e438cbSEd Tanous 
67904e438cbSEd Tanous         for (const PerMethod& pm : perMethods)
68004e438cbSEd Tanous         {
68104e438cbSEd Tanous             std::vector<unsigned> x;
68204e438cbSEd Tanous             pm.trie.findRouteIndexes(parent, x);
68304e438cbSEd Tanous             for (unsigned index : x)
68404e438cbSEd Tanous             {
68504e438cbSEd Tanous                 ret.push_back(&pm.rules[index]->rule);
68604e438cbSEd Tanous             }
68704e438cbSEd Tanous         }
68804e438cbSEd Tanous         return ret;
68904e438cbSEd Tanous     }
69004e438cbSEd Tanous 
69104e438cbSEd Tanous   private:
692a3b9eb98SEd Tanous     std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods;
69304e438cbSEd Tanous 
694a3b9eb98SEd Tanous     PerMethod notFoundRoutes;
695a3b9eb98SEd Tanous     PerMethod upgradeRoutes;
696a3b9eb98SEd Tanous     PerMethod methodNotAllowedRoutes;
697a3b9eb98SEd Tanous 
69804e438cbSEd Tanous     std::vector<std::unique_ptr<BaseRule>> allRules;
69904e438cbSEd Tanous };
70004e438cbSEd Tanous } // namespace crow
701