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 23 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: 40 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: 116 FindResult find(const std::string_view reqUrl) const 117 { 118 std::vector<std::string> start; 119 return findHelper(reqUrl, this->head(), start); 120 } 121 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 ContainedType& node = this->nodes[idx]; 161 size_t* param = &node.stringParamChild; 162 if (str1 == "<path>") 163 { 164 param = &node.pathParamChild; 165 } 166 if (*param == 0U) 167 { 168 *param = this->newNode(); 169 } 170 idx = *param; 171 172 url.remove_prefix(str1.size()); 173 break; 174 } 175 if (found) 176 { 177 continue; 178 } 179 180 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn); 181 return; 182 } 183 std::string piece(&c, 1); 184 if (!this->nodes[idx].children.contains(piece)) 185 { 186 unsigned newNodeIdx = this->newNode(); 187 this->nodes[idx].children.emplace(piece, newNodeIdx); 188 } 189 idx = this->nodes[idx].children[piece]; 190 url.remove_prefix(1); 191 } 192 ContainedType& node = this->nodes[idx]; 193 if (node.fragmentChildren.find(fragment) != node.fragmentChildren.end()) 194 { 195 BMCWEB_LOG_CRITICAL( 196 R"(fragment handler already exists for "{}" fragment "{}")", 197 urlIn, fragment); 198 throw std::runtime_error(std::format( 199 R"(handler already exists for url "{}" fragment "{}")", urlIn, 200 fragment)); 201 } 202 203 node.fragmentChildren.emplace(fragment, ruleIndex); 204 } 205 206 private: 207 void debugNodePrint(ContainedType& n, size_t level) 208 { 209 std::string spaces(level, ' '); 210 if (n.stringParamChild != 0U) 211 { 212 BMCWEB_LOG_DEBUG("{}<str>", spaces); 213 debugNodePrint(this->nodes[n.stringParamChild], level + 5); 214 } 215 if (n.pathParamChild != 0U) 216 { 217 BMCWEB_LOG_DEBUG("{} <path>", spaces); 218 debugNodePrint(this->nodes[n.pathParamChild], level + 6); 219 } 220 for (const typename ContainedType::ChildMap::value_type& kv : 221 n.fragmentChildren) 222 { 223 BMCWEB_LOG_DEBUG("{}#{}", spaces, kv.first); 224 } 225 for (const typename ContainedType::ChildMap::value_type& kv : 226 n.children) 227 { 228 BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first); 229 debugNodePrint(this->nodes[kv.second], level + kv.first.size()); 230 } 231 } 232 233 public: 234 void debugPrint() 235 { 236 debugNodePrint(this->head(), 0U); 237 } 238 }; 239 240 } // namespace crow 241