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