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 8*7cf436c9SEd Tanous #include <charconv> 9f4c99e70SEd Tanous #include <string> 10f4c99e70SEd Tanous #include <string_view> 11*7cf436c9SEd Tanous #include <utility> 12f4c99e70SEd Tanous #include <vector> 13f4c99e70SEd Tanous 14f4c99e70SEd Tanous namespace redfish 15f4c99e70SEd Tanous { 16f4c99e70SEd Tanous namespace query_param 17f4c99e70SEd Tanous { 18f4c99e70SEd Tanous 19*7cf436c9SEd Tanous enum class ExpandType : uint8_t 20*7cf436c9SEd Tanous { 21*7cf436c9SEd Tanous None, 22*7cf436c9SEd Tanous Links, 23*7cf436c9SEd Tanous NotLinks, 24*7cf436c9SEd Tanous Both, 25*7cf436c9SEd Tanous }; 26*7cf436c9SEd Tanous 27f4c99e70SEd Tanous struct Query 28f4c99e70SEd Tanous { 29f4c99e70SEd Tanous bool isOnly = false; 30*7cf436c9SEd Tanous uint8_t expandLevel = 1; 31*7cf436c9SEd Tanous ExpandType expandType = ExpandType::None; 32f4c99e70SEd Tanous }; 33f4c99e70SEd Tanous 34*7cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query) 35*7cf436c9SEd Tanous { 36*7cf436c9SEd Tanous if (value.empty()) 37*7cf436c9SEd Tanous { 38*7cf436c9SEd Tanous return false; 39*7cf436c9SEd Tanous } 40*7cf436c9SEd Tanous switch (value[0]) 41*7cf436c9SEd Tanous { 42*7cf436c9SEd Tanous case '*': 43*7cf436c9SEd Tanous query.expandType = ExpandType::Both; 44*7cf436c9SEd Tanous break; 45*7cf436c9SEd Tanous case '.': 46*7cf436c9SEd Tanous query.expandType = ExpandType::NotLinks; 47*7cf436c9SEd Tanous break; 48*7cf436c9SEd Tanous case '~': 49*7cf436c9SEd Tanous query.expandType = ExpandType::Links; 50*7cf436c9SEd Tanous break; 51*7cf436c9SEd Tanous default: 52*7cf436c9SEd Tanous return false; 53*7cf436c9SEd Tanous 54*7cf436c9SEd Tanous break; 55*7cf436c9SEd Tanous } 56*7cf436c9SEd Tanous value.remove_prefix(1); 57*7cf436c9SEd Tanous if (value.empty()) 58*7cf436c9SEd Tanous { 59*7cf436c9SEd Tanous query.expandLevel = 1; 60*7cf436c9SEd Tanous return true; 61*7cf436c9SEd Tanous } 62*7cf436c9SEd Tanous constexpr std::string_view levels = "($levels="; 63*7cf436c9SEd Tanous if (!value.starts_with(levels)) 64*7cf436c9SEd Tanous { 65*7cf436c9SEd Tanous return false; 66*7cf436c9SEd Tanous } 67*7cf436c9SEd Tanous value.remove_prefix(levels.size()); 68*7cf436c9SEd Tanous 69*7cf436c9SEd Tanous auto it = std::from_chars(value.data(), value.data() + value.size(), 70*7cf436c9SEd Tanous query.expandLevel); 71*7cf436c9SEd Tanous if (it.ec != std::errc()) 72*7cf436c9SEd Tanous { 73*7cf436c9SEd Tanous return false; 74*7cf436c9SEd Tanous } 75*7cf436c9SEd Tanous value.remove_prefix(static_cast<size_t>(it.ptr - value.data())); 76*7cf436c9SEd Tanous return value == ")"; 77*7cf436c9SEd Tanous } 78*7cf436c9SEd Tanous 79f4c99e70SEd Tanous inline std::optional<Query> 80f4c99e70SEd Tanous parseParameters(const boost::urls::params_view& urlParams, 81f4c99e70SEd Tanous crow::Response& res) 82f4c99e70SEd Tanous { 83f4c99e70SEd Tanous Query ret; 84f4c99e70SEd Tanous for (const boost::urls::params_view::value_type& it : urlParams) 85f4c99e70SEd Tanous { 86f4c99e70SEd Tanous std::string_view key(it.key.data(), it.key.size()); 87f4c99e70SEd Tanous std::string_view value(it.value.data(), it.value.size()); 88f4c99e70SEd Tanous if (key == "only") 89f4c99e70SEd Tanous { 90f4c99e70SEd Tanous if (!it.value.empty()) 91f4c99e70SEd Tanous { 92f4c99e70SEd Tanous messages::queryParameterValueFormatError(res, value, key); 93f4c99e70SEd Tanous return std::nullopt; 94f4c99e70SEd Tanous } 95f4c99e70SEd Tanous ret.isOnly = true; 96f4c99e70SEd Tanous } 97*7cf436c9SEd Tanous else if (key == "$expand") 98*7cf436c9SEd Tanous { 99*7cf436c9SEd Tanous if (!getExpandType(value, ret)) 100*7cf436c9SEd Tanous { 101*7cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 102*7cf436c9SEd Tanous return std::nullopt; 103f4c99e70SEd Tanous } 104*7cf436c9SEd Tanous } 105*7cf436c9SEd Tanous else 106*7cf436c9SEd Tanous { 107*7cf436c9SEd Tanous // Intentionally ignore other errors Redfish spec, 7.3.1 108*7cf436c9SEd Tanous if (key.starts_with("$")) 109*7cf436c9SEd Tanous { 110*7cf436c9SEd Tanous // Services shall return... The HTTP 501 Not Implemented 111*7cf436c9SEd Tanous // status code for any unsupported query parameters that 112*7cf436c9SEd Tanous // start with $ . 113*7cf436c9SEd Tanous messages::queryParameterValueFormatError(res, value, key); 114*7cf436c9SEd Tanous res.result(boost::beast::http::status::not_implemented); 115*7cf436c9SEd Tanous return std::nullopt; 116*7cf436c9SEd Tanous } 117*7cf436c9SEd Tanous // "Shall ignore unknown or unsupported query parameters that do 118*7cf436c9SEd Tanous // not begin with $ ." 119*7cf436c9SEd Tanous } 120*7cf436c9SEd Tanous } 121*7cf436c9SEd Tanous 122f4c99e70SEd Tanous return ret; 123f4c99e70SEd Tanous } 124f4c99e70SEd Tanous 125f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res, 126f4c99e70SEd Tanous std::function<void(crow::Response&)>& completionHandler) 127f4c99e70SEd Tanous { 128f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing only query param"; 129f4c99e70SEd Tanous auto itMembers = res.jsonValue.find("Members"); 130f4c99e70SEd Tanous if (itMembers == res.jsonValue.end()) 131f4c99e70SEd Tanous { 132f4c99e70SEd Tanous messages::queryNotSupportedOnResource(res); 133f4c99e70SEd Tanous completionHandler(res); 134f4c99e70SEd Tanous return false; 135f4c99e70SEd Tanous } 136f4c99e70SEd Tanous auto itMemBegin = itMembers->begin(); 137f4c99e70SEd Tanous if (itMemBegin == itMembers->end() || itMembers->size() != 1) 138f4c99e70SEd Tanous { 139f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 140f4c99e70SEd Tanous << " element, returning full collection."; 141f4c99e70SEd Tanous completionHandler(res); 142f4c99e70SEd Tanous return false; 143f4c99e70SEd Tanous } 144f4c99e70SEd Tanous 145f4c99e70SEd Tanous auto itUrl = itMemBegin->find("@odata.id"); 146f4c99e70SEd Tanous if (itUrl == itMemBegin->end()) 147f4c99e70SEd Tanous { 148f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "No found odata.id"; 149f4c99e70SEd Tanous messages::internalError(res); 150f4c99e70SEd Tanous completionHandler(res); 151f4c99e70SEd Tanous return false; 152f4c99e70SEd Tanous } 153f4c99e70SEd Tanous const std::string* url = itUrl->get_ptr<const std::string*>(); 154f4c99e70SEd Tanous if (url == nullptr) 155f4c99e70SEd Tanous { 156f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 157f4c99e70SEd Tanous messages::internalError(res); 158f4c99e70SEd Tanous completionHandler(res); 159f4c99e70SEd Tanous return false; 160f4c99e70SEd Tanous } 161f4c99e70SEd Tanous // TODO(Ed) copy request headers? 162f4c99e70SEd Tanous // newReq.session = req.session; 163f4c99e70SEd Tanous std::error_code ec; 164f4c99e70SEd Tanous crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 165f4c99e70SEd Tanous if (ec) 166f4c99e70SEd Tanous { 167f4c99e70SEd Tanous messages::internalError(res); 168f4c99e70SEd Tanous completionHandler(res); 169f4c99e70SEd Tanous return false; 170f4c99e70SEd Tanous } 171f4c99e70SEd Tanous 172f4c99e70SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 173f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 174f4c99e70SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 175f4c99e70SEd Tanous asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 176f4c99e70SEd Tanous app.handle(newReq, asyncResp); 177f4c99e70SEd Tanous return true; 178f4c99e70SEd Tanous } 179f4c99e70SEd Tanous 180*7cf436c9SEd Tanous struct ExpandNode 181*7cf436c9SEd Tanous { 182*7cf436c9SEd Tanous nlohmann::json::json_pointer location; 183*7cf436c9SEd Tanous std::string uri; 184*7cf436c9SEd Tanous 185*7cf436c9SEd Tanous inline bool operator==(const ExpandNode& other) const 186*7cf436c9SEd Tanous { 187*7cf436c9SEd Tanous return location == other.location && uri == other.uri; 188*7cf436c9SEd Tanous } 189*7cf436c9SEd Tanous }; 190*7cf436c9SEd Tanous 191*7cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that 192*7cf436c9SEd Tanous // might need resolved. It recursively walks the jsonResponse object, looking 193*7cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the 194*7cf436c9SEd Tanous // tree that need to be expanded. The current json pointer location p is passed 195*7cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined 196*7cf436c9SEd Tanous // with the keys from the jsonResponse object 197*7cf436c9SEd Tanous inline void findNavigationReferencesRecursive( 198*7cf436c9SEd Tanous ExpandType eType, nlohmann::json& jsonResponse, 199*7cf436c9SEd Tanous const nlohmann::json::json_pointer& p, bool inLinks, 200*7cf436c9SEd Tanous std::vector<ExpandNode>& out) 201*7cf436c9SEd Tanous { 202*7cf436c9SEd Tanous // If no expand is needed, return early 203*7cf436c9SEd Tanous if (eType == ExpandType::None) 204*7cf436c9SEd Tanous { 205*7cf436c9SEd Tanous return; 206*7cf436c9SEd Tanous } 207*7cf436c9SEd Tanous nlohmann::json::array_t* array = 208*7cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::array_t*>(); 209*7cf436c9SEd Tanous if (array != nullptr) 210*7cf436c9SEd Tanous { 211*7cf436c9SEd Tanous size_t index = 0; 212*7cf436c9SEd Tanous // For arrays, walk every element in the array 213*7cf436c9SEd Tanous for (auto& element : *array) 214*7cf436c9SEd Tanous { 215*7cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / index; 216*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 217*7cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 218*7cf436c9SEd Tanous out); 219*7cf436c9SEd Tanous index++; 220*7cf436c9SEd Tanous } 221*7cf436c9SEd Tanous } 222*7cf436c9SEd Tanous nlohmann::json::object_t* obj = 223*7cf436c9SEd Tanous jsonResponse.get_ptr<nlohmann::json::object_t*>(); 224*7cf436c9SEd Tanous if (obj == nullptr) 225*7cf436c9SEd Tanous { 226*7cf436c9SEd Tanous return; 227*7cf436c9SEd Tanous } 228*7cf436c9SEd Tanous // Navigation References only ever have a single element 229*7cf436c9SEd Tanous if (obj->size() == 1) 230*7cf436c9SEd Tanous { 231*7cf436c9SEd Tanous if (obj->begin()->first == "@odata.id") 232*7cf436c9SEd Tanous { 233*7cf436c9SEd Tanous const std::string* uri = 234*7cf436c9SEd Tanous obj->begin()->second.get_ptr<const std::string*>(); 235*7cf436c9SEd Tanous if (uri != nullptr) 236*7cf436c9SEd Tanous { 237*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 238*7cf436c9SEd Tanous out.push_back({p, *uri}); 239*7cf436c9SEd Tanous } 240*7cf436c9SEd Tanous } 241*7cf436c9SEd Tanous } 242*7cf436c9SEd Tanous // Loop the object and look for links 243*7cf436c9SEd Tanous for (auto& element : *obj) 244*7cf436c9SEd Tanous { 245*7cf436c9SEd Tanous if (!inLinks) 246*7cf436c9SEd Tanous { 247*7cf436c9SEd Tanous // Check if this is a links node 248*7cf436c9SEd Tanous inLinks = element.first == "Links"; 249*7cf436c9SEd Tanous } 250*7cf436c9SEd Tanous // Only traverse the parts of the tree the user asked for 251*7cf436c9SEd Tanous // Per section 7.3 of the redfish specification 252*7cf436c9SEd Tanous if (inLinks && eType == ExpandType::NotLinks) 253*7cf436c9SEd Tanous { 254*7cf436c9SEd Tanous continue; 255*7cf436c9SEd Tanous } 256*7cf436c9SEd Tanous if (!inLinks && eType == ExpandType::Links) 257*7cf436c9SEd Tanous { 258*7cf436c9SEd Tanous continue; 259*7cf436c9SEd Tanous } 260*7cf436c9SEd Tanous nlohmann::json::json_pointer newPtr = p / element.first; 261*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 262*7cf436c9SEd Tanous 263*7cf436c9SEd Tanous findNavigationReferencesRecursive(eType, element.second, newPtr, 264*7cf436c9SEd Tanous inLinks, out); 265*7cf436c9SEd Tanous } 266*7cf436c9SEd Tanous } 267*7cf436c9SEd Tanous 268*7cf436c9SEd Tanous inline std::vector<ExpandNode> 269*7cf436c9SEd Tanous findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse, 270*7cf436c9SEd Tanous const nlohmann::json::json_pointer& root) 271*7cf436c9SEd Tanous { 272*7cf436c9SEd Tanous std::vector<ExpandNode> ret; 273*7cf436c9SEd Tanous findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 274*7cf436c9SEd Tanous return ret; 275*7cf436c9SEd Tanous } 276*7cf436c9SEd Tanous 277*7cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 278*7cf436c9SEd Tanous { 279*7cf436c9SEd Tanous public: 280*7cf436c9SEd Tanous // This object takes a single asyncResp object as the "final" one, then 281*7cf436c9SEd Tanous // allows callers to attach sub-responses within the json tree that need 282*7cf436c9SEd Tanous // to be executed and filled into their appropriate locations. This 283*7cf436c9SEd Tanous // class manages the final "merge" of the json resources. 284*7cf436c9SEd Tanous MultiAsyncResp(crow::App& app, 285*7cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 286*7cf436c9SEd Tanous app(app), 287*7cf436c9SEd Tanous finalRes(std::move(finalResIn)) 288*7cf436c9SEd Tanous {} 289*7cf436c9SEd Tanous 290*7cf436c9SEd Tanous void addAwaitingResponse( 291*7cf436c9SEd Tanous Query query, std::shared_ptr<bmcweb::AsyncResp>& res, 292*7cf436c9SEd Tanous const nlohmann::json::json_pointer& finalExpandLocation) 293*7cf436c9SEd Tanous { 294*7cf436c9SEd Tanous res->res.setCompleteRequestHandler(std::bind_front( 295*7cf436c9SEd Tanous onEndStatic, shared_from_this(), query, finalExpandLocation)); 296*7cf436c9SEd Tanous } 297*7cf436c9SEd Tanous 298*7cf436c9SEd Tanous void onEnd(Query query, const nlohmann::json::json_pointer& locationToPlace, 299*7cf436c9SEd Tanous crow::Response& res) 300*7cf436c9SEd Tanous { 301*7cf436c9SEd Tanous nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 302*7cf436c9SEd Tanous finalObj = std::move(res.jsonValue); 303*7cf436c9SEd Tanous 304*7cf436c9SEd Tanous if (query.expandLevel <= 0) 305*7cf436c9SEd Tanous { 306*7cf436c9SEd Tanous // Last level to expand, no need to go deeper 307*7cf436c9SEd Tanous return; 308*7cf436c9SEd Tanous } 309*7cf436c9SEd Tanous // Now decrease the depth by one to account for the tree node we 310*7cf436c9SEd Tanous // just resolved 311*7cf436c9SEd Tanous query.expandLevel--; 312*7cf436c9SEd Tanous 313*7cf436c9SEd Tanous std::vector<ExpandNode> nodes = findNavigationReferences( 314*7cf436c9SEd Tanous query.expandType, finalObj, locationToPlace); 315*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 316*7cf436c9SEd Tanous for (const ExpandNode& node : nodes) 317*7cf436c9SEd Tanous { 318*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Expanding " << locationToPlace; 319*7cf436c9SEd Tanous std::error_code ec; 320*7cf436c9SEd Tanous crow::Request newReq({boost::beast::http::verb::get, node.uri, 11}, 321*7cf436c9SEd Tanous ec); 322*7cf436c9SEd Tanous if (ec) 323*7cf436c9SEd Tanous { 324*7cf436c9SEd Tanous messages::internalError(res); 325*7cf436c9SEd Tanous return; 326*7cf436c9SEd Tanous } 327*7cf436c9SEd Tanous 328*7cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 329*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "setting completion handler on " 330*7cf436c9SEd Tanous << &asyncResp->res; 331*7cf436c9SEd Tanous addAwaitingResponse(query, asyncResp, node.location); 332*7cf436c9SEd Tanous app.handle(newReq, asyncResp); 333*7cf436c9SEd Tanous } 334*7cf436c9SEd Tanous } 335*7cf436c9SEd Tanous 336*7cf436c9SEd Tanous private: 337*7cf436c9SEd Tanous static void onEndStatic(const std::shared_ptr<MultiAsyncResp>& multi, 338*7cf436c9SEd Tanous Query query, 339*7cf436c9SEd Tanous const nlohmann::json::json_pointer& locationToPlace, 340*7cf436c9SEd Tanous crow::Response& res) 341*7cf436c9SEd Tanous { 342*7cf436c9SEd Tanous multi->onEnd(query, locationToPlace, res); 343*7cf436c9SEd Tanous } 344*7cf436c9SEd Tanous 345*7cf436c9SEd Tanous crow::App& app; 346*7cf436c9SEd Tanous std::shared_ptr<bmcweb::AsyncResp> finalRes; 347*7cf436c9SEd Tanous }; 348*7cf436c9SEd Tanous 349*7cf436c9SEd Tanous inline void 350*7cf436c9SEd Tanous processAllParams(crow::App& app, const Query query, 351*7cf436c9SEd Tanous 352*7cf436c9SEd Tanous std::function<void(crow::Response&)>& completionHandler, 353*7cf436c9SEd Tanous crow::Response& intermediateResponse) 354f4c99e70SEd Tanous { 355f4c99e70SEd Tanous if (!completionHandler) 356f4c99e70SEd Tanous { 357f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Function was invalid?"; 358f4c99e70SEd Tanous return; 359f4c99e70SEd Tanous } 360f4c99e70SEd Tanous 361f4c99e70SEd Tanous BMCWEB_LOG_DEBUG << "Processing query params"; 362f4c99e70SEd Tanous // If the request failed, there's no reason to even try to run query 363f4c99e70SEd Tanous // params. 364f4c99e70SEd Tanous if (intermediateResponse.resultInt() < 200 || 365f4c99e70SEd Tanous intermediateResponse.resultInt() >= 400) 366f4c99e70SEd Tanous { 367f4c99e70SEd Tanous completionHandler(intermediateResponse); 368f4c99e70SEd Tanous return; 369f4c99e70SEd Tanous } 370f4c99e70SEd Tanous if (query.isOnly) 371f4c99e70SEd Tanous { 372f4c99e70SEd Tanous processOnly(app, intermediateResponse, completionHandler); 373f4c99e70SEd Tanous return; 374f4c99e70SEd Tanous } 375*7cf436c9SEd Tanous if (query.expandType != ExpandType::None) 376*7cf436c9SEd Tanous { 377*7cf436c9SEd Tanous BMCWEB_LOG_DEBUG << "Executing expand query"; 378*7cf436c9SEd Tanous // TODO(ed) this is a copy of the response object. Admittedly, 379*7cf436c9SEd Tanous // we're inherently doing something inefficient, but we shouldn't 380*7cf436c9SEd Tanous // have to do a full copy 381*7cf436c9SEd Tanous auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 382*7cf436c9SEd Tanous asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 383*7cf436c9SEd Tanous asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue); 384*7cf436c9SEd Tanous auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 385*7cf436c9SEd Tanous 386*7cf436c9SEd Tanous // Start the chain by "ending" the root response 387*7cf436c9SEd Tanous multi->onEnd(query, nlohmann::json::json_pointer(""), asyncResp->res); 388*7cf436c9SEd Tanous return; 389*7cf436c9SEd Tanous } 390f4c99e70SEd Tanous completionHandler(intermediateResponse); 391f4c99e70SEd Tanous } 392f4c99e70SEd Tanous 393f4c99e70SEd Tanous } // namespace query_param 394f4c99e70SEd Tanous } // namespace redfish 395