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