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