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