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