1f4c99e70SEd Tanous #pragma once 2*d5c80ad9SNan Zhou #include "bmcweb_config.h" 3*d5c80ad9SNan 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" 9*d5c80ad9SNan Zhou #include "logging.hpp" 10f4c99e70SEd Tanous 11*d5c80ad9SNan Zhou #include <sys/types.h> 12*d5c80ad9SNan Zhou 13*d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep 14*d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp> 15*d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp> 16*d5c80ad9SNan Zhou #include <boost/url/params_view.hpp> 17*d5c80ad9SNan Zhou #include <boost/url/string.hpp> 18*d5c80ad9SNan Zhou #include <nlohmann/json.hpp> 19*d5c80ad9SNan Zhou 20*d5c80ad9SNan Zhou #include <algorithm> 217cf436c9SEd Tanous #include <charconv> 22*d5c80ad9SNan Zhou #include <cstdint> 23*d5c80ad9SNan Zhou #include <functional> 24*d5c80ad9SNan Zhou #include <limits> 25*d5c80ad9SNan Zhou #include <map> 26*d5c80ad9SNan Zhou #include <memory> 27*d5c80ad9SNan Zhou #include <optional> 28f4c99e70SEd Tanous #include <string> 29f4c99e70SEd Tanous #include <string_view> 30*d5c80ad9SNan Zhou #include <system_error> 317cf436c9SEd Tanous #include <utility> 32f4c99e70SEd Tanous #include <vector> 33f4c99e70SEd Tanous 34*d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp> 35*d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> 36*d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> 37*d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h> 38*d5c80ad9SNan Zhou 39f4c99e70SEd Tanous namespace redfish 40f4c99e70SEd Tanous { 41f4c99e70SEd Tanous namespace query_param 42f4c99e70SEd Tanous { 43ce8ea743SJiaqing Zhao inline constexpr size_t maxEntriesPerPage = 1000; 44f4c99e70SEd Tanous 457cf436c9SEd Tanous enum class ExpandType : uint8_t 467cf436c9SEd Tanous { 477cf436c9SEd Tanous None, 487cf436c9SEd Tanous Links, 497cf436c9SEd Tanous NotLinks, 507cf436c9SEd Tanous Both, 517cf436c9SEd Tanous }; 527cf436c9SEd Tanous 53a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 54f4c99e70SEd Tanous struct Query 55f4c99e70SEd Tanous { 56a6b9125fSNan Zhou // Only 57f4c99e70SEd Tanous bool isOnly = false; 58a6b9125fSNan Zhou // Expand 59a6b9125fSNan Zhou uint8_t expandLevel = 0; 607cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 61c937d2bfSEd Tanous 62c937d2bfSEd Tanous // Skip 63c937d2bfSEd Tanous size_t skip = 0; 64c937d2bfSEd Tanous 65c937d2bfSEd Tanous // Top 66ce8ea743SJiaqing Zhao size_t top = maxEntriesPerPage; 67f4c99e70SEd Tanous }; 68f4c99e70SEd Tanous 69a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 70a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 71a6b9125fSNan Zhou // the processing. 72a6b9125fSNan Zhou struct QueryCapabilities 73a6b9125fSNan Zhou { 74a6b9125fSNan Zhou bool canDelegateOnly = false; 75c937d2bfSEd Tanous bool canDelegateTop = false; 76c937d2bfSEd Tanous bool canDelegateSkip = false; 77a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 78a6b9125fSNan Zhou }; 79a6b9125fSNan Zhou 80a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 81a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 82a6b9125fSNan Zhou // function will take care of it. 83a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 84a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 85a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 86a6b9125fSNan Zhou { 87a6b9125fSNan Zhou Query delegated; 88a6b9125fSNan Zhou // delegate only 89a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 90a6b9125fSNan Zhou { 91a6b9125fSNan Zhou delegated.isOnly = true; 92a6b9125fSNan Zhou query.isOnly = false; 93a6b9125fSNan Zhou } 94a6b9125fSNan Zhou // delegate expand as much as we can 95a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 96a6b9125fSNan Zhou { 97a6b9125fSNan Zhou delegated.expandType = query.expandType; 98a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 99a6b9125fSNan Zhou { 100a6b9125fSNan Zhou query.expandType = ExpandType::None; 101a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 102a6b9125fSNan Zhou query.expandLevel = 0; 103a6b9125fSNan Zhou } 104a6b9125fSNan Zhou else 105a6b9125fSNan Zhou { 106a6b9125fSNan Zhou query.expandLevel -= queryCapabilities.canDelegateExpandLevel; 107a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 108a6b9125fSNan Zhou } 109a6b9125fSNan Zhou } 110c937d2bfSEd Tanous 111c937d2bfSEd Tanous // delegate top 112c937d2bfSEd Tanous if (queryCapabilities.canDelegateTop) 113c937d2bfSEd Tanous { 114c937d2bfSEd Tanous delegated.top = query.top; 115ce8ea743SJiaqing Zhao query.top = maxEntriesPerPage; 116c937d2bfSEd Tanous } 117c937d2bfSEd Tanous 118c937d2bfSEd Tanous // delegate skip 119c937d2bfSEd Tanous if (queryCapabilities.canDelegateSkip) 120c937d2bfSEd Tanous { 121c937d2bfSEd Tanous delegated.skip = query.skip; 122c937d2bfSEd Tanous query.skip = 0; 123c937d2bfSEd Tanous } 124a6b9125fSNan Zhou return delegated; 125a6b9125fSNan Zhou } 126a6b9125fSNan Zhou 1277cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 1287cf436c9SEd Tanous { 1297cf436c9SEd Tanous if (value.empty()) 1307cf436c9SEd Tanous { 1317cf436c9SEd Tanous return false; 1327cf436c9SEd Tanous } 1337cf436c9SEd Tanous switch (value[0]) 1347cf436c9SEd Tanous { 1357cf436c9SEd Tanous case '*': 1367cf436c9SEd Tanous query.expandType = ExpandType::Both; 1377cf436c9SEd Tanous break; 1387cf436c9SEd Tanous case '.': 1397cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 1407cf436c9SEd Tanous break; 1417cf436c9SEd Tanous case '~': 1427cf436c9SEd Tanous query.expandType = ExpandType::Links; 1437cf436c9SEd Tanous break; 1447cf436c9SEd Tanous default: 1457cf436c9SEd Tanous return false; 1467cf436c9SEd Tanous 1477cf436c9SEd Tanous break; 1487cf436c9SEd Tanous } 1497cf436c9SEd Tanous value.remove_prefix(1); 1507cf436c9SEd Tanous if (value.empty()) 1517cf436c9SEd Tanous { 1527cf436c9SEd Tanous query.expandLevel = 1; 1537cf436c9SEd Tanous return true; 1547cf436c9SEd Tanous } 1557cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 1567cf436c9SEd Tanous if (!value.starts_with(levels)) 1577cf436c9SEd Tanous { 1587cf436c9SEd Tanous return false; 1597cf436c9SEd Tanous } 1607cf436c9SEd Tanous value.remove_prefix(levels.size()); 1617cf436c9SEd Tanous 1627cf436c9SEd Tanous auto it = std::from_chars(value.data(), value.data() + value.size(), 1637cf436c9SEd Tanous query.expandLevel); 1647cf436c9SEd Tanous if (it.ec != std::errc()) 1657cf436c9SEd Tanous { 1667cf436c9SEd Tanous return false; 1677cf436c9SEd Tanous } 1687cf436c9SEd Tanous value.remove_prefix(static_cast<size_t>(it.ptr - value.data())); 1697cf436c9SEd Tanous return value == ")"; 1707cf436c9SEd Tanous } 1717cf436c9SEd Tanous 172c937d2bfSEd Tanous enum class QueryError 173c937d2bfSEd Tanous { 174c937d2bfSEd Tanous Ok, 175c937d2bfSEd Tanous OutOfRange, 176c937d2bfSEd Tanous ValueFormat, 177c937d2bfSEd Tanous }; 178c937d2bfSEd Tanous 179c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 180c937d2bfSEd Tanous { 181c937d2bfSEd Tanous std::from_chars_result r = 182c937d2bfSEd Tanous std::from_chars(value.data(), value.data() + value.size(), param); 183c937d2bfSEd Tanous 184c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 185c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 186c937d2bfSEd Tanous { 187c937d2bfSEd Tanous return QueryError::OutOfRange; 188c937d2bfSEd Tanous } 189c937d2bfSEd Tanous // All other errors are value format 190c937d2bfSEd Tanous if (r.ec != std::errc()) 191c937d2bfSEd Tanous { 192c937d2bfSEd Tanous return QueryError::ValueFormat; 193c937d2bfSEd Tanous } 194c937d2bfSEd Tanous return QueryError::Ok; 195c937d2bfSEd Tanous } 196c937d2bfSEd Tanous 197c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 198c937d2bfSEd Tanous { 199c937d2bfSEd Tanous return getNumericParam(value, query.skip); 200c937d2bfSEd Tanous } 201c937d2bfSEd Tanous 202c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 203c937d2bfSEd Tanous { 204c937d2bfSEd Tanous QueryError ret = getNumericParam(value, query.top); 205c937d2bfSEd Tanous if (ret != QueryError::Ok) 206c937d2bfSEd Tanous { 207c937d2bfSEd Tanous return ret; 208c937d2bfSEd Tanous } 209c937d2bfSEd Tanous 210c937d2bfSEd Tanous // Range check for sanity. 211c937d2bfSEd Tanous if (query.top > maxEntriesPerPage) 212c937d2bfSEd Tanous { 213c937d2bfSEd Tanous return QueryError::OutOfRange; 214c937d2bfSEd Tanous } 215c937d2bfSEd Tanous 216c937d2bfSEd Tanous return QueryError::Ok; 217c937d2bfSEd Tanous } 218c937d2bfSEd Tanous 219f4c99e70SEd Tanous inline std::optional<Query> 220f4c99e70SEd Tanous parseParameters(const boost::urls::params_view& urlParams, 221f4c99e70SEd Tanous crow::Response& res) 222f4c99e70SEd Tanous { 223f4c99e70SEd Tanous Query ret; 224f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 225f4c99e70SEd Tanous { 226f4c99e70SEd Tanous std::string_view key(it.key.data(), it.key.size()); 227f4c99e70SEd Tanous std::string_view value(it.value.data(), it.value.size()); 228f4c99e70SEd Tanous if (key == "only") 229f4c99e70SEd Tanous { 230f4c99e70SEd Tanous if (!it.value.empty()) 231f4c99e70SEd Tanous { 232f4c99e70SEd Tanous messages::queryParameterValueFormatError(res, value, key); 233f4c99e70SEd Tanous return std::nullopt; 234f4c99e70SEd Tanous } 235f4c99e70SEd Tanous ret.isOnly = true; 236f4c99e70SEd Tanous } 2375e52870bSEd Tanous else if (key == "$expand" && bmcwebInsecureEnableQueryParams) 2387cf436c9SEd Tanous { 2397cf436c9SEd Tanous if (!getExpandType(value, ret)) 2407cf436c9SEd Tanous { 2417cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 2427cf436c9SEd Tanous return std::nullopt; 243f4c99e70SEd Tanous } 2447cf436c9SEd Tanous } 245c937d2bfSEd Tanous else if (key == "$top") 246c937d2bfSEd Tanous { 247c937d2bfSEd Tanous QueryError topRet = getTopParam(value, ret); 248c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 249c937d2bfSEd Tanous { 250c937d2bfSEd Tanous messages::queryParameterValueFormatError(res, value, key); 251c937d2bfSEd Tanous return std::nullopt; 252c937d2bfSEd Tanous } 253c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 254c937d2bfSEd Tanous { 255c937d2bfSEd Tanous messages::queryParameterOutOfRange( 256c937d2bfSEd Tanous res, value, "$top", 257a926c53eSJiaqing Zhao "0-" + std::to_string(maxEntriesPerPage)); 258c937d2bfSEd Tanous return std::nullopt; 259c937d2bfSEd Tanous } 260c937d2bfSEd Tanous } 261c937d2bfSEd Tanous else if (key == "$skip") 262c937d2bfSEd Tanous { 263c937d2bfSEd Tanous QueryError topRet = getSkipParam(value, ret); 264c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 265c937d2bfSEd Tanous { 266c937d2bfSEd Tanous messages::queryParameterValueFormatError(res, value, key); 267c937d2bfSEd Tanous return std::nullopt; 268c937d2bfSEd Tanous } 269c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 270c937d2bfSEd Tanous { 271c937d2bfSEd Tanous messages::queryParameterOutOfRange( 272c937d2bfSEd Tanous res, value, key, 273a926c53eSJiaqing Zhao "0-" + std::to_string(std::numeric_limits<size_t>::max())); 274c937d2bfSEd Tanous return std::nullopt; 275c937d2bfSEd Tanous } 276c937d2bfSEd Tanous } 2777cf436c9SEd Tanous else 2787cf436c9SEd Tanous { 2797cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 2807cf436c9SEd Tanous if (key.starts_with("$")) 2817cf436c9SEd Tanous { 2827cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 2837cf436c9SEd Tanous // status code for any unsupported query parameters that 2847cf436c9SEd Tanous // start with $ . 2857cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 2867cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 2877cf436c9SEd Tanous return std::nullopt; 2887cf436c9SEd Tanous } 2897cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 2907cf436c9SEd Tanous // not begin with $ ." 2917cf436c9SEd Tanous } 2927cf436c9SEd Tanous } 2937cf436c9SEd Tanous 294f4c99e70SEd Tanous return ret; 295f4c99e70SEd Tanous } 296f4c99e70SEd Tanous 297f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 298f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 299f4c99e70SEd Tanous { 300f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing only query param"; 301f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 302f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 303f4c99e70SEd Tanous { 304f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 305f4c99e70SEd Tanous completionHandler(res); 306f4c99e70SEd Tanous return false; 307f4c99e70SEd Tanous } 308f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 309f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 310f4c99e70SEd Tanous { 311f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 312f4c99e70SEd Tanous << " element, returning full collection."; 313f4c99e70SEd Tanous completionHandler(res); 314f4c99e70SEd Tanous return false; 315f4c99e70SEd Tanous } 316f4c99e70SEd Tanous 317f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 318f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 319f4c99e70SEd Tanous { 320f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "No found odata.id"; 321f4c99e70SEd Tanous messages::internalError(res); 322f4c99e70SEd Tanous completionHandler(res); 323f4c99e70SEd Tanous return false; 324f4c99e70SEd Tanous } 325f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 326f4c99e70SEd Tanous if (url == nullptr) 327f4c99e70SEd Tanous { 328f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 329f4c99e70SEd Tanous messages::internalError(res); 330f4c99e70SEd Tanous completionHandler(res); 331f4c99e70SEd Tanous return false; 332f4c99e70SEd Tanous } 333f4c99e70SEd Tanous // TODO(Ed) copy request headers? 334f4c99e70SEd Tanous // newReq.session = req.session; 335f4c99e70SEd Tanous std::error_code ec; 336f4c99e70SEd Tanous crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 337f4c99e70SEd Tanous if (ec) 338f4c99e70SEd Tanous { 339f4c99e70SEd Tanous messages::internalError(res); 340f4c99e70SEd Tanous completionHandler(res); 341f4c99e70SEd Tanous return false; 342f4c99e70SEd Tanous } 343f4c99e70SEd Tanous 344f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 345f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 346f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 347f4c99e70SEd Tanous asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 348f4c99e70SEd Tanous app.handle(newReq, asyncResp); 349f4c99e70SEd Tanous return true; 350f4c99e70SEd Tanous } 351f4c99e70SEd Tanous 3527cf436c9SEd Tanous struct ExpandNode 3537cf436c9SEd Tanous { 3547cf436c9SEd Tanous nlohmann::json::json_pointer location; 3557cf436c9SEd Tanous std::string uri; 3567cf436c9SEd Tanous 3577cf436c9SEd Tanous inline bool operator==(const ExpandNode& other) const 3587cf436c9SEd Tanous { 3597cf436c9SEd Tanous return location == other.location && uri == other.uri; 3607cf436c9SEd Tanous } 3617cf436c9SEd Tanous }; 3627cf436c9SEd Tanous 3637cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 3647cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 3657cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 3667cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 3677cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 3687cf436c9SEd Tanous // with the keys from the jsonResponse object 3697cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 3707cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 3717cf436c9SEd Tanous const nlohmann::json::json_pointer& p, bool inLinks, 3727cf436c9SEd Tanous std::vector<ExpandNode>& out) 3737cf436c9SEd Tanous { 3747cf436c9SEd Tanous // If no expand is needed, return early 3757cf436c9SEd Tanous if (eType == ExpandType::None) 3767cf436c9SEd Tanous { 3777cf436c9SEd Tanous return; 3787cf436c9SEd Tanous } 3797cf436c9SEd Tanous nlohmann::json::array_t* array = 3807cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 3817cf436c9SEd Tanous if (array != nullptr) 3827cf436c9SEd Tanous { 3837cf436c9SEd Tanous size_t index = 0; 3847cf436c9SEd Tanous // For arrays, walk every element in the array 3857cf436c9SEd Tanous for (auto& element : *array) 3867cf436c9SEd Tanous { 3877cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / index; 3887cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 3897cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 3907cf436c9SEd Tanous out); 3917cf436c9SEd Tanous index++; 3927cf436c9SEd Tanous } 3937cf436c9SEd Tanous } 3947cf436c9SEd Tanous nlohmann::json::object_t* obj = 3957cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 3967cf436c9SEd Tanous if (obj == nullptr) 3977cf436c9SEd Tanous { 3987cf436c9SEd Tanous return; 3997cf436c9SEd Tanous } 4007cf436c9SEd Tanous // Navigation References only ever have a single element 4017cf436c9SEd Tanous if (obj->size() == 1) 4027cf436c9SEd Tanous { 4037cf436c9SEd Tanous if (obj->begin()->first == "@odata.id") 4047cf436c9SEd Tanous { 4057cf436c9SEd Tanous const std::string* uri = 4067cf436c9SEd Tanous obj->begin()->second.get_ptr<const std::string*>(); 4077cf436c9SEd Tanous if (uri != nullptr) 4087cf436c9SEd Tanous { 4097cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 4107cf436c9SEd Tanous out.push_back({p, *uri}); 4117cf436c9SEd Tanous } 4127cf436c9SEd Tanous } 4137cf436c9SEd Tanous } 4147cf436c9SEd Tanous // Loop the object and look for links 4157cf436c9SEd Tanous for (auto& element : *obj) 4167cf436c9SEd Tanous { 417e479ad58SNan Zhou bool localInLinks = inLinks; 418e479ad58SNan Zhou if (!localInLinks) 4197cf436c9SEd Tanous { 4207cf436c9SEd Tanous // Check if this is a links node 421e479ad58SNan Zhou localInLinks = element.first == "Links"; 4227cf436c9SEd Tanous } 4237cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 4247cf436c9SEd Tanous // Per section 7.3 of the redfish specification 425e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 4267cf436c9SEd Tanous { 4277cf436c9SEd Tanous continue; 4287cf436c9SEd Tanous } 429e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 4307cf436c9SEd Tanous { 4317cf436c9SEd Tanous continue; 4327cf436c9SEd Tanous } 4337cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / element.first; 4347cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 4357cf436c9SEd Tanous 4367cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 437e479ad58SNan Zhou localInLinks, out); 4387cf436c9SEd Tanous } 4397cf436c9SEd Tanous } 4407cf436c9SEd Tanous 4417cf436c9SEd Tanous inline std::vector<ExpandNode> 44272c3ae33SNan Zhou findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse) 4437cf436c9SEd Tanous { 4447cf436c9SEd Tanous std::vector<ExpandNode> ret; 44572c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 4467cf436c9SEd Tanous findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 4477cf436c9SEd Tanous return ret; 4487cf436c9SEd Tanous } 4497cf436c9SEd Tanous 45072c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 451b66cf2a2SNan Zhou // Returns std::nullopt on failures. 45272c3ae33SNan Zhou // This function shall handle $select when it is added. 45372c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand, 45472c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 455b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query) 45672c3ae33SNan Zhou { 45772c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 45872c3ae33SNan Zhou if (query.expandLevel <= 1) 45972c3ae33SNan Zhou { 460b66cf2a2SNan Zhou return ""; 46172c3ae33SNan Zhou } 46272c3ae33SNan Zhou std::string str = "?$expand="; 463b66cf2a2SNan Zhou bool queryTypeExpected = false; 46472c3ae33SNan Zhou switch (query.expandType) 46572c3ae33SNan Zhou { 46672c3ae33SNan Zhou case ExpandType::None: 467b66cf2a2SNan Zhou return ""; 46872c3ae33SNan Zhou case ExpandType::Links: 469b66cf2a2SNan Zhou queryTypeExpected = true; 47072c3ae33SNan Zhou str += '~'; 47172c3ae33SNan Zhou break; 47272c3ae33SNan Zhou case ExpandType::NotLinks: 473b66cf2a2SNan Zhou queryTypeExpected = true; 47472c3ae33SNan Zhou str += '.'; 47572c3ae33SNan Zhou break; 47672c3ae33SNan Zhou case ExpandType::Both: 477b66cf2a2SNan Zhou queryTypeExpected = true; 47872c3ae33SNan Zhou str += '*'; 47972c3ae33SNan Zhou break; 480b66cf2a2SNan Zhou } 481b66cf2a2SNan Zhou if (!queryTypeExpected) 482b66cf2a2SNan Zhou { 483b66cf2a2SNan Zhou return std::nullopt; 48472c3ae33SNan Zhou } 48572c3ae33SNan Zhou str += "($levels="; 48672c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 48772c3ae33SNan Zhou str += ')'; 48872c3ae33SNan Zhou return str; 48972c3ae33SNan Zhou } 49072c3ae33SNan Zhou 4917cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 4927cf436c9SEd Tanous { 4937cf436c9SEd Tanous public: 4947cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 4957cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 4967cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 4977cf436c9SEd Tanous // class manages the final "merge" of the json resources. 4988a592810SEd Tanous MultiAsyncResp(crow::App& appIn, 4997cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 5008a592810SEd Tanous app(appIn), 5017cf436c9SEd Tanous finalRes(std::move(finalResIn)) 5027cf436c9SEd Tanous {} 5037cf436c9SEd Tanous 5047cf436c9SEd Tanous void addAwaitingResponse( 50502cad96eSEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& res, 5067cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 5077cf436c9SEd Tanous { 5087cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 50972c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 5107cf436c9SEd Tanous } 5117cf436c9SEd Tanous 51272c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 5137cf436c9SEd Tanous crow::Response& res) 5147cf436c9SEd Tanous { 5157cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 5167cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 5177cf436c9SEd Tanous } 5187cf436c9SEd Tanous 51972c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 52072c3ae33SNan Zhou // for deeper levels. 52172c3ae33SNan Zhou void startQuery(const Query& query) 52272c3ae33SNan Zhou { 52372c3ae33SNan Zhou std::vector<ExpandNode> nodes = 52472c3ae33SNan Zhou findNavigationReferences(query.expandType, finalRes->res.jsonValue); 5257cf436c9SEd Tanous BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 526b66cf2a2SNan Zhou const std::optional<std::string> queryStr = formatQueryForExpand(query); 527b66cf2a2SNan Zhou if (!queryStr) 528b66cf2a2SNan Zhou { 529b66cf2a2SNan Zhou messages::internalError(finalRes->res); 530b66cf2a2SNan Zhou return; 531b66cf2a2SNan Zhou } 5327cf436c9SEd Tanous for (const ExpandNode& node : nodes) 5337cf436c9SEd Tanous { 534b66cf2a2SNan Zhou const std::string subQuery = node.uri + *queryStr; 53572c3ae33SNan Zhou BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 5367cf436c9SEd Tanous std::error_code ec; 53772c3ae33SNan Zhou crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 5387cf436c9SEd Tanous ec); 5397cf436c9SEd Tanous if (ec) 5407cf436c9SEd Tanous { 54172c3ae33SNan Zhou messages::internalError(finalRes->res); 5427cf436c9SEd Tanous return; 5437cf436c9SEd Tanous } 5447cf436c9SEd Tanous 5457cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 5467cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " 5477cf436c9SEd Tanous << &asyncResp->res; 54872c3ae33SNan Zhou 54972c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 5507cf436c9SEd Tanous app.handle(newReq, asyncResp); 5517cf436c9SEd Tanous } 5527cf436c9SEd Tanous } 5537cf436c9SEd Tanous 5547cf436c9SEd Tanous private: 55572c3ae33SNan Zhou static void 55672c3ae33SNan Zhou placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 5577cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 5587cf436c9SEd Tanous crow::Response& res) 5597cf436c9SEd Tanous { 56072c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 5617cf436c9SEd Tanous } 5627cf436c9SEd Tanous 5637cf436c9SEd Tanous crow::App& app; 5647cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 5657cf436c9SEd Tanous }; 5667cf436c9SEd Tanous 5672a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 5682a68dc80SEd Tanous { 5692a68dc80SEd Tanous nlohmann::json::object_t* obj = 5702a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 5712a68dc80SEd Tanous if (obj == nullptr) 5722a68dc80SEd Tanous { 5732a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 5742a68dc80SEd Tanous messages::internalError(res); 5752a68dc80SEd Tanous return; 5762a68dc80SEd Tanous } 5772a68dc80SEd Tanous 5782a68dc80SEd Tanous BMCWEB_LOG_DEBUG << "Handling top/skip"; 5792a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 5802a68dc80SEd Tanous if (members == obj->end()) 5812a68dc80SEd Tanous { 5822a68dc80SEd Tanous // From the Redfish specification 7.3.1 5832a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 5842a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 5852a68dc80SEd Tanous // for any supported query parameters that apply only to resource 5862a68dc80SEd Tanous // collections but are used on singular resources. 5872a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 5882a68dc80SEd Tanous return; 5892a68dc80SEd Tanous } 5902a68dc80SEd Tanous 5912a68dc80SEd Tanous nlohmann::json::array_t* arr = 5922a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 5932a68dc80SEd Tanous if (arr == nullptr) 5942a68dc80SEd Tanous { 5952a68dc80SEd Tanous messages::internalError(res); 5962a68dc80SEd Tanous return; 5972a68dc80SEd Tanous } 5982a68dc80SEd Tanous 5992a68dc80SEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before $top 6002a68dc80SEd Tanous // Can only skip as many values as we have 6012a68dc80SEd Tanous size_t skip = std::min(arr->size(), query.skip); 6022a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 6032a68dc80SEd Tanous 6042a68dc80SEd Tanous size_t top = std::min(arr->size(), query.top); 6052a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 6062a68dc80SEd Tanous } 6072a68dc80SEd Tanous 6087cf436c9SEd Tanous inline void 609593f6449SNan Zhou processAllParams(crow::App& app, const Query& query, 6107cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 6117cf436c9SEd Tanous crow::Response& intermediateResponse) 612f4c99e70SEd Tanous { 613f4c99e70SEd Tanous if (!completionHandler) 614f4c99e70SEd Tanous { 615f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Function was invalid?"; 616f4c99e70SEd Tanous return; 617f4c99e70SEd Tanous } 618f4c99e70SEd Tanous 619f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing query params"; 620f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 621f4c99e70SEd Tanous // params. 622f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 623f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 624f4c99e70SEd Tanous { 625f4c99e70SEd Tanous completionHandler(intermediateResponse); 626f4c99e70SEd Tanous return; 627f4c99e70SEd Tanous } 628f4c99e70SEd Tanous if (query.isOnly) 629f4c99e70SEd Tanous { 630f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 631f4c99e70SEd Tanous return; 632f4c99e70SEd Tanous } 6332a68dc80SEd Tanous 634ce8ea743SJiaqing Zhao if (query.top <= maxEntriesPerPage || query.skip != 0) 6352a68dc80SEd Tanous { 6362a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 6372a68dc80SEd Tanous } 6382a68dc80SEd Tanous 6397cf436c9SEd Tanous if (query.expandType != ExpandType::None) 6407cf436c9SEd Tanous { 6417cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Executing expand query"; 6427cf436c9SEd Tanous // TODO(ed) this is a copy of the response object. Admittedly, 6437cf436c9SEd Tanous // we're inherently doing something inefficient, but we shouldn't 6447cf436c9SEd Tanous // have to do a full copy 6457cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 6467cf436c9SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 6477cf436c9SEd Tanous asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue); 6487cf436c9SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 6497cf436c9SEd Tanous 65072c3ae33SNan Zhou multi->startQuery(query); 6517cf436c9SEd Tanous return; 6527cf436c9SEd Tanous } 653f4c99e70SEd Tanous completionHandler(intermediateResponse); 654f4c99e70SEd Tanous } 655f4c99e70SEd Tanous 656f4c99e70SEd Tanous } // namespace query_param 657f4c99e70SEd Tanous } // namespace redfish 658