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