1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "async_resp.hpp" 6 #include "dbus_privileges.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 #include "routing/baserule.hpp" 11 #include "routing/dynamicrule.hpp" 12 #include "routing/taggedrule.hpp" 13 #include "routing/trie.hpp" 14 #include "verb.hpp" 15 16 #include <boost/beast/http/field.hpp> 17 #include <boost/beast/http/status.hpp> 18 19 #include <algorithm> 20 #include <array> 21 #include <cstdint> 22 #include <cstdlib> 23 #include <format> 24 #include <functional> 25 #include <memory> 26 #include <optional> 27 #include <stdexcept> 28 #include <string> 29 #include <string_view> 30 #include <tuple> 31 #include <utility> 32 #include <vector> 33 34 namespace crow 35 { 36 37 class Router 38 { 39 public: 40 Router() = default; 41 newRuleDynamic(const std::string & rule)42 DynamicRule& newRuleDynamic(const std::string& rule) 43 { 44 std::unique_ptr<DynamicRule> ruleObject = 45 std::make_unique<DynamicRule>(rule); 46 DynamicRule* ptr = ruleObject.get(); 47 allRules.emplace_back(std::move(ruleObject)); 48 49 return *ptr; 50 } 51 52 template <uint64_t NumArgs> newRuleTagged(const std::string & rule)53 auto& newRuleTagged(const std::string& rule) 54 { 55 if constexpr (NumArgs == 0) 56 { 57 using RuleT = TaggedRule<>; 58 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 59 RuleT* ptr = ruleObject.get(); 60 allRules.emplace_back(std::move(ruleObject)); 61 return *ptr; 62 } 63 else if constexpr (NumArgs == 1) 64 { 65 using RuleT = TaggedRule<std::string>; 66 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 67 RuleT* ptr = ruleObject.get(); 68 allRules.emplace_back(std::move(ruleObject)); 69 return *ptr; 70 } 71 else if constexpr (NumArgs == 2) 72 { 73 using RuleT = TaggedRule<std::string, std::string>; 74 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 75 RuleT* ptr = ruleObject.get(); 76 allRules.emplace_back(std::move(ruleObject)); 77 return *ptr; 78 } 79 else if constexpr (NumArgs == 3) 80 { 81 using RuleT = TaggedRule<std::string, std::string, std::string>; 82 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 83 RuleT* ptr = ruleObject.get(); 84 allRules.emplace_back(std::move(ruleObject)); 85 return *ptr; 86 } 87 else if constexpr (NumArgs == 4) 88 { 89 using RuleT = 90 TaggedRule<std::string, std::string, std::string, std::string>; 91 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 92 RuleT* ptr = ruleObject.get(); 93 allRules.emplace_back(std::move(ruleObject)); 94 return *ptr; 95 } 96 else 97 { 98 using RuleT = TaggedRule<std::string, std::string, std::string, 99 std::string, std::string>; 100 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 101 RuleT* ptr = ruleObject.get(); 102 allRules.emplace_back(std::move(ruleObject)); 103 return *ptr; 104 } 105 static_assert(NumArgs <= 5, "Max number of args supported is 5"); 106 } 107 108 struct PerMethod 109 { 110 std::vector<BaseRule*> rules; 111 Trie<crow::Node> trie; 112 // rule index 0 has special meaning; preallocate it to avoid 113 // duplication. PerMethodcrow::Router::PerMethod114 PerMethod() : rules(1) {} 115 internalAddcrow::Router::PerMethod116 void internalAdd(std::string_view rule, BaseRule* ruleObject) 117 { 118 rules.emplace_back(ruleObject); 119 trie.add(rule, static_cast<unsigned>(rules.size() - 1U)); 120 // directory case: 121 // request to `/about' url matches `/about/' rule 122 if (rule.size() > 2 && rule.back() == '/') 123 { 124 trie.add(rule.substr(0, rule.size() - 1), 125 static_cast<unsigned>(rules.size() - 1)); 126 } 127 } 128 }; 129 internalAddRuleObject(const std::string & rule,BaseRule * ruleObject)130 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 131 { 132 if (ruleObject == nullptr) 133 { 134 return; 135 } 136 for (size_t method = 0; method <= maxVerbIndex; method++) 137 { 138 size_t methodBit = 1 << method; 139 if ((ruleObject->methodsBitfield & methodBit) > 0U) 140 { 141 perMethods[method].internalAdd(rule, ruleObject); 142 } 143 } 144 145 if (ruleObject->isNotFound) 146 { 147 notFoundRoutes.internalAdd(rule, ruleObject); 148 } 149 150 if (ruleObject->isMethodNotAllowed) 151 { 152 methodNotAllowedRoutes.internalAdd(rule, ruleObject); 153 } 154 155 if (ruleObject->isUpgrade) 156 { 157 upgradeRoutes.internalAdd(rule, ruleObject); 158 } 159 } 160 validate()161 void validate() 162 { 163 for (std::unique_ptr<BaseRule>& rule : allRules) 164 { 165 if (rule) 166 { 167 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 168 if (upgraded) 169 { 170 rule = std::move(upgraded); 171 } 172 rule->validate(); 173 internalAddRuleObject(rule->rule, rule.get()); 174 } 175 } 176 for (PerMethod& perMethod : perMethods) 177 { 178 perMethod.trie.validate(); 179 } 180 } 181 182 struct FindRoute 183 { 184 BaseRule* rule = nullptr; 185 std::vector<std::string> params; 186 }; 187 188 struct FindRouteResponse 189 { 190 std::string allowHeader; 191 FindRoute route; 192 }; 193 findRouteByPerMethod(std::string_view url,const PerMethod & perMethod)194 static FindRoute findRouteByPerMethod(std::string_view url, 195 const PerMethod& perMethod) 196 { 197 FindRoute route; 198 199 Trie<crow::Node>::FindResult found = perMethod.trie.find(url); 200 if (found.ruleIndex >= perMethod.rules.size()) 201 { 202 throw std::runtime_error("Trie internal structure corrupted!"); 203 } 204 // Found a 404 route, switch that in 205 if (found.ruleIndex != 0U) 206 { 207 route.rule = perMethod.rules[found.ruleIndex]; 208 route.params = std::move(found.params); 209 } 210 return route; 211 } 212 findRoute(const Request & req) const213 FindRouteResponse findRoute(const Request& req) const 214 { 215 FindRouteResponse findRoute; 216 217 // Check to see if this url exists at any verb 218 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 219 perMethodIndex++) 220 { 221 // Make sure it's safe to deference the array at that index 222 static_assert( 223 maxVerbIndex < std::tuple_size_v<decltype(perMethods)>); 224 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 225 perMethods[perMethodIndex]); 226 if (route.rule == nullptr) 227 { 228 continue; 229 } 230 if (!findRoute.allowHeader.empty()) 231 { 232 findRoute.allowHeader += ", "; 233 } 234 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 235 findRoute.allowHeader += httpVerbToString(thisVerb); 236 } 237 238 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 239 if (!verb) 240 { 241 return findRoute; 242 } 243 size_t reqMethodIndex = static_cast<size_t>(*verb); 244 if (reqMethodIndex >= perMethods.size()) 245 { 246 return findRoute; 247 } 248 249 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 250 perMethods[reqMethodIndex]); 251 if (route.rule != nullptr) 252 { 253 findRoute.route = route; 254 } 255 256 return findRoute; 257 } 258 259 template <typename Adaptor> handleUpgrade(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,Adaptor && adaptor)260 void handleUpgrade(const std::shared_ptr<Request>& req, 261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 262 Adaptor&& adaptor) 263 { 264 PerMethod& perMethod = upgradeRoutes; 265 Trie<crow::Node>& trie = perMethod.trie; 266 std::vector<BaseRule*>& rules = perMethod.rules; 267 268 Trie<crow::Node>::FindResult found = 269 trie.find(req->url().encoded_path()); 270 unsigned ruleIndex = found.ruleIndex; 271 if (ruleIndex == 0U) 272 { 273 BMCWEB_LOG_DEBUG("Cannot match rules {}", 274 req->url().encoded_path()); 275 asyncResp->res.result(boost::beast::http::status::not_found); 276 return; 277 } 278 279 if (ruleIndex >= rules.size()) 280 { 281 throw std::runtime_error("Trie internal structure corrupted!"); 282 } 283 284 BaseRule& rule = *rules[ruleIndex]; 285 286 BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}'", rule.rule); 287 288 // TODO(ed) This should be able to use std::bind_front, but it doesn't 289 // appear to work with the std::move on adaptor. 290 validatePrivilege( 291 req, asyncResp, rule, 292 [req, &rule, asyncResp, 293 adaptor = std::forward<Adaptor>(adaptor)]() mutable { 294 rule.handleUpgrade(*req, asyncResp, std::move(adaptor)); 295 }); 296 } 297 handle(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)298 void handle(const std::shared_ptr<Request>& req, 299 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 300 { 301 FindRouteResponse foundRoute = findRoute(*req); 302 303 if (foundRoute.route.rule == nullptr) 304 { 305 // Couldn't find a normal route with any verb, try looking for a 404 306 // route 307 if (foundRoute.allowHeader.empty()) 308 { 309 foundRoute.route = findRouteByPerMethod( 310 req->url().encoded_path(), notFoundRoutes); 311 } 312 else 313 { 314 // See if we have a method not allowed (405) handler 315 foundRoute.route = findRouteByPerMethod( 316 req->url().encoded_path(), methodNotAllowedRoutes); 317 } 318 } 319 320 // Fill in the allow header if it's valid 321 if (!foundRoute.allowHeader.empty()) 322 { 323 asyncResp->res.addHeader(boost::beast::http::field::allow, 324 foundRoute.allowHeader); 325 } 326 327 // If we couldn't find a real route or a 404 route, return a generic 328 // response 329 if (foundRoute.route.rule == nullptr) 330 { 331 if (foundRoute.allowHeader.empty()) 332 { 333 asyncResp->res.result(boost::beast::http::status::not_found); 334 } 335 else 336 { 337 asyncResp->res.result( 338 boost::beast::http::status::method_not_allowed); 339 } 340 return; 341 } 342 343 BaseRule& rule = *foundRoute.route.rule; 344 std::vector<std::string> params = std::move(foundRoute.route.params); 345 346 BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule, 347 req->methodString(), rule.getMethods()); 348 349 if (req->session == nullptr) 350 { 351 rule.handle(*req, asyncResp, params); 352 return; 353 } 354 validatePrivilege( 355 req, asyncResp, rule, 356 [req, asyncResp, &rule, params = std::move(params)]() { 357 rule.handle(*req, asyncResp, params); 358 }); 359 } 360 debugPrint()361 void debugPrint() 362 { 363 for (size_t i = 0; i < perMethods.size(); i++) 364 { 365 BMCWEB_LOG_DEBUG("{}", httpVerbToString(static_cast<HttpVerb>(i))); 366 perMethods[i].trie.debugPrint(); 367 } 368 } 369 getRoutes(const std::string & parent)370 std::vector<const std::string*> getRoutes(const std::string& parent) 371 { 372 std::vector<const std::string*> ret; 373 374 for (const PerMethod& pm : perMethods) 375 { 376 std::vector<unsigned> x; 377 pm.trie.findRouteIndexes(parent, x); 378 for (unsigned index : x) 379 { 380 ret.push_back(&pm.rules[index]->rule); 381 } 382 } 383 return ret; 384 } 385 386 private: 387 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods; 388 389 PerMethod notFoundRoutes; 390 PerMethod upgradeRoutes; 391 PerMethod methodNotAllowedRoutes; 392 393 std::vector<std::unique_ptr<BaseRule>> allRules; 394 }; 395 } // namespace crow 396