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> 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> 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> 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> 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> 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 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 // Note, these types are kept for historical completeness, but should not be used, 507 // As they do not provide object type safety. Instead, rely on nlohmann::json::object_t 508 // Will be removed Q2 2025 509 nlohmann::json*, 510 std::optional<std::vector<nlohmann::json>>*, 511 std::vector<nlohmann::json>*, 512 std::optional<nlohmann::json>* 513 >; 514 // clang-format on 515 516 struct PerUnpack 517 { 518 std::string_view key; 519 UnpackVariant value; 520 bool complete = false; 521 }; 522 523 inline bool readJsonHelperObject(nlohmann::json::object_t& obj, 524 crow::Response& res, 525 std::span<PerUnpack> toUnpack) 526 { 527 bool result = true; 528 for (auto& item : obj) 529 { 530 size_t unpackIndex = 0; 531 for (; unpackIndex < toUnpack.size(); unpackIndex++) 532 { 533 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 534 std::string_view key = unpackSpec.key; 535 size_t keysplitIndex = key.find('/'); 536 std::string_view leftover; 537 if (keysplitIndex != std::string_view::npos) 538 { 539 leftover = key.substr(keysplitIndex + 1); 540 key = key.substr(0, keysplitIndex); 541 } 542 543 if (key != item.first || unpackSpec.complete) 544 { 545 continue; 546 } 547 548 // Sublevel key 549 if (!leftover.empty()) 550 { 551 // Include the slash in the key so we can compare later 552 key = unpackSpec.key.substr(0, keysplitIndex + 1); 553 nlohmann::json::object_t j; 554 result = details::unpackValue<nlohmann::json::object_t>( 555 item.second, key, res, j) && 556 result; 557 if (!result) 558 { 559 return result; 560 } 561 562 std::vector<PerUnpack> nextLevel; 563 for (PerUnpack& p : toUnpack) 564 { 565 if (!p.key.starts_with(key)) 566 { 567 continue; 568 } 569 std::string_view thisLeftover = p.key.substr(key.size()); 570 nextLevel.push_back({thisLeftover, p.value, false}); 571 p.complete = true; 572 } 573 574 result = readJsonHelperObject(j, res, nextLevel) && result; 575 break; 576 } 577 578 result = 579 std::visit( 580 [&item, &unpackSpec, &res](auto& val) { 581 using ContainedT = 582 std::remove_pointer_t<std::decay_t<decltype(val)>>; 583 return details::unpackValue<ContainedT>( 584 item.second, unpackSpec.key, res, *val); 585 }, 586 unpackSpec.value) && 587 result; 588 589 unpackSpec.complete = true; 590 break; 591 } 592 593 if (unpackIndex == toUnpack.size()) 594 { 595 messages::propertyUnknown(res, item.first); 596 result = false; 597 } 598 } 599 600 for (PerUnpack& perUnpack : toUnpack) 601 { 602 if (!perUnpack.complete) 603 { 604 bool isOptional = std::visit( 605 [](auto& val) { 606 using ContainedType = 607 std::remove_pointer_t<std::decay_t<decltype(val)>>; 608 return details::IsOptional<ContainedType>::value; 609 }, 610 perUnpack.value); 611 if (isOptional) 612 { 613 continue; 614 } 615 messages::propertyMissing(res, perUnpack.key); 616 result = false; 617 } 618 } 619 return result; 620 } 621 622 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 623 624 template <typename FirstType, typename... UnpackTypes> 625 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 626 FirstType&& first, UnpackTypes&&... in) 627 { 628 if (toPack.empty()) 629 { 630 return; 631 } 632 toPack[0].key = key; 633 toPack[0].value = &first; 634 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 635 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 636 } 637 638 template <typename FirstType, typename... UnpackTypes> 639 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, 640 std::string_view key, FirstType&& first, 641 UnpackTypes&&... in) 642 { 643 const std::size_t n = sizeof...(UnpackTypes) + 2; 644 std::array<PerUnpack, n / 2> toUnpack2; 645 packVariant(toUnpack2, key, std::forward<FirstType>(first), 646 std::forward<UnpackTypes&&>(in)...); 647 return readJsonHelperObject(jsonRequest, res, toUnpack2); 648 } 649 650 template <typename FirstType, typename... UnpackTypes> 651 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 652 std::string_view key, FirstType&& first, UnpackTypes&&... in) 653 { 654 nlohmann::json::object_t* obj = 655 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 656 if (obj == nullptr) 657 { 658 BMCWEB_LOG_DEBUG("Json value is not an object"); 659 messages::unrecognizedRequestBody(res); 660 return false; 661 } 662 return readJsonObject(*obj, res, key, std::forward<FirstType>(first), 663 std::forward<UnpackTypes&&>(in)...); 664 } 665 666 inline std::optional<nlohmann::json::object_t> readJsonPatchHelper( 667 const crow::Request& req, crow::Response& res) 668 { 669 nlohmann::json jsonRequest; 670 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 671 { 672 BMCWEB_LOG_DEBUG("Json value not readable"); 673 return std::nullopt; 674 } 675 nlohmann::json::object_t* object = 676 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 677 if (object == nullptr || object->empty()) 678 { 679 BMCWEB_LOG_DEBUG("Json value is empty"); 680 messages::emptyJSON(res); 681 return std::nullopt; 682 } 683 std::erase_if(*object, 684 [](const std::pair<std::string, nlohmann::json>& item) { 685 return item.first.starts_with("@odata."); 686 }); 687 if (object->empty()) 688 { 689 // If the update request only contains OData annotations, the service 690 // should return the HTTP 400 Bad Request status code with the 691 // NoOperation message from the Base Message Registry, ... 692 messages::noOperation(res); 693 return std::nullopt; 694 } 695 696 return {std::move(*object)}; 697 } 698 699 inline const nlohmann::json* findNestedKey(std::string_view key, 700 const nlohmann::json& value) 701 { 702 size_t keysplitIndex = key.find('/'); 703 std::string_view leftover; 704 nlohmann::json::const_iterator it; 705 if (keysplitIndex != std::string_view::npos) 706 { 707 const nlohmann::json::object_t* obj = 708 value.get_ptr<const nlohmann::json::object_t*>(); 709 if (obj == nullptr || obj->empty()) 710 { 711 BMCWEB_LOG_ERROR("Requested key wasn't an object"); 712 return nullptr; 713 } 714 715 leftover = key.substr(keysplitIndex + 1); 716 std::string_view keypart = key.substr(0, keysplitIndex); 717 it = value.find(keypart); 718 if (it == value.end()) 719 { 720 // Entry didn't have key 721 return nullptr; 722 } 723 return findNestedKey(leftover, it.value()); 724 } 725 726 it = value.find(key); 727 if (it == value.end()) 728 { 729 return nullptr; 730 } 731 return &*it; 732 } 733 734 template <typename... UnpackTypes> 735 bool readJsonPatch(const crow::Request& req, crow::Response& res, 736 std::string_view key, UnpackTypes&&... in) 737 { 738 std::optional<nlohmann::json::object_t> jsonRequest = 739 readJsonPatchHelper(req, res); 740 if (!jsonRequest) 741 { 742 return false; 743 } 744 if (jsonRequest->empty()) 745 { 746 messages::emptyJSON(res); 747 return false; 748 } 749 750 return readJsonObject(*jsonRequest, res, key, 751 std::forward<UnpackTypes&&>(in)...); 752 } 753 754 inline std::optional<nlohmann::json::json_pointer> 755 createJsonPointerFromFragment(std::string_view input) 756 { 757 auto hashPos = input.find('#'); 758 if (hashPos == std::string_view::npos || hashPos + 1 >= input.size()) 759 { 760 BMCWEB_LOG_ERROR( 761 "createJsonPointerFromFragment() No fragment found after #"); 762 return std::nullopt; 763 } 764 765 std::string_view fragment = input.substr(hashPos + 1); 766 return nlohmann::json::json_pointer(std::string(fragment)); 767 } 768 769 template <typename... UnpackTypes> 770 bool readJsonAction(const crow::Request& req, crow::Response& res, 771 const char* key, UnpackTypes&&... in) 772 { 773 nlohmann::json jsonRequest; 774 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 775 { 776 BMCWEB_LOG_DEBUG("Json value not readable"); 777 return false; 778 } 779 nlohmann::json::object_t* object = 780 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 781 if (object == nullptr) 782 { 783 BMCWEB_LOG_DEBUG("Json value is empty"); 784 messages::emptyJSON(res); 785 return false; 786 } 787 return readJsonObject(*object, res, key, 788 std::forward<UnpackTypes&&>(in)...); 789 } 790 791 // Determines if two json objects are less, based on the presence of the 792 // @odata.id key 793 inline int objectKeyCmp(std::string_view key, const nlohmann::json& a, 794 const nlohmann::json& b) 795 { 796 using object_t = nlohmann::json::object_t; 797 const object_t* aObj = a.get_ptr<const object_t*>(); 798 const object_t* bObj = b.get_ptr<const object_t*>(); 799 800 if (aObj == nullptr) 801 { 802 if (bObj == nullptr) 803 { 804 return 0; 805 } 806 return -1; 807 } 808 if (bObj == nullptr) 809 { 810 return 1; 811 } 812 object_t::const_iterator aIt = aObj->find(key); 813 object_t::const_iterator bIt = bObj->find(key); 814 // If either object doesn't have the key, they get "sorted" to the 815 // beginning. 816 if (aIt == aObj->end()) 817 { 818 if (bIt == bObj->end()) 819 { 820 return 0; 821 } 822 return -1; 823 } 824 if (bIt == bObj->end()) 825 { 826 return 1; 827 } 828 const nlohmann::json::string_t* nameA = 829 aIt->second.get_ptr<const std::string*>(); 830 const nlohmann::json::string_t* nameB = 831 bIt->second.get_ptr<const std::string*>(); 832 // If either object doesn't have a string as the key, they get "sorted" to 833 // the beginning. 834 if (nameA == nullptr) 835 { 836 if (nameB == nullptr) 837 { 838 return 0; 839 } 840 return -1; 841 } 842 if (nameB == nullptr) 843 { 844 return 1; 845 } 846 if (key != "@odata.id") 847 { 848 return alphanumComp(*nameA, *nameB); 849 } 850 851 boost::system::result<boost::urls::url_view> aUrl = 852 boost::urls::parse_relative_ref(*nameA); 853 boost::system::result<boost::urls::url_view> bUrl = 854 boost::urls::parse_relative_ref(*nameB); 855 if (!aUrl) 856 { 857 if (!bUrl) 858 { 859 return 0; 860 } 861 return -1; 862 } 863 if (!bUrl) 864 { 865 return 1; 866 } 867 868 auto segmentsAIt = aUrl->segments().begin(); 869 auto segmentsBIt = bUrl->segments().begin(); 870 871 while (true) 872 { 873 if (segmentsAIt == aUrl->segments().end()) 874 { 875 if (segmentsBIt == bUrl->segments().end()) 876 { 877 return 0; 878 } 879 return -1; 880 } 881 if (segmentsBIt == bUrl->segments().end()) 882 { 883 return 1; 884 } 885 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 886 if (res != 0) 887 { 888 return res; 889 } 890 891 segmentsAIt++; 892 segmentsBIt++; 893 } 894 return 0; 895 }; 896 897 // kept for backward compatibility 898 inline int odataObjectCmp(const nlohmann::json& left, 899 const nlohmann::json& right) 900 { 901 return objectKeyCmp("@odata.id", left, right); 902 } 903 904 struct ODataObjectLess 905 { 906 std::string_view key; 907 908 explicit ODataObjectLess(std::string_view keyIn) : key(keyIn) {} 909 910 bool operator()(const nlohmann::json& left, 911 const nlohmann::json& right) const 912 { 913 return objectKeyCmp(key, left, right) < 0; 914 } 915 }; 916 917 // Sort the JSON array by |element[key]|. 918 // Elements without |key| or type of |element[key]| is not string are smaller 919 // those whose |element[key]| is string. 920 inline void sortJsonArrayByKey(nlohmann::json::array_t& array, 921 std::string_view key) 922 { 923 std::ranges::sort(array, ODataObjectLess(key)); 924 } 925 926 // Sort the JSON array by |element[key]|. 927 // Elements without |key| or type of |element[key]| is not string are smaller 928 // those whose |element[key]| is string. 929 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 930 { 931 std::ranges::sort(array, ODataObjectLess("@odata.id")); 932 } 933 934 // Returns the estimated size of the JSON value 935 // The implementation walks through every key and every value, accumulates the 936 // total size of keys and values. 937 // Ideally, we should use a custom allocator that nlohmann JSON supports. 938 939 // Assumption made: 940 // 1. number: 8 characters 941 // 2. boolean: 5 characters (False) 942 // 3. string: len(str) + 2 characters (quote) 943 // 4. bytes: len(bytes) characters 944 // 5. null: 4 characters (null) 945 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 946 947 // Hashes a json value, recursively omitting every member with key `keyToIgnore` 948 inline size_t hashJsonWithoutKey(const nlohmann::json& jsonValue, 949 std::string_view keyToIgnore) 950 { 951 const nlohmann::json::object_t* obj = 952 jsonValue.get_ptr<const nlohmann::json::object_t*>(); 953 if (obj == nullptr) 954 { 955 // Object has no keys to remove so just return hash 956 return std::hash<nlohmann::json>{}(jsonValue); 957 } 958 959 const size_t type = static_cast<std::size_t>(jsonValue.type()); 960 size_t seed = details::combine(type, jsonValue.size()); 961 for (const auto& element : *obj) 962 { 963 const size_t h = std::hash<std::string>{}(element.first); 964 seed = details::combine(seed, h); 965 if (element.first != keyToIgnore) 966 { 967 seed = details::combine( 968 seed, std::hash<nlohmann::json>{}(element.second)); 969 } 970 } 971 return seed; 972 } 973 974 } // namespace json_util 975 } // namespace redfish 976