xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision e155ab54ec5ad4c31937f4d7de8b502e91468e43)
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 
13*e155ab54SNan Zhou #include <boost/algorithm/string/classification.hpp>
14*e155ab54SNan 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>
18*e155ab54SNan Zhou #include <boost/url/error.hpp>
19d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
20d5c80ad9SNan Zhou #include <boost/url/string.hpp>
21d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
22d5c80ad9SNan Zhou 
23d5c80ad9SNan Zhou #include <algorithm>
24*e155ab54SNan Zhou #include <array>
25*e155ab54SNan Zhou #include <cctype>
267cf436c9SEd Tanous #include <charconv>
27d5c80ad9SNan Zhou #include <cstdint>
28d5c80ad9SNan Zhou #include <functional>
29*e155ab54SNan Zhou #include <iterator>
30d5c80ad9SNan Zhou #include <limits>
31d5c80ad9SNan Zhou #include <map>
32d5c80ad9SNan Zhou #include <memory>
33d5c80ad9SNan Zhou #include <optional>
34*e155ab54SNan Zhou #include <span>
35f4c99e70SEd Tanous #include <string>
36f4c99e70SEd Tanous #include <string_view>
37d5c80ad9SNan Zhou #include <system_error>
38*e155ab54SNan Zhou #include <unordered_set>
397cf436c9SEd Tanous #include <utility>
40f4c99e70SEd Tanous #include <vector>
41f4c99e70SEd Tanous 
42d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
43d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
44d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
45*e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp>
46*e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp>
47*e155ab54SNan Zhou // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp>
48d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h>
49d5c80ad9SNan Zhou 
50f4c99e70SEd Tanous namespace redfish
51f4c99e70SEd Tanous {
52f4c99e70SEd Tanous namespace query_param
53f4c99e70SEd Tanous {
54ce8ea743SJiaqing Zhao inline constexpr size_t maxEntriesPerPage = 1000;
55f4c99e70SEd Tanous 
567cf436c9SEd Tanous enum class ExpandType : uint8_t
577cf436c9SEd Tanous {
587cf436c9SEd Tanous     None,
597cf436c9SEd Tanous     Links,
607cf436c9SEd Tanous     NotLinks,
617cf436c9SEd Tanous     Both,
627cf436c9SEd Tanous };
637cf436c9SEd Tanous 
64a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
65f4c99e70SEd Tanous struct Query
66f4c99e70SEd Tanous {
67a6b9125fSNan Zhou     // Only
68f4c99e70SEd Tanous     bool isOnly = false;
69a6b9125fSNan Zhou     // Expand
70a6b9125fSNan Zhou     uint8_t expandLevel = 0;
717cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
72c937d2bfSEd Tanous 
73c937d2bfSEd Tanous     // Skip
743648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
75c937d2bfSEd Tanous 
76c937d2bfSEd Tanous     // Top
77*e155ab54SNan Zhou 
783648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
79*e155ab54SNan Zhou 
80*e155ab54SNan Zhou     // Select
81*e155ab54SNan Zhou     std::unordered_set<std::string> selectedProperties = {};
82f4c99e70SEd Tanous };
83f4c99e70SEd Tanous 
84a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
85a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
86a6b9125fSNan Zhou // the processing.
87a6b9125fSNan Zhou struct QueryCapabilities
88a6b9125fSNan Zhou {
89a6b9125fSNan Zhou     bool canDelegateOnly = false;
90c937d2bfSEd Tanous     bool canDelegateTop = false;
91c937d2bfSEd Tanous     bool canDelegateSkip = false;
92a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
93*e155ab54SNan Zhou     bool canDelegateSelect = false;
94a6b9125fSNan Zhou };
95a6b9125fSNan Zhou 
96a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
97a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
98a6b9125fSNan Zhou // function will take care of it.
99a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
100a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
101a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
102a6b9125fSNan Zhou {
103a6b9125fSNan Zhou     Query delegated;
104a6b9125fSNan Zhou     // delegate only
105a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
106a6b9125fSNan Zhou     {
107a6b9125fSNan Zhou         delegated.isOnly = true;
108a6b9125fSNan Zhou         query.isOnly = false;
109a6b9125fSNan Zhou     }
110a6b9125fSNan Zhou     // delegate expand as much as we can
111a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
112a6b9125fSNan Zhou     {
113a6b9125fSNan Zhou         delegated.expandType = query.expandType;
114a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
115a6b9125fSNan Zhou         {
116a6b9125fSNan Zhou             query.expandType = ExpandType::None;
117a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
118a6b9125fSNan Zhou             query.expandLevel = 0;
119a6b9125fSNan Zhou         }
120a6b9125fSNan Zhou         else
121a6b9125fSNan Zhou         {
122a6b9125fSNan Zhou             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
123a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
124a6b9125fSNan Zhou         }
125a6b9125fSNan Zhou     }
126c937d2bfSEd Tanous 
127c937d2bfSEd Tanous     // delegate top
1283648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
129c937d2bfSEd Tanous     {
130c937d2bfSEd Tanous         delegated.top = query.top;
1313648c8beSEd Tanous         query.top = std::nullopt;
132c937d2bfSEd Tanous     }
133c937d2bfSEd Tanous 
134c937d2bfSEd Tanous     // delegate skip
1353648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
136c937d2bfSEd Tanous     {
137c937d2bfSEd Tanous         delegated.skip = query.skip;
138c937d2bfSEd Tanous         query.skip = 0;
139c937d2bfSEd Tanous     }
140*e155ab54SNan Zhou 
141*e155ab54SNan Zhou     // delegate select
142*e155ab54SNan Zhou     if (!query.selectedProperties.empty() &&
143*e155ab54SNan Zhou         queryCapabilities.canDelegateSelect)
144*e155ab54SNan Zhou     {
145*e155ab54SNan Zhou         delegated.selectedProperties = std::move(query.selectedProperties);
146*e155ab54SNan Zhou         query.selectedProperties.clear();
147*e155ab54SNan Zhou     }
148a6b9125fSNan Zhou     return delegated;
149a6b9125fSNan Zhou }
150a6b9125fSNan Zhou 
1517cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
1527cf436c9SEd Tanous {
1537cf436c9SEd Tanous     if (value.empty())
1547cf436c9SEd Tanous     {
1557cf436c9SEd Tanous         return false;
1567cf436c9SEd Tanous     }
1577cf436c9SEd Tanous     switch (value[0])
1587cf436c9SEd Tanous     {
1597cf436c9SEd Tanous         case '*':
1607cf436c9SEd Tanous             query.expandType = ExpandType::Both;
1617cf436c9SEd Tanous             break;
1627cf436c9SEd Tanous         case '.':
1637cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
1647cf436c9SEd Tanous             break;
1657cf436c9SEd Tanous         case '~':
1667cf436c9SEd Tanous             query.expandType = ExpandType::Links;
1677cf436c9SEd Tanous             break;
1687cf436c9SEd Tanous         default:
1697cf436c9SEd Tanous             return false;
1707cf436c9SEd Tanous 
1717cf436c9SEd Tanous             break;
1727cf436c9SEd Tanous     }
1737cf436c9SEd Tanous     value.remove_prefix(1);
1747cf436c9SEd Tanous     if (value.empty())
1757cf436c9SEd Tanous     {
1767cf436c9SEd Tanous         query.expandLevel = 1;
1777cf436c9SEd Tanous         return true;
1787cf436c9SEd Tanous     }
1797cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
1807cf436c9SEd Tanous     if (!value.starts_with(levels))
1817cf436c9SEd Tanous     {
1827cf436c9SEd Tanous         return false;
1837cf436c9SEd Tanous     }
1847cf436c9SEd Tanous     value.remove_prefix(levels.size());
1857cf436c9SEd Tanous 
1867cf436c9SEd Tanous     auto it = std::from_chars(value.data(), value.data() + value.size(),
1877cf436c9SEd Tanous                               query.expandLevel);
1887cf436c9SEd Tanous     if (it.ec != std::errc())
1897cf436c9SEd Tanous     {
1907cf436c9SEd Tanous         return false;
1917cf436c9SEd Tanous     }
1927cf436c9SEd Tanous     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
1937cf436c9SEd Tanous     return value == ")";
1947cf436c9SEd Tanous }
1957cf436c9SEd Tanous 
196c937d2bfSEd Tanous enum class QueryError
197c937d2bfSEd Tanous {
198c937d2bfSEd Tanous     Ok,
199c937d2bfSEd Tanous     OutOfRange,
200c937d2bfSEd Tanous     ValueFormat,
201c937d2bfSEd Tanous };
202c937d2bfSEd Tanous 
203c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
204c937d2bfSEd Tanous {
205c937d2bfSEd Tanous     std::from_chars_result r =
206c937d2bfSEd Tanous         std::from_chars(value.data(), value.data() + value.size(), param);
207c937d2bfSEd Tanous 
208c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
209c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
210c937d2bfSEd Tanous     {
211c937d2bfSEd Tanous         return QueryError::OutOfRange;
212c937d2bfSEd Tanous     }
213c937d2bfSEd Tanous     // All other errors are value format
214c937d2bfSEd Tanous     if (r.ec != std::errc())
215c937d2bfSEd Tanous     {
216c937d2bfSEd Tanous         return QueryError::ValueFormat;
217c937d2bfSEd Tanous     }
218c937d2bfSEd Tanous     return QueryError::Ok;
219c937d2bfSEd Tanous }
220c937d2bfSEd Tanous 
221c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
222c937d2bfSEd Tanous {
2233648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
224c937d2bfSEd Tanous }
225c937d2bfSEd Tanous 
226c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
227c937d2bfSEd Tanous {
2283648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
229c937d2bfSEd Tanous     if (ret != QueryError::Ok)
230c937d2bfSEd Tanous     {
231c937d2bfSEd Tanous         return ret;
232c937d2bfSEd Tanous     }
233c937d2bfSEd Tanous 
234c937d2bfSEd Tanous     // Range check for sanity.
235c937d2bfSEd Tanous     if (query.top > maxEntriesPerPage)
236c937d2bfSEd Tanous     {
237c937d2bfSEd Tanous         return QueryError::OutOfRange;
238c937d2bfSEd Tanous     }
239c937d2bfSEd Tanous 
240c937d2bfSEd Tanous     return QueryError::Ok;
241c937d2bfSEd Tanous }
242c937d2bfSEd Tanous 
243*e155ab54SNan Zhou // Validates the property in the $select parameter. Every character is among
244*e155ab54SNan Zhou // [a-zA-Z0-9\/#@_.] (taken from Redfish spec, section 9.6 Properties)
245*e155ab54SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property)
246*e155ab54SNan Zhou {
247*e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
248*e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
249*e155ab54SNan Zhou     constexpr int maxPropertyLength = 60;
250*e155ab54SNan Zhou     if (property.empty() || property.size() > maxPropertyLength)
251*e155ab54SNan Zhou     {
252*e155ab54SNan Zhou         return false;
253*e155ab54SNan Zhou     }
254*e155ab54SNan Zhou     for (char ch : property)
255*e155ab54SNan Zhou     {
256*e155ab54SNan Zhou         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '/' &&
257*e155ab54SNan Zhou             ch != '#' && ch != '@' && ch != '.')
258*e155ab54SNan Zhou         {
259*e155ab54SNan Zhou             return false;
260*e155ab54SNan Zhou         }
261*e155ab54SNan Zhou     }
262*e155ab54SNan Zhou     return true;
263*e155ab54SNan Zhou }
264*e155ab54SNan Zhou 
265*e155ab54SNan Zhou // Parses and validates the $select parameter.
266*e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be
267*e155ab54SNan Zhou // comma separated Resource Path
268*e155ab54SNan Zhou // Ref:
269*e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
270*e155ab54SNan Zhou // 2.
271*e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
272*e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query)
273*e155ab54SNan Zhou {
274*e155ab54SNan Zhou     std::vector<std::string> properties;
275*e155ab54SNan Zhou     boost::split(properties, value, boost::is_any_of(","));
276*e155ab54SNan Zhou     if (properties.empty())
277*e155ab54SNan Zhou     {
278*e155ab54SNan Zhou         return false;
279*e155ab54SNan Zhou     }
280*e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
281*e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
282*e155ab54SNan Zhou     constexpr int maxNumProperties = 10;
283*e155ab54SNan Zhou     if (properties.size() > maxNumProperties)
284*e155ab54SNan Zhou     {
285*e155ab54SNan Zhou         return false;
286*e155ab54SNan Zhou     }
287*e155ab54SNan Zhou     for (std::string& property : properties)
288*e155ab54SNan Zhou     {
289*e155ab54SNan Zhou         if (!isSelectedPropertyAllowed(property))
290*e155ab54SNan Zhou         {
291*e155ab54SNan Zhou             return false;
292*e155ab54SNan Zhou         }
293*e155ab54SNan Zhou         property.insert(property.begin(), '/');
294*e155ab54SNan Zhou     }
295*e155ab54SNan Zhou     query.selectedProperties = {std::make_move_iterator(properties.begin()),
296*e155ab54SNan Zhou                                 std::make_move_iterator(properties.end())};
297*e155ab54SNan Zhou     // Per the Redfish spec section 7.3.3, the service shall select certain
298*e155ab54SNan Zhou     // properties as if $select was omitted.
299*e155ab54SNan Zhou     constexpr std::array<std::string_view, 5> reservedProperties = {
300*e155ab54SNan Zhou         "/@odata.id", "/@odata.type", "/@odata.context", "/@odata.etag",
301*e155ab54SNan Zhou         "/error"};
302*e155ab54SNan Zhou     for (auto const& str : reservedProperties)
303*e155ab54SNan Zhou     {
304*e155ab54SNan Zhou         query.selectedProperties.emplace(std::string(str));
305*e155ab54SNan Zhou     }
306*e155ab54SNan Zhou     return true;
307*e155ab54SNan Zhou }
308*e155ab54SNan Zhou 
309f4c99e70SEd Tanous inline std::optional<Query>
310f4c99e70SEd Tanous     parseParameters(const boost::urls::params_view& urlParams,
311f4c99e70SEd Tanous                     crow::Response& res)
312f4c99e70SEd Tanous {
313f4c99e70SEd Tanous     Query ret;
314f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
315f4c99e70SEd Tanous     {
316f4c99e70SEd Tanous         std::string_view key(it.key.data(), it.key.size());
317f4c99e70SEd Tanous         std::string_view value(it.value.data(), it.value.size());
318f4c99e70SEd Tanous         if (key == "only")
319f4c99e70SEd Tanous         {
320f4c99e70SEd Tanous             if (!it.value.empty())
321f4c99e70SEd Tanous             {
322f4c99e70SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
323f4c99e70SEd Tanous                 return std::nullopt;
324f4c99e70SEd Tanous             }
325f4c99e70SEd Tanous             ret.isOnly = true;
326f4c99e70SEd Tanous         }
3275e52870bSEd Tanous         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
3287cf436c9SEd Tanous         {
3297cf436c9SEd Tanous             if (!getExpandType(value, ret))
3307cf436c9SEd Tanous             {
3317cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
3327cf436c9SEd Tanous                 return std::nullopt;
333f4c99e70SEd Tanous             }
3347cf436c9SEd Tanous         }
335c937d2bfSEd Tanous         else if (key == "$top")
336c937d2bfSEd Tanous         {
337c937d2bfSEd Tanous             QueryError topRet = getTopParam(value, ret);
338c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
339c937d2bfSEd Tanous             {
340c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
341c937d2bfSEd Tanous                 return std::nullopt;
342c937d2bfSEd Tanous             }
343c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
344c937d2bfSEd Tanous             {
345c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
346c937d2bfSEd Tanous                     res, value, "$top",
347a926c53eSJiaqing Zhao                     "0-" + std::to_string(maxEntriesPerPage));
348c937d2bfSEd Tanous                 return std::nullopt;
349c937d2bfSEd Tanous             }
350c937d2bfSEd Tanous         }
351c937d2bfSEd Tanous         else if (key == "$skip")
352c937d2bfSEd Tanous         {
353c937d2bfSEd Tanous             QueryError topRet = getSkipParam(value, ret);
354c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
355c937d2bfSEd Tanous             {
356c937d2bfSEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
357c937d2bfSEd Tanous                 return std::nullopt;
358c937d2bfSEd Tanous             }
359c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
360c937d2bfSEd Tanous             {
361c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
362c937d2bfSEd Tanous                     res, value, key,
363a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
364c937d2bfSEd Tanous                 return std::nullopt;
365c937d2bfSEd Tanous             }
366c937d2bfSEd Tanous         }
367*e155ab54SNan Zhou         else if (key == "$select" && bmcwebInsecureEnableQueryParams)
368*e155ab54SNan Zhou         {
369*e155ab54SNan Zhou             if (!getSelectParam(value, ret))
370*e155ab54SNan Zhou             {
371*e155ab54SNan Zhou                 messages::queryParameterValueFormatError(res, value, key);
372*e155ab54SNan Zhou                 return std::nullopt;
373*e155ab54SNan Zhou             }
374*e155ab54SNan Zhou         }
3757cf436c9SEd Tanous         else
3767cf436c9SEd Tanous         {
3777cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
3787cf436c9SEd Tanous             if (key.starts_with("$"))
3797cf436c9SEd Tanous             {
3807cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
3817cf436c9SEd Tanous                 // status code for any unsupported query parameters that
3827cf436c9SEd Tanous                 // start with $ .
3837cf436c9SEd Tanous                 messages::queryParameterValueFormatError(res, value, key);
3847cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
3857cf436c9SEd Tanous                 return std::nullopt;
3867cf436c9SEd Tanous             }
3877cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
3887cf436c9SEd Tanous             // not begin with $ ."
3897cf436c9SEd Tanous         }
3907cf436c9SEd Tanous     }
3917cf436c9SEd Tanous 
392*e155ab54SNan Zhou     if (ret.expandType != ExpandType::None && !ret.selectedProperties.empty())
393*e155ab54SNan Zhou     {
394*e155ab54SNan Zhou         messages::queryCombinationInvalid(res);
395*e155ab54SNan Zhou         return std::nullopt;
396*e155ab54SNan Zhou     }
397*e155ab54SNan Zhou 
398f4c99e70SEd Tanous     return ret;
399f4c99e70SEd Tanous }
400f4c99e70SEd Tanous 
401f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
402f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
403f4c99e70SEd Tanous {
404f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
405f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
406f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
407f4c99e70SEd Tanous     {
408f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
409f4c99e70SEd Tanous         completionHandler(res);
410f4c99e70SEd Tanous         return false;
411f4c99e70SEd Tanous     }
412f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
413f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
414f4c99e70SEd Tanous     {
415f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
416f4c99e70SEd Tanous                          << " element, returning full collection.";
417f4c99e70SEd Tanous         completionHandler(res);
418f4c99e70SEd Tanous         return false;
419f4c99e70SEd Tanous     }
420f4c99e70SEd Tanous 
421f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
422f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
423f4c99e70SEd Tanous     {
424f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
425f4c99e70SEd Tanous         messages::internalError(res);
426f4c99e70SEd Tanous         completionHandler(res);
427f4c99e70SEd Tanous         return false;
428f4c99e70SEd Tanous     }
429f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
430f4c99e70SEd Tanous     if (url == nullptr)
431f4c99e70SEd Tanous     {
432f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
433f4c99e70SEd Tanous         messages::internalError(res);
434f4c99e70SEd Tanous         completionHandler(res);
435f4c99e70SEd Tanous         return false;
436f4c99e70SEd Tanous     }
437f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
438f4c99e70SEd Tanous     // newReq.session = req.session;
439f4c99e70SEd Tanous     std::error_code ec;
440f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
441f4c99e70SEd Tanous     if (ec)
442f4c99e70SEd Tanous     {
443f4c99e70SEd Tanous         messages::internalError(res);
444f4c99e70SEd Tanous         completionHandler(res);
445f4c99e70SEd Tanous         return false;
446f4c99e70SEd Tanous     }
447f4c99e70SEd Tanous 
448f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
449f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
450f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
451f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
452f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
453f4c99e70SEd Tanous     return true;
454f4c99e70SEd Tanous }
455f4c99e70SEd Tanous 
4567cf436c9SEd Tanous struct ExpandNode
4577cf436c9SEd Tanous {
4587cf436c9SEd Tanous     nlohmann::json::json_pointer location;
4597cf436c9SEd Tanous     std::string uri;
4607cf436c9SEd Tanous 
4617cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
4627cf436c9SEd Tanous     {
4637cf436c9SEd Tanous         return location == other.location && uri == other.uri;
4647cf436c9SEd Tanous     }
4657cf436c9SEd Tanous };
4667cf436c9SEd Tanous 
4677cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
4687cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
4697cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
4707cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
4717cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
4727cf436c9SEd Tanous // with the keys from the jsonResponse object
4737cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
4747cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
4757cf436c9SEd Tanous     const nlohmann::json::json_pointer& p, bool inLinks,
4767cf436c9SEd Tanous     std::vector<ExpandNode>& out)
4777cf436c9SEd Tanous {
4787cf436c9SEd Tanous     // If no expand is needed, return early
4797cf436c9SEd Tanous     if (eType == ExpandType::None)
4807cf436c9SEd Tanous     {
4817cf436c9SEd Tanous         return;
4827cf436c9SEd Tanous     }
4837cf436c9SEd Tanous     nlohmann::json::array_t* array =
4847cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
4857cf436c9SEd Tanous     if (array != nullptr)
4867cf436c9SEd Tanous     {
4877cf436c9SEd Tanous         size_t index = 0;
4887cf436c9SEd Tanous         // For arrays, walk every element in the array
4897cf436c9SEd Tanous         for (auto& element : *array)
4907cf436c9SEd Tanous         {
4917cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
4927cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
4937cf436c9SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
4947cf436c9SEd Tanous                                               out);
4957cf436c9SEd Tanous             index++;
4967cf436c9SEd Tanous         }
4977cf436c9SEd Tanous     }
4987cf436c9SEd Tanous     nlohmann::json::object_t* obj =
4997cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
5007cf436c9SEd Tanous     if (obj == nullptr)
5017cf436c9SEd Tanous     {
5027cf436c9SEd Tanous         return;
5037cf436c9SEd Tanous     }
5047cf436c9SEd Tanous     // Navigation References only ever have a single element
5057cf436c9SEd Tanous     if (obj->size() == 1)
5067cf436c9SEd Tanous     {
5077cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
5087cf436c9SEd Tanous         {
5097cf436c9SEd Tanous             const std::string* uri =
5107cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
5117cf436c9SEd Tanous             if (uri != nullptr)
5127cf436c9SEd Tanous             {
5137cf436c9SEd Tanous                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
5147cf436c9SEd Tanous                 out.push_back({p, *uri});
5157cf436c9SEd Tanous             }
5167cf436c9SEd Tanous         }
5177cf436c9SEd Tanous     }
5187cf436c9SEd Tanous     // Loop the object and look for links
5197cf436c9SEd Tanous     for (auto& element : *obj)
5207cf436c9SEd Tanous     {
521e479ad58SNan Zhou         bool localInLinks = inLinks;
522e479ad58SNan Zhou         if (!localInLinks)
5237cf436c9SEd Tanous         {
5247cf436c9SEd Tanous             // Check if this is a links node
525e479ad58SNan Zhou             localInLinks = element.first == "Links";
5267cf436c9SEd Tanous         }
5277cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
5287cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
529e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
5307cf436c9SEd Tanous         {
5317cf436c9SEd Tanous             continue;
5327cf436c9SEd Tanous         }
533e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
5347cf436c9SEd Tanous         {
5357cf436c9SEd Tanous             continue;
5367cf436c9SEd Tanous         }
5377cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
5387cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
5397cf436c9SEd Tanous 
5407cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
541e479ad58SNan Zhou                                           localInLinks, out);
5427cf436c9SEd Tanous     }
5437cf436c9SEd Tanous }
5447cf436c9SEd Tanous 
5457cf436c9SEd Tanous inline std::vector<ExpandNode>
54672c3ae33SNan Zhou     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
5477cf436c9SEd Tanous {
5487cf436c9SEd Tanous     std::vector<ExpandNode> ret;
54972c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
5507cf436c9SEd Tanous     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
5517cf436c9SEd Tanous     return ret;
5527cf436c9SEd Tanous }
5537cf436c9SEd Tanous 
55472c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
555b66cf2a2SNan Zhou // Returns std::nullopt on failures.
55672c3ae33SNan Zhou // This function shall handle $select when it is added.
55772c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
55872c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
559b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
56072c3ae33SNan Zhou {
56172c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
56272c3ae33SNan Zhou     if (query.expandLevel <= 1)
56372c3ae33SNan Zhou     {
564b66cf2a2SNan Zhou         return "";
56572c3ae33SNan Zhou     }
56672c3ae33SNan Zhou     std::string str = "?$expand=";
567b66cf2a2SNan Zhou     bool queryTypeExpected = false;
56872c3ae33SNan Zhou     switch (query.expandType)
56972c3ae33SNan Zhou     {
57072c3ae33SNan Zhou         case ExpandType::None:
571b66cf2a2SNan Zhou             return "";
57272c3ae33SNan Zhou         case ExpandType::Links:
573b66cf2a2SNan Zhou             queryTypeExpected = true;
57472c3ae33SNan Zhou             str += '~';
57572c3ae33SNan Zhou             break;
57672c3ae33SNan Zhou         case ExpandType::NotLinks:
577b66cf2a2SNan Zhou             queryTypeExpected = true;
57872c3ae33SNan Zhou             str += '.';
57972c3ae33SNan Zhou             break;
58072c3ae33SNan Zhou         case ExpandType::Both:
581b66cf2a2SNan Zhou             queryTypeExpected = true;
58272c3ae33SNan Zhou             str += '*';
58372c3ae33SNan Zhou             break;
584b66cf2a2SNan Zhou     }
585b66cf2a2SNan Zhou     if (!queryTypeExpected)
586b66cf2a2SNan Zhou     {
587b66cf2a2SNan Zhou         return std::nullopt;
58872c3ae33SNan Zhou     }
58972c3ae33SNan Zhou     str += "($levels=";
59072c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
59172c3ae33SNan Zhou     str += ')';
59272c3ae33SNan Zhou     return str;
59372c3ae33SNan Zhou }
59472c3ae33SNan Zhou 
5957cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
5967cf436c9SEd Tanous {
5977cf436c9SEd Tanous   public:
5987cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
5997cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
6007cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
6017cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
6028a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
6037cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
6048a592810SEd Tanous         app(appIn),
6057cf436c9SEd Tanous         finalRes(std::move(finalResIn))
6067cf436c9SEd Tanous     {}
6077cf436c9SEd Tanous 
6087cf436c9SEd Tanous     void addAwaitingResponse(
60902cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
6107cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
6117cf436c9SEd Tanous     {
6127cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
61372c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
6147cf436c9SEd Tanous     }
6157cf436c9SEd Tanous 
61672c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
6177cf436c9SEd Tanous                      crow::Response& res)
6187cf436c9SEd Tanous     {
6197cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
6207cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
6217cf436c9SEd Tanous     }
6227cf436c9SEd Tanous 
62372c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
62472c3ae33SNan Zhou     // for deeper levels.
62572c3ae33SNan Zhou     void startQuery(const Query& query)
62672c3ae33SNan Zhou     {
62772c3ae33SNan Zhou         std::vector<ExpandNode> nodes =
62872c3ae33SNan Zhou             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
6297cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
630b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
631b66cf2a2SNan Zhou         if (!queryStr)
632b66cf2a2SNan Zhou         {
633b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
634b66cf2a2SNan Zhou             return;
635b66cf2a2SNan Zhou         }
6367cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
6377cf436c9SEd Tanous         {
638b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
63972c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
6407cf436c9SEd Tanous             std::error_code ec;
64172c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
6427cf436c9SEd Tanous                                  ec);
6437cf436c9SEd Tanous             if (ec)
6447cf436c9SEd Tanous             {
64572c3ae33SNan Zhou                 messages::internalError(finalRes->res);
6467cf436c9SEd Tanous                 return;
6477cf436c9SEd Tanous             }
6487cf436c9SEd Tanous 
6497cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
6507cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
6517cf436c9SEd Tanous                              << &asyncResp->res;
65272c3ae33SNan Zhou 
65372c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
6547cf436c9SEd Tanous             app.handle(newReq, asyncResp);
6557cf436c9SEd Tanous         }
6567cf436c9SEd Tanous     }
6577cf436c9SEd Tanous 
6587cf436c9SEd Tanous   private:
65972c3ae33SNan Zhou     static void
66072c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
6617cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
6627cf436c9SEd Tanous                           crow::Response& res)
6637cf436c9SEd Tanous     {
66472c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
6657cf436c9SEd Tanous     }
6667cf436c9SEd Tanous 
6677cf436c9SEd Tanous     crow::App& app;
6687cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
6697cf436c9SEd Tanous };
6707cf436c9SEd Tanous 
6712a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
6722a68dc80SEd Tanous {
6733648c8beSEd Tanous     if (!query.skip && !query.top)
6743648c8beSEd Tanous     {
6753648c8beSEd Tanous         // No work to do.
6763648c8beSEd Tanous         return;
6773648c8beSEd Tanous     }
6782a68dc80SEd Tanous     nlohmann::json::object_t* obj =
6792a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
6802a68dc80SEd Tanous     if (obj == nullptr)
6812a68dc80SEd Tanous     {
6822a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
6832a68dc80SEd Tanous         messages::internalError(res);
6842a68dc80SEd Tanous         return;
6852a68dc80SEd Tanous     }
6862a68dc80SEd Tanous 
6872a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
6882a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
6892a68dc80SEd Tanous     if (members == obj->end())
6902a68dc80SEd Tanous     {
6912a68dc80SEd Tanous         // From the Redfish specification 7.3.1
6922a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
6932a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
6942a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
6952a68dc80SEd Tanous         // collections but are used on singular resources.
6962a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
6972a68dc80SEd Tanous         return;
6982a68dc80SEd Tanous     }
6992a68dc80SEd Tanous 
7002a68dc80SEd Tanous     nlohmann::json::array_t* arr =
7012a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
7022a68dc80SEd Tanous     if (arr == nullptr)
7032a68dc80SEd Tanous     {
7042a68dc80SEd Tanous         messages::internalError(res);
7052a68dc80SEd Tanous         return;
7062a68dc80SEd Tanous     }
7072a68dc80SEd Tanous 
7083648c8beSEd Tanous     if (query.skip)
7093648c8beSEd Tanous     {
7103648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
7113648c8beSEd Tanous         // $top Can only skip as many values as we have
7123648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
7132a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
7143648c8beSEd Tanous     }
7153648c8beSEd Tanous     if (query.top)
7163648c8beSEd Tanous     {
7173648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
7182a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
7192a68dc80SEd Tanous     }
7203648c8beSEd Tanous }
7212a68dc80SEd Tanous 
722*e155ab54SNan Zhou // Given a JSON subtree |currRoot|, and its JSON pointer |currRootPtr| to the
723*e155ab54SNan Zhou // |root| JSON in the async response, this function erases leaves whose keys are
724*e155ab54SNan Zhou // not in the |shouldSelect| set.
725*e155ab54SNan Zhou // |shouldSelect| contains all the properties that needs to be selected.
726*e155ab54SNan Zhou inline void recursiveSelect(
727*e155ab54SNan Zhou     nlohmann::json& currRoot, const nlohmann::json::json_pointer& currRootPtr,
728*e155ab54SNan Zhou     const std::unordered_set<std::string>& intermediatePaths,
729*e155ab54SNan Zhou     const std::unordered_set<std::string>& properties, nlohmann::json& root)
730*e155ab54SNan Zhou {
731*e155ab54SNan Zhou     nlohmann::json::object_t* object =
732*e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::object_t*>();
733*e155ab54SNan Zhou     if (object != nullptr)
734*e155ab54SNan Zhou     {
735*e155ab54SNan Zhou         BMCWEB_LOG_DEBUG << "Current JSON is an object: " << currRootPtr;
736*e155ab54SNan Zhou         auto it = currRoot.begin();
737*e155ab54SNan Zhou         while (it != currRoot.end())
738*e155ab54SNan Zhou         {
739*e155ab54SNan Zhou             auto nextIt = std::next(it);
740*e155ab54SNan Zhou             nlohmann::json::json_pointer childPtr = currRootPtr / it.key();
741*e155ab54SNan Zhou             BMCWEB_LOG_DEBUG << "childPtr=" << childPtr;
742*e155ab54SNan Zhou             if (properties.contains(childPtr))
743*e155ab54SNan Zhou             {
744*e155ab54SNan Zhou                 it = nextIt;
745*e155ab54SNan Zhou                 continue;
746*e155ab54SNan Zhou             }
747*e155ab54SNan Zhou             if (intermediatePaths.contains(childPtr))
748*e155ab54SNan Zhou             {
749*e155ab54SNan Zhou                 BMCWEB_LOG_DEBUG << "Recursively select: " << childPtr;
750*e155ab54SNan Zhou                 recursiveSelect(*it, childPtr, intermediatePaths, properties,
751*e155ab54SNan Zhou                                 root);
752*e155ab54SNan Zhou                 it = nextIt;
753*e155ab54SNan Zhou                 continue;
754*e155ab54SNan Zhou             }
755*e155ab54SNan Zhou             BMCWEB_LOG_DEBUG << childPtr << " is getting removed!";
756*e155ab54SNan Zhou             it = currRoot.erase(it);
757*e155ab54SNan Zhou         }
758*e155ab54SNan Zhou         return;
759*e155ab54SNan Zhou     }
760*e155ab54SNan Zhou     nlohmann::json::array_t* array =
761*e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::array_t*>();
762*e155ab54SNan Zhou     if (array != nullptr)
763*e155ab54SNan Zhou     {
764*e155ab54SNan Zhou         BMCWEB_LOG_DEBUG << "Current JSON is an array: " << currRootPtr;
765*e155ab54SNan Zhou         if (properties.contains(currRootPtr))
766*e155ab54SNan Zhou         {
767*e155ab54SNan Zhou             return;
768*e155ab54SNan Zhou         }
769*e155ab54SNan Zhou         root[currRootPtr.parent_pointer()].erase(currRootPtr.back());
770*e155ab54SNan Zhou         BMCWEB_LOG_DEBUG << currRootPtr << " is getting removed!";
771*e155ab54SNan Zhou         return;
772*e155ab54SNan Zhou     }
773*e155ab54SNan Zhou     BMCWEB_LOG_DEBUG << "Current JSON is a property value: " << currRootPtr;
774*e155ab54SNan Zhou }
775*e155ab54SNan Zhou 
776*e155ab54SNan Zhou inline std::unordered_set<std::string>
777*e155ab54SNan Zhou     getIntermediatePaths(const std::unordered_set<std::string>& properties)
778*e155ab54SNan Zhou {
779*e155ab54SNan Zhou     std::unordered_set<std::string> res;
780*e155ab54SNan Zhou     std::vector<std::string> segments;
781*e155ab54SNan Zhou 
782*e155ab54SNan Zhou     for (auto const& property : properties)
783*e155ab54SNan Zhou     {
784*e155ab54SNan Zhou         // Omit the root "/" and split all other segments
785*e155ab54SNan Zhou         boost::split(segments, property.substr(1), boost::is_any_of("/"));
786*e155ab54SNan Zhou         std::string path;
787*e155ab54SNan Zhou         if (!segments.empty())
788*e155ab54SNan Zhou         {
789*e155ab54SNan Zhou             segments.pop_back();
790*e155ab54SNan Zhou         }
791*e155ab54SNan Zhou         for (auto const& segment : segments)
792*e155ab54SNan Zhou         {
793*e155ab54SNan Zhou             path += '/';
794*e155ab54SNan Zhou             path += segment;
795*e155ab54SNan Zhou             res.insert(path);
796*e155ab54SNan Zhou         }
797*e155ab54SNan Zhou     }
798*e155ab54SNan Zhou     return res;
799*e155ab54SNan Zhou }
800*e155ab54SNan Zhou 
801*e155ab54SNan Zhou inline void performSelect(nlohmann::json& root,
802*e155ab54SNan Zhou                           const std::unordered_set<std::string>& properties)
803*e155ab54SNan Zhou {
804*e155ab54SNan Zhou     std::unordered_set<std::string> intermediatePaths =
805*e155ab54SNan Zhou         getIntermediatePaths(properties);
806*e155ab54SNan Zhou     recursiveSelect(root, nlohmann::json::json_pointer(""), intermediatePaths,
807*e155ab54SNan Zhou                     properties, root);
808*e155ab54SNan Zhou }
809*e155ab54SNan Zhou 
810*e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to
811*e155ab54SNan Zhou //  ambiguity and/or complexity.
812*e155ab54SNan Zhou // 1. select properties in array of objects;
813*e155ab54SNan Zhou // https://github.com/DMTF/Redfish/issues/5188 was created for clarification.
814*e155ab54SNan Zhou // 2. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
815*e155ab54SNan Zhou // created for clarification.
816*e155ab54SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
817*e155ab54SNan Zhou // etc.
818*e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse,
819*e155ab54SNan Zhou                           const std::unordered_set<std::string>& shouldSelect)
820*e155ab54SNan Zhou {
821*e155ab54SNan Zhou     BMCWEB_LOG_DEBUG << "Process $select quary parameter";
822*e155ab54SNan Zhou     performSelect(intermediateResponse.jsonValue, shouldSelect);
823*e155ab54SNan Zhou }
824*e155ab54SNan Zhou 
8257cf436c9SEd Tanous inline void
826593f6449SNan Zhou     processAllParams(crow::App& app, const Query& query,
8277cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
8287cf436c9SEd Tanous                      crow::Response& intermediateResponse)
829f4c99e70SEd Tanous {
830f4c99e70SEd Tanous     if (!completionHandler)
831f4c99e70SEd Tanous     {
832f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
833f4c99e70SEd Tanous         return;
834f4c99e70SEd Tanous     }
835f4c99e70SEd Tanous 
836f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
837f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
838f4c99e70SEd Tanous     // params.
839f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
840f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
841f4c99e70SEd Tanous     {
842f4c99e70SEd Tanous         completionHandler(intermediateResponse);
843f4c99e70SEd Tanous         return;
844f4c99e70SEd Tanous     }
845f4c99e70SEd Tanous     if (query.isOnly)
846f4c99e70SEd Tanous     {
847f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
848f4c99e70SEd Tanous         return;
849f4c99e70SEd Tanous     }
8502a68dc80SEd Tanous 
8513648c8beSEd Tanous     if (query.top || query.skip)
8522a68dc80SEd Tanous     {
8532a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
8542a68dc80SEd Tanous     }
8552a68dc80SEd Tanous 
8567cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
8577cf436c9SEd Tanous     {
8587cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
8597cf436c9SEd Tanous         // TODO(ed) this is a copy of the response object.  Admittedly,
8607cf436c9SEd Tanous         // we're inherently doing something inefficient, but we shouldn't
8617cf436c9SEd Tanous         // have to do a full copy
8627cf436c9SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
8637cf436c9SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
8647cf436c9SEd Tanous         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
8657cf436c9SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
8667cf436c9SEd Tanous 
86772c3ae33SNan Zhou         multi->startQuery(query);
8687cf436c9SEd Tanous         return;
8697cf436c9SEd Tanous     }
870*e155ab54SNan Zhou 
871*e155ab54SNan Zhou     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
872*e155ab54SNan Zhou     // to process
873*e155ab54SNan Zhou     if (!query.selectedProperties.empty())
874*e155ab54SNan Zhou     {
875*e155ab54SNan Zhou         processSelect(intermediateResponse, query.selectedProperties);
876*e155ab54SNan Zhou     }
877*e155ab54SNan Zhou 
878f4c99e70SEd Tanous     completionHandler(intermediateResponse);
879f4c99e70SEd Tanous }
880f4c99e70SEd Tanous 
881f4c99e70SEd Tanous } // namespace query_param
882f4c99e70SEd Tanous } // namespace redfish
883