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