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