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" 7f4c99e70SEd Tanous #include "http_request.hpp" 802cad96eSEd Tanous #include "http_response.hpp" 9d5c80ad9SNan Zhou #include "logging.hpp" 1050ebd4afSEd Tanous #include "str_utility.hpp" 11f4c99e70SEd Tanous 12d5c80ad9SNan Zhou #include <sys/types.h> 13d5c80ad9SNan Zhou 14e155ab54SNan Zhou #include <boost/algorithm/string/classification.hpp> 15d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep 16d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp> 17d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp> 18d5c80ad9SNan Zhou #include <boost/url/params_view.hpp> 19d5c80ad9SNan Zhou #include <nlohmann/json.hpp> 20d5c80ad9SNan Zhou 21d5c80ad9SNan Zhou #include <algorithm> 22e155ab54SNan Zhou #include <array> 23e155ab54SNan Zhou #include <cctype> 247cf436c9SEd Tanous #include <charconv> 25827c4902SNan Zhou #include <compare> 26d5c80ad9SNan Zhou #include <cstdint> 27d5c80ad9SNan Zhou #include <functional> 28e155ab54SNan Zhou #include <iterator> 29d5c80ad9SNan Zhou #include <limits> 30d5c80ad9SNan Zhou #include <map> 31d5c80ad9SNan Zhou #include <memory> 32d5c80ad9SNan Zhou #include <optional> 33f4c99e70SEd Tanous #include <string> 34f4c99e70SEd Tanous #include <string_view> 35d5c80ad9SNan Zhou #include <system_error> 367cf436c9SEd Tanous #include <utility> 37f4c99e70SEd Tanous #include <vector> 38f4c99e70SEd Tanous 39d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp> 40d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> 41d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> 42e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp> 43e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp> 44e155ab54SNan Zhou // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp> 45d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h> 46d5c80ad9SNan Zhou 47f4c99e70SEd Tanous namespace redfish 48f4c99e70SEd Tanous { 49f4c99e70SEd Tanous namespace query_param 50f4c99e70SEd Tanous { 51f4c99e70SEd Tanous 527cf436c9SEd Tanous enum class ExpandType : uint8_t 537cf436c9SEd Tanous { 547cf436c9SEd Tanous None, 557cf436c9SEd Tanous Links, 567cf436c9SEd Tanous NotLinks, 577cf436c9SEd Tanous Both, 587cf436c9SEd Tanous }; 597cf436c9SEd Tanous 60827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|. 61827c4902SNan Zhou class SelectTrieNode 62827c4902SNan Zhou { 63827c4902SNan Zhou public: 64827c4902SNan Zhou SelectTrieNode() = default; 65827c4902SNan Zhou 66827c4902SNan Zhou const SelectTrieNode* find(const std::string& jsonKey) const 67827c4902SNan Zhou { 68827c4902SNan Zhou auto it = children.find(jsonKey); 69827c4902SNan Zhou if (it == children.end()) 70827c4902SNan Zhou { 71827c4902SNan Zhou return nullptr; 72827c4902SNan Zhou } 73827c4902SNan Zhou return &it->second; 74827c4902SNan Zhou } 75827c4902SNan Zhou 76827c4902SNan Zhou // Creates a new node if the key doesn't exist, returns the reference to the 77827c4902SNan Zhou // newly created node; otherwise, return the reference to the existing node 78827c4902SNan Zhou SelectTrieNode* emplace(std::string_view jsonKey) 79827c4902SNan Zhou { 80827c4902SNan Zhou auto [it, _] = children.emplace(jsonKey, SelectTrieNode{}); 81827c4902SNan Zhou return &it->second; 82827c4902SNan Zhou } 83827c4902SNan Zhou 84827c4902SNan Zhou bool empty() const 85827c4902SNan Zhou { 86827c4902SNan Zhou return children.empty(); 87827c4902SNan Zhou } 88827c4902SNan Zhou 89827c4902SNan Zhou void clear() 90827c4902SNan Zhou { 91827c4902SNan Zhou children.clear(); 92827c4902SNan Zhou } 93827c4902SNan Zhou 94827c4902SNan Zhou void setToSelected() 95827c4902SNan Zhou { 96827c4902SNan Zhou selected = true; 97827c4902SNan Zhou } 98827c4902SNan Zhou 99827c4902SNan Zhou bool isSelected() const 100827c4902SNan Zhou { 101827c4902SNan Zhou return selected; 102827c4902SNan Zhou } 103827c4902SNan Zhou 104827c4902SNan Zhou private: 105827c4902SNan Zhou std::map<std::string, SelectTrieNode, std::less<>> children; 106827c4902SNan Zhou bool selected = false; 107827c4902SNan Zhou }; 108827c4902SNan Zhou 109827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among 110827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties) 111827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property) 112827c4902SNan Zhou { 113827c4902SNan Zhou // These a magic number, but with it it's less likely that this code 114827c4902SNan Zhou // introduces CVE; e.g., too large properties crash the service. 115827c4902SNan Zhou constexpr int maxPropertyLength = 60; 116827c4902SNan Zhou if (property.empty() || property.size() > maxPropertyLength) 117827c4902SNan Zhou { 118827c4902SNan Zhou return false; 119827c4902SNan Zhou } 120827c4902SNan Zhou for (char ch : property) 121827c4902SNan Zhou { 122827c4902SNan Zhou if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' && 123827c4902SNan Zhou ch != '@' && ch != '.') 124827c4902SNan Zhou { 125827c4902SNan Zhou return false; 126827c4902SNan Zhou } 127827c4902SNan Zhou } 128827c4902SNan Zhou return true; 129827c4902SNan Zhou } 130827c4902SNan Zhou 131827c4902SNan Zhou struct SelectTrie 132827c4902SNan Zhou { 133827c4902SNan Zhou SelectTrie() = default; 134827c4902SNan Zhou 135827c4902SNan Zhou // Inserts a $select value; returns false if the nestedProperty is illegal. 136827c4902SNan Zhou bool insertNode(std::string_view nestedProperty) 137827c4902SNan Zhou { 138827c4902SNan Zhou if (nestedProperty.empty()) 139827c4902SNan Zhou { 140827c4902SNan Zhou return false; 141827c4902SNan Zhou } 142827c4902SNan Zhou SelectTrieNode* currNode = &root; 143827c4902SNan Zhou size_t index = nestedProperty.find_first_of('/'); 144827c4902SNan Zhou while (!nestedProperty.empty()) 145827c4902SNan Zhou { 146827c4902SNan Zhou std::string_view property = nestedProperty.substr(0, index); 147827c4902SNan Zhou if (!isSelectedPropertyAllowed(property)) 148827c4902SNan Zhou { 149827c4902SNan Zhou return false; 150827c4902SNan Zhou } 151827c4902SNan Zhou currNode = currNode->emplace(property); 152827c4902SNan Zhou if (index == std::string::npos) 153827c4902SNan Zhou { 154827c4902SNan Zhou break; 155827c4902SNan Zhou } 156827c4902SNan Zhou nestedProperty.remove_prefix(index + 1); 157827c4902SNan Zhou index = nestedProperty.find_first_of('/'); 158827c4902SNan Zhou } 159827c4902SNan Zhou currNode->setToSelected(); 160827c4902SNan Zhou return true; 161827c4902SNan Zhou } 162827c4902SNan Zhou 163827c4902SNan Zhou SelectTrieNode root; 164827c4902SNan Zhou }; 165827c4902SNan Zhou 166a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 167f4c99e70SEd Tanous struct Query 168f4c99e70SEd Tanous { 169a6b9125fSNan Zhou // Only 170f4c99e70SEd Tanous bool isOnly = false; 171a6b9125fSNan Zhou // Expand 172a6b9125fSNan Zhou uint8_t expandLevel = 0; 1737cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 174c937d2bfSEd Tanous 175c937d2bfSEd Tanous // Skip 1763648c8beSEd Tanous std::optional<size_t> skip = std::nullopt; 177c937d2bfSEd Tanous 178c937d2bfSEd Tanous // Top 1795143f7a5SJiaqing Zhao static constexpr size_t maxTop = 1000; // Max entries a response contain 1803648c8beSEd Tanous std::optional<size_t> top = std::nullopt; 181e155ab54SNan Zhou 182e155ab54SNan Zhou // Select 183827c4902SNan Zhou SelectTrie selectTrie = {}; 184f4c99e70SEd Tanous }; 185f4c99e70SEd Tanous 186a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 187a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 188a6b9125fSNan Zhou // the processing. 189a6b9125fSNan Zhou struct QueryCapabilities 190a6b9125fSNan Zhou { 191a6b9125fSNan Zhou bool canDelegateOnly = false; 192c937d2bfSEd Tanous bool canDelegateTop = false; 193c937d2bfSEd Tanous bool canDelegateSkip = false; 194a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 195e155ab54SNan Zhou bool canDelegateSelect = false; 196a6b9125fSNan Zhou }; 197a6b9125fSNan Zhou 198a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 199a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 200a6b9125fSNan Zhou // function will take care of it. 201a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 202a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 203a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 204a6b9125fSNan Zhou { 205a6b9125fSNan Zhou Query delegated; 206a6b9125fSNan Zhou // delegate only 207a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 208a6b9125fSNan Zhou { 209a6b9125fSNan Zhou delegated.isOnly = true; 210a6b9125fSNan Zhou query.isOnly = false; 211a6b9125fSNan Zhou } 212a6b9125fSNan Zhou // delegate expand as much as we can 213a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 214a6b9125fSNan Zhou { 215a6b9125fSNan Zhou delegated.expandType = query.expandType; 216a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 217a6b9125fSNan Zhou { 218a6b9125fSNan Zhou query.expandType = ExpandType::None; 219a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 220a6b9125fSNan Zhou query.expandLevel = 0; 221a6b9125fSNan Zhou } 222a6b9125fSNan Zhou else 223a6b9125fSNan Zhou { 224a6b9125fSNan Zhou query.expandLevel -= queryCapabilities.canDelegateExpandLevel; 225a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 226a6b9125fSNan Zhou } 227a6b9125fSNan Zhou } 228c937d2bfSEd Tanous 229c937d2bfSEd Tanous // delegate top 2303648c8beSEd Tanous if (query.top && queryCapabilities.canDelegateTop) 231c937d2bfSEd Tanous { 232c937d2bfSEd Tanous delegated.top = query.top; 2333648c8beSEd Tanous query.top = std::nullopt; 234c937d2bfSEd Tanous } 235c937d2bfSEd Tanous 236c937d2bfSEd Tanous // delegate skip 2373648c8beSEd Tanous if (query.skip && queryCapabilities.canDelegateSkip) 238c937d2bfSEd Tanous { 239c937d2bfSEd Tanous delegated.skip = query.skip; 240c937d2bfSEd Tanous query.skip = 0; 241c937d2bfSEd Tanous } 242e155ab54SNan Zhou 243e155ab54SNan Zhou // delegate select 244827c4902SNan Zhou if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect) 245e155ab54SNan Zhou { 246827c4902SNan Zhou delegated.selectTrie = std::move(query.selectTrie); 247827c4902SNan Zhou query.selectTrie.root.clear(); 248e155ab54SNan Zhou } 249a6b9125fSNan Zhou return delegated; 250a6b9125fSNan Zhou } 251a6b9125fSNan Zhou 2527cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 2537cf436c9SEd Tanous { 2547cf436c9SEd Tanous if (value.empty()) 2557cf436c9SEd Tanous { 2567cf436c9SEd Tanous return false; 2577cf436c9SEd Tanous } 2587cf436c9SEd Tanous switch (value[0]) 2597cf436c9SEd Tanous { 2607cf436c9SEd Tanous case '*': 2617cf436c9SEd Tanous query.expandType = ExpandType::Both; 2627cf436c9SEd Tanous break; 2637cf436c9SEd Tanous case '.': 2647cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 2657cf436c9SEd Tanous break; 2667cf436c9SEd Tanous case '~': 2677cf436c9SEd Tanous query.expandType = ExpandType::Links; 2687cf436c9SEd Tanous break; 2697cf436c9SEd Tanous default: 2707cf436c9SEd Tanous return false; 2717cf436c9SEd Tanous 2727cf436c9SEd Tanous break; 2737cf436c9SEd Tanous } 2747cf436c9SEd Tanous value.remove_prefix(1); 2757cf436c9SEd Tanous if (value.empty()) 2767cf436c9SEd Tanous { 2777cf436c9SEd Tanous query.expandLevel = 1; 2787cf436c9SEd Tanous return true; 2797cf436c9SEd Tanous } 2807cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 2817cf436c9SEd Tanous if (!value.starts_with(levels)) 2827cf436c9SEd Tanous { 2837cf436c9SEd Tanous return false; 2847cf436c9SEd Tanous } 2857cf436c9SEd Tanous value.remove_prefix(levels.size()); 2867cf436c9SEd Tanous 287*2bd4ab43SPatrick Williams auto it = std::from_chars(value.begin(), value.end(), query.expandLevel); 2887cf436c9SEd Tanous if (it.ec != std::errc()) 2897cf436c9SEd Tanous { 2907cf436c9SEd Tanous return false; 2917cf436c9SEd Tanous } 292*2bd4ab43SPatrick Williams value.remove_prefix( 293*2bd4ab43SPatrick Williams static_cast<size_t>(std::distance(value.begin(), it.ptr))); 2947cf436c9SEd Tanous return value == ")"; 2957cf436c9SEd Tanous } 2967cf436c9SEd Tanous 297c937d2bfSEd Tanous enum class QueryError 298c937d2bfSEd Tanous { 299c937d2bfSEd Tanous Ok, 300c937d2bfSEd Tanous OutOfRange, 301c937d2bfSEd Tanous ValueFormat, 302c937d2bfSEd Tanous }; 303c937d2bfSEd Tanous 304c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 305c937d2bfSEd Tanous { 306*2bd4ab43SPatrick Williams std::from_chars_result r = std::from_chars(value.begin(), value.end(), 307*2bd4ab43SPatrick Williams param); 308c937d2bfSEd Tanous 309c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 310c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 311c937d2bfSEd Tanous { 312c937d2bfSEd Tanous return QueryError::OutOfRange; 313c937d2bfSEd Tanous } 314c937d2bfSEd Tanous // All other errors are value format 315c937d2bfSEd Tanous if (r.ec != std::errc()) 316c937d2bfSEd Tanous { 317c937d2bfSEd Tanous return QueryError::ValueFormat; 318c937d2bfSEd Tanous } 319c937d2bfSEd Tanous return QueryError::Ok; 320c937d2bfSEd Tanous } 321c937d2bfSEd Tanous 322c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 323c937d2bfSEd Tanous { 3243648c8beSEd Tanous return getNumericParam(value, query.skip.emplace()); 325c937d2bfSEd Tanous } 326c937d2bfSEd Tanous 327c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 328c937d2bfSEd Tanous { 3293648c8beSEd Tanous QueryError ret = getNumericParam(value, query.top.emplace()); 330c937d2bfSEd Tanous if (ret != QueryError::Ok) 331c937d2bfSEd Tanous { 332c937d2bfSEd Tanous return ret; 333c937d2bfSEd Tanous } 334c937d2bfSEd Tanous 335c937d2bfSEd Tanous // Range check for sanity. 3365143f7a5SJiaqing Zhao if (query.top > Query::maxTop) 337c937d2bfSEd Tanous { 338c937d2bfSEd Tanous return QueryError::OutOfRange; 339c937d2bfSEd Tanous } 340c937d2bfSEd Tanous 341c937d2bfSEd Tanous return QueryError::Ok; 342c937d2bfSEd Tanous } 343c937d2bfSEd Tanous 344e155ab54SNan Zhou // Parses and validates the $select parameter. 345e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be 346e155ab54SNan Zhou // comma separated Resource Path 347e155ab54SNan Zhou // Ref: 348e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 349e155ab54SNan Zhou // 2. 350e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 351e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query) 352e155ab54SNan Zhou { 353e155ab54SNan Zhou std::vector<std::string> properties; 35450ebd4afSEd Tanous bmcweb::split(properties, value, ','); 355e155ab54SNan Zhou if (properties.empty()) 356e155ab54SNan Zhou { 357e155ab54SNan Zhou return false; 358e155ab54SNan Zhou } 359e155ab54SNan Zhou // These a magic number, but with it it's less likely that this code 360e155ab54SNan Zhou // introduces CVE; e.g., too large properties crash the service. 361e155ab54SNan Zhou constexpr int maxNumProperties = 10; 362e155ab54SNan Zhou if (properties.size() > maxNumProperties) 363e155ab54SNan Zhou { 364e155ab54SNan Zhou return false; 365e155ab54SNan Zhou } 366827c4902SNan Zhou for (const auto& property : properties) 367e155ab54SNan Zhou { 368827c4902SNan Zhou if (!query.selectTrie.insertNode(property)) 369e155ab54SNan Zhou { 370e155ab54SNan Zhou return false; 371e155ab54SNan Zhou } 372e155ab54SNan Zhou } 373e155ab54SNan Zhou return true; 374e155ab54SNan Zhou } 375e155ab54SNan Zhou 376079360aeSEd Tanous inline std::optional<Query> parseParameters(boost::urls::params_view urlParams, 377f4c99e70SEd Tanous crow::Response& res) 378f4c99e70SEd Tanous { 379f4c99e70SEd Tanous Query ret; 380f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 381f4c99e70SEd Tanous { 382079360aeSEd Tanous if (it.key == "only") 383f4c99e70SEd Tanous { 384f4c99e70SEd Tanous if (!it.value.empty()) 385f4c99e70SEd Tanous { 386079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 387f4c99e70SEd Tanous return std::nullopt; 388f4c99e70SEd Tanous } 389f4c99e70SEd Tanous ret.isOnly = true; 390f4c99e70SEd Tanous } 391079360aeSEd Tanous else if (it.key == "$expand" && bmcwebInsecureEnableQueryParams) 3927cf436c9SEd Tanous { 393079360aeSEd Tanous if (!getExpandType(it.value, ret)) 3947cf436c9SEd Tanous { 395079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 3967cf436c9SEd Tanous return std::nullopt; 397f4c99e70SEd Tanous } 3987cf436c9SEd Tanous } 399079360aeSEd Tanous else if (it.key == "$top") 400c937d2bfSEd Tanous { 401079360aeSEd Tanous QueryError topRet = getTopParam(it.value, ret); 402c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 403c937d2bfSEd Tanous { 404079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 405c937d2bfSEd Tanous return std::nullopt; 406c937d2bfSEd Tanous } 407c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 408c937d2bfSEd Tanous { 409c937d2bfSEd Tanous messages::queryParameterOutOfRange( 410079360aeSEd Tanous res, it.value, "$top", 411079360aeSEd Tanous "0-" + std::to_string(Query::maxTop)); 412c937d2bfSEd Tanous return std::nullopt; 413c937d2bfSEd Tanous } 414c937d2bfSEd Tanous } 415079360aeSEd Tanous else if (it.key == "$skip") 416c937d2bfSEd Tanous { 417079360aeSEd Tanous QueryError topRet = getSkipParam(it.value, ret); 418c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 419c937d2bfSEd Tanous { 420079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 421c937d2bfSEd Tanous return std::nullopt; 422c937d2bfSEd Tanous } 423c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 424c937d2bfSEd Tanous { 425c937d2bfSEd Tanous messages::queryParameterOutOfRange( 426079360aeSEd Tanous res, it.value, it.key, 427a926c53eSJiaqing Zhao "0-" + std::to_string(std::numeric_limits<size_t>::max())); 428c937d2bfSEd Tanous return std::nullopt; 429c937d2bfSEd Tanous } 430c937d2bfSEd Tanous } 431079360aeSEd Tanous else if (it.key == "$select") 432e155ab54SNan Zhou { 433079360aeSEd Tanous if (!getSelectParam(it.value, ret)) 434e155ab54SNan Zhou { 435079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 436e155ab54SNan Zhou return std::nullopt; 437e155ab54SNan Zhou } 438e155ab54SNan Zhou } 4397cf436c9SEd Tanous else 4407cf436c9SEd Tanous { 4417cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 442079360aeSEd Tanous if (it.key.starts_with("$")) 4437cf436c9SEd Tanous { 4447cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 4457cf436c9SEd Tanous // status code for any unsupported query parameters that 4467cf436c9SEd Tanous // start with $ . 447079360aeSEd Tanous messages::queryParameterValueFormatError(res, it.value, it.key); 4487cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 4497cf436c9SEd Tanous return std::nullopt; 4507cf436c9SEd Tanous } 4517cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 4527cf436c9SEd Tanous // not begin with $ ." 4537cf436c9SEd Tanous } 4547cf436c9SEd Tanous } 4557cf436c9SEd Tanous 456827c4902SNan Zhou if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty()) 457e155ab54SNan Zhou { 458e155ab54SNan Zhou messages::queryCombinationInvalid(res); 459e155ab54SNan Zhou return std::nullopt; 460e155ab54SNan Zhou } 461e155ab54SNan Zhou 462f4c99e70SEd Tanous return ret; 463f4c99e70SEd Tanous } 464f4c99e70SEd Tanous 465f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 466f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 467f4c99e70SEd Tanous { 468f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing only query param"; 469f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 470f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 471f4c99e70SEd Tanous { 472f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 473f4c99e70SEd Tanous completionHandler(res); 474f4c99e70SEd Tanous return false; 475f4c99e70SEd Tanous } 476f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 477f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 478f4c99e70SEd Tanous { 479f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 480f4c99e70SEd Tanous << " element, returning full collection."; 481f4c99e70SEd Tanous completionHandler(res); 482f4c99e70SEd Tanous return false; 483f4c99e70SEd Tanous } 484f4c99e70SEd Tanous 485f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 486f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 487f4c99e70SEd Tanous { 488f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "No found odata.id"; 489f4c99e70SEd Tanous messages::internalError(res); 490f4c99e70SEd Tanous completionHandler(res); 491f4c99e70SEd Tanous return false; 492f4c99e70SEd Tanous } 493f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 494f4c99e70SEd Tanous if (url == nullptr) 495f4c99e70SEd Tanous { 496f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 497f4c99e70SEd Tanous messages::internalError(res); 498f4c99e70SEd Tanous completionHandler(res); 499f4c99e70SEd Tanous return false; 500f4c99e70SEd Tanous } 501f4c99e70SEd Tanous // TODO(Ed) copy request headers? 502f4c99e70SEd Tanous // newReq.session = req.session; 503f4c99e70SEd Tanous std::error_code ec; 504f4c99e70SEd Tanous crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 505f4c99e70SEd Tanous if (ec) 506f4c99e70SEd Tanous { 507f4c99e70SEd Tanous messages::internalError(res); 508f4c99e70SEd Tanous completionHandler(res); 509f4c99e70SEd Tanous return false; 510f4c99e70SEd Tanous } 511f4c99e70SEd Tanous 512f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 513f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 514f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 515f4c99e70SEd Tanous asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 516f4c99e70SEd Tanous app.handle(newReq, asyncResp); 517f4c99e70SEd Tanous return true; 518f4c99e70SEd Tanous } 519f4c99e70SEd Tanous 5207cf436c9SEd Tanous struct ExpandNode 5217cf436c9SEd Tanous { 5227cf436c9SEd Tanous nlohmann::json::json_pointer location; 5237cf436c9SEd Tanous std::string uri; 5247cf436c9SEd Tanous 5257cf436c9SEd Tanous inline bool operator==(const ExpandNode& other) const 5267cf436c9SEd Tanous { 5277cf436c9SEd Tanous return location == other.location && uri == other.uri; 5287cf436c9SEd Tanous } 5297cf436c9SEd Tanous }; 5307cf436c9SEd Tanous 5317cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 5327cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 5337cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 5347cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 5357cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 5367cf436c9SEd Tanous // with the keys from the jsonResponse object 5377cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 5387cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 539ad595fa6SEd Tanous const nlohmann::json::json_pointer& p, int depth, bool inLinks, 5407cf436c9SEd Tanous std::vector<ExpandNode>& out) 5417cf436c9SEd Tanous { 5427cf436c9SEd Tanous // If no expand is needed, return early 5437cf436c9SEd Tanous if (eType == ExpandType::None) 5447cf436c9SEd Tanous { 5457cf436c9SEd Tanous return; 5467cf436c9SEd Tanous } 547ad595fa6SEd Tanous 5487cf436c9SEd Tanous nlohmann::json::array_t* array = 5497cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 5507cf436c9SEd Tanous if (array != nullptr) 5517cf436c9SEd Tanous { 5527cf436c9SEd Tanous size_t index = 0; 5537cf436c9SEd Tanous // For arrays, walk every element in the array 5547cf436c9SEd Tanous for (auto& element : *array) 5557cf436c9SEd Tanous { 5567cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / index; 5577cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 558ad595fa6SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, depth, 559ad595fa6SEd Tanous inLinks, out); 5607cf436c9SEd Tanous index++; 5617cf436c9SEd Tanous } 5627cf436c9SEd Tanous } 5637cf436c9SEd Tanous nlohmann::json::object_t* obj = 5647cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 5657cf436c9SEd Tanous if (obj == nullptr) 5667cf436c9SEd Tanous { 5677cf436c9SEd Tanous return; 5687cf436c9SEd Tanous } 5697cf436c9SEd Tanous // Navigation References only ever have a single element 5707cf436c9SEd Tanous if (obj->size() == 1) 5717cf436c9SEd Tanous { 5727cf436c9SEd Tanous if (obj->begin()->first == "@odata.id") 5737cf436c9SEd Tanous { 5747cf436c9SEd Tanous const std::string* uri = 5757cf436c9SEd Tanous obj->begin()->second.get_ptr<const std::string*>(); 5767cf436c9SEd Tanous if (uri != nullptr) 5777cf436c9SEd Tanous { 578ad595fa6SEd Tanous BMCWEB_LOG_DEBUG << "Found " << *uri << " at " << p.to_string(); 5797cf436c9SEd Tanous out.push_back({p, *uri}); 580ad595fa6SEd Tanous return; 5817cf436c9SEd Tanous } 5827cf436c9SEd Tanous } 5837cf436c9SEd Tanous } 584ad595fa6SEd Tanous 585ad595fa6SEd Tanous int newDepth = depth; 586ad595fa6SEd Tanous auto odataId = obj->find("@odata.id"); 587ad595fa6SEd Tanous if (odataId != obj->end()) 588ad595fa6SEd Tanous { 589ad595fa6SEd Tanous // The Redfish spec requires all resources to include the resource 590ad595fa6SEd Tanous // identifier. If the object has multiple elements and one of them is 591ad595fa6SEd Tanous // "@odata.id" then that means we have entered a new level / expanded 592ad595fa6SEd Tanous // resource. We need to stop traversing if we're already at the desired 593ad595fa6SEd Tanous // depth 594ad595fa6SEd Tanous if ((obj->size() > 1) && (depth == 0)) 595ad595fa6SEd Tanous { 596ad595fa6SEd Tanous return; 597ad595fa6SEd Tanous } 598ad595fa6SEd Tanous newDepth--; 599ad595fa6SEd Tanous } 600ad595fa6SEd Tanous 6017cf436c9SEd Tanous // Loop the object and look for links 6027cf436c9SEd Tanous for (auto& element : *obj) 6037cf436c9SEd Tanous { 604e479ad58SNan Zhou bool localInLinks = inLinks; 605e479ad58SNan Zhou if (!localInLinks) 6067cf436c9SEd Tanous { 6077cf436c9SEd Tanous // Check if this is a links node 608e479ad58SNan Zhou localInLinks = element.first == "Links"; 6097cf436c9SEd Tanous } 6107cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 6117cf436c9SEd Tanous // Per section 7.3 of the redfish specification 612e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 6137cf436c9SEd Tanous { 6147cf436c9SEd Tanous continue; 6157cf436c9SEd Tanous } 616e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 6177cf436c9SEd Tanous { 6187cf436c9SEd Tanous continue; 6197cf436c9SEd Tanous } 6207cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / element.first; 6217cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 6227cf436c9SEd Tanous 6237cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 624ad595fa6SEd Tanous newDepth, localInLinks, out); 6257cf436c9SEd Tanous } 6267cf436c9SEd Tanous } 6277cf436c9SEd Tanous 628ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded 629ad595fa6SEd Tanous // response we may need need additional handling when the original URI was 630ad595fa6SEd Tanous // up tree from a top level collection. 631ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556 632ad595fa6SEd Tanous // lands. May want to avoid forwarding query params when request is uptree from 633ad595fa6SEd Tanous // a top level collection. 6347cf436c9SEd Tanous inline std::vector<ExpandNode> 635ad595fa6SEd Tanous findNavigationReferences(ExpandType eType, int depth, 636ad595fa6SEd Tanous nlohmann::json& jsonResponse) 6377cf436c9SEd Tanous { 6387cf436c9SEd Tanous std::vector<ExpandNode> ret; 63972c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 640ad595fa6SEd Tanous findNavigationReferencesRecursive(eType, jsonResponse, root, depth, false, 641ad595fa6SEd Tanous ret); 6427cf436c9SEd Tanous return ret; 6437cf436c9SEd Tanous } 6447cf436c9SEd Tanous 64572c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 646b66cf2a2SNan Zhou // Returns std::nullopt on failures. 64772c3ae33SNan Zhou // This function shall handle $select when it is added. 64872c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand, 64972c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 650b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query) 65172c3ae33SNan Zhou { 65272c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 65372c3ae33SNan Zhou if (query.expandLevel <= 1) 65472c3ae33SNan Zhou { 655b66cf2a2SNan Zhou return ""; 65672c3ae33SNan Zhou } 65772c3ae33SNan Zhou std::string str = "?$expand="; 658b66cf2a2SNan Zhou bool queryTypeExpected = false; 65972c3ae33SNan Zhou switch (query.expandType) 66072c3ae33SNan Zhou { 66172c3ae33SNan Zhou case ExpandType::None: 662b66cf2a2SNan Zhou return ""; 66372c3ae33SNan Zhou case ExpandType::Links: 664b66cf2a2SNan Zhou queryTypeExpected = true; 66572c3ae33SNan Zhou str += '~'; 66672c3ae33SNan Zhou break; 66772c3ae33SNan Zhou case ExpandType::NotLinks: 668b66cf2a2SNan Zhou queryTypeExpected = true; 66972c3ae33SNan Zhou str += '.'; 67072c3ae33SNan Zhou break; 67172c3ae33SNan Zhou case ExpandType::Both: 672b66cf2a2SNan Zhou queryTypeExpected = true; 67372c3ae33SNan Zhou str += '*'; 67472c3ae33SNan Zhou break; 675b66cf2a2SNan Zhou } 676b66cf2a2SNan Zhou if (!queryTypeExpected) 677b66cf2a2SNan Zhou { 678b66cf2a2SNan Zhou return std::nullopt; 67972c3ae33SNan Zhou } 68072c3ae33SNan Zhou str += "($levels="; 68172c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 68272c3ae33SNan Zhou str += ')'; 68372c3ae33SNan Zhou return str; 68472c3ae33SNan Zhou } 68572c3ae33SNan Zhou 6863590bd1dSNan Zhou // Propogates the worst error code to the final response. 6873590bd1dSNan Zhou // The order of error code is (from high to low) 6883590bd1dSNan Zhou // 500 Internal Server Error 6893590bd1dSNan Zhou // 511 Network Authentication Required 6903590bd1dSNan Zhou // 510 Not Extended 6913590bd1dSNan Zhou // 508 Loop Detected 6923590bd1dSNan Zhou // 507 Insufficient Storage 6933590bd1dSNan Zhou // 506 Variant Also Negotiates 6943590bd1dSNan Zhou // 505 HTTP Version Not Supported 6953590bd1dSNan Zhou // 504 Gateway Timeout 6963590bd1dSNan Zhou // 503 Service Unavailable 6973590bd1dSNan Zhou // 502 Bad Gateway 6983590bd1dSNan Zhou // 501 Not Implemented 6993590bd1dSNan Zhou // 401 Unauthorized 7003590bd1dSNan Zhou // 451 - 409 Error codes (not listed explictly) 7013590bd1dSNan Zhou // 408 Request Timeout 7023590bd1dSNan Zhou // 407 Proxy Authentication Required 7033590bd1dSNan Zhou // 406 Not Acceptable 7043590bd1dSNan Zhou // 405 Method Not Allowed 7053590bd1dSNan Zhou // 404 Not Found 7063590bd1dSNan Zhou // 403 Forbidden 7073590bd1dSNan Zhou // 402 Payment Required 7083590bd1dSNan Zhou // 400 Bad Request 7093590bd1dSNan Zhou inline unsigned propogateErrorCode(unsigned finalCode, unsigned subResponseCode) 7103590bd1dSNan Zhou { 7113590bd1dSNan Zhou // We keep a explicit list for error codes that this project often uses 7123590bd1dSNan Zhou // Higer priority codes are in lower indexes 7133590bd1dSNan Zhou constexpr std::array<unsigned, 13> orderedCodes = { 7143590bd1dSNan Zhou 500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400}; 7153590bd1dSNan Zhou size_t finalCodeIndex = std::numeric_limits<size_t>::max(); 7163590bd1dSNan Zhou size_t subResponseCodeIndex = std::numeric_limits<size_t>::max(); 7173590bd1dSNan Zhou for (size_t i = 0; i < orderedCodes.size(); ++i) 7183590bd1dSNan Zhou { 7193590bd1dSNan Zhou if (orderedCodes[i] == finalCode) 7203590bd1dSNan Zhou { 7213590bd1dSNan Zhou finalCodeIndex = i; 7223590bd1dSNan Zhou } 7233590bd1dSNan Zhou if (orderedCodes[i] == subResponseCode) 7243590bd1dSNan Zhou { 7253590bd1dSNan Zhou subResponseCodeIndex = i; 7263590bd1dSNan Zhou } 7273590bd1dSNan Zhou } 7283590bd1dSNan Zhou if (finalCodeIndex != std::numeric_limits<size_t>::max() && 7293590bd1dSNan Zhou subResponseCodeIndex != std::numeric_limits<size_t>::max()) 7303590bd1dSNan Zhou { 7313590bd1dSNan Zhou return finalCodeIndex <= subResponseCodeIndex ? finalCode 7323590bd1dSNan Zhou : subResponseCode; 7333590bd1dSNan Zhou } 7343590bd1dSNan Zhou if (subResponseCode == 500 || finalCode == 500) 7353590bd1dSNan Zhou { 7363590bd1dSNan Zhou return 500; 7373590bd1dSNan Zhou } 7383590bd1dSNan Zhou if (subResponseCode > 500 || finalCode > 500) 7393590bd1dSNan Zhou { 7403590bd1dSNan Zhou return std::max(finalCode, subResponseCode); 7413590bd1dSNan Zhou } 7423590bd1dSNan Zhou if (subResponseCode == 401) 7433590bd1dSNan Zhou { 7443590bd1dSNan Zhou return subResponseCode; 7453590bd1dSNan Zhou } 7463590bd1dSNan Zhou return std::max(finalCode, subResponseCode); 7473590bd1dSNan Zhou } 7483590bd1dSNan Zhou 7493590bd1dSNan Zhou // Propogates all error messages into |finalResponse| 7503590bd1dSNan Zhou inline void propogateError(crow::Response& finalResponse, 7513590bd1dSNan Zhou crow::Response& subResponse) 7523590bd1dSNan Zhou { 7533590bd1dSNan Zhou // no errors 7543590bd1dSNan Zhou if (subResponse.resultInt() >= 200 && subResponse.resultInt() < 400) 7553590bd1dSNan Zhou { 7563590bd1dSNan Zhou return; 7573590bd1dSNan Zhou } 7583590bd1dSNan Zhou messages::moveErrorsToErrorJson(finalResponse.jsonValue, 7593590bd1dSNan Zhou subResponse.jsonValue); 7603590bd1dSNan Zhou finalResponse.result( 7613590bd1dSNan Zhou propogateErrorCode(finalResponse.resultInt(), subResponse.resultInt())); 7623590bd1dSNan Zhou } 7633590bd1dSNan Zhou 7647cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 7657cf436c9SEd Tanous { 7667cf436c9SEd Tanous public: 7677cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 7687cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 7697cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 7707cf436c9SEd Tanous // class manages the final "merge" of the json resources. 7718a592810SEd Tanous MultiAsyncResp(crow::App& appIn, 7727cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 7738a592810SEd Tanous app(appIn), 7747cf436c9SEd Tanous finalRes(std::move(finalResIn)) 7757cf436c9SEd Tanous {} 7767cf436c9SEd Tanous 7777cf436c9SEd Tanous void addAwaitingResponse( 77802cad96eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& res, 7797cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 7807cf436c9SEd Tanous { 7817cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 78272c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 7837cf436c9SEd Tanous } 7847cf436c9SEd Tanous 78572c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 7867cf436c9SEd Tanous crow::Response& res) 7877cf436c9SEd Tanous { 7883590bd1dSNan Zhou BMCWEB_LOG_DEBUG << "placeResult for " << locationToPlace; 7893590bd1dSNan Zhou propogateError(finalRes->res, res); 7903590bd1dSNan Zhou if (!res.jsonValue.is_object() || res.jsonValue.empty()) 7913590bd1dSNan Zhou { 7923590bd1dSNan Zhou return; 7933590bd1dSNan Zhou } 7947cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 7957cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 7967cf436c9SEd Tanous } 7977cf436c9SEd Tanous 79872c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 79972c3ae33SNan Zhou // for deeper levels. 80072c3ae33SNan Zhou void startQuery(const Query& query) 80172c3ae33SNan Zhou { 802ad595fa6SEd Tanous std::vector<ExpandNode> nodes = findNavigationReferences( 803ad595fa6SEd Tanous query.expandType, query.expandLevel, finalRes->res.jsonValue); 8047cf436c9SEd Tanous BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 805b66cf2a2SNan Zhou const std::optional<std::string> queryStr = formatQueryForExpand(query); 806b66cf2a2SNan Zhou if (!queryStr) 807b66cf2a2SNan Zhou { 808b66cf2a2SNan Zhou messages::internalError(finalRes->res); 809b66cf2a2SNan Zhou return; 810b66cf2a2SNan Zhou } 8117cf436c9SEd Tanous for (const ExpandNode& node : nodes) 8127cf436c9SEd Tanous { 813b66cf2a2SNan Zhou const std::string subQuery = node.uri + *queryStr; 81472c3ae33SNan Zhou BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 8157cf436c9SEd Tanous std::error_code ec; 81672c3ae33SNan Zhou crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 8177cf436c9SEd Tanous ec); 8187cf436c9SEd Tanous if (ec) 8197cf436c9SEd Tanous { 82072c3ae33SNan Zhou messages::internalError(finalRes->res); 8217cf436c9SEd Tanous return; 8227cf436c9SEd Tanous } 8237cf436c9SEd Tanous 8247cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 8257cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " 8267cf436c9SEd Tanous << &asyncResp->res; 82772c3ae33SNan Zhou 82872c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 8297cf436c9SEd Tanous app.handle(newReq, asyncResp); 8307cf436c9SEd Tanous } 8317cf436c9SEd Tanous } 8327cf436c9SEd Tanous 8337cf436c9SEd Tanous private: 83472c3ae33SNan Zhou static void 83572c3ae33SNan Zhou placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 8367cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 8377cf436c9SEd Tanous crow::Response& res) 8387cf436c9SEd Tanous { 83972c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 8407cf436c9SEd Tanous } 8417cf436c9SEd Tanous 8427cf436c9SEd Tanous crow::App& app; 8437cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 8447cf436c9SEd Tanous }; 8457cf436c9SEd Tanous 8462a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 8472a68dc80SEd Tanous { 8483648c8beSEd Tanous if (!query.skip && !query.top) 8493648c8beSEd Tanous { 8503648c8beSEd Tanous // No work to do. 8513648c8beSEd Tanous return; 8523648c8beSEd Tanous } 8532a68dc80SEd Tanous nlohmann::json::object_t* obj = 8542a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 8552a68dc80SEd Tanous if (obj == nullptr) 8562a68dc80SEd Tanous { 8572a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 8582a68dc80SEd Tanous messages::internalError(res); 8592a68dc80SEd Tanous return; 8602a68dc80SEd Tanous } 8612a68dc80SEd Tanous 8622a68dc80SEd Tanous BMCWEB_LOG_DEBUG << "Handling top/skip"; 8632a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 8642a68dc80SEd Tanous if (members == obj->end()) 8652a68dc80SEd Tanous { 8662a68dc80SEd Tanous // From the Redfish specification 7.3.1 8672a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 8682a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 8692a68dc80SEd Tanous // for any supported query parameters that apply only to resource 8702a68dc80SEd Tanous // collections but are used on singular resources. 8712a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 8722a68dc80SEd Tanous return; 8732a68dc80SEd Tanous } 8742a68dc80SEd Tanous 8752a68dc80SEd Tanous nlohmann::json::array_t* arr = 8762a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 8772a68dc80SEd Tanous if (arr == nullptr) 8782a68dc80SEd Tanous { 8792a68dc80SEd Tanous messages::internalError(res); 8802a68dc80SEd Tanous return; 8812a68dc80SEd Tanous } 8822a68dc80SEd Tanous 8833648c8beSEd Tanous if (query.skip) 8843648c8beSEd Tanous { 8853648c8beSEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before 8863648c8beSEd Tanous // $top Can only skip as many values as we have 8873648c8beSEd Tanous size_t skip = std::min(arr->size(), *query.skip); 8882a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 8893648c8beSEd Tanous } 8903648c8beSEd Tanous if (query.top) 8913648c8beSEd Tanous { 8923648c8beSEd Tanous size_t top = std::min(arr->size(), *query.top); 8932a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 8942a68dc80SEd Tanous } 8953648c8beSEd Tanous } 8962a68dc80SEd Tanous 897827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are 898827c4902SNan Zhou // not in the |currNode| Trie node. 899827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot, 900827c4902SNan Zhou const SelectTrieNode& currNode) 901e155ab54SNan Zhou { 902e155ab54SNan Zhou nlohmann::json::object_t* object = 903e155ab54SNan Zhou currRoot.get_ptr<nlohmann::json::object_t*>(); 904e155ab54SNan Zhou if (object != nullptr) 905e155ab54SNan Zhou { 906827c4902SNan Zhou BMCWEB_LOG_DEBUG << "Current JSON is an object"; 907e155ab54SNan Zhou auto it = currRoot.begin(); 908e155ab54SNan Zhou while (it != currRoot.end()) 909e155ab54SNan Zhou { 910e155ab54SNan Zhou auto nextIt = std::next(it); 911827c4902SNan Zhou BMCWEB_LOG_DEBUG << "key=" << it.key(); 912827c4902SNan Zhou const SelectTrieNode* nextNode = currNode.find(it.key()); 9135c9fb2d6SNan Zhou // Per the Redfish spec section 7.3.3, the service shall select 9145c9fb2d6SNan Zhou // certain properties as if $select was omitted. This applies to 9155c9fb2d6SNan Zhou // every TrieNode that contains leaves and the root. 9165c9fb2d6SNan Zhou constexpr std::array<std::string_view, 5> reservedProperties = { 9175c9fb2d6SNan Zhou "@odata.id", "@odata.type", "@odata.context", "@odata.etag", 9185c9fb2d6SNan Zhou "error"}; 91989492a15SPatrick Williams bool reserved = std::find(reservedProperties.begin(), 92089492a15SPatrick Williams reservedProperties.end(), 9215c9fb2d6SNan Zhou it.key()) != reservedProperties.end(); 9225c9fb2d6SNan Zhou if (reserved || (nextNode != nullptr && nextNode->isSelected())) 923e155ab54SNan Zhou { 924e155ab54SNan Zhou it = nextIt; 925e155ab54SNan Zhou continue; 926e155ab54SNan Zhou } 927827c4902SNan Zhou if (nextNode != nullptr) 928e155ab54SNan Zhou { 929827c4902SNan Zhou BMCWEB_LOG_DEBUG << "Recursively select: " << it.key(); 930827c4902SNan Zhou recursiveSelect(*it, *nextNode); 931e155ab54SNan Zhou it = nextIt; 932e155ab54SNan Zhou continue; 933e155ab54SNan Zhou } 934827c4902SNan Zhou BMCWEB_LOG_DEBUG << it.key() << " is getting removed!"; 935e155ab54SNan Zhou it = currRoot.erase(it); 936e155ab54SNan Zhou } 937e155ab54SNan Zhou } 9385c9fb2d6SNan Zhou nlohmann::json::array_t* array = 9395c9fb2d6SNan Zhou currRoot.get_ptr<nlohmann::json::array_t*>(); 9405c9fb2d6SNan Zhou if (array != nullptr) 9415c9fb2d6SNan Zhou { 9425c9fb2d6SNan Zhou BMCWEB_LOG_DEBUG << "Current JSON is an array"; 9435c9fb2d6SNan Zhou // Array index is omitted, so reuse the same Trie node 9445c9fb2d6SNan Zhou for (nlohmann::json& nextRoot : *array) 9455c9fb2d6SNan Zhou { 9465c9fb2d6SNan Zhou recursiveSelect(nextRoot, currNode); 9475c9fb2d6SNan Zhou } 9485c9fb2d6SNan Zhou } 949e155ab54SNan Zhou } 950e155ab54SNan Zhou 951e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to 952e155ab54SNan Zhou // ambiguity and/or complexity. 9535c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 954e155ab54SNan Zhou // created for clarification. 9555c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 956e155ab54SNan Zhou // etc. 957e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse, 958827c4902SNan Zhou const SelectTrieNode& trieRoot) 959e155ab54SNan Zhou { 960e155ab54SNan Zhou BMCWEB_LOG_DEBUG << "Process $select quary parameter"; 961827c4902SNan Zhou recursiveSelect(intermediateResponse.jsonValue, trieRoot); 962e155ab54SNan Zhou } 963e155ab54SNan Zhou 9647cf436c9SEd Tanous inline void 965593f6449SNan Zhou processAllParams(crow::App& app, const Query& query, 9667cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 9677cf436c9SEd Tanous crow::Response& intermediateResponse) 968f4c99e70SEd Tanous { 969f4c99e70SEd Tanous if (!completionHandler) 970f4c99e70SEd Tanous { 971f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Function was invalid?"; 972f4c99e70SEd Tanous return; 973f4c99e70SEd Tanous } 974f4c99e70SEd Tanous 975f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing query params"; 976f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 977f4c99e70SEd Tanous // params. 978f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 979f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 980f4c99e70SEd Tanous { 981f4c99e70SEd Tanous completionHandler(intermediateResponse); 982f4c99e70SEd Tanous return; 983f4c99e70SEd Tanous } 984f4c99e70SEd Tanous if (query.isOnly) 985f4c99e70SEd Tanous { 986f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 987f4c99e70SEd Tanous return; 988f4c99e70SEd Tanous } 9892a68dc80SEd Tanous 9903648c8beSEd Tanous if (query.top || query.skip) 9912a68dc80SEd Tanous { 9922a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 9932a68dc80SEd Tanous } 9942a68dc80SEd Tanous 9957cf436c9SEd Tanous if (query.expandType != ExpandType::None) 9967cf436c9SEd Tanous { 9977cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Executing expand query"; 99813548d85SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>( 99913548d85SEd Tanous std::move(intermediateResponse)); 10007cf436c9SEd Tanous 100113548d85SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 100213548d85SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 100372c3ae33SNan Zhou multi->startQuery(query); 10047cf436c9SEd Tanous return; 10057cf436c9SEd Tanous } 1006e155ab54SNan Zhou 1007e155ab54SNan Zhou // According to Redfish Spec Section 7.3.1, $select is the last parameter to 1008e155ab54SNan Zhou // to process 1009827c4902SNan Zhou if (!query.selectTrie.root.empty()) 1010e155ab54SNan Zhou { 1011827c4902SNan Zhou processSelect(intermediateResponse, query.selectTrie.root); 1012e155ab54SNan Zhou } 1013e155ab54SNan Zhou 1014f4c99e70SEd Tanous completionHandler(intermediateResponse); 1015f4c99e70SEd Tanous } 1016f4c99e70SEd Tanous 1017f4c99e70SEd Tanous } // namespace query_param 1018f4c99e70SEd Tanous } // namespace redfish 1019