xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 32cdb4a78399fec17442dc2cd36b2e57382475a3)
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"
1050ebd4afSEd Tanous #include "str_utility.hpp"
11f4c99e70SEd Tanous 
12d5c80ad9SNan Zhou #include <sys/types.h>
13d5c80ad9SNan Zhou 
14e155ab54SNan Zhou #include <boost/algorithm/string/classification.hpp>
15d5c80ad9SNan Zhou #include <boost/beast/http/message.hpp> // IWYU pragma: keep
16d5c80ad9SNan Zhou #include <boost/beast/http/status.hpp>
17d5c80ad9SNan Zhou #include <boost/beast/http/verb.hpp>
18d5c80ad9SNan Zhou #include <boost/url/params_view.hpp>
19d5c80ad9SNan Zhou #include <nlohmann/json.hpp>
20d5c80ad9SNan Zhou 
21d5c80ad9SNan Zhou #include <algorithm>
22e155ab54SNan Zhou #include <array>
23e155ab54SNan Zhou #include <cctype>
247cf436c9SEd Tanous #include <charconv>
25827c4902SNan Zhou #include <compare>
26d5c80ad9SNan Zhou #include <cstdint>
27d5c80ad9SNan Zhou #include <functional>
28e155ab54SNan Zhou #include <iterator>
29d5c80ad9SNan Zhou #include <limits>
30d5c80ad9SNan Zhou #include <map>
31d5c80ad9SNan Zhou #include <memory>
32d5c80ad9SNan Zhou #include <optional>
33f4c99e70SEd Tanous #include <string>
34f4c99e70SEd Tanous #include <string_view>
35d5c80ad9SNan Zhou #include <system_error>
367cf436c9SEd Tanous #include <utility>
37f4c99e70SEd Tanous #include <vector>
38f4c99e70SEd Tanous 
39d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
40d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
41d5c80ad9SNan Zhou // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
42e155ab54SNan Zhou // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp>
43e155ab54SNan Zhou // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp>
44e155ab54SNan Zhou // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp>
45d5c80ad9SNan Zhou // IWYU pragma: no_include <stdint.h>
46d5c80ad9SNan Zhou 
47f4c99e70SEd Tanous namespace redfish
48f4c99e70SEd Tanous {
49f4c99e70SEd Tanous namespace query_param
50f4c99e70SEd Tanous {
51f4c99e70SEd Tanous 
527cf436c9SEd Tanous enum class ExpandType : uint8_t
537cf436c9SEd Tanous {
547cf436c9SEd Tanous     None,
557cf436c9SEd Tanous     Links,
567cf436c9SEd Tanous     NotLinks,
577cf436c9SEd Tanous     Both,
587cf436c9SEd Tanous };
597cf436c9SEd Tanous 
60827c4902SNan Zhou // A simple implementation of Trie to help |recursiveSelect|.
61827c4902SNan Zhou class SelectTrieNode
62827c4902SNan Zhou {
63827c4902SNan Zhou   public:
64827c4902SNan Zhou     SelectTrieNode() = default;
65827c4902SNan Zhou 
66827c4902SNan Zhou     const SelectTrieNode* find(const std::string& jsonKey) const
67827c4902SNan Zhou     {
68827c4902SNan Zhou         auto it = children.find(jsonKey);
69827c4902SNan Zhou         if (it == children.end())
70827c4902SNan Zhou         {
71827c4902SNan Zhou             return nullptr;
72827c4902SNan Zhou         }
73827c4902SNan Zhou         return &it->second;
74827c4902SNan Zhou     }
75827c4902SNan Zhou 
76827c4902SNan Zhou     // Creates a new node if the key doesn't exist, returns the reference to the
77827c4902SNan Zhou     // newly created node; otherwise, return the reference to the existing node
78827c4902SNan Zhou     SelectTrieNode* emplace(std::string_view jsonKey)
79827c4902SNan Zhou     {
80827c4902SNan Zhou         auto [it, _] = children.emplace(jsonKey, SelectTrieNode{});
81827c4902SNan Zhou         return &it->second;
82827c4902SNan Zhou     }
83827c4902SNan Zhou 
84827c4902SNan Zhou     bool empty() const
85827c4902SNan Zhou     {
86827c4902SNan Zhou         return children.empty();
87827c4902SNan Zhou     }
88827c4902SNan Zhou 
89827c4902SNan Zhou     void clear()
90827c4902SNan Zhou     {
91827c4902SNan Zhou         children.clear();
92827c4902SNan Zhou     }
93827c4902SNan Zhou 
94827c4902SNan Zhou     void setToSelected()
95827c4902SNan Zhou     {
96827c4902SNan Zhou         selected = true;
97827c4902SNan Zhou     }
98827c4902SNan Zhou 
99827c4902SNan Zhou     bool isSelected() const
100827c4902SNan Zhou     {
101827c4902SNan Zhou         return selected;
102827c4902SNan Zhou     }
103827c4902SNan Zhou 
104827c4902SNan Zhou   private:
105827c4902SNan Zhou     std::map<std::string, SelectTrieNode, std::less<>> children;
106827c4902SNan Zhou     bool selected = false;
107827c4902SNan Zhou };
108827c4902SNan Zhou 
109827c4902SNan Zhou // Validates the property in the $select parameter. Every character is among
110827c4902SNan Zhou // [a-zA-Z0-9#@_.] (taken from Redfish spec, section 9.6 Properties)
111827c4902SNan Zhou inline bool isSelectedPropertyAllowed(std::string_view property)
112827c4902SNan Zhou {
113827c4902SNan Zhou     // These a magic number, but with it it's less likely that this code
114827c4902SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
115827c4902SNan Zhou     constexpr int maxPropertyLength = 60;
116827c4902SNan Zhou     if (property.empty() || property.size() > maxPropertyLength)
117827c4902SNan Zhou     {
118827c4902SNan Zhou         return false;
119827c4902SNan Zhou     }
120827c4902SNan Zhou     for (char ch : property)
121827c4902SNan Zhou     {
122827c4902SNan Zhou         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '#' &&
123827c4902SNan Zhou             ch != '@' && ch != '.')
124827c4902SNan Zhou         {
125827c4902SNan Zhou             return false;
126827c4902SNan Zhou         }
127827c4902SNan Zhou     }
128827c4902SNan Zhou     return true;
129827c4902SNan Zhou }
130827c4902SNan Zhou 
131827c4902SNan Zhou struct SelectTrie
132827c4902SNan Zhou {
133827c4902SNan Zhou     SelectTrie() = default;
134827c4902SNan Zhou 
135827c4902SNan Zhou     // Inserts a $select value; returns false if the nestedProperty is illegal.
136827c4902SNan Zhou     bool insertNode(std::string_view nestedProperty)
137827c4902SNan Zhou     {
138827c4902SNan Zhou         if (nestedProperty.empty())
139827c4902SNan Zhou         {
140827c4902SNan Zhou             return false;
141827c4902SNan Zhou         }
142827c4902SNan Zhou         SelectTrieNode* currNode = &root;
143827c4902SNan Zhou         size_t index = nestedProperty.find_first_of('/');
144827c4902SNan Zhou         while (!nestedProperty.empty())
145827c4902SNan Zhou         {
146827c4902SNan Zhou             std::string_view property = nestedProperty.substr(0, index);
147827c4902SNan Zhou             if (!isSelectedPropertyAllowed(property))
148827c4902SNan Zhou             {
149827c4902SNan Zhou                 return false;
150827c4902SNan Zhou             }
151827c4902SNan Zhou             currNode = currNode->emplace(property);
152827c4902SNan Zhou             if (index == std::string::npos)
153827c4902SNan Zhou             {
154827c4902SNan Zhou                 break;
155827c4902SNan Zhou             }
156827c4902SNan Zhou             nestedProperty.remove_prefix(index + 1);
157827c4902SNan Zhou             index = nestedProperty.find_first_of('/');
158827c4902SNan Zhou         }
159827c4902SNan Zhou         currNode->setToSelected();
160827c4902SNan Zhou         return true;
161827c4902SNan Zhou     }
162827c4902SNan Zhou 
163827c4902SNan Zhou     SelectTrieNode root;
164827c4902SNan Zhou };
165827c4902SNan Zhou 
166a6b9125fSNan Zhou // The struct stores the parsed query parameters of the default Redfish route.
167f4c99e70SEd Tanous struct Query
168f4c99e70SEd Tanous {
169a6b9125fSNan Zhou     // Only
170f4c99e70SEd Tanous     bool isOnly = false;
171a6b9125fSNan Zhou     // Expand
172a6b9125fSNan Zhou     uint8_t expandLevel = 0;
1737cf436c9SEd Tanous     ExpandType expandType = ExpandType::None;
174c937d2bfSEd Tanous 
175c937d2bfSEd Tanous     // Skip
1763648c8beSEd Tanous     std::optional<size_t> skip = std::nullopt;
177c937d2bfSEd Tanous 
178c937d2bfSEd Tanous     // Top
1795143f7a5SJiaqing Zhao     static constexpr size_t maxTop = 1000; // Max entries a response contain
1803648c8beSEd Tanous     std::optional<size_t> top = std::nullopt;
181e155ab54SNan Zhou 
182e155ab54SNan Zhou     // Select
183827c4902SNan Zhou     SelectTrie selectTrie = {};
184f4c99e70SEd Tanous };
185f4c99e70SEd Tanous 
186a6b9125fSNan Zhou // The struct defines how resource handlers in redfish-core/lib/ can handle
187a6b9125fSNan Zhou // query parameters themselves, so that the default Redfish route will delegate
188a6b9125fSNan Zhou // the processing.
189a6b9125fSNan Zhou struct QueryCapabilities
190a6b9125fSNan Zhou {
191a6b9125fSNan Zhou     bool canDelegateOnly = false;
192c937d2bfSEd Tanous     bool canDelegateTop = false;
193c937d2bfSEd Tanous     bool canDelegateSkip = false;
194a6b9125fSNan Zhou     uint8_t canDelegateExpandLevel = 0;
195e155ab54SNan Zhou     bool canDelegateSelect = false;
196a6b9125fSNan Zhou };
197a6b9125fSNan Zhou 
198a6b9125fSNan Zhou // Delegates query parameters according to the given |queryCapabilities|
199a6b9125fSNan Zhou // This function doesn't check query parameter conflicts since the parse
200a6b9125fSNan Zhou // function will take care of it.
201a6b9125fSNan Zhou // Returns a delegated query object which can be used by individual resource
202a6b9125fSNan Zhou // handlers so that handlers don't need to query again.
203a6b9125fSNan Zhou inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
204a6b9125fSNan Zhou {
205a6b9125fSNan Zhou     Query delegated;
206a6b9125fSNan Zhou     // delegate only
207a6b9125fSNan Zhou     if (query.isOnly && queryCapabilities.canDelegateOnly)
208a6b9125fSNan Zhou     {
209a6b9125fSNan Zhou         delegated.isOnly = true;
210a6b9125fSNan Zhou         query.isOnly = false;
211a6b9125fSNan Zhou     }
212a6b9125fSNan Zhou     // delegate expand as much as we can
213a6b9125fSNan Zhou     if (query.expandType != ExpandType::None)
214a6b9125fSNan Zhou     {
215a6b9125fSNan Zhou         delegated.expandType = query.expandType;
216a6b9125fSNan Zhou         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
217a6b9125fSNan Zhou         {
218a6b9125fSNan Zhou             query.expandType = ExpandType::None;
219a6b9125fSNan Zhou             delegated.expandLevel = query.expandLevel;
220a6b9125fSNan Zhou             query.expandLevel = 0;
221a6b9125fSNan Zhou         }
222a6b9125fSNan Zhou         else
223a6b9125fSNan Zhou         {
224a6b9125fSNan Zhou             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
225a6b9125fSNan Zhou         }
226a6b9125fSNan Zhou     }
227c937d2bfSEd Tanous 
228c937d2bfSEd Tanous     // delegate top
2293648c8beSEd Tanous     if (query.top && queryCapabilities.canDelegateTop)
230c937d2bfSEd Tanous     {
231c937d2bfSEd Tanous         delegated.top = query.top;
2323648c8beSEd Tanous         query.top = std::nullopt;
233c937d2bfSEd Tanous     }
234c937d2bfSEd Tanous 
235c937d2bfSEd Tanous     // delegate skip
2363648c8beSEd Tanous     if (query.skip && queryCapabilities.canDelegateSkip)
237c937d2bfSEd Tanous     {
238c937d2bfSEd Tanous         delegated.skip = query.skip;
239c937d2bfSEd Tanous         query.skip = 0;
240c937d2bfSEd Tanous     }
241e155ab54SNan Zhou 
242e155ab54SNan Zhou     // delegate select
243827c4902SNan Zhou     if (!query.selectTrie.root.empty() && queryCapabilities.canDelegateSelect)
244e155ab54SNan Zhou     {
245827c4902SNan Zhou         delegated.selectTrie = std::move(query.selectTrie);
246827c4902SNan Zhou         query.selectTrie.root.clear();
247e155ab54SNan Zhou     }
248a6b9125fSNan Zhou     return delegated;
249a6b9125fSNan Zhou }
250a6b9125fSNan Zhou 
2517cf436c9SEd Tanous inline bool getExpandType(std::string_view value, Query& query)
2527cf436c9SEd Tanous {
2537cf436c9SEd Tanous     if (value.empty())
2547cf436c9SEd Tanous     {
2557cf436c9SEd Tanous         return false;
2567cf436c9SEd Tanous     }
2577cf436c9SEd Tanous     switch (value[0])
2587cf436c9SEd Tanous     {
2597cf436c9SEd Tanous         case '*':
2607cf436c9SEd Tanous             query.expandType = ExpandType::Both;
2617cf436c9SEd Tanous             break;
2627cf436c9SEd Tanous         case '.':
2637cf436c9SEd Tanous             query.expandType = ExpandType::NotLinks;
2647cf436c9SEd Tanous             break;
2657cf436c9SEd Tanous         case '~':
2667cf436c9SEd Tanous             query.expandType = ExpandType::Links;
2677cf436c9SEd Tanous             break;
2687cf436c9SEd Tanous         default:
2697cf436c9SEd Tanous             return false;
2707cf436c9SEd Tanous     }
2717cf436c9SEd Tanous     value.remove_prefix(1);
2727cf436c9SEd Tanous     if (value.empty())
2737cf436c9SEd Tanous     {
2747cf436c9SEd Tanous         query.expandLevel = 1;
2757cf436c9SEd Tanous         return true;
2767cf436c9SEd Tanous     }
2777cf436c9SEd Tanous     constexpr std::string_view levels = "($levels=";
2787cf436c9SEd Tanous     if (!value.starts_with(levels))
2797cf436c9SEd Tanous     {
2807cf436c9SEd Tanous         return false;
2817cf436c9SEd Tanous     }
2827cf436c9SEd Tanous     value.remove_prefix(levels.size());
2837cf436c9SEd Tanous 
2842bd4ab43SPatrick Williams     auto it = std::from_chars(value.begin(), value.end(), query.expandLevel);
2857cf436c9SEd Tanous     if (it.ec != std::errc())
2867cf436c9SEd Tanous     {
2877cf436c9SEd Tanous         return false;
2887cf436c9SEd Tanous     }
2892bd4ab43SPatrick Williams     value.remove_prefix(
2902bd4ab43SPatrick Williams         static_cast<size_t>(std::distance(value.begin(), it.ptr)));
2917cf436c9SEd Tanous     return value == ")";
2927cf436c9SEd Tanous }
2937cf436c9SEd Tanous 
294c937d2bfSEd Tanous enum class QueryError
295c937d2bfSEd Tanous {
296c937d2bfSEd Tanous     Ok,
297c937d2bfSEd Tanous     OutOfRange,
298c937d2bfSEd Tanous     ValueFormat,
299c937d2bfSEd Tanous };
300c937d2bfSEd Tanous 
301c937d2bfSEd Tanous inline QueryError getNumericParam(std::string_view value, size_t& param)
302c937d2bfSEd Tanous {
3032bd4ab43SPatrick Williams     std::from_chars_result r = std::from_chars(value.begin(), value.end(),
3042bd4ab43SPatrick Williams                                                param);
305c937d2bfSEd Tanous 
306c937d2bfSEd Tanous     // If the number wasn't representable in the type, it's out of range
307c937d2bfSEd Tanous     if (r.ec == std::errc::result_out_of_range)
308c937d2bfSEd Tanous     {
309c937d2bfSEd Tanous         return QueryError::OutOfRange;
310c937d2bfSEd Tanous     }
311c937d2bfSEd Tanous     // All other errors are value format
312c937d2bfSEd Tanous     if (r.ec != std::errc())
313c937d2bfSEd Tanous     {
314c937d2bfSEd Tanous         return QueryError::ValueFormat;
315c937d2bfSEd Tanous     }
316c937d2bfSEd Tanous     return QueryError::Ok;
317c937d2bfSEd Tanous }
318c937d2bfSEd Tanous 
319c937d2bfSEd Tanous inline QueryError getSkipParam(std::string_view value, Query& query)
320c937d2bfSEd Tanous {
3213648c8beSEd Tanous     return getNumericParam(value, query.skip.emplace());
322c937d2bfSEd Tanous }
323c937d2bfSEd Tanous 
324c937d2bfSEd Tanous inline QueryError getTopParam(std::string_view value, Query& query)
325c937d2bfSEd Tanous {
3263648c8beSEd Tanous     QueryError ret = getNumericParam(value, query.top.emplace());
327c937d2bfSEd Tanous     if (ret != QueryError::Ok)
328c937d2bfSEd Tanous     {
329c937d2bfSEd Tanous         return ret;
330c937d2bfSEd Tanous     }
331c937d2bfSEd Tanous 
332c937d2bfSEd Tanous     // Range check for sanity.
3335143f7a5SJiaqing Zhao     if (query.top > Query::maxTop)
334c937d2bfSEd Tanous     {
335c937d2bfSEd Tanous         return QueryError::OutOfRange;
336c937d2bfSEd Tanous     }
337c937d2bfSEd Tanous 
338c937d2bfSEd Tanous     return QueryError::Ok;
339c937d2bfSEd Tanous }
340c937d2bfSEd Tanous 
341e155ab54SNan Zhou // Parses and validates the $select parameter.
342e155ab54SNan Zhou // As per OData URL Conventions and Redfish Spec, the $select values shall be
343e155ab54SNan Zhou // comma separated Resource Path
344e155ab54SNan Zhou // Ref:
345e155ab54SNan Zhou // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
346e155ab54SNan Zhou // 2.
347e155ab54SNan Zhou // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
348e155ab54SNan Zhou inline bool getSelectParam(std::string_view value, Query& query)
349e155ab54SNan Zhou {
350e155ab54SNan Zhou     std::vector<std::string> properties;
35150ebd4afSEd Tanous     bmcweb::split(properties, value, ',');
352e155ab54SNan Zhou     if (properties.empty())
353e155ab54SNan Zhou     {
354e155ab54SNan Zhou         return false;
355e155ab54SNan Zhou     }
356e155ab54SNan Zhou     // These a magic number, but with it it's less likely that this code
357e155ab54SNan Zhou     // introduces CVE; e.g., too large properties crash the service.
358e155ab54SNan Zhou     constexpr int maxNumProperties = 10;
359e155ab54SNan Zhou     if (properties.size() > maxNumProperties)
360e155ab54SNan Zhou     {
361e155ab54SNan Zhou         return false;
362e155ab54SNan Zhou     }
363827c4902SNan Zhou     for (const auto& property : properties)
364e155ab54SNan Zhou     {
365827c4902SNan Zhou         if (!query.selectTrie.insertNode(property))
366e155ab54SNan Zhou         {
367e155ab54SNan Zhou             return false;
368e155ab54SNan Zhou         }
369e155ab54SNan Zhou     }
370e155ab54SNan Zhou     return true;
371e155ab54SNan Zhou }
372e155ab54SNan Zhou 
373079360aeSEd Tanous inline std::optional<Query> parseParameters(boost::urls::params_view urlParams,
374f4c99e70SEd Tanous                                             crow::Response& res)
375f4c99e70SEd Tanous {
376f4c99e70SEd Tanous     Query ret;
377f4c99e70SEd Tanous     for (const boost::urls::params_view::value_type& it : urlParams)
378f4c99e70SEd Tanous     {
379079360aeSEd Tanous         if (it.key == "only")
380f4c99e70SEd Tanous         {
381f4c99e70SEd Tanous             if (!it.value.empty())
382f4c99e70SEd Tanous             {
383079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
384f4c99e70SEd Tanous                 return std::nullopt;
385f4c99e70SEd Tanous             }
386f4c99e70SEd Tanous             ret.isOnly = true;
387f4c99e70SEd Tanous         }
388079360aeSEd Tanous         else if (it.key == "$expand" && bmcwebInsecureEnableQueryParams)
3897cf436c9SEd Tanous         {
390079360aeSEd Tanous             if (!getExpandType(it.value, ret))
3917cf436c9SEd Tanous             {
392079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
3937cf436c9SEd Tanous                 return std::nullopt;
394f4c99e70SEd Tanous             }
3957cf436c9SEd Tanous         }
396079360aeSEd Tanous         else if (it.key == "$top")
397c937d2bfSEd Tanous         {
398079360aeSEd Tanous             QueryError topRet = getTopParam(it.value, ret);
399c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
400c937d2bfSEd Tanous             {
401079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
402c937d2bfSEd Tanous                 return std::nullopt;
403c937d2bfSEd Tanous             }
404c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
405c937d2bfSEd Tanous             {
406c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
407079360aeSEd Tanous                     res, it.value, "$top",
408079360aeSEd Tanous                     "0-" + std::to_string(Query::maxTop));
409c937d2bfSEd Tanous                 return std::nullopt;
410c937d2bfSEd Tanous             }
411c937d2bfSEd Tanous         }
412079360aeSEd Tanous         else if (it.key == "$skip")
413c937d2bfSEd Tanous         {
414079360aeSEd Tanous             QueryError topRet = getSkipParam(it.value, ret);
415c937d2bfSEd Tanous             if (topRet == QueryError::ValueFormat)
416c937d2bfSEd Tanous             {
417079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
418c937d2bfSEd Tanous                 return std::nullopt;
419c937d2bfSEd Tanous             }
420c937d2bfSEd Tanous             if (topRet == QueryError::OutOfRange)
421c937d2bfSEd Tanous             {
422c937d2bfSEd Tanous                 messages::queryParameterOutOfRange(
423079360aeSEd Tanous                     res, it.value, it.key,
424a926c53eSJiaqing Zhao                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
425c937d2bfSEd Tanous                 return std::nullopt;
426c937d2bfSEd Tanous             }
427c937d2bfSEd Tanous         }
428079360aeSEd Tanous         else if (it.key == "$select")
429e155ab54SNan Zhou         {
430079360aeSEd Tanous             if (!getSelectParam(it.value, ret))
431e155ab54SNan Zhou             {
432079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
433e155ab54SNan Zhou                 return std::nullopt;
434e155ab54SNan Zhou             }
435e155ab54SNan Zhou         }
4367cf436c9SEd Tanous         else
4377cf436c9SEd Tanous         {
4387cf436c9SEd Tanous             // Intentionally ignore other errors Redfish spec, 7.3.1
439079360aeSEd Tanous             if (it.key.starts_with("$"))
4407cf436c9SEd Tanous             {
4417cf436c9SEd Tanous                 // Services shall return... The HTTP 501 Not Implemented
4427cf436c9SEd Tanous                 // status code for any unsupported query parameters that
4437cf436c9SEd Tanous                 // start with $ .
444079360aeSEd Tanous                 messages::queryParameterValueFormatError(res, it.value, it.key);
4457cf436c9SEd Tanous                 res.result(boost::beast::http::status::not_implemented);
4467cf436c9SEd Tanous                 return std::nullopt;
4477cf436c9SEd Tanous             }
4487cf436c9SEd Tanous             // "Shall ignore unknown or unsupported query parameters that do
4497cf436c9SEd Tanous             // not begin with $ ."
4507cf436c9SEd Tanous         }
4517cf436c9SEd Tanous     }
4527cf436c9SEd Tanous 
453827c4902SNan Zhou     if (ret.expandType != ExpandType::None && !ret.selectTrie.root.empty())
454e155ab54SNan Zhou     {
455e155ab54SNan Zhou         messages::queryCombinationInvalid(res);
456e155ab54SNan Zhou         return std::nullopt;
457e155ab54SNan Zhou     }
458e155ab54SNan Zhou 
459f4c99e70SEd Tanous     return ret;
460f4c99e70SEd Tanous }
461f4c99e70SEd Tanous 
462f4c99e70SEd Tanous inline bool processOnly(crow::App& app, crow::Response& res,
463f4c99e70SEd Tanous                         std::function<void(crow::Response&)>& completionHandler)
464f4c99e70SEd Tanous {
465f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing only query param";
466f4c99e70SEd Tanous     auto itMembers = res.jsonValue.find("Members");
467f4c99e70SEd Tanous     if (itMembers == res.jsonValue.end())
468f4c99e70SEd Tanous     {
469f4c99e70SEd Tanous         messages::queryNotSupportedOnResource(res);
470f4c99e70SEd Tanous         completionHandler(res);
471f4c99e70SEd Tanous         return false;
472f4c99e70SEd Tanous     }
473f4c99e70SEd Tanous     auto itMemBegin = itMembers->begin();
474f4c99e70SEd Tanous     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
475f4c99e70SEd Tanous     {
476f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
477f4c99e70SEd Tanous                          << " element, returning full collection.";
478f4c99e70SEd Tanous         completionHandler(res);
479f4c99e70SEd Tanous         return false;
480f4c99e70SEd Tanous     }
481f4c99e70SEd Tanous 
482f4c99e70SEd Tanous     auto itUrl = itMemBegin->find("@odata.id");
483f4c99e70SEd Tanous     if (itUrl == itMemBegin->end())
484f4c99e70SEd Tanous     {
485f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "No found odata.id";
486f4c99e70SEd Tanous         messages::internalError(res);
487f4c99e70SEd Tanous         completionHandler(res);
488f4c99e70SEd Tanous         return false;
489f4c99e70SEd Tanous     }
490f4c99e70SEd Tanous     const std::string* url = itUrl->get_ptr<const std::string*>();
491f4c99e70SEd Tanous     if (url == nullptr)
492f4c99e70SEd Tanous     {
493f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
494f4c99e70SEd Tanous         messages::internalError(res);
495f4c99e70SEd Tanous         completionHandler(res);
496f4c99e70SEd Tanous         return false;
497f4c99e70SEd Tanous     }
498f4c99e70SEd Tanous     // TODO(Ed) copy request headers?
499f4c99e70SEd Tanous     // newReq.session = req.session;
500f4c99e70SEd Tanous     std::error_code ec;
501f4c99e70SEd Tanous     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
502f4c99e70SEd Tanous     if (ec)
503f4c99e70SEd Tanous     {
504f4c99e70SEd Tanous         messages::internalError(res);
505f4c99e70SEd Tanous         completionHandler(res);
506f4c99e70SEd Tanous         return false;
507f4c99e70SEd Tanous     }
508f4c99e70SEd Tanous 
509f4c99e70SEd Tanous     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
510f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
511f4c99e70SEd Tanous     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
512f4c99e70SEd Tanous     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
513f4c99e70SEd Tanous     app.handle(newReq, asyncResp);
514f4c99e70SEd Tanous     return true;
515f4c99e70SEd Tanous }
516f4c99e70SEd Tanous 
5177cf436c9SEd Tanous struct ExpandNode
5187cf436c9SEd Tanous {
5197cf436c9SEd Tanous     nlohmann::json::json_pointer location;
5207cf436c9SEd Tanous     std::string uri;
5217cf436c9SEd Tanous 
5227cf436c9SEd Tanous     inline bool operator==(const ExpandNode& other) const
5237cf436c9SEd Tanous     {
5247cf436c9SEd Tanous         return location == other.location && uri == other.uri;
5257cf436c9SEd Tanous     }
5267cf436c9SEd Tanous };
5277cf436c9SEd Tanous 
5287cf436c9SEd Tanous // Walks a json object looking for Redfish NavigationReference entries that
5297cf436c9SEd Tanous // might need resolved.  It recursively walks the jsonResponse object, looking
5307cf436c9SEd Tanous // for links at every level, and returns a list (out) of locations within the
5317cf436c9SEd Tanous // tree that need to be expanded.  The current json pointer location p is passed
5327cf436c9SEd Tanous // in to reference the current node that's being expanded, so it can be combined
5337cf436c9SEd Tanous // with the keys from the jsonResponse object
5347cf436c9SEd Tanous inline void findNavigationReferencesRecursive(
5357cf436c9SEd Tanous     ExpandType eType, nlohmann::json& jsonResponse,
536*32cdb4a7SWilly Tu     const nlohmann::json::json_pointer& p, int depth, int skipDepth,
537*32cdb4a7SWilly Tu     bool inLinks, std::vector<ExpandNode>& out)
5387cf436c9SEd Tanous {
5397cf436c9SEd Tanous     // If no expand is needed, return early
5407cf436c9SEd Tanous     if (eType == ExpandType::None)
5417cf436c9SEd Tanous     {
5427cf436c9SEd Tanous         return;
5437cf436c9SEd Tanous     }
544ad595fa6SEd Tanous 
5457cf436c9SEd Tanous     nlohmann::json::array_t* array =
5467cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::array_t*>();
5477cf436c9SEd Tanous     if (array != nullptr)
5487cf436c9SEd Tanous     {
5497cf436c9SEd Tanous         size_t index = 0;
5507cf436c9SEd Tanous         // For arrays, walk every element in the array
5517cf436c9SEd Tanous         for (auto& element : *array)
5527cf436c9SEd Tanous         {
5537cf436c9SEd Tanous             nlohmann::json::json_pointer newPtr = p / index;
5547cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
555ad595fa6SEd Tanous             findNavigationReferencesRecursive(eType, element, newPtr, depth,
556*32cdb4a7SWilly Tu                                               skipDepth, inLinks, out);
5577cf436c9SEd Tanous             index++;
5587cf436c9SEd Tanous         }
5597cf436c9SEd Tanous     }
5607cf436c9SEd Tanous     nlohmann::json::object_t* obj =
5617cf436c9SEd Tanous         jsonResponse.get_ptr<nlohmann::json::object_t*>();
5627cf436c9SEd Tanous     if (obj == nullptr)
5637cf436c9SEd Tanous     {
5647cf436c9SEd Tanous         return;
5657cf436c9SEd Tanous     }
5667cf436c9SEd Tanous     // Navigation References only ever have a single element
5677cf436c9SEd Tanous     if (obj->size() == 1)
5687cf436c9SEd Tanous     {
5697cf436c9SEd Tanous         if (obj->begin()->first == "@odata.id")
5707cf436c9SEd Tanous         {
5717cf436c9SEd Tanous             const std::string* uri =
5727cf436c9SEd Tanous                 obj->begin()->second.get_ptr<const std::string*>();
5737cf436c9SEd Tanous             if (uri != nullptr)
5747cf436c9SEd Tanous             {
575ad595fa6SEd Tanous                 BMCWEB_LOG_DEBUG << "Found " << *uri << " at " << p.to_string();
576*32cdb4a7SWilly Tu                 if (skipDepth == 0)
577*32cdb4a7SWilly Tu                 {
5787cf436c9SEd Tanous                     out.push_back({p, *uri});
579*32cdb4a7SWilly Tu                 }
580ad595fa6SEd Tanous                 return;
5817cf436c9SEd Tanous             }
5827cf436c9SEd Tanous         }
5837cf436c9SEd Tanous     }
584ad595fa6SEd Tanous 
585ad595fa6SEd Tanous     int newDepth = depth;
586ad595fa6SEd Tanous     auto odataId = obj->find("@odata.id");
587ad595fa6SEd Tanous     if (odataId != obj->end())
588ad595fa6SEd Tanous     {
589ad595fa6SEd Tanous         // The Redfish spec requires all resources to include the resource
590ad595fa6SEd Tanous         // identifier.  If the object has multiple elements and one of them is
591ad595fa6SEd Tanous         // "@odata.id" then that means we have entered a new level / expanded
592ad595fa6SEd Tanous         // resource.  We need to stop traversing if we're already at the desired
593ad595fa6SEd Tanous         // depth
594*32cdb4a7SWilly Tu         if (obj->size() > 1)
595*32cdb4a7SWilly Tu         {
596*32cdb4a7SWilly Tu             if (depth == 0)
597ad595fa6SEd Tanous             {
598ad595fa6SEd Tanous                 return;
599ad595fa6SEd Tanous             }
600*32cdb4a7SWilly Tu             if (skipDepth > 0)
601*32cdb4a7SWilly Tu             {
602*32cdb4a7SWilly Tu                 skipDepth--;
603*32cdb4a7SWilly Tu             }
604*32cdb4a7SWilly Tu         }
605*32cdb4a7SWilly Tu 
606*32cdb4a7SWilly Tu         if (skipDepth == 0)
607*32cdb4a7SWilly Tu         {
608ad595fa6SEd Tanous             newDepth--;
609ad595fa6SEd Tanous         }
610*32cdb4a7SWilly Tu     }
611ad595fa6SEd Tanous 
6127cf436c9SEd Tanous     // Loop the object and look for links
6137cf436c9SEd Tanous     for (auto& element : *obj)
6147cf436c9SEd Tanous     {
615e479ad58SNan Zhou         bool localInLinks = inLinks;
616e479ad58SNan Zhou         if (!localInLinks)
6177cf436c9SEd Tanous         {
6187cf436c9SEd Tanous             // Check if this is a links node
619e479ad58SNan Zhou             localInLinks = element.first == "Links";
6207cf436c9SEd Tanous         }
6217cf436c9SEd Tanous         // Only traverse the parts of the tree the user asked for
6227cf436c9SEd Tanous         // Per section 7.3 of the redfish specification
623e479ad58SNan Zhou         if (localInLinks && eType == ExpandType::NotLinks)
6247cf436c9SEd Tanous         {
6257cf436c9SEd Tanous             continue;
6267cf436c9SEd Tanous         }
627e479ad58SNan Zhou         if (!localInLinks && eType == ExpandType::Links)
6287cf436c9SEd Tanous         {
6297cf436c9SEd Tanous             continue;
6307cf436c9SEd Tanous         }
6317cf436c9SEd Tanous         nlohmann::json::json_pointer newPtr = p / element.first;
6327cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
6337cf436c9SEd Tanous 
6347cf436c9SEd Tanous         findNavigationReferencesRecursive(eType, element.second, newPtr,
635*32cdb4a7SWilly Tu                                           newDepth, skipDepth, localInLinks,
636*32cdb4a7SWilly Tu                                           out);
6377cf436c9SEd Tanous     }
6387cf436c9SEd Tanous }
6397cf436c9SEd Tanous 
640ad595fa6SEd Tanous // TODO: When aggregation is enabled and we receive a partially expanded
641ad595fa6SEd Tanous // response we may need need additional handling when the original URI was
642ad595fa6SEd Tanous // up tree from a top level collection.
643ad595fa6SEd Tanous // Isn't a concern until https://gerrit.openbmc.org/c/openbmc/bmcweb/+/60556
644ad595fa6SEd Tanous // lands.  May want to avoid forwarding query params when request is uptree from
645ad595fa6SEd Tanous // a top level collection.
6467cf436c9SEd Tanous inline std::vector<ExpandNode>
647*32cdb4a7SWilly Tu     findNavigationReferences(ExpandType eType, int depth, int skipDepth,
648ad595fa6SEd Tanous                              nlohmann::json& jsonResponse)
6497cf436c9SEd Tanous {
6507cf436c9SEd Tanous     std::vector<ExpandNode> ret;
65172c3ae33SNan Zhou     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
652*32cdb4a7SWilly Tu     // SkipDepth +1 since we are skipping the root by default.
653*32cdb4a7SWilly Tu     findNavigationReferencesRecursive(eType, jsonResponse, root, depth,
654*32cdb4a7SWilly Tu                                       skipDepth + 1, false, ret);
6557cf436c9SEd Tanous     return ret;
6567cf436c9SEd Tanous }
6577cf436c9SEd Tanous 
65872c3ae33SNan Zhou // Formats a query parameter string for the sub-query.
659b66cf2a2SNan Zhou // Returns std::nullopt on failures.
66072c3ae33SNan Zhou // This function shall handle $select when it is added.
66172c3ae33SNan Zhou // There is no need to handle parameters that's not campatible with $expand,
66272c3ae33SNan Zhou // e.g., $only, since this function will only be called in side $expand handlers
663b66cf2a2SNan Zhou inline std::optional<std::string> formatQueryForExpand(const Query& query)
66472c3ae33SNan Zhou {
66572c3ae33SNan Zhou     // query.expandLevel<=1: no need to do subqueries
66672c3ae33SNan Zhou     if (query.expandLevel <= 1)
66772c3ae33SNan Zhou     {
668b66cf2a2SNan Zhou         return "";
66972c3ae33SNan Zhou     }
67072c3ae33SNan Zhou     std::string str = "?$expand=";
671b66cf2a2SNan Zhou     bool queryTypeExpected = false;
67272c3ae33SNan Zhou     switch (query.expandType)
67372c3ae33SNan Zhou     {
67472c3ae33SNan Zhou         case ExpandType::None:
675b66cf2a2SNan Zhou             return "";
67672c3ae33SNan Zhou         case ExpandType::Links:
677b66cf2a2SNan Zhou             queryTypeExpected = true;
67872c3ae33SNan Zhou             str += '~';
67972c3ae33SNan Zhou             break;
68072c3ae33SNan Zhou         case ExpandType::NotLinks:
681b66cf2a2SNan Zhou             queryTypeExpected = true;
68272c3ae33SNan Zhou             str += '.';
68372c3ae33SNan Zhou             break;
68472c3ae33SNan Zhou         case ExpandType::Both:
685b66cf2a2SNan Zhou             queryTypeExpected = true;
68672c3ae33SNan Zhou             str += '*';
68772c3ae33SNan Zhou             break;
688b66cf2a2SNan Zhou     }
689b66cf2a2SNan Zhou     if (!queryTypeExpected)
690b66cf2a2SNan Zhou     {
691b66cf2a2SNan Zhou         return std::nullopt;
69272c3ae33SNan Zhou     }
69372c3ae33SNan Zhou     str += "($levels=";
69472c3ae33SNan Zhou     str += std::to_string(query.expandLevel - 1);
69572c3ae33SNan Zhou     str += ')';
69672c3ae33SNan Zhou     return str;
69772c3ae33SNan Zhou }
69872c3ae33SNan Zhou 
6993590bd1dSNan Zhou // Propogates the worst error code to the final response.
7003590bd1dSNan Zhou // The order of error code is (from high to low)
7013590bd1dSNan Zhou // 500 Internal Server Error
7023590bd1dSNan Zhou // 511 Network Authentication Required
7033590bd1dSNan Zhou // 510 Not Extended
7043590bd1dSNan Zhou // 508 Loop Detected
7053590bd1dSNan Zhou // 507 Insufficient Storage
7063590bd1dSNan Zhou // 506 Variant Also Negotiates
7073590bd1dSNan Zhou // 505 HTTP Version Not Supported
7083590bd1dSNan Zhou // 504 Gateway Timeout
7093590bd1dSNan Zhou // 503 Service Unavailable
7103590bd1dSNan Zhou // 502 Bad Gateway
7113590bd1dSNan Zhou // 501 Not Implemented
7123590bd1dSNan Zhou // 401 Unauthorized
7133590bd1dSNan Zhou // 451 - 409 Error codes (not listed explictly)
7143590bd1dSNan Zhou // 408 Request Timeout
7153590bd1dSNan Zhou // 407 Proxy Authentication Required
7163590bd1dSNan Zhou // 406 Not Acceptable
7173590bd1dSNan Zhou // 405 Method Not Allowed
7183590bd1dSNan Zhou // 404 Not Found
7193590bd1dSNan Zhou // 403 Forbidden
7203590bd1dSNan Zhou // 402 Payment Required
7213590bd1dSNan Zhou // 400 Bad Request
7223590bd1dSNan Zhou inline unsigned propogateErrorCode(unsigned finalCode, unsigned subResponseCode)
7233590bd1dSNan Zhou {
7243590bd1dSNan Zhou     // We keep a explicit list for error codes that this project often uses
7253590bd1dSNan Zhou     // Higer priority codes are in lower indexes
7263590bd1dSNan Zhou     constexpr std::array<unsigned, 13> orderedCodes = {
7273590bd1dSNan Zhou         500, 507, 503, 502, 501, 401, 412, 409, 406, 405, 404, 403, 400};
7283590bd1dSNan Zhou     size_t finalCodeIndex = std::numeric_limits<size_t>::max();
7293590bd1dSNan Zhou     size_t subResponseCodeIndex = std::numeric_limits<size_t>::max();
7303590bd1dSNan Zhou     for (size_t i = 0; i < orderedCodes.size(); ++i)
7313590bd1dSNan Zhou     {
7323590bd1dSNan Zhou         if (orderedCodes[i] == finalCode)
7333590bd1dSNan Zhou         {
7343590bd1dSNan Zhou             finalCodeIndex = i;
7353590bd1dSNan Zhou         }
7363590bd1dSNan Zhou         if (orderedCodes[i] == subResponseCode)
7373590bd1dSNan Zhou         {
7383590bd1dSNan Zhou             subResponseCodeIndex = i;
7393590bd1dSNan Zhou         }
7403590bd1dSNan Zhou     }
7413590bd1dSNan Zhou     if (finalCodeIndex != std::numeric_limits<size_t>::max() &&
7423590bd1dSNan Zhou         subResponseCodeIndex != std::numeric_limits<size_t>::max())
7433590bd1dSNan Zhou     {
7443590bd1dSNan Zhou         return finalCodeIndex <= subResponseCodeIndex ? finalCode
7453590bd1dSNan Zhou                                                       : subResponseCode;
7463590bd1dSNan Zhou     }
7473590bd1dSNan Zhou     if (subResponseCode == 500 || finalCode == 500)
7483590bd1dSNan Zhou     {
7493590bd1dSNan Zhou         return 500;
7503590bd1dSNan Zhou     }
7513590bd1dSNan Zhou     if (subResponseCode > 500 || finalCode > 500)
7523590bd1dSNan Zhou     {
7533590bd1dSNan Zhou         return std::max(finalCode, subResponseCode);
7543590bd1dSNan Zhou     }
7553590bd1dSNan Zhou     if (subResponseCode == 401)
7563590bd1dSNan Zhou     {
7573590bd1dSNan Zhou         return subResponseCode;
7583590bd1dSNan Zhou     }
7593590bd1dSNan Zhou     return std::max(finalCode, subResponseCode);
7603590bd1dSNan Zhou }
7613590bd1dSNan Zhou 
7623590bd1dSNan Zhou // Propogates all error messages into |finalResponse|
7633590bd1dSNan Zhou inline void propogateError(crow::Response& finalResponse,
7643590bd1dSNan Zhou                            crow::Response& subResponse)
7653590bd1dSNan Zhou {
7663590bd1dSNan Zhou     // no errors
7673590bd1dSNan Zhou     if (subResponse.resultInt() >= 200 && subResponse.resultInt() < 400)
7683590bd1dSNan Zhou     {
7693590bd1dSNan Zhou         return;
7703590bd1dSNan Zhou     }
7713590bd1dSNan Zhou     messages::moveErrorsToErrorJson(finalResponse.jsonValue,
7723590bd1dSNan Zhou                                     subResponse.jsonValue);
7733590bd1dSNan Zhou     finalResponse.result(
7743590bd1dSNan Zhou         propogateErrorCode(finalResponse.resultInt(), subResponse.resultInt()));
7753590bd1dSNan Zhou }
7763590bd1dSNan Zhou 
7777cf436c9SEd Tanous class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
7787cf436c9SEd Tanous {
7797cf436c9SEd Tanous   public:
7807cf436c9SEd Tanous     // This object takes a single asyncResp object as the "final" one, then
7817cf436c9SEd Tanous     // allows callers to attach sub-responses within the json tree that need
7827cf436c9SEd Tanous     // to be executed and filled into their appropriate locations.  This
7837cf436c9SEd Tanous     // class manages the final "merge" of the json resources.
7848a592810SEd Tanous     MultiAsyncResp(crow::App& appIn,
7857cf436c9SEd Tanous                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
7868a592810SEd Tanous         app(appIn),
7877cf436c9SEd Tanous         finalRes(std::move(finalResIn))
7887cf436c9SEd Tanous     {}
7897cf436c9SEd Tanous 
7907cf436c9SEd Tanous     void addAwaitingResponse(
79102cad96eSEd Tanous         const std::shared_ptr<bmcweb::AsyncResp>& res,
7927cf436c9SEd Tanous         const nlohmann::json::json_pointer& finalExpandLocation)
7937cf436c9SEd Tanous     {
7947cf436c9SEd Tanous         res->res.setCompleteRequestHandler(std::bind_front(
79572c3ae33SNan Zhou             placeResultStatic, shared_from_this(), finalExpandLocation));
7967cf436c9SEd Tanous     }
7977cf436c9SEd Tanous 
79872c3ae33SNan Zhou     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
7997cf436c9SEd Tanous                      crow::Response& res)
8007cf436c9SEd Tanous     {
8013590bd1dSNan Zhou         BMCWEB_LOG_DEBUG << "placeResult for " << locationToPlace;
8023590bd1dSNan Zhou         propogateError(finalRes->res, res);
8033590bd1dSNan Zhou         if (!res.jsonValue.is_object() || res.jsonValue.empty())
8043590bd1dSNan Zhou         {
8053590bd1dSNan Zhou             return;
8063590bd1dSNan Zhou         }
8077cf436c9SEd Tanous         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
8087cf436c9SEd Tanous         finalObj = std::move(res.jsonValue);
8097cf436c9SEd Tanous     }
8107cf436c9SEd Tanous 
81172c3ae33SNan Zhou     // Handles the very first level of Expand, and starts a chain of sub-queries
81272c3ae33SNan Zhou     // for deeper levels.
813*32cdb4a7SWilly Tu     void startQuery(const Query& query, const Query& delegated)
81472c3ae33SNan Zhou     {
815ad595fa6SEd Tanous         std::vector<ExpandNode> nodes = findNavigationReferences(
816*32cdb4a7SWilly Tu             query.expandType, query.expandLevel, delegated.expandLevel,
817*32cdb4a7SWilly Tu             finalRes->res.jsonValue);
8187cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
819b66cf2a2SNan Zhou         const std::optional<std::string> queryStr = formatQueryForExpand(query);
820b66cf2a2SNan Zhou         if (!queryStr)
821b66cf2a2SNan Zhou         {
822b66cf2a2SNan Zhou             messages::internalError(finalRes->res);
823b66cf2a2SNan Zhou             return;
824b66cf2a2SNan Zhou         }
8257cf436c9SEd Tanous         for (const ExpandNode& node : nodes)
8267cf436c9SEd Tanous         {
827b66cf2a2SNan Zhou             const std::string subQuery = node.uri + *queryStr;
82872c3ae33SNan Zhou             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
8297cf436c9SEd Tanous             std::error_code ec;
83072c3ae33SNan Zhou             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
8317cf436c9SEd Tanous                                  ec);
8327cf436c9SEd Tanous             if (ec)
8337cf436c9SEd Tanous             {
83472c3ae33SNan Zhou                 messages::internalError(finalRes->res);
8357cf436c9SEd Tanous                 return;
8367cf436c9SEd Tanous             }
8377cf436c9SEd Tanous 
8387cf436c9SEd Tanous             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
8397cf436c9SEd Tanous             BMCWEB_LOG_DEBUG << "setting completion handler on "
8407cf436c9SEd Tanous                              << &asyncResp->res;
84172c3ae33SNan Zhou 
84272c3ae33SNan Zhou             addAwaitingResponse(asyncResp, node.location);
8437cf436c9SEd Tanous             app.handle(newReq, asyncResp);
8447cf436c9SEd Tanous         }
8457cf436c9SEd Tanous     }
8467cf436c9SEd Tanous 
8477cf436c9SEd Tanous   private:
84872c3ae33SNan Zhou     static void
84972c3ae33SNan Zhou         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
8507cf436c9SEd Tanous                           const nlohmann::json::json_pointer& locationToPlace,
8517cf436c9SEd Tanous                           crow::Response& res)
8527cf436c9SEd Tanous     {
85372c3ae33SNan Zhou         multi->placeResult(locationToPlace, res);
8547cf436c9SEd Tanous     }
8557cf436c9SEd Tanous 
8567cf436c9SEd Tanous     crow::App& app;
8577cf436c9SEd Tanous     std::shared_ptr<bmcweb::AsyncResp> finalRes;
8587cf436c9SEd Tanous };
8597cf436c9SEd Tanous 
8602a68dc80SEd Tanous inline void processTopAndSkip(const Query& query, crow::Response& res)
8612a68dc80SEd Tanous {
8623648c8beSEd Tanous     if (!query.skip && !query.top)
8633648c8beSEd Tanous     {
8643648c8beSEd Tanous         // No work to do.
8653648c8beSEd Tanous         return;
8663648c8beSEd Tanous     }
8672a68dc80SEd Tanous     nlohmann::json::object_t* obj =
8682a68dc80SEd Tanous         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
8692a68dc80SEd Tanous     if (obj == nullptr)
8702a68dc80SEd Tanous     {
8712a68dc80SEd Tanous         // Shouldn't be possible.  All responses should be objects.
8722a68dc80SEd Tanous         messages::internalError(res);
8732a68dc80SEd Tanous         return;
8742a68dc80SEd Tanous     }
8752a68dc80SEd Tanous 
8762a68dc80SEd Tanous     BMCWEB_LOG_DEBUG << "Handling top/skip";
8772a68dc80SEd Tanous     nlohmann::json::object_t::iterator members = obj->find("Members");
8782a68dc80SEd Tanous     if (members == obj->end())
8792a68dc80SEd Tanous     {
8802a68dc80SEd Tanous         // From the Redfish specification 7.3.1
8812a68dc80SEd Tanous         // ... the HTTP 400 Bad Request status code with the
8822a68dc80SEd Tanous         // QueryNotSupportedOnResource message from the Base Message Registry
8832a68dc80SEd Tanous         // for any supported query parameters that apply only to resource
8842a68dc80SEd Tanous         // collections but are used on singular resources.
8852a68dc80SEd Tanous         messages::queryNotSupportedOnResource(res);
8862a68dc80SEd Tanous         return;
8872a68dc80SEd Tanous     }
8882a68dc80SEd Tanous 
8892a68dc80SEd Tanous     nlohmann::json::array_t* arr =
8902a68dc80SEd Tanous         members->second.get_ptr<nlohmann::json::array_t*>();
8912a68dc80SEd Tanous     if (arr == nullptr)
8922a68dc80SEd Tanous     {
8932a68dc80SEd Tanous         messages::internalError(res);
8942a68dc80SEd Tanous         return;
8952a68dc80SEd Tanous     }
8962a68dc80SEd Tanous 
8973648c8beSEd Tanous     if (query.skip)
8983648c8beSEd Tanous     {
8993648c8beSEd Tanous         // Per section 7.3.1 of the Redfish specification, $skip is run before
9003648c8beSEd Tanous         // $top Can only skip as many values as we have
9013648c8beSEd Tanous         size_t skip = std::min(arr->size(), *query.skip);
9022a68dc80SEd Tanous         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
9033648c8beSEd Tanous     }
9043648c8beSEd Tanous     if (query.top)
9053648c8beSEd Tanous     {
9063648c8beSEd Tanous         size_t top = std::min(arr->size(), *query.top);
9072a68dc80SEd Tanous         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
9082a68dc80SEd Tanous     }
9093648c8beSEd Tanous }
9102a68dc80SEd Tanous 
911827c4902SNan Zhou // Given a JSON subtree |currRoot|, this function erases leaves whose keys are
912827c4902SNan Zhou // not in the |currNode| Trie node.
913827c4902SNan Zhou inline void recursiveSelect(nlohmann::json& currRoot,
914827c4902SNan Zhou                             const SelectTrieNode& currNode)
915e155ab54SNan Zhou {
916e155ab54SNan Zhou     nlohmann::json::object_t* object =
917e155ab54SNan Zhou         currRoot.get_ptr<nlohmann::json::object_t*>();
918e155ab54SNan Zhou     if (object != nullptr)
919e155ab54SNan Zhou     {
920827c4902SNan Zhou         BMCWEB_LOG_DEBUG << "Current JSON is an object";
921e155ab54SNan Zhou         auto it = currRoot.begin();
922e155ab54SNan Zhou         while (it != currRoot.end())
923e155ab54SNan Zhou         {
924e155ab54SNan Zhou             auto nextIt = std::next(it);
925827c4902SNan Zhou             BMCWEB_LOG_DEBUG << "key=" << it.key();
926827c4902SNan Zhou             const SelectTrieNode* nextNode = currNode.find(it.key());
9275c9fb2d6SNan Zhou             // Per the Redfish spec section 7.3.3, the service shall select
9285c9fb2d6SNan Zhou             // certain properties as if $select was omitted. This applies to
9295c9fb2d6SNan Zhou             // every TrieNode that contains leaves and the root.
9305c9fb2d6SNan Zhou             constexpr std::array<std::string_view, 5> reservedProperties = {
9315c9fb2d6SNan Zhou                 "@odata.id", "@odata.type", "@odata.context", "@odata.etag",
9325c9fb2d6SNan Zhou                 "error"};
93389492a15SPatrick Williams             bool reserved = std::find(reservedProperties.begin(),
93489492a15SPatrick Williams                                       reservedProperties.end(),
9355c9fb2d6SNan Zhou                                       it.key()) != reservedProperties.end();
9365c9fb2d6SNan Zhou             if (reserved || (nextNode != nullptr && nextNode->isSelected()))
937e155ab54SNan Zhou             {
938e155ab54SNan Zhou                 it = nextIt;
939e155ab54SNan Zhou                 continue;
940e155ab54SNan Zhou             }
941827c4902SNan Zhou             if (nextNode != nullptr)
942e155ab54SNan Zhou             {
943827c4902SNan Zhou                 BMCWEB_LOG_DEBUG << "Recursively select: " << it.key();
944827c4902SNan Zhou                 recursiveSelect(*it, *nextNode);
945e155ab54SNan Zhou                 it = nextIt;
946e155ab54SNan Zhou                 continue;
947e155ab54SNan Zhou             }
948827c4902SNan Zhou             BMCWEB_LOG_DEBUG << it.key() << " is getting removed!";
949e155ab54SNan Zhou             it = currRoot.erase(it);
950e155ab54SNan Zhou         }
951e155ab54SNan Zhou     }
9525c9fb2d6SNan Zhou     nlohmann::json::array_t* array =
9535c9fb2d6SNan Zhou         currRoot.get_ptr<nlohmann::json::array_t*>();
9545c9fb2d6SNan Zhou     if (array != nullptr)
9555c9fb2d6SNan Zhou     {
9565c9fb2d6SNan Zhou         BMCWEB_LOG_DEBUG << "Current JSON is an array";
9575c9fb2d6SNan Zhou         // Array index is omitted, so reuse the same Trie node
9585c9fb2d6SNan Zhou         for (nlohmann::json& nextRoot : *array)
9595c9fb2d6SNan Zhou         {
9605c9fb2d6SNan Zhou             recursiveSelect(nextRoot, currNode);
9615c9fb2d6SNan Zhou         }
9625c9fb2d6SNan Zhou     }
963e155ab54SNan Zhou }
964e155ab54SNan Zhou 
965e155ab54SNan Zhou // The current implementation of $select still has the following TODOs due to
966e155ab54SNan Zhou //  ambiguity and/or complexity.
9675c9fb2d6SNan Zhou // 1. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
968e155ab54SNan Zhou // created for clarification.
9695c9fb2d6SNan Zhou // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
970e155ab54SNan Zhou // etc.
971e155ab54SNan Zhou inline void processSelect(crow::Response& intermediateResponse,
972827c4902SNan Zhou                           const SelectTrieNode& trieRoot)
973e155ab54SNan Zhou {
974e155ab54SNan Zhou     BMCWEB_LOG_DEBUG << "Process $select quary parameter";
975827c4902SNan Zhou     recursiveSelect(intermediateResponse.jsonValue, trieRoot);
976e155ab54SNan Zhou }
977e155ab54SNan Zhou 
9787cf436c9SEd Tanous inline void
979*32cdb4a7SWilly Tu     processAllParams(crow::App& app, const Query& query, const Query& delegated,
9807cf436c9SEd Tanous                      std::function<void(crow::Response&)>& completionHandler,
9817cf436c9SEd Tanous                      crow::Response& intermediateResponse)
982f4c99e70SEd Tanous {
983f4c99e70SEd Tanous     if (!completionHandler)
984f4c99e70SEd Tanous     {
985f4c99e70SEd Tanous         BMCWEB_LOG_DEBUG << "Function was invalid?";
986f4c99e70SEd Tanous         return;
987f4c99e70SEd Tanous     }
988f4c99e70SEd Tanous 
989f4c99e70SEd Tanous     BMCWEB_LOG_DEBUG << "Processing query params";
990f4c99e70SEd Tanous     // If the request failed, there's no reason to even try to run query
991f4c99e70SEd Tanous     // params.
992f4c99e70SEd Tanous     if (intermediateResponse.resultInt() < 200 ||
993f4c99e70SEd Tanous         intermediateResponse.resultInt() >= 400)
994f4c99e70SEd Tanous     {
995f4c99e70SEd Tanous         completionHandler(intermediateResponse);
996f4c99e70SEd Tanous         return;
997f4c99e70SEd Tanous     }
998f4c99e70SEd Tanous     if (query.isOnly)
999f4c99e70SEd Tanous     {
1000f4c99e70SEd Tanous         processOnly(app, intermediateResponse, completionHandler);
1001f4c99e70SEd Tanous         return;
1002f4c99e70SEd Tanous     }
10032a68dc80SEd Tanous 
10043648c8beSEd Tanous     if (query.top || query.skip)
10052a68dc80SEd Tanous     {
10062a68dc80SEd Tanous         processTopAndSkip(query, intermediateResponse);
10072a68dc80SEd Tanous     }
10082a68dc80SEd Tanous 
10097cf436c9SEd Tanous     if (query.expandType != ExpandType::None)
10107cf436c9SEd Tanous     {
10117cf436c9SEd Tanous         BMCWEB_LOG_DEBUG << "Executing expand query";
101213548d85SEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
101313548d85SEd Tanous             std::move(intermediateResponse));
10147cf436c9SEd Tanous 
101513548d85SEd Tanous         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
101613548d85SEd Tanous         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
1017*32cdb4a7SWilly Tu         multi->startQuery(query, delegated);
10187cf436c9SEd Tanous         return;
10197cf436c9SEd Tanous     }
1020e155ab54SNan Zhou 
1021e155ab54SNan Zhou     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
1022e155ab54SNan Zhou     // to process
1023827c4902SNan Zhou     if (!query.selectTrie.root.empty())
1024e155ab54SNan Zhou     {
1025827c4902SNan Zhou         processSelect(intermediateResponse, query.selectTrie.root);
1026e155ab54SNan Zhou     }
1027e155ab54SNan Zhou 
1028f4c99e70SEd Tanous     completionHandler(intermediateResponse);
1029f4c99e70SEd Tanous }
1030f4c99e70SEd Tanous 
1031f4c99e70SEd Tanous } // namespace query_param
1032f4c99e70SEd Tanous } // namespace redfish
1033