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