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 "verb.hpp" 14 15 #include <boost/beast/http/field.hpp> 16 #include <boost/beast/http/status.hpp> 17 #include <boost/container/flat_map.hpp> 18 #include <boost/container/small_vector.hpp> 19 20 #include <algorithm> 21 #include <array> 22 #include <cerrno> 23 #include <cstdint> 24 #include <cstdlib> 25 #include <format> 26 #include <functional> 27 #include <memory> 28 #include <optional> 29 #include <stdexcept> 30 #include <string> 31 #include <string_view> 32 #include <tuple> 33 #include <utility> 34 #include <vector> 35 36 namespace crow 37 { 38 39 class Trie 40 { 41 public: 42 struct Node 43 { 44 unsigned ruleIndex = 0U; 45 46 size_t stringParamChild = 0U; 47 size_t pathParamChild = 0U; 48 49 using ChildMap = boost::container::flat_map< 50 std::string, unsigned, std::less<>, 51 boost::container::small_vector<std::pair<std::string, unsigned>, 52 1>>; 53 ChildMap children; 54 isSimpleNodecrow::Trie::Node55 bool isSimpleNode() const 56 { 57 return ruleIndex == 0 && stringParamChild == 0 && 58 pathParamChild == 0; 59 } 60 }; 61 Trie()62 Trie() : nodes(1) {} 63 64 private: optimizeNode(Node & node)65 void optimizeNode(Node& node) 66 { 67 if (node.stringParamChild != 0U) 68 { 69 optimizeNode(nodes[node.stringParamChild]); 70 } 71 if (node.pathParamChild != 0U) 72 { 73 optimizeNode(nodes[node.pathParamChild]); 74 } 75 76 if (node.children.empty()) 77 { 78 return; 79 } 80 while (true) 81 { 82 bool didMerge = false; 83 Node::ChildMap merged; 84 for (const Node::ChildMap::value_type& kv : node.children) 85 { 86 Node& child = nodes[kv.second]; 87 if (child.isSimpleNode()) 88 { 89 for (const Node::ChildMap::value_type& childKv : 90 child.children) 91 { 92 merged[kv.first + childKv.first] = childKv.second; 93 didMerge = true; 94 } 95 } 96 else 97 { 98 merged[kv.first] = kv.second; 99 } 100 } 101 node.children = std::move(merged); 102 if (!didMerge) 103 { 104 break; 105 } 106 } 107 108 for (const Node::ChildMap::value_type& kv : node.children) 109 { 110 optimizeNode(nodes[kv.second]); 111 } 112 } 113 optimize()114 void optimize() 115 { 116 optimizeNode(head()); 117 } 118 119 public: validate()120 void validate() 121 { 122 optimize(); 123 } 124 findRouteIndexesHelper(std::string_view reqUrl,std::vector<unsigned> & routeIndexes,const Node & node) const125 void findRouteIndexesHelper(std::string_view reqUrl, 126 std::vector<unsigned>& routeIndexes, 127 const Node& node) const 128 { 129 for (const Node::ChildMap::value_type& kv : node.children) 130 { 131 const std::string& fragment = kv.first; 132 const Node& child = nodes[kv.second]; 133 if (reqUrl.empty()) 134 { 135 if (child.ruleIndex != 0 && fragment != "/") 136 { 137 routeIndexes.push_back(child.ruleIndex); 138 } 139 findRouteIndexesHelper(reqUrl, routeIndexes, child); 140 } 141 else 142 { 143 if (reqUrl.starts_with(fragment)) 144 { 145 findRouteIndexesHelper(reqUrl.substr(fragment.size()), 146 routeIndexes, child); 147 } 148 } 149 } 150 } 151 findRouteIndexes(const std::string & reqUrl,std::vector<unsigned> & routeIndexes) const152 void findRouteIndexes(const std::string& reqUrl, 153 std::vector<unsigned>& routeIndexes) const 154 { 155 findRouteIndexesHelper(reqUrl, routeIndexes, head()); 156 } 157 158 struct FindResult 159 { 160 unsigned ruleIndex; 161 std::vector<std::string> params; 162 }; 163 164 private: findHelper(const std::string_view reqUrl,const Node & node,std::vector<std::string> & params) const165 FindResult findHelper(const std::string_view reqUrl, const Node& node, 166 std::vector<std::string>& params) const 167 { 168 if (reqUrl.empty()) 169 { 170 return {node.ruleIndex, params}; 171 } 172 173 if (node.stringParamChild != 0U) 174 { 175 size_t epos = 0; 176 for (; epos < reqUrl.size(); epos++) 177 { 178 if (reqUrl[epos] == '/') 179 { 180 break; 181 } 182 } 183 184 if (epos != 0) 185 { 186 params.emplace_back(reqUrl.substr(0, epos)); 187 FindResult ret = findHelper( 188 reqUrl.substr(epos), nodes[node.stringParamChild], params); 189 if (ret.ruleIndex != 0U) 190 { 191 return {ret.ruleIndex, std::move(ret.params)}; 192 } 193 params.pop_back(); 194 } 195 } 196 197 if (node.pathParamChild != 0U) 198 { 199 params.emplace_back(reqUrl); 200 FindResult ret = findHelper("", nodes[node.pathParamChild], params); 201 if (ret.ruleIndex != 0U) 202 { 203 return {ret.ruleIndex, std::move(ret.params)}; 204 } 205 params.pop_back(); 206 } 207 208 for (const Node::ChildMap::value_type& kv : node.children) 209 { 210 const std::string& fragment = kv.first; 211 const Node& child = nodes[kv.second]; 212 213 if (reqUrl.starts_with(fragment)) 214 { 215 FindResult ret = 216 findHelper(reqUrl.substr(fragment.size()), child, params); 217 if (ret.ruleIndex != 0U) 218 { 219 return {ret.ruleIndex, std::move(ret.params)}; 220 } 221 } 222 } 223 224 return {0U, std::vector<std::string>()}; 225 } 226 227 public: find(const std::string_view reqUrl) const228 FindResult find(const std::string_view reqUrl) const 229 { 230 std::vector<std::string> start; 231 return findHelper(reqUrl, head(), start); 232 } 233 add(std::string_view urlIn,unsigned ruleIndex)234 void add(std::string_view urlIn, unsigned ruleIndex) 235 { 236 size_t idx = 0; 237 238 std::string_view url = urlIn; 239 240 while (!url.empty()) 241 { 242 char c = url[0]; 243 if (c == '<') 244 { 245 bool found = false; 246 for (const std::string_view str1 : 247 {"<str>", "<string>", "<path>"}) 248 { 249 if (!url.starts_with(str1)) 250 { 251 continue; 252 } 253 found = true; 254 Node& node = nodes[idx]; 255 size_t* param = &node.stringParamChild; 256 if (str1 == "<path>") 257 { 258 param = &node.pathParamChild; 259 } 260 if (*param == 0U) 261 { 262 *param = newNode(); 263 } 264 idx = *param; 265 266 url.remove_prefix(str1.size()); 267 break; 268 } 269 if (found) 270 { 271 continue; 272 } 273 274 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn); 275 return; 276 } 277 std::string piece(&c, 1); 278 if (!nodes[idx].children.contains(piece)) 279 { 280 unsigned newNodeIdx = newNode(); 281 nodes[idx].children.emplace(piece, newNodeIdx); 282 } 283 idx = nodes[idx].children[piece]; 284 url.remove_prefix(1); 285 } 286 Node& node = nodes[idx]; 287 if (node.ruleIndex != 0U) 288 { 289 BMCWEB_LOG_CRITICAL("handler already exists for \"{}\"", urlIn); 290 throw std::runtime_error( 291 std::format("handler already exists for \"{}\"", urlIn)); 292 } 293 node.ruleIndex = ruleIndex; 294 } 295 296 private: debugNodePrint(Node & n,size_t level)297 void debugNodePrint(Node& n, size_t level) 298 { 299 std::string spaces(level, ' '); 300 if (n.stringParamChild != 0U) 301 { 302 BMCWEB_LOG_DEBUG("{}<str>", spaces); 303 debugNodePrint(nodes[n.stringParamChild], level + 5); 304 } 305 if (n.pathParamChild != 0U) 306 { 307 BMCWEB_LOG_DEBUG("{} <path>", spaces); 308 debugNodePrint(nodes[n.pathParamChild], level + 6); 309 } 310 for (const Node::ChildMap::value_type& kv : n.children) 311 { 312 BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first); 313 debugNodePrint(nodes[kv.second], level + kv.first.size()); 314 } 315 } 316 317 public: debugPrint()318 void debugPrint() 319 { 320 debugNodePrint(head(), 0U); 321 } 322 323 private: head() const324 const Node& head() const 325 { 326 return nodes.front(); 327 } 328 head()329 Node& head() 330 { 331 return nodes.front(); 332 } 333 newNode()334 unsigned newNode() 335 { 336 nodes.resize(nodes.size() + 1); 337 return static_cast<unsigned>(nodes.size() - 1); 338 } 339 340 std::vector<Node> nodes; 341 }; 342 343 class Router 344 { 345 public: 346 Router() = default; 347 newRuleDynamic(const std::string & rule)348 DynamicRule& newRuleDynamic(const std::string& rule) 349 { 350 std::unique_ptr<DynamicRule> ruleObject = 351 std::make_unique<DynamicRule>(rule); 352 DynamicRule* ptr = ruleObject.get(); 353 allRules.emplace_back(std::move(ruleObject)); 354 355 return *ptr; 356 } 357 358 template <uint64_t NumArgs> newRuleTagged(const std::string & rule)359 auto& newRuleTagged(const std::string& rule) 360 { 361 if constexpr (NumArgs == 0) 362 { 363 using RuleT = TaggedRule<>; 364 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 365 RuleT* ptr = ruleObject.get(); 366 allRules.emplace_back(std::move(ruleObject)); 367 return *ptr; 368 } 369 else if constexpr (NumArgs == 1) 370 { 371 using RuleT = TaggedRule<std::string>; 372 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 373 RuleT* ptr = ruleObject.get(); 374 allRules.emplace_back(std::move(ruleObject)); 375 return *ptr; 376 } 377 else if constexpr (NumArgs == 2) 378 { 379 using RuleT = TaggedRule<std::string, std::string>; 380 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 381 RuleT* ptr = ruleObject.get(); 382 allRules.emplace_back(std::move(ruleObject)); 383 return *ptr; 384 } 385 else if constexpr (NumArgs == 3) 386 { 387 using RuleT = TaggedRule<std::string, std::string, std::string>; 388 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 389 RuleT* ptr = ruleObject.get(); 390 allRules.emplace_back(std::move(ruleObject)); 391 return *ptr; 392 } 393 else if constexpr (NumArgs == 4) 394 { 395 using RuleT = 396 TaggedRule<std::string, std::string, std::string, std::string>; 397 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 398 RuleT* ptr = ruleObject.get(); 399 allRules.emplace_back(std::move(ruleObject)); 400 return *ptr; 401 } 402 else 403 { 404 using RuleT = TaggedRule<std::string, std::string, std::string, 405 std::string, std::string>; 406 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 407 RuleT* ptr = ruleObject.get(); 408 allRules.emplace_back(std::move(ruleObject)); 409 return *ptr; 410 } 411 static_assert(NumArgs <= 5, "Max number of args supported is 5"); 412 } 413 414 struct PerMethod 415 { 416 std::vector<BaseRule*> rules; 417 Trie trie; 418 // rule index 0 has special meaning; preallocate it to avoid 419 // duplication. PerMethodcrow::Router::PerMethod420 PerMethod() : rules(1) {} 421 internalAddcrow::Router::PerMethod422 void internalAdd(std::string_view rule, BaseRule* ruleObject) 423 { 424 rules.emplace_back(ruleObject); 425 trie.add(rule, static_cast<unsigned>(rules.size() - 1U)); 426 // directory case: 427 // request to `/about' url matches `/about/' rule 428 if (rule.size() > 2 && rule.back() == '/') 429 { 430 trie.add(rule.substr(0, rule.size() - 1), 431 static_cast<unsigned>(rules.size() - 1)); 432 } 433 } 434 }; 435 internalAddRuleObject(const std::string & rule,BaseRule * ruleObject)436 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 437 { 438 if (ruleObject == nullptr) 439 { 440 return; 441 } 442 for (size_t method = 0; method <= maxVerbIndex; method++) 443 { 444 size_t methodBit = 1 << method; 445 if ((ruleObject->methodsBitfield & methodBit) > 0U) 446 { 447 perMethods[method].internalAdd(rule, ruleObject); 448 } 449 } 450 451 if (ruleObject->isNotFound) 452 { 453 notFoundRoutes.internalAdd(rule, ruleObject); 454 } 455 456 if (ruleObject->isMethodNotAllowed) 457 { 458 methodNotAllowedRoutes.internalAdd(rule, ruleObject); 459 } 460 461 if (ruleObject->isUpgrade) 462 { 463 upgradeRoutes.internalAdd(rule, ruleObject); 464 } 465 } 466 validate()467 void validate() 468 { 469 for (std::unique_ptr<BaseRule>& rule : allRules) 470 { 471 if (rule) 472 { 473 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 474 if (upgraded) 475 { 476 rule = std::move(upgraded); 477 } 478 rule->validate(); 479 internalAddRuleObject(rule->rule, rule.get()); 480 } 481 } 482 for (PerMethod& perMethod : perMethods) 483 { 484 perMethod.trie.validate(); 485 } 486 } 487 488 struct FindRoute 489 { 490 BaseRule* rule = nullptr; 491 std::vector<std::string> params; 492 }; 493 494 struct FindRouteResponse 495 { 496 std::string allowHeader; 497 FindRoute route; 498 }; 499 findRouteByPerMethod(std::string_view url,const PerMethod & perMethod)500 static FindRoute findRouteByPerMethod(std::string_view url, 501 const PerMethod& perMethod) 502 { 503 FindRoute route; 504 505 Trie::FindResult found = perMethod.trie.find(url); 506 if (found.ruleIndex >= perMethod.rules.size()) 507 { 508 throw std::runtime_error("Trie internal structure corrupted!"); 509 } 510 // Found a 404 route, switch that in 511 if (found.ruleIndex != 0U) 512 { 513 route.rule = perMethod.rules[found.ruleIndex]; 514 route.params = std::move(found.params); 515 } 516 return route; 517 } 518 findRoute(const Request & req) const519 FindRouteResponse findRoute(const Request& req) const 520 { 521 FindRouteResponse findRoute; 522 523 // Check to see if this url exists at any verb 524 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 525 perMethodIndex++) 526 { 527 // Make sure it's safe to deference the array at that index 528 static_assert( 529 maxVerbIndex < std::tuple_size_v<decltype(perMethods)>); 530 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 531 perMethods[perMethodIndex]); 532 if (route.rule == nullptr) 533 { 534 continue; 535 } 536 if (!findRoute.allowHeader.empty()) 537 { 538 findRoute.allowHeader += ", "; 539 } 540 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 541 findRoute.allowHeader += httpVerbToString(thisVerb); 542 } 543 544 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 545 if (!verb) 546 { 547 return findRoute; 548 } 549 size_t reqMethodIndex = static_cast<size_t>(*verb); 550 if (reqMethodIndex >= perMethods.size()) 551 { 552 return findRoute; 553 } 554 555 FindRoute route = findRouteByPerMethod(req.url().encoded_path(), 556 perMethods[reqMethodIndex]); 557 if (route.rule != nullptr) 558 { 559 findRoute.route = route; 560 } 561 562 return findRoute; 563 } 564 565 template <typename Adaptor> handleUpgrade(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,Adaptor && adaptor)566 void handleUpgrade(const std::shared_ptr<Request>& req, 567 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 568 Adaptor&& adaptor) 569 { 570 PerMethod& perMethod = upgradeRoutes; 571 Trie& trie = perMethod.trie; 572 std::vector<BaseRule*>& rules = perMethod.rules; 573 574 Trie::FindResult found = trie.find(req->url().encoded_path()); 575 unsigned ruleIndex = found.ruleIndex; 576 if (ruleIndex == 0U) 577 { 578 BMCWEB_LOG_DEBUG("Cannot match rules {}", 579 req->url().encoded_path()); 580 asyncResp->res.result(boost::beast::http::status::not_found); 581 return; 582 } 583 584 if (ruleIndex >= rules.size()) 585 { 586 throw std::runtime_error("Trie internal structure corrupted!"); 587 } 588 589 BaseRule& rule = *rules[ruleIndex]; 590 591 BMCWEB_LOG_DEBUG("Matched rule (upgrade) '{}'", rule.rule); 592 593 // TODO(ed) This should be able to use std::bind_front, but it doesn't 594 // appear to work with the std::move on adaptor. 595 validatePrivilege( 596 req, asyncResp, rule, 597 [req, &rule, asyncResp, 598 adaptor = std::forward<Adaptor>(adaptor)]() mutable { 599 rule.handleUpgrade(*req, asyncResp, std::move(adaptor)); 600 }); 601 } 602 handle(const std::shared_ptr<Request> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)603 void handle(const std::shared_ptr<Request>& req, 604 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 605 { 606 FindRouteResponse foundRoute = findRoute(*req); 607 608 if (foundRoute.route.rule == nullptr) 609 { 610 // Couldn't find a normal route with any verb, try looking for a 404 611 // route 612 if (foundRoute.allowHeader.empty()) 613 { 614 foundRoute.route = findRouteByPerMethod( 615 req->url().encoded_path(), notFoundRoutes); 616 } 617 else 618 { 619 // See if we have a method not allowed (405) handler 620 foundRoute.route = findRouteByPerMethod( 621 req->url().encoded_path(), methodNotAllowedRoutes); 622 } 623 } 624 625 // Fill in the allow header if it's valid 626 if (!foundRoute.allowHeader.empty()) 627 { 628 asyncResp->res.addHeader(boost::beast::http::field::allow, 629 foundRoute.allowHeader); 630 } 631 632 // If we couldn't find a real route or a 404 route, return a generic 633 // response 634 if (foundRoute.route.rule == nullptr) 635 { 636 if (foundRoute.allowHeader.empty()) 637 { 638 asyncResp->res.result(boost::beast::http::status::not_found); 639 } 640 else 641 { 642 asyncResp->res.result( 643 boost::beast::http::status::method_not_allowed); 644 } 645 return; 646 } 647 648 BaseRule& rule = *foundRoute.route.rule; 649 std::vector<std::string> params = std::move(foundRoute.route.params); 650 651 BMCWEB_LOG_DEBUG("Matched rule '{}' {} / {}", rule.rule, 652 req->methodString(), rule.getMethods()); 653 654 if (req->session == nullptr) 655 { 656 rule.handle(*req, asyncResp, params); 657 return; 658 } 659 validatePrivilege( 660 req, asyncResp, rule, 661 [req, asyncResp, &rule, params = std::move(params)]() { 662 rule.handle(*req, asyncResp, params); 663 }); 664 } 665 debugPrint()666 void debugPrint() 667 { 668 for (size_t i = 0; i < perMethods.size(); i++) 669 { 670 BMCWEB_LOG_DEBUG("{}", httpVerbToString(static_cast<HttpVerb>(i))); 671 perMethods[i].trie.debugPrint(); 672 } 673 } 674 getRoutes(const std::string & parent)675 std::vector<const std::string*> getRoutes(const std::string& parent) 676 { 677 std::vector<const std::string*> ret; 678 679 for (const PerMethod& pm : perMethods) 680 { 681 std::vector<unsigned> x; 682 pm.trie.findRouteIndexes(parent, x); 683 for (unsigned index : x) 684 { 685 ret.push_back(&pm.rules[index]->rule); 686 } 687 } 688 return ret; 689 } 690 691 private: 692 std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods; 693 694 PerMethod notFoundRoutes; 695 PerMethod upgradeRoutes; 696 PerMethod methodNotAllowedRoutes; 697 698 std::vector<std::unique_ptr<BaseRule>> allRules; 699 }; 700 } // namespace crow 701