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