1 #pragma once 2 #include "bmcweb_config.h" 3 4 #include "app.hpp" 5 #include "async_resp.hpp" 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 11 #include <sys/types.h> 12 13 #include <boost/algorithm/string/classification.hpp> 14 #include <boost/algorithm/string/split.hpp> 15 #include <boost/beast/http/message.hpp> // IWYU pragma: keep 16 #include <boost/beast/http/status.hpp> 17 #include <boost/beast/http/verb.hpp> 18 #include <boost/url/params_view.hpp> 19 #include <boost/url/string.hpp> 20 #include <nlohmann/json.hpp> 21 22 #include <algorithm> 23 #include <array> 24 #include <cctype> 25 #include <charconv> 26 #include <compare> 27 #include <cstdint> 28 #include <functional> 29 #include <iterator> 30 #include <limits> 31 #include <map> 32 #include <memory> 33 #include <optional> 34 #include <string> 35 #include <string_view> 36 #include <system_error> 37 #include <utility> 38 #include <vector> 39 40 // IWYU pragma: no_include <boost/url/impl/params_view.hpp> 41 // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> 42 // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> 43 // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp> 44 // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp> 45 // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp> 46 // IWYU pragma: no_include <stdint.h> 47 48 namespace redfish 49 { 50 namespace query_param 51 { 52 inline constexpr size_t maxEntriesPerPage = 1000; 53 54 enum class ExpandType : uint8_t 55 { 56 None, 57 Links, 58 NotLinks, 59 Both, 60 }; 61 62 // A simple implementation of Trie to help |recursiveSelect|. 63 class SelectTrieNode 64 { 65 public: 66 SelectTrieNode() = default; 67 68 const SelectTrieNode* find(const std::string& jsonKey) const 69 { 70 auto it = children.find(jsonKey); 71 if (it == children.end()) 72 { 73 return nullptr; 74 } 75 return &it->second; 76 } 77 78 // Creates a new node if the key doesn't exist, returns the reference to the 79 // newly created node; otherwise, return the reference to the existing node 80 SelectTrieNode* emplace(std::string_view jsonKey) 81 { 82 auto [it, _] = children.emplace(jsonKey, SelectTrieNode{}); 83 return &it->second; 84 } 85 86 bool empty() const 87 { 88 return children.empty(); 89 } 90 91 void clear() 92 { 93 children.clear(); 94 } 95 96 void setToSelected() 97 { 98 selected = true; 99 } 100 101 bool isSelected() const 102 { 103 return selected; 104 } 105 106 private: 107 std::map<std::string, SelectTrieNode, std::less<>> children; 108 bool selected = false; 109 }; 110 111 // Validates the property in the $select parameter. Every character is among 112 // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties) 113 inline bool isSelectedPropertyAllowed(std::string_view property) 114 { 115 // These a magic number, but with it it's less likely that this code 116 // introduces CVE; e.g., too large properties crash the service. 117 constexpr int maxPropertyLength = 60; 118 if (property.empty() || property.size() > maxPropertyLength) 119 { 120 return false; 121 } 122 for (char ch : property) 123 { 124 if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' && 125 ch != '@' && ch != '.') 126 { 127 return false; 128 } 129 } 130 return true; 131 } 132 133 struct SelectTrie 134 { 135 SelectTrie() = default; 136 137 // Inserts a $select value; returns false if the nestedProperty is illegal. 138 bool insertNode(std::string_view nestedProperty) 139 { 140 if (nestedProperty.empty()) 141 { 142 return false; 143 } 144 SelectTrieNode* currNode = &root; 145 size_t index = nestedProperty.find_first_of('/'); 146 while (!nestedProperty.empty()) 147 { 148 std::string_view property = nestedProperty.substr(0, index); 149 if (!isSelectedPropertyAllowed(property)) 150 { 151 return false; 152 } 153 currNode = currNode->emplace(property); 154 if (index == std::string::npos) 155 { 156 break; 157 } 158 nestedProperty.remove_prefix(index + 1); 159 index = nestedProperty.find_first_of('/'); 160 } 161 162 currNode->setToSelected(); 163 return true; 164 } 165 166 SelectTrieNode root; 167 }; 168 169 // The struct stores the parsed query parameters of the default Redfish route. 170 struct Query 171 { 172 // Only 173 bool isOnly = false; 174 // Expand 175 uint8_t expandLevel = 0; 176 ExpandType expandType = ExpandType::None; 177 178 // Skip 179 std::optional<size_t> skip = std::nullopt; 180 181 // Top 182 std::optional<size_t> top = std::nullopt; 183 184 // Select 185 SelectTrie selectTrie = {}; 186 }; 187 188 // The struct defines how resource handlers in redfish-core/lib/ can handle 189 // query parameters themselves, so that the default Redfish route will delegate 190 // the processing. 191 struct QueryCapabilities 192 { 193 bool canDelegateOnly = false; 194 bool canDelegateTop = false; 195 bool canDelegateSkip = false; 196 uint8_t canDelegateExpandLevel = 0; 197 bool canDelegateSelect = false; 198 }; 199 200 // Delegates query parameters according to the given |queryCapabilities| 201 // This function doesn't check query parameter conflicts since the parse 202 // function will take care of it. 203 // Returns a delegated query object which can be used by individual resource 204 // handlers so that handlers don't need to query again. 205 inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 206 { 207 Query delegated; 208 // delegate only 209 if (query.isOnly && queryCapabilities.canDelegateOnly) 210 { 211 delegated.isOnly = true; 212 query.isOnly = false; 213 } 214 // delegate expand as much as we can 215 if (query.expandType != ExpandType::None) 216 { 217 delegated.expandType = query.expandType; 218 if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 219 { 220 query.expandType = ExpandType::None; 221 delegated.expandLevel = query.expandLevel; 222 query.expandLevel = 0; 223 } 224 else 225 { 226 query.expandLevel -= queryCapabilities.canDelegateExpandLevel; 227 delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 228 } 229 } 230 231 // delegate top 232 if (query.top && queryCapabilities.canDelegateTop) 233 { 234 delegated.top = query.top; 235 query.top = std::nullopt; 236 } 237 238 // delegate skip 239 if (query.skip && queryCapabilities.canDelegateSkip) 240 { 241 delegated.skip = query.skip; 242 query.skip = 0; 243 } 244 245 // delegate select 246 if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect) 247 { 248 delegated.selectTrie = std::move(query.selectTrie); 249 query.selectTrie.root.clear(); 250 } 251 return delegated; 252 } 253 254 inline bool getExpandType(std::string_view value, Query& query) 255 { 256 if (value.empty()) 257 { 258 return false; 259 } 260 switch (value[0]) 261 { 262 case '*': 263 query.expandType = ExpandType::Both; 264 break; 265 case '.': 266 query.expandType = ExpandType::NotLinks; 267 break; 268 case '~': 269 query.expandType = ExpandType::Links; 270 break; 271 default: 272 return false; 273 274 break; 275 } 276 value.remove_prefix(1); 277 if (value.empty()) 278 { 279 query.expandLevel = 1; 280 return true; 281 } 282 constexpr std::string_view levels = "($levels="; 283 if (!value.starts_with(levels)) 284 { 285 return false; 286 } 287 value.remove_prefix(levels.size()); 288 289 auto it = std::from_chars(value.data(), value.data() + value.size(), 290 query.expandLevel); 291 if (it.ec != std::errc()) 292 { 293 return false; 294 } 295 value.remove_prefix(static_cast<size_t>(it.ptr - value.data())); 296 return value == ")"; 297 } 298 299 enum class QueryError 300 { 301 Ok, 302 OutOfRange, 303 ValueFormat, 304 }; 305 306 inline QueryError getNumericParam(std::string_view value, size_t& param) 307 { 308 std::from_chars_result r = 309 std::from_chars(value.data(), value.data() + value.size(), param); 310 311 // If the number wasn't representable in the type, it's out of range 312 if (r.ec == std::errc::result_out_of_range) 313 { 314 return QueryError::OutOfRange; 315 } 316 // All other errors are value format 317 if (r.ec != std::errc()) 318 { 319 return QueryError::ValueFormat; 320 } 321 return QueryError::Ok; 322 } 323 324 inline QueryError getSkipParam(std::string_view value, Query& query) 325 { 326 return getNumericParam(value, query.skip.emplace()); 327 } 328 329 inline QueryError getTopParam(std::string_view value, Query& query) 330 { 331 QueryError ret = getNumericParam(value, query.top.emplace()); 332 if (ret != QueryError::Ok) 333 { 334 return ret; 335 } 336 337 // Range check for sanity. 338 if (query.top > maxEntriesPerPage) 339 { 340 return QueryError::OutOfRange; 341 } 342 343 return QueryError::Ok; 344 } 345 346 // Parses and validates the $select parameter. 347 // As per OData URL Conventions and Redfish Spec, the $select values shall be 348 // comma separated Resource Path 349 // Ref: 350 // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 351 // 2. 352 // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 353 inline bool getSelectParam(std::string_view value, Query& query) 354 { 355 std::vector<std::string> properties; 356 boost::split(properties, value, boost::is_any_of(",")); 357 if (properties.empty()) 358 { 359 return false; 360 } 361 // These a magic number, but with it it's less likely that this code 362 // introduces CVE; e.g., too large properties crash the service. 363 constexpr int maxNumProperties = 10; 364 if (properties.size() > maxNumProperties) 365 { 366 return false; 367 } 368 for (const auto& property : properties) 369 { 370 if (!query.selectTrie.insertNode(property)) 371 { 372 return false; 373 } 374 } 375 // Per the Redfish spec section 7.3.3, the service shall select certain 376 // properties as if $select was omitted. 377 constexpr std::array<std::string_view, 5> reservedProperties = { 378 "@odata.id", "@odata.type", "@odata.context", "@odata.etag", "error"}; 379 for (auto const& str : reservedProperties) 380 { 381 query.selectTrie.insertNode(str.data()); 382 } 383 return true; 384 } 385 386 inline std::optional<Query> 387 parseParameters(const boost::urls::params_view& urlParams, 388 crow::Response& res) 389 { 390 Query ret; 391 for (const boost::urls::params_view::value_type& it : urlParams) 392 { 393 std::string_view key(it.key.data(), it.key.size()); 394 std::string_view value(it.value.data(), it.value.size()); 395 if (key == "only") 396 { 397 if (!it.value.empty()) 398 { 399 messages::queryParameterValueFormatError(res, value, key); 400 return std::nullopt; 401 } 402 ret.isOnly = true; 403 } 404 else if (key == "$expand" && bmcwebInsecureEnableQueryParams) 405 { 406 if (!getExpandType(value, ret)) 407 { 408 messages::queryParameterValueFormatError(res, value, key); 409 return std::nullopt; 410 } 411 } 412 else if (key == "$top") 413 { 414 QueryError topRet = getTopParam(value, ret); 415 if (topRet == QueryError::ValueFormat) 416 { 417 messages::queryParameterValueFormatError(res, value, key); 418 return std::nullopt; 419 } 420 if (topRet == QueryError::OutOfRange) 421 { 422 messages::queryParameterOutOfRange( 423 res, value, "$top", 424 "0-" + std::to_string(maxEntriesPerPage)); 425 return std::nullopt; 426 } 427 } 428 else if (key == "$skip") 429 { 430 QueryError topRet = getSkipParam(value, ret); 431 if (topRet == QueryError::ValueFormat) 432 { 433 messages::queryParameterValueFormatError(res, value, key); 434 return std::nullopt; 435 } 436 if (topRet == QueryError::OutOfRange) 437 { 438 messages::queryParameterOutOfRange( 439 res, value, key, 440 "0-" + std::to_string(std::numeric_limits<size_t>::max())); 441 return std::nullopt; 442 } 443 } 444 else if (key == "$select" && bmcwebInsecureEnableQueryParams) 445 { 446 if (!getSelectParam(value, ret)) 447 { 448 messages::queryParameterValueFormatError(res, value, key); 449 return std::nullopt; 450 } 451 } 452 else 453 { 454 // Intentionally ignore other errors Redfish spec, 7.3.1 455 if (key.starts_with("$")) 456 { 457 // Services shall return... The HTTP 501 Not Implemented 458 // status code for any unsupported query parameters that 459 // start with $ . 460 messages::queryParameterValueFormatError(res, value, key); 461 res.result(boost::beast::http::status::not_implemented); 462 return std::nullopt; 463 } 464 // "Shall ignore unknown or unsupported query parameters that do 465 // not begin with $ ." 466 } 467 } 468 469 if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 470 { 471 messages::queryCombinationInvalid(res); 472 return std::nullopt; 473 } 474 475 return ret; 476 } 477 478 inline bool processOnly(crow::App& app, crow::Response& res, 479 std::function<void(crow::Response&)>& completionHandler) 480 { 481 BMCWEB_LOG_DEBUG << "Processing only query param"; 482 auto itMembers = res.jsonValue.find("Members"); 483 if (itMembers == res.jsonValue.end()) 484 { 485 messages::queryNotSupportedOnResource(res); 486 completionHandler(res); 487 return false; 488 } 489 auto itMemBegin = itMembers->begin(); 490 if (itMemBegin == itMembers->end() || itMembers->size() != 1) 491 { 492 BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 493 << " element, returning full collection."; 494 completionHandler(res); 495 return false; 496 } 497 498 auto itUrl = itMemBegin->find("@odata.id"); 499 if (itUrl == itMemBegin->end()) 500 { 501 BMCWEB_LOG_DEBUG << "No found odata.id"; 502 messages::internalError(res); 503 completionHandler(res); 504 return false; 505 } 506 const std::string* url = itUrl->get_ptr<const std::string*>(); 507 if (url == nullptr) 508 { 509 BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 510 messages::internalError(res); 511 completionHandler(res); 512 return false; 513 } 514 // TODO(Ed) copy request headers? 515 // newReq.session = req.session; 516 std::error_code ec; 517 crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 518 if (ec) 519 { 520 messages::internalError(res); 521 completionHandler(res); 522 return false; 523 } 524 525 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 526 BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 527 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 528 asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 529 app.handle(newReq, asyncResp); 530 return true; 531 } 532 533 struct ExpandNode 534 { 535 nlohmann::json::json_pointer location; 536 std::string uri; 537 538 inline bool operator==(const ExpandNode& other) const 539 { 540 return location == other.location && uri == other.uri; 541 } 542 }; 543 544 // Walks a json object looking for Redfish NavigationReference entries that 545 // might need resolved. It recursively walks the jsonResponse object, looking 546 // for links at every level, and returns a list (out) of locations within the 547 // tree that need to be expanded. The current json pointer location p is passed 548 // in to reference the current node that's being expanded, so it can be combined 549 // with the keys from the jsonResponse object 550 inline void findNavigationReferencesRecursive( 551 ExpandType eType, nlohmann::json& jsonResponse, 552 const nlohmann::json::json_pointer& p, bool inLinks, 553 std::vector<ExpandNode>& out) 554 { 555 // If no expand is needed, return early 556 if (eType == ExpandType::None) 557 { 558 return; 559 } 560 nlohmann::json::array_t* array = 561 jsonResponse.get_ptr<nlohmann::json::array_t*>(); 562 if (array != nullptr) 563 { 564 size_t index = 0; 565 // For arrays, walk every element in the array 566 for (auto& element : *array) 567 { 568 nlohmann::json::json_pointer newPtr = p / index; 569 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 570 findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 571 out); 572 index++; 573 } 574 } 575 nlohmann::json::object_t* obj = 576 jsonResponse.get_ptr<nlohmann::json::object_t*>(); 577 if (obj == nullptr) 578 { 579 return; 580 } 581 // Navigation References only ever have a single element 582 if (obj->size() == 1) 583 { 584 if (obj->begin()->first == "@odata.id") 585 { 586 const std::string* uri = 587 obj->begin()->second.get_ptr<const std::string*>(); 588 if (uri != nullptr) 589 { 590 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 591 out.push_back({p, *uri}); 592 } 593 } 594 } 595 // Loop the object and look for links 596 for (auto& element : *obj) 597 { 598 bool localInLinks = inLinks; 599 if (!localInLinks) 600 { 601 // Check if this is a links node 602 localInLinks = element.first == "Links"; 603 } 604 // Only traverse the parts of the tree the user asked for 605 // Per section 7.3 of the redfish specification 606 if (localInLinks && eType == ExpandType::NotLinks) 607 { 608 continue; 609 } 610 if (!localInLinks && eType == ExpandType::Links) 611 { 612 continue; 613 } 614 nlohmann::json::json_pointer newPtr = p / element.first; 615 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 616 617 findNavigationReferencesRecursive(eType, element.second, newPtr, 618 localInLinks, out); 619 } 620 } 621 622 inline std::vector<ExpandNode> 623 findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse) 624 { 625 std::vector<ExpandNode> ret; 626 const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 627 findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 628 return ret; 629 } 630 631 // Formats a query parameter string for the sub-query. 632 // Returns std::nullopt on failures. 633 // This function shall handle $select when it is added. 634 // There is no need to handle parameters that's not campatible with $expand, 635 // e.g., $only, since this function will only be called in side $expand handlers 636 inline std::optional<std::string> formatQueryForExpand(const Query& query) 637 { 638 // query.expandLevel<=1: no need to do subqueries 639 if (query.expandLevel <= 1) 640 { 641 return ""; 642 } 643 std::string str = "?$expand="; 644 bool queryTypeExpected = false; 645 switch (query.expandType) 646 { 647 case ExpandType::None: 648 return ""; 649 case ExpandType::Links: 650 queryTypeExpected = true; 651 str += '~'; 652 break; 653 case ExpandType::NotLinks: 654 queryTypeExpected = true; 655 str += '.'; 656 break; 657 case ExpandType::Both: 658 queryTypeExpected = true; 659 str += '*'; 660 break; 661 } 662 if (!queryTypeExpected) 663 { 664 return std::nullopt; 665 } 666 str += "($levels="; 667 str += std::to_string(query.expandLevel - 1); 668 str += ')'; 669 return str; 670 } 671 672 class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 673 { 674 public: 675 // This object takes a single asyncResp object as the "final" one, then 676 // allows callers to attach sub-responses within the json tree that need 677 // to be executed and filled into their appropriate locations. This 678 // class manages the final "merge" of the json resources. 679 MultiAsyncResp(crow::App& appIn, 680 std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 681 app(appIn), 682 finalRes(std::move(finalResIn)) 683 {} 684 685 void addAwaitingResponse( 686 const std::shared_ptr<bmcweb::AsyncResp>& res, 687 const nlohmann::json::json_pointer& finalExpandLocation) 688 { 689 res->res.setCompleteRequestHandler(std::bind_front( 690 placeResultStatic, shared_from_this(), finalExpandLocation)); 691 } 692 693 void placeResult(const nlohmann::json::json_pointer& locationToPlace, 694 crow::Response& res) 695 { 696 nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 697 finalObj = std::move(res.jsonValue); 698 } 699 700 // Handles the very first level of Expand, and starts a chain of sub-queries 701 // for deeper levels. 702 void startQuery(const Query& query) 703 { 704 std::vector<ExpandNode> nodes = 705 findNavigationReferences(query.expandType, finalRes->res.jsonValue); 706 BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 707 const std::optional<std::string> queryStr = formatQueryForExpand(query); 708 if (!queryStr) 709 { 710 messages::internalError(finalRes->res); 711 return; 712 } 713 for (const ExpandNode& node : nodes) 714 { 715 const std::string subQuery = node.uri + *queryStr; 716 BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 717 std::error_code ec; 718 crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 719 ec); 720 if (ec) 721 { 722 messages::internalError(finalRes->res); 723 return; 724 } 725 726 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 727 BMCWEB_LOG_DEBUG << "setting completion handler on " 728 << &asyncResp->res; 729 730 addAwaitingResponse(asyncResp, node.location); 731 app.handle(newReq, asyncResp); 732 } 733 } 734 735 private: 736 static void 737 placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 738 const nlohmann::json::json_pointer& locationToPlace, 739 crow::Response& res) 740 { 741 multi->placeResult(locationToPlace, res); 742 } 743 744 crow::App& app; 745 std::shared_ptr<bmcweb::AsyncResp> finalRes; 746 }; 747 748 inline void processTopAndSkip(const Query& query, crow::Response& res) 749 { 750 if (!query.skip && !query.top) 751 { 752 // No work to do. 753 return; 754 } 755 nlohmann::json::object_t* obj = 756 res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 757 if (obj == nullptr) 758 { 759 // Shouldn't be possible. All responses should be objects. 760 messages::internalError(res); 761 return; 762 } 763 764 BMCWEB_LOG_DEBUG << "Handling top/skip"; 765 nlohmann::json::object_t::iterator members = obj->find("Members"); 766 if (members == obj->end()) 767 { 768 // From the Redfish specification 7.3.1 769 // ... the HTTP 400 Bad Request status code with the 770 // QueryNotSupportedOnResource message from the Base Message Registry 771 // for any supported query parameters that apply only to resource 772 // collections but are used on singular resources. 773 messages::queryNotSupportedOnResource(res); 774 return; 775 } 776 777 nlohmann::json::array_t* arr = 778 members->second.get_ptr<nlohmann::json::array_t*>(); 779 if (arr == nullptr) 780 { 781 messages::internalError(res); 782 return; 783 } 784 785 if (query.skip) 786 { 787 // Per section 7.3.1 of the Redfish specification, $skip is run before 788 // $top Can only skip as many values as we have 789 size_t skip = std::min(arr->size(), *query.skip); 790 arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 791 } 792 if (query.top) 793 { 794 size_t top = std::min(arr->size(), *query.top); 795 arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 796 } 797 } 798 799 // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 800 // not in the |currNode| Trie node. 801 inline void recursiveSelect(nlohmann::json& currRoot, 802 const SelectTrieNode& currNode) 803 { 804 nlohmann::json::object_t* object = 805 currRoot.get_ptr<nlohmann::json::object_t*>(); 806 if (object != nullptr) 807 { 808 BMCWEB_LOG_DEBUG << "Current JSON is an object"; 809 auto it = currRoot.begin(); 810 while (it != currRoot.end()) 811 { 812 auto nextIt = std::next(it); 813 BMCWEB_LOG_DEBUG << "key=" << it.key(); 814 const SelectTrieNode* nextNode = currNode.find(it.key()); 815 if (nextNode != nullptr && nextNode->isSelected()) 816 { 817 it = nextIt; 818 continue; 819 } 820 if (nextNode != nullptr) 821 { 822 BMCWEB_LOG_DEBUG << "Recursively select: " << it.key(); 823 recursiveSelect(*it, *nextNode); 824 it = nextIt; 825 continue; 826 } 827 BMCWEB_LOG_DEBUG << it.key() << " is getting removed!"; 828 it = currRoot.erase(it); 829 } 830 } 831 } 832 833 // The current implementation of $select still has the following TODOs due to 834 // ambiguity and/or complexity. 835 // 1. select properties in array of objects; 836 // https://github.com/DMTF/Redfish/issues/5188 was created for clarification. 837 // 2. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 838 // created for clarification. 839 // 3. respect the full odata spec; e.g., deduplication, namespace, star (*), 840 // etc. 841 inline void processSelect(crow::Response& intermediateResponse, 842 const SelectTrieNode& trieRoot) 843 { 844 BMCWEB_LOG_DEBUG << "Process $select quary parameter"; 845 recursiveSelect(intermediateResponse.jsonValue, trieRoot); 846 } 847 848 inline void 849 processAllParams(crow::App& app, const Query& query, 850 std::function<void(crow::Response&)>& completionHandler, 851 crow::Response& intermediateResponse) 852 { 853 if (!completionHandler) 854 { 855 BMCWEB_LOG_DEBUG << "Function was invalid?"; 856 return; 857 } 858 859 BMCWEB_LOG_DEBUG << "Processing query params"; 860 // If the request failed, there's no reason to even try to run query 861 // params. 862 if (intermediateResponse.resultInt() < 200 || 863 intermediateResponse.resultInt() >= 400) 864 { 865 completionHandler(intermediateResponse); 866 return; 867 } 868 if (query.isOnly) 869 { 870 processOnly(app, intermediateResponse, completionHandler); 871 return; 872 } 873 874 if (query.top || query.skip) 875 { 876 processTopAndSkip(query, intermediateResponse); 877 } 878 879 if (query.expandType != ExpandType::None) 880 { 881 BMCWEB_LOG_DEBUG << "Executing expand query"; 882 auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 883 std::move(intermediateResponse)); 884 885 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 886 auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 887 multi->startQuery(query); 888 return; 889 } 890 891 // According to Redfish Spec Section 7.3.1, $select is the last parameter to 892 // to process 893 if (!query.selectTrie.root.empty()) 894 { 895 processSelect(intermediateResponse, query.selectTrie.root); 896 } 897 898 completionHandler(intermediateResponse); 899 } 900 901 } // namespace query_param 902 } // namespace redfish 903