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