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 currNode->setToSelected(); 162 return true; 163 } 164 165 SelectTrieNode root; 166 }; 167 168 // The struct stores the parsed query parameters of the default Redfish route. 169 struct Query 170 { 171 // Only 172 bool isOnly = false; 173 // Expand 174 uint8_t expandLevel = 0; 175 ExpandType expandType = ExpandType::None; 176 177 // Skip 178 std::optional<size_t> skip = std::nullopt; 179 180 // Top 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 > maxEntriesPerPage) 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", 415 "0-" + std::to_string(maxEntriesPerPage)); 416 return std::nullopt; 417 } 418 } 419 else if (key == "$skip") 420 { 421 QueryError topRet = getSkipParam(value, ret); 422 if (topRet == QueryError::ValueFormat) 423 { 424 messages::queryParameterValueFormatError(res, value, key); 425 return std::nullopt; 426 } 427 if (topRet == QueryError::OutOfRange) 428 { 429 messages::queryParameterOutOfRange( 430 res, value, key, 431 "0-" + std::to_string(std::numeric_limits<size_t>::max())); 432 return std::nullopt; 433 } 434 } 435 else if (key == "$select" && bmcwebInsecureEnableQueryParams) 436 { 437 if (!getSelectParam(value, ret)) 438 { 439 messages::queryParameterValueFormatError(res, value, key); 440 return std::nullopt; 441 } 442 } 443 else 444 { 445 // Intentionally ignore other errors Redfish spec, 7.3.1 446 if (key.starts_with("$")) 447 { 448 // Services shall return... The HTTP 501 Not Implemented 449 // status code for any unsupported query parameters that 450 // start with $ . 451 messages::queryParameterValueFormatError(res, value, key); 452 res.result(boost::beast::http::status::not_implemented); 453 return std::nullopt; 454 } 455 // "Shall ignore unknown or unsupported query parameters that do 456 // not begin with $ ." 457 } 458 } 459 460 if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 461 { 462 messages::queryCombinationInvalid(res); 463 return std::nullopt; 464 } 465 466 return ret; 467 } 468 469 inline bool processOnly(crow::App& app, crow::Response& res, 470 std::function<void(crow::Response&)>& completionHandler) 471 { 472 BMCWEB_LOG_DEBUG << "Processing only query param"; 473 auto itMembers = res.jsonValue.find("Members"); 474 if (itMembers == res.jsonValue.end()) 475 { 476 messages::queryNotSupportedOnResource(res); 477 completionHandler(res); 478 return false; 479 } 480 auto itMemBegin = itMembers->begin(); 481 if (itMemBegin == itMembers->end() || itMembers->size() != 1) 482 { 483 BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 484 << " element, returning full collection."; 485 completionHandler(res); 486 return false; 487 } 488 489 auto itUrl = itMemBegin->find("@odata.id"); 490 if (itUrl == itMemBegin->end()) 491 { 492 BMCWEB_LOG_DEBUG << "No found odata.id"; 493 messages::internalError(res); 494 completionHandler(res); 495 return false; 496 } 497 const std::string* url = itUrl->get_ptr<const std::string*>(); 498 if (url == nullptr) 499 { 500 BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 501 messages::internalError(res); 502 completionHandler(res); 503 return false; 504 } 505 // TODO(Ed) copy request headers? 506 // newReq.session = req.session; 507 std::error_code ec; 508 crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 509 if (ec) 510 { 511 messages::internalError(res); 512 completionHandler(res); 513 return false; 514 } 515 516 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 517 BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 518 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 519 asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 520 app.handle(newReq, asyncResp); 521 return true; 522 } 523 524 struct ExpandNode 525 { 526 nlohmann::json::json_pointer location; 527 std::string uri; 528 529 inline bool operator==(const ExpandNode& other) const 530 { 531 return location == other.location && uri == other.uri; 532 } 533 }; 534 535 // Walks a json object looking for Redfish NavigationReference entries that 536 // might need resolved. It recursively walks the jsonResponse object, looking 537 // for links at every level, and returns a list (out) of locations within the 538 // tree that need to be expanded. The current json pointer location p is passed 539 // in to reference the current node that's being expanded, so it can be combined 540 // with the keys from the jsonResponse object 541 inline void findNavigationReferencesRecursive( 542 ExpandType eType, nlohmann::json& jsonResponse, 543 const nlohmann::json::json_pointer& p, bool inLinks, 544 std::vector<ExpandNode>& out) 545 { 546 // If no expand is needed, return early 547 if (eType == ExpandType::None) 548 { 549 return; 550 } 551 nlohmann::json::array_t* array = 552 jsonResponse.get_ptr<nlohmann::json::array_t*>(); 553 if (array != nullptr) 554 { 555 size_t index = 0; 556 // For arrays, walk every element in the array 557 for (auto& element : *array) 558 { 559 nlohmann::json::json_pointer newPtr = p / index; 560 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 561 findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 562 out); 563 index++; 564 } 565 } 566 nlohmann::json::object_t* obj = 567 jsonResponse.get_ptr<nlohmann::json::object_t*>(); 568 if (obj == nullptr) 569 { 570 return; 571 } 572 // Navigation References only ever have a single element 573 if (obj->size() == 1) 574 { 575 if (obj->begin()->first == "@odata.id") 576 { 577 const std::string* uri = 578 obj->begin()->second.get_ptr<const std::string*>(); 579 if (uri != nullptr) 580 { 581 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 582 out.push_back({p, *uri}); 583 } 584 } 585 } 586 // Loop the object and look for links 587 for (auto& element : *obj) 588 { 589 bool localInLinks = inLinks; 590 if (!localInLinks) 591 { 592 // Check if this is a links node 593 localInLinks = element.first == "Links"; 594 } 595 // Only traverse the parts of the tree the user asked for 596 // Per section 7.3 of the redfish specification 597 if (localInLinks && eType == ExpandType::NotLinks) 598 { 599 continue; 600 } 601 if (!localInLinks && eType == ExpandType::Links) 602 { 603 continue; 604 } 605 nlohmann::json::json_pointer newPtr = p / element.first; 606 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 607 608 findNavigationReferencesRecursive(eType, element.second, newPtr, 609 localInLinks, out); 610 } 611 } 612 613 inline std::vector<ExpandNode> 614 findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse) 615 { 616 std::vector<ExpandNode> ret; 617 const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 618 findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 619 return ret; 620 } 621 622 // Formats a query parameter string for the sub-query. 623 // Returns std::nullopt on failures. 624 // This function shall handle $select when it is added. 625 // There is no need to handle parameters that's not campatible with $expand, 626 // e.g., $only, since this function will only be called in side $expand handlers 627 inline std::optional<std::string> formatQueryForExpand(const Query& query) 628 { 629 // query.expandLevel<=1: no need to do subqueries 630 if (query.expandLevel <= 1) 631 { 632 return ""; 633 } 634 std::string str = "?$expand="; 635 bool queryTypeExpected = false; 636 switch (query.expandType) 637 { 638 case ExpandType::None: 639 return ""; 640 case ExpandType::Links: 641 queryTypeExpected = true; 642 str += '~'; 643 break; 644 case ExpandType::NotLinks: 645 queryTypeExpected = true; 646 str += '.'; 647 break; 648 case ExpandType::Both: 649 queryTypeExpected = true; 650 str += '*'; 651 break; 652 } 653 if (!queryTypeExpected) 654 { 655 return std::nullopt; 656 } 657 str += "($levels="; 658 str += std::to_string(query.expandLevel - 1); 659 str += ')'; 660 return str; 661 } 662 663 class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 664 { 665 public: 666 // This object takes a single asyncResp object as the "final" one, then 667 // allows callers to attach sub-responses within the json tree that need 668 // to be executed and filled into their appropriate locations. This 669 // class manages the final "merge" of the json resources. 670 MultiAsyncResp(crow::App& appIn, 671 std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 672 app(appIn), 673 finalRes(std::move(finalResIn)) 674 {} 675 676 void addAwaitingResponse( 677 const std::shared_ptr<bmcweb::AsyncResp>& res, 678 const nlohmann::json::json_pointer& finalExpandLocation) 679 { 680 res->res.setCompleteRequestHandler(std::bind_front( 681 placeResultStatic, shared_from_this(), finalExpandLocation)); 682 } 683 684 void placeResult(const nlohmann::json::json_pointer& locationToPlace, 685 crow::Response& res) 686 { 687 nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 688 finalObj = std::move(res.jsonValue); 689 } 690 691 // Handles the very first level of Expand, and starts a chain of sub-queries 692 // for deeper levels. 693 void startQuery(const Query& query) 694 { 695 std::vector<ExpandNode> nodes = 696 findNavigationReferences(query.expandType, finalRes->res.jsonValue); 697 BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 698 const std::optional<std::string> queryStr = formatQueryForExpand(query); 699 if (!queryStr) 700 { 701 messages::internalError(finalRes->res); 702 return; 703 } 704 for (const ExpandNode& node : nodes) 705 { 706 const std::string subQuery = node.uri + *queryStr; 707 BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 708 std::error_code ec; 709 crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 710 ec); 711 if (ec) 712 { 713 messages::internalError(finalRes->res); 714 return; 715 } 716 717 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 718 BMCWEB_LOG_DEBUG << "setting completion handler on " 719 << &asyncResp->res; 720 721 addAwaitingResponse(asyncResp, node.location); 722 app.handle(newReq, asyncResp); 723 } 724 } 725 726 private: 727 static void 728 placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 729 const nlohmann::json::json_pointer& locationToPlace, 730 crow::Response& res) 731 { 732 multi->placeResult(locationToPlace, res); 733 } 734 735 crow::App& app; 736 std::shared_ptr<bmcweb::AsyncResp> finalRes; 737 }; 738 739 inline void processTopAndSkip(const Query& query, crow::Response& res) 740 { 741 if (!query.skip && !query.top) 742 { 743 // No work to do. 744 return; 745 } 746 nlohmann::json::object_t* obj = 747 res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 748 if (obj == nullptr) 749 { 750 // Shouldn't be possible. All responses should be objects. 751 messages::internalError(res); 752 return; 753 } 754 755 BMCWEB_LOG_DEBUG << "Handling top/skip"; 756 nlohmann::json::object_t::iterator members = obj->find("Members"); 757 if (members == obj->end()) 758 { 759 // From the Redfish specification 7.3.1 760 // ... the HTTP 400 Bad Request status code with the 761 // QueryNotSupportedOnResource message from the Base Message Registry 762 // for any supported query parameters that apply only to resource 763 // collections but are used on singular resources. 764 messages::queryNotSupportedOnResource(res); 765 return; 766 } 767 768 nlohmann::json::array_t* arr = 769 members->second.get_ptr<nlohmann::json::array_t*>(); 770 if (arr == nullptr) 771 { 772 messages::internalError(res); 773 return; 774 } 775 776 if (query.skip) 777 { 778 // Per section 7.3.1 of the Redfish specification, $skip is run before 779 // $top Can only skip as many values as we have 780 size_t skip = std::min(arr->size(), *query.skip); 781 arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 782 } 783 if (query.top) 784 { 785 size_t top = std::min(arr->size(), *query.top); 786 arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 787 } 788 } 789 790 // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 791 // not in the |currNode| Trie node. 792 inline void recursiveSelect(nlohmann::json& currRoot, 793 const SelectTrieNode& currNode) 794 { 795 nlohmann::json::object_t* object = 796 currRoot.get_ptr<nlohmann::json::object_t*>(); 797 if (object != nullptr) 798 { 799 BMCWEB_LOG_DEBUG << "Current JSON is an object"; 800 auto it = currRoot.begin(); 801 while (it != currRoot.end()) 802 { 803 auto nextIt = std::next(it); 804 BMCWEB_LOG_DEBUG << "key=" << it.key(); 805 const SelectTrieNode* nextNode = currNode.find(it.key()); 806 // Per the Redfish spec section 7.3.3, the service shall select 807 // certain properties as if $select was omitted. This applies to 808 // every TrieNode that contains leaves and the root. 809 constexpr std::array<std::string_view, 5> reservedProperties = { 810 "@odata.id", "@odata.type", "@odata.context", "@odata.etag", 811 "error"}; 812 bool reserved = 813 std::find(reservedProperties.begin(), reservedProperties.end(), 814 it.key()) != reservedProperties.end(); 815 if (reserved || (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 nlohmann::json::array_t* array = 832 currRoot.get_ptr<nlohmann::json::array_t*>(); 833 if (array != nullptr) 834 { 835 BMCWEB_LOG_DEBUG << "Current JSON is an array"; 836 // Array index is omitted, so reuse the same Trie node 837 for (nlohmann::json& nextRoot : *array) 838 { 839 recursiveSelect(nextRoot, currNode); 840 } 841 } 842 } 843 844 // The current implementation of $select still has the following TODOs due to 845 // ambiguity and/or complexity. 846 // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 847 // created for clarification. 848 // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 849 // etc. 850 inline void processSelect(crow::Response& intermediateResponse, 851 const SelectTrieNode& trieRoot) 852 { 853 BMCWEB_LOG_DEBUG << "Process $select quary parameter"; 854 recursiveSelect(intermediateResponse.jsonValue, trieRoot); 855 } 856 857 inline void 858 processAllParams(crow::App& app, const Query& query, 859 std::function<void(crow::Response&)>& completionHandler, 860 crow::Response& intermediateResponse) 861 { 862 if (!completionHandler) 863 { 864 BMCWEB_LOG_DEBUG << "Function was invalid?"; 865 return; 866 } 867 868 BMCWEB_LOG_DEBUG << "Processing query params"; 869 // If the request failed, there's no reason to even try to run query 870 // params. 871 if (intermediateResponse.resultInt() < 200 || 872 intermediateResponse.resultInt() >= 400) 873 { 874 completionHandler(intermediateResponse); 875 return; 876 } 877 if (query.isOnly) 878 { 879 processOnly(app, intermediateResponse, completionHandler); 880 return; 881 } 882 883 if (query.top || query.skip) 884 { 885 processTopAndSkip(query, intermediateResponse); 886 } 887 888 if (query.expandType != ExpandType::None) 889 { 890 BMCWEB_LOG_DEBUG << "Executing expand query"; 891 auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 892 std::move(intermediateResponse)); 893 894 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 895 auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 896 multi->startQuery(query); 897 return; 898 } 899 900 // According to Redfish Spec Section 7.3.1, $select is the last parameter to 901 // to process 902 if (!query.selectTrie.root.empty()) 903 { 904 processSelect(intermediateResponse, query.selectTrie.root); 905 } 906 907 completionHandler(intermediateResponse); 908 } 909 910 } // namespace query_param 911 } // namespace redfish 912