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, first, in...); 546 } 547 548 inline std::optional<nlohmann::json::object_t> 549 readJsonPatchHelper(const crow::Request& req, crow::Response& res) 550 { 551 nlohmann::json jsonRequest; 552 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 553 { 554 BMCWEB_LOG_DEBUG("Json value not readable"); 555 return std::nullopt; 556 } 557 nlohmann::json::object_t* object = 558 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 559 if (object == nullptr || object->empty()) 560 { 561 BMCWEB_LOG_DEBUG("Json value is empty"); 562 messages::emptyJSON(res); 563 return std::nullopt; 564 } 565 std::erase_if(*object, 566 [](const std::pair<std::string, nlohmann::json>& item) { 567 return item.first.starts_with("@odata."); 568 }); 569 if (object->empty()) 570 { 571 // If the update request only contains OData annotations, the service 572 // should return the HTTP 400 Bad Request status code with the 573 // NoOperation message from the Base Message Registry, ... 574 messages::noOperation(res); 575 return std::nullopt; 576 } 577 578 return {std::move(*object)}; 579 } 580 581 template <typename... UnpackTypes> 582 bool readJsonPatch(const crow::Request& req, crow::Response& res, 583 std::string_view key, UnpackTypes&&... in) 584 { 585 std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); 586 if (!jsonRequest) 587 { 588 return false; 589 } 590 nlohmann::json::object_t* object = 591 jsonRequest->get_ptr<nlohmann::json::object_t*>(); 592 if (object == nullptr) 593 { 594 BMCWEB_LOG_DEBUG("Json value is empty"); 595 messages::emptyJSON(res); 596 return false; 597 } 598 599 return readJsonObject(*object, res, key, 600 std::forward<UnpackTypes&&>(in)...); 601 } 602 603 template <typename... UnpackTypes> 604 bool readJsonAction(const crow::Request& req, crow::Response& res, 605 const char* key, UnpackTypes&&... in) 606 { 607 nlohmann::json jsonRequest; 608 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 609 { 610 BMCWEB_LOG_DEBUG("Json value not readable"); 611 return false; 612 } 613 nlohmann::json::object_t* object = 614 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 615 if (object == nullptr) 616 { 617 BMCWEB_LOG_DEBUG("Json value is empty"); 618 messages::emptyJSON(res); 619 return false; 620 } 621 return readJsonObject(*object, res, key, 622 std::forward<UnpackTypes&&>(in)...); 623 } 624 625 // Determines if two json objects are less, based on the presence of the 626 // @odata.id key 627 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b) 628 { 629 using object_t = nlohmann::json::object_t; 630 const object_t* aObj = a.get_ptr<const object_t*>(); 631 const object_t* bObj = b.get_ptr<const object_t*>(); 632 633 if (aObj == nullptr) 634 { 635 if (bObj == nullptr) 636 { 637 return 0; 638 } 639 return -1; 640 } 641 if (bObj == nullptr) 642 { 643 return 1; 644 } 645 object_t::const_iterator aIt = aObj->find("@odata.id"); 646 object_t::const_iterator bIt = bObj->find("@odata.id"); 647 // If either object doesn't have the key, they get "sorted" to the end. 648 if (aIt == aObj->end()) 649 { 650 if (bIt == bObj->end()) 651 { 652 return 0; 653 } 654 return -1; 655 } 656 if (bIt == bObj->end()) 657 { 658 return 1; 659 } 660 const nlohmann::json::string_t* nameA = 661 aIt->second.get_ptr<const std::string*>(); 662 const nlohmann::json::string_t* nameB = 663 bIt->second.get_ptr<const std::string*>(); 664 // If either object doesn't have a string as the key, they get "sorted" to 665 // the end. 666 if (nameA == nullptr) 667 { 668 if (nameB == nullptr) 669 { 670 return 0; 671 } 672 return -1; 673 } 674 if (nameB == nullptr) 675 { 676 return 1; 677 } 678 boost::urls::url_view aUrl(*nameA); 679 boost::urls::url_view bUrl(*nameB); 680 auto segmentsAIt = aUrl.segments().begin(); 681 auto segmentsBIt = bUrl.segments().begin(); 682 683 while (true) 684 { 685 if (segmentsAIt == aUrl.segments().end()) 686 { 687 if (segmentsBIt == bUrl.segments().end()) 688 { 689 return 0; 690 } 691 return -1; 692 } 693 if (segmentsBIt == bUrl.segments().end()) 694 { 695 return 1; 696 } 697 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 698 if (res != 0) 699 { 700 return res; 701 } 702 703 segmentsAIt++; 704 segmentsBIt++; 705 } 706 }; 707 708 struct ODataObjectLess 709 { 710 bool operator()(const nlohmann::json& left, 711 const nlohmann::json& right) const 712 { 713 return odataObjectCmp(left, right) < 0; 714 } 715 }; 716 717 // Sort the JSON array by |element[key]|. 718 // Elements without |key| or type of |element[key]| is not string are smaller 719 // those whose |element[key]| is string. 720 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 721 { 722 std::ranges::sort(array, ODataObjectLess()); 723 } 724 725 // Returns the estimated size of the JSON value 726 // The implementation walks through every key and every value, accumulates the 727 // total size of keys and values. 728 // Ideally, we should use a custom allocator that nlohmann JSON supports. 729 730 // Assumption made: 731 // 1. number: 8 characters 732 // 2. boolean: 5 characters (False) 733 // 3. string: len(str) + 2 characters (quote) 734 // 4. bytes: len(bytes) characters 735 // 5. null: 4 characters (null) 736 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 737 738 } // namespace json_util 739 } // namespace redfish 740