1 #pragma once 2 3 #include "common.hpp" 4 #include "error_messages.hpp" 5 #include "http_request.hpp" 6 #include "http_response.hpp" 7 #include "logging.hpp" 8 #include "privileges.hpp" 9 #include "sessions.hpp" 10 #include "utility.hpp" 11 #include "websocket.hpp" 12 13 #include <async_resp.hpp> 14 #include <boost/container/flat_map.hpp> 15 #include <boost/container/small_vector.hpp> 16 #include <boost/lexical_cast.hpp> 17 18 #include <cerrno> 19 #include <cstdint> 20 #include <cstdlib> 21 #include <limits> 22 #include <memory> 23 #include <tuple> 24 #include <utility> 25 #include <vector> 26 27 namespace crow 28 { 29 30 class BaseRule 31 { 32 public: 33 BaseRule(const std::string& thisRule) : rule(thisRule) 34 {} 35 36 virtual ~BaseRule() = default; 37 38 virtual void validate() = 0; 39 std::unique_ptr<BaseRule> upgrade() 40 { 41 if (ruleToUpgrade) 42 { 43 return std::move(ruleToUpgrade); 44 } 45 return {}; 46 } 47 48 virtual void handle(const Request&, 49 const std::shared_ptr<bmcweb::AsyncResp>&, 50 const RoutingParams&) = 0; 51 virtual void handleUpgrade(const Request&, Response& res, 52 boost::asio::ip::tcp::socket&&) 53 { 54 res.result(boost::beast::http::status::not_found); 55 res.end(); 56 } 57 #ifdef BMCWEB_ENABLE_SSL 58 virtual void 59 handleUpgrade(const Request&, Response& res, 60 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&&) 61 { 62 res.result(boost::beast::http::status::not_found); 63 res.end(); 64 } 65 #endif 66 67 size_t getMethods() 68 { 69 return methodsBitfield; 70 } 71 72 bool checkPrivileges(const redfish::Privileges& userPrivileges) 73 { 74 // If there are no privileges assigned, assume no privileges 75 // required 76 if (privilegesSet.empty()) 77 { 78 return true; 79 } 80 81 for (const redfish::Privileges& requiredPrivileges : privilegesSet) 82 { 83 if (userPrivileges.isSupersetOf(requiredPrivileges)) 84 { 85 return true; 86 } 87 } 88 return false; 89 } 90 91 size_t methodsBitfield{ 92 1 << static_cast<size_t>(boost::beast::http::verb::get)}; 93 94 std::vector<redfish::Privileges> privilegesSet; 95 96 std::string rule; 97 std::string nameStr; 98 99 std::unique_ptr<BaseRule> ruleToUpgrade; 100 101 friend class Router; 102 template <typename T> 103 friend struct RuleParameterTraits; 104 }; 105 106 namespace detail 107 { 108 namespace routing_handler_call_helper 109 { 110 template <typename T, int Pos> 111 struct CallPair 112 { 113 using type = T; 114 static const int pos = Pos; 115 }; 116 117 template <typename H1> 118 struct CallParams 119 { 120 H1& handler; 121 const RoutingParams& params; 122 const Request& req; 123 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; 124 }; 125 126 template <typename F, int NInt, int NUint, int NDouble, int NString, 127 typename S1, typename S2> 128 struct Call 129 {}; 130 131 template <typename F, int NInt, int NUint, int NDouble, int NString, 132 typename... Args1, typename... Args2> 133 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, 134 black_magic::S<Args2...>> 135 { 136 void operator()(F cparams) 137 { 138 using pushed = typename black_magic::S<Args2...>::template push_back< 139 CallPair<int64_t, NInt>>; 140 Call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, 141 pushed>()(cparams); 142 } 143 }; 144 145 template <typename F, int NInt, int NUint, int NDouble, int NString, 146 typename... Args1, typename... Args2> 147 struct Call<F, NInt, NUint, NDouble, NString, 148 black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> 149 { 150 void operator()(F cparams) 151 { 152 using pushed = typename black_magic::S<Args2...>::template push_back< 153 CallPair<uint64_t, NUint>>; 154 Call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, 155 pushed>()(cparams); 156 } 157 }; 158 159 template <typename F, int NInt, int NUint, int NDouble, int NString, 160 typename... Args1, typename... Args2> 161 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, 162 black_magic::S<Args2...>> 163 { 164 void operator()(F cparams) 165 { 166 using pushed = typename black_magic::S<Args2...>::template push_back< 167 CallPair<double, NDouble>>; 168 Call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, 169 pushed>()(cparams); 170 } 171 }; 172 173 template <typename F, int NInt, int NUint, int NDouble, int NString, 174 typename... Args1, typename... Args2> 175 struct Call<F, NInt, NUint, NDouble, NString, 176 black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> 177 { 178 void operator()(F cparams) 179 { 180 using pushed = typename black_magic::S<Args2...>::template push_back< 181 CallPair<std::string, NString>>; 182 Call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, 183 pushed>()(cparams); 184 } 185 }; 186 187 template <typename F, int NInt, int NUint, int NDouble, int NString, 188 typename... Args1> 189 struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>, 190 black_magic::S<Args1...>> 191 { 192 void operator()(F cparams) 193 { 194 cparams.handler( 195 cparams.req, cparams.asyncResp, 196 cparams.params.template get<typename Args1::type>(Args1::pos)...); 197 } 198 }; 199 200 template <typename Func, typename... ArgsWrapped> 201 struct Wrapped 202 { 203 template <typename... Args> 204 void set( 205 Func f, 206 typename std::enable_if< 207 !std::is_same< 208 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 209 const Request&>::value, 210 int>::type = 0) 211 { 212 handler = [f = std::move(f)]( 213 const Request&, 214 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 215 Args... args) { asyncResp->res.result(f(args...)); }; 216 } 217 218 template <typename Req, typename... Args> 219 struct ReqHandlerWrapper 220 { 221 ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) 222 {} 223 224 void operator()(const Request& req, 225 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 226 Args... args) 227 { 228 asyncResp->res.result(f(req, args...)); 229 } 230 231 Func f; 232 }; 233 234 template <typename... Args> 235 void set( 236 Func f, 237 typename std::enable_if< 238 std::is_same< 239 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 240 const Request&>::value && 241 !std::is_same<typename std::tuple_element< 242 1, std::tuple<Args..., void, void>>::type, 243 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 244 int>::type = 0) 245 { 246 handler = ReqHandlerWrapper<Args...>(std::move(f)); 247 /*handler = ( 248 [f = std::move(f)] 249 (const Request& req, Response& res, Args... args){ 250 res.result(f(req, args...)); 251 res.end(); 252 });*/ 253 } 254 255 template <typename... Args> 256 void set( 257 Func f, 258 typename std::enable_if< 259 std::is_same< 260 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 261 const Request&>::value && 262 std::is_same<typename std::tuple_element< 263 1, std::tuple<Args..., void, void>>::type, 264 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 265 int>::type = 0) 266 { 267 handler = std::move(f); 268 } 269 270 template <typename... Args> 271 struct HandlerTypeHelper 272 { 273 using type = std::function<void( 274 const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&, 275 Args...)>; 276 using args_type = 277 black_magic::S<typename black_magic::PromoteT<Args>...>; 278 }; 279 280 template <typename... Args> 281 struct HandlerTypeHelper<const Request&, Args...> 282 { 283 using type = std::function<void( 284 const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&, 285 Args...)>; 286 using args_type = 287 black_magic::S<typename black_magic::PromoteT<Args>...>; 288 }; 289 290 template <typename... Args> 291 struct HandlerTypeHelper<const Request&, 292 const std::shared_ptr<bmcweb::AsyncResp>&, Args...> 293 { 294 using type = std::function<void( 295 const crow::Request&, const std::shared_ptr<bmcweb::AsyncResp>&, 296 Args...)>; 297 using args_type = 298 black_magic::S<typename black_magic::PromoteT<Args>...>; 299 }; 300 301 typename HandlerTypeHelper<ArgsWrapped...>::type handler; 302 303 void operator()(const Request& req, 304 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 305 const RoutingParams& params) 306 { 307 detail::routing_handler_call_helper::Call< 308 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 309 0, 0, 0, 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, 310 black_magic::S<>>()( 311 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 312 handler, params, req, asyncResp}); 313 } 314 }; 315 } // namespace routing_handler_call_helper 316 } // namespace detail 317 318 class WebSocketRule : public BaseRule 319 { 320 using self_t = WebSocketRule; 321 322 public: 323 WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) 324 {} 325 326 void validate() override 327 {} 328 329 void handle(const Request&, 330 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 331 const RoutingParams&) override 332 { 333 asyncResp->res.result(boost::beast::http::status::not_found); 334 } 335 336 void handleUpgrade(const Request& req, Response&, 337 boost::asio::ip::tcp::socket&& adaptor) override 338 { 339 std::shared_ptr< 340 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>> 341 myConnection = std::make_shared< 342 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>( 343 req, std::move(adaptor), openHandler, messageHandler, 344 closeHandler, errorHandler); 345 myConnection->start(); 346 } 347 #ifdef BMCWEB_ENABLE_SSL 348 void handleUpgrade(const Request& req, Response&, 349 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& 350 adaptor) override 351 { 352 std::shared_ptr<crow::websocket::ConnectionImpl< 353 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> 354 myConnection = std::make_shared<crow::websocket::ConnectionImpl< 355 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( 356 req, std::move(adaptor), openHandler, messageHandler, 357 closeHandler, errorHandler); 358 myConnection->start(); 359 } 360 #endif 361 362 template <typename Func> 363 self_t& onopen(Func f) 364 { 365 openHandler = f; 366 return *this; 367 } 368 369 template <typename Func> 370 self_t& onmessage(Func f) 371 { 372 messageHandler = f; 373 return *this; 374 } 375 376 template <typename Func> 377 self_t& onclose(Func f) 378 { 379 closeHandler = f; 380 return *this; 381 } 382 383 template <typename Func> 384 self_t& onerror(Func f) 385 { 386 errorHandler = f; 387 return *this; 388 } 389 390 protected: 391 std::function<void(crow::websocket::Connection&, 392 std::shared_ptr<bmcweb::AsyncResp>)> 393 openHandler; 394 std::function<void(crow::websocket::Connection&, const std::string&, bool)> 395 messageHandler; 396 std::function<void(crow::websocket::Connection&, const std::string&)> 397 closeHandler; 398 std::function<void(crow::websocket::Connection&)> errorHandler; 399 }; 400 401 template <typename T> 402 struct RuleParameterTraits 403 { 404 using self_t = T; 405 WebSocketRule& websocket() 406 { 407 self_t* self = static_cast<self_t*>(this); 408 WebSocketRule* p = new WebSocketRule(self->rule); 409 self->ruleToUpgrade.reset(p); 410 return *p; 411 } 412 413 self_t& name(const std::string_view name) noexcept 414 { 415 self_t* self = static_cast<self_t*>(this); 416 self->nameStr = name; 417 return *self; 418 } 419 420 self_t& methods(boost::beast::http::verb method) 421 { 422 self_t* self = static_cast<self_t*>(this); 423 self->methodsBitfield = 1U << static_cast<size_t>(method); 424 return *self; 425 } 426 427 template <typename... MethodArgs> 428 self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) 429 { 430 self_t* self = static_cast<self_t*>(this); 431 methods(argsMethod...); 432 self->methodsBitfield |= 1U << static_cast<size_t>(method); 433 return *self; 434 } 435 436 template <typename... MethodArgs> 437 self_t& privileges(std::initializer_list<const char*> l) 438 { 439 self_t* self = static_cast<self_t*>(this); 440 self->privilegesSet.emplace_back(l); 441 return *self; 442 } 443 444 template <typename... MethodArgs> 445 self_t& privileges(const std::vector<redfish::Privileges>& p) 446 { 447 self_t* self = static_cast<self_t*>(this); 448 for (const redfish::Privileges& privilege : p) 449 { 450 self->privilegesSet.emplace_back(privilege); 451 } 452 return *self; 453 } 454 }; 455 456 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> 457 { 458 public: 459 DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) 460 {} 461 462 void validate() override 463 { 464 if (!erasedHandler) 465 { 466 throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + 467 "no handler for url " + rule); 468 } 469 } 470 471 void handle(const Request& req, 472 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 473 const RoutingParams& params) override 474 { 475 erasedHandler(req, asyncResp, params); 476 } 477 478 template <typename Func> 479 void operator()(Func f) 480 { 481 using function_t = utility::function_traits<Func>; 482 erasedHandler = 483 wrap(std::move(f), 484 std::make_integer_sequence<unsigned, function_t::arity>{}); 485 } 486 487 // enable_if Arg1 == request && Arg2 == Response 488 // enable_if Arg1 == request && Arg2 != response 489 // enable_if Arg1 != request 490 491 template <typename Func, unsigned... Indices> 492 std::function<void(const Request&, 493 const std::shared_ptr<bmcweb::AsyncResp>&, 494 const RoutingParams&)> 495 wrap(Func f, std::integer_sequence<unsigned, Indices...>) 496 { 497 using function_t = crow::utility::function_traits<Func>; 498 499 if (!black_magic::isParameterTagCompatible( 500 black_magic::getParameterTag(rule.c_str()), 501 black_magic::computeParameterTagFromArgsList< 502 typename function_t::template arg<Indices>...>::value)) 503 { 504 throw std::runtime_error("routeDynamic: Handler type is mismatched " 505 "with URL parameters: " + 506 rule); 507 } 508 auto ret = detail::routing_handler_call_helper::Wrapped< 509 Func, typename function_t::template arg<Indices>...>(); 510 ret.template set<typename function_t::template arg<Indices>...>( 511 std::move(f)); 512 return ret; 513 } 514 515 template <typename Func> 516 void operator()(std::string name, Func&& f) 517 { 518 nameStr = std::move(name); 519 (*this).template operator()<Func>(std::forward(f)); 520 } 521 522 private: 523 std::function<void(const Request&, 524 const std::shared_ptr<bmcweb::AsyncResp>&, 525 const RoutingParams&)> 526 erasedHandler; 527 }; 528 529 template <typename... Args> 530 class TaggedRule : 531 public BaseRule, 532 public RuleParameterTraits<TaggedRule<Args...>> 533 { 534 public: 535 using self_t = TaggedRule<Args...>; 536 537 TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) 538 {} 539 540 void validate() override 541 { 542 if (!handler) 543 { 544 throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + 545 "no handler for url " + rule); 546 } 547 } 548 549 template <typename Func> 550 typename std::enable_if< 551 black_magic::CallHelper<Func, black_magic::S<Args...>>::value, 552 void>::type 553 operator()(Func&& f) 554 { 555 static_assert( 556 black_magic::CallHelper<Func, black_magic::S<Args...>>::value || 557 black_magic::CallHelper< 558 Func, black_magic::S<crow::Request, Args...>>::value, 559 "Handler type is mismatched with URL parameters"); 560 static_assert( 561 !std::is_same<void, decltype(f(std::declval<Args>()...))>::value, 562 "Handler function cannot have void return type; valid return " 563 "types: " 564 "string, int, crow::response, nlohmann::json"); 565 566 handler = [f = std::move(f)]( 567 const Request&, 568 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 569 Args... args) { asyncResp->res.result(f(args...)); }; 570 } 571 572 template <typename Func> 573 typename std::enable_if< 574 !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && 575 black_magic::CallHelper< 576 Func, black_magic::S<crow::Request, Args...>>::value, 577 void>::type 578 operator()(Func&& f) 579 { 580 static_assert( 581 black_magic::CallHelper<Func, black_magic::S<Args...>>::value || 582 black_magic::CallHelper< 583 Func, black_magic::S<crow::Request, Args...>>::value, 584 "Handler type is mismatched with URL parameters"); 585 static_assert( 586 !std::is_same<void, decltype(f(std::declval<crow::Request>(), 587 std::declval<Args>()...))>::value, 588 "Handler function cannot have void return type; valid return " 589 "types: " 590 "string, int, crow::response,nlohmann::json"); 591 592 handler = [f = std::move(f)]( 593 const crow::Request& req, 594 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 595 Args... args) { asyncResp->res.result(f(req, args...)); }; 596 } 597 598 template <typename Func> 599 typename std::enable_if< 600 !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && 601 !black_magic::CallHelper< 602 Func, black_magic::S<crow::Request, Args...>>::value, 603 void>::type 604 operator()(Func&& f) 605 { 606 static_assert( 607 black_magic::CallHelper<Func, black_magic::S<Args...>>::value || 608 black_magic::CallHelper< 609 Func, black_magic::S<crow::Request, Args...>>::value || 610 black_magic::CallHelper< 611 Func, black_magic::S<crow::Request, 612 std::shared_ptr<bmcweb::AsyncResp>&, 613 Args...>>::value, 614 "Handler type is mismatched with URL parameters"); 615 static_assert( 616 std::is_same< 617 void, 618 decltype(f(std::declval<crow::Request>(), 619 std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), 620 std::declval<Args>()...))>::value, 621 "Handler function with response argument should have void " 622 "return " 623 "type"); 624 625 handler = std::move(f); 626 } 627 628 template <typename Func> 629 void operator()(const std::string_view name, Func&& f) 630 { 631 nameStr = name; 632 (*this).template operator()<Func>(std::forward(f)); 633 } 634 635 void handle(const Request& req, 636 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 637 const RoutingParams& params) override 638 { 639 detail::routing_handler_call_helper::Call< 640 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 641 0, 0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()( 642 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 643 handler, params, req, asyncResp}); 644 } 645 646 private: 647 std::function<void(const crow::Request&, 648 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> 649 handler; 650 }; 651 652 const int ruleSpecialRedirectSlash = 1; 653 654 class Trie 655 { 656 public: 657 struct Node 658 { 659 unsigned ruleIndex{}; 660 std::array<size_t, static_cast<size_t>(ParamType::MAX)> 661 paramChildrens{}; 662 boost::container::flat_map<std::string, unsigned> children; 663 664 bool isSimpleNode() const 665 { 666 return !ruleIndex && std::all_of(std::begin(paramChildrens), 667 std::end(paramChildrens), 668 [](size_t x) { return !x; }); 669 } 670 }; 671 672 Trie() : nodes(1) 673 {} 674 675 private: 676 void optimizeNode(Node* node) 677 { 678 for (size_t x : node->paramChildrens) 679 { 680 if (!x) 681 { 682 continue; 683 } 684 Node* child = &nodes[x]; 685 optimizeNode(child); 686 } 687 if (node->children.empty()) 688 { 689 return; 690 } 691 bool mergeWithChild = true; 692 for (const std::pair<std::string, unsigned>& kv : node->children) 693 { 694 Node* child = &nodes[kv.second]; 695 if (!child->isSimpleNode()) 696 { 697 mergeWithChild = false; 698 break; 699 } 700 } 701 if (mergeWithChild) 702 { 703 decltype(node->children) merged; 704 for (const std::pair<std::string, unsigned>& kv : node->children) 705 { 706 Node* child = &nodes[kv.second]; 707 for (const std::pair<std::string, unsigned>& childKv : 708 child->children) 709 { 710 merged[kv.first + childKv.first] = childKv.second; 711 } 712 } 713 node->children = std::move(merged); 714 optimizeNode(node); 715 } 716 else 717 { 718 for (const std::pair<std::string, unsigned>& kv : node->children) 719 { 720 Node* child = &nodes[kv.second]; 721 optimizeNode(child); 722 } 723 } 724 } 725 726 void optimize() 727 { 728 optimizeNode(head()); 729 } 730 731 public: 732 void validate() 733 { 734 optimize(); 735 } 736 737 void findRouteIndexes(const std::string& reqUrl, 738 std::vector<unsigned>& routeIndexes, 739 const Node* node = nullptr, unsigned pos = 0) const 740 { 741 if (node == nullptr) 742 { 743 node = head(); 744 } 745 for (const std::pair<std::string, unsigned>& kv : node->children) 746 { 747 const std::string& fragment = kv.first; 748 const Node* child = &nodes[kv.second]; 749 if (pos >= reqUrl.size()) 750 { 751 if (child->ruleIndex != 0 && fragment != "/") 752 { 753 routeIndexes.push_back(child->ruleIndex); 754 } 755 findRouteIndexes(reqUrl, routeIndexes, child, 756 static_cast<unsigned>(pos + fragment.size())); 757 } 758 else 759 { 760 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 761 { 762 findRouteIndexes( 763 reqUrl, routeIndexes, child, 764 static_cast<unsigned>(pos + fragment.size())); 765 } 766 } 767 } 768 } 769 770 std::pair<unsigned, RoutingParams> 771 find(const std::string_view reqUrl, const Node* node = nullptr, 772 size_t pos = 0, RoutingParams* params = nullptr) const 773 { 774 RoutingParams empty; 775 if (params == nullptr) 776 { 777 params = ∅ 778 } 779 780 unsigned found{}; 781 RoutingParams matchParams; 782 783 if (node == nullptr) 784 { 785 node = head(); 786 } 787 if (pos == reqUrl.size()) 788 { 789 return {node->ruleIndex, *params}; 790 } 791 792 auto updateFound = 793 [&found, &matchParams](std::pair<unsigned, RoutingParams>& ret) { 794 if (ret.first && (!found || found > ret.first)) 795 { 796 found = ret.first; 797 matchParams = std::move(ret.second); 798 } 799 }; 800 801 if (node->paramChildrens[static_cast<size_t>(ParamType::INT)]) 802 { 803 char c = reqUrl[pos]; 804 if ((c >= '0' && c <= '9') || c == '+' || c == '-') 805 { 806 char* eptr; 807 errno = 0; 808 long long int value = 809 std::strtoll(reqUrl.data() + pos, &eptr, 10); 810 if (errno != ERANGE && eptr != reqUrl.data() + pos) 811 { 812 params->intParams.push_back(value); 813 std::pair<unsigned, RoutingParams> ret = 814 find(reqUrl, 815 &nodes[node->paramChildrens[static_cast<size_t>( 816 ParamType::INT)]], 817 static_cast<size_t>(eptr - reqUrl.data()), params); 818 updateFound(ret); 819 params->intParams.pop_back(); 820 } 821 } 822 } 823 824 if (node->paramChildrens[static_cast<size_t>(ParamType::UINT)]) 825 { 826 char c = reqUrl[pos]; 827 if ((c >= '0' && c <= '9') || c == '+') 828 { 829 char* eptr; 830 errno = 0; 831 unsigned long long int value = 832 std::strtoull(reqUrl.data() + pos, &eptr, 10); 833 if (errno != ERANGE && eptr != reqUrl.data() + pos) 834 { 835 params->uintParams.push_back(value); 836 std::pair<unsigned, RoutingParams> ret = 837 find(reqUrl, 838 &nodes[node->paramChildrens[static_cast<size_t>( 839 ParamType::UINT)]], 840 static_cast<size_t>(eptr - reqUrl.data()), params); 841 updateFound(ret); 842 params->uintParams.pop_back(); 843 } 844 } 845 } 846 847 if (node->paramChildrens[static_cast<size_t>(ParamType::DOUBLE)]) 848 { 849 char c = reqUrl[pos]; 850 if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') 851 { 852 char* eptr; 853 errno = 0; 854 double value = std::strtod(reqUrl.data() + pos, &eptr); 855 if (errno != ERANGE && eptr != reqUrl.data() + pos) 856 { 857 params->doubleParams.push_back(value); 858 std::pair<unsigned, RoutingParams> ret = 859 find(reqUrl, 860 &nodes[node->paramChildrens[static_cast<size_t>( 861 ParamType::DOUBLE)]], 862 static_cast<size_t>(eptr - reqUrl.data()), params); 863 updateFound(ret); 864 params->doubleParams.pop_back(); 865 } 866 } 867 } 868 869 if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)]) 870 { 871 size_t epos = pos; 872 for (; epos < reqUrl.size(); epos++) 873 { 874 if (reqUrl[epos] == '/') 875 { 876 break; 877 } 878 } 879 880 if (epos != pos) 881 { 882 params->stringParams.emplace_back( 883 reqUrl.substr(pos, epos - pos)); 884 std::pair<unsigned, RoutingParams> ret = 885 find(reqUrl, 886 &nodes[node->paramChildrens[static_cast<size_t>( 887 ParamType::STRING)]], 888 epos, params); 889 updateFound(ret); 890 params->stringParams.pop_back(); 891 } 892 } 893 894 if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)]) 895 { 896 size_t epos = reqUrl.size(); 897 898 if (epos != pos) 899 { 900 params->stringParams.emplace_back( 901 reqUrl.substr(pos, epos - pos)); 902 std::pair<unsigned, RoutingParams> ret = 903 find(reqUrl, 904 &nodes[node->paramChildrens[static_cast<size_t>( 905 ParamType::PATH)]], 906 epos, params); 907 updateFound(ret); 908 params->stringParams.pop_back(); 909 } 910 } 911 912 for (const std::pair<std::string, unsigned>& kv : node->children) 913 { 914 const std::string& fragment = kv.first; 915 const Node* child = &nodes[kv.second]; 916 917 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 918 { 919 std::pair<unsigned, RoutingParams> ret = 920 find(reqUrl, child, pos + fragment.size(), params); 921 updateFound(ret); 922 } 923 } 924 925 return {found, matchParams}; 926 } 927 928 void add(const std::string& url, unsigned ruleIndex) 929 { 930 size_t idx = 0; 931 932 for (unsigned i = 0; i < url.size(); i++) 933 { 934 char c = url[i]; 935 if (c == '<') 936 { 937 const static std::array<std::pair<ParamType, std::string>, 7> 938 paramTraits = {{ 939 {ParamType::INT, "<int>"}, 940 {ParamType::UINT, "<uint>"}, 941 {ParamType::DOUBLE, "<float>"}, 942 {ParamType::DOUBLE, "<double>"}, 943 {ParamType::STRING, "<str>"}, 944 {ParamType::STRING, "<string>"}, 945 {ParamType::PATH, "<path>"}, 946 }}; 947 948 for (const std::pair<ParamType, std::string>& x : paramTraits) 949 { 950 if (url.compare(i, x.second.size(), x.second) == 0) 951 { 952 size_t index = static_cast<size_t>(x.first); 953 if (!nodes[idx].paramChildrens[index]) 954 { 955 unsigned newNodeIdx = newNode(); 956 nodes[idx].paramChildrens[index] = newNodeIdx; 957 } 958 idx = nodes[idx].paramChildrens[index]; 959 i += static_cast<unsigned>(x.second.size()); 960 break; 961 } 962 } 963 964 i--; 965 } 966 else 967 { 968 std::string piece(&c, 1); 969 if (!nodes[idx].children.count(piece)) 970 { 971 unsigned newNodeIdx = newNode(); 972 nodes[idx].children.emplace(piece, newNodeIdx); 973 } 974 idx = nodes[idx].children[piece]; 975 } 976 } 977 if (nodes[idx].ruleIndex) 978 { 979 throw std::runtime_error("handler already exists for " + url); 980 } 981 nodes[idx].ruleIndex = ruleIndex; 982 } 983 984 private: 985 void debugNodePrint(Node* n, size_t level) 986 { 987 for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++) 988 { 989 if (n->paramChildrens[i]) 990 { 991 BMCWEB_LOG_DEBUG << std::string( 992 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/; 993 switch (static_cast<ParamType>(i)) 994 { 995 case ParamType::INT: 996 BMCWEB_LOG_DEBUG << "<int>"; 997 break; 998 case ParamType::UINT: 999 BMCWEB_LOG_DEBUG << "<uint>"; 1000 break; 1001 case ParamType::DOUBLE: 1002 BMCWEB_LOG_DEBUG << "<float>"; 1003 break; 1004 case ParamType::STRING: 1005 BMCWEB_LOG_DEBUG << "<str>"; 1006 break; 1007 case ParamType::PATH: 1008 BMCWEB_LOG_DEBUG << "<path>"; 1009 break; 1010 case ParamType::MAX: 1011 BMCWEB_LOG_DEBUG << "<ERROR>"; 1012 break; 1013 } 1014 1015 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); 1016 } 1017 } 1018 for (const std::pair<std::string, unsigned>& kv : n->children) 1019 { 1020 BMCWEB_LOG_DEBUG 1021 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ 1022 << kv.first; 1023 debugNodePrint(&nodes[kv.second], level + 1); 1024 } 1025 } 1026 1027 public: 1028 void debugPrint() 1029 { 1030 debugNodePrint(head(), 0U); 1031 } 1032 1033 private: 1034 const Node* head() const 1035 { 1036 return &nodes.front(); 1037 } 1038 1039 Node* head() 1040 { 1041 return &nodes.front(); 1042 } 1043 1044 unsigned newNode() 1045 { 1046 nodes.resize(nodes.size() + 1); 1047 return static_cast<unsigned>(nodes.size() - 1); 1048 } 1049 1050 std::vector<Node> nodes; 1051 }; 1052 1053 class Router 1054 { 1055 public: 1056 Router() = default; 1057 1058 DynamicRule& newRuleDynamic(const std::string& rule) 1059 { 1060 std::unique_ptr<DynamicRule> ruleObject = 1061 std::make_unique<DynamicRule>(rule); 1062 DynamicRule* ptr = ruleObject.get(); 1063 allRules.emplace_back(std::move(ruleObject)); 1064 1065 return *ptr; 1066 } 1067 1068 template <uint64_t N> 1069 typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& 1070 newRuleTagged(const std::string& rule) 1071 { 1072 using RuleT = typename black_magic::Arguments<N>::type::template rebind< 1073 TaggedRule>; 1074 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 1075 RuleT* ptr = ruleObject.get(); 1076 allRules.emplace_back(std::move(ruleObject)); 1077 1078 return *ptr; 1079 } 1080 1081 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 1082 { 1083 if (ruleObject == nullptr) 1084 { 1085 return; 1086 } 1087 for (size_t method = 0, methodBit = 1; method < maxHttpVerbCount; 1088 method++, methodBit <<= 1) 1089 { 1090 if (ruleObject->methodsBitfield & methodBit) 1091 { 1092 perMethods[method].rules.emplace_back(ruleObject); 1093 perMethods[method].trie.add( 1094 rule, static_cast<unsigned>( 1095 perMethods[method].rules.size() - 1U)); 1096 // directory case: 1097 // request to `/about' url matches `/about/' rule 1098 if (rule.size() > 2 && rule.back() == '/') 1099 { 1100 perMethods[method].trie.add( 1101 rule.substr(0, rule.size() - 1), 1102 static_cast<unsigned>(perMethods[method].rules.size() - 1103 1)); 1104 } 1105 } 1106 } 1107 } 1108 1109 void validate() 1110 { 1111 for (std::unique_ptr<BaseRule>& rule : allRules) 1112 { 1113 if (rule) 1114 { 1115 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 1116 if (upgraded) 1117 { 1118 rule = std::move(upgraded); 1119 } 1120 rule->validate(); 1121 internalAddRuleObject(rule->rule, rule.get()); 1122 } 1123 } 1124 for (PerMethod& perMethod : perMethods) 1125 { 1126 perMethod.trie.validate(); 1127 } 1128 } 1129 1130 template <typename Adaptor> 1131 void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) 1132 { 1133 if (static_cast<size_t>(req.method()) >= perMethods.size()) 1134 { 1135 res.result(boost::beast::http::status::not_found); 1136 res.end(); 1137 return; 1138 } 1139 1140 PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())]; 1141 Trie& trie = perMethod.trie; 1142 std::vector<BaseRule*>& rules = perMethod.rules; 1143 1144 const std::pair<unsigned, RoutingParams>& found = trie.find(req.url); 1145 unsigned ruleIndex = found.first; 1146 if (!ruleIndex) 1147 { 1148 BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; 1149 res.result(boost::beast::http::status::not_found); 1150 res.end(); 1151 return; 1152 } 1153 1154 if (ruleIndex >= rules.size()) 1155 { 1156 throw std::runtime_error("Trie internal structure corrupted!"); 1157 } 1158 1159 if (ruleIndex == ruleSpecialRedirectSlash) 1160 { 1161 BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: " 1162 << req.url; 1163 res.result(boost::beast::http::status::moved_permanently); 1164 1165 // TODO absolute url building 1166 if (req.getHeaderValue("Host").empty()) 1167 { 1168 res.addHeader("Location", std::string(req.url) + "/"); 1169 } 1170 else 1171 { 1172 res.addHeader( 1173 "Location", 1174 req.isSecure 1175 ? "https://" 1176 : "http://" + std::string(req.getHeaderValue("Host")) + 1177 std::string(req.url) + "/"); 1178 } 1179 res.end(); 1180 return; 1181 } 1182 1183 if ((rules[ruleIndex]->getMethods() & 1184 (1U << static_cast<size_t>(req.method()))) == 0) 1185 { 1186 BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url 1187 << " with " << req.methodString() << "(" 1188 << static_cast<uint32_t>(req.method()) << ") / " 1189 << rules[ruleIndex]->getMethods(); 1190 res.result(boost::beast::http::status::not_found); 1191 res.end(); 1192 return; 1193 } 1194 1195 BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule 1196 << "' " << static_cast<uint32_t>(req.method()) << " / " 1197 << rules[ruleIndex]->getMethods(); 1198 1199 // any uncaught exceptions become 500s 1200 try 1201 { 1202 rules[ruleIndex]->handleUpgrade(req, res, std::move(adaptor)); 1203 } 1204 catch (std::exception& e) 1205 { 1206 BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); 1207 res.result(boost::beast::http::status::internal_server_error); 1208 res.end(); 1209 return; 1210 } 1211 catch (...) 1212 { 1213 BMCWEB_LOG_ERROR 1214 << "An uncaught exception occurred. The type was unknown " 1215 "so no information was available."; 1216 res.result(boost::beast::http::status::internal_server_error); 1217 res.end(); 1218 return; 1219 } 1220 } 1221 1222 void handle(Request& req, 1223 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1224 { 1225 if (static_cast<size_t>(req.method()) >= perMethods.size()) 1226 { 1227 asyncResp->res.result(boost::beast::http::status::not_found); 1228 return; 1229 } 1230 PerMethod& perMethod = perMethods[static_cast<size_t>(req.method())]; 1231 Trie& trie = perMethod.trie; 1232 std::vector<BaseRule*>& rules = perMethod.rules; 1233 1234 const std::pair<unsigned, RoutingParams>& found = trie.find(req.url); 1235 1236 unsigned ruleIndex = found.first; 1237 1238 if (!ruleIndex) 1239 { 1240 // Check to see if this url exists at any verb 1241 for (const PerMethod& p : perMethods) 1242 { 1243 const std::pair<unsigned, RoutingParams>& found2 = 1244 p.trie.find(req.url); 1245 if (found2.first > 0) 1246 { 1247 asyncResp->res.result( 1248 boost::beast::http::status::method_not_allowed); 1249 return; 1250 } 1251 } 1252 BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; 1253 asyncResp->res.result(boost::beast::http::status::not_found); 1254 return; 1255 } 1256 1257 if (ruleIndex >= rules.size()) 1258 { 1259 throw std::runtime_error("Trie internal structure corrupted!"); 1260 } 1261 1262 if (ruleIndex == ruleSpecialRedirectSlash) 1263 { 1264 BMCWEB_LOG_INFO << "Redirecting to a url with trailing slash: " 1265 << req.url; 1266 asyncResp->res.result( 1267 boost::beast::http::status::moved_permanently); 1268 1269 // TODO absolute url building 1270 if (req.getHeaderValue("Host").empty()) 1271 { 1272 asyncResp->res.addHeader("Location", 1273 std::string(req.url) + "/"); 1274 } 1275 else 1276 { 1277 asyncResp->res.addHeader( 1278 "Location", (req.isSecure ? "https://" : "http://") + 1279 std::string(req.getHeaderValue("Host")) + 1280 std::string(req.url) + "/"); 1281 } 1282 return; 1283 } 1284 1285 if ((rules[ruleIndex]->getMethods() & 1286 (1U << static_cast<uint32_t>(req.method()))) == 0) 1287 { 1288 BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url 1289 << " with " << req.methodString() << "(" 1290 << static_cast<uint32_t>(req.method()) << ") / " 1291 << rules[ruleIndex]->getMethods(); 1292 asyncResp->res.result( 1293 boost::beast::http::status::method_not_allowed); 1294 return; 1295 } 1296 1297 BMCWEB_LOG_DEBUG << "Matched rule '" << rules[ruleIndex]->rule << "' " 1298 << static_cast<uint32_t>(req.method()) << " / " 1299 << rules[ruleIndex]->getMethods(); 1300 1301 if (req.session == nullptr) 1302 { 1303 rules[ruleIndex]->handle(req, asyncResp, found.second); 1304 return; 1305 } 1306 1307 crow::connections::systemBus->async_method_call( 1308 [&req, asyncResp, &rules, ruleIndex, found]( 1309 const boost::system::error_code ec, 1310 std::map<std::string, std::variant<bool, std::string, 1311 std::vector<std::string>>> 1312 userInfo) { 1313 if (ec) 1314 { 1315 BMCWEB_LOG_ERROR << "GetUserInfo failed..."; 1316 asyncResp->res.result( 1317 boost::beast::http::status::internal_server_error); 1318 return; 1319 } 1320 1321 const std::string* userRolePtr = nullptr; 1322 auto userInfoIter = userInfo.find("UserPrivilege"); 1323 if (userInfoIter != userInfo.end()) 1324 { 1325 userRolePtr = 1326 std::get_if<std::string>(&userInfoIter->second); 1327 } 1328 1329 std::string userRole{}; 1330 if (userRolePtr != nullptr) 1331 { 1332 userRole = *userRolePtr; 1333 BMCWEB_LOG_DEBUG << "userName = " << req.session->username 1334 << " userRole = " << *userRolePtr; 1335 } 1336 1337 bool* remoteUserPtr = nullptr; 1338 auto remoteUserIter = userInfo.find("RemoteUser"); 1339 if (remoteUserIter != userInfo.end()) 1340 { 1341 remoteUserPtr = std::get_if<bool>(&remoteUserIter->second); 1342 } 1343 if (remoteUserPtr == nullptr) 1344 { 1345 BMCWEB_LOG_ERROR 1346 << "RemoteUser property missing or wrong type"; 1347 asyncResp->res.result( 1348 boost::beast::http::status::internal_server_error); 1349 return; 1350 } 1351 bool remoteUser = *remoteUserPtr; 1352 1353 bool passwordExpired = false; // default for remote user 1354 if (!remoteUser) 1355 { 1356 bool* passwordExpiredPtr = nullptr; 1357 auto passwordExpiredIter = 1358 userInfo.find("UserPasswordExpired"); 1359 if (passwordExpiredIter != userInfo.end()) 1360 { 1361 passwordExpiredPtr = 1362 std::get_if<bool>(&passwordExpiredIter->second); 1363 } 1364 if (passwordExpiredPtr != nullptr) 1365 { 1366 passwordExpired = *passwordExpiredPtr; 1367 } 1368 else 1369 { 1370 BMCWEB_LOG_ERROR 1371 << "UserPasswordExpired property is expected for" 1372 " local user but is missing or wrong type"; 1373 asyncResp->res.result( 1374 boost::beast::http::status::internal_server_error); 1375 return; 1376 } 1377 } 1378 1379 // Get the userprivileges from the role 1380 redfish::Privileges userPrivileges = 1381 redfish::getUserPrivileges(userRole); 1382 1383 // Set isConfigureSelfOnly based on D-Bus results. This 1384 // ignores the results from both pamAuthenticateUser and the 1385 // value from any previous use of this session. 1386 req.session->isConfigureSelfOnly = passwordExpired; 1387 1388 // Modifyprivileges if isConfigureSelfOnly. 1389 if (req.session->isConfigureSelfOnly) 1390 { 1391 // Remove allprivileges except ConfigureSelf 1392 userPrivileges = userPrivileges.intersection( 1393 redfish::Privileges{"ConfigureSelf"}); 1394 BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; 1395 } 1396 1397 if (!rules[ruleIndex]->checkPrivileges(userPrivileges)) 1398 { 1399 asyncResp->res.result( 1400 boost::beast::http::status::forbidden); 1401 if (req.session->isConfigureSelfOnly) 1402 { 1403 redfish::messages::passwordChangeRequired( 1404 asyncResp->res, 1405 "/redfish/v1/AccountService/Accounts/" + 1406 req.session->username); 1407 } 1408 return; 1409 } 1410 1411 req.userRole = userRole; 1412 rules[ruleIndex]->handle(req, asyncResp, found.second); 1413 }, 1414 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1415 "xyz.openbmc_project.User.Manager", "GetUserInfo", 1416 req.session->username); 1417 } 1418 1419 void debugPrint() 1420 { 1421 for (size_t i = 0; i < perMethods.size(); i++) 1422 { 1423 BMCWEB_LOG_DEBUG << boost::beast::http::to_string( 1424 static_cast<boost::beast::http::verb>(i)); 1425 perMethods[i].trie.debugPrint(); 1426 } 1427 } 1428 1429 std::vector<const std::string*> getRoutes(const std::string& parent) 1430 { 1431 std::vector<const std::string*> ret; 1432 1433 for (const PerMethod& pm : perMethods) 1434 { 1435 std::vector<unsigned> x; 1436 pm.trie.findRouteIndexes(parent, x); 1437 for (unsigned index : x) 1438 { 1439 ret.push_back(&pm.rules[index]->rule); 1440 } 1441 } 1442 return ret; 1443 } 1444 1445 private: 1446 struct PerMethod 1447 { 1448 std::vector<BaseRule*> rules; 1449 Trie trie; 1450 // rule index 0, 1 has special meaning; preallocate it to avoid 1451 // duplication. 1452 PerMethod() : rules(2) 1453 {} 1454 }; 1455 1456 const static size_t maxHttpVerbCount = 1457 static_cast<size_t>(boost::beast::http::verb::unlink); 1458 1459 std::array<PerMethod, maxHttpVerbCount> perMethods; 1460 std::vector<std::unique_ptr<BaseRule>> allRules; 1461 }; 1462 } // namespace crow 1463