xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 5c9fb2d6a1cddabc22af7603f11efd2850a05a80)
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 inline constexpr size_t maxEntriesPerPage = 1000;
53 
54 enum class ExpandType : uint8_t
55 {
56     None,
57     Links,
58     NotLinks,
59     Both,
60 };
61 
62 // A simple implementation of Trie to help |recursiveSelect|.
63 class SelectTrieNode
64 {
65   public:
66     SelectTrieNode() = default;
67 
68     const SelectTrieNode* find(const std::string& jsonKey) const
69     {
70         auto it = children.find(jsonKey);
71         if (it == children.end())
72         {
73             return nullptr;
74         }
75         return &it->second;
76     }
77 
78     // Creates a new node if the key doesn't exist, returns the reference to the
79     // newly created node; otherwise, return the reference to the existing node
80     SelectTrieNode* emplace(std::string_view jsonKey)
81     {
82         auto [it, _] = children.emplace(jsonKey, SelectTrieNode{});
83         return &it->second;
84     }
85 
86     bool empty() const
87     {
88         return children.empty();
89     }
90 
91     void clear()
92     {
93         children.clear();
94     }
95 
96     void setToSelected()
97     {
98         selected = true;
99     }
100 
101     bool isSelected() const
102     {
103         return selected;
104     }
105 
106   private:
107     std::map<std::string, SelectTrieNode, std::less<>> children;
108     bool selected = false;
109 };
110 
111 // Validates the property in the $select parameter. Every character is among
112 // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties)
113 inline bool isSelectedPropertyAllowed(std::string_view property)
114 {
115     // These a magic number, but with it it's less likely that this code
116     // introduces CVE; e.g., too large properties crash the service.
117     constexpr int maxPropertyLength = 60;
118     if (property.empty() || property.size() > maxPropertyLength)
119     {
120         return false;
121     }
122     for (char ch : property)
123     {
124         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' &&
125             ch != '@' && ch != '.')
126         {
127             return false;
128         }
129     }
130     return true;
131 }
132 
133 struct SelectTrie
134 {
135     SelectTrie() = default;
136 
137     // Inserts a $select value; returns false if the nestedProperty is illegal.
138     bool insertNode(std::string_view nestedProperty)
139     {
140         if (nestedProperty.empty())
141         {
142             return false;
143         }
144         SelectTrieNode* currNode = &root;
145         size_t index = nestedProperty.find_first_of('/');
146         while (!nestedProperty.empty())
147         {
148             std::string_view property = nestedProperty.substr(0, index);
149             if (!isSelectedPropertyAllowed(property))
150             {
151                 return false;
152             }
153             currNode = currNode->emplace(property);
154             if (index == std::string::npos)
155             {
156                 break;
157             }
158             nestedProperty.remove_prefix(index + 1);
159             index = nestedProperty.find_first_of('/');
160         }
161         currNode->setToSelected();
162         return true;
163     }
164 
165     SelectTrieNode root;
166 };
167 
168 // The struct stores the parsed query parameters of the default Redfish route.
169 struct Query
170 {
171     // Only
172     bool isOnly = false;
173     // Expand
174     uint8_t expandLevel = 0;
175     ExpandType expandType = ExpandType::None;
176 
177     // Skip
178     std::optional<size_t> skip = std::nullopt;
179 
180     // Top
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 > maxEntriesPerPage)
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",
415                     "0-" + std::to_string(maxEntriesPerPage));
416                 return std::nullopt;
417             }
418         }
419         else if (key == "$skip")
420         {
421             QueryError topRet = getSkipParam(value, ret);
422             if (topRet == QueryError::ValueFormat)
423             {
424                 messages::queryParameterValueFormatError(res, value, key);
425                 return std::nullopt;
426             }
427             if (topRet == QueryError::OutOfRange)
428             {
429                 messages::queryParameterOutOfRange(
430                     res, value, key,
431                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
432                 return std::nullopt;
433             }
434         }
435         else if (key == "$select" && bmcwebInsecureEnableQueryParams)
436         {
437             if (!getSelectParam(value, ret))
438             {
439                 messages::queryParameterValueFormatError(res, value, key);
440                 return std::nullopt;
441             }
442         }
443         else
444         {
445             // Intentionally ignore other errors Redfish spec, 7.3.1
446             if (key.starts_with("$"))
447             {
448                 // Services shall return... The HTTP 501 Not Implemented
449                 // status code for any unsupported query parameters that
450                 // start with $ .
451                 messages::queryParameterValueFormatError(res, value, key);
452                 res.result(boost::beast::http::status::not_implemented);
453                 return std::nullopt;
454             }
455             // "Shall ignore unknown or unsupported query parameters that do
456             // not begin with $ ."
457         }
458     }
459 
460     if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty())
461     {
462         messages::queryCombinationInvalid(res);
463         return std::nullopt;
464     }
465 
466     return ret;
467 }
468 
469 inline bool processOnly(crow::App& app, crow::Response& res,
470                         std::function<void(crow::Response&)>& completionHandler)
471 {
472     BMCWEB_LOG_DEBUG << "Processing only query param";
473     auto itMembers = res.jsonValue.find("Members");
474     if (itMembers == res.jsonValue.end())
475     {
476         messages::queryNotSupportedOnResource(res);
477         completionHandler(res);
478         return false;
479     }
480     auto itMemBegin = itMembers->begin();
481     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
482     {
483         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
484                          << " element, returning full collection.";
485         completionHandler(res);
486         return false;
487     }
488 
489     auto itUrl = itMemBegin->find("@odata.id");
490     if (itUrl == itMemBegin->end())
491     {
492         BMCWEB_LOG_DEBUG << "No found odata.id";
493         messages::internalError(res);
494         completionHandler(res);
495         return false;
496     }
497     const std::string* url = itUrl->get_ptr<const std::string*>();
498     if (url == nullptr)
499     {
500         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
501         messages::internalError(res);
502         completionHandler(res);
503         return false;
504     }
505     // TODO(Ed) copy request headers?
506     // newReq.session = req.session;
507     std::error_code ec;
508     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
509     if (ec)
510     {
511         messages::internalError(res);
512         completionHandler(res);
513         return false;
514     }
515 
516     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
517     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
518     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
519     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
520     app.handle(newReq, asyncResp);
521     return true;
522 }
523 
524 struct ExpandNode
525 {
526     nlohmann::json::json_pointer location;
527     std::string uri;
528 
529     inline bool operator==(const ExpandNode& other) const
530     {
531         return location == other.location && uri == other.uri;
532     }
533 };
534 
535 // Walks a json object looking for Redfish NavigationReference entries that
536 // might need resolved.  It recursively walks the jsonResponse object, looking
537 // for links at every level, and returns a list (out) of locations within the
538 // tree that need to be expanded.  The current json pointer location p is passed
539 // in to reference the current node that's being expanded, so it can be combined
540 // with the keys from the jsonResponse object
541 inline void findNavigationReferencesRecursive(
542     ExpandType eType, nlohmann::json& jsonResponse,
543     const nlohmann::json::json_pointer& p, bool inLinks,
544     std::vector<ExpandNode>& out)
545 {
546     // If no expand is needed, return early
547     if (eType == ExpandType::None)
548     {
549         return;
550     }
551     nlohmann::json::array_t* array =
552         jsonResponse.get_ptr<nlohmann::json::array_t*>();
553     if (array != nullptr)
554     {
555         size_t index = 0;
556         // For arrays, walk every element in the array
557         for (auto& element : *array)
558         {
559             nlohmann::json::json_pointer newPtr = p / index;
560             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
561             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
562                                               out);
563             index++;
564         }
565     }
566     nlohmann::json::object_t* obj =
567         jsonResponse.get_ptr<nlohmann::json::object_t*>();
568     if (obj == nullptr)
569     {
570         return;
571     }
572     // Navigation References only ever have a single element
573     if (obj->size() == 1)
574     {
575         if (obj->begin()->first == "@odata.id")
576         {
577             const std::string* uri =
578                 obj->begin()->second.get_ptr<const std::string*>();
579             if (uri != nullptr)
580             {
581                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
582                 out.push_back({p, *uri});
583             }
584         }
585     }
586     // Loop the object and look for links
587     for (auto& element : *obj)
588     {
589         bool localInLinks = inLinks;
590         if (!localInLinks)
591         {
592             // Check if this is a links node
593             localInLinks = element.first == "Links";
594         }
595         // Only traverse the parts of the tree the user asked for
596         // Per section 7.3 of the redfish specification
597         if (localInLinks && eType == ExpandType::NotLinks)
598         {
599             continue;
600         }
601         if (!localInLinks && eType == ExpandType::Links)
602         {
603             continue;
604         }
605         nlohmann::json::json_pointer newPtr = p / element.first;
606         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
607 
608         findNavigationReferencesRecursive(eType, element.second, newPtr,
609                                           localInLinks, out);
610     }
611 }
612 
613 inline std::vector<ExpandNode>
614     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
615 {
616     std::vector<ExpandNode> ret;
617     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
618     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
619     return ret;
620 }
621 
622 // Formats a query parameter string for the sub-query.
623 // Returns std::nullopt on failures.
624 // This function shall handle $select when it is added.
625 // There is no need to handle parameters that's not campatible with $expand,
626 // e.g., $only, since this function will only be called in side $expand handlers
627 inline std::optional<std::string> formatQueryForExpand(const Query& query)
628 {
629     // query.expandLevel<=1: no need to do subqueries
630     if (query.expandLevel <= 1)
631     {
632         return "";
633     }
634     std::string str = "?$expand=";
635     bool queryTypeExpected = false;
636     switch (query.expandType)
637     {
638         case ExpandType::None:
639             return "";
640         case ExpandType::Links:
641             queryTypeExpected = true;
642             str += '~';
643             break;
644         case ExpandType::NotLinks:
645             queryTypeExpected = true;
646             str += '.';
647             break;
648         case ExpandType::Both:
649             queryTypeExpected = true;
650             str += '*';
651             break;
652     }
653     if (!queryTypeExpected)
654     {
655         return std::nullopt;
656     }
657     str += "($levels=";
658     str += std::to_string(query.expandLevel - 1);
659     str += ')';
660     return str;
661 }
662 
663 class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
664 {
665   public:
666     // This object takes a single asyncResp object as the "final" one, then
667     // allows callers to attach sub-responses within the json tree that need
668     // to be executed and filled into their appropriate locations.  This
669     // class manages the final "merge" of the json resources.
670     MultiAsyncResp(crow::App& appIn,
671                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
672         app(appIn),
673         finalRes(std::move(finalResIn))
674     {}
675 
676     void addAwaitingResponse(
677         const std::shared_ptr<bmcweb::AsyncResp>& res,
678         const nlohmann::json::json_pointer& finalExpandLocation)
679     {
680         res->res.setCompleteRequestHandler(std::bind_front(
681             placeResultStatic, shared_from_this(), finalExpandLocation));
682     }
683 
684     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
685                      crow::Response& res)
686     {
687         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
688         finalObj = std::move(res.jsonValue);
689     }
690 
691     // Handles the very first level of Expand, and starts a chain of sub-queries
692     // for deeper levels.
693     void startQuery(const Query& query)
694     {
695         std::vector<ExpandNode> nodes =
696             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
697         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
698         const std::optional<std::string> queryStr = formatQueryForExpand(query);
699         if (!queryStr)
700         {
701             messages::internalError(finalRes->res);
702             return;
703         }
704         for (const ExpandNode& node : nodes)
705         {
706             const std::string subQuery = node.uri + *queryStr;
707             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
708             std::error_code ec;
709             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
710                                  ec);
711             if (ec)
712             {
713                 messages::internalError(finalRes->res);
714                 return;
715             }
716 
717             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
718             BMCWEB_LOG_DEBUG << "setting completion handler on "
719                              << &asyncResp->res;
720 
721             addAwaitingResponse(asyncResp, node.location);
722             app.handle(newReq, asyncResp);
723         }
724     }
725 
726   private:
727     static void
728         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
729                           const nlohmann::json::json_pointer& locationToPlace,
730                           crow::Response& res)
731     {
732         multi->placeResult(locationToPlace, res);
733     }
734 
735     crow::App& app;
736     std::shared_ptr<bmcweb::AsyncResp> finalRes;
737 };
738 
739 inline void processTopAndSkip(const Query& query, crow::Response& res)
740 {
741     if (!query.skip && !query.top)
742     {
743         // No work to do.
744         return;
745     }
746     nlohmann::json::object_t* obj =
747         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
748     if (obj == nullptr)
749     {
750         // Shouldn't be possible.  All responses should be objects.
751         messages::internalError(res);
752         return;
753     }
754 
755     BMCWEB_LOG_DEBUG << "Handling top/skip";
756     nlohmann::json::object_t::iterator members = obj->find("Members");
757     if (members == obj->end())
758     {
759         // From the Redfish specification 7.3.1
760         // ... the HTTP 400 Bad Request status code with the
761         // QueryNotSupportedOnResource message from the Base Message Registry
762         // for any supported query parameters that apply only to resource
763         // collections but are used on singular resources.
764         messages::queryNotSupportedOnResource(res);
765         return;
766     }
767 
768     nlohmann::json::array_t* arr =
769         members->second.get_ptr<nlohmann::json::array_t*>();
770     if (arr == nullptr)
771     {
772         messages::internalError(res);
773         return;
774     }
775 
776     if (query.skip)
777     {
778         // Per section 7.3.1 of the Redfish specification, $skip is run before
779         // $top Can only skip as many values as we have
780         size_t skip = std::min(arr->size(), *query.skip);
781         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
782     }
783     if (query.top)
784     {
785         size_t top = std::min(arr->size(), *query.top);
786         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
787     }
788 }
789 
790 // Given a JSON subtree |currRoot|, this function erases leaves whose keys are
791 // not in the |currNode| Trie node.
792 inline void recursiveSelect(nlohmann::json& currRoot,
793                             const SelectTrieNode& currNode)
794 {
795     nlohmann::json::object_t* object =
796         currRoot.get_ptr<nlohmann::json::object_t*>();
797     if (object != nullptr)
798     {
799         BMCWEB_LOG_DEBUG << "Current JSON is an object";
800         auto it = currRoot.begin();
801         while (it != currRoot.end())
802         {
803             auto nextIt = std::next(it);
804             BMCWEB_LOG_DEBUG << "key=" << it.key();
805             const SelectTrieNode* nextNode = currNode.find(it.key());
806             // Per the Redfish spec section 7.3.3, the service shall select
807             // certain properties as if $select was omitted. This applies to
808             // every TrieNode that contains leaves and the root.
809             constexpr std::array<std::string_view, 5> reservedProperties = {
810                 "@odata.id", "@odata.type", "@odata.context", "@odata.etag",
811                 "error"};
812             bool reserved =
813                 std::find(reservedProperties.begin(), reservedProperties.end(),
814                           it.key()) != reservedProperties.end();
815             if (reserved || (nextNode != nullptr && nextNode->isSelected()))
816             {
817                 it = nextIt;
818                 continue;
819             }
820             if (nextNode != nullptr)
821             {
822                 BMCWEB_LOG_DEBUG << "Recursively select: " << it.key();
823                 recursiveSelect(*it, *nextNode);
824                 it = nextIt;
825                 continue;
826             }
827             BMCWEB_LOG_DEBUG << it.key() << " is getting removed!";
828             it = currRoot.erase(it);
829         }
830     }
831     nlohmann::json::array_t* array =
832         currRoot.get_ptr<nlohmann::json::array_t*>();
833     if (array != nullptr)
834     {
835         BMCWEB_LOG_DEBUG << "Current JSON is an array";
836         // Array index is omitted, so reuse the same Trie node
837         for (nlohmann::json& nextRoot : *array)
838         {
839             recursiveSelect(nextRoot, currNode);
840         }
841     }
842 }
843 
844 // The current implementation of $select still has the following TODOs due to
845 //  ambiguity and/or complexity.
846 // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
847 // created for clarification.
848 // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
849 // etc.
850 inline void processSelect(crow::Response& intermediateResponse,
851                           const SelectTrieNode& trieRoot)
852 {
853     BMCWEB_LOG_DEBUG << "Process $select quary parameter";
854     recursiveSelect(intermediateResponse.jsonValue, trieRoot);
855 }
856 
857 inline void
858     processAllParams(crow::App& app, const Query& query,
859                      std::function<void(crow::Response&)>& completionHandler,
860                      crow::Response& intermediateResponse)
861 {
862     if (!completionHandler)
863     {
864         BMCWEB_LOG_DEBUG << "Function was invalid?";
865         return;
866     }
867 
868     BMCWEB_LOG_DEBUG << "Processing query params";
869     // If the request failed, there's no reason to even try to run query
870     // params.
871     if (intermediateResponse.resultInt() < 200 ||
872         intermediateResponse.resultInt() >= 400)
873     {
874         completionHandler(intermediateResponse);
875         return;
876     }
877     if (query.isOnly)
878     {
879         processOnly(app, intermediateResponse, completionHandler);
880         return;
881     }
882 
883     if (query.top || query.skip)
884     {
885         processTopAndSkip(query, intermediateResponse);
886     }
887 
888     if (query.expandType != ExpandType::None)
889     {
890         BMCWEB_LOG_DEBUG << "Executing expand query";
891         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
892             std::move(intermediateResponse));
893 
894         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
895         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
896         multi->startQuery(query);
897         return;
898     }
899 
900     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
901     // to process
902     if (!query.selectTrie.root.empty())
903     {
904         processSelect(intermediateResponse, query.selectTrie.root);
905     }
906 
907     completionHandler(intermediateResponse);
908 }
909 
910 } // namespace query_param
911 } // namespace redfish
912