1f4c99e70SEd Tanous #pragma once 2f4c99e70SEd Tanous #include "app.hpp" 3f4c99e70SEd Tanous #include "async_resp.hpp" 4f4c99e70SEd Tanous #include "error_messages.hpp" 5f4c99e70SEd Tanous #include "http_request.hpp" 6f4c99e70SEd Tanous #include "routing.hpp" 7f4c99e70SEd Tanous 87cf436c9SEd Tanous #include <charconv> 9f4c99e70SEd Tanous #include <string> 10f4c99e70SEd Tanous #include <string_view> 117cf436c9SEd Tanous #include <utility> 12f4c99e70SEd Tanous #include <vector> 13f4c99e70SEd Tanous 14f4c99e70SEd Tanous namespace redfish 15f4c99e70SEd Tanous { 16f4c99e70SEd Tanous namespace query_param 17f4c99e70SEd Tanous { 18f4c99e70SEd Tanous 197cf436c9SEd Tanous enum class ExpandType : uint8_t 207cf436c9SEd Tanous { 217cf436c9SEd Tanous None, 227cf436c9SEd Tanous Links, 237cf436c9SEd Tanous NotLinks, 247cf436c9SEd Tanous Both, 257cf436c9SEd Tanous }; 267cf436c9SEd Tanous 27a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route. 28f4c99e70SEd Tanous struct Query 29f4c99e70SEd Tanous { 30a6b9125fSNan Zhou // Only 31f4c99e70SEd Tanous bool isOnly = false; 32a6b9125fSNan Zhou // Expand 33a6b9125fSNan Zhou uint8_t expandLevel = 0; 347cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 35c937d2bfSEd Tanous 36c937d2bfSEd Tanous // Skip 37c937d2bfSEd Tanous size_t skip = 0; 38c937d2bfSEd Tanous 39c937d2bfSEd Tanous // Top 40c937d2bfSEd Tanous size_t top = std::numeric_limits<size_t>::max(); 41f4c99e70SEd Tanous }; 42f4c99e70SEd Tanous 43a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle 44a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate 45a6b9125fSNan Zhou // the processing. 46a6b9125fSNan Zhou struct QueryCapabilities 47a6b9125fSNan Zhou { 48a6b9125fSNan Zhou bool canDelegateOnly = false; 49c937d2bfSEd Tanous bool canDelegateTop = false; 50c937d2bfSEd Tanous bool canDelegateSkip = false; 51a6b9125fSNan Zhou uint8_t canDelegateExpandLevel = 0; 52a6b9125fSNan Zhou }; 53a6b9125fSNan Zhou 54a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities| 55a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse 56a6b9125fSNan Zhou // function will take care of it. 57a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource 58a6b9125fSNan Zhou // handlers so that handlers don't need to query again. 59a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 60a6b9125fSNan Zhou { 61a6b9125fSNan Zhou Query delegated; 62a6b9125fSNan Zhou // delegate only 63a6b9125fSNan Zhou if (query.isOnly && queryCapabilities.canDelegateOnly) 64a6b9125fSNan Zhou { 65a6b9125fSNan Zhou delegated.isOnly = true; 66a6b9125fSNan Zhou query.isOnly = false; 67a6b9125fSNan Zhou } 68a6b9125fSNan Zhou // delegate expand as much as we can 69a6b9125fSNan Zhou if (query.expandType != ExpandType::None) 70a6b9125fSNan Zhou { 71a6b9125fSNan Zhou delegated.expandType = query.expandType; 72a6b9125fSNan Zhou if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 73a6b9125fSNan Zhou { 74a6b9125fSNan Zhou query.expandType = ExpandType::None; 75a6b9125fSNan Zhou delegated.expandLevel = query.expandLevel; 76a6b9125fSNan Zhou query.expandLevel = 0; 77a6b9125fSNan Zhou } 78a6b9125fSNan Zhou else 79a6b9125fSNan Zhou { 80a6b9125fSNan Zhou query.expandLevel -= queryCapabilities.canDelegateExpandLevel; 81a6b9125fSNan Zhou delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 82a6b9125fSNan Zhou } 83a6b9125fSNan Zhou } 84c937d2bfSEd Tanous 85c937d2bfSEd Tanous // delegate top 86c937d2bfSEd Tanous if (queryCapabilities.canDelegateTop) 87c937d2bfSEd Tanous { 88c937d2bfSEd Tanous delegated.top = query.top; 89c937d2bfSEd Tanous query.top = std::numeric_limits<size_t>::max(); 90c937d2bfSEd Tanous } 91c937d2bfSEd Tanous 92c937d2bfSEd Tanous // delegate skip 93c937d2bfSEd Tanous if (queryCapabilities.canDelegateSkip) 94c937d2bfSEd Tanous { 95c937d2bfSEd Tanous delegated.skip = query.skip; 96c937d2bfSEd Tanous query.skip = 0; 97c937d2bfSEd Tanous } 98a6b9125fSNan Zhou return delegated; 99a6b9125fSNan Zhou } 100a6b9125fSNan Zhou 1017cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 1027cf436c9SEd Tanous { 1037cf436c9SEd Tanous if (value.empty()) 1047cf436c9SEd Tanous { 1057cf436c9SEd Tanous return false; 1067cf436c9SEd Tanous } 1077cf436c9SEd Tanous switch (value[0]) 1087cf436c9SEd Tanous { 1097cf436c9SEd Tanous case '*': 1107cf436c9SEd Tanous query.expandType = ExpandType::Both; 1117cf436c9SEd Tanous break; 1127cf436c9SEd Tanous case '.': 1137cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 1147cf436c9SEd Tanous break; 1157cf436c9SEd Tanous case '~': 1167cf436c9SEd Tanous query.expandType = ExpandType::Links; 1177cf436c9SEd Tanous break; 1187cf436c9SEd Tanous default: 1197cf436c9SEd Tanous return false; 1207cf436c9SEd Tanous 1217cf436c9SEd Tanous break; 1227cf436c9SEd Tanous } 1237cf436c9SEd Tanous value.remove_prefix(1); 1247cf436c9SEd Tanous if (value.empty()) 1257cf436c9SEd Tanous { 1267cf436c9SEd Tanous query.expandLevel = 1; 1277cf436c9SEd Tanous return true; 1287cf436c9SEd Tanous } 1297cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 1307cf436c9SEd Tanous if (!value.starts_with(levels)) 1317cf436c9SEd Tanous { 1327cf436c9SEd Tanous return false; 1337cf436c9SEd Tanous } 1347cf436c9SEd Tanous value.remove_prefix(levels.size()); 1357cf436c9SEd Tanous 1367cf436c9SEd Tanous auto it = std::from_chars(value.data(), value.data() + value.size(), 1377cf436c9SEd Tanous query.expandLevel); 1387cf436c9SEd Tanous if (it.ec != std::errc()) 1397cf436c9SEd Tanous { 1407cf436c9SEd Tanous return false; 1417cf436c9SEd Tanous } 1427cf436c9SEd Tanous value.remove_prefix(static_cast<size_t>(it.ptr - value.data())); 1437cf436c9SEd Tanous return value == ")"; 1447cf436c9SEd Tanous } 1457cf436c9SEd Tanous 146c937d2bfSEd Tanous enum class QueryError 147c937d2bfSEd Tanous { 148c937d2bfSEd Tanous Ok, 149c937d2bfSEd Tanous OutOfRange, 150c937d2bfSEd Tanous ValueFormat, 151c937d2bfSEd Tanous }; 152c937d2bfSEd Tanous 153c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param) 154c937d2bfSEd Tanous { 155c937d2bfSEd Tanous std::from_chars_result r = 156c937d2bfSEd Tanous std::from_chars(value.data(), value.data() + value.size(), param); 157c937d2bfSEd Tanous 158c937d2bfSEd Tanous // If the number wasn't representable in the type, it's out of range 159c937d2bfSEd Tanous if (r.ec == std::errc::result_out_of_range) 160c937d2bfSEd Tanous { 161c937d2bfSEd Tanous return QueryError::OutOfRange; 162c937d2bfSEd Tanous } 163c937d2bfSEd Tanous // All other errors are value format 164c937d2bfSEd Tanous if (r.ec != std::errc()) 165c937d2bfSEd Tanous { 166c937d2bfSEd Tanous return QueryError::ValueFormat; 167c937d2bfSEd Tanous } 168c937d2bfSEd Tanous return QueryError::Ok; 169c937d2bfSEd Tanous } 170c937d2bfSEd Tanous 171c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query) 172c937d2bfSEd Tanous { 173c937d2bfSEd Tanous return getNumericParam(value, query.skip); 174c937d2bfSEd Tanous } 175c937d2bfSEd Tanous 176c937d2bfSEd Tanous static constexpr size_t maxEntriesPerPage = 1000; 177c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query) 178c937d2bfSEd Tanous { 179c937d2bfSEd Tanous QueryError ret = getNumericParam(value, query.top); 180c937d2bfSEd Tanous if (ret != QueryError::Ok) 181c937d2bfSEd Tanous { 182c937d2bfSEd Tanous return ret; 183c937d2bfSEd Tanous } 184c937d2bfSEd Tanous 185c937d2bfSEd Tanous // Range check for sanity. 186c937d2bfSEd Tanous if (query.top > maxEntriesPerPage) 187c937d2bfSEd Tanous { 188c937d2bfSEd Tanous return QueryError::OutOfRange; 189c937d2bfSEd Tanous } 190c937d2bfSEd Tanous 191c937d2bfSEd Tanous return QueryError::Ok; 192c937d2bfSEd Tanous } 193c937d2bfSEd Tanous 194f4c99e70SEd Tanous inline std::optional<Query> 195f4c99e70SEd Tanous parseParameters(const boost::urls::params_view& urlParams, 196f4c99e70SEd Tanous crow::Response& res) 197f4c99e70SEd Tanous { 198f4c99e70SEd Tanous Query ret; 199f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 200f4c99e70SEd Tanous { 201f4c99e70SEd Tanous std::string_view key(it.key.data(), it.key.size()); 202f4c99e70SEd Tanous std::string_view value(it.value.data(), it.value.size()); 203f4c99e70SEd Tanous if (key == "only") 204f4c99e70SEd Tanous { 205f4c99e70SEd Tanous if (!it.value.empty()) 206f4c99e70SEd Tanous { 207f4c99e70SEd Tanous messages::queryParameterValueFormatError(res, value, key); 208f4c99e70SEd Tanous return std::nullopt; 209f4c99e70SEd Tanous } 210f4c99e70SEd Tanous ret.isOnly = true; 211f4c99e70SEd Tanous } 2125e52870bSEd Tanous else if (key == "$expand" && bmcwebInsecureEnableQueryParams) 2137cf436c9SEd Tanous { 2147cf436c9SEd Tanous if (!getExpandType(value, ret)) 2157cf436c9SEd Tanous { 2167cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 2177cf436c9SEd Tanous return std::nullopt; 218f4c99e70SEd Tanous } 2197cf436c9SEd Tanous } 220c937d2bfSEd Tanous else if (key == "$top") 221c937d2bfSEd Tanous { 222c937d2bfSEd Tanous QueryError topRet = getTopParam(value, ret); 223c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 224c937d2bfSEd Tanous { 225c937d2bfSEd Tanous messages::queryParameterValueFormatError(res, value, key); 226c937d2bfSEd Tanous return std::nullopt; 227c937d2bfSEd Tanous } 228c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 229c937d2bfSEd Tanous { 230c937d2bfSEd Tanous messages::queryParameterOutOfRange( 231c937d2bfSEd Tanous res, value, "$top", 232c937d2bfSEd Tanous "1-" + std::to_string(maxEntriesPerPage)); 233c937d2bfSEd Tanous return std::nullopt; 234c937d2bfSEd Tanous } 235c937d2bfSEd Tanous } 236c937d2bfSEd Tanous else if (key == "$skip") 237c937d2bfSEd Tanous { 238c937d2bfSEd Tanous QueryError topRet = getSkipParam(value, ret); 239c937d2bfSEd Tanous if (topRet == QueryError::ValueFormat) 240c937d2bfSEd Tanous { 241c937d2bfSEd Tanous messages::queryParameterValueFormatError(res, value, key); 242c937d2bfSEd Tanous return std::nullopt; 243c937d2bfSEd Tanous } 244c937d2bfSEd Tanous if (topRet == QueryError::OutOfRange) 245c937d2bfSEd Tanous { 246c937d2bfSEd Tanous messages::queryParameterOutOfRange( 247c937d2bfSEd Tanous res, value, key, 248c937d2bfSEd Tanous "1-" + std::to_string(std::numeric_limits<size_t>::max())); 249c937d2bfSEd Tanous return std::nullopt; 250c937d2bfSEd Tanous } 251c937d2bfSEd Tanous } 2527cf436c9SEd Tanous else 2537cf436c9SEd Tanous { 2547cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 2557cf436c9SEd Tanous if (key.starts_with("$")) 2567cf436c9SEd Tanous { 2577cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 2587cf436c9SEd Tanous // status code for any unsupported query parameters that 2597cf436c9SEd Tanous // start with $ . 2607cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 2617cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 2627cf436c9SEd Tanous return std::nullopt; 2637cf436c9SEd Tanous } 2647cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 2657cf436c9SEd Tanous // not begin with $ ." 2667cf436c9SEd Tanous } 2677cf436c9SEd Tanous } 2687cf436c9SEd Tanous 269f4c99e70SEd Tanous return ret; 270f4c99e70SEd Tanous } 271f4c99e70SEd Tanous 272f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 273f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 274f4c99e70SEd Tanous { 275f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing only query param"; 276f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 277f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 278f4c99e70SEd Tanous { 279f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 280f4c99e70SEd Tanous completionHandler(res); 281f4c99e70SEd Tanous return false; 282f4c99e70SEd Tanous } 283f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 284f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 285f4c99e70SEd Tanous { 286f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 287f4c99e70SEd Tanous << " element, returning full collection."; 288f4c99e70SEd Tanous completionHandler(res); 289f4c99e70SEd Tanous return false; 290f4c99e70SEd Tanous } 291f4c99e70SEd Tanous 292f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 293f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 294f4c99e70SEd Tanous { 295f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "No found odata.id"; 296f4c99e70SEd Tanous messages::internalError(res); 297f4c99e70SEd Tanous completionHandler(res); 298f4c99e70SEd Tanous return false; 299f4c99e70SEd Tanous } 300f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 301f4c99e70SEd Tanous if (url == nullptr) 302f4c99e70SEd Tanous { 303f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 304f4c99e70SEd Tanous messages::internalError(res); 305f4c99e70SEd Tanous completionHandler(res); 306f4c99e70SEd Tanous return false; 307f4c99e70SEd Tanous } 308f4c99e70SEd Tanous // TODO(Ed) copy request headers? 309f4c99e70SEd Tanous // newReq.session = req.session; 310f4c99e70SEd Tanous std::error_code ec; 311f4c99e70SEd Tanous crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 312f4c99e70SEd Tanous if (ec) 313f4c99e70SEd Tanous { 314f4c99e70SEd Tanous messages::internalError(res); 315f4c99e70SEd Tanous completionHandler(res); 316f4c99e70SEd Tanous return false; 317f4c99e70SEd Tanous } 318f4c99e70SEd Tanous 319f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 320f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 321f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 322f4c99e70SEd Tanous asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 323f4c99e70SEd Tanous app.handle(newReq, asyncResp); 324f4c99e70SEd Tanous return true; 325f4c99e70SEd Tanous } 326f4c99e70SEd Tanous 3277cf436c9SEd Tanous struct ExpandNode 3287cf436c9SEd Tanous { 3297cf436c9SEd Tanous nlohmann::json::json_pointer location; 3307cf436c9SEd Tanous std::string uri; 3317cf436c9SEd Tanous 3327cf436c9SEd Tanous inline bool operator==(const ExpandNode& other) const 3337cf436c9SEd Tanous { 3347cf436c9SEd Tanous return location == other.location && uri == other.uri; 3357cf436c9SEd Tanous } 3367cf436c9SEd Tanous }; 3377cf436c9SEd Tanous 3387cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 3397cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 3407cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 3417cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 3427cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 3437cf436c9SEd Tanous // with the keys from the jsonResponse object 3447cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 3457cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 3467cf436c9SEd Tanous const nlohmann::json::json_pointer& p, bool inLinks, 3477cf436c9SEd Tanous std::vector<ExpandNode>& out) 3487cf436c9SEd Tanous { 3497cf436c9SEd Tanous // If no expand is needed, return early 3507cf436c9SEd Tanous if (eType == ExpandType::None) 3517cf436c9SEd Tanous { 3527cf436c9SEd Tanous return; 3537cf436c9SEd Tanous } 3547cf436c9SEd Tanous nlohmann::json::array_t* array = 3557cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 3567cf436c9SEd Tanous if (array != nullptr) 3577cf436c9SEd Tanous { 3587cf436c9SEd Tanous size_t index = 0; 3597cf436c9SEd Tanous // For arrays, walk every element in the array 3607cf436c9SEd Tanous for (auto& element : *array) 3617cf436c9SEd Tanous { 3627cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / index; 3637cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 3647cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 3657cf436c9SEd Tanous out); 3667cf436c9SEd Tanous index++; 3677cf436c9SEd Tanous } 3687cf436c9SEd Tanous } 3697cf436c9SEd Tanous nlohmann::json::object_t* obj = 3707cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 3717cf436c9SEd Tanous if (obj == nullptr) 3727cf436c9SEd Tanous { 3737cf436c9SEd Tanous return; 3747cf436c9SEd Tanous } 3757cf436c9SEd Tanous // Navigation References only ever have a single element 3767cf436c9SEd Tanous if (obj->size() == 1) 3777cf436c9SEd Tanous { 3787cf436c9SEd Tanous if (obj->begin()->first == "@odata.id") 3797cf436c9SEd Tanous { 3807cf436c9SEd Tanous const std::string* uri = 3817cf436c9SEd Tanous obj->begin()->second.get_ptr<const std::string*>(); 3827cf436c9SEd Tanous if (uri != nullptr) 3837cf436c9SEd Tanous { 3847cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 3857cf436c9SEd Tanous out.push_back({p, *uri}); 3867cf436c9SEd Tanous } 3877cf436c9SEd Tanous } 3887cf436c9SEd Tanous } 3897cf436c9SEd Tanous // Loop the object and look for links 3907cf436c9SEd Tanous for (auto& element : *obj) 3917cf436c9SEd Tanous { 392e479ad58SNan Zhou bool localInLinks = inLinks; 393e479ad58SNan Zhou if (!localInLinks) 3947cf436c9SEd Tanous { 3957cf436c9SEd Tanous // Check if this is a links node 396e479ad58SNan Zhou localInLinks = element.first == "Links"; 3977cf436c9SEd Tanous } 3987cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 3997cf436c9SEd Tanous // Per section 7.3 of the redfish specification 400e479ad58SNan Zhou if (localInLinks && eType == ExpandType::NotLinks) 4017cf436c9SEd Tanous { 4027cf436c9SEd Tanous continue; 4037cf436c9SEd Tanous } 404e479ad58SNan Zhou if (!localInLinks && eType == ExpandType::Links) 4057cf436c9SEd Tanous { 4067cf436c9SEd Tanous continue; 4077cf436c9SEd Tanous } 4087cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / element.first; 4097cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 4107cf436c9SEd Tanous 4117cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 412e479ad58SNan Zhou localInLinks, out); 4137cf436c9SEd Tanous } 4147cf436c9SEd Tanous } 4157cf436c9SEd Tanous 4167cf436c9SEd Tanous inline std::vector<ExpandNode> 41772c3ae33SNan Zhou findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse) 4187cf436c9SEd Tanous { 4197cf436c9SEd Tanous std::vector<ExpandNode> ret; 42072c3ae33SNan Zhou const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 4217cf436c9SEd Tanous findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 4227cf436c9SEd Tanous return ret; 4237cf436c9SEd Tanous } 4247cf436c9SEd Tanous 42572c3ae33SNan Zhou // Formats a query parameter string for the sub-query. 42672c3ae33SNan Zhou // This function shall handle $select when it is added. 42772c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand, 42872c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers 42972c3ae33SNan Zhou inline std::string formatQueryForExpand(const Query& query) 43072c3ae33SNan Zhou { 43172c3ae33SNan Zhou // query.expandLevel<=1: no need to do subqueries 43272c3ae33SNan Zhou if (query.expandLevel <= 1) 43372c3ae33SNan Zhou { 43472c3ae33SNan Zhou return {}; 43572c3ae33SNan Zhou } 43672c3ae33SNan Zhou std::string str = "?$expand="; 43772c3ae33SNan Zhou switch (query.expandType) 43872c3ae33SNan Zhou { 43972c3ae33SNan Zhou case ExpandType::None: 44072c3ae33SNan Zhou return {}; 44172c3ae33SNan Zhou case ExpandType::Links: 44272c3ae33SNan Zhou str += '~'; 44372c3ae33SNan Zhou break; 44472c3ae33SNan Zhou case ExpandType::NotLinks: 44572c3ae33SNan Zhou str += '.'; 44672c3ae33SNan Zhou break; 44772c3ae33SNan Zhou case ExpandType::Both: 44872c3ae33SNan Zhou str += '*'; 44972c3ae33SNan Zhou break; 45072c3ae33SNan Zhou default: 45172c3ae33SNan Zhou return {}; 45272c3ae33SNan Zhou } 45372c3ae33SNan Zhou str += "($levels="; 45472c3ae33SNan Zhou str += std::to_string(query.expandLevel - 1); 45572c3ae33SNan Zhou str += ')'; 45672c3ae33SNan Zhou return str; 45772c3ae33SNan Zhou } 45872c3ae33SNan Zhou 4597cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 4607cf436c9SEd Tanous { 4617cf436c9SEd Tanous public: 4627cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 4637cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 4647cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 4657cf436c9SEd Tanous // class manages the final "merge" of the json resources. 4667cf436c9SEd Tanous MultiAsyncResp(crow::App& app, 4677cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 4687cf436c9SEd Tanous app(app), 4697cf436c9SEd Tanous finalRes(std::move(finalResIn)) 4707cf436c9SEd Tanous {} 4717cf436c9SEd Tanous 4727cf436c9SEd Tanous void addAwaitingResponse( 47372c3ae33SNan Zhou std::shared_ptr<bmcweb::AsyncResp>& res, 4747cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 4757cf436c9SEd Tanous { 4767cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 47772c3ae33SNan Zhou placeResultStatic, shared_from_this(), finalExpandLocation)); 4787cf436c9SEd Tanous } 4797cf436c9SEd Tanous 48072c3ae33SNan Zhou void placeResult(const nlohmann::json::json_pointer& locationToPlace, 4817cf436c9SEd Tanous crow::Response& res) 4827cf436c9SEd Tanous { 4837cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 4847cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 4857cf436c9SEd Tanous } 4867cf436c9SEd Tanous 48772c3ae33SNan Zhou // Handles the very first level of Expand, and starts a chain of sub-queries 48872c3ae33SNan Zhou // for deeper levels. 48972c3ae33SNan Zhou void startQuery(const Query& query) 49072c3ae33SNan Zhou { 49172c3ae33SNan Zhou std::vector<ExpandNode> nodes = 49272c3ae33SNan Zhou findNavigationReferences(query.expandType, finalRes->res.jsonValue); 4937cf436c9SEd Tanous BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 49472c3ae33SNan Zhou const std::string queryStr = formatQueryForExpand(query); 4957cf436c9SEd Tanous for (const ExpandNode& node : nodes) 4967cf436c9SEd Tanous { 49772c3ae33SNan Zhou const std::string subQuery = node.uri + queryStr; 49872c3ae33SNan Zhou BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 4997cf436c9SEd Tanous std::error_code ec; 50072c3ae33SNan Zhou crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 5017cf436c9SEd Tanous ec); 5027cf436c9SEd Tanous if (ec) 5037cf436c9SEd Tanous { 50472c3ae33SNan Zhou messages::internalError(finalRes->res); 5057cf436c9SEd Tanous return; 5067cf436c9SEd Tanous } 5077cf436c9SEd Tanous 5087cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 5097cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " 5107cf436c9SEd Tanous << &asyncResp->res; 51172c3ae33SNan Zhou 51272c3ae33SNan Zhou addAwaitingResponse(asyncResp, node.location); 5137cf436c9SEd Tanous app.handle(newReq, asyncResp); 5147cf436c9SEd Tanous } 5157cf436c9SEd Tanous } 5167cf436c9SEd Tanous 5177cf436c9SEd Tanous private: 51872c3ae33SNan Zhou static void 51972c3ae33SNan Zhou placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 5207cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 5217cf436c9SEd Tanous crow::Response& res) 5227cf436c9SEd Tanous { 52372c3ae33SNan Zhou multi->placeResult(locationToPlace, res); 5247cf436c9SEd Tanous } 5257cf436c9SEd Tanous 5267cf436c9SEd Tanous crow::App& app; 5277cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 5287cf436c9SEd Tanous }; 5297cf436c9SEd Tanous 5302a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res) 5312a68dc80SEd Tanous { 5322a68dc80SEd Tanous nlohmann::json::object_t* obj = 5332a68dc80SEd Tanous res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 5342a68dc80SEd Tanous if (obj == nullptr) 5352a68dc80SEd Tanous { 5362a68dc80SEd Tanous // Shouldn't be possible. All responses should be objects. 5372a68dc80SEd Tanous messages::internalError(res); 5382a68dc80SEd Tanous return; 5392a68dc80SEd Tanous } 5402a68dc80SEd Tanous 5412a68dc80SEd Tanous BMCWEB_LOG_DEBUG << "Handling top/skip"; 5422a68dc80SEd Tanous nlohmann::json::object_t::iterator members = obj->find("Members"); 5432a68dc80SEd Tanous if (members == obj->end()) 5442a68dc80SEd Tanous { 5452a68dc80SEd Tanous // From the Redfish specification 7.3.1 5462a68dc80SEd Tanous // ... the HTTP 400 Bad Request status code with the 5472a68dc80SEd Tanous // QueryNotSupportedOnResource message from the Base Message Registry 5482a68dc80SEd Tanous // for any supported query parameters that apply only to resource 5492a68dc80SEd Tanous // collections but are used on singular resources. 5502a68dc80SEd Tanous messages::queryNotSupportedOnResource(res); 5512a68dc80SEd Tanous return; 5522a68dc80SEd Tanous } 5532a68dc80SEd Tanous 5542a68dc80SEd Tanous nlohmann::json::array_t* arr = 5552a68dc80SEd Tanous members->second.get_ptr<nlohmann::json::array_t*>(); 5562a68dc80SEd Tanous if (arr == nullptr) 5572a68dc80SEd Tanous { 5582a68dc80SEd Tanous messages::internalError(res); 5592a68dc80SEd Tanous return; 5602a68dc80SEd Tanous } 5612a68dc80SEd Tanous 5622a68dc80SEd Tanous // Per section 7.3.1 of the Redfish specification, $skip is run before $top 5632a68dc80SEd Tanous // Can only skip as many values as we have 5642a68dc80SEd Tanous size_t skip = std::min(arr->size(), query.skip); 5652a68dc80SEd Tanous arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 5662a68dc80SEd Tanous 5672a68dc80SEd Tanous size_t top = std::min(arr->size(), query.top); 5682a68dc80SEd Tanous arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 5692a68dc80SEd Tanous } 5702a68dc80SEd Tanous 5717cf436c9SEd Tanous inline void 572*593f6449SNan Zhou processAllParams(crow::App& app, const Query& query, 5737cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 5747cf436c9SEd Tanous crow::Response& intermediateResponse) 575f4c99e70SEd Tanous { 576f4c99e70SEd Tanous if (!completionHandler) 577f4c99e70SEd Tanous { 578f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Function was invalid?"; 579f4c99e70SEd Tanous return; 580f4c99e70SEd Tanous } 581f4c99e70SEd Tanous 582f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing query params"; 583f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 584f4c99e70SEd Tanous // params. 585f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 586f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 587f4c99e70SEd Tanous { 588f4c99e70SEd Tanous completionHandler(intermediateResponse); 589f4c99e70SEd Tanous return; 590f4c99e70SEd Tanous } 591f4c99e70SEd Tanous if (query.isOnly) 592f4c99e70SEd Tanous { 593f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 594f4c99e70SEd Tanous return; 595f4c99e70SEd Tanous } 5962a68dc80SEd Tanous 5972a68dc80SEd Tanous if (query.top != std::numeric_limits<size_t>::max() || query.skip != 0) 5982a68dc80SEd Tanous { 5992a68dc80SEd Tanous processTopAndSkip(query, intermediateResponse); 6002a68dc80SEd Tanous } 6012a68dc80SEd Tanous 6027cf436c9SEd Tanous if (query.expandType != ExpandType::None) 6037cf436c9SEd Tanous { 6047cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Executing expand query"; 6057cf436c9SEd Tanous // TODO(ed) this is a copy of the response object. Admittedly, 6067cf436c9SEd Tanous // we're inherently doing something inefficient, but we shouldn't 6077cf436c9SEd Tanous // have to do a full copy 6087cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 6097cf436c9SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 6107cf436c9SEd Tanous asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue); 6117cf436c9SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 6127cf436c9SEd Tanous 61372c3ae33SNan Zhou multi->startQuery(query); 6147cf436c9SEd Tanous return; 6157cf436c9SEd Tanous } 616f4c99e70SEd Tanous completionHandler(intermediateResponse); 617f4c99e70SEd Tanous } 618f4c99e70SEd Tanous 619f4c99e70SEd Tanous } // namespace query_param 620f4c99e70SEd Tanous } // namespace redfish 621