xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 19fab295552d0baf3035afbb74f580b5c3c3f429)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3f4c99e70SEd Tanous #pragma once
4d5c80ad9SNan Zhou #include "bmcweb_config.h"
5d5c80ad9SNan Zhou 
6f4c99e70SEd Tanous #include "app.hpp"
7f4c99e70SEd Tanous #include "async_resp.hpp"
85d92fffcSrohitpai #include "error_code.hpp"
9f4c99e70SEd Tanous #include "error_messages.hpp"
1025991f7dSEd Tanous #include "filter_expr_executor.hpp"
11d7857201SEd Tanous #include "filter_expr_parser_ast.hpp"
1225991f7dSEd Tanous #include "filter_expr_printer.hpp"
13f4c99e70SEd Tanous #include "http_request.hpp"
1402cad96eSEd Tanous #include "http_response.hpp"
1595c6307aSEd Tanous #include "json_formatters.hpp"
16d5c80ad9SNan Zhou #include "logging.hpp"
17c1a75ebcSrohitpai #include "redfishoemrule.hpp"
1850ebd4afSEd Tanous #include "str_utility.hpp"
19fdf51f5cSRohit PAI #include "sub_request.hpp"
20c1a75ebcSrohitpai #include "utils/json_utils.hpp"
21f4c99e70SEd Tanous 
22d7857201SEd Tanous #include <unistd.h>
23d5c80ad9SNan Zhou 
24d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp>
25d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp>
26d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
27d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
28d5c80ad9SNan Zhou 
29d5c80ad9SNan Zhou #include <algorithm>
30e155ab54SNan Zhou #include <array>
31e155ab54SNan Zhou #include <cctype>
327cf436c9SEd Tanous #include <charconv>
33d7857201SEd Tanous #include <cstddef>
34d5c80ad9SNan Zhou #include <cstdint>
35d5c80ad9SNan Zhou #include <functional>
36e155ab54SNan Zhou #include <iterator>
37d5c80ad9SNan Zhou #include <limits>
38d5c80ad9SNan Zhou #include <map>
39d5c80ad9SNan Zhou #include <memory>
40d5c80ad9SNan Zhou #include <optional>
413544d2a7SEd Tanous #include <ranges>
42f4c99e70SEd Tanous #include <string>
43f4c99e70SEd Tanous #include <string_view>
44d5c80ad9SNan Zhou #include <system_error>
457cf436c9SEd Tanous #include <utility>
46f4c99e70SEd Tanous #include <vector>
47f4c99e70SEd Tanous 
48f4c99e70SEd Tanous namespace redfish
49f4c99e70SEd Tanous {
50f4c99e70SEd Tanous namespace query_param
51f4c99e70SEd Tanous {
52f4c99e70SEd Tanous 
537cf436c9SEd Tanous enum class ExpandType : uint8_t
547cf436c9SEd Tanous {
557cf436c9SEd Tanous     None,
567cf436c9SEd Tanous     Links,
577cf436c9SEd Tanous     NotLinks,
587cf436c9SEd Tanous     Both,
597cf436c9SEd Tanous };
607cf436c9SEd Tanous 
61827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|.
62827c4902SNan Zhou class SelectTrieNode
63827c4902SNan Zhou {
64827c4902SNan Zhou   public:
65827c4902SNan Zhou     SelectTrieNode() = default;
66827c4902SNan Zhou 
67827c4902SNan Zhou     const SelectTrieNode* find(const std::string& jsonKey) const
68827c4902SNan Zhou     {
69827c4902SNan Zhou         auto it = children.find(jsonKey);
70827c4902SNan Zhou         if (it == children.end())
71827c4902SNan Zhou         {
72827c4902SNan Zhou             return nullptr;
73827c4902SNan Zhou         }
74827c4902SNan Zhou         return &it->second;
75827c4902SNan Zhou     }
76827c4902SNan Zhou 
77827c4902SNan Zhou     // Creates a new node if the key doesn't exist, returns the reference to the
78827c4902SNan Zhou     // newly created node; otherwise, return the reference to the existing node
79827c4902SNan Zhou     SelectTrieNode* emplace(std::string_view jsonKey)
80827c4902SNan Zhou     {
81827c4902SNan Zhou         auto [it, _] = children.emplace(jsonKey, SelectTrieNode{});
82827c4902SNan Zhou         return &it->second;
83827c4902SNan Zhou     }
84827c4902SNan Zhou 
85827c4902SNan Zhou     bool empty() const
86827c4902SNan Zhou     {
87827c4902SNan Zhou         return children.empty();
88827c4902SNan Zhou     }
89827c4902SNan Zhou 
90827c4902SNan Zhou     void clear()
91827c4902SNan Zhou     {
92827c4902SNan Zhou         children.clear();
93827c4902SNan Zhou     }
94827c4902SNan Zhou 
95827c4902SNan Zhou     void setToSelected()
96827c4902SNan Zhou     {
97827c4902SNan Zhou         selected = true;
98827c4902SNan Zhou     }
99827c4902SNan Zhou 
100827c4902SNan Zhou     bool isSelected() const
101827c4902SNan Zhou     {
102827c4902SNan Zhou         return selected;
103827c4902SNan Zhou     }
104827c4902SNan Zhou 
105827c4902SNan Zhou   private:
106827c4902SNan Zhou     std::map<std::string, SelectTrieNode, std::less<>> children;
107827c4902SNan Zhou     bool selected = false;
108827c4902SNan Zhou };
109827c4902SNan Zhou 
110827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among
111827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties)
112827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property)
113827c4902SNan Zhou {
114827c4902SNan Zhou     // These a magic number, but with it it's less likely that this code
115827c4902SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
116827c4902SNan Zhou     constexpr int maxPropertyLength = 60;
117827c4902SNan Zhou     if (property.empty() || property.size() > maxPropertyLength)
118827c4902SNan Zhou     {
119827c4902SNan Zhou         return false;
120827c4902SNan Zhou     }
121827c4902SNan Zhou     for (char ch : property)
122827c4902SNan Zhou     {
123827c4902SNan Zhou         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' &&
124827c4902SNan Zhou             ch != '@' && ch != '.')
125827c4902SNan Zhou         {
126827c4902SNan Zhou             return false;
127827c4902SNan Zhou         }
128827c4902SNan Zhou     }
129827c4902SNan Zhou     return true;
130827c4902SNan Zhou }
131827c4902SNan Zhou 
132827c4902SNan Zhou struct SelectTrie
133827c4902SNan Zhou {
134827c4902SNan Zhou     SelectTrie() = default;
135827c4902SNan Zhou 
136827c4902SNan Zhou     // Inserts a $select value; returns false if the nestedProperty is illegal.
137827c4902SNan Zhou     bool insertNode(std::string_view nestedProperty)
138827c4902SNan Zhou     {
139827c4902SNan Zhou         if (nestedProperty.empty())
140827c4902SNan Zhou         {
141827c4902SNan Zhou             return false;
142827c4902SNan Zhou         }
143827c4902SNan Zhou         SelectTrieNode* currNode = &root;
144827c4902SNan Zhou         size_t index = nestedProperty.find_first_of('/');
145827c4902SNan Zhou         while (!nestedProperty.empty())
146827c4902SNan Zhou         {
147827c4902SNan Zhou             std::string_view property = nestedProperty.substr(0, index);
148827c4902SNan Zhou             if (!isSelectedPropertyAllowed(property))
149827c4902SNan Zhou             {
150827c4902SNan Zhou                 return false;
151827c4902SNan Zhou             }
152827c4902SNan Zhou             currNode = currNode->emplace(property);
153827c4902SNan Zhou             if (index == std::string::npos)
154827c4902SNan Zhou             {
155827c4902SNan Zhou                 break;
156827c4902SNan Zhou             }
157827c4902SNan Zhou             nestedProperty.remove_prefix(index + 1);
158827c4902SNan Zhou             index = nestedProperty.find_first_of('/');
159827c4902SNan Zhou         }
160827c4902SNan Zhou         currNode->setToSelected();
161827c4902SNan Zhou         return true;
162827c4902SNan Zhou     }
163827c4902SNan Zhou 
164827c4902SNan Zhou     SelectTrieNode root;
165827c4902SNan Zhou };
166827c4902SNan Zhou 
167a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
168f4c99e70SEd Tanous struct Query
169f4c99e70SEd Tanous {
170a6b9125fSNan Zhou     // Only
171f4c99e70SEd Tanous     bool isOnly = false;
17225991f7dSEd Tanous 
173a6b9125fSNan Zhou     // Expand
174a6b9125fSNan Zhou     uint8_t expandLevel = 0;
1757cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
176c937d2bfSEd Tanous 
177c937d2bfSEd Tanous     // Skip
1783648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
179c937d2bfSEd Tanous 
180c937d2bfSEd Tanous     // Top
1815143f7a5SJiaqing Zhao     static constexpr size_t maxTop = 1000; // Max entries a response contain
1823648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
183e155ab54SNan Zhou 
18425991f7dSEd Tanous     // Filter
18525991f7dSEd Tanous     std::optional<filter_ast::LogicalAnd> filter = std::nullopt;
18625991f7dSEd Tanous 
187e155ab54SNan Zhou     // Select
18847f2934cSEd Tanous     // Unclear how to make this use structured initialization without this.
18947f2934cSEd Tanous     // Might be a tidy bug?  Ignore for now
19047f2934cSEd Tanous     // NOLINTNEXTLINE(readability-redundant-member-init)
19147f2934cSEd Tanous     SelectTrie selectTrie{};
192f4c99e70SEd Tanous };
193f4c99e70SEd Tanous 
194a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
195a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
196a6b9125fSNan Zhou // the processing.
197a6b9125fSNan Zhou struct QueryCapabilities
198a6b9125fSNan Zhou {
199a6b9125fSNan Zhou     bool canDelegateOnly = false;
200c937d2bfSEd Tanous     bool canDelegateTop = false;
201c937d2bfSEd Tanous     bool canDelegateSkip = false;
202a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
203e155ab54SNan Zhou     bool canDelegateSelect = false;
204a6b9125fSNan Zhou };
205a6b9125fSNan Zhou 
206a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
207a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
208a6b9125fSNan Zhou // function will take care of it.
209a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
210a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
211a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
212a6b9125fSNan Zhou {
213f1a1e3dcSEd Tanous     Query delegated{};
214a6b9125fSNan Zhou     // delegate only
215a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
216a6b9125fSNan Zhou     {
217a6b9125fSNan Zhou         delegated.isOnly = true;
218a6b9125fSNan Zhou         query.isOnly = false;
219a6b9125fSNan Zhou     }
220a6b9125fSNan Zhou     // delegate expand as much as we can
221a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
222a6b9125fSNan Zhou     {
223a6b9125fSNan Zhou         delegated.expandType = query.expandType;
224a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
225a6b9125fSNan Zhou         {
226a6b9125fSNan Zhou             query.expandType = ExpandType::None;
227a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
228a6b9125fSNan Zhou             query.expandLevel = 0;
229a6b9125fSNan Zhou         }
230a6b9125fSNan Zhou         else
231a6b9125fSNan Zhou         {
232a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
233a6b9125fSNan Zhou         }
234a6b9125fSNan Zhou     }
235c937d2bfSEd Tanous 
236c937d2bfSEd Tanous     // delegate top
2373648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
238c937d2bfSEd Tanous     {
239c937d2bfSEd Tanous         delegated.top = query.top;
2403648c8beSEd Tanous         query.top = std::nullopt;
241c937d2bfSEd Tanous     }
242c937d2bfSEd Tanous 
243c937d2bfSEd Tanous     // delegate skip
2443648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
245c937d2bfSEd Tanous     {
246c937d2bfSEd Tanous         delegated.skip = query.skip;
247c937d2bfSEd Tanous         query.skip = 0;
248c937d2bfSEd Tanous     }
249e155ab54SNan Zhou 
250e155ab54SNan Zhou     // delegate select
251827c4902SNan Zhou     if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect)
252e155ab54SNan Zhou     {
253827c4902SNan Zhou         delegated.selectTrie = std::move(query.selectTrie);
254827c4902SNan Zhou         query.selectTrie.root.clear();
255e155ab54SNan Zhou     }
256a6b9125fSNan Zhou     return delegated;
257a6b9125fSNan Zhou }
258a6b9125fSNan Zhou 
2597cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
2607cf436c9SEd Tanous {
2617cf436c9SEd Tanous     if (value.empty())
2627cf436c9SEd Tanous     {
2637cf436c9SEd Tanous         return false;
2647cf436c9SEd Tanous     }
2657cf436c9SEd Tanous     switch (value[0])
2667cf436c9SEd Tanous     {
2677cf436c9SEd Tanous         case '*':
2687cf436c9SEd Tanous             query.expandType = ExpandType::Both;
2697cf436c9SEd Tanous             break;
2707cf436c9SEd Tanous         case '.':
2717cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
2727cf436c9SEd Tanous             break;
2737cf436c9SEd Tanous         case '~':
2747cf436c9SEd Tanous             query.expandType = ExpandType::Links;
2757cf436c9SEd Tanous             break;
2767cf436c9SEd Tanous         default:
2777cf436c9SEd Tanous             return false;
2787cf436c9SEd Tanous     }
2797cf436c9SEd Tanous     value.remove_prefix(1);
2807cf436c9SEd Tanous     if (value.empty())
2817cf436c9SEd Tanous     {
2827cf436c9SEd Tanous         query.expandLevel = 1;
2837cf436c9SEd Tanous         return true;
2847cf436c9SEd Tanous     }
2857cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
2867cf436c9SEd Tanous     if (!value.starts_with(levels))
2877cf436c9SEd Tanous     {
2887cf436c9SEd Tanous         return false;
2897cf436c9SEd Tanous     }
2907cf436c9SEd Tanous     value.remove_prefix(levels.size());
2917cf436c9SEd Tanous 
2922bd4ab43SPatrick Williams     auto it = std::from_chars(value.begin(), value.end(), query.expandLevel);
2937cf436c9SEd Tanous     if (it.ec != std::errc())
2947cf436c9SEd Tanous     {
2957cf436c9SEd Tanous         return false;
2967cf436c9SEd Tanous     }
2972bd4ab43SPatrick Williams     value.remove_prefix(
2982bd4ab43SPatrick Williams         static_cast<size_t>(std::distance(value.begin(), it.ptr)));
2997cf436c9SEd Tanous     return value == ")";
3007cf436c9SEd Tanous }
3017cf436c9SEd Tanous 
302c937d2bfSEd Tanous enum class QueryError
303c937d2bfSEd Tanous {
304c937d2bfSEd Tanous     Ok,
305c937d2bfSEd Tanous     OutOfRange,
306c937d2bfSEd Tanous     ValueFormat,
307c937d2bfSEd Tanous };
308c937d2bfSEd Tanous 
309c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
310c937d2bfSEd Tanous {
311bd79bce8SPatrick Williams     std::from_chars_result r =
312bd79bce8SPatrick Williams         std::from_chars(value.begin(), value.end(), param);
313c937d2bfSEd Tanous 
314c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
315c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
316c937d2bfSEd Tanous     {
317c937d2bfSEd Tanous         return QueryError::OutOfRange;
318c937d2bfSEd Tanous     }
319c937d2bfSEd Tanous     // All other errors are value format
320c937d2bfSEd Tanous     if (r.ec != std::errc())
321c937d2bfSEd Tanous     {
322c937d2bfSEd Tanous         return QueryError::ValueFormat;
323c937d2bfSEd Tanous     }
324c937d2bfSEd Tanous     return QueryError::Ok;
325c937d2bfSEd Tanous }
326c937d2bfSEd Tanous 
327c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
328c937d2bfSEd Tanous {
3293648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
330c937d2bfSEd Tanous }
331c937d2bfSEd Tanous 
332c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
333c937d2bfSEd Tanous {
3343648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
335c937d2bfSEd Tanous     if (ret != QueryError::Ok)
336c937d2bfSEd Tanous     {
337c937d2bfSEd Tanous         return ret;
338c937d2bfSEd Tanous     }
339c937d2bfSEd Tanous 
340c937d2bfSEd Tanous     // Range check for sanity.
3415143f7a5SJiaqing Zhao     if (query.top > Query::maxTop)
342c937d2bfSEd Tanous     {
343c937d2bfSEd Tanous         return QueryError::OutOfRange;
344c937d2bfSEd Tanous     }
345c937d2bfSEd Tanous 
346c937d2bfSEd Tanous     return QueryError::Ok;
347c937d2bfSEd Tanous }
348c937d2bfSEd Tanous 
349e155ab54SNan Zhou // Parses and validates the $select parameter.
350e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be
351e155ab54SNan Zhou // comma separated Resource Path
352e155ab54SNan Zhou // Ref:
353e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
354e155ab54SNan Zhou // 2.
355e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
356e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query)
357e155ab54SNan Zhou {
358e155ab54SNan Zhou     std::vector<std::string> properties;
35950ebd4afSEd Tanous     bmcweb::split(properties, value, ',');
360e155ab54SNan Zhou     if (properties.empty())
361e155ab54SNan Zhou     {
362e155ab54SNan Zhou         return false;
363e155ab54SNan Zhou     }
364e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
365e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
366e155ab54SNan Zhou     constexpr int maxNumProperties = 10;
367e155ab54SNan Zhou     if (properties.size() > maxNumProperties)
368e155ab54SNan Zhou     {
369e155ab54SNan Zhou         return false;
370e155ab54SNan Zhou     }
371827c4902SNan Zhou     for (const auto& property : properties)
372e155ab54SNan Zhou     {
373827c4902SNan Zhou         if (!query.selectTrie.insertNode(property))
374e155ab54SNan Zhou         {
375e155ab54SNan Zhou             return false;
376e155ab54SNan Zhou         }
377e155ab54SNan Zhou     }
378e155ab54SNan Zhou     return true;
379e155ab54SNan Zhou }
380e155ab54SNan Zhou 
38125991f7dSEd Tanous // Parses and validates the $filter parameter.
38225991f7dSEd Tanous inline bool getFilterParam(std::string_view value, Query& query)
38325991f7dSEd Tanous {
38425991f7dSEd Tanous     query.filter = parseFilter(value);
38525991f7dSEd Tanous     return query.filter.has_value();
38625991f7dSEd Tanous }
38725991f7dSEd Tanous 
388504af5a0SPatrick Williams inline std::optional<Query> parseParameters(boost::urls::params_view urlParams,
389504af5a0SPatrick Williams                                             crow::Response& res)
390f4c99e70SEd Tanous {
391f1a1e3dcSEd Tanous     Query ret{};
392f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
393f4c99e70SEd Tanous     {
394079360aeSEd Tanous         if (it.key == "only")
395f4c99e70SEd Tanous         {
396f4c99e70SEd Tanous             if (!it.value.empty())
397f4c99e70SEd Tanous             {
398079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
399f4c99e70SEd Tanous                 return std::nullopt;
400f4c99e70SEd Tanous             }
401f4c99e70SEd Tanous             ret.isOnly = true;
402f4c99e70SEd Tanous         }
40325b54dbaSEd Tanous         else if (it.key == "$expand" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY)
4047cf436c9SEd Tanous         {
405079360aeSEd Tanous             if (!getExpandType(it.value, ret))
4067cf436c9SEd Tanous             {
407079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
4087cf436c9SEd Tanous                 return std::nullopt;
409f4c99e70SEd Tanous             }
4107cf436c9SEd Tanous         }
411079360aeSEd Tanous         else if (it.key == "$top")
412c937d2bfSEd Tanous         {
413079360aeSEd Tanous             QueryError topRet = getTopParam(it.value, ret);
414c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
415c937d2bfSEd Tanous             {
416079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
417c937d2bfSEd Tanous                 return std::nullopt;
418c937d2bfSEd Tanous             }
419c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
420c937d2bfSEd Tanous             {
421c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
422079360aeSEd Tanous                     res, it.value, "$top",
423079360aeSEd Tanous                     "0-" + std::to_string(Query::maxTop));
424c937d2bfSEd Tanous                 return std::nullopt;
425c937d2bfSEd Tanous             }
426c937d2bfSEd Tanous         }
427079360aeSEd Tanous         else if (it.key == "$skip")
428c937d2bfSEd Tanous         {
429079360aeSEd Tanous             QueryError topRet = getSkipParam(it.value, ret);
430c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
431c937d2bfSEd Tanous             {
432079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
433c937d2bfSEd Tanous                 return std::nullopt;
434c937d2bfSEd Tanous             }
435c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
436c937d2bfSEd Tanous             {
437c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
438079360aeSEd Tanous                     res, it.value, it.key,
439a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
440c937d2bfSEd Tanous                 return std::nullopt;
441c937d2bfSEd Tanous             }
442c937d2bfSEd Tanous         }
443079360aeSEd Tanous         else if (it.key == "$select")
444e155ab54SNan Zhou         {
445079360aeSEd Tanous             if (!getSelectParam(it.value, ret))
446e155ab54SNan Zhou             {
447079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
448e155ab54SNan Zhou                 return std::nullopt;
449e155ab54SNan Zhou             }
450e155ab54SNan Zhou         }
45125991f7dSEd Tanous         else if (it.key == "$filter" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY)
45225991f7dSEd Tanous         {
45325991f7dSEd Tanous             if (!getFilterParam(it.value, ret))
45425991f7dSEd Tanous             {
45525991f7dSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
45625991f7dSEd Tanous                 return std::nullopt;
45725991f7dSEd Tanous             }
45825991f7dSEd Tanous         }
4597cf436c9SEd Tanous         else
4607cf436c9SEd Tanous         {
4617cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
462079360aeSEd Tanous             if (it.key.starts_with("$"))
4637cf436c9SEd Tanous             {
4647cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
4657cf436c9SEd Tanous                 // status code for any unsupported query parameters that
4667cf436c9SEd Tanous                 // start with $ .
467079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
4687cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
4697cf436c9SEd Tanous                 return std::nullopt;
4707cf436c9SEd Tanous             }
4717cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
4727cf436c9SEd Tanous             // not begin with $ ."
4737cf436c9SEd Tanous         }
4747cf436c9SEd Tanous     }
4757cf436c9SEd Tanous 
476827c4902SNan Zhou     if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty())
477e155ab54SNan Zhou     {
478e155ab54SNan Zhou         messages::queryCombinationInvalid(res);
479e155ab54SNan Zhou         return std::nullopt;
480e155ab54SNan Zhou     }
481e155ab54SNan Zhou 
482f4c99e70SEd Tanous     return ret;
483f4c99e70SEd Tanous }
484f4c99e70SEd Tanous 
485f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
486f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
487f4c99e70SEd Tanous {
48862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Processing only query param");
489f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
490f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
491f4c99e70SEd Tanous     {
492f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
493f4c99e70SEd Tanous         completionHandler(res);
494f4c99e70SEd Tanous         return false;
495f4c99e70SEd Tanous     }
496f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
497f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
498f4c99e70SEd Tanous     {
49962598e31SEd Tanous         BMCWEB_LOG_DEBUG(
50062598e31SEd Tanous             "Members contains {} element, returning full collection.",
50162598e31SEd Tanous             itMembers->size());
502f4c99e70SEd Tanous         completionHandler(res);
503f4c99e70SEd Tanous         return false;
504f4c99e70SEd Tanous     }
505f4c99e70SEd Tanous 
506f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
507f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
508f4c99e70SEd Tanous     {
50962598e31SEd Tanous         BMCWEB_LOG_DEBUG("No found odata.id");
510f4c99e70SEd Tanous         messages::internalError(res);
511f4c99e70SEd Tanous         completionHandler(res);
512f4c99e70SEd Tanous         return false;
513f4c99e70SEd Tanous     }
514f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
515f4c99e70SEd Tanous     if (url == nullptr)
516f4c99e70SEd Tanous     {
51762598e31SEd Tanous         BMCWEB_LOG_DEBUG("@odata.id wasn't a string????");
518f4c99e70SEd Tanous         messages::internalError(res);
519f4c99e70SEd Tanous         completionHandler(res);
520f4c99e70SEd Tanous         return false;
521f4c99e70SEd Tanous     }
522f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
523f4c99e70SEd Tanous     // newReq.session = req.session;
524f4c99e70SEd Tanous     std::error_code ec;
525102a4cdaSJonathan Doman     auto newReq = std::make_shared<crow::Request>(
526102a4cdaSJonathan Doman         crow::Request::Body{boost::beast::http::verb::get, *url, 11}, ec);
527f4c99e70SEd Tanous     if (ec)
528f4c99e70SEd Tanous     {
529f4c99e70SEd Tanous         messages::internalError(res);
530f4c99e70SEd Tanous         completionHandler(res);
531f4c99e70SEd Tanous         return false;
532f4c99e70SEd Tanous     }
533f4c99e70SEd Tanous 
534f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
53562598e31SEd Tanous     BMCWEB_LOG_DEBUG("setting completion handler on {}",
53662598e31SEd Tanous                      logPtr(&asyncResp->res));
537f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
538f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
539f4c99e70SEd Tanous     return true;
540f4c99e70SEd Tanous }
541f4c99e70SEd Tanous 
5427cf436c9SEd Tanous struct ExpandNode
5437cf436c9SEd Tanous {
5447cf436c9SEd Tanous     nlohmann::json::json_pointer location;
5457cf436c9SEd Tanous     std::string uri;
5467cf436c9SEd Tanous 
5479de65b34SEd Tanous     bool operator==(const ExpandNode& other) const
5487cf436c9SEd Tanous     {
5497cf436c9SEd Tanous         return location == other.location && uri == other.uri;
5507cf436c9SEd Tanous     }
5517cf436c9SEd Tanous };
5527cf436c9SEd Tanous 
55387788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive(
554c59e338cSEd Tanous     ExpandType eType, nlohmann::json::array_t& array,
55537b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
55687788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out);
55787788abfSEd Tanous 
55887788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive(
559c59e338cSEd Tanous     ExpandType eType, nlohmann::json::object_t& obj,
56037b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
56187788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out);
56287788abfSEd Tanous 
5637cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
5647cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
5657cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
5667cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
5677cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
5687cf436c9SEd Tanous // with the keys from the jsonResponse object
5697cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
5707cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
57137b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
57232cdb4a7SWilly Tu     bool inLinks, std::vector<ExpandNode>& out)
5737cf436c9SEd Tanous {
5747cf436c9SEd Tanous     // If no expand is needed, return early
5757cf436c9SEd Tanous     if (eType == ExpandType::None)
5767cf436c9SEd Tanous     {
5777cf436c9SEd Tanous         return;
5787cf436c9SEd Tanous     }
579ad595fa6SEd Tanous 
5807cf436c9SEd Tanous     nlohmann::json::array_t* array =
5817cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
5827cf436c9SEd Tanous     if (array != nullptr)
5837cf436c9SEd Tanous     {
58437b1f7beSEd Tanous         findNavigationReferencesInArrayRecursive(eType, *array, jsonPtr, depth,
58587788abfSEd Tanous                                                  skipDepth, inLinks, out);
58687788abfSEd Tanous     }
58787788abfSEd Tanous     nlohmann::json::object_t* obj =
58887788abfSEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
58987788abfSEd Tanous     if (obj == nullptr)
59087788abfSEd Tanous     {
59187788abfSEd Tanous         return;
59287788abfSEd Tanous     }
59337b1f7beSEd Tanous     findNavigationReferencesInObjectRecursive(eType, *obj, jsonPtr, depth,
59437b1f7beSEd Tanous                                               skipDepth, inLinks, out);
59587788abfSEd Tanous }
59687788abfSEd Tanous 
59787788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive(
598c59e338cSEd Tanous     ExpandType eType, nlohmann::json::array_t& array,
59937b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
60087788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out)
60187788abfSEd Tanous {
6027cf436c9SEd Tanous     size_t index = 0;
6037cf436c9SEd Tanous     // For arrays, walk every element in the array
604c59e338cSEd Tanous     for (auto& element : array)
6057cf436c9SEd Tanous     {
60637b1f7beSEd Tanous         nlohmann::json::json_pointer newPtr = jsonPtr / index;
60762598e31SEd Tanous         BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr.to_string());
608ad595fa6SEd Tanous         findNavigationReferencesRecursive(eType, element, newPtr, depth,
60932cdb4a7SWilly Tu                                           skipDepth, inLinks, out);
6107cf436c9SEd Tanous         index++;
6117cf436c9SEd Tanous     }
6127cf436c9SEd Tanous }
61387788abfSEd Tanous 
61487788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive(
615c59e338cSEd Tanous     ExpandType eType, nlohmann::json::object_t& obj,
61637b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
61787788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out)
6187cf436c9SEd Tanous {
6197cf436c9SEd Tanous     // Navigation References only ever have a single element
620c59e338cSEd Tanous     if (obj.size() == 1)
6217cf436c9SEd Tanous     {
622c59e338cSEd Tanous         if (obj.begin()->first == "@odata.id")
6237cf436c9SEd Tanous         {
6247cf436c9SEd Tanous             const std::string* uri =
625c59e338cSEd Tanous                 obj.begin()->second.get_ptr<const std::string*>();
6267cf436c9SEd Tanous             if (uri != nullptr)
6277cf436c9SEd Tanous             {
62862598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Found {} at {}", *uri, jsonPtr.to_string());
62932cdb4a7SWilly Tu                 if (skipDepth == 0)
63032cdb4a7SWilly Tu                 {
63137b1f7beSEd Tanous                     out.push_back({jsonPtr, *uri});
63232cdb4a7SWilly Tu                 }
633ad595fa6SEd Tanous                 return;
6347cf436c9SEd Tanous             }
6357cf436c9SEd Tanous         }
6367cf436c9SEd Tanous     }
637ad595fa6SEd Tanous 
638ad595fa6SEd Tanous     int newDepth = depth;
639c59e338cSEd Tanous     auto odataId = obj.find("@odata.id");
640c59e338cSEd Tanous     if (odataId != obj.end())
641ad595fa6SEd Tanous     {
642ad595fa6SEd Tanous         // The Redfish spec requires all resources to include the resource
643ad595fa6SEd Tanous         // identifier.  If the object has multiple elements and one of them is
644ad595fa6SEd Tanous         // "@odata.id" then that means we have entered a new level / expanded
645ad595fa6SEd Tanous         // resource.  We need to stop traversing if we're already at the desired
646ad595fa6SEd Tanous         // depth
647c59e338cSEd Tanous         if (obj.size() > 1)
64832cdb4a7SWilly Tu         {
64932cdb4a7SWilly Tu             if (depth == 0)
650ad595fa6SEd Tanous             {
651ad595fa6SEd Tanous                 return;
652ad595fa6SEd Tanous             }
65332cdb4a7SWilly Tu             if (skipDepth > 0)
65432cdb4a7SWilly Tu             {
65532cdb4a7SWilly Tu                 skipDepth--;
65632cdb4a7SWilly Tu             }
65732cdb4a7SWilly Tu         }
65832cdb4a7SWilly Tu 
65932cdb4a7SWilly Tu         if (skipDepth == 0)
66032cdb4a7SWilly Tu         {
661ad595fa6SEd Tanous             newDepth--;
662ad595fa6SEd Tanous         }
66332cdb4a7SWilly Tu     }
664ad595fa6SEd Tanous 
6657cf436c9SEd Tanous     // Loop the object and look for links
666c59e338cSEd Tanous     for (auto& element : obj)
6677cf436c9SEd Tanous     {
668e479ad58SNan Zhou         bool localInLinks = inLinks;
669e479ad58SNan Zhou         if (!localInLinks)
6707cf436c9SEd Tanous         {
6717cf436c9SEd Tanous             // Check if this is a links node
672e479ad58SNan Zhou             localInLinks = element.first == "Links";
6737cf436c9SEd Tanous         }
6747cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
6757cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
676e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
6777cf436c9SEd Tanous         {
6787cf436c9SEd Tanous             continue;
6797cf436c9SEd Tanous         }
680e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
6817cf436c9SEd Tanous         {
6827cf436c9SEd Tanous             continue;
6837cf436c9SEd Tanous         }
68437b1f7beSEd Tanous         nlohmann::json::json_pointer newPtr = jsonPtr / element.first;
68562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr);
6867cf436c9SEd Tanous 
6877cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
68832cdb4a7SWilly Tu                                           newDepth, skipDepth, localInLinks,
68932cdb4a7SWilly Tu                                           out);
6907cf436c9SEd Tanous     }
6917cf436c9SEd Tanous }
6927cf436c9SEd Tanous 
693ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded
694ad595fa6SEd Tanous // response we may need need additional handling when the original URI was
695ad595fa6SEd Tanous // up tree from a top level collection.
696ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556
697ad595fa6SEd Tanous // lands.  May want to avoid forwarding query params when request is uptree from
698ad595fa6SEd Tanous // a top level collection.
699bd79bce8SPatrick Williams inline std::vector<ExpandNode> findNavigationReferences(
700bd79bce8SPatrick Williams     ExpandType eType, int depth, int skipDepth, nlohmann::json& jsonResponse)
7017cf436c9SEd Tanous {
7027cf436c9SEd Tanous     std::vector<ExpandNode> ret;
70372c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
70432cdb4a7SWilly Tu     // SkipDepth +1 since we are skipping the root by default.
70532cdb4a7SWilly Tu     findNavigationReferencesRecursive(eType, jsonResponse, root, depth,
70632cdb4a7SWilly Tu                                       skipDepth + 1, false, ret);
7077cf436c9SEd Tanous     return ret;
7087cf436c9SEd Tanous }
7097cf436c9SEd Tanous 
71072c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
711b66cf2a2SNan Zhou // Returns std::nullopt on failures.
71272c3ae33SNan Zhou // This function shall handle $select when it is added.
7138ece0e45SEd Tanous // There is no need to handle parameters that's not compatible with $expand,
71472c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
715b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
71672c3ae33SNan Zhou {
71772c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
71872c3ae33SNan Zhou     if (query.expandLevel <= 1)
71972c3ae33SNan Zhou     {
720b66cf2a2SNan Zhou         return "";
72172c3ae33SNan Zhou     }
72272c3ae33SNan Zhou     std::string str = "?$expand=";
72372c3ae33SNan Zhou     switch (query.expandType)
72472c3ae33SNan Zhou     {
72572c3ae33SNan Zhou         case ExpandType::Links:
72672c3ae33SNan Zhou             str += '~';
72772c3ae33SNan Zhou             break;
72872c3ae33SNan Zhou         case ExpandType::NotLinks:
72972c3ae33SNan Zhou             str += '.';
73072c3ae33SNan Zhou             break;
73172c3ae33SNan Zhou         case ExpandType::Both:
73272c3ae33SNan Zhou             str += '*';
73372c3ae33SNan Zhou             break;
734f1a1e3dcSEd Tanous         case ExpandType::None:
735f1a1e3dcSEd Tanous             return "";
7364da0490bSEd Tanous         default:
7374da0490bSEd Tanous             return std::nullopt;
738b66cf2a2SNan Zhou     }
73972c3ae33SNan Zhou     str += "($levels=";
74072c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
74172c3ae33SNan Zhou     str += ')';
74272c3ae33SNan Zhou     return str;
74372c3ae33SNan Zhou }
74472c3ae33SNan Zhou 
7457cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
7467cf436c9SEd Tanous {
7477cf436c9SEd Tanous   public:
7487cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
7497cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
7507cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
7517cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
7528a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
7537cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
754c1a75ebcSrohitpai         app(&appIn), finalRes(std::move(finalResIn))
755c1a75ebcSrohitpai     {}
756c1a75ebcSrohitpai 
757c1a75ebcSrohitpai     explicit MultiAsyncResp(std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
758c1a75ebcSrohitpai         app(nullptr), finalRes(std::move(finalResIn))
7597cf436c9SEd Tanous     {}
7607cf436c9SEd Tanous 
7617cf436c9SEd Tanous     void addAwaitingResponse(
76202cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
7637cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
7647cf436c9SEd Tanous     {
7657cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
76672c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
7677cf436c9SEd Tanous     }
7687cf436c9SEd Tanous 
76972c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
7707cf436c9SEd Tanous                      crow::Response& res)
7717cf436c9SEd Tanous     {
77262598e31SEd Tanous         BMCWEB_LOG_DEBUG("placeResult for {}", locationToPlace);
7733590bd1dSNan Zhou         propogateError(finalRes->res, res);
774d0a341b9SEd Tanous         nlohmann::json::object_t* obj =
775d0a341b9SEd Tanous             res.jsonValue.get_ptr<nlohmann::json::object_t*>();
776d0a341b9SEd Tanous         if (obj == nullptr || res.jsonValue.empty())
7773590bd1dSNan Zhou         {
7783590bd1dSNan Zhou             return;
7793590bd1dSNan Zhou         }
7807cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
781d0a341b9SEd Tanous         finalObj = std::move(*obj);
7827cf436c9SEd Tanous     }
7837cf436c9SEd Tanous 
78472c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
78572c3ae33SNan Zhou     // for deeper levels.
786*19fab295SChandramohan Harkude     void startQuery(const Query& query, const Query& delegated,
787*19fab295SChandramohan Harkude                     const crow::Request& req)
78872c3ae33SNan Zhou     {
789ad595fa6SEd Tanous         std::vector<ExpandNode> nodes = findNavigationReferences(
79032cdb4a7SWilly Tu             query.expandType, query.expandLevel, delegated.expandLevel,
79132cdb4a7SWilly Tu             finalRes->res.jsonValue);
79262598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} nodes to traverse", nodes.size());
793b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
794b66cf2a2SNan Zhou         if (!queryStr)
795b66cf2a2SNan Zhou         {
796b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
797b66cf2a2SNan Zhou             return;
798b66cf2a2SNan Zhou         }
7997cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
8007cf436c9SEd Tanous         {
801b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
80262598e31SEd Tanous             BMCWEB_LOG_DEBUG("URL of subquery:  {}", subQuery);
8037cf436c9SEd Tanous             std::error_code ec;
804102a4cdaSJonathan Doman             auto newReq = std::make_shared<crow::Request>(
805102a4cdaSJonathan Doman                 crow::Request::Body{boost::beast::http::verb::get, subQuery,
806102a4cdaSJonathan Doman                                     11},
8077cf436c9SEd Tanous                 ec);
8087cf436c9SEd Tanous             if (ec)
8097cf436c9SEd Tanous             {
81072c3ae33SNan Zhou                 messages::internalError(finalRes->res);
8117cf436c9SEd Tanous                 return;
8127cf436c9SEd Tanous             }
8137cf436c9SEd Tanous 
814*19fab295SChandramohan Harkude             if (req.session == nullptr)
815*19fab295SChandramohan Harkude             {
816*19fab295SChandramohan Harkude                 BMCWEB_LOG_ERROR("Session is null");
817*19fab295SChandramohan Harkude                 messages::internalError(finalRes->res);
818*19fab295SChandramohan Harkude                 return;
819*19fab295SChandramohan Harkude             }
820*19fab295SChandramohan Harkude             // Share the session from the original request
821*19fab295SChandramohan Harkude             newReq->session = req.session;
822*19fab295SChandramohan Harkude 
8237cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
82462598e31SEd Tanous             BMCWEB_LOG_DEBUG("setting completion handler on {}",
82562598e31SEd Tanous                              logPtr(&asyncResp->res));
82672c3ae33SNan Zhou 
82772c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
828c1a75ebcSrohitpai             if (app != nullptr)
829c1a75ebcSrohitpai             {
830c1a75ebcSrohitpai                 app->handle(newReq, asyncResp);
831c1a75ebcSrohitpai             }
832c1a75ebcSrohitpai         }
833c1a75ebcSrohitpai     }
834c1a75ebcSrohitpai 
835c1a75ebcSrohitpai     static void startMultiFragmentHandle(
836fdf51f5cSRohit PAI         const std::shared_ptr<redfish::SubRequest>& req,
837c1a75ebcSrohitpai         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
838c1a75ebcSrohitpai         const std::shared_ptr<std::vector<OemBaseRule*>>& fragments,
839c1a75ebcSrohitpai         const std::shared_ptr<std::vector<std::string>>& params,
840c1a75ebcSrohitpai         const crow::Response& resIn)
841c1a75ebcSrohitpai     {
842c1a75ebcSrohitpai         asyncResp->res.jsonValue = resIn.jsonValue;
843c1a75ebcSrohitpai         auto multi = std::make_shared<MultiAsyncResp>(asyncResp);
844c1a75ebcSrohitpai         for (OemBaseRule* fragment : *fragments)
845c1a75ebcSrohitpai         {
846c1a75ebcSrohitpai             if (fragment != nullptr)
847c1a75ebcSrohitpai             {
848c1a75ebcSrohitpai                 OemBaseRule& fragmentRule = *fragment;
849c1a75ebcSrohitpai                 auto rsp = std::make_shared<bmcweb::AsyncResp>();
850fdf51f5cSRohit PAI                 BMCWEB_LOG_DEBUG("Matched fragment rule '{}' method '{}'",
851fdf51f5cSRohit PAI                                  fragmentRule.rule,
852fdf51f5cSRohit PAI                                  boost::beast::http::to_string(req->method()));
853c1a75ebcSrohitpai                 BMCWEB_LOG_DEBUG(
854c1a75ebcSrohitpai                     "Handling fragment rules: setting completion handler on {}",
855c1a75ebcSrohitpai                     logPtr(&rsp->res));
856c1a75ebcSrohitpai                 std::optional<nlohmann::json::json_pointer> jsonFragmentPtr =
857c1a75ebcSrohitpai                     json_util::createJsonPointerFromFragment(fragmentRule.rule);
858c1a75ebcSrohitpai                 if (jsonFragmentPtr)
859c1a75ebcSrohitpai                 {
860c1a75ebcSrohitpai                     multi->addAwaitingResponse(rsp, *jsonFragmentPtr);
861c1a75ebcSrohitpai                     fragmentRule.handle(*req, rsp, *params);
862c1a75ebcSrohitpai                 }
863c1a75ebcSrohitpai             }
8647cf436c9SEd Tanous         }
8657cf436c9SEd Tanous     }
8667cf436c9SEd Tanous 
8677cf436c9SEd Tanous   private:
868504af5a0SPatrick Williams     static void placeResultStatic(
869504af5a0SPatrick Williams         const std::shared_ptr<MultiAsyncResp>& multi,
8707cf436c9SEd Tanous         const nlohmann::json::json_pointer& locationToPlace,
8717cf436c9SEd Tanous         crow::Response& res)
8727cf436c9SEd Tanous     {
87372c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
8747cf436c9SEd Tanous     }
8757cf436c9SEd Tanous 
876c1a75ebcSrohitpai     crow::App* app;
8777cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
8787cf436c9SEd Tanous };
8797cf436c9SEd Tanous 
8802a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
8812a68dc80SEd Tanous {
8823648c8beSEd Tanous     if (!query.skip && !query.top)
8833648c8beSEd Tanous     {
8843648c8beSEd Tanous         // No work to do.
8853648c8beSEd Tanous         return;
8863648c8beSEd Tanous     }
8872a68dc80SEd Tanous     nlohmann::json::object_t* obj =
8882a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
8892a68dc80SEd Tanous     if (obj == nullptr)
8902a68dc80SEd Tanous     {
8912a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
8922a68dc80SEd Tanous         messages::internalError(res);
8932a68dc80SEd Tanous         return;
8942a68dc80SEd Tanous     }
8952a68dc80SEd Tanous 
89662598e31SEd Tanous     BMCWEB_LOG_DEBUG("Handling top/skip");
8972a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
8982a68dc80SEd Tanous     if (members == obj->end())
8992a68dc80SEd Tanous     {
9002a68dc80SEd Tanous         // From the Redfish specification 7.3.1
9012a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
9022a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
9032a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
9042a68dc80SEd Tanous         // collections but are used on singular resources.
9052a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
9062a68dc80SEd Tanous         return;
9072a68dc80SEd Tanous     }
9082a68dc80SEd Tanous 
9092a68dc80SEd Tanous     nlohmann::json::array_t* arr =
9102a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
9112a68dc80SEd Tanous     if (arr == nullptr)
9122a68dc80SEd Tanous     {
9132a68dc80SEd Tanous         messages::internalError(res);
9142a68dc80SEd Tanous         return;
9152a68dc80SEd Tanous     }
9162a68dc80SEd Tanous 
9173648c8beSEd Tanous     if (query.skip)
9183648c8beSEd Tanous     {
9193648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
9203648c8beSEd Tanous         // $top Can only skip as many values as we have
9213648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
9222a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
9233648c8beSEd Tanous     }
9243648c8beSEd Tanous     if (query.top)
9253648c8beSEd Tanous     {
9263648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
9272a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
9282a68dc80SEd Tanous     }
9293648c8beSEd Tanous }
9302a68dc80SEd Tanous 
931827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are
932827c4902SNan Zhou // not in the |currNode| Trie node.
933827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot,
934827c4902SNan Zhou                             const SelectTrieNode& currNode)
935e155ab54SNan Zhou {
936e155ab54SNan Zhou     nlohmann::json::object_t* object =
937e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::object_t*>();
938e155ab54SNan Zhou     if (object != nullptr)
939e155ab54SNan Zhou     {
94062598e31SEd Tanous         BMCWEB_LOG_DEBUG("Current JSON is an object");
941761cdfa5SEd Tanous         auto it = object->begin();
942761cdfa5SEd Tanous         while (it != object->end())
943e155ab54SNan Zhou         {
944e155ab54SNan Zhou             auto nextIt = std::next(it);
945761cdfa5SEd Tanous             BMCWEB_LOG_DEBUG("key={}", it->first);
946761cdfa5SEd Tanous             const SelectTrieNode* nextNode = currNode.find(it->first);
9475c9fb2d6SNan Zhou             // Per the Redfish spec section 7.3.3, the service shall select
9485c9fb2d6SNan Zhou             // certain properties as if $select was omitted. This applies to
9495c9fb2d6SNan Zhou             // every TrieNode that contains leaves and the root.
9505c9fb2d6SNan Zhou             constexpr std::array<std::string_view, 5> reservedProperties = {
9515c9fb2d6SNan Zhou                 "@odata.id", "@odata.type", "@odata.context", "@odata.etag",
9525c9fb2d6SNan Zhou                 "error"};
953761cdfa5SEd Tanous             bool reserved = std::ranges::find(reservedProperties, it->first) !=
9543544d2a7SEd Tanous                             reservedProperties.end();
9555c9fb2d6SNan Zhou             if (reserved || (nextNode != nullptr && nextNode->isSelected()))
956e155ab54SNan Zhou             {
957e155ab54SNan Zhou                 it = nextIt;
958e155ab54SNan Zhou                 continue;
959e155ab54SNan Zhou             }
960827c4902SNan Zhou             if (nextNode != nullptr)
961e155ab54SNan Zhou             {
962761cdfa5SEd Tanous                 BMCWEB_LOG_DEBUG("Recursively select: {}", it->first);
963761cdfa5SEd Tanous                 recursiveSelect(it->second, *nextNode);
964e155ab54SNan Zhou                 it = nextIt;
965e155ab54SNan Zhou                 continue;
966e155ab54SNan Zhou             }
967761cdfa5SEd Tanous             BMCWEB_LOG_DEBUG("{} is getting removed!", it->first);
968761cdfa5SEd Tanous             it = object->erase(it);
969e155ab54SNan Zhou         }
970e155ab54SNan Zhou     }
9715c9fb2d6SNan Zhou     nlohmann::json::array_t* array =
9725c9fb2d6SNan Zhou         currRoot.get_ptr<nlohmann::json::array_t*>();
9735c9fb2d6SNan Zhou     if (array != nullptr)
9745c9fb2d6SNan Zhou     {
97562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Current JSON is an array");
9765c9fb2d6SNan Zhou         // Array index is omitted, so reuse the same Trie node
9775c9fb2d6SNan Zhou         for (nlohmann::json& nextRoot : *array)
9785c9fb2d6SNan Zhou         {
9795c9fb2d6SNan Zhou             recursiveSelect(nextRoot, currNode);
9805c9fb2d6SNan Zhou         }
9815c9fb2d6SNan Zhou     }
982e155ab54SNan Zhou }
983e155ab54SNan Zhou 
984e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to
985e155ab54SNan Zhou //  ambiguity and/or complexity.
9865c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
987e155ab54SNan Zhou // created for clarification.
9885c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
989e155ab54SNan Zhou // etc.
990e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse,
991827c4902SNan Zhou                           const SelectTrieNode& trieRoot)
992e155ab54SNan Zhou {
99362598e31SEd Tanous     BMCWEB_LOG_DEBUG("Process $select quary parameter");
994827c4902SNan Zhou     recursiveSelect(intermediateResponse.jsonValue, trieRoot);
995e155ab54SNan Zhou }
996e155ab54SNan Zhou 
997504af5a0SPatrick Williams inline void processAllParams(
998504af5a0SPatrick Williams     crow::App& app, const Query& query, const Query& delegated,
9997cf436c9SEd Tanous     std::function<void(crow::Response&)>& completionHandler,
1000*19fab295SChandramohan Harkude     crow::Response& intermediateResponse, const crow::Request& req)
1001f4c99e70SEd Tanous {
1002f4c99e70SEd Tanous     if (!completionHandler)
1003f4c99e70SEd Tanous     {
100462598e31SEd Tanous         BMCWEB_LOG_DEBUG("Function was invalid?");
1005f4c99e70SEd Tanous         return;
1006f4c99e70SEd Tanous     }
1007f4c99e70SEd Tanous 
100862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Processing query params");
1009f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
1010f4c99e70SEd Tanous     // params.
1011f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
1012f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
1013f4c99e70SEd Tanous     {
1014f4c99e70SEd Tanous         completionHandler(intermediateResponse);
1015f4c99e70SEd Tanous         return;
1016f4c99e70SEd Tanous     }
1017f4c99e70SEd Tanous     if (query.isOnly)
1018f4c99e70SEd Tanous     {
1019f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
1020f4c99e70SEd Tanous         return;
1021f4c99e70SEd Tanous     }
10222a68dc80SEd Tanous 
10233648c8beSEd Tanous     if (query.top || query.skip)
10242a68dc80SEd Tanous     {
10252a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
10262a68dc80SEd Tanous     }
10272a68dc80SEd Tanous 
10287cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
10297cf436c9SEd Tanous     {
103062598e31SEd Tanous         BMCWEB_LOG_DEBUG("Executing expand query");
103113548d85SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
103213548d85SEd Tanous             std::move(intermediateResponse));
10337cf436c9SEd Tanous 
103413548d85SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
103513548d85SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
1036*19fab295SChandramohan Harkude         multi->startQuery(query, delegated, req);
10377cf436c9SEd Tanous         return;
10387cf436c9SEd Tanous     }
1039e155ab54SNan Zhou 
104025991f7dSEd Tanous     if (query.filter)
104125991f7dSEd Tanous     {
1042f80a87f2SEd Tanous         applyFilterToCollection(intermediateResponse.jsonValue, *query.filter);
104325991f7dSEd Tanous     }
104425991f7dSEd Tanous 
1045e155ab54SNan Zhou     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
1046e155ab54SNan Zhou     // to process
1047827c4902SNan Zhou     if (!query.selectTrie.root.empty())
1048e155ab54SNan Zhou     {
1049827c4902SNan Zhou         processSelect(intermediateResponse, query.selectTrie.root);
1050e155ab54SNan Zhou     }
1051e155ab54SNan Zhou 
1052f4c99e70SEd Tanous     completionHandler(intermediateResponse);
1053f4c99e70SEd Tanous }
1054f4c99e70SEd Tanous 
1055f4c99e70SEd Tanous } // namespace query_param
1056f4c99e70SEd Tanous } // namespace redfish
1057