xref: /openbmc/bmcweb/redfish-core/include/sub_route_trie.hpp (revision c1a75ebc267a78853fb26a3da8c6b3388e6ee07c)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "logging.hpp"
6 #include "routing/trie.hpp"
7 
8 #include <cerrno>
9 #include <cstdlib>
10 #include <format>
11 #include <stdexcept>
12 #include <string>
13 #include <string_view>
14 #include <vector>
15 
16 namespace crow
17 {
18 
19 struct SubRouteNode : public crow::Node
20 {
21     using ChildMap = crow::Node::ChildMap;
22     ChildMap fragmentChildren;
23 
isSimpleNodecrow::SubRouteNode24     bool isSimpleNode() const
25     {
26         return crow::Node::isSimpleNode() && fragmentChildren.empty();
27     }
28 };
29 
30 template <typename ContainedType>
31 class SubRouteTrie : public crow::Trie<ContainedType>
32 {
33   public:
34     struct FindResult
35     {
36         std::vector<std::string> params;
37         std::vector<unsigned> fragmentRuleIndexes;
38     };
39 
40   private:
findHelper(const std::string_view reqUrl,const ContainedType & node,std::vector<std::string> & params) const41     FindResult findHelper(const std::string_view reqUrl,
42                           const ContainedType& node,
43                           std::vector<std::string>& params) const
44     {
45         if (reqUrl.empty())
46         {
47             FindResult result = {params, {}};
48             for (const auto& [fragment, fragmentRuleIndex] :
49                  node.fragmentChildren)
50             {
51                 result.fragmentRuleIndexes.push_back(fragmentRuleIndex);
52             }
53             return result;
54         }
55 
56         if (node.stringParamChild != 0U)
57         {
58             size_t epos = reqUrl.find('/');
59             if (epos == std::string_view::npos)
60             {
61                 params.emplace_back(reqUrl);
62                 FindResult ret =
63                     findHelper("", this->nodes[node.stringParamChild], params);
64                 if (!ret.fragmentRuleIndexes.empty())
65                 {
66                     return ret;
67                 }
68                 params.pop_back();
69             }
70             else
71             {
72                 params.emplace_back(reqUrl.substr(0, epos));
73                 FindResult ret =
74                     findHelper(reqUrl.substr(epos),
75                                this->nodes[node.stringParamChild], params);
76                 if (!ret.fragmentRuleIndexes.empty())
77                 {
78                     return ret;
79                 }
80                 params.pop_back();
81             }
82         }
83 
84         if (node.pathParamChild != 0U)
85         {
86             params.emplace_back(reqUrl);
87             FindResult ret =
88                 findHelper("", this->nodes[node.pathParamChild], params);
89             if (!ret.fragmentRuleIndexes.empty())
90             {
91                 return ret;
92             }
93             params.pop_back();
94         }
95 
96         for (const typename ContainedType::ChildMap::value_type& kv :
97              node.children)
98         {
99             const std::string& fragment = kv.first;
100             const ContainedType& child = this->nodes[kv.second];
101 
102             if (reqUrl.starts_with(fragment))
103             {
104                 FindResult ret =
105                     findHelper(reqUrl.substr(fragment.size()), child, params);
106                 if (!ret.fragmentRuleIndexes.empty())
107                 {
108                     return ret;
109                 }
110             }
111         }
112 
113         return {std::vector<std::string>(), {}};
114     }
115 
116   public:
find(const std::string_view reqUrl) const117     FindResult find(const std::string_view reqUrl) const
118     {
119         std::vector<std::string> start;
120         return findHelper(reqUrl, this->head(), start);
121     }
122 
add(std::string_view urlIn,unsigned ruleIndex)123     void add(std::string_view urlIn, unsigned ruleIndex)
124     {
125         size_t idx = 0;
126 
127         std::string_view url = urlIn;
128 
129         std::string_view fragment;
130         // Check if the URL contains a fragment (#)
131         size_t fragmentPos = urlIn.find('#');
132         size_t queryPos = urlIn.find('?');
133         if (fragmentPos != std::string::npos && queryPos == std::string::npos &&
134             fragmentPos != urlIn.length() - 1)
135         {
136             fragment = urlIn.substr(fragmentPos + 1);
137             url = urlIn.substr(0, fragmentPos);
138         }
139 
140         if (fragment.empty())
141         {
142             BMCWEB_LOG_CRITICAL("empty fragment on rule \"{}\"", urlIn);
143             throw std::runtime_error(
144                 std::format("empty fragment on rule \"{}\"", urlIn));
145         }
146 
147         while (!url.empty())
148         {
149             char c = url[0];
150             if (c == '<')
151             {
152                 bool found = false;
153                 for (const std::string_view str1 :
154                      {"<str>", "<string>", "<path>"})
155                 {
156                     if (!url.starts_with(str1))
157                     {
158                         continue;
159                     }
160                     found = true;
161                     ContainedType& node = this->nodes[idx];
162                     size_t* param = &node.stringParamChild;
163                     if (str1 == "<path>")
164                     {
165                         param = &node.pathParamChild;
166                     }
167                     if (*param == 0U)
168                     {
169                         *param = this->newNode();
170                     }
171                     idx = *param;
172 
173                     url.remove_prefix(str1.size());
174                     break;
175                 }
176                 if (found)
177                 {
178                     continue;
179                 }
180 
181                 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
182                 return;
183             }
184             std::string piece(&c, 1);
185             if (!this->nodes[idx].children.contains(piece))
186             {
187                 unsigned newNodeIdx = this->newNode();
188                 this->nodes[idx].children.emplace(piece, newNodeIdx);
189             }
190             idx = this->nodes[idx].children[piece];
191             url.remove_prefix(1);
192         }
193         ContainedType& node = this->nodes[idx];
194         if (node.fragmentChildren.find(fragment) != node.fragmentChildren.end())
195         {
196             BMCWEB_LOG_CRITICAL(
197                 R"(fragment handler already exists for "{}" fragment "{}")",
198                 urlIn, fragment);
199             throw std::runtime_error(std::format(
200                 R"(handler already exists for url "{}" fragment "{}")", urlIn,
201                 fragment));
202         }
203 
204         node.fragmentChildren.emplace(fragment, ruleIndex);
205     }
206 
207   private:
debugNodePrint(ContainedType & n,size_t level)208     void debugNodePrint(ContainedType& n, size_t level)
209     {
210         std::string spaces(level, ' ');
211         if (n.stringParamChild != 0U)
212         {
213             BMCWEB_LOG_DEBUG("{}<str>", spaces);
214             debugNodePrint(this->nodes[n.stringParamChild], level + 5);
215         }
216         if (n.pathParamChild != 0U)
217         {
218             BMCWEB_LOG_DEBUG("{} <path>", spaces);
219             debugNodePrint(this->nodes[n.pathParamChild], level + 6);
220         }
221         for (const typename ContainedType::ChildMap::value_type& kv :
222              n.fragmentChildren)
223         {
224             BMCWEB_LOG_DEBUG("{}#{}", spaces, kv.first);
225         }
226         for (const typename ContainedType::ChildMap::value_type& kv :
227              n.children)
228         {
229             BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
230             debugNodePrint(this->nodes[kv.second], level + kv.first.size());
231         }
232     }
233 
234   public:
debugPrint()235     void debugPrint()
236     {
237         debugNodePrint(this->head(), 0U);
238     }
239 };
240 
241 } // namespace crow
242