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 <nlohmann/json.hpp> 26 27 #include <algorithm> 28 #include <array> 29 #include <cmath> 30 #include <cstddef> 31 #include <cstdint> 32 #include <limits> 33 #include <map> 34 #include <optional> 35 #include <ranges> 36 #include <span> 37 #include <string> 38 #include <string_view> 39 #include <type_traits> 40 #include <utility> 41 #include <variant> 42 #include <vector> 43 44 // IWYU pragma: no_include <stdint.h> 45 // IWYU pragma: no_forward_declare crow::Request 46 47 namespace redfish 48 { 49 50 namespace json_util 51 { 52 53 /** 54 * @brief Processes request to extract JSON from its body. If it fails, adds 55 * MalformedJSON message to response and ends it. 56 * 57 * @param[io] res Response object 58 * @param[in] req Request object 59 * @param[out] reqJson JSON object extracted from request's body 60 * 61 * @return true if JSON is valid, false when JSON is invalid and response has 62 * been filled with message and ended. 63 */ 64 bool processJsonFromRequest(crow::Response& res, const crow::Request& req, 65 nlohmann::json& reqJson); 66 namespace details 67 { 68 69 template <typename Type> 70 struct IsOptional : std::false_type 71 {}; 72 73 template <typename Type> 74 struct IsOptional<std::optional<Type>> : std::true_type 75 {}; 76 77 template <typename Type> 78 struct IsVector : std::false_type 79 {}; 80 81 template <typename Type> 82 struct IsVector<std::vector<Type>> : std::true_type 83 {}; 84 85 template <typename Type> 86 struct IsStdArray : std::false_type 87 {}; 88 89 template <typename Type, std::size_t size> 90 struct IsStdArray<std::array<Type, size>> : std::true_type 91 {}; 92 93 enum class UnpackErrorCode 94 { 95 success, 96 invalidType, 97 outOfRange 98 }; 99 100 template <typename ToType, typename FromType> 101 bool checkRange(const FromType& from, std::string_view key) 102 { 103 if (from > std::numeric_limits<ToType>::max()) 104 { 105 BMCWEB_LOG_DEBUG("Value for key {} was greater than max: {}", key, 106 __PRETTY_FUNCTION__); 107 return false; 108 } 109 if (from < std::numeric_limits<ToType>::lowest()) 110 { 111 BMCWEB_LOG_DEBUG("Value for key {} was less than min: {}", key, 112 __PRETTY_FUNCTION__); 113 return false; 114 } 115 if constexpr (std::is_floating_point_v<ToType>) 116 { 117 if (std::isnan(from)) 118 { 119 BMCWEB_LOG_DEBUG("Value for key {} was NAN", key); 120 return false; 121 } 122 } 123 124 return true; 125 } 126 127 template <typename Type> 128 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 129 std::string_view key, Type& value) 130 { 131 UnpackErrorCode ret = UnpackErrorCode::success; 132 133 if constexpr (std::is_floating_point_v<Type>) 134 { 135 double helper = 0; 136 double* jsonPtr = jsonValue.get_ptr<double*>(); 137 138 if (jsonPtr == nullptr) 139 { 140 int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); 141 if (intPtr != nullptr) 142 { 143 helper = static_cast<double>(*intPtr); 144 jsonPtr = &helper; 145 } 146 } 147 if (jsonPtr == nullptr) 148 { 149 return UnpackErrorCode::invalidType; 150 } 151 if (!checkRange<Type>(*jsonPtr, key)) 152 { 153 return UnpackErrorCode::outOfRange; 154 } 155 value = static_cast<Type>(*jsonPtr); 156 } 157 158 else if constexpr (std::is_signed_v<Type>) 159 { 160 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); 161 if (jsonPtr == nullptr) 162 { 163 return UnpackErrorCode::invalidType; 164 } 165 if (!checkRange<Type>(*jsonPtr, key)) 166 { 167 return UnpackErrorCode::outOfRange; 168 } 169 value = static_cast<Type>(*jsonPtr); 170 } 171 172 else if constexpr ((std::is_unsigned_v<Type>)&&( 173 !std::is_same_v<bool, Type>)) 174 { 175 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); 176 if (jsonPtr == nullptr) 177 { 178 return UnpackErrorCode::invalidType; 179 } 180 if (!checkRange<Type>(*jsonPtr, key)) 181 { 182 return UnpackErrorCode::outOfRange; 183 } 184 value = static_cast<Type>(*jsonPtr); 185 } 186 187 else if constexpr (std::is_same_v<nlohmann::json, Type>) 188 { 189 value = std::move(jsonValue); 190 } 191 else 192 { 193 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 194 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 195 if (jsonPtr == nullptr) 196 { 197 BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key, 198 jsonValue.type_name()); 199 return UnpackErrorCode::invalidType; 200 } 201 value = std::move(*jsonPtr); 202 } 203 return ret; 204 } 205 206 template <typename Type> 207 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, 208 crow::Response& res, Type& value) 209 { 210 bool ret = true; 211 212 if constexpr (IsOptional<Type>::value) 213 { 214 value.emplace(); 215 ret = unpackValue<typename Type::value_type>(jsonValue, key, res, 216 *value) && 217 ret; 218 } 219 else if constexpr (IsStdArray<Type>::value) 220 { 221 if (!jsonValue.is_array()) 222 { 223 messages::propertyValueTypeError(res, res.jsonValue, key); 224 return false; 225 } 226 if (jsonValue.size() != value.size()) 227 { 228 messages::propertyValueTypeError(res, res.jsonValue, key); 229 return false; 230 } 231 size_t index = 0; 232 for (const auto& val : jsonValue.items()) 233 { 234 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 235 value[index++]) && 236 ret; 237 } 238 } 239 else if constexpr (IsVector<Type>::value) 240 { 241 if (!jsonValue.is_array()) 242 { 243 messages::propertyValueTypeError(res, res.jsonValue, key); 244 return false; 245 } 246 247 for (const auto& val : jsonValue.items()) 248 { 249 value.emplace_back(); 250 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 251 value.back()) && 252 ret; 253 } 254 } 255 else 256 { 257 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 258 if (ec != UnpackErrorCode::success) 259 { 260 if (ec == UnpackErrorCode::invalidType) 261 { 262 messages::propertyValueTypeError(res, jsonValue, key); 263 } 264 else if (ec == UnpackErrorCode::outOfRange) 265 { 266 messages::propertyValueNotInList(res, jsonValue, key); 267 } 268 return false; 269 } 270 } 271 272 return ret; 273 } 274 275 template <typename Type> 276 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) 277 { 278 bool ret = true; 279 if constexpr (IsOptional<Type>::value) 280 { 281 value.emplace(); 282 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 283 ret; 284 } 285 else if constexpr (IsStdArray<Type>::value) 286 { 287 if (!jsonValue.is_array()) 288 { 289 return false; 290 } 291 if (jsonValue.size() != value.size()) 292 { 293 return false; 294 } 295 size_t index = 0; 296 for (const auto& val : jsonValue.items()) 297 { 298 ret = unpackValue<typename Type::value_type>(val.value(), key, 299 value[index++]) && 300 ret; 301 } 302 } 303 else if constexpr (IsVector<Type>::value) 304 { 305 if (!jsonValue.is_array()) 306 { 307 return false; 308 } 309 310 for (const auto& val : jsonValue.items()) 311 { 312 value.emplace_back(); 313 ret = unpackValue<typename Type::value_type>(val.value(), key, 314 value.back()) && 315 ret; 316 } 317 } 318 else 319 { 320 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 321 if (ec != UnpackErrorCode::success) 322 { 323 return false; 324 } 325 } 326 327 return ret; 328 } 329 } // namespace details 330 331 // clang-format off 332 using UnpackVariant = std::variant< 333 uint8_t*, 334 uint16_t*, 335 int16_t*, 336 uint32_t*, 337 int32_t*, 338 uint64_t*, 339 int64_t*, 340 bool*, 341 double*, 342 std::string*, 343 nlohmann::json*, 344 nlohmann::json::object_t*, 345 std::vector<uint8_t>*, 346 std::vector<uint16_t>*, 347 std::vector<int16_t>*, 348 std::vector<uint32_t>*, 349 std::vector<int32_t>*, 350 std::vector<uint64_t>*, 351 std::vector<int64_t>*, 352 //std::vector<bool>*, 353 std::vector<double>*, 354 std::vector<std::string>*, 355 std::vector<nlohmann::json>*, 356 std::vector<nlohmann::json::object_t>*, 357 std::optional<uint8_t>*, 358 std::optional<uint16_t>*, 359 std::optional<int16_t>*, 360 std::optional<uint32_t>*, 361 std::optional<int32_t>*, 362 std::optional<uint64_t>*, 363 std::optional<int64_t>*, 364 std::optional<bool>*, 365 std::optional<double>*, 366 std::optional<std::string>*, 367 std::optional<nlohmann::json>*, 368 std::optional<nlohmann::json::object_t>*, 369 std::optional<std::vector<uint8_t>>*, 370 std::optional<std::vector<uint16_t>>*, 371 std::optional<std::vector<int16_t>>*, 372 std::optional<std::vector<uint32_t>>*, 373 std::optional<std::vector<int32_t>>*, 374 std::optional<std::vector<uint64_t>>*, 375 std::optional<std::vector<int64_t>>*, 376 //std::optional<std::vector<bool>>*, 377 std::optional<std::vector<double>>*, 378 std::optional<std::vector<std::string>>*, 379 std::optional<std::vector<nlohmann::json>>*, 380 std::optional<std::vector<nlohmann::json::object_t>>* 381 >; 382 // clang-format on 383 384 struct PerUnpack 385 { 386 std::string_view key; 387 UnpackVariant value; 388 bool complete = false; 389 }; 390 391 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 392 std::span<PerUnpack> toUnpack); 393 394 inline bool readJsonHelperObject(nlohmann::json::object_t& obj, 395 crow::Response& res, 396 std::span<PerUnpack> toUnpack) 397 { 398 bool result = true; 399 for (auto& item : obj) 400 { 401 size_t unpackIndex = 0; 402 for (; unpackIndex < toUnpack.size(); unpackIndex++) 403 { 404 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 405 std::string_view key = unpackSpec.key; 406 size_t keysplitIndex = key.find('/'); 407 std::string_view leftover; 408 if (keysplitIndex != std::string_view::npos) 409 { 410 leftover = key.substr(keysplitIndex + 1); 411 key = key.substr(0, keysplitIndex); 412 } 413 414 if (key != item.first || unpackSpec.complete) 415 { 416 continue; 417 } 418 419 // Sublevel key 420 if (!leftover.empty()) 421 { 422 // Include the slash in the key so we can compare later 423 key = unpackSpec.key.substr(0, keysplitIndex + 1); 424 nlohmann::json j; 425 result = details::unpackValue<nlohmann::json>(item.second, key, 426 res, j) && 427 result; 428 if (!result) 429 { 430 return result; 431 } 432 433 std::vector<PerUnpack> nextLevel; 434 for (PerUnpack& p : toUnpack) 435 { 436 if (!p.key.starts_with(key)) 437 { 438 continue; 439 } 440 std::string_view thisLeftover = p.key.substr(key.size()); 441 nextLevel.push_back({thisLeftover, p.value, false}); 442 p.complete = true; 443 } 444 445 result = readJsonHelper(j, res, nextLevel) && result; 446 break; 447 } 448 449 result = std::visit( 450 [&item, &unpackSpec, &res](auto&& val) { 451 using ContainedT = 452 std::remove_pointer_t<std::decay_t<decltype(val)>>; 453 return details::unpackValue<ContainedT>( 454 item.second, unpackSpec.key, res, *val); 455 }, 456 unpackSpec.value) && 457 result; 458 459 unpackSpec.complete = true; 460 break; 461 } 462 463 if (unpackIndex == toUnpack.size()) 464 { 465 messages::propertyUnknown(res, item.first); 466 result = false; 467 } 468 } 469 470 for (PerUnpack& perUnpack : toUnpack) 471 { 472 if (!perUnpack.complete) 473 { 474 bool isOptional = std::visit( 475 [](auto&& val) { 476 using ContainedType = 477 std::remove_pointer_t<std::decay_t<decltype(val)>>; 478 return details::IsOptional<ContainedType>::value; 479 }, 480 perUnpack.value); 481 if (isOptional) 482 { 483 continue; 484 } 485 messages::propertyMissing(res, perUnpack.key); 486 result = false; 487 } 488 } 489 return result; 490 } 491 492 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 493 std::span<PerUnpack> toUnpack) 494 { 495 nlohmann::json::object_t* obj = 496 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 497 if (obj == nullptr) 498 { 499 BMCWEB_LOG_DEBUG("Json value is not an object"); 500 messages::unrecognizedRequestBody(res); 501 return false; 502 } 503 return readJsonHelperObject(*obj, res, toUnpack); 504 } 505 506 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 507 508 template <typename FirstType, typename... UnpackTypes> 509 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 510 FirstType& first, UnpackTypes&&... in) 511 { 512 if (toPack.empty()) 513 { 514 return; 515 } 516 toPack[0].key = key; 517 toPack[0].value = &first; 518 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 519 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 520 } 521 522 template <typename FirstType, typename... UnpackTypes> 523 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, 524 std::string_view key, FirstType&& first, 525 UnpackTypes&&... in) 526 { 527 const std::size_t n = sizeof...(UnpackTypes) + 2; 528 std::array<PerUnpack, n / 2> toUnpack2; 529 packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...); 530 return readJsonHelperObject(jsonRequest, res, toUnpack2); 531 } 532 533 template <typename FirstType, typename... UnpackTypes> 534 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 535 std::string_view key, FirstType&& first, UnpackTypes&&... in) 536 { 537 nlohmann::json::object_t* obj = 538 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 539 if (obj == nullptr) 540 { 541 BMCWEB_LOG_DEBUG("Json value is not an object"); 542 messages::unrecognizedRequestBody(res); 543 return false; 544 } 545 return readJsonObject(*obj, res, key, std::forward<FirstType>(first), 546 std::forward<UnpackTypes&&>(in)...); 547 } 548 549 inline std::optional<nlohmann::json::object_t> 550 readJsonPatchHelper(const crow::Request& req, crow::Response& res) 551 { 552 nlohmann::json jsonRequest; 553 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 554 { 555 BMCWEB_LOG_DEBUG("Json value not readable"); 556 return std::nullopt; 557 } 558 nlohmann::json::object_t* object = 559 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 560 if (object == nullptr || object->empty()) 561 { 562 BMCWEB_LOG_DEBUG("Json value is empty"); 563 messages::emptyJSON(res); 564 return std::nullopt; 565 } 566 std::erase_if(*object, 567 [](const std::pair<std::string, nlohmann::json>& item) { 568 return item.first.starts_with("@odata."); 569 }); 570 if (object->empty()) 571 { 572 // If the update request only contains OData annotations, the service 573 // should return the HTTP 400 Bad Request status code with the 574 // NoOperation message from the Base Message Registry, ... 575 messages::noOperation(res); 576 return std::nullopt; 577 } 578 579 return {std::move(*object)}; 580 } 581 582 template <typename... UnpackTypes> 583 bool readJsonPatch(const crow::Request& req, crow::Response& res, 584 std::string_view key, UnpackTypes&&... in) 585 { 586 std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); 587 if (!jsonRequest) 588 { 589 return false; 590 } 591 nlohmann::json::object_t* object = 592 jsonRequest->get_ptr<nlohmann::json::object_t*>(); 593 if (object == nullptr) 594 { 595 BMCWEB_LOG_DEBUG("Json value is empty"); 596 messages::emptyJSON(res); 597 return false; 598 } 599 600 return readJsonObject(*object, res, key, 601 std::forward<UnpackTypes&&>(in)...); 602 } 603 604 template <typename... UnpackTypes> 605 bool readJsonAction(const crow::Request& req, crow::Response& res, 606 const char* key, UnpackTypes&&... in) 607 { 608 nlohmann::json jsonRequest; 609 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 610 { 611 BMCWEB_LOG_DEBUG("Json value not readable"); 612 return false; 613 } 614 nlohmann::json::object_t* object = 615 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 616 if (object == nullptr) 617 { 618 BMCWEB_LOG_DEBUG("Json value is empty"); 619 messages::emptyJSON(res); 620 return false; 621 } 622 return readJsonObject(*object, res, key, 623 std::forward<UnpackTypes&&>(in)...); 624 } 625 626 // Determines if two json objects are less, based on the presence of the 627 // @odata.id key 628 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b) 629 { 630 using object_t = nlohmann::json::object_t; 631 const object_t* aObj = a.get_ptr<const object_t*>(); 632 const object_t* bObj = b.get_ptr<const object_t*>(); 633 634 if (aObj == nullptr) 635 { 636 if (bObj == nullptr) 637 { 638 return 0; 639 } 640 return -1; 641 } 642 if (bObj == nullptr) 643 { 644 return 1; 645 } 646 object_t::const_iterator aIt = aObj->find("@odata.id"); 647 object_t::const_iterator bIt = bObj->find("@odata.id"); 648 // If either object doesn't have the key, they get "sorted" to the end. 649 if (aIt == aObj->end()) 650 { 651 if (bIt == bObj->end()) 652 { 653 return 0; 654 } 655 return -1; 656 } 657 if (bIt == bObj->end()) 658 { 659 return 1; 660 } 661 const nlohmann::json::string_t* nameA = 662 aIt->second.get_ptr<const std::string*>(); 663 const nlohmann::json::string_t* nameB = 664 bIt->second.get_ptr<const std::string*>(); 665 // If either object doesn't have a string as the key, they get "sorted" to 666 // the end. 667 if (nameA == nullptr) 668 { 669 if (nameB == nullptr) 670 { 671 return 0; 672 } 673 return -1; 674 } 675 if (nameB == nullptr) 676 { 677 return 1; 678 } 679 boost::urls::url_view aUrl(*nameA); 680 boost::urls::url_view bUrl(*nameB); 681 auto segmentsAIt = aUrl.segments().begin(); 682 auto segmentsBIt = bUrl.segments().begin(); 683 684 while (true) 685 { 686 if (segmentsAIt == aUrl.segments().end()) 687 { 688 if (segmentsBIt == bUrl.segments().end()) 689 { 690 return 0; 691 } 692 return -1; 693 } 694 if (segmentsBIt == bUrl.segments().end()) 695 { 696 return 1; 697 } 698 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 699 if (res != 0) 700 { 701 return res; 702 } 703 704 segmentsAIt++; 705 segmentsBIt++; 706 } 707 }; 708 709 struct ODataObjectLess 710 { 711 bool operator()(const nlohmann::json& left, 712 const nlohmann::json& right) const 713 { 714 return odataObjectCmp(left, right) < 0; 715 } 716 }; 717 718 // Sort the JSON array by |element[key]|. 719 // Elements without |key| or type of |element[key]| is not string are smaller 720 // those whose |element[key]| is string. 721 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 722 { 723 std::ranges::sort(array, ODataObjectLess()); 724 } 725 726 // Returns the estimated size of the JSON value 727 // The implementation walks through every key and every value, accumulates the 728 // total size of keys and values. 729 // Ideally, we should use a custom allocator that nlohmann JSON supports. 730 731 // Assumption made: 732 // 1. number: 8 characters 733 // 2. boolean: 5 characters (False) 734 // 3. string: len(str) + 2 characters (quote) 735 // 4. bytes: len(bytes) characters 736 // 5. null: 4 characters (null) 737 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 738 739 } // namespace json_util 740 } // namespace redfish 741