140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3f4c99e70SEd Tanous #pragma once 4d5c80ad9SNan Zhou #include "bmcweb_config.h" 5d5c80ad9SNan Zhou 6f4c99e70SEd Tanous #include "app.hpp" 7f4c99e70SEd Tanous #include "async_resp.hpp" 85d92fffcSrohitpai #include "error_code.hpp" 9f4c99e70SEd Tanous #include "error_messages.hpp" 1025991f7dSEd Tanous #include "filter_expr_executor.hpp" 11d7857201SEd Tanous #include "filter_expr_parser_ast.hpp" 1225991f7dSEd Tanous #include "filter_expr_printer.hpp" 13f4c99e70SEd Tanous #include "http_request.hpp" 1402cad96eSEd Tanous #include "http_response.hpp" 1595c6307aSEd Tanous #include "json_formatters.hpp" 16d5c80ad9SNan Zhou #include "logging.hpp" 17*c1a75ebcSrohitpai #include "redfishoemrule.hpp" 1850ebd4afSEd Tanous #include "str_utility.hpp" 19*c1a75ebcSrohitpai #include "utils/json_utils.hpp" 20f4c99e70SEd Tanous 21d7857201SEd Tanous #include <unistd.h> 22d5c80ad9SNan Zhou 23d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp> 24d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp> 25d5c80ad9SNan Zhou #include <boost/url/params_view.hpp> 26d5c80ad9SNan Zhou #include <nlohmann/json.hpp> 27d5c80ad9SNan Zhou 28d5c80ad9SNan Zhou #include <algorithm> 29e155ab54SNan Zhou #include <array> 30e155ab54SNan Zhou #include <cctype> 317cf436c9SEd Tanous #include <charconv> 32d7857201SEd Tanous #include <cstddef> 33d5c80ad9SNan Zhou #include <cstdint> 34d5c80ad9SNan Zhou #include <functional> 35e155ab54SNan Zhou #include <iterator> 36d5c80ad9SNan Zhou #include <limits> 37d5c80ad9SNan Zhou #include <map> 38d5c80ad9SNan Zhou #include <memory> 39d5c80ad9SNan Zhou #include <optional> 403544d2a7SEd Tanous #include <ranges> 41f4c99e70SEd Tanous #include <string> 42f4c99e70SEd Tanous #include <string_view> 43d5c80ad9SNan Zhou #include <system_error> 447cf436c9SEd Tanous #include <utility> 45f4c99e70SEd Tanous #include <vector> 46f4c99e70SEd Tanous 47f4c99e70SEd Tanous namespace redfish 48f4c99e70SEd Tanous { 49f4c99e70SEd Tanous namespace query_param 50f4c99e70SEd Tanous { 51f4c99e70SEd Tanous 527cf436c9SEd Tanous enum class ExpandType : uint8_t 537cf436c9SEd Tanous { 547cf436c9SEd Tanous None, 557cf436c9SEd Tanous Links, 567cf436c9SEd Tanous NotLinks, 577cf436c9SEd Tanous Both, 587cf436c9SEd Tanous }; 597cf436c9SEd Tanous 60827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|. 61827c4902SNan Zhou class SelectTrieNode 62827c4902SNan Zhou { 63827c4902SNan Zhou public: 64827c4902SNan Zhou SelectTrieNode() = default; 65827c4902SNan Zhou 66827c4902SNan Zhou const SelectTrieNode* find(const std::string& jsonKey) const 67827c4902SNan Zhou { 68827c4902SNan Zhou auto it = children.find(jsonKey); 69827c4902SNan Zhou if (it == children.end()) 70827c4902SNan Zhou { 71827c4902SNan Zhou return nullptr; 72827c4902SNan Zhou } 73827c4902SNan Zhou return &it->second; 74827c4902SNan Zhou } 75827c4902SNan Zhou 76827c4902SNan Zhou // Creates a new node if the key doesn't exist, returns the reference to the 77827c4902SNan Zhou // newly created node; otherwise, return the reference to the existing node 78827c4902SNan Zhou SelectTrieNode* emplace(std::string_view jsonKey) 79827c4902SNan Zhou { 80827c4902SNan Zhou auto [it, _] = children.emplace(jsonKey, SelectTrieNode{}); 81827c4902SNan Zhou return &it->second; 82827c4902SNan Zhou } 83827c4902SNan Zhou 84827c4902SNan Zhou bool empty() const 85827c4902SNan Zhou { 86827c4902SNan Zhou return children.empty(); 87827c4902SNan Zhou } 88827c4902SNan Zhou 89827c4902SNan Zhou void clear() 90827c4902SNan Zhou { 91827c4902SNan Zhou children.clear(); 92827c4902SNan Zhou } 93827c4902SNan Zhou 94827c4902SNan Zhou void setToSelected() 95827c4902SNan Zhou { 96827c4902SNan Zhou selected = true; 97827c4902SNan Zhou } 98827c4902SNan Zhou 99827c4902SNan Zhou bool isSelected() const 100827c4902SNan Zhou { 101827c4902SNan Zhou return selected; 102827c4902SNan Zhou } 103827c4902SNan Zhou 104827c4902SNan Zhou private: 105827c4902SNan Zhou std::map<std::string, SelectTrieNode, std::less<>> children; 106827c4902SNan Zhou bool selected = false; 107827c4902SNan Zhou }; 108827c4902SNan Zhou 109827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among 110827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties) 111827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property) 112827c4902SNan Zhou { 113827c4902SNan Zhou // These a magic number, but with it it's less likely that this code 114827c4902SNan Zhou // introduces CVE; e.g., too large properties crash the service. 115827c4902SNan Zhou constexpr int maxPropertyLength = 60; 116827c4902SNan Zhou if (property.empty() || property.size() > maxPropertyLength) 117827c4902SNan Zhou { 118827c4902SNan Zhou return false; 119827c4902SNan Zhou } 120827c4902SNan Zhou for (char ch : property) 121827c4902SNan Zhou { 122827c4902SNan Zhou if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' && 123827c4902SNan Zhou ch != '@' && ch != '.') 124827c4902SNan Zhou { 125827c4902SNan Zhou return false; 126827c4902SNan Zhou } 127827c4902SNan Zhou } 128827c4902SNan Zhou return true; 129827c4902SNan Zhou } 130827c4902SNan Zhou 131827c4902SNan Zhou struct SelectTrie 132827c4902SNan Zhou { 133827c4902SNan Zhou SelectTrie() = default; 134827c4902SNan Zhou 135827c4902SNan Zhou // Inserts a $select value; returns false if the nestedProperty is illegal. 136827c4902SNan Zhou bool insertNode(std::string_view nestedProperty) 137827c4902SNan Zhou { 138827c4902SNan Zhou if (nestedProperty.empty()) 139827c4902SNan Zhou { 140827c4902SNan Zhou return false; 141827c4902SNan Zhou } 142827c4902SNan Zhou SelectTrieNode* currNode = &root; 143827c4902SNan Zhou size_t index = nestedProperty.find_first_of('/'); 144827c4902SNan Zhou while (!nestedProperty.empty()) 145827c4902SNan Zhou { 146827c4902SNan Zhou std::string_view property = nestedProperty.substr(0, index); 147827c4902SNan Zhou if (!isSelectedPropertyAllowed(property)) 148827c4902SNan Zhou { 149827c4902SNan Zhou return false; 150827c4902SNan Zhou } 151827c4902SNan Zhou currNode = currNode->emplace(property); 152827c4902SNan Zhou if (index == std::string::npos) 153827c4902SNan Zhou { 154827c4902SNan Zhou break; 155827c4902SNan Zhou } 156827c4902SNan Zhou nestedProperty.remove_prefix(index + 1); 157827c4902SNan Zhou index = nestedProperty.find_first_of('/'); 158827c4902SNan Zhou } 159827c4902SNan Zhou currNode->setToSelected(); 160827c4902SNan Zhou return true; 161827c4902SNan Zhou } 162827c4902SNan Zhou 163827c4902SNan Zhou SelectTrieNode root; 164827c4902SNan Zhou }; 165827c4902SNan Zhou 166a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 167f4c99e70SEd Tanous struct Query 168f4c99e70SEd Tanous { 169a6b9125fSNan Zhou // Only 170f4c99e70SEd Tanous bool isOnly = false; 17125991f7dSEd Tanous 172a6b9125fSNan Zhou // Expand 173a6b9125fSNan Zhou uint8_t expandLevel = 0; 1747cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 175c937d2bfSEd Tanous 176c937d2bfSEd Tanous // Skip 1773648c8beSEd Tanous std::optional<size_t> skip = std::nullopt; 178c937d2bfSEd Tanous 179c937d2bfSEd Tanous // Top 1805143f7a5SJiaqing Zhao static constexpr size_t maxTop = 1000; // Max entries a response contain 1813648c8beSEd Tanous std::optional<size_t> top = std::nullopt; 182e155ab54SNan Zhou 18325991f7dSEd Tanous // Filter 18425991f7dSEd Tanous std::optional<filter_ast::LogicalAnd> filter = std::nullopt; 18525991f7dSEd Tanous 186e155ab54SNan Zhou // Select 18747f2934cSEd Tanous // Unclear how to make this use structured initialization without this. 18847f2934cSEd Tanous // Might be a tidy bug? Ignore for now 18947f2934cSEd Tanous // NOLINTNEXTLINE(readability-redundant-member-init) 19047f2934cSEd Tanous SelectTrie selectTrie{}; 191f4c99e70SEd Tanous }; 192f4c99e70SEd Tanous 193a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 194a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 195a6b9125fSNan Zhou // the processing. 196a6b9125fSNan Zhou struct QueryCapabilities 197a6b9125fSNan Zhou { 198a6b9125fSNan Zhou bool canDelegateOnly = false; 199c937d2bfSEd Tanous bool canDelegateTop = false; 200c937d2bfSEd Tanous bool canDelegateSkip = false; 201a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 202e155ab54SNan Zhou bool canDelegateSelect = false; 203a6b9125fSNan Zhou }; 204a6b9125fSNan Zhou 205a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 206a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 207a6b9125fSNan Zhou // function will take care of it. 208a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 209a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 210a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 211a6b9125fSNan Zhou { 212f1a1e3dcSEd Tanous Query delegated{}; 213a6b9125fSNan Zhou // delegate only 214a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 215a6b9125fSNan Zhou { 216a6b9125fSNan Zhou delegated.isOnly = true; 217a6b9125fSNan Zhou query.isOnly = false; 218a6b9125fSNan Zhou } 219a6b9125fSNan Zhou // delegate expand as much as we can 220a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 221a6b9125fSNan Zhou { 222a6b9125fSNan Zhou delegated.expandType = query.expandType; 223a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 224a6b9125fSNan Zhou { 225a6b9125fSNan Zhou query.expandType = ExpandType::None; 226a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 227a6b9125fSNan Zhou query.expandLevel = 0; 228a6b9125fSNan Zhou } 229a6b9125fSNan Zhou else 230a6b9125fSNan Zhou { 231a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 232a6b9125fSNan Zhou } 233a6b9125fSNan Zhou } 234c937d2bfSEd Tanous 235c937d2bfSEd Tanous // delegate top 2363648c8beSEd Tanous if (query.top && queryCapabilities.canDelegateTop) 237c937d2bfSEd Tanous { 238c937d2bfSEd Tanous delegated.top = query.top; 2393648c8beSEd Tanous query.top = std::nullopt; 240c937d2bfSEd Tanous } 241c937d2bfSEd Tanous 242c937d2bfSEd Tanous // delegate skip 2433648c8beSEd Tanous if (query.skip && queryCapabilities.canDelegateSkip) 244c937d2bfSEd Tanous { 245c937d2bfSEd Tanous delegated.skip = query.skip; 246c937d2bfSEd Tanous query.skip = 0; 247c937d2bfSEd Tanous } 248e155ab54SNan Zhou 249e155ab54SNan Zhou // delegate select 250827c4902SNan Zhou if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect) 251e155ab54SNan Zhou { 252827c4902SNan Zhou delegated.selectTrie = std::move(query.selectTrie); 253827c4902SNan Zhou query.selectTrie.root.clear(); 254e155ab54SNan Zhou } 255a6b9125fSNan Zhou return delegated; 256a6b9125fSNan Zhou } 257a6b9125fSNan Zhou 2587cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 2597cf436c9SEd Tanous { 2607cf436c9SEd Tanous if (value.empty()) 2617cf436c9SEd Tanous { 2627cf436c9SEd Tanous return false; 2637cf436c9SEd Tanous } 2647cf436c9SEd Tanous switch (value[0]) 2657cf436c9SEd Tanous { 2667cf436c9SEd Tanous case '*': 2677cf436c9SEd Tanous query.expandType = ExpandType::Both; 2687cf436c9SEd Tanous break; 2697cf436c9SEd Tanous case '.': 2707cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 2717cf436c9SEd Tanous break; 2727cf436c9SEd Tanous case '~': 2737cf436c9SEd Tanous query.expandType = ExpandType::Links; 2747cf436c9SEd Tanous break; 2757cf436c9SEd Tanous default: 2767cf436c9SEd Tanous return false; 2777cf436c9SEd Tanous } 2787cf436c9SEd Tanous value.remove_prefix(1); 2797cf436c9SEd Tanous if (value.empty()) 2807cf436c9SEd Tanous { 2817cf436c9SEd Tanous query.expandLevel = 1; 2827cf436c9SEd Tanous return true; 2837cf436c9SEd Tanous } 2847cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 2857cf436c9SEd Tanous if (!value.starts_with(levels)) 2867cf436c9SEd Tanous { 2877cf436c9SEd Tanous return false; 2887cf436c9SEd Tanous } 2897cf436c9SEd Tanous value.remove_prefix(levels.size()); 2907cf436c9SEd Tanous 2912bd4ab43SPatrick Williams auto it = std::from_chars(value.begin(), value.end(), query.expandLevel); 2927cf436c9SEd Tanous if (it.ec != std::errc()) 2937cf436c9SEd Tanous { 2947cf436c9SEd Tanous return false; 2957cf436c9SEd Tanous } 2962bd4ab43SPatrick Williams value.remove_prefix( 2972bd4ab43SPatrick Williams static_cast<size_t>(std::distance(value.begin(), it.ptr))); 2987cf436c9SEd Tanous return value == ")"; 2997cf436c9SEd Tanous } 3007cf436c9SEd Tanous 301c937d2bfSEd Tanous enum class QueryError 302c937d2bfSEd Tanous { 303c937d2bfSEd Tanous Ok, 304c937d2bfSEd Tanous OutOfRange, 305c937d2bfSEd Tanous ValueFormat, 306c937d2bfSEd Tanous }; 307c937d2bfSEd Tanous 308c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 309c937d2bfSEd Tanous { 310bd79bce8SPatrick Williams std::from_chars_result r = 311bd79bce8SPatrick Williams std::from_chars(value.begin(), value.end(), param); 312c937d2bfSEd Tanous 313c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 314c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 315c937d2bfSEd Tanous { 316c937d2bfSEd Tanous return QueryError::OutOfRange; 317c937d2bfSEd Tanous } 318c937d2bfSEd Tanous // All other errors are value format 319c937d2bfSEd Tanous if (r.ec != std::errc()) 320c937d2bfSEd Tanous { 321c937d2bfSEd Tanous return QueryError::ValueFormat; 322c937d2bfSEd Tanous } 323c937d2bfSEd Tanous return QueryError::Ok; 324c937d2bfSEd Tanous } 325c937d2bfSEd Tanous 326c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 327c937d2bfSEd Tanous { 3283648c8beSEd Tanous return getNumericParam(value, query.skip.emplace()); 329c937d2bfSEd Tanous } 330c937d2bfSEd Tanous 331c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 332c937d2bfSEd Tanous { 3333648c8beSEd Tanous QueryError ret = getNumericParam(value, query.top.emplace()); 334c937d2bfSEd Tanous if (ret != QueryError::Ok) 335c937d2bfSEd Tanous { 336c937d2bfSEd Tanous return ret; 337c937d2bfSEd Tanous } 338c937d2bfSEd Tanous 339c937d2bfSEd Tanous // Range check for sanity. 3405143f7a5SJiaqing Zhao if (query.top > Query::maxTop) 341c937d2bfSEd Tanous { 342c937d2bfSEd Tanous return QueryError::OutOfRange; 343c937d2bfSEd Tanous } 344c937d2bfSEd Tanous 345c937d2bfSEd Tanous return QueryError::Ok; 346c937d2bfSEd Tanous } 347c937d2bfSEd Tanous 348e155ab54SNan Zhou // Parses and validates the $select parameter. 349e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be 350e155ab54SNan Zhou // comma separated Resource Path 351e155ab54SNan Zhou // Ref: 352e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 353e155ab54SNan Zhou // 2. 354e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 355e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query) 356e155ab54SNan Zhou { 357e155ab54SNan Zhou std::vector<std::string> properties; 35850ebd4afSEd Tanous bmcweb::split(properties, value, ','); 359e155ab54SNan Zhou if (properties.empty()) 360e155ab54SNan Zhou { 361e155ab54SNan Zhou return false; 362e155ab54SNan Zhou } 363e155ab54SNan Zhou // These a magic number, but with it it's less likely that this code 364e155ab54SNan Zhou // introduces CVE; e.g., too large properties crash the service. 365e155ab54SNan Zhou constexpr int maxNumProperties = 10; 366e155ab54SNan Zhou if (properties.size() > maxNumProperties) 367e155ab54SNan Zhou { 368e155ab54SNan Zhou return false; 369e155ab54SNan Zhou } 370827c4902SNan Zhou for (const auto& property : properties) 371e155ab54SNan Zhou { 372827c4902SNan Zhou if (!query.selectTrie.insertNode(property)) 373e155ab54SNan Zhou { 374e155ab54SNan Zhou return false; 375e155ab54SNan Zhou } 376e155ab54SNan Zhou } 377e155ab54SNan Zhou return true; 378e155ab54SNan Zhou } 379e155ab54SNan Zhou 38025991f7dSEd Tanous // Parses and validates the $filter parameter. 38125991f7dSEd Tanous inline bool getFilterParam(std::string_view value, Query& query) 38225991f7dSEd Tanous { 38325991f7dSEd Tanous query.filter = parseFilter(value); 38425991f7dSEd Tanous return query.filter.has_value(); 38525991f7dSEd Tanous } 38625991f7dSEd Tanous 387504af5a0SPatrick Williams inline std::optional<Query> parseParameters(boost::urls::params_view urlParams, 388504af5a0SPatrick Williams crow::Response& res) 389f4c99e70SEd Tanous { 390f1a1e3dcSEd Tanous Query ret{}; 391f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 392f4c99e70SEd Tanous { 393079360aeSEd Tanous if (it.key == "only") 394f4c99e70SEd Tanous { 395f4c99e70SEd Tanous if (!it.value.empty()) 396f4c99e70SEd Tanous { 397079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 398f4c99e70SEd Tanous return std::nullopt; 399f4c99e70SEd Tanous } 400f4c99e70SEd Tanous ret.isOnly = true; 401f4c99e70SEd Tanous } 40225b54dbaSEd Tanous else if (it.key == "$expand" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 4037cf436c9SEd Tanous { 404079360aeSEd Tanous if (!getExpandType(it.value, ret)) 4057cf436c9SEd Tanous { 406079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4077cf436c9SEd Tanous return std::nullopt; 408f4c99e70SEd Tanous } 4097cf436c9SEd Tanous } 410079360aeSEd Tanous else if (it.key == "$top") 411c937d2bfSEd Tanous { 412079360aeSEd Tanous QueryError topRet = getTopParam(it.value, ret); 413c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 414c937d2bfSEd Tanous { 415079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 416c937d2bfSEd Tanous return std::nullopt; 417c937d2bfSEd Tanous } 418c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 419c937d2bfSEd Tanous { 420c937d2bfSEd Tanous messages::queryParameterOutOfRange( 421079360aeSEd Tanous res, it.value, "$top", 422079360aeSEd Tanous "0-" + std::to_string(Query::maxTop)); 423c937d2bfSEd Tanous return std::nullopt; 424c937d2bfSEd Tanous } 425c937d2bfSEd Tanous } 426079360aeSEd Tanous else if (it.key == "$skip") 427c937d2bfSEd Tanous { 428079360aeSEd Tanous QueryError topRet = getSkipParam(it.value, ret); 429c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 430c937d2bfSEd Tanous { 431079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 432c937d2bfSEd Tanous return std::nullopt; 433c937d2bfSEd Tanous } 434c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 435c937d2bfSEd Tanous { 436c937d2bfSEd Tanous messages::queryParameterOutOfRange( 437079360aeSEd Tanous res, it.value, it.key, 438a926c53eSJiaqing Zhao "0-" + std::to_string(std::numeric_limits<size_t>::max())); 439c937d2bfSEd Tanous return std::nullopt; 440c937d2bfSEd Tanous } 441c937d2bfSEd Tanous } 442079360aeSEd Tanous else if (it.key == "$select") 443e155ab54SNan Zhou { 444079360aeSEd Tanous if (!getSelectParam(it.value, ret)) 445e155ab54SNan Zhou { 446079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 447e155ab54SNan Zhou return std::nullopt; 448e155ab54SNan Zhou } 449e155ab54SNan Zhou } 45025991f7dSEd Tanous else if (it.key == "$filter" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 45125991f7dSEd Tanous { 45225991f7dSEd Tanous if (!getFilterParam(it.value, ret)) 45325991f7dSEd Tanous { 45425991f7dSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 45525991f7dSEd Tanous return std::nullopt; 45625991f7dSEd Tanous } 45725991f7dSEd Tanous } 4587cf436c9SEd Tanous else 4597cf436c9SEd Tanous { 4607cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 461079360aeSEd Tanous if (it.key.starts_with("$")) 4627cf436c9SEd Tanous { 4637cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 4647cf436c9SEd Tanous // status code for any unsupported query parameters that 4657cf436c9SEd Tanous // start with $ . 466079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4677cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 4687cf436c9SEd Tanous return std::nullopt; 4697cf436c9SEd Tanous } 4707cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 4717cf436c9SEd Tanous // not begin with $ ." 4727cf436c9SEd Tanous } 4737cf436c9SEd Tanous } 4747cf436c9SEd Tanous 475827c4902SNan Zhou if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 476e155ab54SNan Zhou { 477e155ab54SNan Zhou messages::queryCombinationInvalid(res); 478e155ab54SNan Zhou return std::nullopt; 479e155ab54SNan Zhou } 480e155ab54SNan Zhou 481f4c99e70SEd Tanous return ret; 482f4c99e70SEd Tanous } 483f4c99e70SEd Tanous 484f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 485f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 486f4c99e70SEd Tanous { 48762598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing only query param"); 488f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 489f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 490f4c99e70SEd Tanous { 491f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 492f4c99e70SEd Tanous completionHandler(res); 493f4c99e70SEd Tanous return false; 494f4c99e70SEd Tanous } 495f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 496f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 497f4c99e70SEd Tanous { 49862598e31SEd Tanous BMCWEB_LOG_DEBUG( 49962598e31SEd Tanous "Members contains {} element, returning full collection.", 50062598e31SEd Tanous itMembers->size()); 501f4c99e70SEd Tanous completionHandler(res); 502f4c99e70SEd Tanous return false; 503f4c99e70SEd Tanous } 504f4c99e70SEd Tanous 505f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 506f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 507f4c99e70SEd Tanous { 50862598e31SEd Tanous BMCWEB_LOG_DEBUG("No found odata.id"); 509f4c99e70SEd Tanous messages::internalError(res); 510f4c99e70SEd Tanous completionHandler(res); 511f4c99e70SEd Tanous return false; 512f4c99e70SEd Tanous } 513f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 514f4c99e70SEd Tanous if (url == nullptr) 515f4c99e70SEd Tanous { 51662598e31SEd Tanous BMCWEB_LOG_DEBUG("@odata.id wasn't a string????"); 517f4c99e70SEd Tanous messages::internalError(res); 518f4c99e70SEd Tanous completionHandler(res); 519f4c99e70SEd Tanous return false; 520f4c99e70SEd Tanous } 521f4c99e70SEd Tanous // TODO(Ed) copy request headers? 522f4c99e70SEd Tanous // newReq.session = req.session; 523f4c99e70SEd Tanous std::error_code ec; 524102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 525102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, *url, 11}, ec); 526f4c99e70SEd Tanous if (ec) 527f4c99e70SEd Tanous { 528f4c99e70SEd Tanous messages::internalError(res); 529f4c99e70SEd Tanous completionHandler(res); 530f4c99e70SEd Tanous return false; 531f4c99e70SEd Tanous } 532f4c99e70SEd Tanous 533f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 53462598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 53562598e31SEd Tanous logPtr(&asyncResp->res)); 536f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 537f4c99e70SEd Tanous app.handle(newReq, asyncResp); 538f4c99e70SEd Tanous return true; 539f4c99e70SEd Tanous } 540f4c99e70SEd Tanous 5417cf436c9SEd Tanous struct ExpandNode 5427cf436c9SEd Tanous { 5437cf436c9SEd Tanous nlohmann::json::json_pointer location; 5447cf436c9SEd Tanous std::string uri; 5457cf436c9SEd Tanous 5469de65b34SEd Tanous bool operator==(const ExpandNode& other) const 5477cf436c9SEd Tanous { 5487cf436c9SEd Tanous return location == other.location && uri == other.uri; 5497cf436c9SEd Tanous } 5507cf436c9SEd Tanous }; 5517cf436c9SEd Tanous 55287788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 553c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 55437b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 55587788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 55687788abfSEd Tanous 55787788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 558c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 55937b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 56087788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 56187788abfSEd Tanous 5627cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 5637cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 5647cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 5657cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 5667cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 5677cf436c9SEd Tanous // with the keys from the jsonResponse object 5687cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 5697cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 57037b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 57132cdb4a7SWilly Tu bool inLinks, std::vector<ExpandNode>& out) 5727cf436c9SEd Tanous { 5737cf436c9SEd Tanous // If no expand is needed, return early 5747cf436c9SEd Tanous if (eType == ExpandType::None) 5757cf436c9SEd Tanous { 5767cf436c9SEd Tanous return; 5777cf436c9SEd Tanous } 578ad595fa6SEd Tanous 5797cf436c9SEd Tanous nlohmann::json::array_t* array = 5807cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 5817cf436c9SEd Tanous if (array != nullptr) 5827cf436c9SEd Tanous { 58337b1f7beSEd Tanous findNavigationReferencesInArrayRecursive(eType, *array, jsonPtr, depth, 58487788abfSEd Tanous skipDepth, inLinks, out); 58587788abfSEd Tanous } 58687788abfSEd Tanous nlohmann::json::object_t* obj = 58787788abfSEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 58887788abfSEd Tanous if (obj == nullptr) 58987788abfSEd Tanous { 59087788abfSEd Tanous return; 59187788abfSEd Tanous } 59237b1f7beSEd Tanous findNavigationReferencesInObjectRecursive(eType, *obj, jsonPtr, depth, 59337b1f7beSEd Tanous skipDepth, inLinks, out); 59487788abfSEd Tanous } 59587788abfSEd Tanous 59687788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 597c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 59837b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 59987788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 60087788abfSEd Tanous { 6017cf436c9SEd Tanous size_t index = 0; 6027cf436c9SEd Tanous // For arrays, walk every element in the array 603c59e338cSEd Tanous for (auto& element : array) 6047cf436c9SEd Tanous { 60537b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / index; 60662598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr.to_string()); 607ad595fa6SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, depth, 60832cdb4a7SWilly Tu skipDepth, inLinks, out); 6097cf436c9SEd Tanous index++; 6107cf436c9SEd Tanous } 6117cf436c9SEd Tanous } 61287788abfSEd Tanous 61387788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 614c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 61537b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 61687788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 6177cf436c9SEd Tanous { 6187cf436c9SEd Tanous // Navigation References only ever have a single element 619c59e338cSEd Tanous if (obj.size() == 1) 6207cf436c9SEd Tanous { 621c59e338cSEd Tanous if (obj.begin()->first == "@odata.id") 6227cf436c9SEd Tanous { 6237cf436c9SEd Tanous const std::string* uri = 624c59e338cSEd Tanous obj.begin()->second.get_ptr<const std::string*>(); 6257cf436c9SEd Tanous if (uri != nullptr) 6267cf436c9SEd Tanous { 62762598e31SEd Tanous BMCWEB_LOG_DEBUG("Found {} at {}", *uri, jsonPtr.to_string()); 62832cdb4a7SWilly Tu if (skipDepth == 0) 62932cdb4a7SWilly Tu { 63037b1f7beSEd Tanous out.push_back({jsonPtr, *uri}); 63132cdb4a7SWilly Tu } 632ad595fa6SEd Tanous return; 6337cf436c9SEd Tanous } 6347cf436c9SEd Tanous } 6357cf436c9SEd Tanous } 636ad595fa6SEd Tanous 637ad595fa6SEd Tanous int newDepth = depth; 638c59e338cSEd Tanous auto odataId = obj.find("@odata.id"); 639c59e338cSEd Tanous if (odataId != obj.end()) 640ad595fa6SEd Tanous { 641ad595fa6SEd Tanous // The Redfish spec requires all resources to include the resource 642ad595fa6SEd Tanous // identifier. If the object has multiple elements and one of them is 643ad595fa6SEd Tanous // "@odata.id" then that means we have entered a new level / expanded 644ad595fa6SEd Tanous // resource. We need to stop traversing if we're already at the desired 645ad595fa6SEd Tanous // depth 646c59e338cSEd Tanous if (obj.size() > 1) 64732cdb4a7SWilly Tu { 64832cdb4a7SWilly Tu if (depth == 0) 649ad595fa6SEd Tanous { 650ad595fa6SEd Tanous return; 651ad595fa6SEd Tanous } 65232cdb4a7SWilly Tu if (skipDepth > 0) 65332cdb4a7SWilly Tu { 65432cdb4a7SWilly Tu skipDepth--; 65532cdb4a7SWilly Tu } 65632cdb4a7SWilly Tu } 65732cdb4a7SWilly Tu 65832cdb4a7SWilly Tu if (skipDepth == 0) 65932cdb4a7SWilly Tu { 660ad595fa6SEd Tanous newDepth--; 661ad595fa6SEd Tanous } 66232cdb4a7SWilly Tu } 663ad595fa6SEd Tanous 6647cf436c9SEd Tanous // Loop the object and look for links 665c59e338cSEd Tanous for (auto& element : obj) 6667cf436c9SEd Tanous { 667e479ad58SNan Zhou bool localInLinks = inLinks; 668e479ad58SNan Zhou if (!localInLinks) 6697cf436c9SEd Tanous { 6707cf436c9SEd Tanous // Check if this is a links node 671e479ad58SNan Zhou localInLinks = element.first == "Links"; 6727cf436c9SEd Tanous } 6737cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 6747cf436c9SEd Tanous // Per section 7.3 of the redfish specification 675e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 6767cf436c9SEd Tanous { 6777cf436c9SEd Tanous continue; 6787cf436c9SEd Tanous } 679e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 6807cf436c9SEd Tanous { 6817cf436c9SEd Tanous continue; 6827cf436c9SEd Tanous } 68337b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / element.first; 68462598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr); 6857cf436c9SEd Tanous 6867cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 68732cdb4a7SWilly Tu newDepth, skipDepth, localInLinks, 68832cdb4a7SWilly Tu out); 6897cf436c9SEd Tanous } 6907cf436c9SEd Tanous } 6917cf436c9SEd Tanous 692ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded 693ad595fa6SEd Tanous // response we may need need additional handling when the original URI was 694ad595fa6SEd Tanous // up tree from a top level collection. 695ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556 696ad595fa6SEd Tanous // lands. May want to avoid forwarding query params when request is uptree from 697ad595fa6SEd Tanous // a top level collection. 698bd79bce8SPatrick Williams inline std::vector<ExpandNode> findNavigationReferences( 699bd79bce8SPatrick Williams ExpandType eType, int depth, int skipDepth, nlohmann::json& jsonResponse) 7007cf436c9SEd Tanous { 7017cf436c9SEd Tanous std::vector<ExpandNode> ret; 70272c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 70332cdb4a7SWilly Tu // SkipDepth +1 since we are skipping the root by default. 70432cdb4a7SWilly Tu findNavigationReferencesRecursive(eType, jsonResponse, root, depth, 70532cdb4a7SWilly Tu skipDepth + 1, false, ret); 7067cf436c9SEd Tanous return ret; 7077cf436c9SEd Tanous } 7087cf436c9SEd Tanous 70972c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 710b66cf2a2SNan Zhou // Returns std::nullopt on failures. 71172c3ae33SNan Zhou // This function shall handle $select when it is added. 7128ece0e45SEd Tanous // There is no need to handle parameters that's not compatible with $expand, 71372c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 714b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query) 71572c3ae33SNan Zhou { 71672c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 71772c3ae33SNan Zhou if (query.expandLevel <= 1) 71872c3ae33SNan Zhou { 719b66cf2a2SNan Zhou return ""; 72072c3ae33SNan Zhou } 72172c3ae33SNan Zhou std::string str = "?$expand="; 72272c3ae33SNan Zhou switch (query.expandType) 72372c3ae33SNan Zhou { 72472c3ae33SNan Zhou case ExpandType::Links: 72572c3ae33SNan Zhou str += '~'; 72672c3ae33SNan Zhou break; 72772c3ae33SNan Zhou case ExpandType::NotLinks: 72872c3ae33SNan Zhou str += '.'; 72972c3ae33SNan Zhou break; 73072c3ae33SNan Zhou case ExpandType::Both: 73172c3ae33SNan Zhou str += '*'; 73272c3ae33SNan Zhou break; 733f1a1e3dcSEd Tanous case ExpandType::None: 734f1a1e3dcSEd Tanous return ""; 7354da0490bSEd Tanous default: 7364da0490bSEd Tanous return std::nullopt; 737b66cf2a2SNan Zhou } 73872c3ae33SNan Zhou str += "($levels="; 73972c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 74072c3ae33SNan Zhou str += ')'; 74172c3ae33SNan Zhou return str; 74272c3ae33SNan Zhou } 74372c3ae33SNan Zhou 7447cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 7457cf436c9SEd Tanous { 7467cf436c9SEd Tanous public: 7477cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 7487cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 7497cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 7507cf436c9SEd Tanous // class manages the final "merge" of the json resources. 7518a592810SEd Tanous MultiAsyncResp(crow::App& appIn, 7527cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 753*c1a75ebcSrohitpai app(&appIn), finalRes(std::move(finalResIn)) 754*c1a75ebcSrohitpai {} 755*c1a75ebcSrohitpai 756*c1a75ebcSrohitpai explicit MultiAsyncResp(std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 757*c1a75ebcSrohitpai app(nullptr), finalRes(std::move(finalResIn)) 7587cf436c9SEd Tanous {} 7597cf436c9SEd Tanous 7607cf436c9SEd Tanous void addAwaitingResponse( 76102cad96eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& res, 7627cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 7637cf436c9SEd Tanous { 7647cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 76572c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 7667cf436c9SEd Tanous } 7677cf436c9SEd Tanous 76872c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 7697cf436c9SEd Tanous crow::Response& res) 7707cf436c9SEd Tanous { 77162598e31SEd Tanous BMCWEB_LOG_DEBUG("placeResult for {}", locationToPlace); 7723590bd1dSNan Zhou propogateError(finalRes->res, res); 7733590bd1dSNan Zhou if (!res.jsonValue.is_object() || res.jsonValue.empty()) 7743590bd1dSNan Zhou { 7753590bd1dSNan Zhou return; 7763590bd1dSNan Zhou } 7777cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 7787cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 7797cf436c9SEd Tanous } 7807cf436c9SEd Tanous 78172c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 78272c3ae33SNan Zhou // for deeper levels. 78332cdb4a7SWilly Tu void startQuery(const Query& query, const Query& delegated) 78472c3ae33SNan Zhou { 785ad595fa6SEd Tanous std::vector<ExpandNode> nodes = findNavigationReferences( 78632cdb4a7SWilly Tu query.expandType, query.expandLevel, delegated.expandLevel, 78732cdb4a7SWilly Tu finalRes->res.jsonValue); 78862598e31SEd Tanous BMCWEB_LOG_DEBUG("{} nodes to traverse", nodes.size()); 789b66cf2a2SNan Zhou const std::optional<std::string> queryStr = formatQueryForExpand(query); 790b66cf2a2SNan Zhou if (!queryStr) 791b66cf2a2SNan Zhou { 792b66cf2a2SNan Zhou messages::internalError(finalRes->res); 793b66cf2a2SNan Zhou return; 794b66cf2a2SNan Zhou } 7957cf436c9SEd Tanous for (const ExpandNode& node : nodes) 7967cf436c9SEd Tanous { 797b66cf2a2SNan Zhou const std::string subQuery = node.uri + *queryStr; 79862598e31SEd Tanous BMCWEB_LOG_DEBUG("URL of subquery: {}", subQuery); 7997cf436c9SEd Tanous std::error_code ec; 800102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 801102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, subQuery, 802102a4cdaSJonathan Doman 11}, 8037cf436c9SEd Tanous ec); 8047cf436c9SEd Tanous if (ec) 8057cf436c9SEd Tanous { 80672c3ae33SNan Zhou messages::internalError(finalRes->res); 8077cf436c9SEd Tanous return; 8087cf436c9SEd Tanous } 8097cf436c9SEd Tanous 8107cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 81162598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 81262598e31SEd Tanous logPtr(&asyncResp->res)); 81372c3ae33SNan Zhou 81472c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 815*c1a75ebcSrohitpai if (app != nullptr) 816*c1a75ebcSrohitpai { 817*c1a75ebcSrohitpai app->handle(newReq, asyncResp); 818*c1a75ebcSrohitpai } 819*c1a75ebcSrohitpai } 820*c1a75ebcSrohitpai } 821*c1a75ebcSrohitpai 822*c1a75ebcSrohitpai static void startMultiFragmentHandle( 823*c1a75ebcSrohitpai const std::shared_ptr<crow::Request>& req, 824*c1a75ebcSrohitpai const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 825*c1a75ebcSrohitpai const std::shared_ptr<std::vector<OemBaseRule*>>& fragments, 826*c1a75ebcSrohitpai const std::shared_ptr<std::vector<std::string>>& params, 827*c1a75ebcSrohitpai const crow::Response& resIn) 828*c1a75ebcSrohitpai { 829*c1a75ebcSrohitpai asyncResp->res.jsonValue = resIn.jsonValue; 830*c1a75ebcSrohitpai auto multi = std::make_shared<MultiAsyncResp>(asyncResp); 831*c1a75ebcSrohitpai for (OemBaseRule* fragment : *fragments) 832*c1a75ebcSrohitpai { 833*c1a75ebcSrohitpai if (fragment != nullptr) 834*c1a75ebcSrohitpai { 835*c1a75ebcSrohitpai OemBaseRule& fragmentRule = *fragment; 836*c1a75ebcSrohitpai auto rsp = std::make_shared<bmcweb::AsyncResp>(); 837*c1a75ebcSrohitpai BMCWEB_LOG_DEBUG("Matched fragment GET rule '{}' {}", 838*c1a75ebcSrohitpai fragmentRule.rule, req->methodString()); 839*c1a75ebcSrohitpai BMCWEB_LOG_DEBUG( 840*c1a75ebcSrohitpai "Handling fragment rules: setting completion handler on {}", 841*c1a75ebcSrohitpai logPtr(&rsp->res)); 842*c1a75ebcSrohitpai std::optional<nlohmann::json::json_pointer> jsonFragmentPtr = 843*c1a75ebcSrohitpai json_util::createJsonPointerFromFragment(fragmentRule.rule); 844*c1a75ebcSrohitpai if (jsonFragmentPtr) 845*c1a75ebcSrohitpai { 846*c1a75ebcSrohitpai multi->addAwaitingResponse(rsp, *jsonFragmentPtr); 847*c1a75ebcSrohitpai fragmentRule.handle(*req, rsp, *params); 848*c1a75ebcSrohitpai } 849*c1a75ebcSrohitpai } 8507cf436c9SEd Tanous } 8517cf436c9SEd Tanous } 8527cf436c9SEd Tanous 8537cf436c9SEd Tanous private: 854504af5a0SPatrick Williams static void placeResultStatic( 855504af5a0SPatrick Williams const std::shared_ptr<MultiAsyncResp>& multi, 8567cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 8577cf436c9SEd Tanous crow::Response& res) 8587cf436c9SEd Tanous { 85972c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 8607cf436c9SEd Tanous } 8617cf436c9SEd Tanous 862*c1a75ebcSrohitpai crow::App* app; 8637cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 8647cf436c9SEd Tanous }; 8657cf436c9SEd Tanous 8662a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 8672a68dc80SEd Tanous { 8683648c8beSEd Tanous if (!query.skip && !query.top) 8693648c8beSEd Tanous { 8703648c8beSEd Tanous // No work to do. 8713648c8beSEd Tanous return; 8723648c8beSEd Tanous } 8732a68dc80SEd Tanous nlohmann::json::object_t* obj = 8742a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 8752a68dc80SEd Tanous if (obj == nullptr) 8762a68dc80SEd Tanous { 8772a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 8782a68dc80SEd Tanous messages::internalError(res); 8792a68dc80SEd Tanous return; 8802a68dc80SEd Tanous } 8812a68dc80SEd Tanous 88262598e31SEd Tanous BMCWEB_LOG_DEBUG("Handling top/skip"); 8832a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 8842a68dc80SEd Tanous if (members == obj->end()) 8852a68dc80SEd Tanous { 8862a68dc80SEd Tanous // From the Redfish specification 7.3.1 8872a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 8882a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 8892a68dc80SEd Tanous // for any supported query parameters that apply only to resource 8902a68dc80SEd Tanous // collections but are used on singular resources. 8912a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 8922a68dc80SEd Tanous return; 8932a68dc80SEd Tanous } 8942a68dc80SEd Tanous 8952a68dc80SEd Tanous nlohmann::json::array_t* arr = 8962a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 8972a68dc80SEd Tanous if (arr == nullptr) 8982a68dc80SEd Tanous { 8992a68dc80SEd Tanous messages::internalError(res); 9002a68dc80SEd Tanous return; 9012a68dc80SEd Tanous } 9022a68dc80SEd Tanous 9033648c8beSEd Tanous if (query.skip) 9043648c8beSEd Tanous { 9053648c8beSEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before 9063648c8beSEd Tanous // $top Can only skip as many values as we have 9073648c8beSEd Tanous size_t skip = std::min(arr->size(), *query.skip); 9082a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 9093648c8beSEd Tanous } 9103648c8beSEd Tanous if (query.top) 9113648c8beSEd Tanous { 9123648c8beSEd Tanous size_t top = std::min(arr->size(), *query.top); 9132a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 9142a68dc80SEd Tanous } 9153648c8beSEd Tanous } 9162a68dc80SEd Tanous 917827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 918827c4902SNan Zhou // not in the |currNode| Trie node. 919827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot, 920827c4902SNan Zhou const SelectTrieNode& currNode) 921e155ab54SNan Zhou { 922e155ab54SNan Zhou nlohmann::json::object_t* object = 923e155ab54SNan Zhou currRoot.get_ptr<nlohmann::json::object_t*>(); 924e155ab54SNan Zhou if (object != nullptr) 925e155ab54SNan Zhou { 92662598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an object"); 927e155ab54SNan Zhou auto it = currRoot.begin(); 928e155ab54SNan Zhou while (it != currRoot.end()) 929e155ab54SNan Zhou { 930e155ab54SNan Zhou auto nextIt = std::next(it); 93162598e31SEd Tanous BMCWEB_LOG_DEBUG("key={}", it.key()); 932827c4902SNan Zhou const SelectTrieNode* nextNode = currNode.find(it.key()); 9335c9fb2d6SNan Zhou // Per the Redfish spec section 7.3.3, the service shall select 9345c9fb2d6SNan Zhou // certain properties as if $select was omitted. This applies to 9355c9fb2d6SNan Zhou // every TrieNode that contains leaves and the root. 9365c9fb2d6SNan Zhou constexpr std::array<std::string_view, 5> reservedProperties = { 9375c9fb2d6SNan Zhou "@odata.id", "@odata.type", "@odata.context", "@odata.etag", 9385c9fb2d6SNan Zhou "error"}; 9393544d2a7SEd Tanous bool reserved = std::ranges::find(reservedProperties, it.key()) != 9403544d2a7SEd Tanous reservedProperties.end(); 9415c9fb2d6SNan Zhou if (reserved || (nextNode != nullptr && nextNode->isSelected())) 942e155ab54SNan Zhou { 943e155ab54SNan Zhou it = nextIt; 944e155ab54SNan Zhou continue; 945e155ab54SNan Zhou } 946827c4902SNan Zhou if (nextNode != nullptr) 947e155ab54SNan Zhou { 94862598e31SEd Tanous BMCWEB_LOG_DEBUG("Recursively select: {}", it.key()); 949827c4902SNan Zhou recursiveSelect(*it, *nextNode); 950e155ab54SNan Zhou it = nextIt; 951e155ab54SNan Zhou continue; 952e155ab54SNan Zhou } 95362598e31SEd Tanous BMCWEB_LOG_DEBUG("{} is getting removed!", it.key()); 954e155ab54SNan Zhou it = currRoot.erase(it); 955e155ab54SNan Zhou } 956e155ab54SNan Zhou } 9575c9fb2d6SNan Zhou nlohmann::json::array_t* array = 9585c9fb2d6SNan Zhou currRoot.get_ptr<nlohmann::json::array_t*>(); 9595c9fb2d6SNan Zhou if (array != nullptr) 9605c9fb2d6SNan Zhou { 96162598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an array"); 9625c9fb2d6SNan Zhou // Array index is omitted, so reuse the same Trie node 9635c9fb2d6SNan Zhou for (nlohmann::json& nextRoot : *array) 9645c9fb2d6SNan Zhou { 9655c9fb2d6SNan Zhou recursiveSelect(nextRoot, currNode); 9665c9fb2d6SNan Zhou } 9675c9fb2d6SNan Zhou } 968e155ab54SNan Zhou } 969e155ab54SNan Zhou 970e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to 971e155ab54SNan Zhou // ambiguity and/or complexity. 9725c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 973e155ab54SNan Zhou // created for clarification. 9745c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 975e155ab54SNan Zhou // etc. 976e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse, 977827c4902SNan Zhou const SelectTrieNode& trieRoot) 978e155ab54SNan Zhou { 97962598e31SEd Tanous BMCWEB_LOG_DEBUG("Process $select quary parameter"); 980827c4902SNan Zhou recursiveSelect(intermediateResponse.jsonValue, trieRoot); 981e155ab54SNan Zhou } 982e155ab54SNan Zhou 983504af5a0SPatrick Williams inline void processAllParams( 984504af5a0SPatrick Williams crow::App& app, const Query& query, const Query& delegated, 9857cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 9867cf436c9SEd Tanous crow::Response& intermediateResponse) 987f4c99e70SEd Tanous { 988f4c99e70SEd Tanous if (!completionHandler) 989f4c99e70SEd Tanous { 99062598e31SEd Tanous BMCWEB_LOG_DEBUG("Function was invalid?"); 991f4c99e70SEd Tanous return; 992f4c99e70SEd Tanous } 993f4c99e70SEd Tanous 99462598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing query params"); 995f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 996f4c99e70SEd Tanous // params. 997f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 998f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 999f4c99e70SEd Tanous { 1000f4c99e70SEd Tanous completionHandler(intermediateResponse); 1001f4c99e70SEd Tanous return; 1002f4c99e70SEd Tanous } 1003f4c99e70SEd Tanous if (query.isOnly) 1004f4c99e70SEd Tanous { 1005f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 1006f4c99e70SEd Tanous return; 1007f4c99e70SEd Tanous } 10082a68dc80SEd Tanous 10093648c8beSEd Tanous if (query.top || query.skip) 10102a68dc80SEd Tanous { 10112a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 10122a68dc80SEd Tanous } 10132a68dc80SEd Tanous 10147cf436c9SEd Tanous if (query.expandType != ExpandType::None) 10157cf436c9SEd Tanous { 101662598e31SEd Tanous BMCWEB_LOG_DEBUG("Executing expand query"); 101713548d85SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 101813548d85SEd Tanous std::move(intermediateResponse)); 10197cf436c9SEd Tanous 102013548d85SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 102113548d85SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 102232cdb4a7SWilly Tu multi->startQuery(query, delegated); 10237cf436c9SEd Tanous return; 10247cf436c9SEd Tanous } 1025e155ab54SNan Zhou 102625991f7dSEd Tanous if (query.filter) 102725991f7dSEd Tanous { 1028f80a87f2SEd Tanous applyFilterToCollection(intermediateResponse.jsonValue, *query.filter); 102925991f7dSEd Tanous } 103025991f7dSEd Tanous 1031e155ab54SNan Zhou // According to Redfish Spec Section 7.3.1, $select is the last parameter to 1032e155ab54SNan Zhou // to process 1033827c4902SNan Zhou if (!query.selectTrie.root.empty()) 1034e155ab54SNan Zhou { 1035827c4902SNan Zhou processSelect(intermediateResponse, query.selectTrie.root); 1036e155ab54SNan Zhou } 1037e155ab54SNan Zhou 1038f4c99e70SEd Tanous completionHandler(intermediateResponse); 1039f4c99e70SEd Tanous } 1040f4c99e70SEd Tanous 1041f4c99e70SEd Tanous } // namespace query_param 1042f4c99e70SEd Tanous } // namespace redfish 1043