1 /*
2 Copyright (c) 2018 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 
18 #include "error_messages.hpp"
19 #include "http_connection.hpp"
20 #include "http_request.hpp"
21 #include "http_response.hpp"
22 #include "human_sort.hpp"
23 #include "logging.hpp"
24 
25 #include <boost/system/result.hpp>
26 #include <boost/url/parse.hpp>
27 #include <boost/url/url_view.hpp>
28 #include <nlohmann/json.hpp>
29 
30 #include <algorithm>
31 #include <array>
32 #include <cmath>
33 #include <cstddef>
34 #include <cstdint>
35 #include <limits>
36 #include <map>
37 #include <optional>
38 #include <ranges>
39 #include <span>
40 #include <string>
41 #include <string_view>
42 #include <type_traits>
43 #include <utility>
44 #include <variant>
45 #include <vector>
46 
47 // IWYU pragma: no_forward_declare crow::Request
48 
49 namespace redfish
50 {
51 
52 namespace json_util
53 {
54 
55 /**
56  * @brief Processes request to extract JSON from its body. If it fails, adds
57  *       MalformedJSON message to response and ends it.
58  *
59  * @param[io]  res       Response object
60  * @param[in]  req       Request object
61  * @param[out] reqJson   JSON object extracted from request's body
62  *
63  * @return true if JSON is valid, false when JSON is invalid and response has
64  *         been filled with message and ended.
65  */
66 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
67                             nlohmann::json& reqJson);
68 namespace details
69 {
70 
71 template <typename Type>
72 struct IsOptional : std::false_type
73 {};
74 
75 template <typename Type>
76 struct IsOptional<std::optional<Type>> : std::true_type
77 {};
78 
79 template <typename Type>
80 struct IsVector : std::false_type
81 {};
82 
83 template <typename Type>
84 struct IsVector<std::vector<Type>> : std::true_type
85 {};
86 
87 template <typename Type>
88 struct IsStdArray : std::false_type
89 {};
90 
91 template <typename Type, std::size_t size>
92 struct IsStdArray<std::array<Type, size>> : std::true_type
93 {};
94 
95 template <typename Type>
96 struct IsVariant : std::false_type
97 {};
98 
99 template <typename... Types>
100 struct IsVariant<std::variant<Types...>> : std::true_type
101 {};
102 
103 enum class UnpackErrorCode
104 {
105     success,
106     invalidType,
107     outOfRange
108 };
109 
110 template <typename ToType, typename FromType>
checkRange(const FromType & from,std::string_view key)111 bool checkRange(const FromType& from [[maybe_unused]],
112                 std::string_view key [[maybe_unused]])
113 {
114     if constexpr (std::is_floating_point_v<ToType>)
115     {
116         if (std::isnan(from))
117         {
118             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
119             return false;
120         }
121     }
122     if constexpr (std::numeric_limits<ToType>::max() <
123                   std::numeric_limits<FromType>::max())
124     {
125         if (from > std::numeric_limits<ToType>::max())
126         {
127             BMCWEB_LOG_DEBUG("Value for key {} was greater than max {}", key,
128                              std::numeric_limits<FromType>::max());
129             return false;
130         }
131     }
132     if constexpr (std::numeric_limits<ToType>::lowest() >
133                   std::numeric_limits<FromType>::lowest())
134     {
135         if (from < std::numeric_limits<ToType>::lowest())
136         {
137             BMCWEB_LOG_DEBUG("Value for key {} was less than min {}", key,
138                              std::numeric_limits<FromType>::lowest());
139             return false;
140         }
141     }
142 
143     return true;
144 }
145 
146 template <typename Type>
147 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
148                                          std::string_view key, Type& value);
149 
150 template <std::size_t Index = 0, typename... Args>
unpackValueVariant(nlohmann::json & j,std::string_view key,std::variant<Args...> & v)151 UnpackErrorCode unpackValueVariant(nlohmann::json& j, std::string_view key,
152                                    std::variant<Args...>& v)
153 {
154     if constexpr (Index < std::variant_size_v<std::variant<Args...>>)
155     {
156         std::variant_alternative_t<Index, std::variant<Args...>> type{};
157         UnpackErrorCode unpack = unpackValueWithErrorCode(j, key, type);
158         if (unpack == UnpackErrorCode::success)
159         {
160             v = std::move(type);
161             return unpack;
162         }
163 
164         return unpackValueVariant<Index + 1, Args...>(j, key, v);
165     }
166     return UnpackErrorCode::invalidType;
167 }
168 
169 template <typename Type>
unpackValueWithErrorCode(nlohmann::json & jsonValue,std::string_view key,Type & value)170 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
171                                          std::string_view key, Type& value)
172 {
173     UnpackErrorCode ret = UnpackErrorCode::success;
174 
175     if constexpr (std::is_floating_point_v<Type>)
176     {
177         double helper = 0;
178         double* jsonPtr = jsonValue.get_ptr<double*>();
179 
180         if (jsonPtr == nullptr)
181         {
182             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
183             if (intPtr != nullptr)
184             {
185                 helper = static_cast<double>(*intPtr);
186                 jsonPtr = &helper;
187             }
188         }
189         if (jsonPtr == nullptr)
190         {
191             return UnpackErrorCode::invalidType;
192         }
193         if (!checkRange<Type>(*jsonPtr, key))
194         {
195             return UnpackErrorCode::outOfRange;
196         }
197         value = static_cast<Type>(*jsonPtr);
198     }
199 
200     else if constexpr (std::is_signed_v<Type>)
201     {
202         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
203         if (jsonPtr == nullptr)
204         {
205             return UnpackErrorCode::invalidType;
206         }
207         if (!checkRange<Type>(*jsonPtr, key))
208         {
209             return UnpackErrorCode::outOfRange;
210         }
211         value = static_cast<Type>(*jsonPtr);
212     }
213 
214     else if constexpr ((std::is_unsigned_v<Type>) &&
215                        (!std::is_same_v<bool, Type>))
216     {
217         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
218         if (jsonPtr == nullptr)
219         {
220             return UnpackErrorCode::invalidType;
221         }
222         if (!checkRange<Type>(*jsonPtr, key))
223         {
224             return UnpackErrorCode::outOfRange;
225         }
226         value = static_cast<Type>(*jsonPtr);
227     }
228 
229     else if constexpr (std::is_same_v<nlohmann::json, Type>)
230     {
231         value = std::move(jsonValue);
232     }
233     else if constexpr (std::is_same_v<std::nullptr_t, Type>)
234     {
235         if (!jsonValue.is_null())
236         {
237             return UnpackErrorCode::invalidType;
238         }
239     }
240     else if constexpr (IsVector<Type>::value)
241     {
242         nlohmann::json::object_t* obj =
243             jsonValue.get_ptr<nlohmann::json::object_t*>();
244         if (obj == nullptr)
245         {
246             return UnpackErrorCode::invalidType;
247         }
248 
249         for (const auto& val : *obj)
250         {
251             value.emplace_back();
252             ret = unpackValueWithErrorCode<typename Type::value_type>(
253                       val, key, value.back()) &&
254                   ret;
255         }
256     }
257     else
258     {
259         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
260         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
261         if (jsonPtr == nullptr)
262         {
263             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
264                              jsonValue.type_name());
265             return UnpackErrorCode::invalidType;
266         }
267         value = std::move(*jsonPtr);
268     }
269     return ret;
270 }
271 
272 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,crow::Response & res,Type & value)273 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
274                  crow::Response& res, Type& value)
275 {
276     bool ret = true;
277 
278     if constexpr (IsOptional<Type>::value)
279     {
280         value.emplace();
281         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
282                                                      *value) &&
283               ret;
284     }
285     else if constexpr (IsStdArray<Type>::value)
286     {
287         nlohmann::json::array_t* arr =
288             jsonValue.get_ptr<nlohmann::json::array_t*>();
289         if (arr == nullptr)
290         {
291             messages::propertyValueTypeError(res, res.jsonValue, key);
292             return false;
293         }
294         if (jsonValue.size() != value.size())
295         {
296             messages::propertyValueTypeError(res, res.jsonValue, key);
297             return false;
298         }
299         size_t index = 0;
300         for (auto& val : *arr)
301         {
302             ret = unpackValue<typename Type::value_type>(val, key, res,
303                                                          value[index++]) &&
304                   ret;
305         }
306     }
307     else if constexpr (IsVector<Type>::value)
308     {
309         nlohmann::json::array_t* arr =
310             jsonValue.get_ptr<nlohmann::json::array_t*>();
311         if (arr == nullptr)
312         {
313             messages::propertyValueTypeError(res, res.jsonValue, key);
314             return false;
315         }
316 
317         for (auto& val : *arr)
318         {
319             value.emplace_back();
320             ret = unpackValue<typename Type::value_type>(val, key, res,
321                                                          value.back()) &&
322                   ret;
323         }
324     }
325     else if constexpr (IsVariant<Type>::value)
326     {
327         UnpackErrorCode ec = unpackValueVariant(jsonValue, key, value);
328         if (ec != UnpackErrorCode::success)
329         {
330             if (ec == UnpackErrorCode::invalidType)
331             {
332                 messages::propertyValueTypeError(res, jsonValue, key);
333             }
334             else if (ec == UnpackErrorCode::outOfRange)
335             {
336                 messages::propertyValueOutOfRange(res, jsonValue, key);
337             }
338             return false;
339         }
340     }
341     else
342     {
343         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
344         if (ec != UnpackErrorCode::success)
345         {
346             if (ec == UnpackErrorCode::invalidType)
347             {
348                 messages::propertyValueTypeError(res, jsonValue, key);
349             }
350             else if (ec == UnpackErrorCode::outOfRange)
351             {
352                 messages::propertyValueOutOfRange(res, jsonValue, key);
353             }
354             return false;
355         }
356     }
357 
358     return ret;
359 }
360 
361 template <typename Type>
unpackValue(nlohmann::json & jsonValue,std::string_view key,Type & value)362 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
363 {
364     bool ret = true;
365     if constexpr (IsOptional<Type>::value)
366     {
367         value.emplace();
368         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
369               ret;
370     }
371     else if constexpr (IsStdArray<Type>::value)
372     {
373         nlohmann::json::array_t* arr =
374             jsonValue.get_ptr<nlohmann::json::array_t*>();
375         if (arr == nullptr)
376         {
377             return false;
378         }
379         if (jsonValue.size() != value.size())
380         {
381             return false;
382         }
383         size_t index = 0;
384         for (const auto& val : *arr)
385         {
386             ret = unpackValue<typename Type::value_type>(val, key,
387                                                          value[index++]) &&
388                   ret;
389         }
390     }
391     else if constexpr (IsVector<Type>::value)
392     {
393         nlohmann::json::array_t* arr =
394             jsonValue.get_ptr<nlohmann::json::array_t*>();
395         if (arr == nullptr)
396         {
397             return false;
398         }
399 
400         for (const auto& val : *arr)
401         {
402             value.emplace_back();
403             ret = unpackValue<typename Type::value_type>(val, key,
404                                                          value.back()) &&
405                   ret;
406         }
407     }
408     else
409     {
410         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
411         if (ec != UnpackErrorCode::success)
412         {
413             return false;
414         }
415     }
416 
417     return ret;
418 }
419 } // namespace details
420 
421 // clang-format off
422 using UnpackVariant = std::variant<
423     uint8_t*,
424     uint16_t*,
425     int16_t*,
426     uint32_t*,
427     int32_t*,
428     uint64_t*,
429     int64_t*,
430     bool*,
431     double*,
432     std::string*,
433     nlohmann::json::object_t*,
434     std::variant<std::string, std::nullptr_t>*,
435     std::variant<uint8_t, std::nullptr_t>*,
436     std::variant<int16_t, std::nullptr_t>*,
437     std::variant<uint16_t, std::nullptr_t>*,
438     std::variant<int32_t, std::nullptr_t>*,
439     std::variant<uint32_t, std::nullptr_t>*,
440     std::variant<int64_t, std::nullptr_t>*,
441     std::variant<uint64_t, std::nullptr_t>*,
442     std::variant<double, std::nullptr_t>*,
443     std::variant<bool, std::nullptr_t>*,
444     std::vector<uint8_t>*,
445     std::vector<uint16_t>*,
446     std::vector<int16_t>*,
447     std::vector<uint32_t>*,
448     std::vector<int32_t>*,
449     std::vector<uint64_t>*,
450     std::vector<int64_t>*,
451     //std::vector<bool>*,
452     std::vector<double>*,
453     std::vector<std::string>*,
454     std::vector<nlohmann::json::object_t>*,
455     std::optional<uint8_t>*,
456     std::optional<uint16_t>*,
457     std::optional<int16_t>*,
458     std::optional<uint32_t>*,
459     std::optional<int32_t>*,
460     std::optional<uint64_t>*,
461     std::optional<int64_t>*,
462     std::optional<bool>*,
463     std::optional<double>*,
464     std::optional<std::string>*,
465     std::optional<nlohmann::json::object_t>*,
466     std::optional<std::vector<uint8_t>>*,
467     std::optional<std::vector<uint16_t>>*,
468     std::optional<std::vector<int16_t>>*,
469     std::optional<std::vector<uint32_t>>*,
470     std::optional<std::vector<int32_t>>*,
471     std::optional<std::vector<uint64_t>>*,
472     std::optional<std::vector<int64_t>>*,
473     //std::optional<std::vector<bool>>*,
474     std::optional<std::vector<double>>*,
475     std::optional<std::vector<std::string>>*,
476     std::optional<std::vector<nlohmann::json::object_t>>*,
477     std::optional<std::variant<std::string, std::nullptr_t>>*,
478     std::optional<std::variant<uint8_t, std::nullptr_t>>*,
479     std::optional<std::variant<int16_t, std::nullptr_t>>*,
480     std::optional<std::variant<uint16_t, std::nullptr_t>>*,
481     std::optional<std::variant<int32_t, std::nullptr_t>>*,
482     std::optional<std::variant<uint32_t, std::nullptr_t>>*,
483     std::optional<std::variant<int64_t, std::nullptr_t>>*,
484     std::optional<std::variant<uint64_t, std::nullptr_t>>*,
485     std::optional<std::variant<double, std::nullptr_t>>*,
486     std::optional<std::variant<bool, std::nullptr_t>>*,
487     std::optional<std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>>*,
488     std::optional<std::vector<std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>>>*,
489 
490     // Note, these types are kept for historical completeness, but should not be used,
491     // As they do not provide object type safety.  Instead, rely on nlohmann::json::object_t
492     // Will be removed Q2 2025
493     nlohmann::json*,
494     std::optional<std::vector<nlohmann::json>>*,
495     std::vector<nlohmann::json>*,
496     std::optional<nlohmann::json>*
497 >;
498 // clang-format on
499 
500 struct PerUnpack
501 {
502     std::string_view key;
503     UnpackVariant value;
504     bool complete = false;
505 };
506 
507 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
508                            std::span<PerUnpack> toUnpack);
509 
readJsonHelperObject(nlohmann::json::object_t & obj,crow::Response & res,std::span<PerUnpack> toUnpack)510 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
511                                  crow::Response& res,
512                                  std::span<PerUnpack> toUnpack)
513 {
514     bool result = true;
515     for (auto& item : obj)
516     {
517         size_t unpackIndex = 0;
518         for (; unpackIndex < toUnpack.size(); unpackIndex++)
519         {
520             PerUnpack& unpackSpec = toUnpack[unpackIndex];
521             std::string_view key = unpackSpec.key;
522             size_t keysplitIndex = key.find('/');
523             std::string_view leftover;
524             if (keysplitIndex != std::string_view::npos)
525             {
526                 leftover = key.substr(keysplitIndex + 1);
527                 key = key.substr(0, keysplitIndex);
528             }
529 
530             if (key != item.first || unpackSpec.complete)
531             {
532                 continue;
533             }
534 
535             // Sublevel key
536             if (!leftover.empty())
537             {
538                 // Include the slash in the key so we can compare later
539                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
540                 nlohmann::json j;
541                 result = details::unpackValue<nlohmann::json>(item.second, key,
542                                                               res, j) &&
543                          result;
544                 if (!result)
545                 {
546                     return result;
547                 }
548 
549                 std::vector<PerUnpack> nextLevel;
550                 for (PerUnpack& p : toUnpack)
551                 {
552                     if (!p.key.starts_with(key))
553                     {
554                         continue;
555                     }
556                     std::string_view thisLeftover = p.key.substr(key.size());
557                     nextLevel.push_back({thisLeftover, p.value, false});
558                     p.complete = true;
559                 }
560 
561                 result = readJsonHelper(j, res, nextLevel) && result;
562                 break;
563             }
564 
565             result =
566                 std::visit(
567                     [&item, &unpackSpec, &res](auto& val) {
568                         using ContainedT =
569                             std::remove_pointer_t<std::decay_t<decltype(val)>>;
570                         return details::unpackValue<ContainedT>(
571                             item.second, unpackSpec.key, res, *val);
572                     },
573                     unpackSpec.value) &&
574                 result;
575 
576             unpackSpec.complete = true;
577             break;
578         }
579 
580         if (unpackIndex == toUnpack.size())
581         {
582             messages::propertyUnknown(res, item.first);
583             result = false;
584         }
585     }
586 
587     for (PerUnpack& perUnpack : toUnpack)
588     {
589         if (!perUnpack.complete)
590         {
591             bool isOptional = std::visit(
592                 [](auto& val) {
593                     using ContainedType =
594                         std::remove_pointer_t<std::decay_t<decltype(val)>>;
595                     return details::IsOptional<ContainedType>::value;
596                 },
597                 perUnpack.value);
598             if (isOptional)
599             {
600                 continue;
601             }
602             messages::propertyMissing(res, perUnpack.key);
603             result = false;
604         }
605     }
606     return result;
607 }
608 
readJsonHelper(nlohmann::json & jsonRequest,crow::Response & res,std::span<PerUnpack> toUnpack)609 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
610                            std::span<PerUnpack> toUnpack)
611 {
612     nlohmann::json::object_t* obj =
613         jsonRequest.get_ptr<nlohmann::json::object_t*>();
614     if (obj == nullptr)
615     {
616         BMCWEB_LOG_DEBUG("Json value is not an object");
617         messages::unrecognizedRequestBody(res);
618         return false;
619     }
620     return readJsonHelperObject(*obj, res, toUnpack);
621 }
622 
packVariant(std::span<PerUnpack>)623 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
624 
625 template <typename FirstType, typename... UnpackTypes>
packVariant(std::span<PerUnpack> toPack,std::string_view key,FirstType && first,UnpackTypes &&...in)626 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
627                  FirstType&& first, UnpackTypes&&... in)
628 {
629     if (toPack.empty())
630     {
631         return;
632     }
633     toPack[0].key = key;
634     toPack[0].value = &first;
635     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
636     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
637 }
638 
639 template <typename FirstType, typename... UnpackTypes>
readJsonObject(nlohmann::json::object_t & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)640 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
641                     std::string_view key, FirstType&& first,
642                     UnpackTypes&&... in)
643 {
644     const std::size_t n = sizeof...(UnpackTypes) + 2;
645     std::array<PerUnpack, n / 2> toUnpack2;
646     packVariant(toUnpack2, key, std::forward<FirstType>(first),
647                 std::forward<UnpackTypes&&>(in)...);
648     return readJsonHelperObject(jsonRequest, res, toUnpack2);
649 }
650 
651 template <typename FirstType, typename... UnpackTypes>
readJson(nlohmann::json & jsonRequest,crow::Response & res,std::string_view key,FirstType && first,UnpackTypes &&...in)652 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
653               std::string_view key, FirstType&& first, UnpackTypes&&... in)
654 {
655     nlohmann::json::object_t* obj =
656         jsonRequest.get_ptr<nlohmann::json::object_t*>();
657     if (obj == nullptr)
658     {
659         BMCWEB_LOG_DEBUG("Json value is not an object");
660         messages::unrecognizedRequestBody(res);
661         return false;
662     }
663     return readJsonObject(*obj, res, key, std::forward<FirstType>(first),
664                           std::forward<UnpackTypes&&>(in)...);
665 }
666 
667 inline std::optional<nlohmann::json::object_t>
readJsonPatchHelper(const crow::Request & req,crow::Response & res)668     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
669 {
670     nlohmann::json jsonRequest;
671     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
672     {
673         BMCWEB_LOG_DEBUG("Json value not readable");
674         return std::nullopt;
675     }
676     nlohmann::json::object_t* object =
677         jsonRequest.get_ptr<nlohmann::json::object_t*>();
678     if (object == nullptr || object->empty())
679     {
680         BMCWEB_LOG_DEBUG("Json value is empty");
681         messages::emptyJSON(res);
682         return std::nullopt;
683     }
684     std::erase_if(*object,
685                   [](const std::pair<std::string, nlohmann::json>& item) {
686                       return item.first.starts_with("@odata.");
687                   });
688     if (object->empty())
689     {
690         //  If the update request only contains OData annotations, the service
691         //  should return the HTTP 400 Bad Request status code with the
692         //  NoOperation message from the Base Message Registry, ...
693         messages::noOperation(res);
694         return std::nullopt;
695     }
696 
697     return {std::move(*object)};
698 }
699 
700 template <typename... UnpackTypes>
readJsonPatch(const crow::Request & req,crow::Response & res,std::string_view key,UnpackTypes &&...in)701 bool readJsonPatch(const crow::Request& req, crow::Response& res,
702                    std::string_view key, UnpackTypes&&... in)
703 {
704     std::optional<nlohmann::json::object_t> jsonRequest =
705         readJsonPatchHelper(req, res);
706     if (!jsonRequest)
707     {
708         return false;
709     }
710     if (jsonRequest->empty())
711     {
712         messages::emptyJSON(res);
713         return false;
714     }
715 
716     return readJsonObject(*jsonRequest, res, key,
717                           std::forward<UnpackTypes&&>(in)...);
718 }
719 
720 template <typename... UnpackTypes>
readJsonAction(const crow::Request & req,crow::Response & res,const char * key,UnpackTypes &&...in)721 bool readJsonAction(const crow::Request& req, crow::Response& res,
722                     const char* key, UnpackTypes&&... in)
723 {
724     nlohmann::json jsonRequest;
725     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
726     {
727         BMCWEB_LOG_DEBUG("Json value not readable");
728         return false;
729     }
730     nlohmann::json::object_t* object =
731         jsonRequest.get_ptr<nlohmann::json::object_t*>();
732     if (object == nullptr)
733     {
734         BMCWEB_LOG_DEBUG("Json value is empty");
735         messages::emptyJSON(res);
736         return false;
737     }
738     return readJsonObject(*object, res, key,
739                           std::forward<UnpackTypes&&>(in)...);
740 }
741 
742 // Determines if two json objects are less, based on the presence of the
743 // @odata.id key
objectKeyCmp(std::string_view key,const nlohmann::json & a,const nlohmann::json & b)744 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a,
745                         const nlohmann::json& b)
746 {
747     using object_t = nlohmann::json::object_t;
748     const object_t* aObj = a.get_ptr<const object_t*>();
749     const object_t* bObj = b.get_ptr<const object_t*>();
750 
751     if (aObj == nullptr)
752     {
753         if (bObj == nullptr)
754         {
755             return 0;
756         }
757         return -1;
758     }
759     if (bObj == nullptr)
760     {
761         return 1;
762     }
763     object_t::const_iterator aIt = aObj->find(key);
764     object_t::const_iterator bIt = bObj->find(key);
765     // If either object doesn't have the key, they get "sorted" to the
766     // beginning.
767     if (aIt == aObj->end())
768     {
769         if (bIt == bObj->end())
770         {
771             return 0;
772         }
773         return -1;
774     }
775     if (bIt == bObj->end())
776     {
777         return 1;
778     }
779     const nlohmann::json::string_t* nameA =
780         aIt->second.get_ptr<const std::string*>();
781     const nlohmann::json::string_t* nameB =
782         bIt->second.get_ptr<const std::string*>();
783     // If either object doesn't have a string as the key, they get "sorted" to
784     // the beginning.
785     if (nameA == nullptr)
786     {
787         if (nameB == nullptr)
788         {
789             return 0;
790         }
791         return -1;
792     }
793     if (nameB == nullptr)
794     {
795         return 1;
796     }
797     if (key != "@odata.id")
798     {
799         return alphanumComp(*nameA, *nameB);
800     }
801 
802     boost::system::result<boost::urls::url_view> aUrl =
803         boost::urls::parse_relative_ref(*nameA);
804     boost::system::result<boost::urls::url_view> bUrl =
805         boost::urls::parse_relative_ref(*nameB);
806     if (!aUrl)
807     {
808         if (!bUrl)
809         {
810             return 0;
811         }
812         return -1;
813     }
814     if (!bUrl)
815     {
816         return 1;
817     }
818 
819     auto segmentsAIt = aUrl->segments().begin();
820     auto segmentsBIt = bUrl->segments().begin();
821 
822     while (true)
823     {
824         if (segmentsAIt == aUrl->segments().end())
825         {
826             if (segmentsBIt == bUrl->segments().end())
827             {
828                 return 0;
829             }
830             return -1;
831         }
832         if (segmentsBIt == bUrl->segments().end())
833         {
834             return 1;
835         }
836         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
837         if (res != 0)
838         {
839             return res;
840         }
841 
842         segmentsAIt++;
843         segmentsBIt++;
844     }
845     return 0;
846 };
847 
848 // kept for backward compatibility
odataObjectCmp(const nlohmann::json & left,const nlohmann::json & right)849 inline int odataObjectCmp(const nlohmann::json& left,
850                           const nlohmann::json& right)
851 {
852     return objectKeyCmp("@odata.id", left, right);
853 }
854 
855 struct ODataObjectLess
856 {
857     std::string_view key;
858 
ODataObjectLessredfish::json_util::ODataObjectLess859     explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {}
860 
operator ()redfish::json_util::ODataObjectLess861     bool operator()(const nlohmann::json& left,
862                     const nlohmann::json& right) const
863     {
864         return objectKeyCmp(key, left, right) < 0;
865     }
866 };
867 
868 // Sort the JSON array by |element[key]|.
869 // Elements without |key| or type of |element[key]| is not string are smaller
870 // those whose |element[key]| is string.
sortJsonArrayByKey(nlohmann::json::array_t & array,std::string_view key)871 inline void sortJsonArrayByKey(nlohmann::json::array_t& array,
872                                std::string_view key)
873 {
874     std::ranges::sort(array, ODataObjectLess(key));
875 }
876 
877 // Sort the JSON array by |element[key]|.
878 // Elements without |key| or type of |element[key]| is not string are smaller
879 // those whose |element[key]| is string.
sortJsonArrayByOData(nlohmann::json::array_t & array)880 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
881 {
882     std::ranges::sort(array, ODataObjectLess("@odata.id"));
883 }
884 
885 // Returns the estimated size of the JSON value
886 // The implementation walks through every key and every value, accumulates the
887 //  total size of keys and values.
888 // Ideally, we should use a custom allocator that nlohmann JSON supports.
889 
890 // Assumption made:
891 //  1. number: 8 characters
892 //  2. boolean: 5 characters (False)
893 //  3. string: len(str) + 2 characters (quote)
894 //  4. bytes: len(bytes) characters
895 //  5. null: 4 characters (null)
896 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
897 
898 } // namespace json_util
899 } // namespace redfish
900