1 #pragma once 2 #include "bmcweb_config.h" 3 4 #include "app.hpp" 5 #include "async_resp.hpp" 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 11 #include <sys/types.h> 12 13 #include <boost/algorithm/string/classification.hpp> 14 #include <boost/algorithm/string/split.hpp> 15 #include <boost/beast/http/message.hpp> // IWYU pragma: keep 16 #include <boost/beast/http/status.hpp> 17 #include <boost/beast/http/verb.hpp> 18 #include <boost/url/error.hpp> 19 #include <boost/url/params_view.hpp> 20 #include <boost/url/string.hpp> 21 #include <nlohmann/json.hpp> 22 23 #include <algorithm> 24 #include <array> 25 #include <cctype> 26 #include <charconv> 27 #include <cstdint> 28 #include <functional> 29 #include <iterator> 30 #include <limits> 31 #include <map> 32 #include <memory> 33 #include <optional> 34 #include <span> 35 #include <string> 36 #include <string_view> 37 #include <system_error> 38 #include <unordered_set> 39 #include <utility> 40 #include <vector> 41 42 // IWYU pragma: no_include <boost/url/impl/params_view.hpp> 43 // IWYU pragma: no_include <boost/beast/http/impl/message.hpp> 44 // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp> 45 // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp> 46 // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp> 47 // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp> 48 // IWYU pragma: no_include <stdint.h> 49 50 namespace redfish 51 { 52 namespace query_param 53 { 54 inline constexpr size_t maxEntriesPerPage = 1000; 55 56 enum class ExpandType : uint8_t 57 { 58 None, 59 Links, 60 NotLinks, 61 Both, 62 }; 63 64 // The struct stores the parsed query parameters of the default Redfish route. 65 struct Query 66 { 67 // Only 68 bool isOnly = false; 69 // Expand 70 uint8_t expandLevel = 0; 71 ExpandType expandType = ExpandType::None; 72 73 // Skip 74 std::optional<size_t> skip = std::nullopt; 75 76 // Top 77 78 std::optional<size_t> top = std::nullopt; 79 80 // Select 81 std::unordered_set<std::string> selectedProperties = {}; 82 }; 83 84 // The struct defines how resource handlers in redfish-core/lib/ can handle 85 // query parameters themselves, so that the default Redfish route will delegate 86 // the processing. 87 struct QueryCapabilities 88 { 89 bool canDelegateOnly = false; 90 bool canDelegateTop = false; 91 bool canDelegateSkip = false; 92 uint8_t canDelegateExpandLevel = 0; 93 bool canDelegateSelect = false; 94 }; 95 96 // Delegates query parameters according to the given |queryCapabilities| 97 // This function doesn't check query parameter conflicts since the parse 98 // function will take care of it. 99 // Returns a delegated query object which can be used by individual resource 100 // handlers so that handlers don't need to query again. 101 inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query) 102 { 103 Query delegated; 104 // delegate only 105 if (query.isOnly && queryCapabilities.canDelegateOnly) 106 { 107 delegated.isOnly = true; 108 query.isOnly = false; 109 } 110 // delegate expand as much as we can 111 if (query.expandType != ExpandType::None) 112 { 113 delegated.expandType = query.expandType; 114 if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel) 115 { 116 query.expandType = ExpandType::None; 117 delegated.expandLevel = query.expandLevel; 118 query.expandLevel = 0; 119 } 120 else 121 { 122 query.expandLevel -= queryCapabilities.canDelegateExpandLevel; 123 delegated.expandLevel = queryCapabilities.canDelegateExpandLevel; 124 } 125 } 126 127 // delegate top 128 if (query.top && queryCapabilities.canDelegateTop) 129 { 130 delegated.top = query.top; 131 query.top = std::nullopt; 132 } 133 134 // delegate skip 135 if (query.skip && queryCapabilities.canDelegateSkip) 136 { 137 delegated.skip = query.skip; 138 query.skip = 0; 139 } 140 141 // delegate select 142 if (!query.selectedProperties.empty() && 143 queryCapabilities.canDelegateSelect) 144 { 145 delegated.selectedProperties = std::move(query.selectedProperties); 146 query.selectedProperties.clear(); 147 } 148 return delegated; 149 } 150 151 inline bool getExpandType(std::string_view value, Query& query) 152 { 153 if (value.empty()) 154 { 155 return false; 156 } 157 switch (value[0]) 158 { 159 case '*': 160 query.expandType = ExpandType::Both; 161 break; 162 case '.': 163 query.expandType = ExpandType::NotLinks; 164 break; 165 case '~': 166 query.expandType = ExpandType::Links; 167 break; 168 default: 169 return false; 170 171 break; 172 } 173 value.remove_prefix(1); 174 if (value.empty()) 175 { 176 query.expandLevel = 1; 177 return true; 178 } 179 constexpr std::string_view levels = "($levels="; 180 if (!value.starts_with(levels)) 181 { 182 return false; 183 } 184 value.remove_prefix(levels.size()); 185 186 auto it = std::from_chars(value.data(), value.data() + value.size(), 187 query.expandLevel); 188 if (it.ec != std::errc()) 189 { 190 return false; 191 } 192 value.remove_prefix(static_cast<size_t>(it.ptr - value.data())); 193 return value == ")"; 194 } 195 196 enum class QueryError 197 { 198 Ok, 199 OutOfRange, 200 ValueFormat, 201 }; 202 203 inline QueryError getNumericParam(std::string_view value, size_t& param) 204 { 205 std::from_chars_result r = 206 std::from_chars(value.data(), value.data() + value.size(), param); 207 208 // If the number wasn't representable in the type, it's out of range 209 if (r.ec == std::errc::result_out_of_range) 210 { 211 return QueryError::OutOfRange; 212 } 213 // All other errors are value format 214 if (r.ec != std::errc()) 215 { 216 return QueryError::ValueFormat; 217 } 218 return QueryError::Ok; 219 } 220 221 inline QueryError getSkipParam(std::string_view value, Query& query) 222 { 223 return getNumericParam(value, query.skip.emplace()); 224 } 225 226 inline QueryError getTopParam(std::string_view value, Query& query) 227 { 228 QueryError ret = getNumericParam(value, query.top.emplace()); 229 if (ret != QueryError::Ok) 230 { 231 return ret; 232 } 233 234 // Range check for sanity. 235 if (query.top > maxEntriesPerPage) 236 { 237 return QueryError::OutOfRange; 238 } 239 240 return QueryError::Ok; 241 } 242 243 // Validates the property in the $select parameter. Every character is among 244 // [a-zA-Z0-9\/#@_.] (taken from Redfish spec, section 9.6 Properties) 245 inline bool isSelectedPropertyAllowed(std::string_view property) 246 { 247 // These a magic number, but with it it's less likely that this code 248 // introduces CVE; e.g., too large properties crash the service. 249 constexpr int maxPropertyLength = 60; 250 if (property.empty() || property.size() > maxPropertyLength) 251 { 252 return false; 253 } 254 for (char ch : property) 255 { 256 if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '/' && 257 ch != '#' && ch != '@' && ch != '.') 258 { 259 return false; 260 } 261 } 262 return true; 263 } 264 265 // Parses and validates the $select parameter. 266 // As per OData URL Conventions and Redfish Spec, the $select values shall be 267 // comma separated Resource Path 268 // Ref: 269 // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 270 // 2. 271 // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt 272 inline bool getSelectParam(std::string_view value, Query& query) 273 { 274 std::vector<std::string> properties; 275 boost::split(properties, value, boost::is_any_of(",")); 276 if (properties.empty()) 277 { 278 return false; 279 } 280 // These a magic number, but with it it's less likely that this code 281 // introduces CVE; e.g., too large properties crash the service. 282 constexpr int maxNumProperties = 10; 283 if (properties.size() > maxNumProperties) 284 { 285 return false; 286 } 287 for (std::string& property : properties) 288 { 289 if (!isSelectedPropertyAllowed(property)) 290 { 291 return false; 292 } 293 property.insert(property.begin(), '/'); 294 } 295 query.selectedProperties = {std::make_move_iterator(properties.begin()), 296 std::make_move_iterator(properties.end())}; 297 // Per the Redfish spec section 7.3.3, the service shall select certain 298 // properties as if $select was omitted. 299 constexpr std::array<std::string_view, 5> reservedProperties = { 300 "/@odata.id", "/@odata.type", "/@odata.context", "/@odata.etag", 301 "/error"}; 302 for (auto const& str : reservedProperties) 303 { 304 query.selectedProperties.emplace(std::string(str)); 305 } 306 return true; 307 } 308 309 inline std::optional<Query> 310 parseParameters(const boost::urls::params_view& urlParams, 311 crow::Response& res) 312 { 313 Query ret; 314 for (const boost::urls::params_view::value_type& it : urlParams) 315 { 316 std::string_view key(it.key.data(), it.key.size()); 317 std::string_view value(it.value.data(), it.value.size()); 318 if (key == "only") 319 { 320 if (!it.value.empty()) 321 { 322 messages::queryParameterValueFormatError(res, value, key); 323 return std::nullopt; 324 } 325 ret.isOnly = true; 326 } 327 else if (key == "$expand" && bmcwebInsecureEnableQueryParams) 328 { 329 if (!getExpandType(value, ret)) 330 { 331 messages::queryParameterValueFormatError(res, value, key); 332 return std::nullopt; 333 } 334 } 335 else if (key == "$top") 336 { 337 QueryError topRet = getTopParam(value, ret); 338 if (topRet == QueryError::ValueFormat) 339 { 340 messages::queryParameterValueFormatError(res, value, key); 341 return std::nullopt; 342 } 343 if (topRet == QueryError::OutOfRange) 344 { 345 messages::queryParameterOutOfRange( 346 res, value, "$top", 347 "0-" + std::to_string(maxEntriesPerPage)); 348 return std::nullopt; 349 } 350 } 351 else if (key == "$skip") 352 { 353 QueryError topRet = getSkipParam(value, ret); 354 if (topRet == QueryError::ValueFormat) 355 { 356 messages::queryParameterValueFormatError(res, value, key); 357 return std::nullopt; 358 } 359 if (topRet == QueryError::OutOfRange) 360 { 361 messages::queryParameterOutOfRange( 362 res, value, key, 363 "0-" + std::to_string(std::numeric_limits<size_t>::max())); 364 return std::nullopt; 365 } 366 } 367 else if (key == "$select" && bmcwebInsecureEnableQueryParams) 368 { 369 if (!getSelectParam(value, ret)) 370 { 371 messages::queryParameterValueFormatError(res, value, key); 372 return std::nullopt; 373 } 374 } 375 else 376 { 377 // Intentionally ignore other errors Redfish spec, 7.3.1 378 if (key.starts_with("$")) 379 { 380 // Services shall return... The HTTP 501 Not Implemented 381 // status code for any unsupported query parameters that 382 // start with $ . 383 messages::queryParameterValueFormatError(res, value, key); 384 res.result(boost::beast::http::status::not_implemented); 385 return std::nullopt; 386 } 387 // "Shall ignore unknown or unsupported query parameters that do 388 // not begin with $ ." 389 } 390 } 391 392 if (ret.expandType != ExpandType::None && !ret.selectedProperties.empty()) 393 { 394 messages::queryCombinationInvalid(res); 395 return std::nullopt; 396 } 397 398 return ret; 399 } 400 401 inline bool processOnly(crow::App& app, crow::Response& res, 402 std::function<void(crow::Response&)>& completionHandler) 403 { 404 BMCWEB_LOG_DEBUG << "Processing only query param"; 405 auto itMembers = res.jsonValue.find("Members"); 406 if (itMembers == res.jsonValue.end()) 407 { 408 messages::queryNotSupportedOnResource(res); 409 completionHandler(res); 410 return false; 411 } 412 auto itMemBegin = itMembers->begin(); 413 if (itMemBegin == itMembers->end() || itMembers->size() != 1) 414 { 415 BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size() 416 << " element, returning full collection."; 417 completionHandler(res); 418 return false; 419 } 420 421 auto itUrl = itMemBegin->find("@odata.id"); 422 if (itUrl == itMemBegin->end()) 423 { 424 BMCWEB_LOG_DEBUG << "No found odata.id"; 425 messages::internalError(res); 426 completionHandler(res); 427 return false; 428 } 429 const std::string* url = itUrl->get_ptr<const std::string*>(); 430 if (url == nullptr) 431 { 432 BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????"; 433 messages::internalError(res); 434 completionHandler(res); 435 return false; 436 } 437 // TODO(Ed) copy request headers? 438 // newReq.session = req.session; 439 std::error_code ec; 440 crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec); 441 if (ec) 442 { 443 messages::internalError(res); 444 completionHandler(res); 445 return false; 446 } 447 448 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 449 BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res; 450 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 451 asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper()); 452 app.handle(newReq, asyncResp); 453 return true; 454 } 455 456 struct ExpandNode 457 { 458 nlohmann::json::json_pointer location; 459 std::string uri; 460 461 inline bool operator==(const ExpandNode& other) const 462 { 463 return location == other.location && uri == other.uri; 464 } 465 }; 466 467 // Walks a json object looking for Redfish NavigationReference entries that 468 // might need resolved. It recursively walks the jsonResponse object, looking 469 // for links at every level, and returns a list (out) of locations within the 470 // tree that need to be expanded. The current json pointer location p is passed 471 // in to reference the current node that's being expanded, so it can be combined 472 // with the keys from the jsonResponse object 473 inline void findNavigationReferencesRecursive( 474 ExpandType eType, nlohmann::json& jsonResponse, 475 const nlohmann::json::json_pointer& p, bool inLinks, 476 std::vector<ExpandNode>& out) 477 { 478 // If no expand is needed, return early 479 if (eType == ExpandType::None) 480 { 481 return; 482 } 483 nlohmann::json::array_t* array = 484 jsonResponse.get_ptr<nlohmann::json::array_t*>(); 485 if (array != nullptr) 486 { 487 size_t index = 0; 488 // For arrays, walk every element in the array 489 for (auto& element : *array) 490 { 491 nlohmann::json::json_pointer newPtr = p / index; 492 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string(); 493 findNavigationReferencesRecursive(eType, element, newPtr, inLinks, 494 out); 495 index++; 496 } 497 } 498 nlohmann::json::object_t* obj = 499 jsonResponse.get_ptr<nlohmann::json::object_t*>(); 500 if (obj == nullptr) 501 { 502 return; 503 } 504 // Navigation References only ever have a single element 505 if (obj->size() == 1) 506 { 507 if (obj->begin()->first == "@odata.id") 508 { 509 const std::string* uri = 510 obj->begin()->second.get_ptr<const std::string*>(); 511 if (uri != nullptr) 512 { 513 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string(); 514 out.push_back({p, *uri}); 515 } 516 } 517 } 518 // Loop the object and look for links 519 for (auto& element : *obj) 520 { 521 bool localInLinks = inLinks; 522 if (!localInLinks) 523 { 524 // Check if this is a links node 525 localInLinks = element.first == "Links"; 526 } 527 // Only traverse the parts of the tree the user asked for 528 // Per section 7.3 of the redfish specification 529 if (localInLinks && eType == ExpandType::NotLinks) 530 { 531 continue; 532 } 533 if (!localInLinks && eType == ExpandType::Links) 534 { 535 continue; 536 } 537 nlohmann::json::json_pointer newPtr = p / element.first; 538 BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr; 539 540 findNavigationReferencesRecursive(eType, element.second, newPtr, 541 localInLinks, out); 542 } 543 } 544 545 inline std::vector<ExpandNode> 546 findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse) 547 { 548 std::vector<ExpandNode> ret; 549 const nlohmann::json::json_pointer root = nlohmann::json::json_pointer(""); 550 findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret); 551 return ret; 552 } 553 554 // Formats a query parameter string for the sub-query. 555 // Returns std::nullopt on failures. 556 // This function shall handle $select when it is added. 557 // There is no need to handle parameters that's not campatible with $expand, 558 // e.g., $only, since this function will only be called in side $expand handlers 559 inline std::optional<std::string> formatQueryForExpand(const Query& query) 560 { 561 // query.expandLevel<=1: no need to do subqueries 562 if (query.expandLevel <= 1) 563 { 564 return ""; 565 } 566 std::string str = "?$expand="; 567 bool queryTypeExpected = false; 568 switch (query.expandType) 569 { 570 case ExpandType::None: 571 return ""; 572 case ExpandType::Links: 573 queryTypeExpected = true; 574 str += '~'; 575 break; 576 case ExpandType::NotLinks: 577 queryTypeExpected = true; 578 str += '.'; 579 break; 580 case ExpandType::Both: 581 queryTypeExpected = true; 582 str += '*'; 583 break; 584 } 585 if (!queryTypeExpected) 586 { 587 return std::nullopt; 588 } 589 str += "($levels="; 590 str += std::to_string(query.expandLevel - 1); 591 str += ')'; 592 return str; 593 } 594 595 class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp> 596 { 597 public: 598 // This object takes a single asyncResp object as the "final" one, then 599 // allows callers to attach sub-responses within the json tree that need 600 // to be executed and filled into their appropriate locations. This 601 // class manages the final "merge" of the json resources. 602 MultiAsyncResp(crow::App& appIn, 603 std::shared_ptr<bmcweb::AsyncResp> finalResIn) : 604 app(appIn), 605 finalRes(std::move(finalResIn)) 606 {} 607 608 void addAwaitingResponse( 609 const std::shared_ptr<bmcweb::AsyncResp>& res, 610 const nlohmann::json::json_pointer& finalExpandLocation) 611 { 612 res->res.setCompleteRequestHandler(std::bind_front( 613 placeResultStatic, shared_from_this(), finalExpandLocation)); 614 } 615 616 void placeResult(const nlohmann::json::json_pointer& locationToPlace, 617 crow::Response& res) 618 { 619 nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace]; 620 finalObj = std::move(res.jsonValue); 621 } 622 623 // Handles the very first level of Expand, and starts a chain of sub-queries 624 // for deeper levels. 625 void startQuery(const Query& query) 626 { 627 std::vector<ExpandNode> nodes = 628 findNavigationReferences(query.expandType, finalRes->res.jsonValue); 629 BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse"; 630 const std::optional<std::string> queryStr = formatQueryForExpand(query); 631 if (!queryStr) 632 { 633 messages::internalError(finalRes->res); 634 return; 635 } 636 for (const ExpandNode& node : nodes) 637 { 638 const std::string subQuery = node.uri + *queryStr; 639 BMCWEB_LOG_DEBUG << "URL of subquery: " << subQuery; 640 std::error_code ec; 641 crow::Request newReq({boost::beast::http::verb::get, subQuery, 11}, 642 ec); 643 if (ec) 644 { 645 messages::internalError(finalRes->res); 646 return; 647 } 648 649 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 650 BMCWEB_LOG_DEBUG << "setting completion handler on " 651 << &asyncResp->res; 652 653 addAwaitingResponse(asyncResp, node.location); 654 app.handle(newReq, asyncResp); 655 } 656 } 657 658 private: 659 static void 660 placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi, 661 const nlohmann::json::json_pointer& locationToPlace, 662 crow::Response& res) 663 { 664 multi->placeResult(locationToPlace, res); 665 } 666 667 crow::App& app; 668 std::shared_ptr<bmcweb::AsyncResp> finalRes; 669 }; 670 671 inline void processTopAndSkip(const Query& query, crow::Response& res) 672 { 673 if (!query.skip && !query.top) 674 { 675 // No work to do. 676 return; 677 } 678 nlohmann::json::object_t* obj = 679 res.jsonValue.get_ptr<nlohmann::json::object_t*>(); 680 if (obj == nullptr) 681 { 682 // Shouldn't be possible. All responses should be objects. 683 messages::internalError(res); 684 return; 685 } 686 687 BMCWEB_LOG_DEBUG << "Handling top/skip"; 688 nlohmann::json::object_t::iterator members = obj->find("Members"); 689 if (members == obj->end()) 690 { 691 // From the Redfish specification 7.3.1 692 // ... the HTTP 400 Bad Request status code with the 693 // QueryNotSupportedOnResource message from the Base Message Registry 694 // for any supported query parameters that apply only to resource 695 // collections but are used on singular resources. 696 messages::queryNotSupportedOnResource(res); 697 return; 698 } 699 700 nlohmann::json::array_t* arr = 701 members->second.get_ptr<nlohmann::json::array_t*>(); 702 if (arr == nullptr) 703 { 704 messages::internalError(res); 705 return; 706 } 707 708 if (query.skip) 709 { 710 // Per section 7.3.1 of the Redfish specification, $skip is run before 711 // $top Can only skip as many values as we have 712 size_t skip = std::min(arr->size(), *query.skip); 713 arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip)); 714 } 715 if (query.top) 716 { 717 size_t top = std::min(arr->size(), *query.top); 718 arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end()); 719 } 720 } 721 722 // Given a JSON subtree |currRoot|, and its JSON pointer |currRootPtr| to the 723 // |root| JSON in the async response, this function erases leaves whose keys are 724 // not in the |shouldSelect| set. 725 // |shouldSelect| contains all the properties that needs to be selected. 726 inline void 727 recursiveSelect(nlohmann::json& currRoot, 728 const nlohmann::json::json_pointer& currRootPtr, 729 const std::unordered_set<std::string>& intermediatePaths, 730 const std::unordered_set<std::string>& properties) 731 { 732 nlohmann::json::object_t* object = 733 currRoot.get_ptr<nlohmann::json::object_t*>(); 734 if (object != nullptr) 735 { 736 BMCWEB_LOG_DEBUG << "Current JSON is an object: " << currRootPtr; 737 auto it = currRoot.begin(); 738 while (it != currRoot.end()) 739 { 740 auto nextIt = std::next(it); 741 nlohmann::json::json_pointer childPtr = currRootPtr / it.key(); 742 BMCWEB_LOG_DEBUG << "childPtr=" << childPtr; 743 if (properties.contains(childPtr)) 744 { 745 it = nextIt; 746 continue; 747 } 748 if (intermediatePaths.contains(childPtr)) 749 { 750 BMCWEB_LOG_DEBUG << "Recursively select: " << childPtr; 751 recursiveSelect(*it, childPtr, intermediatePaths, properties); 752 it = nextIt; 753 continue; 754 } 755 BMCWEB_LOG_DEBUG << childPtr << " is getting removed!"; 756 it = currRoot.erase(it); 757 } 758 } 759 } 760 761 inline std::unordered_set<std::string> 762 getIntermediatePaths(const std::unordered_set<std::string>& properties) 763 { 764 std::unordered_set<std::string> res; 765 std::vector<std::string> segments; 766 767 for (auto const& property : properties) 768 { 769 // Omit the root "/" and split all other segments 770 boost::split(segments, property.substr(1), boost::is_any_of("/")); 771 std::string path; 772 if (!segments.empty()) 773 { 774 segments.pop_back(); 775 } 776 for (auto const& segment : segments) 777 { 778 path += '/'; 779 path += segment; 780 res.insert(path); 781 } 782 } 783 return res; 784 } 785 786 inline void performSelect(nlohmann::json& root, 787 const std::unordered_set<std::string>& properties) 788 { 789 std::unordered_set<std::string> intermediatePaths = 790 getIntermediatePaths(properties); 791 recursiveSelect(root, nlohmann::json::json_pointer(""), intermediatePaths, 792 properties); 793 } 794 795 // The current implementation of $select still has the following TODOs due to 796 // ambiguity and/or complexity. 797 // 1. select properties in array of objects; 798 // https://github.com/DMTF/Redfish/issues/5188 was created for clarification. 799 // 2. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was 800 // created for clarification. 801 // 2. respect the full odata spec; e.g., deduplication, namespace, star (*), 802 // etc. 803 inline void processSelect(crow::Response& intermediateResponse, 804 const std::unordered_set<std::string>& shouldSelect) 805 { 806 BMCWEB_LOG_DEBUG << "Process $select quary parameter"; 807 performSelect(intermediateResponse.jsonValue, shouldSelect); 808 } 809 810 inline void 811 processAllParams(crow::App& app, const Query& query, 812 std::function<void(crow::Response&)>& completionHandler, 813 crow::Response& intermediateResponse) 814 { 815 if (!completionHandler) 816 { 817 BMCWEB_LOG_DEBUG << "Function was invalid?"; 818 return; 819 } 820 821 BMCWEB_LOG_DEBUG << "Processing query params"; 822 // If the request failed, there's no reason to even try to run query 823 // params. 824 if (intermediateResponse.resultInt() < 200 || 825 intermediateResponse.resultInt() >= 400) 826 { 827 completionHandler(intermediateResponse); 828 return; 829 } 830 if (query.isOnly) 831 { 832 processOnly(app, intermediateResponse, completionHandler); 833 return; 834 } 835 836 if (query.top || query.skip) 837 { 838 processTopAndSkip(query, intermediateResponse); 839 } 840 841 if (query.expandType != ExpandType::None) 842 { 843 BMCWEB_LOG_DEBUG << "Executing expand query"; 844 // TODO(ed) this is a copy of the response object. Admittedly, 845 // we're inherently doing something inefficient, but we shouldn't 846 // have to do a full copy 847 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 848 asyncResp->res.setCompleteRequestHandler(std::move(completionHandler)); 849 asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue); 850 auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp); 851 852 multi->startQuery(query); 853 return; 854 } 855 856 // According to Redfish Spec Section 7.3.1, $select is the last parameter to 857 // to process 858 if (!query.selectedProperties.empty()) 859 { 860 processSelect(intermediateResponse, query.selectedProperties); 861 } 862 863 completionHandler(intermediateResponse); 864 } 865 866 } // namespace query_param 867 } // namespace redfish 868