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