1f4c99e70SEd Tanous #pragma once 2d5c80ad9SNan Zhou #include "bmcweb_config.h" 3d5c80ad9SNan Zhou 4f4c99e70SEd Tanous #include "app.hpp" 5f4c99e70SEd Tanous #include "async_resp.hpp" 6f4c99e70SEd Tanous #include "error_messages.hpp" 7*25991f7dSEd Tanous #include "filter_expr_executor.hpp" 8*25991f7dSEd Tanous #include "filter_expr_printer.hpp" 9f4c99e70SEd Tanous #include "http_request.hpp" 1002cad96eSEd Tanous #include "http_response.hpp" 1195c6307aSEd Tanous #include "json_formatters.hpp" 12d5c80ad9SNan Zhou #include "logging.hpp" 1350ebd4afSEd Tanous #include "str_utility.hpp" 14f4c99e70SEd Tanous 15d5c80ad9SNan Zhou #include <sys/types.h> 16d5c80ad9SNan Zhou 17d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep 18d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp> 19d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp> 20d5c80ad9SNan Zhou #include <boost/url/params_view.hpp> 21d5c80ad9SNan Zhou #include <nlohmann/json.hpp> 22d5c80ad9SNan Zhou 23d5c80ad9SNan Zhou #include <algorithm> 24e155ab54SNan Zhou #include <array> 25e155ab54SNan Zhou #include <cctype> 267cf436c9SEd Tanous #include <charconv> 27827c4902SNan Zhou #include <compare> 28d5c80ad9SNan Zhou #include <cstdint> 29d5c80ad9SNan Zhou #include <functional> 30e155ab54SNan Zhou #include <iterator> 31d5c80ad9SNan Zhou #include <limits> 32d5c80ad9SNan Zhou #include <map> 33d5c80ad9SNan Zhou #include <memory> 34d5c80ad9SNan Zhou #include <optional> 353544d2a7SEd Tanous #include <ranges> 36f4c99e70SEd Tanous #include <string> 37f4c99e70SEd Tanous #include <string_view> 38d5c80ad9SNan Zhou #include <system_error> 397cf436c9SEd Tanous #include <utility> 40f4c99e70SEd Tanous #include <vector> 41f4c99e70SEd Tanous 42d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp> 43d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> 44d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> 45e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp> 46e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp> 47e155ab54SNan Zhou // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp> 48d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h> 49d5c80ad9SNan Zhou 50f4c99e70SEd Tanous namespace redfish 51f4c99e70SEd Tanous { 52f4c99e70SEd Tanous namespace query_param 53f4c99e70SEd Tanous { 54f4c99e70SEd Tanous 557cf436c9SEd Tanous enum class ExpandType : uint8_t 567cf436c9SEd Tanous { 577cf436c9SEd Tanous None, 587cf436c9SEd Tanous Links, 597cf436c9SEd Tanous NotLinks, 607cf436c9SEd Tanous Both, 617cf436c9SEd Tanous }; 627cf436c9SEd Tanous 63827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|. 64827c4902SNan Zhou class SelectTrieNode 65827c4902SNan Zhou { 66827c4902SNan Zhou public: 67827c4902SNan Zhou SelectTrieNode() = default; 68827c4902SNan Zhou 69827c4902SNan Zhou const SelectTrieNode* find(const std::string& jsonKey) const 70827c4902SNan Zhou { 71827c4902SNan Zhou auto it = children.find(jsonKey); 72827c4902SNan Zhou if (it == children.end()) 73827c4902SNan Zhou { 74827c4902SNan Zhou return nullptr; 75827c4902SNan Zhou } 76827c4902SNan Zhou return &it->second; 77827c4902SNan Zhou } 78827c4902SNan Zhou 79827c4902SNan Zhou // Creates a new node if the key doesn't exist, returns the reference to the 80827c4902SNan Zhou // newly created node; otherwise, return the reference to the existing node 81827c4902SNan Zhou SelectTrieNode* emplace(std::string_view jsonKey) 82827c4902SNan Zhou { 83827c4902SNan Zhou auto [it, _] = children.emplace(jsonKey, SelectTrieNode{}); 84827c4902SNan Zhou return &it->second; 85827c4902SNan Zhou } 86827c4902SNan Zhou 87827c4902SNan Zhou bool empty() const 88827c4902SNan Zhou { 89827c4902SNan Zhou return children.empty(); 90827c4902SNan Zhou } 91827c4902SNan Zhou 92827c4902SNan Zhou void clear() 93827c4902SNan Zhou { 94827c4902SNan Zhou children.clear(); 95827c4902SNan Zhou } 96827c4902SNan Zhou 97827c4902SNan Zhou void setToSelected() 98827c4902SNan Zhou { 99827c4902SNan Zhou selected = true; 100827c4902SNan Zhou } 101827c4902SNan Zhou 102827c4902SNan Zhou bool isSelected() const 103827c4902SNan Zhou { 104827c4902SNan Zhou return selected; 105827c4902SNan Zhou } 106827c4902SNan Zhou 107827c4902SNan Zhou private: 108827c4902SNan Zhou std::map<std::string, SelectTrieNode, std::less<>> children; 109827c4902SNan Zhou bool selected = false; 110827c4902SNan Zhou }; 111827c4902SNan Zhou 112827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among 113827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties) 114827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property) 115827c4902SNan Zhou { 116827c4902SNan Zhou // These a magic number, but with it it's less likely that this code 117827c4902SNan Zhou // introduces CVE; e.g., too large properties crash the service. 118827c4902SNan Zhou constexpr int maxPropertyLength = 60; 119827c4902SNan Zhou if (property.empty() || property.size() > maxPropertyLength) 120827c4902SNan Zhou { 121827c4902SNan Zhou return false; 122827c4902SNan Zhou } 123827c4902SNan Zhou for (char ch : property) 124827c4902SNan Zhou { 125827c4902SNan Zhou if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' && 126827c4902SNan Zhou ch != '@' && ch != '.') 127827c4902SNan Zhou { 128827c4902SNan Zhou return false; 129827c4902SNan Zhou } 130827c4902SNan Zhou } 131827c4902SNan Zhou return true; 132827c4902SNan Zhou } 133827c4902SNan Zhou 134827c4902SNan Zhou struct SelectTrie 135827c4902SNan Zhou { 136827c4902SNan Zhou SelectTrie() = default; 137827c4902SNan Zhou 138827c4902SNan Zhou // Inserts a $select value; returns false if the nestedProperty is illegal. 139827c4902SNan Zhou bool insertNode(std::string_view nestedProperty) 140827c4902SNan Zhou { 141827c4902SNan Zhou if (nestedProperty.empty()) 142827c4902SNan Zhou { 143827c4902SNan Zhou return false; 144827c4902SNan Zhou } 145827c4902SNan Zhou SelectTrieNode* currNode = &root; 146827c4902SNan Zhou size_t index = nestedProperty.find_first_of('/'); 147827c4902SNan Zhou while (!nestedProperty.empty()) 148827c4902SNan Zhou { 149827c4902SNan Zhou std::string_view property = nestedProperty.substr(0, index); 150827c4902SNan Zhou if (!isSelectedPropertyAllowed(property)) 151827c4902SNan Zhou { 152827c4902SNan Zhou return false; 153827c4902SNan Zhou } 154827c4902SNan Zhou currNode = currNode->emplace(property); 155827c4902SNan Zhou if (index == std::string::npos) 156827c4902SNan Zhou { 157827c4902SNan Zhou break; 158827c4902SNan Zhou } 159827c4902SNan Zhou nestedProperty.remove_prefix(index + 1); 160827c4902SNan Zhou index = nestedProperty.find_first_of('/'); 161827c4902SNan Zhou } 162827c4902SNan Zhou currNode->setToSelected(); 163827c4902SNan Zhou return true; 164827c4902SNan Zhou } 165827c4902SNan Zhou 166827c4902SNan Zhou SelectTrieNode root; 167827c4902SNan Zhou }; 168827c4902SNan Zhou 169a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 170f4c99e70SEd Tanous struct Query 171f4c99e70SEd Tanous { 172a6b9125fSNan Zhou // Only 173f4c99e70SEd Tanous bool isOnly = false; 174*25991f7dSEd Tanous 175a6b9125fSNan Zhou // Expand 176a6b9125fSNan Zhou uint8_t expandLevel = 0; 1777cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 178c937d2bfSEd Tanous 179c937d2bfSEd Tanous // Skip 1803648c8beSEd Tanous std::optional<size_t> skip = std::nullopt; 181c937d2bfSEd Tanous 182c937d2bfSEd Tanous // Top 1835143f7a5SJiaqing Zhao static constexpr size_t maxTop = 1000; // Max entries a response contain 1843648c8beSEd Tanous std::optional<size_t> top = std::nullopt; 185e155ab54SNan Zhou 186*25991f7dSEd Tanous // Filter 187*25991f7dSEd Tanous std::optional<filter_ast::LogicalAnd> filter = std::nullopt; 188*25991f7dSEd Tanous 189e155ab54SNan Zhou // Select 19047f2934cSEd Tanous // Unclear how to make this use structured initialization without this. 19147f2934cSEd Tanous // Might be a tidy bug? Ignore for now 19247f2934cSEd Tanous // NOLINTNEXTLINE(readability-redundant-member-init) 19347f2934cSEd Tanous SelectTrie selectTrie{}; 194f4c99e70SEd Tanous }; 195f4c99e70SEd Tanous 196a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 197a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 198a6b9125fSNan Zhou // the processing. 199a6b9125fSNan Zhou struct QueryCapabilities 200a6b9125fSNan Zhou { 201a6b9125fSNan Zhou bool canDelegateOnly = false; 202c937d2bfSEd Tanous bool canDelegateTop = false; 203c937d2bfSEd Tanous bool canDelegateSkip = false; 204a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 205e155ab54SNan Zhou bool canDelegateSelect = false; 206a6b9125fSNan Zhou }; 207a6b9125fSNan Zhou 208a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 209a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 210a6b9125fSNan Zhou // function will take care of it. 211a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 212a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 213a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 214a6b9125fSNan Zhou { 215f1a1e3dcSEd Tanous Query delegated{}; 216a6b9125fSNan Zhou // delegate only 217a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 218a6b9125fSNan Zhou { 219a6b9125fSNan Zhou delegated.isOnly = true; 220a6b9125fSNan Zhou query.isOnly = false; 221a6b9125fSNan Zhou } 222a6b9125fSNan Zhou // delegate expand as much as we can 223a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 224a6b9125fSNan Zhou { 225a6b9125fSNan Zhou delegated.expandType = query.expandType; 226a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 227a6b9125fSNan Zhou { 228a6b9125fSNan Zhou query.expandType = ExpandType::None; 229a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 230a6b9125fSNan Zhou query.expandLevel = 0; 231a6b9125fSNan Zhou } 232a6b9125fSNan Zhou else 233a6b9125fSNan Zhou { 234a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 235a6b9125fSNan Zhou } 236a6b9125fSNan Zhou } 237c937d2bfSEd Tanous 238c937d2bfSEd Tanous // delegate top 2393648c8beSEd Tanous if (query.top && queryCapabilities.canDelegateTop) 240c937d2bfSEd Tanous { 241c937d2bfSEd Tanous delegated.top = query.top; 2423648c8beSEd Tanous query.top = std::nullopt; 243c937d2bfSEd Tanous } 244c937d2bfSEd Tanous 245c937d2bfSEd Tanous // delegate skip 2463648c8beSEd Tanous if (query.skip && queryCapabilities.canDelegateSkip) 247c937d2bfSEd Tanous { 248c937d2bfSEd Tanous delegated.skip = query.skip; 249c937d2bfSEd Tanous query.skip = 0; 250c937d2bfSEd Tanous } 251e155ab54SNan Zhou 252e155ab54SNan Zhou // delegate select 253827c4902SNan Zhou if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect) 254e155ab54SNan Zhou { 255827c4902SNan Zhou delegated.selectTrie = std::move(query.selectTrie); 256827c4902SNan Zhou query.selectTrie.root.clear(); 257e155ab54SNan Zhou } 258a6b9125fSNan Zhou return delegated; 259a6b9125fSNan Zhou } 260a6b9125fSNan Zhou 2617cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 2627cf436c9SEd Tanous { 2637cf436c9SEd Tanous if (value.empty()) 2647cf436c9SEd Tanous { 2657cf436c9SEd Tanous return false; 2667cf436c9SEd Tanous } 2677cf436c9SEd Tanous switch (value[0]) 2687cf436c9SEd Tanous { 2697cf436c9SEd Tanous case '*': 2707cf436c9SEd Tanous query.expandType = ExpandType::Both; 2717cf436c9SEd Tanous break; 2727cf436c9SEd Tanous case '.': 2737cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 2747cf436c9SEd Tanous break; 2757cf436c9SEd Tanous case '~': 2767cf436c9SEd Tanous query.expandType = ExpandType::Links; 2777cf436c9SEd Tanous break; 2787cf436c9SEd Tanous default: 2797cf436c9SEd Tanous return false; 2807cf436c9SEd Tanous } 2817cf436c9SEd Tanous value.remove_prefix(1); 2827cf436c9SEd Tanous if (value.empty()) 2837cf436c9SEd Tanous { 2847cf436c9SEd Tanous query.expandLevel = 1; 2857cf436c9SEd Tanous return true; 2867cf436c9SEd Tanous } 2877cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 2887cf436c9SEd Tanous if (!value.starts_with(levels)) 2897cf436c9SEd Tanous { 2907cf436c9SEd Tanous return false; 2917cf436c9SEd Tanous } 2927cf436c9SEd Tanous value.remove_prefix(levels.size()); 2937cf436c9SEd Tanous 2942bd4ab43SPatrick Williams auto it = std::from_chars(value.begin(), value.end(), query.expandLevel); 2957cf436c9SEd Tanous if (it.ec != std::errc()) 2967cf436c9SEd Tanous { 2977cf436c9SEd Tanous return false; 2987cf436c9SEd Tanous } 2992bd4ab43SPatrick Williams value.remove_prefix( 3002bd4ab43SPatrick Williams static_cast<size_t>(std::distance(value.begin(), it.ptr))); 3017cf436c9SEd Tanous return value == ")"; 3027cf436c9SEd Tanous } 3037cf436c9SEd Tanous 304c937d2bfSEd Tanous enum class QueryError 305c937d2bfSEd Tanous { 306c937d2bfSEd Tanous Ok, 307c937d2bfSEd Tanous OutOfRange, 308c937d2bfSEd Tanous ValueFormat, 309c937d2bfSEd Tanous }; 310c937d2bfSEd Tanous 311c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 312c937d2bfSEd Tanous { 3132bd4ab43SPatrick Williams std::from_chars_result r = std::from_chars(value.begin(), value.end(), 3142bd4ab43SPatrick Williams param); 315c937d2bfSEd Tanous 316c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 317c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 318c937d2bfSEd Tanous { 319c937d2bfSEd Tanous return QueryError::OutOfRange; 320c937d2bfSEd Tanous } 321c937d2bfSEd Tanous // All other errors are value format 322c937d2bfSEd Tanous if (r.ec != std::errc()) 323c937d2bfSEd Tanous { 324c937d2bfSEd Tanous return QueryError::ValueFormat; 325c937d2bfSEd Tanous } 326c937d2bfSEd Tanous return QueryError::Ok; 327c937d2bfSEd Tanous } 328c937d2bfSEd Tanous 329c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 330c937d2bfSEd Tanous { 3313648c8beSEd Tanous return getNumericParam(value, query.skip.emplace()); 332c937d2bfSEd Tanous } 333c937d2bfSEd Tanous 334c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 335c937d2bfSEd Tanous { 3363648c8beSEd Tanous QueryError ret = getNumericParam(value, query.top.emplace()); 337c937d2bfSEd Tanous if (ret != QueryError::Ok) 338c937d2bfSEd Tanous { 339c937d2bfSEd Tanous return ret; 340c937d2bfSEd Tanous } 341c937d2bfSEd Tanous 342c937d2bfSEd Tanous // Range check for sanity. 3435143f7a5SJiaqing Zhao if (query.top > Query::maxTop) 344c937d2bfSEd Tanous { 345c937d2bfSEd Tanous return QueryError::OutOfRange; 346c937d2bfSEd Tanous } 347c937d2bfSEd Tanous 348c937d2bfSEd Tanous return QueryError::Ok; 349c937d2bfSEd Tanous } 350c937d2bfSEd Tanous 351e155ab54SNan Zhou // Parses and validates the $select parameter. 352e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be 353e155ab54SNan Zhou // comma separated Resource Path 354e155ab54SNan Zhou // Ref: 355e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 356e155ab54SNan Zhou // 2. 357e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 358e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query) 359e155ab54SNan Zhou { 360e155ab54SNan Zhou std::vector<std::string> properties; 36150ebd4afSEd Tanous bmcweb::split(properties, value, ','); 362e155ab54SNan Zhou if (properties.empty()) 363e155ab54SNan Zhou { 364e155ab54SNan Zhou return false; 365e155ab54SNan Zhou } 366e155ab54SNan Zhou // These a magic number, but with it it's less likely that this code 367e155ab54SNan Zhou // introduces CVE; e.g., too large properties crash the service. 368e155ab54SNan Zhou constexpr int maxNumProperties = 10; 369e155ab54SNan Zhou if (properties.size() > maxNumProperties) 370e155ab54SNan Zhou { 371e155ab54SNan Zhou return false; 372e155ab54SNan Zhou } 373827c4902SNan Zhou for (const auto& property : properties) 374e155ab54SNan Zhou { 375827c4902SNan Zhou if (!query.selectTrie.insertNode(property)) 376e155ab54SNan Zhou { 377e155ab54SNan Zhou return false; 378e155ab54SNan Zhou } 379e155ab54SNan Zhou } 380e155ab54SNan Zhou return true; 381e155ab54SNan Zhou } 382e155ab54SNan Zhou 383*25991f7dSEd Tanous // Parses and validates the $filter parameter. 384*25991f7dSEd Tanous inline bool getFilterParam(std::string_view value, Query& query) 385*25991f7dSEd Tanous { 386*25991f7dSEd Tanous query.filter = parseFilter(value); 387*25991f7dSEd Tanous return query.filter.has_value(); 388*25991f7dSEd Tanous } 389*25991f7dSEd Tanous 390079360aeSEd Tanous inline std::optional<Query> parseParameters(boost::urls::params_view urlParams, 391f4c99e70SEd Tanous crow::Response& res) 392f4c99e70SEd Tanous { 393f1a1e3dcSEd Tanous Query ret{}; 394f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 395f4c99e70SEd Tanous { 396079360aeSEd Tanous if (it.key == "only") 397f4c99e70SEd Tanous { 398f4c99e70SEd Tanous if (!it.value.empty()) 399f4c99e70SEd Tanous { 400079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 401f4c99e70SEd Tanous return std::nullopt; 402f4c99e70SEd Tanous } 403f4c99e70SEd Tanous ret.isOnly = true; 404f4c99e70SEd Tanous } 40525b54dbaSEd Tanous else if (it.key == "$expand" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 4067cf436c9SEd Tanous { 407079360aeSEd Tanous if (!getExpandType(it.value, ret)) 4087cf436c9SEd Tanous { 409079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4107cf436c9SEd Tanous return std::nullopt; 411f4c99e70SEd Tanous } 4127cf436c9SEd Tanous } 413079360aeSEd Tanous else if (it.key == "$top") 414c937d2bfSEd Tanous { 415079360aeSEd Tanous QueryError topRet = getTopParam(it.value, ret); 416c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 417c937d2bfSEd Tanous { 418079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 419c937d2bfSEd Tanous return std::nullopt; 420c937d2bfSEd Tanous } 421c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 422c937d2bfSEd Tanous { 423c937d2bfSEd Tanous messages::queryParameterOutOfRange( 424079360aeSEd Tanous res, it.value, "$top", 425079360aeSEd Tanous "0-" + std::to_string(Query::maxTop)); 426c937d2bfSEd Tanous return std::nullopt; 427c937d2bfSEd Tanous } 428c937d2bfSEd Tanous } 429079360aeSEd Tanous else if (it.key == "$skip") 430c937d2bfSEd Tanous { 431079360aeSEd Tanous QueryError topRet = getSkipParam(it.value, ret); 432c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 433c937d2bfSEd Tanous { 434079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 435c937d2bfSEd Tanous return std::nullopt; 436c937d2bfSEd Tanous } 437c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 438c937d2bfSEd Tanous { 439c937d2bfSEd Tanous messages::queryParameterOutOfRange( 440079360aeSEd Tanous res, it.value, it.key, 441a926c53eSJiaqing Zhao "0-" + std::to_string(std::numeric_limits<size_t>::max())); 442c937d2bfSEd Tanous return std::nullopt; 443c937d2bfSEd Tanous } 444c937d2bfSEd Tanous } 445079360aeSEd Tanous else if (it.key == "$select") 446e155ab54SNan Zhou { 447079360aeSEd Tanous if (!getSelectParam(it.value, ret)) 448e155ab54SNan Zhou { 449079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 450e155ab54SNan Zhou return std::nullopt; 451e155ab54SNan Zhou } 452e155ab54SNan Zhou } 453*25991f7dSEd Tanous else if (it.key == "$filter" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY) 454*25991f7dSEd Tanous { 455*25991f7dSEd Tanous if (!getFilterParam(it.value, ret)) 456*25991f7dSEd Tanous { 457*25991f7dSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 458*25991f7dSEd Tanous return std::nullopt; 459*25991f7dSEd Tanous } 460*25991f7dSEd Tanous } 4617cf436c9SEd Tanous else 4627cf436c9SEd Tanous { 4637cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 464079360aeSEd Tanous if (it.key.starts_with("$")) 4657cf436c9SEd Tanous { 4667cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 4677cf436c9SEd Tanous // status code for any unsupported query parameters that 4687cf436c9SEd Tanous // start with $ . 469079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4707cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 4717cf436c9SEd Tanous return std::nullopt; 4727cf436c9SEd Tanous } 4737cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 4747cf436c9SEd Tanous // not begin with $ ." 4757cf436c9SEd Tanous } 4767cf436c9SEd Tanous } 4777cf436c9SEd Tanous 478827c4902SNan Zhou if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 479e155ab54SNan Zhou { 480e155ab54SNan Zhou messages::queryCombinationInvalid(res); 481e155ab54SNan Zhou return std::nullopt; 482e155ab54SNan Zhou } 483e155ab54SNan Zhou 484f4c99e70SEd Tanous return ret; 485f4c99e70SEd Tanous } 486f4c99e70SEd Tanous 487f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 488f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 489f4c99e70SEd Tanous { 49062598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing only query param"); 491f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 492f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 493f4c99e70SEd Tanous { 494f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 495f4c99e70SEd Tanous completionHandler(res); 496f4c99e70SEd Tanous return false; 497f4c99e70SEd Tanous } 498f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 499f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 500f4c99e70SEd Tanous { 50162598e31SEd Tanous BMCWEB_LOG_DEBUG( 50262598e31SEd Tanous "Members contains {} element, returning full collection.", 50362598e31SEd Tanous itMembers->size()); 504f4c99e70SEd Tanous completionHandler(res); 505f4c99e70SEd Tanous return false; 506f4c99e70SEd Tanous } 507f4c99e70SEd Tanous 508f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 509f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 510f4c99e70SEd Tanous { 51162598e31SEd Tanous BMCWEB_LOG_DEBUG("No found odata.id"); 512f4c99e70SEd Tanous messages::internalError(res); 513f4c99e70SEd Tanous completionHandler(res); 514f4c99e70SEd Tanous return false; 515f4c99e70SEd Tanous } 516f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 517f4c99e70SEd Tanous if (url == nullptr) 518f4c99e70SEd Tanous { 51962598e31SEd Tanous BMCWEB_LOG_DEBUG("@odata.id wasn't a string????"); 520f4c99e70SEd Tanous messages::internalError(res); 521f4c99e70SEd Tanous completionHandler(res); 522f4c99e70SEd Tanous return false; 523f4c99e70SEd Tanous } 524f4c99e70SEd Tanous // TODO(Ed) copy request headers? 525f4c99e70SEd Tanous // newReq.session = req.session; 526f4c99e70SEd Tanous std::error_code ec; 527102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 528102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, *url, 11}, ec); 529f4c99e70SEd Tanous if (ec) 530f4c99e70SEd Tanous { 531f4c99e70SEd Tanous messages::internalError(res); 532f4c99e70SEd Tanous completionHandler(res); 533f4c99e70SEd Tanous return false; 534f4c99e70SEd Tanous } 535f4c99e70SEd Tanous 536f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 53762598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 53862598e31SEd Tanous logPtr(&asyncResp->res)); 539f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 540f4c99e70SEd Tanous app.handle(newReq, asyncResp); 541f4c99e70SEd Tanous return true; 542f4c99e70SEd Tanous } 543f4c99e70SEd Tanous 5447cf436c9SEd Tanous struct ExpandNode 5457cf436c9SEd Tanous { 5467cf436c9SEd Tanous nlohmann::json::json_pointer location; 5477cf436c9SEd Tanous std::string uri; 5487cf436c9SEd Tanous 5499de65b34SEd Tanous bool operator==(const ExpandNode& other) const 5507cf436c9SEd Tanous { 5517cf436c9SEd Tanous return location == other.location && uri == other.uri; 5527cf436c9SEd Tanous } 5537cf436c9SEd Tanous }; 5547cf436c9SEd Tanous 55587788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 556c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 55737b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 55887788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 55987788abfSEd Tanous 56087788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 561c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 56237b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 56387788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out); 56487788abfSEd Tanous 5657cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 5667cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 5677cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 5687cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 5697cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 5707cf436c9SEd Tanous // with the keys from the jsonResponse object 5717cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 5727cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 57337b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 57432cdb4a7SWilly Tu bool inLinks, std::vector<ExpandNode>& out) 5757cf436c9SEd Tanous { 5767cf436c9SEd Tanous // If no expand is needed, return early 5777cf436c9SEd Tanous if (eType == ExpandType::None) 5787cf436c9SEd Tanous { 5797cf436c9SEd Tanous return; 5807cf436c9SEd Tanous } 581ad595fa6SEd Tanous 5827cf436c9SEd Tanous nlohmann::json::array_t* array = 5837cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 5847cf436c9SEd Tanous if (array != nullptr) 5857cf436c9SEd Tanous { 58637b1f7beSEd Tanous findNavigationReferencesInArrayRecursive(eType, *array, jsonPtr, depth, 58787788abfSEd Tanous skipDepth, inLinks, out); 58887788abfSEd Tanous } 58987788abfSEd Tanous nlohmann::json::object_t* obj = 59087788abfSEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 59187788abfSEd Tanous if (obj == nullptr) 59287788abfSEd Tanous { 59387788abfSEd Tanous return; 59487788abfSEd Tanous } 59537b1f7beSEd Tanous findNavigationReferencesInObjectRecursive(eType, *obj, jsonPtr, depth, 59637b1f7beSEd Tanous skipDepth, inLinks, out); 59787788abfSEd Tanous } 59887788abfSEd Tanous 59987788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive( 600c59e338cSEd Tanous ExpandType eType, nlohmann::json::array_t& array, 60137b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 60287788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 60387788abfSEd Tanous { 6047cf436c9SEd Tanous size_t index = 0; 6057cf436c9SEd Tanous // For arrays, walk every element in the array 606c59e338cSEd Tanous for (auto& element : array) 6077cf436c9SEd Tanous { 60837b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / index; 60962598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr.to_string()); 610ad595fa6SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, depth, 61132cdb4a7SWilly Tu skipDepth, inLinks, out); 6127cf436c9SEd Tanous index++; 6137cf436c9SEd Tanous } 6147cf436c9SEd Tanous } 61587788abfSEd Tanous 61687788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive( 617c59e338cSEd Tanous ExpandType eType, nlohmann::json::object_t& obj, 61837b1f7beSEd Tanous const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth, 61987788abfSEd Tanous bool inLinks, std::vector<ExpandNode>& out) 6207cf436c9SEd Tanous { 6217cf436c9SEd Tanous // Navigation References only ever have a single element 622c59e338cSEd Tanous if (obj.size() == 1) 6237cf436c9SEd Tanous { 624c59e338cSEd Tanous if (obj.begin()->first == "@odata.id") 6257cf436c9SEd Tanous { 6267cf436c9SEd Tanous const std::string* uri = 627c59e338cSEd Tanous obj.begin()->second.get_ptr<const std::string*>(); 6287cf436c9SEd Tanous if (uri != nullptr) 6297cf436c9SEd Tanous { 63062598e31SEd Tanous BMCWEB_LOG_DEBUG("Found {} at {}", *uri, jsonPtr.to_string()); 63132cdb4a7SWilly Tu if (skipDepth == 0) 63232cdb4a7SWilly Tu { 63337b1f7beSEd Tanous out.push_back({jsonPtr, *uri}); 63432cdb4a7SWilly Tu } 635ad595fa6SEd Tanous return; 6367cf436c9SEd Tanous } 6377cf436c9SEd Tanous } 6387cf436c9SEd Tanous } 639ad595fa6SEd Tanous 640ad595fa6SEd Tanous int newDepth = depth; 641c59e338cSEd Tanous auto odataId = obj.find("@odata.id"); 642c59e338cSEd Tanous if (odataId != obj.end()) 643ad595fa6SEd Tanous { 644ad595fa6SEd Tanous // The Redfish spec requires all resources to include the resource 645ad595fa6SEd Tanous // identifier. If the object has multiple elements and one of them is 646ad595fa6SEd Tanous // "@odata.id" then that means we have entered a new level / expanded 647ad595fa6SEd Tanous // resource. We need to stop traversing if we're already at the desired 648ad595fa6SEd Tanous // depth 649c59e338cSEd Tanous if (obj.size() > 1) 65032cdb4a7SWilly Tu { 65132cdb4a7SWilly Tu if (depth == 0) 652ad595fa6SEd Tanous { 653ad595fa6SEd Tanous return; 654ad595fa6SEd Tanous } 65532cdb4a7SWilly Tu if (skipDepth > 0) 65632cdb4a7SWilly Tu { 65732cdb4a7SWilly Tu skipDepth--; 65832cdb4a7SWilly Tu } 65932cdb4a7SWilly Tu } 66032cdb4a7SWilly Tu 66132cdb4a7SWilly Tu if (skipDepth == 0) 66232cdb4a7SWilly Tu { 663ad595fa6SEd Tanous newDepth--; 664ad595fa6SEd Tanous } 66532cdb4a7SWilly Tu } 666ad595fa6SEd Tanous 6677cf436c9SEd Tanous // Loop the object and look for links 668c59e338cSEd Tanous for (auto& element : obj) 6697cf436c9SEd Tanous { 670e479ad58SNan Zhou bool localInLinks = inLinks; 671e479ad58SNan Zhou if (!localInLinks) 6727cf436c9SEd Tanous { 6737cf436c9SEd Tanous // Check if this is a links node 674e479ad58SNan Zhou localInLinks = element.first == "Links"; 6757cf436c9SEd Tanous } 6767cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 6777cf436c9SEd Tanous // Per section 7.3 of the redfish specification 678e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 6797cf436c9SEd Tanous { 6807cf436c9SEd Tanous continue; 6817cf436c9SEd Tanous } 682e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 6837cf436c9SEd Tanous { 6847cf436c9SEd Tanous continue; 6857cf436c9SEd Tanous } 68637b1f7beSEd Tanous nlohmann::json::json_pointer newPtr = jsonPtr / element.first; 68762598e31SEd Tanous BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr); 6887cf436c9SEd Tanous 6897cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 69032cdb4a7SWilly Tu newDepth, skipDepth, localInLinks, 69132cdb4a7SWilly Tu out); 6927cf436c9SEd Tanous } 6937cf436c9SEd Tanous } 6947cf436c9SEd Tanous 695ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded 696ad595fa6SEd Tanous // response we may need need additional handling when the original URI was 697ad595fa6SEd Tanous // up tree from a top level collection. 698ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556 699ad595fa6SEd Tanous // lands. May want to avoid forwarding query params when request is uptree from 700ad595fa6SEd Tanous // a top level collection. 7017cf436c9SEd Tanous inline std::vector<ExpandNode> 70232cdb4a7SWilly Tu findNavigationReferences(ExpandType eType, int depth, int skipDepth, 703ad595fa6SEd Tanous nlohmann::json& jsonResponse) 7047cf436c9SEd Tanous { 7057cf436c9SEd Tanous std::vector<ExpandNode> ret; 70672c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 70732cdb4a7SWilly Tu // SkipDepth +1 since we are skipping the root by default. 70832cdb4a7SWilly Tu findNavigationReferencesRecursive(eType, jsonResponse, root, depth, 70932cdb4a7SWilly Tu skipDepth + 1, false, ret); 7107cf436c9SEd Tanous return ret; 7117cf436c9SEd Tanous } 7127cf436c9SEd Tanous 71372c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 714b66cf2a2SNan Zhou // Returns std::nullopt on failures. 71572c3ae33SNan Zhou // This function shall handle $select when it is added. 7168ece0e45SEd Tanous // There is no need to handle parameters that's not compatible with $expand, 71772c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 718b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query) 71972c3ae33SNan Zhou { 72072c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 72172c3ae33SNan Zhou if (query.expandLevel <= 1) 72272c3ae33SNan Zhou { 723b66cf2a2SNan Zhou return ""; 72472c3ae33SNan Zhou } 72572c3ae33SNan Zhou std::string str = "?$expand="; 72672c3ae33SNan Zhou switch (query.expandType) 72772c3ae33SNan Zhou { 72872c3ae33SNan Zhou case ExpandType::Links: 72972c3ae33SNan Zhou str += '~'; 73072c3ae33SNan Zhou break; 73172c3ae33SNan Zhou case ExpandType::NotLinks: 73272c3ae33SNan Zhou str += '.'; 73372c3ae33SNan Zhou break; 73472c3ae33SNan Zhou case ExpandType::Both: 73572c3ae33SNan Zhou str += '*'; 73672c3ae33SNan Zhou break; 737f1a1e3dcSEd Tanous case ExpandType::None: 738f1a1e3dcSEd Tanous return ""; 7394da0490bSEd Tanous default: 7404da0490bSEd Tanous return std::nullopt; 741b66cf2a2SNan Zhou } 74272c3ae33SNan Zhou str += "($levels="; 74372c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 74472c3ae33SNan Zhou str += ')'; 74572c3ae33SNan Zhou return str; 74672c3ae33SNan Zhou } 74772c3ae33SNan Zhou 7488ece0e45SEd Tanous // Propagates the worst error code to the final response. 7493590bd1dSNan Zhou // The order of error code is (from high to low) 7503590bd1dSNan Zhou // 500 Internal Server Error 7513590bd1dSNan Zhou // 511 Network Authentication Required 7523590bd1dSNan Zhou // 510 Not Extended 7533590bd1dSNan Zhou // 508 Loop Detected 7543590bd1dSNan Zhou // 507 Insufficient Storage 7553590bd1dSNan Zhou // 506 Variant Also Negotiates 7563590bd1dSNan Zhou // 505 HTTP Version Not Supported 7573590bd1dSNan Zhou // 504 Gateway Timeout 7583590bd1dSNan Zhou // 503 Service Unavailable 7593590bd1dSNan Zhou // 502 Bad Gateway 7603590bd1dSNan Zhou // 501 Not Implemented 7613590bd1dSNan Zhou // 401 Unauthorized 7628ece0e45SEd Tanous // 451 - 409 Error codes (not listed explicitly) 7633590bd1dSNan Zhou // 408 Request Timeout 7643590bd1dSNan Zhou // 407 Proxy Authentication Required 7653590bd1dSNan Zhou // 406 Not Acceptable 7663590bd1dSNan Zhou // 405 Method Not Allowed 7673590bd1dSNan Zhou // 404 Not Found 7683590bd1dSNan Zhou // 403 Forbidden 7693590bd1dSNan Zhou // 402 Payment Required 7703590bd1dSNan Zhou // 400 Bad Request 7713590bd1dSNan Zhou inline unsigned propogateErrorCode(unsigned finalCode, unsigned subResponseCode) 7723590bd1dSNan Zhou { 7733590bd1dSNan Zhou // We keep a explicit list for error codes that this project often uses 7748ece0e45SEd Tanous // Higher priority codes are in lower indexes 7753590bd1dSNan Zhou constexpr std::array<unsigned, 13> orderedCodes = { 7763590bd1dSNan Zhou 500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400}; 7773590bd1dSNan Zhou size_t finalCodeIndex = std::numeric_limits<size_t>::max(); 7783590bd1dSNan Zhou size_t subResponseCodeIndex = std::numeric_limits<size_t>::max(); 7793590bd1dSNan Zhou for (size_t i = 0; i < orderedCodes.size(); ++i) 7803590bd1dSNan Zhou { 7813590bd1dSNan Zhou if (orderedCodes[i] == finalCode) 7823590bd1dSNan Zhou { 7833590bd1dSNan Zhou finalCodeIndex = i; 7843590bd1dSNan Zhou } 7853590bd1dSNan Zhou if (orderedCodes[i] == subResponseCode) 7863590bd1dSNan Zhou { 7873590bd1dSNan Zhou subResponseCodeIndex = i; 7883590bd1dSNan Zhou } 7893590bd1dSNan Zhou } 7903590bd1dSNan Zhou if (finalCodeIndex != std::numeric_limits<size_t>::max() && 7913590bd1dSNan Zhou subResponseCodeIndex != std::numeric_limits<size_t>::max()) 7923590bd1dSNan Zhou { 7933590bd1dSNan Zhou return finalCodeIndex <= subResponseCodeIndex ? finalCode 7943590bd1dSNan Zhou : subResponseCode; 7953590bd1dSNan Zhou } 7963590bd1dSNan Zhou if (subResponseCode == 500 || finalCode == 500) 7973590bd1dSNan Zhou { 7983590bd1dSNan Zhou return 500; 7993590bd1dSNan Zhou } 8003590bd1dSNan Zhou if (subResponseCode > 500 || finalCode > 500) 8013590bd1dSNan Zhou { 8023590bd1dSNan Zhou return std::max(finalCode, subResponseCode); 8033590bd1dSNan Zhou } 8043590bd1dSNan Zhou if (subResponseCode == 401) 8053590bd1dSNan Zhou { 8063590bd1dSNan Zhou return subResponseCode; 8073590bd1dSNan Zhou } 8083590bd1dSNan Zhou return std::max(finalCode, subResponseCode); 8093590bd1dSNan Zhou } 8103590bd1dSNan Zhou 8118ece0e45SEd Tanous // Propagates all error messages into |finalResponse| 8123590bd1dSNan Zhou inline void propogateError(crow::Response& finalResponse, 8133590bd1dSNan Zhou crow::Response& subResponse) 8143590bd1dSNan Zhou { 8153590bd1dSNan Zhou // no errors 8163590bd1dSNan Zhou if (subResponse.resultInt() >= 200 && subResponse.resultInt() < 400) 8173590bd1dSNan Zhou { 8183590bd1dSNan Zhou return; 8193590bd1dSNan Zhou } 8203590bd1dSNan Zhou messages::moveErrorsToErrorJson(finalResponse.jsonValue, 8213590bd1dSNan Zhou subResponse.jsonValue); 8223590bd1dSNan Zhou finalResponse.result( 8233590bd1dSNan Zhou propogateErrorCode(finalResponse.resultInt(), subResponse.resultInt())); 8243590bd1dSNan Zhou } 8253590bd1dSNan Zhou 8267cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 8277cf436c9SEd Tanous { 8287cf436c9SEd Tanous public: 8297cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 8307cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 8317cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 8327cf436c9SEd Tanous // class manages the final "merge" of the json resources. 8338a592810SEd Tanous MultiAsyncResp(crow::App& appIn, 8347cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 8358a592810SEd Tanous app(appIn), 8367cf436c9SEd Tanous finalRes(std::move(finalResIn)) 8377cf436c9SEd Tanous {} 8387cf436c9SEd Tanous 8397cf436c9SEd Tanous void addAwaitingResponse( 84002cad96eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& res, 8417cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 8427cf436c9SEd Tanous { 8437cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 84472c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 8457cf436c9SEd Tanous } 8467cf436c9SEd Tanous 84772c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 8487cf436c9SEd Tanous crow::Response& res) 8497cf436c9SEd Tanous { 85062598e31SEd Tanous BMCWEB_LOG_DEBUG("placeResult for {}", locationToPlace); 8513590bd1dSNan Zhou propogateError(finalRes->res, res); 8523590bd1dSNan Zhou if (!res.jsonValue.is_object() || res.jsonValue.empty()) 8533590bd1dSNan Zhou { 8543590bd1dSNan Zhou return; 8553590bd1dSNan Zhou } 8567cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 8577cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 8587cf436c9SEd Tanous } 8597cf436c9SEd Tanous 86072c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 86172c3ae33SNan Zhou // for deeper levels. 86232cdb4a7SWilly Tu void startQuery(const Query& query, const Query& delegated) 86372c3ae33SNan Zhou { 864ad595fa6SEd Tanous std::vector<ExpandNode> nodes = findNavigationReferences( 86532cdb4a7SWilly Tu query.expandType, query.expandLevel, delegated.expandLevel, 86632cdb4a7SWilly Tu finalRes->res.jsonValue); 86762598e31SEd Tanous BMCWEB_LOG_DEBUG("{} nodes to traverse", nodes.size()); 868b66cf2a2SNan Zhou const std::optional<std::string> queryStr = formatQueryForExpand(query); 869b66cf2a2SNan Zhou if (!queryStr) 870b66cf2a2SNan Zhou { 871b66cf2a2SNan Zhou messages::internalError(finalRes->res); 872b66cf2a2SNan Zhou return; 873b66cf2a2SNan Zhou } 8747cf436c9SEd Tanous for (const ExpandNode& node : nodes) 8757cf436c9SEd Tanous { 876b66cf2a2SNan Zhou const std::string subQuery = node.uri + *queryStr; 87762598e31SEd Tanous BMCWEB_LOG_DEBUG("URL of subquery: {}", subQuery); 8787cf436c9SEd Tanous std::error_code ec; 879102a4cdaSJonathan Doman auto newReq = std::make_shared<crow::Request>( 880102a4cdaSJonathan Doman crow::Request::Body{boost::beast::http::verb::get, subQuery, 881102a4cdaSJonathan Doman 11}, 8827cf436c9SEd Tanous ec); 8837cf436c9SEd Tanous if (ec) 8847cf436c9SEd Tanous { 88572c3ae33SNan Zhou messages::internalError(finalRes->res); 8867cf436c9SEd Tanous return; 8877cf436c9SEd Tanous } 8887cf436c9SEd Tanous 8897cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 89062598e31SEd Tanous BMCWEB_LOG_DEBUG("setting completion handler on {}", 89162598e31SEd Tanous logPtr(&asyncResp->res)); 89272c3ae33SNan Zhou 89372c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 8947cf436c9SEd Tanous app.handle(newReq, asyncResp); 8957cf436c9SEd Tanous } 8967cf436c9SEd Tanous } 8977cf436c9SEd Tanous 8987cf436c9SEd Tanous private: 89972c3ae33SNan Zhou static void 90072c3ae33SNan Zhou placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 9017cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 9027cf436c9SEd Tanous crow::Response& res) 9037cf436c9SEd Tanous { 90472c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 9057cf436c9SEd Tanous } 9067cf436c9SEd Tanous 9077cf436c9SEd Tanous crow::App& app; 9087cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 9097cf436c9SEd Tanous }; 9107cf436c9SEd Tanous 9112a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 9122a68dc80SEd Tanous { 9133648c8beSEd Tanous if (!query.skip && !query.top) 9143648c8beSEd Tanous { 9153648c8beSEd Tanous // No work to do. 9163648c8beSEd Tanous return; 9173648c8beSEd Tanous } 9182a68dc80SEd Tanous nlohmann::json::object_t* obj = 9192a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 9202a68dc80SEd Tanous if (obj == nullptr) 9212a68dc80SEd Tanous { 9222a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 9232a68dc80SEd Tanous messages::internalError(res); 9242a68dc80SEd Tanous return; 9252a68dc80SEd Tanous } 9262a68dc80SEd Tanous 92762598e31SEd Tanous BMCWEB_LOG_DEBUG("Handling top/skip"); 9282a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 9292a68dc80SEd Tanous if (members == obj->end()) 9302a68dc80SEd Tanous { 9312a68dc80SEd Tanous // From the Redfish specification 7.3.1 9322a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 9332a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 9342a68dc80SEd Tanous // for any supported query parameters that apply only to resource 9352a68dc80SEd Tanous // collections but are used on singular resources. 9362a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 9372a68dc80SEd Tanous return; 9382a68dc80SEd Tanous } 9392a68dc80SEd Tanous 9402a68dc80SEd Tanous nlohmann::json::array_t* arr = 9412a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 9422a68dc80SEd Tanous if (arr == nullptr) 9432a68dc80SEd Tanous { 9442a68dc80SEd Tanous messages::internalError(res); 9452a68dc80SEd Tanous return; 9462a68dc80SEd Tanous } 9472a68dc80SEd Tanous 9483648c8beSEd Tanous if (query.skip) 9493648c8beSEd Tanous { 9503648c8beSEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before 9513648c8beSEd Tanous // $top Can only skip as many values as we have 9523648c8beSEd Tanous size_t skip = std::min(arr->size(), *query.skip); 9532a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 9543648c8beSEd Tanous } 9553648c8beSEd Tanous if (query.top) 9563648c8beSEd Tanous { 9573648c8beSEd Tanous size_t top = std::min(arr->size(), *query.top); 9582a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 9592a68dc80SEd Tanous } 9603648c8beSEd Tanous } 9612a68dc80SEd Tanous 962827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 963827c4902SNan Zhou // not in the |currNode| Trie node. 964827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot, 965827c4902SNan Zhou const SelectTrieNode& currNode) 966e155ab54SNan Zhou { 967e155ab54SNan Zhou nlohmann::json::object_t* object = 968e155ab54SNan Zhou currRoot.get_ptr<nlohmann::json::object_t*>(); 969e155ab54SNan Zhou if (object != nullptr) 970e155ab54SNan Zhou { 97162598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an object"); 972e155ab54SNan Zhou auto it = currRoot.begin(); 973e155ab54SNan Zhou while (it != currRoot.end()) 974e155ab54SNan Zhou { 975e155ab54SNan Zhou auto nextIt = std::next(it); 97662598e31SEd Tanous BMCWEB_LOG_DEBUG("key={}", it.key()); 977827c4902SNan Zhou const SelectTrieNode* nextNode = currNode.find(it.key()); 9785c9fb2d6SNan Zhou // Per the Redfish spec section 7.3.3, the service shall select 9795c9fb2d6SNan Zhou // certain properties as if $select was omitted. This applies to 9805c9fb2d6SNan Zhou // every TrieNode that contains leaves and the root. 9815c9fb2d6SNan Zhou constexpr std::array<std::string_view, 5> reservedProperties = { 9825c9fb2d6SNan Zhou "@odata.id", "@odata.type", "@odata.context", "@odata.etag", 9835c9fb2d6SNan Zhou "error"}; 9843544d2a7SEd Tanous bool reserved = std::ranges::find(reservedProperties, it.key()) != 9853544d2a7SEd Tanous reservedProperties.end(); 9865c9fb2d6SNan Zhou if (reserved || (nextNode != nullptr && nextNode->isSelected())) 987e155ab54SNan Zhou { 988e155ab54SNan Zhou it = nextIt; 989e155ab54SNan Zhou continue; 990e155ab54SNan Zhou } 991827c4902SNan Zhou if (nextNode != nullptr) 992e155ab54SNan Zhou { 99362598e31SEd Tanous BMCWEB_LOG_DEBUG("Recursively select: {}", it.key()); 994827c4902SNan Zhou recursiveSelect(*it, *nextNode); 995e155ab54SNan Zhou it = nextIt; 996e155ab54SNan Zhou continue; 997e155ab54SNan Zhou } 99862598e31SEd Tanous BMCWEB_LOG_DEBUG("{} is getting removed!", it.key()); 999e155ab54SNan Zhou it = currRoot.erase(it); 1000e155ab54SNan Zhou } 1001e155ab54SNan Zhou } 10025c9fb2d6SNan Zhou nlohmann::json::array_t* array = 10035c9fb2d6SNan Zhou currRoot.get_ptr<nlohmann::json::array_t*>(); 10045c9fb2d6SNan Zhou if (array != nullptr) 10055c9fb2d6SNan Zhou { 100662598e31SEd Tanous BMCWEB_LOG_DEBUG("Current JSON is an array"); 10075c9fb2d6SNan Zhou // Array index is omitted, so reuse the same Trie node 10085c9fb2d6SNan Zhou for (nlohmann::json& nextRoot : *array) 10095c9fb2d6SNan Zhou { 10105c9fb2d6SNan Zhou recursiveSelect(nextRoot, currNode); 10115c9fb2d6SNan Zhou } 10125c9fb2d6SNan Zhou } 1013e155ab54SNan Zhou } 1014e155ab54SNan Zhou 1015e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to 1016e155ab54SNan Zhou // ambiguity and/or complexity. 10175c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 1018e155ab54SNan Zhou // created for clarification. 10195c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 1020e155ab54SNan Zhou // etc. 1021e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse, 1022827c4902SNan Zhou const SelectTrieNode& trieRoot) 1023e155ab54SNan Zhou { 102462598e31SEd Tanous BMCWEB_LOG_DEBUG("Process $select quary parameter"); 1025827c4902SNan Zhou recursiveSelect(intermediateResponse.jsonValue, trieRoot); 1026e155ab54SNan Zhou } 1027e155ab54SNan Zhou 10287cf436c9SEd Tanous inline void 102932cdb4a7SWilly Tu processAllParams(crow::App& app, const Query& query, const Query& delegated, 10307cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 10317cf436c9SEd Tanous crow::Response& intermediateResponse) 1032f4c99e70SEd Tanous { 1033f4c99e70SEd Tanous if (!completionHandler) 1034f4c99e70SEd Tanous { 103562598e31SEd Tanous BMCWEB_LOG_DEBUG("Function was invalid?"); 1036f4c99e70SEd Tanous return; 1037f4c99e70SEd Tanous } 1038f4c99e70SEd Tanous 103962598e31SEd Tanous BMCWEB_LOG_DEBUG("Processing query params"); 1040f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 1041f4c99e70SEd Tanous // params. 1042f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 1043f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 1044f4c99e70SEd Tanous { 1045f4c99e70SEd Tanous completionHandler(intermediateResponse); 1046f4c99e70SEd Tanous return; 1047f4c99e70SEd Tanous } 1048f4c99e70SEd Tanous if (query.isOnly) 1049f4c99e70SEd Tanous { 1050f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 1051f4c99e70SEd Tanous return; 1052f4c99e70SEd Tanous } 10532a68dc80SEd Tanous 10543648c8beSEd Tanous if (query.top || query.skip) 10552a68dc80SEd Tanous { 10562a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 10572a68dc80SEd Tanous } 10582a68dc80SEd Tanous 10597cf436c9SEd Tanous if (query.expandType != ExpandType::None) 10607cf436c9SEd Tanous { 106162598e31SEd Tanous BMCWEB_LOG_DEBUG("Executing expand query"); 106213548d85SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 106313548d85SEd Tanous std::move(intermediateResponse)); 10647cf436c9SEd Tanous 106513548d85SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 106613548d85SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 106732cdb4a7SWilly Tu multi->startQuery(query, delegated); 10687cf436c9SEd Tanous return; 10697cf436c9SEd Tanous } 1070e155ab54SNan Zhou 1071*25991f7dSEd Tanous if (query.filter) 1072*25991f7dSEd Tanous { 1073*25991f7dSEd Tanous applyFilter(intermediateResponse.jsonValue, *query.filter); 1074*25991f7dSEd Tanous } 1075*25991f7dSEd Tanous 1076e155ab54SNan Zhou // According to Redfish Spec Section 7.3.1, $select is the last parameter to 1077e155ab54SNan Zhou // to process 1078827c4902SNan Zhou if (!query.selectTrie.root.empty()) 1079e155ab54SNan Zhou { 1080827c4902SNan Zhou processSelect(intermediateResponse, query.selectTrie.root); 1081e155ab54SNan Zhou } 1082e155ab54SNan Zhou 1083f4c99e70SEd Tanous completionHandler(intermediateResponse); 1084f4c99e70SEd Tanous } 1085f4c99e70SEd Tanous 1086f4c99e70SEd Tanous } // namespace query_param 1087f4c99e70SEd Tanous } // namespace redfish 1088