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