xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 827c4902835d3175a36d90808a51304f744203ce)
1f4c99e70SEd Tanous #pragma once
2d5c80ad9SNan Zhou #include "bmcweb_config.h"
3d5c80ad9SNan Zhou 
4f4c99e70SEd Tanous #include "app.hpp"
5f4c99e70SEd Tanous #include "async_resp.hpp"
6f4c99e70SEd Tanous #include "error_messages.hpp"
7f4c99e70SEd Tanous #include "http_request.hpp"
802cad96eSEd Tanous #include "http_response.hpp"
9d5c80ad9SNan Zhou #include "logging.hpp"
10f4c99e70SEd Tanous 
11d5c80ad9SNan Zhou #include <sys/types.h>
12d5c80ad9SNan Zhou 
13e155ab54SNan Zhou #include <boost/algorithm/string/classification.hpp>
14e155ab54SNan Zhou #include <boost/algorithm/string/split.hpp>
15d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep
16d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp>
17d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp>
18d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
19d5c80ad9SNan Zhou #include <boost/url/string.hpp>
20d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
21d5c80ad9SNan Zhou 
22d5c80ad9SNan Zhou #include <algorithm>
23e155ab54SNan Zhou #include <array>
24e155ab54SNan Zhou #include <cctype>
257cf436c9SEd Tanous #include <charconv>
26*827c4902SNan Zhou #include <compare>
27d5c80ad9SNan Zhou #include <cstdint>
28d5c80ad9SNan Zhou #include <functional>
29e155ab54SNan Zhou #include <iterator>
30d5c80ad9SNan Zhou #include <limits>
31d5c80ad9SNan Zhou #include <map>
32d5c80ad9SNan Zhou #include <memory>
33d5c80ad9SNan Zhou #include <optional>
34f4c99e70SEd Tanous #include <string>
35f4c99e70SEd Tanous #include <string_view>
36d5c80ad9SNan Zhou #include <system_error>
377cf436c9SEd Tanous #include <utility>
38f4c99e70SEd Tanous #include <vector>
39f4c99e70SEd Tanous 
40d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
41d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
42d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
43e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp>
44e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp>
45e155ab54SNan Zhou // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp>
46d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h>
47d5c80ad9SNan Zhou 
48f4c99e70SEd Tanous namespace redfish
49f4c99e70SEd Tanous {
50f4c99e70SEd Tanous namespace query_param
51f4c99e70SEd Tanous {
52ce8ea743SJiaqing Zhao inline constexpr size_t maxEntriesPerPage = 1000;
53f4c99e70SEd Tanous 
547cf436c9SEd Tanous enum class ExpandType : uint8_t
557cf436c9SEd Tanous {
567cf436c9SEd Tanous     None,
577cf436c9SEd Tanous     Links,
587cf436c9SEd Tanous     NotLinks,
597cf436c9SEd Tanous     Both,
607cf436c9SEd Tanous };
617cf436c9SEd Tanous 
62*827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|.
63*827c4902SNan Zhou class SelectTrieNode
64*827c4902SNan Zhou {
65*827c4902SNan Zhou   public:
66*827c4902SNan Zhou     SelectTrieNode() = default;
67*827c4902SNan Zhou 
68*827c4902SNan Zhou     const SelectTrieNode* find(const std::string& jsonKey) const
69*827c4902SNan Zhou     {
70*827c4902SNan Zhou         auto it = children.find(jsonKey);
71*827c4902SNan Zhou         if (it == children.end())
72*827c4902SNan Zhou         {
73*827c4902SNan Zhou             return nullptr;
74*827c4902SNan Zhou         }
75*827c4902SNan Zhou         return &it->second;
76*827c4902SNan Zhou     }
77*827c4902SNan Zhou 
78*827c4902SNan Zhou     // Creates a new node if the key doesn't exist, returns the reference to the
79*827c4902SNan Zhou     // newly created node; otherwise, return the reference to the existing node
80*827c4902SNan Zhou     SelectTrieNode* emplace(std::string_view jsonKey)
81*827c4902SNan Zhou     {
82*827c4902SNan Zhou         auto [it, _] = children.emplace(jsonKey, SelectTrieNode{});
83*827c4902SNan Zhou         return &it->second;
84*827c4902SNan Zhou     }
85*827c4902SNan Zhou 
86*827c4902SNan Zhou     bool empty() const
87*827c4902SNan Zhou     {
88*827c4902SNan Zhou         return children.empty();
89*827c4902SNan Zhou     }
90*827c4902SNan Zhou 
91*827c4902SNan Zhou     void clear()
92*827c4902SNan Zhou     {
93*827c4902SNan Zhou         children.clear();
94*827c4902SNan Zhou     }
95*827c4902SNan Zhou 
96*827c4902SNan Zhou     void setToSelected()
97*827c4902SNan Zhou     {
98*827c4902SNan Zhou         selected = true;
99*827c4902SNan Zhou     }
100*827c4902SNan Zhou 
101*827c4902SNan Zhou     bool isSelected() const
102*827c4902SNan Zhou     {
103*827c4902SNan Zhou         return selected;
104*827c4902SNan Zhou     }
105*827c4902SNan Zhou 
106*827c4902SNan Zhou   private:
107*827c4902SNan Zhou     std::map<std::string, SelectTrieNode, std::less<>> children;
108*827c4902SNan Zhou     bool selected = false;
109*827c4902SNan Zhou };
110*827c4902SNan Zhou 
111*827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among
112*827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties)
113*827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property)
114*827c4902SNan Zhou {
115*827c4902SNan Zhou     // These a magic number, but with it it's less likely that this code
116*827c4902SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
117*827c4902SNan Zhou     constexpr int maxPropertyLength = 60;
118*827c4902SNan Zhou     if (property.empty() || property.size() > maxPropertyLength)
119*827c4902SNan Zhou     {
120*827c4902SNan Zhou         return false;
121*827c4902SNan Zhou     }
122*827c4902SNan Zhou     for (char ch : property)
123*827c4902SNan Zhou     {
124*827c4902SNan Zhou         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' &&
125*827c4902SNan Zhou             ch != '@' && ch != '.')
126*827c4902SNan Zhou         {
127*827c4902SNan Zhou             return false;
128*827c4902SNan Zhou         }
129*827c4902SNan Zhou     }
130*827c4902SNan Zhou     return true;
131*827c4902SNan Zhou }
132*827c4902SNan Zhou 
133*827c4902SNan Zhou struct SelectTrie
134*827c4902SNan Zhou {
135*827c4902SNan Zhou     SelectTrie() = default;
136*827c4902SNan Zhou 
137*827c4902SNan Zhou     // Inserts a $select value; returns false if the nestedProperty is illegal.
138*827c4902SNan Zhou     bool insertNode(std::string_view nestedProperty)
139*827c4902SNan Zhou     {
140*827c4902SNan Zhou         if (nestedProperty.empty())
141*827c4902SNan Zhou         {
142*827c4902SNan Zhou             return false;
143*827c4902SNan Zhou         }
144*827c4902SNan Zhou         SelectTrieNode* currNode = &root;
145*827c4902SNan Zhou         size_t index = nestedProperty.find_first_of('/');
146*827c4902SNan Zhou         while (!nestedProperty.empty())
147*827c4902SNan Zhou         {
148*827c4902SNan Zhou             std::string_view property = nestedProperty.substr(0, index);
149*827c4902SNan Zhou             if (!isSelectedPropertyAllowed(property))
150*827c4902SNan Zhou             {
151*827c4902SNan Zhou                 return false;
152*827c4902SNan Zhou             }
153*827c4902SNan Zhou             currNode = currNode->emplace(property);
154*827c4902SNan Zhou             if (index == std::string::npos)
155*827c4902SNan Zhou             {
156*827c4902SNan Zhou                 break;
157*827c4902SNan Zhou             }
158*827c4902SNan Zhou             nestedProperty.remove_prefix(index + 1);
159*827c4902SNan Zhou             index = nestedProperty.find_first_of('/');
160*827c4902SNan Zhou         }
161*827c4902SNan Zhou 
162*827c4902SNan Zhou         currNode->setToSelected();
163*827c4902SNan Zhou         return true;
164*827c4902SNan Zhou     }
165*827c4902SNan Zhou 
166*827c4902SNan Zhou     SelectTrieNode root;
167*827c4902SNan Zhou };
168*827c4902SNan Zhou 
169a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
170f4c99e70SEd Tanous struct Query
171f4c99e70SEd Tanous {
172a6b9125fSNan Zhou     // Only
173f4c99e70SEd Tanous     bool isOnly = false;
174a6b9125fSNan Zhou     // Expand
175a6b9125fSNan Zhou     uint8_t expandLevel = 0;
1767cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
177c937d2bfSEd Tanous 
178c937d2bfSEd Tanous     // Skip
1793648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
180c937d2bfSEd Tanous 
181c937d2bfSEd Tanous     // Top
1823648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
183e155ab54SNan Zhou 
184e155ab54SNan Zhou     // Select
185*827c4902SNan Zhou     SelectTrie selectTrie = {};
186f4c99e70SEd Tanous };
187f4c99e70SEd Tanous 
188a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
189a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
190a6b9125fSNan Zhou // the processing.
191a6b9125fSNan Zhou struct QueryCapabilities
192a6b9125fSNan Zhou {
193a6b9125fSNan Zhou     bool canDelegateOnly = false;
194c937d2bfSEd Tanous     bool canDelegateTop = false;
195c937d2bfSEd Tanous     bool canDelegateSkip = false;
196a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
197e155ab54SNan Zhou     bool canDelegateSelect = false;
198a6b9125fSNan Zhou };
199a6b9125fSNan Zhou 
200a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
201a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
202a6b9125fSNan Zhou // function will take care of it.
203a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
204a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
205a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
206a6b9125fSNan Zhou {
207a6b9125fSNan Zhou     Query delegated;
208a6b9125fSNan Zhou     // delegate only
209a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
210a6b9125fSNan Zhou     {
211a6b9125fSNan Zhou         delegated.isOnly = true;
212a6b9125fSNan Zhou         query.isOnly = false;
213a6b9125fSNan Zhou     }
214a6b9125fSNan Zhou     // delegate expand as much as we can
215a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
216a6b9125fSNan Zhou     {
217a6b9125fSNan Zhou         delegated.expandType = query.expandType;
218a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
219a6b9125fSNan Zhou         {
220a6b9125fSNan Zhou             query.expandType = ExpandType::None;
221a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
222a6b9125fSNan Zhou             query.expandLevel = 0;
223a6b9125fSNan Zhou         }
224a6b9125fSNan Zhou         else
225a6b9125fSNan Zhou         {
226a6b9125fSNan Zhou             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
227a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
228a6b9125fSNan Zhou         }
229a6b9125fSNan Zhou     }
230c937d2bfSEd Tanous 
231c937d2bfSEd Tanous     // delegate top
2323648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
233c937d2bfSEd Tanous     {
234c937d2bfSEd Tanous         delegated.top = query.top;
2353648c8beSEd Tanous         query.top = std::nullopt;
236c937d2bfSEd Tanous     }
237c937d2bfSEd Tanous 
238c937d2bfSEd Tanous     // delegate skip
2393648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
240c937d2bfSEd Tanous     {
241c937d2bfSEd Tanous         delegated.skip = query.skip;
242c937d2bfSEd Tanous         query.skip = 0;
243c937d2bfSEd Tanous     }
244e155ab54SNan Zhou 
245e155ab54SNan Zhou     // delegate select
246*827c4902SNan Zhou     if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect)
247e155ab54SNan Zhou     {
248*827c4902SNan Zhou         delegated.selectTrie = std::move(query.selectTrie);
249*827c4902SNan Zhou         query.selectTrie.root.clear();
250e155ab54SNan Zhou     }
251a6b9125fSNan Zhou     return delegated;
252a6b9125fSNan Zhou }
253a6b9125fSNan Zhou 
2547cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
2557cf436c9SEd Tanous {
2567cf436c9SEd Tanous     if (value.empty())
2577cf436c9SEd Tanous     {
2587cf436c9SEd Tanous         return false;
2597cf436c9SEd Tanous     }
2607cf436c9SEd Tanous     switch (value[0])
2617cf436c9SEd Tanous     {
2627cf436c9SEd Tanous         case '*':
2637cf436c9SEd Tanous             query.expandType = ExpandType::Both;
2647cf436c9SEd Tanous             break;
2657cf436c9SEd Tanous         case '.':
2667cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
2677cf436c9SEd Tanous             break;
2687cf436c9SEd Tanous         case '~':
2697cf436c9SEd Tanous             query.expandType = ExpandType::Links;
2707cf436c9SEd Tanous             break;
2717cf436c9SEd Tanous         default:
2727cf436c9SEd Tanous             return false;
2737cf436c9SEd Tanous 
2747cf436c9SEd Tanous             break;
2757cf436c9SEd Tanous     }
2767cf436c9SEd Tanous     value.remove_prefix(1);
2777cf436c9SEd Tanous     if (value.empty())
2787cf436c9SEd Tanous     {
2797cf436c9SEd Tanous         query.expandLevel = 1;
2807cf436c9SEd Tanous         return true;
2817cf436c9SEd Tanous     }
2827cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
2837cf436c9SEd Tanous     if (!value.starts_with(levels))
2847cf436c9SEd Tanous     {
2857cf436c9SEd Tanous         return false;
2867cf436c9SEd Tanous     }
2877cf436c9SEd Tanous     value.remove_prefix(levels.size());
2887cf436c9SEd Tanous 
2897cf436c9SEd Tanous     auto it = std::from_chars(value.data(), value.data() + value.size(),
2907cf436c9SEd Tanous                               query.expandLevel);
2917cf436c9SEd Tanous     if (it.ec != std::errc())
2927cf436c9SEd Tanous     {
2937cf436c9SEd Tanous         return false;
2947cf436c9SEd Tanous     }
2957cf436c9SEd Tanous     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
2967cf436c9SEd Tanous     return value == ")";
2977cf436c9SEd Tanous }
2987cf436c9SEd Tanous 
299c937d2bfSEd Tanous enum class QueryError
300c937d2bfSEd Tanous {
301c937d2bfSEd Tanous     Ok,
302c937d2bfSEd Tanous     OutOfRange,
303c937d2bfSEd Tanous     ValueFormat,
304c937d2bfSEd Tanous };
305c937d2bfSEd Tanous 
306c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
307c937d2bfSEd Tanous {
308c937d2bfSEd Tanous     std::from_chars_result r =
309c937d2bfSEd Tanous         std::from_chars(value.data(), value.data() + value.size(), param);
310c937d2bfSEd Tanous 
311c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
312c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
313c937d2bfSEd Tanous     {
314c937d2bfSEd Tanous         return QueryError::OutOfRange;
315c937d2bfSEd Tanous     }
316c937d2bfSEd Tanous     // All other errors are value format
317c937d2bfSEd Tanous     if (r.ec != std::errc())
318c937d2bfSEd Tanous     {
319c937d2bfSEd Tanous         return QueryError::ValueFormat;
320c937d2bfSEd Tanous     }
321c937d2bfSEd Tanous     return QueryError::Ok;
322c937d2bfSEd Tanous }
323c937d2bfSEd Tanous 
324c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
325c937d2bfSEd Tanous {
3263648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
327c937d2bfSEd Tanous }
328c937d2bfSEd Tanous 
329c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
330c937d2bfSEd Tanous {
3313648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
332c937d2bfSEd Tanous     if (ret != QueryError::Ok)
333c937d2bfSEd Tanous     {
334c937d2bfSEd Tanous         return ret;
335c937d2bfSEd Tanous     }
336c937d2bfSEd Tanous 
337c937d2bfSEd Tanous     // Range check for sanity.
338c937d2bfSEd Tanous     if (query.top > maxEntriesPerPage)
339c937d2bfSEd Tanous     {
340c937d2bfSEd Tanous         return QueryError::OutOfRange;
341c937d2bfSEd Tanous     }
342c937d2bfSEd Tanous 
343c937d2bfSEd Tanous     return QueryError::Ok;
344c937d2bfSEd Tanous }
345c937d2bfSEd Tanous 
346e155ab54SNan Zhou // Parses and validates the $select parameter.
347e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be
348e155ab54SNan Zhou // comma separated Resource Path
349e155ab54SNan Zhou // Ref:
350e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
351e155ab54SNan Zhou // 2.
352e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
353e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query)
354e155ab54SNan Zhou {
355e155ab54SNan Zhou     std::vector<std::string> properties;
356e155ab54SNan Zhou     boost::split(properties, value, boost::is_any_of(","));
357e155ab54SNan Zhou     if (properties.empty())
358e155ab54SNan Zhou     {
359e155ab54SNan Zhou         return false;
360e155ab54SNan Zhou     }
361e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
362e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
363e155ab54SNan Zhou     constexpr int maxNumProperties = 10;
364e155ab54SNan Zhou     if (properties.size() > maxNumProperties)
365e155ab54SNan Zhou     {
366e155ab54SNan Zhou         return false;
367e155ab54SNan Zhou     }
368*827c4902SNan Zhou     for (const auto& property : properties)
369e155ab54SNan Zhou     {
370*827c4902SNan Zhou         if (!query.selectTrie.insertNode(property))
371e155ab54SNan Zhou         {
372e155ab54SNan Zhou             return false;
373e155ab54SNan Zhou         }
374e155ab54SNan Zhou     }
375e155ab54SNan Zhou     // Per the Redfish spec section 7.3.3, the service shall select certain
376e155ab54SNan Zhou     // properties as if $select was omitted.
377e155ab54SNan Zhou     constexpr std::array<std::string_view, 5> reservedProperties = {
378*827c4902SNan Zhou         "@odata.id", "@odata.type", "@odata.context", "@odata.etag", "error"};
379e155ab54SNan Zhou     for (auto const& str : reservedProperties)
380e155ab54SNan Zhou     {
381*827c4902SNan Zhou         query.selectTrie.insertNode(str.data());
382e155ab54SNan Zhou     }
383e155ab54SNan Zhou     return true;
384e155ab54SNan Zhou }
385e155ab54SNan Zhou 
386f4c99e70SEd Tanous inline std::optional<Query>
387f4c99e70SEd Tanous     parseParameters(const boost::urls::params_view& urlParams,
388f4c99e70SEd Tanous                     crow::Response& res)
389f4c99e70SEd Tanous {
390f4c99e70SEd Tanous     Query ret;
391f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
392f4c99e70SEd Tanous     {
393f4c99e70SEd Tanous         std::string_view key(it.key.data(), it.key.size());
394f4c99e70SEd Tanous         std::string_view value(it.value.data(), it.value.size());
395f4c99e70SEd Tanous         if (key == "only")
396f4c99e70SEd Tanous         {
397f4c99e70SEd Tanous             if (!it.value.empty())
398f4c99e70SEd Tanous             {
399f4c99e70SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
400f4c99e70SEd Tanous                 return std::nullopt;
401f4c99e70SEd Tanous             }
402f4c99e70SEd Tanous             ret.isOnly = true;
403f4c99e70SEd Tanous         }
4045e52870bSEd Tanous         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
4057cf436c9SEd Tanous         {
4067cf436c9SEd Tanous             if (!getExpandType(value, ret))
4077cf436c9SEd Tanous             {
4087cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
4097cf436c9SEd Tanous                 return std::nullopt;
410f4c99e70SEd Tanous             }
4117cf436c9SEd Tanous         }
412c937d2bfSEd Tanous         else if (key == "$top")
413c937d2bfSEd Tanous         {
414c937d2bfSEd Tanous             QueryError topRet = getTopParam(value, ret);
415c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
416c937d2bfSEd Tanous             {
417c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
418c937d2bfSEd Tanous                 return std::nullopt;
419c937d2bfSEd Tanous             }
420c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
421c937d2bfSEd Tanous             {
422c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
423c937d2bfSEd Tanous                     res, value, "$top",
424a926c53eSJiaqing Zhao                     "0-" + std::to_string(maxEntriesPerPage));
425c937d2bfSEd Tanous                 return std::nullopt;
426c937d2bfSEd Tanous             }
427c937d2bfSEd Tanous         }
428c937d2bfSEd Tanous         else if (key == "$skip")
429c937d2bfSEd Tanous         {
430c937d2bfSEd Tanous             QueryError topRet = getSkipParam(value, ret);
431c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
432c937d2bfSEd Tanous             {
433c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
434c937d2bfSEd Tanous                 return std::nullopt;
435c937d2bfSEd Tanous             }
436c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
437c937d2bfSEd Tanous             {
438c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
439c937d2bfSEd Tanous                     res, value, key,
440a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
441c937d2bfSEd Tanous                 return std::nullopt;
442c937d2bfSEd Tanous             }
443c937d2bfSEd Tanous         }
444e155ab54SNan Zhou         else if (key == "$select" && bmcwebInsecureEnableQueryParams)
445e155ab54SNan Zhou         {
446e155ab54SNan Zhou             if (!getSelectParam(value, ret))
447e155ab54SNan Zhou             {
448e155ab54SNan Zhou                 messages::queryParameterValueFormatError(res, value, key);
449e155ab54SNan Zhou                 return std::nullopt;
450e155ab54SNan Zhou             }
451e155ab54SNan Zhou         }
4527cf436c9SEd Tanous         else
4537cf436c9SEd Tanous         {
4547cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
4557cf436c9SEd Tanous             if (key.starts_with("$"))
4567cf436c9SEd Tanous             {
4577cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
4587cf436c9SEd Tanous                 // status code for any unsupported query parameters that
4597cf436c9SEd Tanous                 // start with $ .
4607cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
4617cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
4627cf436c9SEd Tanous                 return std::nullopt;
4637cf436c9SEd Tanous             }
4647cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
4657cf436c9SEd Tanous             // not begin with $ ."
4667cf436c9SEd Tanous         }
4677cf436c9SEd Tanous     }
4687cf436c9SEd Tanous 
469*827c4902SNan Zhou     if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty())
470e155ab54SNan Zhou     {
471e155ab54SNan Zhou         messages::queryCombinationInvalid(res);
472e155ab54SNan Zhou         return std::nullopt;
473e155ab54SNan Zhou     }
474e155ab54SNan Zhou 
475f4c99e70SEd Tanous     return ret;
476f4c99e70SEd Tanous }
477f4c99e70SEd Tanous 
478f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
479f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
480f4c99e70SEd Tanous {
481f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
482f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
483f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
484f4c99e70SEd Tanous     {
485f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
486f4c99e70SEd Tanous         completionHandler(res);
487f4c99e70SEd Tanous         return false;
488f4c99e70SEd Tanous     }
489f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
490f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
491f4c99e70SEd Tanous     {
492f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
493f4c99e70SEd Tanous                          << " element, returning full collection.";
494f4c99e70SEd Tanous         completionHandler(res);
495f4c99e70SEd Tanous         return false;
496f4c99e70SEd Tanous     }
497f4c99e70SEd Tanous 
498f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
499f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
500f4c99e70SEd Tanous     {
501f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
502f4c99e70SEd Tanous         messages::internalError(res);
503f4c99e70SEd Tanous         completionHandler(res);
504f4c99e70SEd Tanous         return false;
505f4c99e70SEd Tanous     }
506f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
507f4c99e70SEd Tanous     if (url == nullptr)
508f4c99e70SEd Tanous     {
509f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
510f4c99e70SEd Tanous         messages::internalError(res);
511f4c99e70SEd Tanous         completionHandler(res);
512f4c99e70SEd Tanous         return false;
513f4c99e70SEd Tanous     }
514f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
515f4c99e70SEd Tanous     // newReq.session = req.session;
516f4c99e70SEd Tanous     std::error_code ec;
517f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
518f4c99e70SEd Tanous     if (ec)
519f4c99e70SEd Tanous     {
520f4c99e70SEd Tanous         messages::internalError(res);
521f4c99e70SEd Tanous         completionHandler(res);
522f4c99e70SEd Tanous         return false;
523f4c99e70SEd Tanous     }
524f4c99e70SEd Tanous 
525f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
526f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
527f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
528f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
529f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
530f4c99e70SEd Tanous     return true;
531f4c99e70SEd Tanous }
532f4c99e70SEd Tanous 
5337cf436c9SEd Tanous struct ExpandNode
5347cf436c9SEd Tanous {
5357cf436c9SEd Tanous     nlohmann::json::json_pointer location;
5367cf436c9SEd Tanous     std::string uri;
5377cf436c9SEd Tanous 
5387cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
5397cf436c9SEd Tanous     {
5407cf436c9SEd Tanous         return location == other.location && uri == other.uri;
5417cf436c9SEd Tanous     }
5427cf436c9SEd Tanous };
5437cf436c9SEd Tanous 
5447cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
5457cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
5467cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
5477cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
5487cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
5497cf436c9SEd Tanous // with the keys from the jsonResponse object
5507cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
5517cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
5527cf436c9SEd Tanous     const nlohmann::json::json_pointer& p, bool inLinks,
5537cf436c9SEd Tanous     std::vector<ExpandNode>& out)
5547cf436c9SEd Tanous {
5557cf436c9SEd Tanous     // If no expand is needed, return early
5567cf436c9SEd Tanous     if (eType == ExpandType::None)
5577cf436c9SEd Tanous     {
5587cf436c9SEd Tanous         return;
5597cf436c9SEd Tanous     }
5607cf436c9SEd Tanous     nlohmann::json::array_t* array =
5617cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
5627cf436c9SEd Tanous     if (array != nullptr)
5637cf436c9SEd Tanous     {
5647cf436c9SEd Tanous         size_t index = 0;
5657cf436c9SEd Tanous         // For arrays, walk every element in the array
5667cf436c9SEd Tanous         for (auto& element : *array)
5677cf436c9SEd Tanous         {
5687cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
5697cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
5707cf436c9SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
5717cf436c9SEd Tanous                                               out);
5727cf436c9SEd Tanous             index++;
5737cf436c9SEd Tanous         }
5747cf436c9SEd Tanous     }
5757cf436c9SEd Tanous     nlohmann::json::object_t* obj =
5767cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
5777cf436c9SEd Tanous     if (obj == nullptr)
5787cf436c9SEd Tanous     {
5797cf436c9SEd Tanous         return;
5807cf436c9SEd Tanous     }
5817cf436c9SEd Tanous     // Navigation References only ever have a single element
5827cf436c9SEd Tanous     if (obj->size() == 1)
5837cf436c9SEd Tanous     {
5847cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
5857cf436c9SEd Tanous         {
5867cf436c9SEd Tanous             const std::string* uri =
5877cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
5887cf436c9SEd Tanous             if (uri != nullptr)
5897cf436c9SEd Tanous             {
5907cf436c9SEd Tanous                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
5917cf436c9SEd Tanous                 out.push_back({p, *uri});
5927cf436c9SEd Tanous             }
5937cf436c9SEd Tanous         }
5947cf436c9SEd Tanous     }
5957cf436c9SEd Tanous     // Loop the object and look for links
5967cf436c9SEd Tanous     for (auto& element : *obj)
5977cf436c9SEd Tanous     {
598e479ad58SNan Zhou         bool localInLinks = inLinks;
599e479ad58SNan Zhou         if (!localInLinks)
6007cf436c9SEd Tanous         {
6017cf436c9SEd Tanous             // Check if this is a links node
602e479ad58SNan Zhou             localInLinks = element.first == "Links";
6037cf436c9SEd Tanous         }
6047cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
6057cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
606e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
6077cf436c9SEd Tanous         {
6087cf436c9SEd Tanous             continue;
6097cf436c9SEd Tanous         }
610e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
6117cf436c9SEd Tanous         {
6127cf436c9SEd Tanous             continue;
6137cf436c9SEd Tanous         }
6147cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
6157cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
6167cf436c9SEd Tanous 
6177cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
618e479ad58SNan Zhou                                           localInLinks, out);
6197cf436c9SEd Tanous     }
6207cf436c9SEd Tanous }
6217cf436c9SEd Tanous 
6227cf436c9SEd Tanous inline std::vector<ExpandNode>
62372c3ae33SNan Zhou     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
6247cf436c9SEd Tanous {
6257cf436c9SEd Tanous     std::vector<ExpandNode> ret;
62672c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
6277cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
6287cf436c9SEd Tanous     return ret;
6297cf436c9SEd Tanous }
6307cf436c9SEd Tanous 
63172c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
632b66cf2a2SNan Zhou // Returns std::nullopt on failures.
63372c3ae33SNan Zhou // This function shall handle $select when it is added.
63472c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
63572c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
636b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
63772c3ae33SNan Zhou {
63872c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
63972c3ae33SNan Zhou     if (query.expandLevel <= 1)
64072c3ae33SNan Zhou     {
641b66cf2a2SNan Zhou         return "";
64272c3ae33SNan Zhou     }
64372c3ae33SNan Zhou     std::string str = "?$expand=";
644b66cf2a2SNan Zhou     bool queryTypeExpected = false;
64572c3ae33SNan Zhou     switch (query.expandType)
64672c3ae33SNan Zhou     {
64772c3ae33SNan Zhou         case ExpandType::None:
648b66cf2a2SNan Zhou             return "";
64972c3ae33SNan Zhou         case ExpandType::Links:
650b66cf2a2SNan Zhou             queryTypeExpected = true;
65172c3ae33SNan Zhou             str += '~';
65272c3ae33SNan Zhou             break;
65372c3ae33SNan Zhou         case ExpandType::NotLinks:
654b66cf2a2SNan Zhou             queryTypeExpected = true;
65572c3ae33SNan Zhou             str += '.';
65672c3ae33SNan Zhou             break;
65772c3ae33SNan Zhou         case ExpandType::Both:
658b66cf2a2SNan Zhou             queryTypeExpected = true;
65972c3ae33SNan Zhou             str += '*';
66072c3ae33SNan Zhou             break;
661b66cf2a2SNan Zhou     }
662b66cf2a2SNan Zhou     if (!queryTypeExpected)
663b66cf2a2SNan Zhou     {
664b66cf2a2SNan Zhou         return std::nullopt;
66572c3ae33SNan Zhou     }
66672c3ae33SNan Zhou     str += "($levels=";
66772c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
66872c3ae33SNan Zhou     str += ')';
66972c3ae33SNan Zhou     return str;
67072c3ae33SNan Zhou }
67172c3ae33SNan Zhou 
6727cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
6737cf436c9SEd Tanous {
6747cf436c9SEd Tanous   public:
6757cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
6767cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
6777cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
6787cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
6798a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
6807cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
6818a592810SEd Tanous         app(appIn),
6827cf436c9SEd Tanous         finalRes(std::move(finalResIn))
6837cf436c9SEd Tanous     {}
6847cf436c9SEd Tanous 
6857cf436c9SEd Tanous     void addAwaitingResponse(
68602cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
6877cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
6887cf436c9SEd Tanous     {
6897cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
69072c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
6917cf436c9SEd Tanous     }
6927cf436c9SEd Tanous 
69372c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
6947cf436c9SEd Tanous                      crow::Response& res)
6957cf436c9SEd Tanous     {
6967cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
6977cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
6987cf436c9SEd Tanous     }
6997cf436c9SEd Tanous 
70072c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
70172c3ae33SNan Zhou     // for deeper levels.
70272c3ae33SNan Zhou     void startQuery(const Query& query)
70372c3ae33SNan Zhou     {
70472c3ae33SNan Zhou         std::vector<ExpandNode> nodes =
70572c3ae33SNan Zhou             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
7067cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
707b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
708b66cf2a2SNan Zhou         if (!queryStr)
709b66cf2a2SNan Zhou         {
710b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
711b66cf2a2SNan Zhou             return;
712b66cf2a2SNan Zhou         }
7137cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
7147cf436c9SEd Tanous         {
715b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
71672c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
7177cf436c9SEd Tanous             std::error_code ec;
71872c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
7197cf436c9SEd Tanous                                  ec);
7207cf436c9SEd Tanous             if (ec)
7217cf436c9SEd Tanous             {
72272c3ae33SNan Zhou                 messages::internalError(finalRes->res);
7237cf436c9SEd Tanous                 return;
7247cf436c9SEd Tanous             }
7257cf436c9SEd Tanous 
7267cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
7277cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
7287cf436c9SEd Tanous                              << &asyncResp->res;
72972c3ae33SNan Zhou 
73072c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
7317cf436c9SEd Tanous             app.handle(newReq, asyncResp);
7327cf436c9SEd Tanous         }
7337cf436c9SEd Tanous     }
7347cf436c9SEd Tanous 
7357cf436c9SEd Tanous   private:
73672c3ae33SNan Zhou     static void
73772c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
7387cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
7397cf436c9SEd Tanous                           crow::Response& res)
7407cf436c9SEd Tanous     {
74172c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
7427cf436c9SEd Tanous     }
7437cf436c9SEd Tanous 
7447cf436c9SEd Tanous     crow::App& app;
7457cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
7467cf436c9SEd Tanous };
7477cf436c9SEd Tanous 
7482a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
7492a68dc80SEd Tanous {
7503648c8beSEd Tanous     if (!query.skip && !query.top)
7513648c8beSEd Tanous     {
7523648c8beSEd Tanous         // No work to do.
7533648c8beSEd Tanous         return;
7543648c8beSEd Tanous     }
7552a68dc80SEd Tanous     nlohmann::json::object_t* obj =
7562a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
7572a68dc80SEd Tanous     if (obj == nullptr)
7582a68dc80SEd Tanous     {
7592a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
7602a68dc80SEd Tanous         messages::internalError(res);
7612a68dc80SEd Tanous         return;
7622a68dc80SEd Tanous     }
7632a68dc80SEd Tanous 
7642a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
7652a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
7662a68dc80SEd Tanous     if (members == obj->end())
7672a68dc80SEd Tanous     {
7682a68dc80SEd Tanous         // From the Redfish specification 7.3.1
7692a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
7702a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
7712a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
7722a68dc80SEd Tanous         // collections but are used on singular resources.
7732a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
7742a68dc80SEd Tanous         return;
7752a68dc80SEd Tanous     }
7762a68dc80SEd Tanous 
7772a68dc80SEd Tanous     nlohmann::json::array_t* arr =
7782a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
7792a68dc80SEd Tanous     if (arr == nullptr)
7802a68dc80SEd Tanous     {
7812a68dc80SEd Tanous         messages::internalError(res);
7822a68dc80SEd Tanous         return;
7832a68dc80SEd Tanous     }
7842a68dc80SEd Tanous 
7853648c8beSEd Tanous     if (query.skip)
7863648c8beSEd Tanous     {
7873648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
7883648c8beSEd Tanous         // $top Can only skip as many values as we have
7893648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
7902a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
7913648c8beSEd Tanous     }
7923648c8beSEd Tanous     if (query.top)
7933648c8beSEd Tanous     {
7943648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
7952a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
7962a68dc80SEd Tanous     }
7973648c8beSEd Tanous }
7982a68dc80SEd Tanous 
799*827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are
800*827c4902SNan Zhou // not in the |currNode| Trie node.
801*827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot,
802*827c4902SNan Zhou                             const SelectTrieNode& currNode)
803e155ab54SNan Zhou {
804e155ab54SNan Zhou     nlohmann::json::object_t* object =
805e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::object_t*>();
806e155ab54SNan Zhou     if (object != nullptr)
807e155ab54SNan Zhou     {
808*827c4902SNan Zhou         BMCWEB_LOG_DEBUG << "Current JSON is an object";
809e155ab54SNan Zhou         auto it = currRoot.begin();
810e155ab54SNan Zhou         while (it != currRoot.end())
811e155ab54SNan Zhou         {
812e155ab54SNan Zhou             auto nextIt = std::next(it);
813*827c4902SNan Zhou             BMCWEB_LOG_DEBUG << "key=" << it.key();
814*827c4902SNan Zhou             const SelectTrieNode* nextNode = currNode.find(it.key());
815*827c4902SNan Zhou             if (nextNode != nullptr && nextNode->isSelected())
816e155ab54SNan Zhou             {
817e155ab54SNan Zhou                 it = nextIt;
818e155ab54SNan Zhou                 continue;
819e155ab54SNan Zhou             }
820*827c4902SNan Zhou             if (nextNode != nullptr)
821e155ab54SNan Zhou             {
822*827c4902SNan Zhou                 BMCWEB_LOG_DEBUG << "Recursively select: " << it.key();
823*827c4902SNan Zhou                 recursiveSelect(*it, *nextNode);
824e155ab54SNan Zhou                 it = nextIt;
825e155ab54SNan Zhou                 continue;
826e155ab54SNan Zhou             }
827*827c4902SNan Zhou             BMCWEB_LOG_DEBUG << it.key() << " is getting removed!";
828e155ab54SNan Zhou             it = currRoot.erase(it);
829e155ab54SNan Zhou         }
830e155ab54SNan Zhou     }
831e155ab54SNan Zhou }
832e155ab54SNan Zhou 
833e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to
834e155ab54SNan Zhou //  ambiguity and/or complexity.
835e155ab54SNan Zhou // 1. select properties in array of objects;
836e155ab54SNan Zhou // https://github.com/DMTF/Redfish/issues/5188 was created for clarification.
837e155ab54SNan Zhou // 2. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
838e155ab54SNan Zhou // created for clarification.
839*827c4902SNan Zhou // 3. respect the full odata spec; e.g., deduplication, namespace, star (*),
840e155ab54SNan Zhou // etc.
841e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse,
842*827c4902SNan Zhou                           const SelectTrieNode& trieRoot)
843e155ab54SNan Zhou {
844e155ab54SNan Zhou     BMCWEB_LOG_DEBUG << "Process $select quary parameter";
845*827c4902SNan Zhou     recursiveSelect(intermediateResponse.jsonValue, trieRoot);
846e155ab54SNan Zhou }
847e155ab54SNan Zhou 
8487cf436c9SEd Tanous inline void
849593f6449SNan Zhou     processAllParams(crow::App& app, const Query& query,
8507cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
8517cf436c9SEd Tanous                      crow::Response& intermediateResponse)
852f4c99e70SEd Tanous {
853f4c99e70SEd Tanous     if (!completionHandler)
854f4c99e70SEd Tanous     {
855f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
856f4c99e70SEd Tanous         return;
857f4c99e70SEd Tanous     }
858f4c99e70SEd Tanous 
859f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
860f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
861f4c99e70SEd Tanous     // params.
862f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
863f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
864f4c99e70SEd Tanous     {
865f4c99e70SEd Tanous         completionHandler(intermediateResponse);
866f4c99e70SEd Tanous         return;
867f4c99e70SEd Tanous     }
868f4c99e70SEd Tanous     if (query.isOnly)
869f4c99e70SEd Tanous     {
870f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
871f4c99e70SEd Tanous         return;
872f4c99e70SEd Tanous     }
8732a68dc80SEd Tanous 
8743648c8beSEd Tanous     if (query.top || query.skip)
8752a68dc80SEd Tanous     {
8762a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
8772a68dc80SEd Tanous     }
8782a68dc80SEd Tanous 
8797cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
8807cf436c9SEd Tanous     {
8817cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
88213548d85SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
88313548d85SEd Tanous             std::move(intermediateResponse));
8847cf436c9SEd Tanous 
88513548d85SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
88613548d85SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
88772c3ae33SNan Zhou         multi->startQuery(query);
8887cf436c9SEd Tanous         return;
8897cf436c9SEd Tanous     }
890e155ab54SNan Zhou 
891e155ab54SNan Zhou     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
892e155ab54SNan Zhou     // to process
893*827c4902SNan Zhou     if (!query.selectTrie.root.empty())
894e155ab54SNan Zhou     {
895*827c4902SNan Zhou         processSelect(intermediateResponse, query.selectTrie.root);
896e155ab54SNan Zhou     }
897e155ab54SNan Zhou 
898f4c99e70SEd Tanous     completionHandler(intermediateResponse);
899f4c99e70SEd Tanous }
900f4c99e70SEd Tanous 
901f4c99e70SEd Tanous } // namespace query_param
902f4c99e70SEd Tanous } // namespace redfish
903