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" 17c1a75ebcSrohitpai #include "redfishoemrule.hpp" 1850ebd4afSEd Tanous #include "str_utility.hpp" 19fdf51f5cSRohit PAI #include "sub_request.hpp" 20c1a75ebcSrohitpai #include "utils/json_utils.hpp" 21f4c99e70SEd Tanous 22d7857201SEd Tanous #include <unistd.h> 23d5c80ad9SNan Zhou 24d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp> 25d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp> 26d5c80ad9SNan Zhou #include <boost/url/params_view.hpp> 27d5c80ad9SNan Zhou #include <nlohmann/json.hpp> 28d5c80ad9SNan Zhou 29d5c80ad9SNan Zhou #include <algorithm> 30e155ab54SNan Zhou #include <array> 31e155ab54SNan Zhou #include <cctype> 327cf436c9SEd Tanous #include <charconv> 33d7857201SEd Tanous #include <cstddef> 34d5c80ad9SNan Zhou #include <cstdint> 35d5c80ad9SNan Zhou #include <functional> 36e155ab54SNan Zhou #include <iterator> 37d5c80ad9SNan Zhou #include <limits> 38d5c80ad9SNan Zhou #include <map> 39d5c80ad9SNan Zhou #include <memory> 40d5c80ad9SNan Zhou #include <optional> 413544d2a7SEd Tanous #include <ranges> 42f4c99e70SEd Tanous #include <string> 43f4c99e70SEd Tanous #include <string_view> 44d5c80ad9SNan Zhou #include <system_error> 457cf436c9SEd Tanous #include <utility> 46f4c99e70SEd Tanous #include <vector> 47f4c99e70SEd Tanous 48f4c99e70SEd Tanous namespace redfish 49f4c99e70SEd Tanous { 50f4c99e70SEd Tanous namespace query_param 51f4c99e70SEd Tanous { 52f4c99e70SEd Tanous 537cf436c9SEd Tanous enum class ExpandType : uint8_t 547cf436c9SEd Tanous { 557cf436c9SEd Tanous None, 567cf436c9SEd Tanous Links, 577cf436c9SEd Tanous NotLinks, 587cf436c9SEd Tanous Both, 597cf436c9SEd Tanous }; 607cf436c9SEd Tanous 61827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|. 62827c4902SNan Zhou class SelectTrieNode 63827c4902SNan Zhou { 64827c4902SNan Zhou public: 65827c4902SNan Zhou SelectTrieNode() = default; 66827c4902SNan Zhou 67827c4902SNan Zhou const SelectTrieNode* find(const std::string& jsonKey) const 68827c4902SNan Zhou { 69827c4902SNan Zhou auto it = children.find(jsonKey); 70827c4902SNan Zhou if (it == children.end()) 71827c4902SNan Zhou { 72827c4902SNan Zhou return nullptr; 73827c4902SNan Zhou } 74827c4902SNan Zhou return &it->second; 75827c4902SNan Zhou } 76827c4902SNan Zhou 77827c4902SNan Zhou // Creates a new node if the key doesn't exist, returns the reference to the 78827c4902SNan Zhou // newly created node; otherwise, return the reference to the existing node 79827c4902SNan Zhou SelectTrieNode* emplace(std::string_view jsonKey) 80827c4902SNan Zhou { 81827c4902SNan Zhou auto [it, _] = children.emplace(jsonKey, SelectTrieNode{}); 82827c4902SNan Zhou return &it->second; 83827c4902SNan Zhou } 84827c4902SNan Zhou 85827c4902SNan Zhou bool empty() const 86827c4902SNan Zhou { 87827c4902SNan Zhou return children.empty(); 88827c4902SNan Zhou } 89827c4902SNan Zhou 90827c4902SNan Zhou void clear() 91827c4902SNan Zhou { 92827c4902SNan Zhou children.clear(); 93827c4902SNan Zhou } 94827c4902SNan Zhou 95827c4902SNan Zhou void setToSelected() 96827c4902SNan Zhou { 97827c4902SNan Zhou selected = true; 98827c4902SNan Zhou } 99827c4902SNan Zhou 100827c4902SNan Zhou bool isSelected() const 101827c4902SNan Zhou { 102827c4902SNan Zhou return selected; 103827c4902SNan Zhou } 104827c4902SNan Zhou 105827c4902SNan Zhou private: 106827c4902SNan Zhou std::map<std::string, SelectTrieNode, std::less<>> children; 107827c4902SNan Zhou bool selected = false; 108827c4902SNan Zhou }; 109827c4902SNan Zhou 110827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among 111827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties) 112827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property) 113827c4902SNan Zhou { 114827c4902SNan Zhou // These a magic number, but with it it's less likely that this code 115827c4902SNan Zhou // introduces CVE; e.g., too large properties crash the service. 116827c4902SNan Zhou constexpr int maxPropertyLength = 60; 117827c4902SNan Zhou if (property.empty() || property.size() > maxPropertyLength) 118827c4902SNan Zhou { 119827c4902SNan Zhou return false; 120827c4902SNan Zhou } 121827c4902SNan Zhou for (char ch : property) 122827c4902SNan Zhou { 123827c4902SNan Zhou if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' && 124827c4902SNan Zhou ch != '@' && ch != '.') 125827c4902SNan Zhou { 126827c4902SNan Zhou return false; 127827c4902SNan Zhou } 128827c4902SNan Zhou } 129827c4902SNan Zhou return true; 130827c4902SNan Zhou } 131827c4902SNan Zhou 132827c4902SNan Zhou struct SelectTrie 133827c4902SNan Zhou { 134827c4902SNan Zhou SelectTrie() = default; 135827c4902SNan Zhou 136827c4902SNan Zhou // Inserts a $select value; returns false if the nestedProperty is illegal. 137827c4902SNan Zhou bool insertNode(std::string_view nestedProperty) 138827c4902SNan Zhou { 139827c4902SNan Zhou if (nestedProperty.empty()) 140827c4902SNan Zhou { 141827c4902SNan Zhou return false; 142827c4902SNan Zhou } 143827c4902SNan Zhou SelectTrieNode* currNode = &root; 144827c4902SNan Zhou size_t index = nestedProperty.find_first_of('/'); 145827c4902SNan Zhou while (!nestedProperty.empty()) 146827c4902SNan Zhou { 147827c4902SNan Zhou std::string_view property = nestedProperty.substr(0, index); 148827c4902SNan Zhou if (!isSelectedPropertyAllowed(property)) 149827c4902SNan Zhou { 150827c4902SNan Zhou return false; 151827c4902SNan Zhou } 152827c4902SNan Zhou currNode = currNode->emplace(property); 153827c4902SNan Zhou if (index == std::string::npos) 154827c4902SNan Zhou { 155827c4902SNan Zhou break; 156827c4902SNan Zhou } 157827c4902SNan Zhou nestedProperty.remove_prefix(index + 1); 158827c4902SNan Zhou index = nestedProperty.find_first_of('/'); 159827c4902SNan Zhou } 160827c4902SNan Zhou currNode->setToSelected(); 161827c4902SNan Zhou return true; 162827c4902SNan Zhou } 163827c4902SNan Zhou 164827c4902SNan Zhou SelectTrieNode root; 165827c4902SNan Zhou }; 166827c4902SNan Zhou 167a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 168f4c99e70SEd Tanous struct Query 169f4c99e70SEd Tanous { 170a6b9125fSNan Zhou // Only 171f4c99e70SEd Tanous bool isOnly = false; 17225991f7dSEd Tanous 173a6b9125fSNan Zhou // Expand 174a6b9125fSNan Zhou uint8_t expandLevel = 0; 1757cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 176c937d2bfSEd Tanous 177c937d2bfSEd Tanous // Skip 1783648c8beSEd Tanous std::optional<size_t> skip = std::nullopt; 179c937d2bfSEd Tanous 180c937d2bfSEd Tanous // Top 1815143f7a5SJiaqing Zhao static constexpr size_t maxTop = 1000; // Max entries a response contain 1823648c8beSEd Tanous std::optional<size_t> top = std::nullopt; 183e155ab54SNan Zhou 18425991f7dSEd Tanous // Filter 18525991f7dSEd Tanous std::optional<filter_ast::LogicalAnd> filter = std::nullopt; 18625991f7dSEd Tanous 187e155ab54SNan Zhou // Select 18847f2934cSEd Tanous // Unclear how to make this use structured initialization without this. 18947f2934cSEd Tanous // Might be a tidy bug? Ignore for now 19047f2934cSEd Tanous // NOLINTNEXTLINE(readability-redundant-member-init) 19147f2934cSEd Tanous SelectTrie selectTrie{}; 192f4c99e70SEd Tanous }; 193f4c99e70SEd Tanous 194a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 195a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 196a6b9125fSNan Zhou // the processing. 197a6b9125fSNan Zhou struct QueryCapabilities 198a6b9125fSNan Zhou { 199a6b9125fSNan Zhou bool canDelegateOnly = false; 200c937d2bfSEd Tanous bool canDelegateTop = false; 201c937d2bfSEd Tanous bool canDelegateSkip = false; 202a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 203e155ab54SNan Zhou bool canDelegateSelect = false; 204a6b9125fSNan Zhou }; 205a6b9125fSNan Zhou 206a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 207a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 208a6b9125fSNan Zhou // function will take care of it. 209a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 210a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 211a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 212a6b9125fSNan Zhou { 213f1a1e3dcSEd Tanous Query delegated{}; 214a6b9125fSNan Zhou // delegate only 215a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 216a6b9125fSNan Zhou { 217a6b9125fSNan Zhou delegated.isOnly = true; 218a6b9125fSNan Zhou query.isOnly = false; 219a6b9125fSNan Zhou } 220a6b9125fSNan Zhou // delegate expand as much as we can 221a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 222a6b9125fSNan Zhou { 223a6b9125fSNan Zhou delegated.expandType = query.expandType; 224a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 225a6b9125fSNan Zhou { 226a6b9125fSNan Zhou query.expandType = ExpandType::None; 227a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 228a6b9125fSNan Zhou query.expandLevel = 0; 229a6b9125fSNan Zhou } 230a6b9125fSNan Zhou else 231a6b9125fSNan Zhou { 232a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 233a6b9125fSNan Zhou } 234a6b9125fSNan Zhou } 235c937d2bfSEd Tanous 236c937d2bfSEd Tanous // delegate top 2373648c8beSEd Tanous if (query.top && queryCapabilities.canDelegateTop) 238c937d2bfSEd Tanous { 239c937d2bfSEd Tanous delegated.top = query.top; 2403648c8beSEd Tanous query.top = std::nullopt; 241c937d2bfSEd Tanous } 242c937d2bfSEd Tanous 243c937d2bfSEd Tanous // delegate skip 2443648c8beSEd Tanous if (query.skip && queryCapabilities.canDelegateSkip) 245c937d2bfSEd Tanous { 246c937d2bfSEd Tanous delegated.skip = query.skip; 247c937d2bfSEd Tanous query.skip = 0; 248c937d2bfSEd Tanous } 249e155ab54SNan Zhou 250e155ab54SNan Zhou // delegate select 251827c4902SNan Zhou if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect) 252e155ab54SNan Zhou { 253827c4902SNan Zhou delegated.selectTrie = std::move(query.selectTrie); 254827c4902SNan Zhou query.selectTrie.root.clear(); 255e155ab54SNan Zhou } 256a6b9125fSNan Zhou return delegated; 257a6b9125fSNan Zhou } 258a6b9125fSNan Zhou 2597cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 2607cf436c9SEd Tanous { 2617cf436c9SEd Tanous if (value.empty()) 2627cf436c9SEd Tanous { 2637cf436c9SEd Tanous return false; 2647cf436c9SEd Tanous } 2657cf436c9SEd Tanous switch (value[0]) 2667cf436c9SEd Tanous { 2677cf436c9SEd Tanous case '*': 2687cf436c9SEd Tanous query.expandType = ExpandType::Both; 2697cf436c9SEd Tanous break; 2707cf436c9SEd Tanous case '.': 2717cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 2727cf436c9SEd Tanous break; 2737cf436c9SEd Tanous case '~': 2747cf436c9SEd Tanous query.expandType = ExpandType::Links; 2757cf436c9SEd Tanous break; 2767cf436c9SEd Tanous default: 2777cf436c9SEd Tanous return false; 2787cf436c9SEd Tanous } 2797cf436c9SEd Tanous value.remove_prefix(1); 2807cf436c9SEd Tanous if (value.empty()) 2817cf436c9SEd Tanous { 2827cf436c9SEd Tanous query.expandLevel = 1; 2837cf436c9SEd Tanous return true; 2847cf436c9SEd Tanous } 2857cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 2867cf436c9SEd Tanous if (!value.starts_with(levels)) 2877cf436c9SEd Tanous { 2887cf436c9SEd Tanous return false; 2897cf436c9SEd Tanous } 2907cf436c9SEd Tanous value.remove_prefix(levels.size()); 2917cf436c9SEd Tanous 2922bd4ab43SPatrick Williams auto it = std::from_chars(value.begin(), value.end(), query.expandLevel); 2937cf436c9SEd Tanous if (it.ec != std::errc()) 2947cf436c9SEd Tanous { 2957cf436c9SEd Tanous return false; 2967cf436c9SEd Tanous } 2972bd4ab43SPatrick Williams value.remove_prefix( 2982bd4ab43SPatrick Williams static_cast<size_t>(std::distance(value.begin(), it.ptr))); 2997cf436c9SEd Tanous return value == ")"; 3007cf436c9SEd Tanous } 3017cf436c9SEd Tanous 302c937d2bfSEd Tanous enum class QueryError 303c937d2bfSEd Tanous { 304c937d2bfSEd Tanous Ok, 305c937d2bfSEd Tanous OutOfRange, 306c937d2bfSEd Tanous ValueFormat, 307c937d2bfSEd Tanous }; 308c937d2bfSEd Tanous 309c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 310c937d2bfSEd Tanous { 311bd79bce8SPatrick Williams std::from_chars_result r = 312bd79bce8SPatrick Williams std::from_chars(value.begin(), value.end(), param); 313c937d2bfSEd Tanous 314c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 315c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 316c937d2bfSEd Tanous { 317c937d2bfSEd Tanous return QueryError::OutOfRange; 318c937d2bfSEd Tanous } 319c937d2bfSEd Tanous // All other errors are value format 320c937d2bfSEd Tanous if (r.ec != std::errc()) 321c937d2bfSEd Tanous { 322c937d2bfSEd Tanous return QueryError::ValueFormat; 323c937d2bfSEd Tanous } 324c937d2bfSEd Tanous return QueryError::Ok; 325c937d2bfSEd Tanous } 326c937d2bfSEd Tanous 327c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 328c937d2bfSEd Tanous { 3293648c8beSEd Tanous return getNumericParam(value, query.skip.emplace()); 330c937d2bfSEd Tanous } 331c937d2bfSEd Tanous 332c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 333c937d2bfSEd Tanous { 3343648c8beSEd Tanous QueryError ret = getNumericParam(value, query.top.emplace()); 335c937d2bfSEd Tanous if (ret != QueryError::Ok) 336c937d2bfSEd Tanous { 337c937d2bfSEd Tanous return ret; 338c937d2bfSEd Tanous } 339c937d2bfSEd Tanous 340c937d2bfSEd Tanous // Range check for sanity. 3415143f7a5SJiaqing Zhao if (query.top > Query::maxTop) 342c937d2bfSEd Tanous { 343c937d2bfSEd Tanous return QueryError::OutOfRange; 344c937d2bfSEd Tanous } 345c937d2bfSEd Tanous 346c937d2bfSEd Tanous return QueryError::Ok; 347c937d2bfSEd Tanous } 348c937d2bfSEd Tanous 349e155ab54SNan Zhou // Parses and validates the $select parameter. 350e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be 351e155ab54SNan Zhou // comma separated Resource Path 352e155ab54SNan Zhou // Ref: 353e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 354e155ab54SNan Zhou // 2. 355e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 356e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query) 357e155ab54SNan Zhou { 358e155ab54SNan Zhou std::vector<std::string> properties; 35950ebd4afSEd Tanous bmcweb::split(properties, value, ','); 360e155ab54SNan Zhou if (properties.empty()) 361e155ab54SNan Zhou { 362e155ab54SNan Zhou return false; 363e155ab54SNan Zhou } 364e155ab54SNan Zhou // These a magic number, but with it it's less likely that this code 365e155ab54SNan Zhou // introduces CVE; e.g., too large properties crash the service. 366e155ab54SNan Zhou constexpr int maxNumProperties = 10; 367e155ab54SNan Zhou if (properties.size() > maxNumProperties) 368e155ab54SNan Zhou { 369e155ab54SNan Zhou return false; 370e155ab54SNan Zhou } 371827c4902SNan Zhou for (const auto& property : properties) 372e155ab54SNan Zhou { 373827c4902SNan Zhou if (!query.selectTrie.insertNode(property)) 374e155ab54SNan Zhou { 375e155ab54SNan Zhou return false; 376e155ab54SNan Zhou } 377e155ab54SNan Zhou } 378e155ab54SNan Zhou return true; 379e155ab54SNan Zhou } 380e155ab54SNan Zhou 38125991f7dSEd Tanous // Parses and validates the $filter parameter. 38225991f7dSEd Tanous inline bool getFilterParam(std::string_view value, Query& query) 38325991f7dSEd Tanous { 38425991f7dSEd Tanous query.filter = parseFilter(value); 38525991f7dSEd Tanous return query.filter.has_value(); 38625991f7dSEd Tanous } 38725991f7dSEd Tanous 388504af5a0SPatrick Williams inline std::optional<Query> parseParameters(boost::urls::params_view urlParams, 389504af5a0SPatrick Williams crow::Response& res) 390f4c99e70SEd Tanous { 391f1a1e3dcSEd Tanous Query ret{}; 392f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 393f4c99e70SEd Tanous { 394079360aeSEd Tanous if (it.key == "only") 395f4c99e70SEd Tanous { 396f4c99e70SEd Tanous if (!it.value.empty()) 397f4c99e70SEd Tanous { 398079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 399f4c99e70SEd Tanous return std::nullopt; 400f4c99e70SEd Tanous } 401f4c99e70SEd Tanous ret.isOnly = true; 402f4c99e70SEd Tanous } 40325b54dbaSEd Tanous else if (it.key == "$expand" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 4047cf436c9SEd Tanous { 405079360aeSEd Tanous if (!getExpandType(it.value, ret)) 4067cf436c9SEd Tanous { 407079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4087cf436c9SEd Tanous return std::nullopt; 409f4c99e70SEd Tanous } 4107cf436c9SEd Tanous } 411079360aeSEd Tanous else if (it.key == "$top") 412c937d2bfSEd Tanous { 413079360aeSEd Tanous QueryError topRet = getTopParam(it.value, ret); 414c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 415c937d2bfSEd Tanous { 416079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 417c937d2bfSEd Tanous return std::nullopt; 418c937d2bfSEd Tanous } 419c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 420c937d2bfSEd Tanous { 421c937d2bfSEd Tanous messages::queryParameterOutOfRange( 422079360aeSEd Tanous res, it.value, "$top", 423079360aeSEd Tanous "0-" + std::to_string(Query::maxTop)); 424c937d2bfSEd Tanous return std::nullopt; 425c937d2bfSEd Tanous } 426c937d2bfSEd Tanous } 427079360aeSEd Tanous else if (it.key == "$skip") 428c937d2bfSEd Tanous { 429079360aeSEd Tanous QueryError topRet = getSkipParam(it.value, ret); 430c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 431c937d2bfSEd Tanous { 432079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 433c937d2bfSEd Tanous return std::nullopt; 434c937d2bfSEd Tanous } 435c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 436c937d2bfSEd Tanous { 437c937d2bfSEd Tanous messages::queryParameterOutOfRange( 438079360aeSEd Tanous res, it.value, it.key, 439a926c53eSJiaqing Zhao "0-" + std::to_string(std::numeric_limits<size_t>::max())); 440c937d2bfSEd Tanous return std::nullopt; 441c937d2bfSEd Tanous } 442c937d2bfSEd Tanous } 443079360aeSEd Tanous else if (it.key == "$select") 444e155ab54SNan Zhou { 445079360aeSEd Tanous if (!getSelectParam(it.value, ret)) 446e155ab54SNan Zhou { 447079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 448e155ab54SNan Zhou return std::nullopt; 449e155ab54SNan Zhou } 450e155ab54SNan Zhou } 45125991f7dSEd Tanous else if (it.key == "$filter" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 45225991f7dSEd Tanous { 45325991f7dSEd Tanous if (!getFilterParam(it.value, ret)) 45425991f7dSEd Tanous { 45525991f7dSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 45625991f7dSEd Tanous return std::nullopt; 45725991f7dSEd Tanous } 45825991f7dSEd Tanous } 4597cf436c9SEd Tanous else 4607cf436c9SEd Tanous { 4617cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 462079360aeSEd Tanous if (it.key.starts_with("$")) 4637cf436c9SEd Tanous { 4647cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 4657cf436c9SEd Tanous // status code for any unsupported query parameters that 4667cf436c9SEd Tanous // start with $ . 467079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4687cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 4697cf436c9SEd Tanous return std::nullopt; 4707cf436c9SEd Tanous } 4717cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 4727cf436c9SEd Tanous // not begin with $ ." 4737cf436c9SEd Tanous } 4747cf436c9SEd Tanous } 4757cf436c9SEd Tanous 476827c4902SNan Zhou if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 477e155ab54SNan Zhou { 478e155ab54SNan Zhou messages::queryCombinationInvalid(res); 479e155ab54SNan Zhou return std::nullopt; 480e155ab54SNan Zhou } 481e155ab54SNan Zhou 482f4c99e70SEd Tanous return ret; 483f4c99e70SEd Tanous } 484f4c99e70SEd Tanous 485f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 486f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 487f4c99e70SEd Tanous { 48862598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing only query param"); 489f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 490f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 491f4c99e70SEd Tanous { 492f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 493f4c99e70SEd Tanous completionHandler(res); 494f4c99e70SEd Tanous return false; 495f4c99e70SEd Tanous } 496f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 497f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 498f4c99e70SEd Tanous { 49962598e31SEd Tanous BMCWEB_LOG_DEBUG( 50062598e31SEd Tanous "Members contains {} element, returning full collection.", 50162598e31SEd Tanous itMembers->size()); 502f4c99e70SEd Tanous completionHandler(res); 503f4c99e70SEd Tanous return false; 504f4c99e70SEd Tanous } 505f4c99e70SEd Tanous 506f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 507f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 508f4c99e70SEd Tanous { 50962598e31SEd Tanous BMCWEB_LOG_DEBUG("No found odata.id"); 510f4c99e70SEd Tanous messages::internalError(res); 511f4c99e70SEd Tanous completionHandler(res); 512f4c99e70SEd Tanous return false; 513f4c99e70SEd Tanous } 514f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 515f4c99e70SEd Tanous if (url == nullptr) 516f4c99e70SEd Tanous { 51762598e31SEd Tanous BMCWEB_LOG_DEBUG("@odata.id wasn't a string????"); 518f4c99e70SEd Tanous messages::internalError(res); 519f4c99e70SEd Tanous completionHandler(res); 520f4c99e70SEd Tanous return false; 521f4c99e70SEd Tanous } 522f4c99e70SEd Tanous // TODO(Ed) copy request headers? 523f4c99e70SEd Tanous // newReq.session = req.session; 524f4c99e70SEd Tanous std::error_code ec; 525102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 526102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, *url, 11}, ec); 527f4c99e70SEd Tanous if (ec) 528f4c99e70SEd Tanous { 529f4c99e70SEd Tanous messages::internalError(res); 530f4c99e70SEd Tanous completionHandler(res); 531f4c99e70SEd Tanous return false; 532f4c99e70SEd Tanous } 533f4c99e70SEd Tanous 534f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 53562598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 53662598e31SEd Tanous logPtr(&asyncResp->res)); 537f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 538f4c99e70SEd Tanous app.handle(newReq, asyncResp); 539f4c99e70SEd Tanous return true; 540f4c99e70SEd Tanous } 541f4c99e70SEd Tanous 5427cf436c9SEd Tanous struct ExpandNode 5437cf436c9SEd Tanous { 5447cf436c9SEd Tanous nlohmann::json::json_pointer location; 5457cf436c9SEd Tanous std::string uri; 5467cf436c9SEd Tanous 5479de65b34SEd Tanous bool operator==(const ExpandNode& other) const 5487cf436c9SEd Tanous { 5497cf436c9SEd Tanous return location == other.location && uri == other.uri; 5507cf436c9SEd Tanous } 5517cf436c9SEd Tanous }; 5527cf436c9SEd Tanous 55387788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 554c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 55537b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 55687788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 55787788abfSEd Tanous 55887788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 559c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 56037b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 56187788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 56287788abfSEd Tanous 5637cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 5647cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 5657cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 5667cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 5677cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 5687cf436c9SEd Tanous // with the keys from the jsonResponse object 5697cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 5707cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 57137b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 57232cdb4a7SWilly Tu bool inLinks, std::vector<ExpandNode>& out) 5737cf436c9SEd Tanous { 5747cf436c9SEd Tanous // If no expand is needed, return early 5757cf436c9SEd Tanous if (eType == ExpandType::None) 5767cf436c9SEd Tanous { 5777cf436c9SEd Tanous return; 5787cf436c9SEd Tanous } 579ad595fa6SEd Tanous 5807cf436c9SEd Tanous nlohmann::json::array_t* array = 5817cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 5827cf436c9SEd Tanous if (array != nullptr) 5837cf436c9SEd Tanous { 58437b1f7beSEd Tanous findNavigationReferencesInArrayRecursive(eType, *array, jsonPtr, depth, 58587788abfSEd Tanous skipDepth, inLinks, out); 58687788abfSEd Tanous } 58787788abfSEd Tanous nlohmann::json::object_t* obj = 58887788abfSEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 58987788abfSEd Tanous if (obj == nullptr) 59087788abfSEd Tanous { 59187788abfSEd Tanous return; 59287788abfSEd Tanous } 59337b1f7beSEd Tanous findNavigationReferencesInObjectRecursive(eType, *obj, jsonPtr, depth, 59437b1f7beSEd Tanous skipDepth, inLinks, out); 59587788abfSEd Tanous } 59687788abfSEd Tanous 59787788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 598c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 59937b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 60087788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 60187788abfSEd Tanous { 6027cf436c9SEd Tanous size_t index = 0; 6037cf436c9SEd Tanous // For arrays, walk every element in the array 604c59e338cSEd Tanous for (auto& element : array) 6057cf436c9SEd Tanous { 60637b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / index; 60762598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr.to_string()); 608ad595fa6SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, depth, 60932cdb4a7SWilly Tu skipDepth, inLinks, out); 6107cf436c9SEd Tanous index++; 6117cf436c9SEd Tanous } 6127cf436c9SEd Tanous } 61387788abfSEd Tanous 61487788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 615c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 61637b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 61787788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 6187cf436c9SEd Tanous { 6197cf436c9SEd Tanous // Navigation References only ever have a single element 620c59e338cSEd Tanous if (obj.size() == 1) 6217cf436c9SEd Tanous { 622c59e338cSEd Tanous if (obj.begin()->first == "@odata.id") 6237cf436c9SEd Tanous { 6247cf436c9SEd Tanous const std::string* uri = 625c59e338cSEd Tanous obj.begin()->second.get_ptr<const std::string*>(); 6267cf436c9SEd Tanous if (uri != nullptr) 6277cf436c9SEd Tanous { 62862598e31SEd Tanous BMCWEB_LOG_DEBUG("Found {} at {}", *uri, jsonPtr.to_string()); 62932cdb4a7SWilly Tu if (skipDepth == 0) 63032cdb4a7SWilly Tu { 63137b1f7beSEd Tanous out.push_back({jsonPtr, *uri}); 63232cdb4a7SWilly Tu } 633ad595fa6SEd Tanous return; 6347cf436c9SEd Tanous } 6357cf436c9SEd Tanous } 6367cf436c9SEd Tanous } 637ad595fa6SEd Tanous 638ad595fa6SEd Tanous int newDepth = depth; 639c59e338cSEd Tanous auto odataId = obj.find("@odata.id"); 640c59e338cSEd Tanous if (odataId != obj.end()) 641ad595fa6SEd Tanous { 642ad595fa6SEd Tanous // The Redfish spec requires all resources to include the resource 643ad595fa6SEd Tanous // identifier. If the object has multiple elements and one of them is 644ad595fa6SEd Tanous // "@odata.id" then that means we have entered a new level / expanded 645ad595fa6SEd Tanous // resource. We need to stop traversing if we're already at the desired 646ad595fa6SEd Tanous // depth 647c59e338cSEd Tanous if (obj.size() > 1) 64832cdb4a7SWilly Tu { 64932cdb4a7SWilly Tu if (depth == 0) 650ad595fa6SEd Tanous { 651ad595fa6SEd Tanous return; 652ad595fa6SEd Tanous } 65332cdb4a7SWilly Tu if (skipDepth > 0) 65432cdb4a7SWilly Tu { 65532cdb4a7SWilly Tu skipDepth--; 65632cdb4a7SWilly Tu } 65732cdb4a7SWilly Tu } 65832cdb4a7SWilly Tu 65932cdb4a7SWilly Tu if (skipDepth == 0) 66032cdb4a7SWilly Tu { 661ad595fa6SEd Tanous newDepth--; 662ad595fa6SEd Tanous } 66332cdb4a7SWilly Tu } 664ad595fa6SEd Tanous 6657cf436c9SEd Tanous // Loop the object and look for links 666c59e338cSEd Tanous for (auto& element : obj) 6677cf436c9SEd Tanous { 668e479ad58SNan Zhou bool localInLinks = inLinks; 669e479ad58SNan Zhou if (!localInLinks) 6707cf436c9SEd Tanous { 6717cf436c9SEd Tanous // Check if this is a links node 672e479ad58SNan Zhou localInLinks = element.first == "Links"; 6737cf436c9SEd Tanous } 6747cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 6757cf436c9SEd Tanous // Per section 7.3 of the redfish specification 676e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 6777cf436c9SEd Tanous { 6787cf436c9SEd Tanous continue; 6797cf436c9SEd Tanous } 680e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 6817cf436c9SEd Tanous { 6827cf436c9SEd Tanous continue; 6837cf436c9SEd Tanous } 68437b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / element.first; 68562598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr); 6867cf436c9SEd Tanous 6877cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 68832cdb4a7SWilly Tu newDepth, skipDepth, localInLinks, 68932cdb4a7SWilly Tu out); 6907cf436c9SEd Tanous } 6917cf436c9SEd Tanous } 6927cf436c9SEd Tanous 693ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded 694ad595fa6SEd Tanous // response we may need need additional handling when the original URI was 695ad595fa6SEd Tanous // up tree from a top level collection. 696ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556 697ad595fa6SEd Tanous // lands. May want to avoid forwarding query params when request is uptree from 698ad595fa6SEd Tanous // a top level collection. 699bd79bce8SPatrick Williams inline std::vector<ExpandNode> findNavigationReferences( 700bd79bce8SPatrick Williams ExpandType eType, int depth, int skipDepth, nlohmann::json& jsonResponse) 7017cf436c9SEd Tanous { 7027cf436c9SEd Tanous std::vector<ExpandNode> ret; 70372c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 70432cdb4a7SWilly Tu // SkipDepth +1 since we are skipping the root by default. 70532cdb4a7SWilly Tu findNavigationReferencesRecursive(eType, jsonResponse, root, depth, 70632cdb4a7SWilly Tu skipDepth + 1, false, ret); 7077cf436c9SEd Tanous return ret; 7087cf436c9SEd Tanous } 7097cf436c9SEd Tanous 71072c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 711b66cf2a2SNan Zhou // Returns std::nullopt on failures. 71272c3ae33SNan Zhou // This function shall handle $select when it is added. 7138ece0e45SEd Tanous // There is no need to handle parameters that's not compatible with $expand, 71472c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 715b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query) 71672c3ae33SNan Zhou { 71772c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 71872c3ae33SNan Zhou if (query.expandLevel <= 1) 71972c3ae33SNan Zhou { 720b66cf2a2SNan Zhou return ""; 72172c3ae33SNan Zhou } 72272c3ae33SNan Zhou std::string str = "?$expand="; 72372c3ae33SNan Zhou switch (query.expandType) 72472c3ae33SNan Zhou { 72572c3ae33SNan Zhou case ExpandType::Links: 72672c3ae33SNan Zhou str += '~'; 72772c3ae33SNan Zhou break; 72872c3ae33SNan Zhou case ExpandType::NotLinks: 72972c3ae33SNan Zhou str += '.'; 73072c3ae33SNan Zhou break; 73172c3ae33SNan Zhou case ExpandType::Both: 73272c3ae33SNan Zhou str += '*'; 73372c3ae33SNan Zhou break; 734f1a1e3dcSEd Tanous case ExpandType::None: 735f1a1e3dcSEd Tanous return ""; 7364da0490bSEd Tanous default: 7374da0490bSEd Tanous return std::nullopt; 738b66cf2a2SNan Zhou } 73972c3ae33SNan Zhou str += "($levels="; 74072c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 74172c3ae33SNan Zhou str += ')'; 74272c3ae33SNan Zhou return str; 74372c3ae33SNan Zhou } 74472c3ae33SNan Zhou 7457cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 7467cf436c9SEd Tanous { 7477cf436c9SEd Tanous public: 7487cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 7497cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 7507cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 7517cf436c9SEd Tanous // class manages the final "merge" of the json resources. 7528a592810SEd Tanous MultiAsyncResp(crow::App& appIn, 7537cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 754c1a75ebcSrohitpai app(&appIn), finalRes(std::move(finalResIn)) 755c1a75ebcSrohitpai {} 756c1a75ebcSrohitpai 757c1a75ebcSrohitpai explicit MultiAsyncResp(std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 758c1a75ebcSrohitpai app(nullptr), finalRes(std::move(finalResIn)) 7597cf436c9SEd Tanous {} 7607cf436c9SEd Tanous 7617cf436c9SEd Tanous void addAwaitingResponse( 76202cad96eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& res, 7637cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 7647cf436c9SEd Tanous { 7657cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 76672c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 7677cf436c9SEd Tanous } 7687cf436c9SEd Tanous 76972c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 7707cf436c9SEd Tanous crow::Response& res) 7717cf436c9SEd Tanous { 77262598e31SEd Tanous BMCWEB_LOG_DEBUG("placeResult for {}", locationToPlace); 7733590bd1dSNan Zhou propogateError(finalRes->res, res); 774d0a341b9SEd Tanous nlohmann::json::object_t* obj = 775d0a341b9SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 776d0a341b9SEd Tanous if (obj == nullptr || res.jsonValue.empty()) 7773590bd1dSNan Zhou { 7783590bd1dSNan Zhou return; 7793590bd1dSNan Zhou } 7807cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 781d0a341b9SEd Tanous finalObj = std::move(*obj); 7827cf436c9SEd Tanous } 7837cf436c9SEd Tanous 78472c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 78572c3ae33SNan Zhou // for deeper levels. 786*19fab295SChandramohan Harkude void startQuery(const Query& query, const Query& delegated, 787*19fab295SChandramohan Harkude const crow::Request& req) 78872c3ae33SNan Zhou { 789ad595fa6SEd Tanous std::vector<ExpandNode> nodes = findNavigationReferences( 79032cdb4a7SWilly Tu query.expandType, query.expandLevel, delegated.expandLevel, 79132cdb4a7SWilly Tu finalRes->res.jsonValue); 79262598e31SEd Tanous BMCWEB_LOG_DEBUG("{} nodes to traverse", nodes.size()); 793b66cf2a2SNan Zhou const std::optional<std::string> queryStr = formatQueryForExpand(query); 794b66cf2a2SNan Zhou if (!queryStr) 795b66cf2a2SNan Zhou { 796b66cf2a2SNan Zhou messages::internalError(finalRes->res); 797b66cf2a2SNan Zhou return; 798b66cf2a2SNan Zhou } 7997cf436c9SEd Tanous for (const ExpandNode& node : nodes) 8007cf436c9SEd Tanous { 801b66cf2a2SNan Zhou const std::string subQuery = node.uri + *queryStr; 80262598e31SEd Tanous BMCWEB_LOG_DEBUG("URL of subquery: {}", subQuery); 8037cf436c9SEd Tanous std::error_code ec; 804102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 805102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, subQuery, 806102a4cdaSJonathan Doman 11}, 8077cf436c9SEd Tanous ec); 8087cf436c9SEd Tanous if (ec) 8097cf436c9SEd Tanous { 81072c3ae33SNan Zhou messages::internalError(finalRes->res); 8117cf436c9SEd Tanous return; 8127cf436c9SEd Tanous } 8137cf436c9SEd Tanous 814*19fab295SChandramohan Harkude if (req.session == nullptr) 815*19fab295SChandramohan Harkude { 816*19fab295SChandramohan Harkude BMCWEB_LOG_ERROR("Session is null"); 817*19fab295SChandramohan Harkude messages::internalError(finalRes->res); 818*19fab295SChandramohan Harkude return; 819*19fab295SChandramohan Harkude } 820*19fab295SChandramohan Harkude // Share the session from the original request 821*19fab295SChandramohan Harkude newReq->session = req.session; 822*19fab295SChandramohan Harkude 8237cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 82462598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 82562598e31SEd Tanous logPtr(&asyncResp->res)); 82672c3ae33SNan Zhou 82772c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 828c1a75ebcSrohitpai if (app != nullptr) 829c1a75ebcSrohitpai { 830c1a75ebcSrohitpai app->handle(newReq, asyncResp); 831c1a75ebcSrohitpai } 832c1a75ebcSrohitpai } 833c1a75ebcSrohitpai } 834c1a75ebcSrohitpai 835c1a75ebcSrohitpai static void startMultiFragmentHandle( 836fdf51f5cSRohit PAI const std::shared_ptr<redfish::SubRequest>& req, 837c1a75ebcSrohitpai const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 838c1a75ebcSrohitpai const std::shared_ptr<std::vector<OemBaseRule*>>& fragments, 839c1a75ebcSrohitpai const std::shared_ptr<std::vector<std::string>>& params, 840c1a75ebcSrohitpai const crow::Response& resIn) 841c1a75ebcSrohitpai { 842c1a75ebcSrohitpai asyncResp->res.jsonValue = resIn.jsonValue; 843c1a75ebcSrohitpai auto multi = std::make_shared<MultiAsyncResp>(asyncResp); 844c1a75ebcSrohitpai for (OemBaseRule* fragment : *fragments) 845c1a75ebcSrohitpai { 846c1a75ebcSrohitpai if (fragment != nullptr) 847c1a75ebcSrohitpai { 848c1a75ebcSrohitpai OemBaseRule& fragmentRule = *fragment; 849c1a75ebcSrohitpai auto rsp = std::make_shared<bmcweb::AsyncResp>(); 850fdf51f5cSRohit PAI BMCWEB_LOG_DEBUG("Matched fragment rule '{}' method '{}'", 851fdf51f5cSRohit PAI fragmentRule.rule, 852fdf51f5cSRohit PAI boost::beast::http::to_string(req->method())); 853c1a75ebcSrohitpai BMCWEB_LOG_DEBUG( 854c1a75ebcSrohitpai "Handling fragment rules: setting completion handler on {}", 855c1a75ebcSrohitpai logPtr(&rsp->res)); 856c1a75ebcSrohitpai std::optional<nlohmann::json::json_pointer> jsonFragmentPtr = 857c1a75ebcSrohitpai json_util::createJsonPointerFromFragment(fragmentRule.rule); 858c1a75ebcSrohitpai if (jsonFragmentPtr) 859c1a75ebcSrohitpai { 860c1a75ebcSrohitpai multi->addAwaitingResponse(rsp, *jsonFragmentPtr); 861c1a75ebcSrohitpai fragmentRule.handle(*req, rsp, *params); 862c1a75ebcSrohitpai } 863c1a75ebcSrohitpai } 8647cf436c9SEd Tanous } 8657cf436c9SEd Tanous } 8667cf436c9SEd Tanous 8677cf436c9SEd Tanous private: 868504af5a0SPatrick Williams static void placeResultStatic( 869504af5a0SPatrick Williams const std::shared_ptr<MultiAsyncResp>& multi, 8707cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 8717cf436c9SEd Tanous crow::Response& res) 8727cf436c9SEd Tanous { 87372c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 8747cf436c9SEd Tanous } 8757cf436c9SEd Tanous 876c1a75ebcSrohitpai crow::App* app; 8777cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 8787cf436c9SEd Tanous }; 8797cf436c9SEd Tanous 8802a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 8812a68dc80SEd Tanous { 8823648c8beSEd Tanous if (!query.skip && !query.top) 8833648c8beSEd Tanous { 8843648c8beSEd Tanous // No work to do. 8853648c8beSEd Tanous return; 8863648c8beSEd Tanous } 8872a68dc80SEd Tanous nlohmann::json::object_t* obj = 8882a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 8892a68dc80SEd Tanous if (obj == nullptr) 8902a68dc80SEd Tanous { 8912a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 8922a68dc80SEd Tanous messages::internalError(res); 8932a68dc80SEd Tanous return; 8942a68dc80SEd Tanous } 8952a68dc80SEd Tanous 89662598e31SEd Tanous BMCWEB_LOG_DEBUG("Handling top/skip"); 8972a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 8982a68dc80SEd Tanous if (members == obj->end()) 8992a68dc80SEd Tanous { 9002a68dc80SEd Tanous // From the Redfish specification 7.3.1 9012a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 9022a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 9032a68dc80SEd Tanous // for any supported query parameters that apply only to resource 9042a68dc80SEd Tanous // collections but are used on singular resources. 9052a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 9062a68dc80SEd Tanous return; 9072a68dc80SEd Tanous } 9082a68dc80SEd Tanous 9092a68dc80SEd Tanous nlohmann::json::array_t* arr = 9102a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 9112a68dc80SEd Tanous if (arr == nullptr) 9122a68dc80SEd Tanous { 9132a68dc80SEd Tanous messages::internalError(res); 9142a68dc80SEd Tanous return; 9152a68dc80SEd Tanous } 9162a68dc80SEd Tanous 9173648c8beSEd Tanous if (query.skip) 9183648c8beSEd Tanous { 9193648c8beSEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before 9203648c8beSEd Tanous // $top Can only skip as many values as we have 9213648c8beSEd Tanous size_t skip = std::min(arr->size(), *query.skip); 9222a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 9233648c8beSEd Tanous } 9243648c8beSEd Tanous if (query.top) 9253648c8beSEd Tanous { 9263648c8beSEd Tanous size_t top = std::min(arr->size(), *query.top); 9272a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 9282a68dc80SEd Tanous } 9293648c8beSEd Tanous } 9302a68dc80SEd Tanous 931827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 932827c4902SNan Zhou // not in the |currNode| Trie node. 933827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot, 934827c4902SNan Zhou const SelectTrieNode& currNode) 935e155ab54SNan Zhou { 936e155ab54SNan Zhou nlohmann::json::object_t* object = 937e155ab54SNan Zhou currRoot.get_ptr<nlohmann::json::object_t*>(); 938e155ab54SNan Zhou if (object != nullptr) 939e155ab54SNan Zhou { 94062598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an object"); 941761cdfa5SEd Tanous auto it = object->begin(); 942761cdfa5SEd Tanous while (it != object->end()) 943e155ab54SNan Zhou { 944e155ab54SNan Zhou auto nextIt = std::next(it); 945761cdfa5SEd Tanous BMCWEB_LOG_DEBUG("key={}", it->first); 946761cdfa5SEd Tanous const SelectTrieNode* nextNode = currNode.find(it->first); 9475c9fb2d6SNan Zhou // Per the Redfish spec section 7.3.3, the service shall select 9485c9fb2d6SNan Zhou // certain properties as if $select was omitted. This applies to 9495c9fb2d6SNan Zhou // every TrieNode that contains leaves and the root. 9505c9fb2d6SNan Zhou constexpr std::array<std::string_view, 5> reservedProperties = { 9515c9fb2d6SNan Zhou "@odata.id", "@odata.type", "@odata.context", "@odata.etag", 9525c9fb2d6SNan Zhou "error"}; 953761cdfa5SEd Tanous bool reserved = std::ranges::find(reservedProperties, it->first) != 9543544d2a7SEd Tanous reservedProperties.end(); 9555c9fb2d6SNan Zhou if (reserved || (nextNode != nullptr && nextNode->isSelected())) 956e155ab54SNan Zhou { 957e155ab54SNan Zhou it = nextIt; 958e155ab54SNan Zhou continue; 959e155ab54SNan Zhou } 960827c4902SNan Zhou if (nextNode != nullptr) 961e155ab54SNan Zhou { 962761cdfa5SEd Tanous BMCWEB_LOG_DEBUG("Recursively select: {}", it->first); 963761cdfa5SEd Tanous recursiveSelect(it->second, *nextNode); 964e155ab54SNan Zhou it = nextIt; 965e155ab54SNan Zhou continue; 966e155ab54SNan Zhou } 967761cdfa5SEd Tanous BMCWEB_LOG_DEBUG("{} is getting removed!", it->first); 968761cdfa5SEd Tanous it = object->erase(it); 969e155ab54SNan Zhou } 970e155ab54SNan Zhou } 9715c9fb2d6SNan Zhou nlohmann::json::array_t* array = 9725c9fb2d6SNan Zhou currRoot.get_ptr<nlohmann::json::array_t*>(); 9735c9fb2d6SNan Zhou if (array != nullptr) 9745c9fb2d6SNan Zhou { 97562598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an array"); 9765c9fb2d6SNan Zhou // Array index is omitted, so reuse the same Trie node 9775c9fb2d6SNan Zhou for (nlohmann::json& nextRoot : *array) 9785c9fb2d6SNan Zhou { 9795c9fb2d6SNan Zhou recursiveSelect(nextRoot, currNode); 9805c9fb2d6SNan Zhou } 9815c9fb2d6SNan Zhou } 982e155ab54SNan Zhou } 983e155ab54SNan Zhou 984e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to 985e155ab54SNan Zhou // ambiguity and/or complexity. 9865c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 987e155ab54SNan Zhou // created for clarification. 9885c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 989e155ab54SNan Zhou // etc. 990e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse, 991827c4902SNan Zhou const SelectTrieNode& trieRoot) 992e155ab54SNan Zhou { 99362598e31SEd Tanous BMCWEB_LOG_DEBUG("Process $select quary parameter"); 994827c4902SNan Zhou recursiveSelect(intermediateResponse.jsonValue, trieRoot); 995e155ab54SNan Zhou } 996e155ab54SNan Zhou 997504af5a0SPatrick Williams inline void processAllParams( 998504af5a0SPatrick Williams crow::App& app, const Query& query, const Query& delegated, 9997cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 1000*19fab295SChandramohan Harkude crow::Response& intermediateResponse, const crow::Request& req) 1001f4c99e70SEd Tanous { 1002f4c99e70SEd Tanous if (!completionHandler) 1003f4c99e70SEd Tanous { 100462598e31SEd Tanous BMCWEB_LOG_DEBUG("Function was invalid?"); 1005f4c99e70SEd Tanous return; 1006f4c99e70SEd Tanous } 1007f4c99e70SEd Tanous 100862598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing query params"); 1009f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 1010f4c99e70SEd Tanous // params. 1011f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 1012f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 1013f4c99e70SEd Tanous { 1014f4c99e70SEd Tanous completionHandler(intermediateResponse); 1015f4c99e70SEd Tanous return; 1016f4c99e70SEd Tanous } 1017f4c99e70SEd Tanous if (query.isOnly) 1018f4c99e70SEd Tanous { 1019f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 1020f4c99e70SEd Tanous return; 1021f4c99e70SEd Tanous } 10222a68dc80SEd Tanous 10233648c8beSEd Tanous if (query.top || query.skip) 10242a68dc80SEd Tanous { 10252a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 10262a68dc80SEd Tanous } 10272a68dc80SEd Tanous 10287cf436c9SEd Tanous if (query.expandType != ExpandType::None) 10297cf436c9SEd Tanous { 103062598e31SEd Tanous BMCWEB_LOG_DEBUG("Executing expand query"); 103113548d85SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 103213548d85SEd Tanous std::move(intermediateResponse)); 10337cf436c9SEd Tanous 103413548d85SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 103513548d85SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 1036*19fab295SChandramohan Harkude multi->startQuery(query, delegated, req); 10377cf436c9SEd Tanous return; 10387cf436c9SEd Tanous } 1039e155ab54SNan Zhou 104025991f7dSEd Tanous if (query.filter) 104125991f7dSEd Tanous { 1042f80a87f2SEd Tanous applyFilterToCollection(intermediateResponse.jsonValue, *query.filter); 104325991f7dSEd Tanous } 104425991f7dSEd Tanous 1045e155ab54SNan Zhou // According to Redfish Spec Section 7.3.1, $select is the last parameter to 1046e155ab54SNan Zhou // to process 1047827c4902SNan Zhou if (!query.selectTrie.root.empty()) 1048e155ab54SNan Zhou { 1049827c4902SNan Zhou processSelect(intermediateResponse, query.selectTrie.root); 1050e155ab54SNan Zhou } 1051e155ab54SNan Zhou 1052f4c99e70SEd Tanous completionHandler(intermediateResponse); 1053f4c99e70SEd Tanous } 1054f4c99e70SEd Tanous 1055f4c99e70SEd Tanous } // namespace query_param 1056f4c99e70SEd Tanous } // namespace redfish 1057