xref: /openbmc/bmcweb/features/redfish/include/utils/query_param.hpp (revision 1d8d9a3f8d6f7be032cae9f2d89c1048d7dfbadb)
1 #pragma once
2 #include "bmcweb_config.h"
3 
4 #include "app.hpp"
5 #include "async_resp.hpp"
6 #include "error_messages.hpp"
7 #include "http_request.hpp"
8 #include "http_response.hpp"
9 #include "logging.hpp"
10 
11 #include <sys/types.h>
12 
13 #include <boost/algorithm/string/classification.hpp>
14 #include <boost/algorithm/string/split.hpp>
15 #include <boost/beast/http/message.hpp> // IWYU pragma: keep
16 #include <boost/beast/http/status.hpp>
17 #include <boost/beast/http/verb.hpp>
18 #include <boost/url/error.hpp>
19 #include <boost/url/params_view.hpp>
20 #include <boost/url/string.hpp>
21 #include <nlohmann/json.hpp>
22 
23 #include <algorithm>
24 #include <array>
25 #include <cctype>
26 #include <charconv>
27 #include <cstdint>
28 #include <functional>
29 #include <iterator>
30 #include <limits>
31 #include <map>
32 #include <memory>
33 #include <optional>
34 #include <span>
35 #include <string>
36 #include <string_view>
37 #include <system_error>
38 #include <unordered_set>
39 #include <utility>
40 #include <vector>
41 
42 // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
43 // IWYU pragma: no_include <boost/beast/http/impl/message.hpp>
44 // IWYU pragma: no_include <boost/intrusive/detail/list_iterator.hpp>
45 // IWYU pragma: no_include <boost/algorithm/string/detail/classification.hpp>
46 // IWYU pragma: no_include <boost/iterator/iterator_facade.hpp>
47 // IWYU pragma: no_include <boost/type_index/type_index_facade.hpp>
48 // IWYU pragma: no_include <stdint.h>
49 
50 namespace redfish
51 {
52 namespace query_param
53 {
54 inline constexpr size_t maxEntriesPerPage = 1000;
55 
56 enum class ExpandType : uint8_t
57 {
58     None,
59     Links,
60     NotLinks,
61     Both,
62 };
63 
64 // The struct stores the parsed query parameters of the default Redfish route.
65 struct Query
66 {
67     // Only
68     bool isOnly = false;
69     // Expand
70     uint8_t expandLevel = 0;
71     ExpandType expandType = ExpandType::None;
72 
73     // Skip
74     std::optional<size_t> skip = std::nullopt;
75 
76     // Top
77 
78     std::optional<size_t> top = std::nullopt;
79 
80     // Select
81     std::unordered_set<std::string> selectedProperties = {};
82 };
83 
84 // The struct defines how resource handlers in redfish-core/lib/ can handle
85 // query parameters themselves, so that the default Redfish route will delegate
86 // the processing.
87 struct QueryCapabilities
88 {
89     bool canDelegateOnly = false;
90     bool canDelegateTop = false;
91     bool canDelegateSkip = false;
92     uint8_t canDelegateExpandLevel = 0;
93     bool canDelegateSelect = false;
94 };
95 
96 // Delegates query parameters according to the given |queryCapabilities|
97 // This function doesn't check query parameter conflicts since the parse
98 // function will take care of it.
99 // Returns a delegated query object which can be used by individual resource
100 // handlers so that handlers don't need to query again.
101 inline Query delegate(const QueryCapabilities& queryCapabilities, Query& query)
102 {
103     Query delegated;
104     // delegate only
105     if (query.isOnly && queryCapabilities.canDelegateOnly)
106     {
107         delegated.isOnly = true;
108         query.isOnly = false;
109     }
110     // delegate expand as much as we can
111     if (query.expandType != ExpandType::None)
112     {
113         delegated.expandType = query.expandType;
114         if (query.expandLevel <= queryCapabilities.canDelegateExpandLevel)
115         {
116             query.expandType = ExpandType::None;
117             delegated.expandLevel = query.expandLevel;
118             query.expandLevel = 0;
119         }
120         else
121         {
122             query.expandLevel -= queryCapabilities.canDelegateExpandLevel;
123             delegated.expandLevel = queryCapabilities.canDelegateExpandLevel;
124         }
125     }
126 
127     // delegate top
128     if (query.top && queryCapabilities.canDelegateTop)
129     {
130         delegated.top = query.top;
131         query.top = std::nullopt;
132     }
133 
134     // delegate skip
135     if (query.skip && queryCapabilities.canDelegateSkip)
136     {
137         delegated.skip = query.skip;
138         query.skip = 0;
139     }
140 
141     // delegate select
142     if (!query.selectedProperties.empty() &&
143         queryCapabilities.canDelegateSelect)
144     {
145         delegated.selectedProperties = std::move(query.selectedProperties);
146         query.selectedProperties.clear();
147     }
148     return delegated;
149 }
150 
151 inline bool getExpandType(std::string_view value, Query& query)
152 {
153     if (value.empty())
154     {
155         return false;
156     }
157     switch (value[0])
158     {
159         case '*':
160             query.expandType = ExpandType::Both;
161             break;
162         case '.':
163             query.expandType = ExpandType::NotLinks;
164             break;
165         case '~':
166             query.expandType = ExpandType::Links;
167             break;
168         default:
169             return false;
170 
171             break;
172     }
173     value.remove_prefix(1);
174     if (value.empty())
175     {
176         query.expandLevel = 1;
177         return true;
178     }
179     constexpr std::string_view levels = "($levels=";
180     if (!value.starts_with(levels))
181     {
182         return false;
183     }
184     value.remove_prefix(levels.size());
185 
186     auto it = std::from_chars(value.data(), value.data() + value.size(),
187                               query.expandLevel);
188     if (it.ec != std::errc())
189     {
190         return false;
191     }
192     value.remove_prefix(static_cast<size_t>(it.ptr - value.data()));
193     return value == ")";
194 }
195 
196 enum class QueryError
197 {
198     Ok,
199     OutOfRange,
200     ValueFormat,
201 };
202 
203 inline QueryError getNumericParam(std::string_view value, size_t& param)
204 {
205     std::from_chars_result r =
206         std::from_chars(value.data(), value.data() + value.size(), param);
207 
208     // If the number wasn't representable in the type, it's out of range
209     if (r.ec == std::errc::result_out_of_range)
210     {
211         return QueryError::OutOfRange;
212     }
213     // All other errors are value format
214     if (r.ec != std::errc())
215     {
216         return QueryError::ValueFormat;
217     }
218     return QueryError::Ok;
219 }
220 
221 inline QueryError getSkipParam(std::string_view value, Query& query)
222 {
223     return getNumericParam(value, query.skip.emplace());
224 }
225 
226 inline QueryError getTopParam(std::string_view value, Query& query)
227 {
228     QueryError ret = getNumericParam(value, query.top.emplace());
229     if (ret != QueryError::Ok)
230     {
231         return ret;
232     }
233 
234     // Range check for sanity.
235     if (query.top > maxEntriesPerPage)
236     {
237         return QueryError::OutOfRange;
238     }
239 
240     return QueryError::Ok;
241 }
242 
243 // Validates the property in the $select parameter. Every character is among
244 // [a-zA-Z0-9\/#@_.] (taken from Redfish spec, section 9.6 Properties)
245 inline bool isSelectedPropertyAllowed(std::string_view property)
246 {
247     // These a magic number, but with it it's less likely that this code
248     // introduces CVE; e.g., too large properties crash the service.
249     constexpr int maxPropertyLength = 60;
250     if (property.empty() || property.size() > maxPropertyLength)
251     {
252         return false;
253     }
254     for (char ch : property)
255     {
256         if (std::isalnum(static_cast<unsigned char>(ch)) == 0 && ch != '/' &&
257             ch != '#' && ch != '@' && ch != '.')
258         {
259             return false;
260         }
261     }
262     return true;
263 }
264 
265 // Parses and validates the $select parameter.
266 // As per OData URL Conventions and Redfish Spec, the $select values shall be
267 // comma separated Resource Path
268 // Ref:
269 // 1. https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
270 // 2.
271 // https://docs.oasis-open.org/odata/odata/v4.01/os/abnf/odata-abnf-construction-rules.txt
272 inline bool getSelectParam(std::string_view value, Query& query)
273 {
274     std::vector<std::string> properties;
275     boost::split(properties, value, boost::is_any_of(","));
276     if (properties.empty())
277     {
278         return false;
279     }
280     // These a magic number, but with it it's less likely that this code
281     // introduces CVE; e.g., too large properties crash the service.
282     constexpr int maxNumProperties = 10;
283     if (properties.size() > maxNumProperties)
284     {
285         return false;
286     }
287     for (std::string& property : properties)
288     {
289         if (!isSelectedPropertyAllowed(property))
290         {
291             return false;
292         }
293         property.insert(property.begin(), '/');
294     }
295     query.selectedProperties = {std::make_move_iterator(properties.begin()),
296                                 std::make_move_iterator(properties.end())};
297     // Per the Redfish spec section 7.3.3, the service shall select certain
298     // properties as if $select was omitted.
299     constexpr std::array<std::string_view, 5> reservedProperties = {
300         "/@odata.id", "/@odata.type", "/@odata.context", "/@odata.etag",
301         "/error"};
302     for (auto const& str : reservedProperties)
303     {
304         query.selectedProperties.emplace(std::string(str));
305     }
306     return true;
307 }
308 
309 inline std::optional<Query>
310     parseParameters(const boost::urls::params_view& urlParams,
311                     crow::Response& res)
312 {
313     Query ret;
314     for (const boost::urls::params_view::value_type& it : urlParams)
315     {
316         std::string_view key(it.key.data(), it.key.size());
317         std::string_view value(it.value.data(), it.value.size());
318         if (key == "only")
319         {
320             if (!it.value.empty())
321             {
322                 messages::queryParameterValueFormatError(res, value, key);
323                 return std::nullopt;
324             }
325             ret.isOnly = true;
326         }
327         else if (key == "$expand" && bmcwebInsecureEnableQueryParams)
328         {
329             if (!getExpandType(value, ret))
330             {
331                 messages::queryParameterValueFormatError(res, value, key);
332                 return std::nullopt;
333             }
334         }
335         else if (key == "$top")
336         {
337             QueryError topRet = getTopParam(value, ret);
338             if (topRet == QueryError::ValueFormat)
339             {
340                 messages::queryParameterValueFormatError(res, value, key);
341                 return std::nullopt;
342             }
343             if (topRet == QueryError::OutOfRange)
344             {
345                 messages::queryParameterOutOfRange(
346                     res, value, "$top",
347                     "0-" + std::to_string(maxEntriesPerPage));
348                 return std::nullopt;
349             }
350         }
351         else if (key == "$skip")
352         {
353             QueryError topRet = getSkipParam(value, ret);
354             if (topRet == QueryError::ValueFormat)
355             {
356                 messages::queryParameterValueFormatError(res, value, key);
357                 return std::nullopt;
358             }
359             if (topRet == QueryError::OutOfRange)
360             {
361                 messages::queryParameterOutOfRange(
362                     res, value, key,
363                     "0-" + std::to_string(std::numeric_limits<size_t>::max()));
364                 return std::nullopt;
365             }
366         }
367         else if (key == "$select" && bmcwebInsecureEnableQueryParams)
368         {
369             if (!getSelectParam(value, ret))
370             {
371                 messages::queryParameterValueFormatError(res, value, key);
372                 return std::nullopt;
373             }
374         }
375         else
376         {
377             // Intentionally ignore other errors Redfish spec, 7.3.1
378             if (key.starts_with("$"))
379             {
380                 // Services shall return... The HTTP 501 Not Implemented
381                 // status code for any unsupported query parameters that
382                 // start with $ .
383                 messages::queryParameterValueFormatError(res, value, key);
384                 res.result(boost::beast::http::status::not_implemented);
385                 return std::nullopt;
386             }
387             // "Shall ignore unknown or unsupported query parameters that do
388             // not begin with $ ."
389         }
390     }
391 
392     if (ret.expandType != ExpandType::None && !ret.selectedProperties.empty())
393     {
394         messages::queryCombinationInvalid(res);
395         return std::nullopt;
396     }
397 
398     return ret;
399 }
400 
401 inline bool processOnly(crow::App& app, crow::Response& res,
402                         std::function<void(crow::Response&)>& completionHandler)
403 {
404     BMCWEB_LOG_DEBUG << "Processing only query param";
405     auto itMembers = res.jsonValue.find("Members");
406     if (itMembers == res.jsonValue.end())
407     {
408         messages::queryNotSupportedOnResource(res);
409         completionHandler(res);
410         return false;
411     }
412     auto itMemBegin = itMembers->begin();
413     if (itMemBegin == itMembers->end() || itMembers->size() != 1)
414     {
415         BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
416                          << " element, returning full collection.";
417         completionHandler(res);
418         return false;
419     }
420 
421     auto itUrl = itMemBegin->find("@odata.id");
422     if (itUrl == itMemBegin->end())
423     {
424         BMCWEB_LOG_DEBUG << "No found odata.id";
425         messages::internalError(res);
426         completionHandler(res);
427         return false;
428     }
429     const std::string* url = itUrl->get_ptr<const std::string*>();
430     if (url == nullptr)
431     {
432         BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
433         messages::internalError(res);
434         completionHandler(res);
435         return false;
436     }
437     // TODO(Ed) copy request headers?
438     // newReq.session = req.session;
439     std::error_code ec;
440     crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
441     if (ec)
442     {
443         messages::internalError(res);
444         completionHandler(res);
445         return false;
446     }
447 
448     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
449     BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
450     asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
451     asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
452     app.handle(newReq, asyncResp);
453     return true;
454 }
455 
456 struct ExpandNode
457 {
458     nlohmann::json::json_pointer location;
459     std::string uri;
460 
461     inline bool operator==(const ExpandNode& other) const
462     {
463         return location == other.location && uri == other.uri;
464     }
465 };
466 
467 // Walks a json object looking for Redfish NavigationReference entries that
468 // might need resolved.  It recursively walks the jsonResponse object, looking
469 // for links at every level, and returns a list (out) of locations within the
470 // tree that need to be expanded.  The current json pointer location p is passed
471 // in to reference the current node that's being expanded, so it can be combined
472 // with the keys from the jsonResponse object
473 inline void findNavigationReferencesRecursive(
474     ExpandType eType, nlohmann::json& jsonResponse,
475     const nlohmann::json::json_pointer& p, bool inLinks,
476     std::vector<ExpandNode>& out)
477 {
478     // If no expand is needed, return early
479     if (eType == ExpandType::None)
480     {
481         return;
482     }
483     nlohmann::json::array_t* array =
484         jsonResponse.get_ptr<nlohmann::json::array_t*>();
485     if (array != nullptr)
486     {
487         size_t index = 0;
488         // For arrays, walk every element in the array
489         for (auto& element : *array)
490         {
491             nlohmann::json::json_pointer newPtr = p / index;
492             BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr.to_string();
493             findNavigationReferencesRecursive(eType, element, newPtr, inLinks,
494                                               out);
495             index++;
496         }
497     }
498     nlohmann::json::object_t* obj =
499         jsonResponse.get_ptr<nlohmann::json::object_t*>();
500     if (obj == nullptr)
501     {
502         return;
503     }
504     // Navigation References only ever have a single element
505     if (obj->size() == 1)
506     {
507         if (obj->begin()->first == "@odata.id")
508         {
509             const std::string* uri =
510                 obj->begin()->second.get_ptr<const std::string*>();
511             if (uri != nullptr)
512             {
513                 BMCWEB_LOG_DEBUG << "Found element at " << p.to_string();
514                 out.push_back({p, *uri});
515             }
516         }
517     }
518     // Loop the object and look for links
519     for (auto& element : *obj)
520     {
521         bool localInLinks = inLinks;
522         if (!localInLinks)
523         {
524             // Check if this is a links node
525             localInLinks = element.first == "Links";
526         }
527         // Only traverse the parts of the tree the user asked for
528         // Per section 7.3 of the redfish specification
529         if (localInLinks && eType == ExpandType::NotLinks)
530         {
531             continue;
532         }
533         if (!localInLinks && eType == ExpandType::Links)
534         {
535             continue;
536         }
537         nlohmann::json::json_pointer newPtr = p / element.first;
538         BMCWEB_LOG_DEBUG << "Traversing response at " << newPtr;
539 
540         findNavigationReferencesRecursive(eType, element.second, newPtr,
541                                           localInLinks, out);
542     }
543 }
544 
545 inline std::vector<ExpandNode>
546     findNavigationReferences(ExpandType eType, nlohmann::json& jsonResponse)
547 {
548     std::vector<ExpandNode> ret;
549     const nlohmann::json::json_pointer root = nlohmann::json::json_pointer("");
550     findNavigationReferencesRecursive(eType, jsonResponse, root, false, ret);
551     return ret;
552 }
553 
554 // Formats a query parameter string for the sub-query.
555 // Returns std::nullopt on failures.
556 // This function shall handle $select when it is added.
557 // There is no need to handle parameters that's not campatible with $expand,
558 // e.g., $only, since this function will only be called in side $expand handlers
559 inline std::optional<std::string> formatQueryForExpand(const Query& query)
560 {
561     // query.expandLevel<=1: no need to do subqueries
562     if (query.expandLevel <= 1)
563     {
564         return "";
565     }
566     std::string str = "?$expand=";
567     bool queryTypeExpected = false;
568     switch (query.expandType)
569     {
570         case ExpandType::None:
571             return "";
572         case ExpandType::Links:
573             queryTypeExpected = true;
574             str += '~';
575             break;
576         case ExpandType::NotLinks:
577             queryTypeExpected = true;
578             str += '.';
579             break;
580         case ExpandType::Both:
581             queryTypeExpected = true;
582             str += '*';
583             break;
584     }
585     if (!queryTypeExpected)
586     {
587         return std::nullopt;
588     }
589     str += "($levels=";
590     str += std::to_string(query.expandLevel - 1);
591     str += ')';
592     return str;
593 }
594 
595 class MultiAsyncResp : public std::enable_shared_from_this<MultiAsyncResp>
596 {
597   public:
598     // This object takes a single asyncResp object as the "final" one, then
599     // allows callers to attach sub-responses within the json tree that need
600     // to be executed and filled into their appropriate locations.  This
601     // class manages the final "merge" of the json resources.
602     MultiAsyncResp(crow::App& appIn,
603                    std::shared_ptr<bmcweb::AsyncResp> finalResIn) :
604         app(appIn),
605         finalRes(std::move(finalResIn))
606     {}
607 
608     void addAwaitingResponse(
609         const std::shared_ptr<bmcweb::AsyncResp>& res,
610         const nlohmann::json::json_pointer& finalExpandLocation)
611     {
612         res->res.setCompleteRequestHandler(std::bind_front(
613             placeResultStatic, shared_from_this(), finalExpandLocation));
614     }
615 
616     void placeResult(const nlohmann::json::json_pointer& locationToPlace,
617                      crow::Response& res)
618     {
619         nlohmann::json& finalObj = finalRes->res.jsonValue[locationToPlace];
620         finalObj = std::move(res.jsonValue);
621     }
622 
623     // Handles the very first level of Expand, and starts a chain of sub-queries
624     // for deeper levels.
625     void startQuery(const Query& query)
626     {
627         std::vector<ExpandNode> nodes =
628             findNavigationReferences(query.expandType, finalRes->res.jsonValue);
629         BMCWEB_LOG_DEBUG << nodes.size() << " nodes to traverse";
630         const std::optional<std::string> queryStr = formatQueryForExpand(query);
631         if (!queryStr)
632         {
633             messages::internalError(finalRes->res);
634             return;
635         }
636         for (const ExpandNode& node : nodes)
637         {
638             const std::string subQuery = node.uri + *queryStr;
639             BMCWEB_LOG_DEBUG << "URL of subquery:  " << subQuery;
640             std::error_code ec;
641             crow::Request newReq({boost::beast::http::verb::get, subQuery, 11},
642                                  ec);
643             if (ec)
644             {
645                 messages::internalError(finalRes->res);
646                 return;
647             }
648 
649             auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
650             BMCWEB_LOG_DEBUG << "setting completion handler on "
651                              << &asyncResp->res;
652 
653             addAwaitingResponse(asyncResp, node.location);
654             app.handle(newReq, asyncResp);
655         }
656     }
657 
658   private:
659     static void
660         placeResultStatic(const std::shared_ptr<MultiAsyncResp>& multi,
661                           const nlohmann::json::json_pointer& locationToPlace,
662                           crow::Response& res)
663     {
664         multi->placeResult(locationToPlace, res);
665     }
666 
667     crow::App& app;
668     std::shared_ptr<bmcweb::AsyncResp> finalRes;
669 };
670 
671 inline void processTopAndSkip(const Query& query, crow::Response& res)
672 {
673     if (!query.skip && !query.top)
674     {
675         // No work to do.
676         return;
677     }
678     nlohmann::json::object_t* obj =
679         res.jsonValue.get_ptr<nlohmann::json::object_t*>();
680     if (obj == nullptr)
681     {
682         // Shouldn't be possible.  All responses should be objects.
683         messages::internalError(res);
684         return;
685     }
686 
687     BMCWEB_LOG_DEBUG << "Handling top/skip";
688     nlohmann::json::object_t::iterator members = obj->find("Members");
689     if (members == obj->end())
690     {
691         // From the Redfish specification 7.3.1
692         // ... the HTTP 400 Bad Request status code with the
693         // QueryNotSupportedOnResource message from the Base Message Registry
694         // for any supported query parameters that apply only to resource
695         // collections but are used on singular resources.
696         messages::queryNotSupportedOnResource(res);
697         return;
698     }
699 
700     nlohmann::json::array_t* arr =
701         members->second.get_ptr<nlohmann::json::array_t*>();
702     if (arr == nullptr)
703     {
704         messages::internalError(res);
705         return;
706     }
707 
708     if (query.skip)
709     {
710         // Per section 7.3.1 of the Redfish specification, $skip is run before
711         // $top Can only skip as many values as we have
712         size_t skip = std::min(arr->size(), *query.skip);
713         arr->erase(arr->begin(), arr->begin() + static_cast<ssize_t>(skip));
714     }
715     if (query.top)
716     {
717         size_t top = std::min(arr->size(), *query.top);
718         arr->erase(arr->begin() + static_cast<ssize_t>(top), arr->end());
719     }
720 }
721 
722 // Given a JSON subtree |currRoot|, and its JSON pointer |currRootPtr| to the
723 // |root| JSON in the async response, this function erases leaves whose keys are
724 // not in the |shouldSelect| set.
725 // |shouldSelect| contains all the properties that needs to be selected.
726 inline void
727     recursiveSelect(nlohmann::json& currRoot,
728                     const nlohmann::json::json_pointer& currRootPtr,
729                     const std::unordered_set<std::string>& intermediatePaths,
730                     const std::unordered_set<std::string>& properties)
731 {
732     nlohmann::json::object_t* object =
733         currRoot.get_ptr<nlohmann::json::object_t*>();
734     if (object != nullptr)
735     {
736         BMCWEB_LOG_DEBUG << "Current JSON is an object: " << currRootPtr;
737         auto it = currRoot.begin();
738         while (it != currRoot.end())
739         {
740             auto nextIt = std::next(it);
741             nlohmann::json::json_pointer childPtr = currRootPtr / it.key();
742             BMCWEB_LOG_DEBUG << "childPtr=" << childPtr;
743             if (properties.contains(childPtr))
744             {
745                 it = nextIt;
746                 continue;
747             }
748             if (intermediatePaths.contains(childPtr))
749             {
750                 BMCWEB_LOG_DEBUG << "Recursively select: " << childPtr;
751                 recursiveSelect(*it, childPtr, intermediatePaths, properties);
752                 it = nextIt;
753                 continue;
754             }
755             BMCWEB_LOG_DEBUG << childPtr << " is getting removed!";
756             it = currRoot.erase(it);
757         }
758     }
759 }
760 
761 inline std::unordered_set<std::string>
762     getIntermediatePaths(const std::unordered_set<std::string>& properties)
763 {
764     std::unordered_set<std::string> res;
765     std::vector<std::string> segments;
766 
767     for (auto const& property : properties)
768     {
769         // Omit the root "/" and split all other segments
770         boost::split(segments, property.substr(1), boost::is_any_of("/"));
771         std::string path;
772         if (!segments.empty())
773         {
774             segments.pop_back();
775         }
776         for (auto const& segment : segments)
777         {
778             path += '/';
779             path += segment;
780             res.insert(path);
781         }
782     }
783     return res;
784 }
785 
786 inline void performSelect(nlohmann::json& root,
787                           const std::unordered_set<std::string>& properties)
788 {
789     std::unordered_set<std::string> intermediatePaths =
790         getIntermediatePaths(properties);
791     recursiveSelect(root, nlohmann::json::json_pointer(""), intermediatePaths,
792                     properties);
793 }
794 
795 // The current implementation of $select still has the following TODOs due to
796 //  ambiguity and/or complexity.
797 // 1. select properties in array of objects;
798 // https://github.com/DMTF/Redfish/issues/5188 was created for clarification.
799 // 2. combined with $expand; https://github.com/DMTF/Redfish/issues/5058 was
800 // created for clarification.
801 // 2. respect the full odata spec; e.g., deduplication, namespace, star (*),
802 // etc.
803 inline void processSelect(crow::Response& intermediateResponse,
804                           const std::unordered_set<std::string>& shouldSelect)
805 {
806     BMCWEB_LOG_DEBUG << "Process $select quary parameter";
807     performSelect(intermediateResponse.jsonValue, shouldSelect);
808 }
809 
810 inline void
811     processAllParams(crow::App& app, const Query& query,
812                      std::function<void(crow::Response&)>& completionHandler,
813                      crow::Response& intermediateResponse)
814 {
815     if (!completionHandler)
816     {
817         BMCWEB_LOG_DEBUG << "Function was invalid?";
818         return;
819     }
820 
821     BMCWEB_LOG_DEBUG << "Processing query params";
822     // If the request failed, there's no reason to even try to run query
823     // params.
824     if (intermediateResponse.resultInt() < 200 ||
825         intermediateResponse.resultInt() >= 400)
826     {
827         completionHandler(intermediateResponse);
828         return;
829     }
830     if (query.isOnly)
831     {
832         processOnly(app, intermediateResponse, completionHandler);
833         return;
834     }
835 
836     if (query.top || query.skip)
837     {
838         processTopAndSkip(query, intermediateResponse);
839     }
840 
841     if (query.expandType != ExpandType::None)
842     {
843         BMCWEB_LOG_DEBUG << "Executing expand query";
844         // TODO(ed) this is a copy of the response object.  Admittedly,
845         // we're inherently doing something inefficient, but we shouldn't
846         // have to do a full copy
847         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
848         asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
849         asyncResp->res.jsonValue = std::move(intermediateResponse.jsonValue);
850         auto multi = std::make_shared<MultiAsyncResp>(app, asyncResp);
851 
852         multi->startQuery(query);
853         return;
854     }
855 
856     // According to Redfish Spec Section 7.3.1, $select is the last parameter to
857     // to process
858     if (!query.selectedProperties.empty())
859     {
860         processSelect(intermediateResponse, query.selectedProperties);
861     }
862 
863     completionHandler(intermediateResponse);
864 }
865 
866 } // namespace query_param
867 } // namespace redfish
868