xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 25991f7dd01e7b36b6691a2c8a37bda5c933ba84)
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"
7*25991f7dSEd Tanous #include "filter_expr_executor.hpp"
8*25991f7dSEd Tanous #include "filter_expr_printer.hpp"
9f4c99e70SEd Tanous #include "http_request.hpp"
1002cad96eSEd Tanous #include "http_response.hpp"
1195c6307aSEd Tanous #include "json_formatters.hpp"
12d5c80ad9SNan Zhou #include "logging.hpp"
1350ebd4afSEd Tanous #include "str_utility.hpp"
14f4c99e70SEd Tanous 
15d5c80ad9SNan Zhou #include <sys/types.h>
16d5c80ad9SNan Zhou 
17d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep
18d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp>
19d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp>
20d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
21d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
22d5c80ad9SNan Zhou 
23d5c80ad9SNan Zhou #include <algorithm>
24e155ab54SNan Zhou #include <array>
25e155ab54SNan Zhou #include <cctype>
267cf436c9SEd Tanous #include <charconv>
27827c4902SNan Zhou #include <compare>
28d5c80ad9SNan Zhou #include <cstdint>
29d5c80ad9SNan Zhou #include <functional>
30e155ab54SNan Zhou #include <iterator>
31d5c80ad9SNan Zhou #include <limits>
32d5c80ad9SNan Zhou #include <map>
33d5c80ad9SNan Zhou #include <memory>
34d5c80ad9SNan Zhou #include <optional>
353544d2a7SEd Tanous #include <ranges>
36f4c99e70SEd Tanous #include <string>
37f4c99e70SEd Tanous #include <string_view>
38d5c80ad9SNan Zhou #include <system_error>
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>
45e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp>
46e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp>
47e155ab54SNan 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 {
54f4c99e70SEd Tanous 
557cf436c9SEd Tanous enum class ExpandType : uint8_t
567cf436c9SEd Tanous {
577cf436c9SEd Tanous     None,
587cf436c9SEd Tanous     Links,
597cf436c9SEd Tanous     NotLinks,
607cf436c9SEd Tanous     Both,
617cf436c9SEd Tanous };
627cf436c9SEd Tanous 
63827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|.
64827c4902SNan Zhou class SelectTrieNode
65827c4902SNan Zhou {
66827c4902SNan Zhou   public:
67827c4902SNan Zhou     SelectTrieNode() = default;
68827c4902SNan Zhou 
69827c4902SNan Zhou     const SelectTrieNode* find(const std::string& jsonKey) const
70827c4902SNan Zhou     {
71827c4902SNan Zhou         auto it = children.find(jsonKey);
72827c4902SNan Zhou         if (it == children.end())
73827c4902SNan Zhou         {
74827c4902SNan Zhou             return nullptr;
75827c4902SNan Zhou         }
76827c4902SNan Zhou         return &it->second;
77827c4902SNan Zhou     }
78827c4902SNan Zhou 
79827c4902SNan Zhou     // Creates a new node if the key doesn't exist, returns the reference to the
80827c4902SNan Zhou     // newly created node; otherwise, return the reference to the existing node
81827c4902SNan Zhou     SelectTrieNode* emplace(std::string_view jsonKey)
82827c4902SNan Zhou     {
83827c4902SNan Zhou         auto [it, _] = children.emplace(jsonKey, SelectTrieNode{});
84827c4902SNan Zhou         return &it->second;
85827c4902SNan Zhou     }
86827c4902SNan Zhou 
87827c4902SNan Zhou     bool empty() const
88827c4902SNan Zhou     {
89827c4902SNan Zhou         return children.empty();
90827c4902SNan Zhou     }
91827c4902SNan Zhou 
92827c4902SNan Zhou     void clear()
93827c4902SNan Zhou     {
94827c4902SNan Zhou         children.clear();
95827c4902SNan Zhou     }
96827c4902SNan Zhou 
97827c4902SNan Zhou     void setToSelected()
98827c4902SNan Zhou     {
99827c4902SNan Zhou         selected = true;
100827c4902SNan Zhou     }
101827c4902SNan Zhou 
102827c4902SNan Zhou     bool isSelected() const
103827c4902SNan Zhou     {
104827c4902SNan Zhou         return selected;
105827c4902SNan Zhou     }
106827c4902SNan Zhou 
107827c4902SNan Zhou   private:
108827c4902SNan Zhou     std::map<std::string, SelectTrieNode, std::less<>> children;
109827c4902SNan Zhou     bool selected = false;
110827c4902SNan Zhou };
111827c4902SNan Zhou 
112827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among
113827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties)
114827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property)
115827c4902SNan Zhou {
116827c4902SNan Zhou     // These a magic number, but with it it's less likely that this code
117827c4902SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
118827c4902SNan Zhou     constexpr int maxPropertyLength = 60;
119827c4902SNan Zhou     if (property.empty() || property.size() > maxPropertyLength)
120827c4902SNan Zhou     {
121827c4902SNan Zhou         return false;
122827c4902SNan Zhou     }
123827c4902SNan Zhou     for (char ch : property)
124827c4902SNan Zhou     {
125827c4902SNan Zhou         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' &&
126827c4902SNan Zhou             ch != '@' && ch != '.')
127827c4902SNan Zhou         {
128827c4902SNan Zhou             return false;
129827c4902SNan Zhou         }
130827c4902SNan Zhou     }
131827c4902SNan Zhou     return true;
132827c4902SNan Zhou }
133827c4902SNan Zhou 
134827c4902SNan Zhou struct SelectTrie
135827c4902SNan Zhou {
136827c4902SNan Zhou     SelectTrie() = default;
137827c4902SNan Zhou 
138827c4902SNan Zhou     // Inserts a $select value; returns false if the nestedProperty is illegal.
139827c4902SNan Zhou     bool insertNode(std::string_view nestedProperty)
140827c4902SNan Zhou     {
141827c4902SNan Zhou         if (nestedProperty.empty())
142827c4902SNan Zhou         {
143827c4902SNan Zhou             return false;
144827c4902SNan Zhou         }
145827c4902SNan Zhou         SelectTrieNode* currNode = &root;
146827c4902SNan Zhou         size_t index = nestedProperty.find_first_of('/');
147827c4902SNan Zhou         while (!nestedProperty.empty())
148827c4902SNan Zhou         {
149827c4902SNan Zhou             std::string_view property = nestedProperty.substr(0, index);
150827c4902SNan Zhou             if (!isSelectedPropertyAllowed(property))
151827c4902SNan Zhou             {
152827c4902SNan Zhou                 return false;
153827c4902SNan Zhou             }
154827c4902SNan Zhou             currNode = currNode->emplace(property);
155827c4902SNan Zhou             if (index == std::string::npos)
156827c4902SNan Zhou             {
157827c4902SNan Zhou                 break;
158827c4902SNan Zhou             }
159827c4902SNan Zhou             nestedProperty.remove_prefix(index + 1);
160827c4902SNan Zhou             index = nestedProperty.find_first_of('/');
161827c4902SNan Zhou         }
162827c4902SNan Zhou         currNode->setToSelected();
163827c4902SNan Zhou         return true;
164827c4902SNan Zhou     }
165827c4902SNan Zhou 
166827c4902SNan Zhou     SelectTrieNode root;
167827c4902SNan Zhou };
168827c4902SNan Zhou 
169a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
170f4c99e70SEd Tanous struct Query
171f4c99e70SEd Tanous {
172a6b9125fSNan Zhou     // Only
173f4c99e70SEd Tanous     bool isOnly = false;
174*25991f7dSEd Tanous 
175a6b9125fSNan Zhou     // Expand
176a6b9125fSNan Zhou     uint8_t expandLevel = 0;
1777cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
178c937d2bfSEd Tanous 
179c937d2bfSEd Tanous     // Skip
1803648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
181c937d2bfSEd Tanous 
182c937d2bfSEd Tanous     // Top
1835143f7a5SJiaqing Zhao     static constexpr size_t maxTop = 1000; // Max entries a response contain
1843648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
185e155ab54SNan Zhou 
186*25991f7dSEd Tanous     // Filter
187*25991f7dSEd Tanous     std::optional<filter_ast::LogicalAnd> filter = std::nullopt;
188*25991f7dSEd Tanous 
189e155ab54SNan Zhou     // Select
19047f2934cSEd Tanous     // Unclear how to make this use structured initialization without this.
19147f2934cSEd Tanous     // Might be a tidy bug?  Ignore for now
19247f2934cSEd Tanous     // NOLINTNEXTLINE(readability-redundant-member-init)
19347f2934cSEd Tanous     SelectTrie selectTrie{};
194f4c99e70SEd Tanous };
195f4c99e70SEd Tanous 
196a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
197a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
198a6b9125fSNan Zhou // the processing.
199a6b9125fSNan Zhou struct QueryCapabilities
200a6b9125fSNan Zhou {
201a6b9125fSNan Zhou     bool canDelegateOnly = false;
202c937d2bfSEd Tanous     bool canDelegateTop = false;
203c937d2bfSEd Tanous     bool canDelegateSkip = false;
204a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
205e155ab54SNan Zhou     bool canDelegateSelect = false;
206a6b9125fSNan Zhou };
207a6b9125fSNan Zhou 
208a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
209a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
210a6b9125fSNan Zhou // function will take care of it.
211a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
212a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
213a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
214a6b9125fSNan Zhou {
215f1a1e3dcSEd Tanous     Query delegated{};
216a6b9125fSNan Zhou     // delegate only
217a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
218a6b9125fSNan Zhou     {
219a6b9125fSNan Zhou         delegated.isOnly = true;
220a6b9125fSNan Zhou         query.isOnly = false;
221a6b9125fSNan Zhou     }
222a6b9125fSNan Zhou     // delegate expand as much as we can
223a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
224a6b9125fSNan Zhou     {
225a6b9125fSNan Zhou         delegated.expandType = query.expandType;
226a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
227a6b9125fSNan Zhou         {
228a6b9125fSNan Zhou             query.expandType = ExpandType::None;
229a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
230a6b9125fSNan Zhou             query.expandLevel = 0;
231a6b9125fSNan Zhou         }
232a6b9125fSNan Zhou         else
233a6b9125fSNan Zhou         {
234a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
235a6b9125fSNan Zhou         }
236a6b9125fSNan Zhou     }
237c937d2bfSEd Tanous 
238c937d2bfSEd Tanous     // delegate top
2393648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
240c937d2bfSEd Tanous     {
241c937d2bfSEd Tanous         delegated.top = query.top;
2423648c8beSEd Tanous         query.top = std::nullopt;
243c937d2bfSEd Tanous     }
244c937d2bfSEd Tanous 
245c937d2bfSEd Tanous     // delegate skip
2463648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
247c937d2bfSEd Tanous     {
248c937d2bfSEd Tanous         delegated.skip = query.skip;
249c937d2bfSEd Tanous         query.skip = 0;
250c937d2bfSEd Tanous     }
251e155ab54SNan Zhou 
252e155ab54SNan Zhou     // delegate select
253827c4902SNan Zhou     if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect)
254e155ab54SNan Zhou     {
255827c4902SNan Zhou         delegated.selectTrie = std::move(query.selectTrie);
256827c4902SNan Zhou         query.selectTrie.root.clear();
257e155ab54SNan Zhou     }
258a6b9125fSNan Zhou     return delegated;
259a6b9125fSNan Zhou }
260a6b9125fSNan Zhou 
2617cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
2627cf436c9SEd Tanous {
2637cf436c9SEd Tanous     if (value.empty())
2647cf436c9SEd Tanous     {
2657cf436c9SEd Tanous         return false;
2667cf436c9SEd Tanous     }
2677cf436c9SEd Tanous     switch (value[0])
2687cf436c9SEd Tanous     {
2697cf436c9SEd Tanous         case '*':
2707cf436c9SEd Tanous             query.expandType = ExpandType::Both;
2717cf436c9SEd Tanous             break;
2727cf436c9SEd Tanous         case '.':
2737cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
2747cf436c9SEd Tanous             break;
2757cf436c9SEd Tanous         case '~':
2767cf436c9SEd Tanous             query.expandType = ExpandType::Links;
2777cf436c9SEd Tanous             break;
2787cf436c9SEd Tanous         default:
2797cf436c9SEd Tanous             return false;
2807cf436c9SEd Tanous     }
2817cf436c9SEd Tanous     value.remove_prefix(1);
2827cf436c9SEd Tanous     if (value.empty())
2837cf436c9SEd Tanous     {
2847cf436c9SEd Tanous         query.expandLevel = 1;
2857cf436c9SEd Tanous         return true;
2867cf436c9SEd Tanous     }
2877cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
2887cf436c9SEd Tanous     if (!value.starts_with(levels))
2897cf436c9SEd Tanous     {
2907cf436c9SEd Tanous         return false;
2917cf436c9SEd Tanous     }
2927cf436c9SEd Tanous     value.remove_prefix(levels.size());
2937cf436c9SEd Tanous 
2942bd4ab43SPatrick Williams     auto it = std::from_chars(value.begin(), value.end(), query.expandLevel);
2957cf436c9SEd Tanous     if (it.ec != std::errc())
2967cf436c9SEd Tanous     {
2977cf436c9SEd Tanous         return false;
2987cf436c9SEd Tanous     }
2992bd4ab43SPatrick Williams     value.remove_prefix(
3002bd4ab43SPatrick Williams         static_cast<size_t>(std::distance(value.begin(), it.ptr)));
3017cf436c9SEd Tanous     return value == ")";
3027cf436c9SEd Tanous }
3037cf436c9SEd Tanous 
304c937d2bfSEd Tanous enum class QueryError
305c937d2bfSEd Tanous {
306c937d2bfSEd Tanous     Ok,
307c937d2bfSEd Tanous     OutOfRange,
308c937d2bfSEd Tanous     ValueFormat,
309c937d2bfSEd Tanous };
310c937d2bfSEd Tanous 
311c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
312c937d2bfSEd Tanous {
3132bd4ab43SPatrick Williams     std::from_chars_result r = std::from_chars(value.begin(), value.end(),
3142bd4ab43SPatrick Williams                                                param);
315c937d2bfSEd Tanous 
316c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
317c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
318c937d2bfSEd Tanous     {
319c937d2bfSEd Tanous         return QueryError::OutOfRange;
320c937d2bfSEd Tanous     }
321c937d2bfSEd Tanous     // All other errors are value format
322c937d2bfSEd Tanous     if (r.ec != std::errc())
323c937d2bfSEd Tanous     {
324c937d2bfSEd Tanous         return QueryError::ValueFormat;
325c937d2bfSEd Tanous     }
326c937d2bfSEd Tanous     return QueryError::Ok;
327c937d2bfSEd Tanous }
328c937d2bfSEd Tanous 
329c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
330c937d2bfSEd Tanous {
3313648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
332c937d2bfSEd Tanous }
333c937d2bfSEd Tanous 
334c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
335c937d2bfSEd Tanous {
3363648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
337c937d2bfSEd Tanous     if (ret != QueryError::Ok)
338c937d2bfSEd Tanous     {
339c937d2bfSEd Tanous         return ret;
340c937d2bfSEd Tanous     }
341c937d2bfSEd Tanous 
342c937d2bfSEd Tanous     // Range check for sanity.
3435143f7a5SJiaqing Zhao     if (query.top > Query::maxTop)
344c937d2bfSEd Tanous     {
345c937d2bfSEd Tanous         return QueryError::OutOfRange;
346c937d2bfSEd Tanous     }
347c937d2bfSEd Tanous 
348c937d2bfSEd Tanous     return QueryError::Ok;
349c937d2bfSEd Tanous }
350c937d2bfSEd Tanous 
351e155ab54SNan Zhou // Parses and validates the $select parameter.
352e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be
353e155ab54SNan Zhou // comma separated Resource Path
354e155ab54SNan Zhou // Ref:
355e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
356e155ab54SNan Zhou // 2.
357e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
358e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query)
359e155ab54SNan Zhou {
360e155ab54SNan Zhou     std::vector<std::string> properties;
36150ebd4afSEd Tanous     bmcweb::split(properties, value, ',');
362e155ab54SNan Zhou     if (properties.empty())
363e155ab54SNan Zhou     {
364e155ab54SNan Zhou         return false;
365e155ab54SNan Zhou     }
366e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
367e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
368e155ab54SNan Zhou     constexpr int maxNumProperties = 10;
369e155ab54SNan Zhou     if (properties.size() > maxNumProperties)
370e155ab54SNan Zhou     {
371e155ab54SNan Zhou         return false;
372e155ab54SNan Zhou     }
373827c4902SNan Zhou     for (const auto& property : properties)
374e155ab54SNan Zhou     {
375827c4902SNan Zhou         if (!query.selectTrie.insertNode(property))
376e155ab54SNan Zhou         {
377e155ab54SNan Zhou             return false;
378e155ab54SNan Zhou         }
379e155ab54SNan Zhou     }
380e155ab54SNan Zhou     return true;
381e155ab54SNan Zhou }
382e155ab54SNan Zhou 
383*25991f7dSEd Tanous // Parses and validates the $filter parameter.
384*25991f7dSEd Tanous inline bool getFilterParam(std::string_view value, Query& query)
385*25991f7dSEd Tanous {
386*25991f7dSEd Tanous     query.filter = parseFilter(value);
387*25991f7dSEd Tanous     return query.filter.has_value();
388*25991f7dSEd Tanous }
389*25991f7dSEd Tanous 
390079360aeSEd Tanous inline std::optional<Query> parseParameters(boost::urls::params_view urlParams,
391f4c99e70SEd Tanous                                             crow::Response& res)
392f4c99e70SEd Tanous {
393f1a1e3dcSEd Tanous     Query ret{};
394f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
395f4c99e70SEd Tanous     {
396079360aeSEd Tanous         if (it.key == "only")
397f4c99e70SEd Tanous         {
398f4c99e70SEd Tanous             if (!it.value.empty())
399f4c99e70SEd Tanous             {
400079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
401f4c99e70SEd Tanous                 return std::nullopt;
402f4c99e70SEd Tanous             }
403f4c99e70SEd Tanous             ret.isOnly = true;
404f4c99e70SEd Tanous         }
40525b54dbaSEd Tanous         else if (it.key == "$expand" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY)
4067cf436c9SEd Tanous         {
407079360aeSEd Tanous             if (!getExpandType(it.value, ret))
4087cf436c9SEd Tanous             {
409079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
4107cf436c9SEd Tanous                 return std::nullopt;
411f4c99e70SEd Tanous             }
4127cf436c9SEd Tanous         }
413079360aeSEd Tanous         else if (it.key == "$top")
414c937d2bfSEd Tanous         {
415079360aeSEd Tanous             QueryError topRet = getTopParam(it.value, ret);
416c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
417c937d2bfSEd Tanous             {
418079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
419c937d2bfSEd Tanous                 return std::nullopt;
420c937d2bfSEd Tanous             }
421c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
422c937d2bfSEd Tanous             {
423c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
424079360aeSEd Tanous                     res, it.value, "$top",
425079360aeSEd Tanous                     "0-" + std::to_string(Query::maxTop));
426c937d2bfSEd Tanous                 return std::nullopt;
427c937d2bfSEd Tanous             }
428c937d2bfSEd Tanous         }
429079360aeSEd Tanous         else if (it.key == "$skip")
430c937d2bfSEd Tanous         {
431079360aeSEd Tanous             QueryError topRet = getSkipParam(it.value, ret);
432c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
433c937d2bfSEd Tanous             {
434079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
435c937d2bfSEd Tanous                 return std::nullopt;
436c937d2bfSEd Tanous             }
437c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
438c937d2bfSEd Tanous             {
439c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
440079360aeSEd Tanous                     res, it.value, it.key,
441a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
442c937d2bfSEd Tanous                 return std::nullopt;
443c937d2bfSEd Tanous             }
444c937d2bfSEd Tanous         }
445079360aeSEd Tanous         else if (it.key == "$select")
446e155ab54SNan Zhou         {
447079360aeSEd Tanous             if (!getSelectParam(it.value, ret))
448e155ab54SNan Zhou             {
449079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
450e155ab54SNan Zhou                 return std::nullopt;
451e155ab54SNan Zhou             }
452e155ab54SNan Zhou         }
453*25991f7dSEd Tanous         else if (it.key == "$filter" && BMCWEB_INSECURE_ENABLE_REDFISH_QUERY)
454*25991f7dSEd Tanous         {
455*25991f7dSEd Tanous             if (!getFilterParam(it.value, ret))
456*25991f7dSEd Tanous             {
457*25991f7dSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
458*25991f7dSEd Tanous                 return std::nullopt;
459*25991f7dSEd Tanous             }
460*25991f7dSEd Tanous         }
4617cf436c9SEd Tanous         else
4627cf436c9SEd Tanous         {
4637cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
464079360aeSEd Tanous             if (it.key.starts_with("$"))
4657cf436c9SEd Tanous             {
4667cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
4677cf436c9SEd Tanous                 // status code for any unsupported query parameters that
4687cf436c9SEd Tanous                 // start with $ .
469079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
4707cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
4717cf436c9SEd Tanous                 return std::nullopt;
4727cf436c9SEd Tanous             }
4737cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
4747cf436c9SEd Tanous             // not begin with $ ."
4757cf436c9SEd Tanous         }
4767cf436c9SEd Tanous     }
4777cf436c9SEd Tanous 
478827c4902SNan Zhou     if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty())
479e155ab54SNan Zhou     {
480e155ab54SNan Zhou         messages::queryCombinationInvalid(res);
481e155ab54SNan Zhou         return std::nullopt;
482e155ab54SNan Zhou     }
483e155ab54SNan Zhou 
484f4c99e70SEd Tanous     return ret;
485f4c99e70SEd Tanous }
486f4c99e70SEd Tanous 
487f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
488f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
489f4c99e70SEd Tanous {
49062598e31SEd Tanous     BMCWEB_LOG_DEBUG("Processing only query param");
491f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
492f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
493f4c99e70SEd Tanous     {
494f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
495f4c99e70SEd Tanous         completionHandler(res);
496f4c99e70SEd Tanous         return false;
497f4c99e70SEd Tanous     }
498f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
499f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
500f4c99e70SEd Tanous     {
50162598e31SEd Tanous         BMCWEB_LOG_DEBUG(
50262598e31SEd Tanous             "Members contains {} element, returning full collection.",
50362598e31SEd Tanous             itMembers->size());
504f4c99e70SEd Tanous         completionHandler(res);
505f4c99e70SEd Tanous         return false;
506f4c99e70SEd Tanous     }
507f4c99e70SEd Tanous 
508f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
509f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
510f4c99e70SEd Tanous     {
51162598e31SEd Tanous         BMCWEB_LOG_DEBUG("No found odata.id");
512f4c99e70SEd Tanous         messages::internalError(res);
513f4c99e70SEd Tanous         completionHandler(res);
514f4c99e70SEd Tanous         return false;
515f4c99e70SEd Tanous     }
516f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
517f4c99e70SEd Tanous     if (url == nullptr)
518f4c99e70SEd Tanous     {
51962598e31SEd Tanous         BMCWEB_LOG_DEBUG("@odata.id wasn't a string????");
520f4c99e70SEd Tanous         messages::internalError(res);
521f4c99e70SEd Tanous         completionHandler(res);
522f4c99e70SEd Tanous         return false;
523f4c99e70SEd Tanous     }
524f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
525f4c99e70SEd Tanous     // newReq.session = req.session;
526f4c99e70SEd Tanous     std::error_code ec;
527102a4cdaSJonathan Doman     auto newReq = std::make_shared<crow::Request>(
528102a4cdaSJonathan Doman         crow::Request::Body{boost::beast::http::verb::get, *url, 11}, ec);
529f4c99e70SEd Tanous     if (ec)
530f4c99e70SEd Tanous     {
531f4c99e70SEd Tanous         messages::internalError(res);
532f4c99e70SEd Tanous         completionHandler(res);
533f4c99e70SEd Tanous         return false;
534f4c99e70SEd Tanous     }
535f4c99e70SEd Tanous 
536f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
53762598e31SEd Tanous     BMCWEB_LOG_DEBUG("setting completion handler on {}",
53862598e31SEd Tanous                      logPtr(&asyncResp->res));
539f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
540f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
541f4c99e70SEd Tanous     return true;
542f4c99e70SEd Tanous }
543f4c99e70SEd Tanous 
5447cf436c9SEd Tanous struct ExpandNode
5457cf436c9SEd Tanous {
5467cf436c9SEd Tanous     nlohmann::json::json_pointer location;
5477cf436c9SEd Tanous     std::string uri;
5487cf436c9SEd Tanous 
5499de65b34SEd Tanous     bool operator==(const ExpandNode& other) const
5507cf436c9SEd Tanous     {
5517cf436c9SEd Tanous         return location == other.location && uri == other.uri;
5527cf436c9SEd Tanous     }
5537cf436c9SEd Tanous };
5547cf436c9SEd Tanous 
55587788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive(
556c59e338cSEd Tanous     ExpandType eType, nlohmann::json::array_t& array,
55737b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
55887788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out);
55987788abfSEd Tanous 
56087788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive(
561c59e338cSEd Tanous     ExpandType eType, nlohmann::json::object_t& obj,
56237b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
56387788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out);
56487788abfSEd Tanous 
5657cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
5667cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
5677cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
5687cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
5697cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
5707cf436c9SEd Tanous // with the keys from the jsonResponse object
5717cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
5727cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
57337b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
57432cdb4a7SWilly Tu     bool inLinks, std::vector<ExpandNode>& out)
5757cf436c9SEd Tanous {
5767cf436c9SEd Tanous     // If no expand is needed, return early
5777cf436c9SEd Tanous     if (eType == ExpandType::None)
5787cf436c9SEd Tanous     {
5797cf436c9SEd Tanous         return;
5807cf436c9SEd Tanous     }
581ad595fa6SEd Tanous 
5827cf436c9SEd Tanous     nlohmann::json::array_t* array =
5837cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
5847cf436c9SEd Tanous     if (array != nullptr)
5857cf436c9SEd Tanous     {
58637b1f7beSEd Tanous         findNavigationReferencesInArrayRecursive(eType, *array, jsonPtr, depth,
58787788abfSEd Tanous                                                  skipDepth, inLinks, out);
58887788abfSEd Tanous     }
58987788abfSEd Tanous     nlohmann::json::object_t* obj =
59087788abfSEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
59187788abfSEd Tanous     if (obj == nullptr)
59287788abfSEd Tanous     {
59387788abfSEd Tanous         return;
59487788abfSEd Tanous     }
59537b1f7beSEd Tanous     findNavigationReferencesInObjectRecursive(eType, *obj, jsonPtr, depth,
59637b1f7beSEd Tanous                                               skipDepth, inLinks, out);
59787788abfSEd Tanous }
59887788abfSEd Tanous 
59987788abfSEd Tanous inline void findNavigationReferencesInArrayRecursive(
600c59e338cSEd Tanous     ExpandType eType, nlohmann::json::array_t& array,
60137b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
60287788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out)
60387788abfSEd Tanous {
6047cf436c9SEd Tanous     size_t index = 0;
6057cf436c9SEd Tanous     // For arrays, walk every element in the array
606c59e338cSEd Tanous     for (auto& element : array)
6077cf436c9SEd Tanous     {
60837b1f7beSEd Tanous         nlohmann::json::json_pointer newPtr = jsonPtr / index;
60962598e31SEd Tanous         BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr.to_string());
610ad595fa6SEd Tanous         findNavigationReferencesRecursive(eType, element, newPtr, depth,
61132cdb4a7SWilly Tu                                           skipDepth, inLinks, out);
6127cf436c9SEd Tanous         index++;
6137cf436c9SEd Tanous     }
6147cf436c9SEd Tanous }
61587788abfSEd Tanous 
61687788abfSEd Tanous inline void findNavigationReferencesInObjectRecursive(
617c59e338cSEd Tanous     ExpandType eType, nlohmann::json::object_t& obj,
61837b1f7beSEd Tanous     const nlohmann::json::json_pointer& jsonPtr, int depth, int skipDepth,
61987788abfSEd Tanous     bool inLinks, std::vector<ExpandNode>& out)
6207cf436c9SEd Tanous {
6217cf436c9SEd Tanous     // Navigation References only ever have a single element
622c59e338cSEd Tanous     if (obj.size() == 1)
6237cf436c9SEd Tanous     {
624c59e338cSEd Tanous         if (obj.begin()->first == "@odata.id")
6257cf436c9SEd Tanous         {
6267cf436c9SEd Tanous             const std::string* uri =
627c59e338cSEd Tanous                 obj.begin()->second.get_ptr<const std::string*>();
6287cf436c9SEd Tanous             if (uri != nullptr)
6297cf436c9SEd Tanous             {
63062598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Found {} at {}", *uri, jsonPtr.to_string());
63132cdb4a7SWilly Tu                 if (skipDepth == 0)
63232cdb4a7SWilly Tu                 {
63337b1f7beSEd Tanous                     out.push_back({jsonPtr, *uri});
63432cdb4a7SWilly Tu                 }
635ad595fa6SEd Tanous                 return;
6367cf436c9SEd Tanous             }
6377cf436c9SEd Tanous         }
6387cf436c9SEd Tanous     }
639ad595fa6SEd Tanous 
640ad595fa6SEd Tanous     int newDepth = depth;
641c59e338cSEd Tanous     auto odataId = obj.find("@odata.id");
642c59e338cSEd Tanous     if (odataId != obj.end())
643ad595fa6SEd Tanous     {
644ad595fa6SEd Tanous         // The Redfish spec requires all resources to include the resource
645ad595fa6SEd Tanous         // identifier.  If the object has multiple elements and one of them is
646ad595fa6SEd Tanous         // "@odata.id" then that means we have entered a new level / expanded
647ad595fa6SEd Tanous         // resource.  We need to stop traversing if we're already at the desired
648ad595fa6SEd Tanous         // depth
649c59e338cSEd Tanous         if (obj.size() > 1)
65032cdb4a7SWilly Tu         {
65132cdb4a7SWilly Tu             if (depth == 0)
652ad595fa6SEd Tanous             {
653ad595fa6SEd Tanous                 return;
654ad595fa6SEd Tanous             }
65532cdb4a7SWilly Tu             if (skipDepth > 0)
65632cdb4a7SWilly Tu             {
65732cdb4a7SWilly Tu                 skipDepth--;
65832cdb4a7SWilly Tu             }
65932cdb4a7SWilly Tu         }
66032cdb4a7SWilly Tu 
66132cdb4a7SWilly Tu         if (skipDepth == 0)
66232cdb4a7SWilly Tu         {
663ad595fa6SEd Tanous             newDepth--;
664ad595fa6SEd Tanous         }
66532cdb4a7SWilly Tu     }
666ad595fa6SEd Tanous 
6677cf436c9SEd Tanous     // Loop the object and look for links
668c59e338cSEd Tanous     for (auto& element : obj)
6697cf436c9SEd Tanous     {
670e479ad58SNan Zhou         bool localInLinks = inLinks;
671e479ad58SNan Zhou         if (!localInLinks)
6727cf436c9SEd Tanous         {
6737cf436c9SEd Tanous             // Check if this is a links node
674e479ad58SNan Zhou             localInLinks = element.first == "Links";
6757cf436c9SEd Tanous         }
6767cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
6777cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
678e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
6797cf436c9SEd Tanous         {
6807cf436c9SEd Tanous             continue;
6817cf436c9SEd Tanous         }
682e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
6837cf436c9SEd Tanous         {
6847cf436c9SEd Tanous             continue;
6857cf436c9SEd Tanous         }
68637b1f7beSEd Tanous         nlohmann::json::json_pointer newPtr = jsonPtr / element.first;
68762598e31SEd Tanous         BMCWEB_LOG_DEBUG("Traversing response at {}", newPtr);
6887cf436c9SEd Tanous 
6897cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
69032cdb4a7SWilly Tu                                           newDepth, skipDepth, localInLinks,
69132cdb4a7SWilly Tu                                           out);
6927cf436c9SEd Tanous     }
6937cf436c9SEd Tanous }
6947cf436c9SEd Tanous 
695ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded
696ad595fa6SEd Tanous // response we may need need additional handling when the original URI was
697ad595fa6SEd Tanous // up tree from a top level collection.
698ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556
699ad595fa6SEd Tanous // lands.  May want to avoid forwarding query params when request is uptree from
700ad595fa6SEd Tanous // a top level collection.
7017cf436c9SEd Tanous inline std::vector<ExpandNode>
70232cdb4a7SWilly Tu     findNavigationReferences(ExpandType eType, int depth, int skipDepth,
703ad595fa6SEd Tanous                              nlohmann::json& jsonResponse)
7047cf436c9SEd Tanous {
7057cf436c9SEd Tanous     std::vector<ExpandNode> ret;
70672c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
70732cdb4a7SWilly Tu     // SkipDepth +1 since we are skipping the root by default.
70832cdb4a7SWilly Tu     findNavigationReferencesRecursive(eType, jsonResponse, root, depth,
70932cdb4a7SWilly Tu                                       skipDepth + 1, false, ret);
7107cf436c9SEd Tanous     return ret;
7117cf436c9SEd Tanous }
7127cf436c9SEd Tanous 
71372c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
714b66cf2a2SNan Zhou // Returns std::nullopt on failures.
71572c3ae33SNan Zhou // This function shall handle $select when it is added.
7168ece0e45SEd Tanous // There is no need to handle parameters that's not compatible with $expand,
71772c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
718b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
71972c3ae33SNan Zhou {
72072c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
72172c3ae33SNan Zhou     if (query.expandLevel <= 1)
72272c3ae33SNan Zhou     {
723b66cf2a2SNan Zhou         return "";
72472c3ae33SNan Zhou     }
72572c3ae33SNan Zhou     std::string str = "?$expand=";
72672c3ae33SNan Zhou     switch (query.expandType)
72772c3ae33SNan Zhou     {
72872c3ae33SNan Zhou         case ExpandType::Links:
72972c3ae33SNan Zhou             str += '~';
73072c3ae33SNan Zhou             break;
73172c3ae33SNan Zhou         case ExpandType::NotLinks:
73272c3ae33SNan Zhou             str += '.';
73372c3ae33SNan Zhou             break;
73472c3ae33SNan Zhou         case ExpandType::Both:
73572c3ae33SNan Zhou             str += '*';
73672c3ae33SNan Zhou             break;
737f1a1e3dcSEd Tanous         case ExpandType::None:
738f1a1e3dcSEd Tanous             return "";
7394da0490bSEd Tanous         default:
7404da0490bSEd Tanous             return std::nullopt;
741b66cf2a2SNan Zhou     }
74272c3ae33SNan Zhou     str += "($levels=";
74372c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
74472c3ae33SNan Zhou     str += ')';
74572c3ae33SNan Zhou     return str;
74672c3ae33SNan Zhou }
74772c3ae33SNan Zhou 
7488ece0e45SEd Tanous // Propagates the worst error code to the final response.
7493590bd1dSNan Zhou // The order of error code is (from high to low)
7503590bd1dSNan Zhou // 500 Internal Server Error
7513590bd1dSNan Zhou // 511 Network Authentication Required
7523590bd1dSNan Zhou // 510 Not Extended
7533590bd1dSNan Zhou // 508 Loop Detected
7543590bd1dSNan Zhou // 507 Insufficient Storage
7553590bd1dSNan Zhou // 506 Variant Also Negotiates
7563590bd1dSNan Zhou // 505 HTTP Version Not Supported
7573590bd1dSNan Zhou // 504 Gateway Timeout
7583590bd1dSNan Zhou // 503 Service Unavailable
7593590bd1dSNan Zhou // 502 Bad Gateway
7603590bd1dSNan Zhou // 501 Not Implemented
7613590bd1dSNan Zhou // 401 Unauthorized
7628ece0e45SEd Tanous // 451 - 409 Error codes (not listed explicitly)
7633590bd1dSNan Zhou // 408 Request Timeout
7643590bd1dSNan Zhou // 407 Proxy Authentication Required
7653590bd1dSNan Zhou // 406 Not Acceptable
7663590bd1dSNan Zhou // 405 Method Not Allowed
7673590bd1dSNan Zhou // 404 Not Found
7683590bd1dSNan Zhou // 403 Forbidden
7693590bd1dSNan Zhou // 402 Payment Required
7703590bd1dSNan Zhou // 400 Bad Request
7713590bd1dSNan Zhou inline unsigned propogateErrorCode(unsigned finalCode, unsigned subResponseCode)
7723590bd1dSNan Zhou {
7733590bd1dSNan Zhou     // We keep a explicit list for error codes that this project often uses
7748ece0e45SEd Tanous     // Higher priority codes are in lower indexes
7753590bd1dSNan Zhou     constexpr std::array<unsigned, 13> orderedCodes = {
7763590bd1dSNan Zhou         500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400};
7773590bd1dSNan Zhou     size_t finalCodeIndex = std::numeric_limits<size_t>::max();
7783590bd1dSNan Zhou     size_t subResponseCodeIndex = std::numeric_limits<size_t>::max();
7793590bd1dSNan Zhou     for (size_t i = 0; i < orderedCodes.size(); ++i)
7803590bd1dSNan Zhou     {
7813590bd1dSNan Zhou         if (orderedCodes[i] == finalCode)
7823590bd1dSNan Zhou         {
7833590bd1dSNan Zhou             finalCodeIndex = i;
7843590bd1dSNan Zhou         }
7853590bd1dSNan Zhou         if (orderedCodes[i] == subResponseCode)
7863590bd1dSNan Zhou         {
7873590bd1dSNan Zhou             subResponseCodeIndex = i;
7883590bd1dSNan Zhou         }
7893590bd1dSNan Zhou     }
7903590bd1dSNan Zhou     if (finalCodeIndex != std::numeric_limits<size_t>::max() &&
7913590bd1dSNan Zhou         subResponseCodeIndex != std::numeric_limits<size_t>::max())
7923590bd1dSNan Zhou     {
7933590bd1dSNan Zhou         return finalCodeIndex <= subResponseCodeIndex ? finalCode
7943590bd1dSNan Zhou                                                       : subResponseCode;
7953590bd1dSNan Zhou     }
7963590bd1dSNan Zhou     if (subResponseCode == 500 || finalCode == 500)
7973590bd1dSNan Zhou     {
7983590bd1dSNan Zhou         return 500;
7993590bd1dSNan Zhou     }
8003590bd1dSNan Zhou     if (subResponseCode > 500 || finalCode > 500)
8013590bd1dSNan Zhou     {
8023590bd1dSNan Zhou         return std::max(finalCode, subResponseCode);
8033590bd1dSNan Zhou     }
8043590bd1dSNan Zhou     if (subResponseCode == 401)
8053590bd1dSNan Zhou     {
8063590bd1dSNan Zhou         return subResponseCode;
8073590bd1dSNan Zhou     }
8083590bd1dSNan Zhou     return std::max(finalCode, subResponseCode);
8093590bd1dSNan Zhou }
8103590bd1dSNan Zhou 
8118ece0e45SEd Tanous // Propagates all error messages into |finalResponse|
8123590bd1dSNan Zhou inline void propogateError(crow::Response& finalResponse,
8133590bd1dSNan Zhou                            crow::Response& subResponse)
8143590bd1dSNan Zhou {
8153590bd1dSNan Zhou     // no errors
8163590bd1dSNan Zhou     if (subResponse.resultInt() >= 200 && subResponse.resultInt() < 400)
8173590bd1dSNan Zhou     {
8183590bd1dSNan Zhou         return;
8193590bd1dSNan Zhou     }
8203590bd1dSNan Zhou     messages::moveErrorsToErrorJson(finalResponse.jsonValue,
8213590bd1dSNan Zhou                                     subResponse.jsonValue);
8223590bd1dSNan Zhou     finalResponse.result(
8233590bd1dSNan Zhou         propogateErrorCode(finalResponse.resultInt(), subResponse.resultInt()));
8243590bd1dSNan Zhou }
8253590bd1dSNan Zhou 
8267cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
8277cf436c9SEd Tanous {
8287cf436c9SEd Tanous   public:
8297cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
8307cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
8317cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
8327cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
8338a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
8347cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
8358a592810SEd Tanous         app(appIn),
8367cf436c9SEd Tanous         finalRes(std::move(finalResIn))
8377cf436c9SEd Tanous     {}
8387cf436c9SEd Tanous 
8397cf436c9SEd Tanous     void addAwaitingResponse(
84002cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
8417cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
8427cf436c9SEd Tanous     {
8437cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
84472c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
8457cf436c9SEd Tanous     }
8467cf436c9SEd Tanous 
84772c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
8487cf436c9SEd Tanous                      crow::Response& res)
8497cf436c9SEd Tanous     {
85062598e31SEd Tanous         BMCWEB_LOG_DEBUG("placeResult for {}", locationToPlace);
8513590bd1dSNan Zhou         propogateError(finalRes->res, res);
8523590bd1dSNan Zhou         if (!res.jsonValue.is_object() || res.jsonValue.empty())
8533590bd1dSNan Zhou         {
8543590bd1dSNan Zhou             return;
8553590bd1dSNan Zhou         }
8567cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
8577cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
8587cf436c9SEd Tanous     }
8597cf436c9SEd Tanous 
86072c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
86172c3ae33SNan Zhou     // for deeper levels.
86232cdb4a7SWilly Tu     void startQuery(const Query& query, const Query& delegated)
86372c3ae33SNan Zhou     {
864ad595fa6SEd Tanous         std::vector<ExpandNode> nodes = findNavigationReferences(
86532cdb4a7SWilly Tu             query.expandType, query.expandLevel, delegated.expandLevel,
86632cdb4a7SWilly Tu             finalRes->res.jsonValue);
86762598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} nodes to traverse", nodes.size());
868b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
869b66cf2a2SNan Zhou         if (!queryStr)
870b66cf2a2SNan Zhou         {
871b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
872b66cf2a2SNan Zhou             return;
873b66cf2a2SNan Zhou         }
8747cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
8757cf436c9SEd Tanous         {
876b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
87762598e31SEd Tanous             BMCWEB_LOG_DEBUG("URL of subquery:  {}", subQuery);
8787cf436c9SEd Tanous             std::error_code ec;
879102a4cdaSJonathan Doman             auto newReq = std::make_shared<crow::Request>(
880102a4cdaSJonathan Doman                 crow::Request::Body{boost::beast::http::verb::get, subQuery,
881102a4cdaSJonathan Doman                                     11},
8827cf436c9SEd Tanous                 ec);
8837cf436c9SEd Tanous             if (ec)
8847cf436c9SEd Tanous             {
88572c3ae33SNan Zhou                 messages::internalError(finalRes->res);
8867cf436c9SEd Tanous                 return;
8877cf436c9SEd Tanous             }
8887cf436c9SEd Tanous 
8897cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
89062598e31SEd Tanous             BMCWEB_LOG_DEBUG("setting completion handler on {}",
89162598e31SEd Tanous                              logPtr(&asyncResp->res));
89272c3ae33SNan Zhou 
89372c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
8947cf436c9SEd Tanous             app.handle(newReq, asyncResp);
8957cf436c9SEd Tanous         }
8967cf436c9SEd Tanous     }
8977cf436c9SEd Tanous 
8987cf436c9SEd Tanous   private:
89972c3ae33SNan Zhou     static void
90072c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
9017cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
9027cf436c9SEd Tanous                           crow::Response& res)
9037cf436c9SEd Tanous     {
90472c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
9057cf436c9SEd Tanous     }
9067cf436c9SEd Tanous 
9077cf436c9SEd Tanous     crow::App& app;
9087cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
9097cf436c9SEd Tanous };
9107cf436c9SEd Tanous 
9112a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
9122a68dc80SEd Tanous {
9133648c8beSEd Tanous     if (!query.skip && !query.top)
9143648c8beSEd Tanous     {
9153648c8beSEd Tanous         // No work to do.
9163648c8beSEd Tanous         return;
9173648c8beSEd Tanous     }
9182a68dc80SEd Tanous     nlohmann::json::object_t* obj =
9192a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
9202a68dc80SEd Tanous     if (obj == nullptr)
9212a68dc80SEd Tanous     {
9222a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
9232a68dc80SEd Tanous         messages::internalError(res);
9242a68dc80SEd Tanous         return;
9252a68dc80SEd Tanous     }
9262a68dc80SEd Tanous 
92762598e31SEd Tanous     BMCWEB_LOG_DEBUG("Handling top/skip");
9282a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
9292a68dc80SEd Tanous     if (members == obj->end())
9302a68dc80SEd Tanous     {
9312a68dc80SEd Tanous         // From the Redfish specification 7.3.1
9322a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
9332a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
9342a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
9352a68dc80SEd Tanous         // collections but are used on singular resources.
9362a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
9372a68dc80SEd Tanous         return;
9382a68dc80SEd Tanous     }
9392a68dc80SEd Tanous 
9402a68dc80SEd Tanous     nlohmann::json::array_t* arr =
9412a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
9422a68dc80SEd Tanous     if (arr == nullptr)
9432a68dc80SEd Tanous     {
9442a68dc80SEd Tanous         messages::internalError(res);
9452a68dc80SEd Tanous         return;
9462a68dc80SEd Tanous     }
9472a68dc80SEd Tanous 
9483648c8beSEd Tanous     if (query.skip)
9493648c8beSEd Tanous     {
9503648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
9513648c8beSEd Tanous         // $top Can only skip as many values as we have
9523648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
9532a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
9543648c8beSEd Tanous     }
9553648c8beSEd Tanous     if (query.top)
9563648c8beSEd Tanous     {
9573648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
9582a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
9592a68dc80SEd Tanous     }
9603648c8beSEd Tanous }
9612a68dc80SEd Tanous 
962827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are
963827c4902SNan Zhou // not in the |currNode| Trie node.
964827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot,
965827c4902SNan Zhou                             const SelectTrieNode& currNode)
966e155ab54SNan Zhou {
967e155ab54SNan Zhou     nlohmann::json::object_t* object =
968e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::object_t*>();
969e155ab54SNan Zhou     if (object != nullptr)
970e155ab54SNan Zhou     {
97162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Current JSON is an object");
972e155ab54SNan Zhou         auto it = currRoot.begin();
973e155ab54SNan Zhou         while (it != currRoot.end())
974e155ab54SNan Zhou         {
975e155ab54SNan Zhou             auto nextIt = std::next(it);
97662598e31SEd Tanous             BMCWEB_LOG_DEBUG("key={}", it.key());
977827c4902SNan Zhou             const SelectTrieNode* nextNode = currNode.find(it.key());
9785c9fb2d6SNan Zhou             // Per the Redfish spec section 7.3.3, the service shall select
9795c9fb2d6SNan Zhou             // certain properties as if $select was omitted. This applies to
9805c9fb2d6SNan Zhou             // every TrieNode that contains leaves and the root.
9815c9fb2d6SNan Zhou             constexpr std::array<std::string_view, 5> reservedProperties = {
9825c9fb2d6SNan Zhou                 "@odata.id", "@odata.type", "@odata.context", "@odata.etag",
9835c9fb2d6SNan Zhou                 "error"};
9843544d2a7SEd Tanous             bool reserved = std::ranges::find(reservedProperties, it.key()) !=
9853544d2a7SEd Tanous                             reservedProperties.end();
9865c9fb2d6SNan Zhou             if (reserved || (nextNode != nullptr && nextNode->isSelected()))
987e155ab54SNan Zhou             {
988e155ab54SNan Zhou                 it = nextIt;
989e155ab54SNan Zhou                 continue;
990e155ab54SNan Zhou             }
991827c4902SNan Zhou             if (nextNode != nullptr)
992e155ab54SNan Zhou             {
99362598e31SEd Tanous                 BMCWEB_LOG_DEBUG("Recursively select: {}", it.key());
994827c4902SNan Zhou                 recursiveSelect(*it, *nextNode);
995e155ab54SNan Zhou                 it = nextIt;
996e155ab54SNan Zhou                 continue;
997e155ab54SNan Zhou             }
99862598e31SEd Tanous             BMCWEB_LOG_DEBUG("{} is getting removed!", it.key());
999e155ab54SNan Zhou             it = currRoot.erase(it);
1000e155ab54SNan Zhou         }
1001e155ab54SNan Zhou     }
10025c9fb2d6SNan Zhou     nlohmann::json::array_t* array =
10035c9fb2d6SNan Zhou         currRoot.get_ptr<nlohmann::json::array_t*>();
10045c9fb2d6SNan Zhou     if (array != nullptr)
10055c9fb2d6SNan Zhou     {
100662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Current JSON is an array");
10075c9fb2d6SNan Zhou         // Array index is omitted, so reuse the same Trie node
10085c9fb2d6SNan Zhou         for (nlohmann::json& nextRoot : *array)
10095c9fb2d6SNan Zhou         {
10105c9fb2d6SNan Zhou             recursiveSelect(nextRoot, currNode);
10115c9fb2d6SNan Zhou         }
10125c9fb2d6SNan Zhou     }
1013e155ab54SNan Zhou }
1014e155ab54SNan Zhou 
1015e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to
1016e155ab54SNan Zhou //  ambiguity and/or complexity.
10175c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
1018e155ab54SNan Zhou // created for clarification.
10195c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
1020e155ab54SNan Zhou // etc.
1021e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse,
1022827c4902SNan Zhou                           const SelectTrieNode& trieRoot)
1023e155ab54SNan Zhou {
102462598e31SEd Tanous     BMCWEB_LOG_DEBUG("Process $select quary parameter");
1025827c4902SNan Zhou     recursiveSelect(intermediateResponse.jsonValue, trieRoot);
1026e155ab54SNan Zhou }
1027e155ab54SNan Zhou 
10287cf436c9SEd Tanous inline void
102932cdb4a7SWilly Tu     processAllParams(crow::App& app, const Query& query, const Query& delegated,
10307cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
10317cf436c9SEd Tanous                      crow::Response& intermediateResponse)
1032f4c99e70SEd Tanous {
1033f4c99e70SEd Tanous     if (!completionHandler)
1034f4c99e70SEd Tanous     {
103562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Function was invalid?");
1036f4c99e70SEd Tanous         return;
1037f4c99e70SEd Tanous     }
1038f4c99e70SEd Tanous 
103962598e31SEd Tanous     BMCWEB_LOG_DEBUG("Processing query params");
1040f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
1041f4c99e70SEd Tanous     // params.
1042f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
1043f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
1044f4c99e70SEd Tanous     {
1045f4c99e70SEd Tanous         completionHandler(intermediateResponse);
1046f4c99e70SEd Tanous         return;
1047f4c99e70SEd Tanous     }
1048f4c99e70SEd Tanous     if (query.isOnly)
1049f4c99e70SEd Tanous     {
1050f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
1051f4c99e70SEd Tanous         return;
1052f4c99e70SEd Tanous     }
10532a68dc80SEd Tanous 
10543648c8beSEd Tanous     if (query.top || query.skip)
10552a68dc80SEd Tanous     {
10562a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
10572a68dc80SEd Tanous     }
10582a68dc80SEd Tanous 
10597cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
10607cf436c9SEd Tanous     {
106162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Executing expand query");
106213548d85SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
106313548d85SEd Tanous             std::move(intermediateResponse));
10647cf436c9SEd Tanous 
106513548d85SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
106613548d85SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
106732cdb4a7SWilly Tu         multi->startQuery(query, delegated);
10687cf436c9SEd Tanous         return;
10697cf436c9SEd Tanous     }
1070e155ab54SNan Zhou 
1071*25991f7dSEd Tanous     if (query.filter)
1072*25991f7dSEd Tanous     {
1073*25991f7dSEd Tanous         applyFilter(intermediateResponse.jsonValue, *query.filter);
1074*25991f7dSEd Tanous     }
1075*25991f7dSEd Tanous 
1076e155ab54SNan Zhou     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
1077e155ab54SNan Zhou     // to process
1078827c4902SNan Zhou     if (!query.selectTrie.root.empty())
1079e155ab54SNan Zhou     {
1080827c4902SNan Zhou         processSelect(intermediateResponse, query.selectTrie.root);
1081e155ab54SNan Zhou     }
1082e155ab54SNan Zhou 
1083f4c99e70SEd Tanous     completionHandler(intermediateResponse);
1084f4c99e70SEd Tanous }
1085f4c99e70SEd Tanous 
1086f4c99e70SEd Tanous } // namespace query_param
1087f4c99e70SEd Tanous } // namespace redfish
1088