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 std::vector<uint8_t>*, 345 std::vector<uint16_t>*, 346 std::vector<int16_t>*, 347 std::vector<uint32_t>*, 348 std::vector<int32_t>*, 349 std::vector<uint64_t>*, 350 std::vector<int64_t>*, 351 //std::vector<bool>*, 352 std::vector<double>*, 353 std::vector<std::string>*, 354 std::vector<nlohmann::json>*, 355 std::optional<uint8_t>*, 356 std::optional<uint16_t>*, 357 std::optional<int16_t>*, 358 std::optional<uint32_t>*, 359 std::optional<int32_t>*, 360 std::optional<uint64_t>*, 361 std::optional<int64_t>*, 362 std::optional<bool>*, 363 std::optional<double>*, 364 std::optional<std::string>*, 365 std::optional<nlohmann::json>*, 366 std::optional<std::vector<uint8_t>>*, 367 std::optional<std::vector<uint16_t>>*, 368 std::optional<std::vector<int16_t>>*, 369 std::optional<std::vector<uint32_t>>*, 370 std::optional<std::vector<int32_t>>*, 371 std::optional<std::vector<uint64_t>>*, 372 std::optional<std::vector<int64_t>>*, 373 //std::optional<std::vector<bool>>*, 374 std::optional<std::vector<double>>*, 375 std::optional<std::vector<std::string>>*, 376 std::optional<std::vector<nlohmann::json>>* 377 >; 378 // clang-format on 379 380 struct PerUnpack 381 { 382 std::string_view key; 383 UnpackVariant value; 384 bool complete = false; 385 }; 386 387 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 388 std::span<PerUnpack> toUnpack) 389 { 390 bool result = true; 391 nlohmann::json::object_t* obj = 392 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 393 if (obj == nullptr) 394 { 395 BMCWEB_LOG_DEBUG("Json value is not an object"); 396 messages::unrecognizedRequestBody(res); 397 return false; 398 } 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 void packVariant(std::span<PerUnpack> /*toPack*/) {} 493 494 template <typename FirstType, typename... UnpackTypes> 495 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 496 FirstType& first, UnpackTypes&&... in) 497 { 498 if (toPack.empty()) 499 { 500 return; 501 } 502 toPack[0].key = key; 503 toPack[0].value = &first; 504 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 505 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 506 } 507 508 template <typename FirstType, typename... UnpackTypes> 509 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 510 std::string_view key, FirstType&& first, UnpackTypes&&... in) 511 { 512 const std::size_t n = sizeof...(UnpackTypes) + 2; 513 std::array<PerUnpack, n / 2> toUnpack2; 514 packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...); 515 return readJsonHelper(jsonRequest, res, toUnpack2); 516 } 517 518 inline std::optional<nlohmann::json> 519 readJsonPatchHelper(const crow::Request& req, crow::Response& res) 520 { 521 nlohmann::json jsonRequest; 522 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 523 { 524 BMCWEB_LOG_DEBUG("Json value not readable"); 525 return std::nullopt; 526 } 527 nlohmann::json::object_t* object = 528 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 529 if (object == nullptr || object->empty()) 530 { 531 BMCWEB_LOG_DEBUG("Json value is empty"); 532 messages::emptyJSON(res); 533 return std::nullopt; 534 } 535 std::erase_if(*object, 536 [](const std::pair<std::string, nlohmann::json>& item) { 537 return item.first.starts_with("@odata."); 538 }); 539 if (object->empty()) 540 { 541 // If the update request only contains OData annotations, the service 542 // should return the HTTP 400 Bad Request status code with the 543 // NoOperation message from the Base Message Registry, ... 544 messages::noOperation(res); 545 return std::nullopt; 546 } 547 548 return {std::move(jsonRequest)}; 549 } 550 551 template <typename... UnpackTypes> 552 bool readJsonPatch(const crow::Request& req, crow::Response& res, 553 std::string_view key, UnpackTypes&&... in) 554 { 555 std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); 556 if (!jsonRequest) 557 { 558 return false; 559 } 560 561 return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 562 } 563 564 template <typename... UnpackTypes> 565 bool readJsonAction(const crow::Request& req, crow::Response& res, 566 const char* key, UnpackTypes&&... in) 567 { 568 nlohmann::json jsonRequest; 569 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 570 { 571 BMCWEB_LOG_DEBUG("Json value not readable"); 572 return false; 573 } 574 return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 575 } 576 577 // Determines if two json objects are less, based on the presence of the 578 // @odata.id key 579 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b) 580 { 581 using object_t = nlohmann::json::object_t; 582 const object_t* aObj = a.get_ptr<const object_t*>(); 583 const object_t* bObj = b.get_ptr<const object_t*>(); 584 585 if (aObj == nullptr) 586 { 587 if (bObj == nullptr) 588 { 589 return 0; 590 } 591 return -1; 592 } 593 if (bObj == nullptr) 594 { 595 return 1; 596 } 597 object_t::const_iterator aIt = aObj->find("@odata.id"); 598 object_t::const_iterator bIt = bObj->find("@odata.id"); 599 // If either object doesn't have the key, they get "sorted" to the end. 600 if (aIt == aObj->end()) 601 { 602 if (bIt == bObj->end()) 603 { 604 return 0; 605 } 606 return -1; 607 } 608 if (bIt == bObj->end()) 609 { 610 return 1; 611 } 612 const nlohmann::json::string_t* nameA = 613 aIt->second.get_ptr<const std::string*>(); 614 const nlohmann::json::string_t* nameB = 615 bIt->second.get_ptr<const std::string*>(); 616 // If either object doesn't have a string as the key, they get "sorted" to 617 // the end. 618 if (nameA == nullptr) 619 { 620 if (nameB == nullptr) 621 { 622 return 0; 623 } 624 return -1; 625 } 626 if (nameB == nullptr) 627 { 628 return 1; 629 } 630 boost::urls::url_view aUrl(*nameA); 631 boost::urls::url_view bUrl(*nameB); 632 auto segmentsAIt = aUrl.segments().begin(); 633 auto segmentsBIt = bUrl.segments().begin(); 634 635 while (true) 636 { 637 if (segmentsAIt == aUrl.segments().end()) 638 { 639 if (segmentsBIt == bUrl.segments().end()) 640 { 641 return 0; 642 } 643 return -1; 644 } 645 if (segmentsBIt == bUrl.segments().end()) 646 { 647 return 1; 648 } 649 int res = alphanumComp(*segmentsAIt, *segmentsBIt); 650 if (res != 0) 651 { 652 return res; 653 } 654 655 segmentsAIt++; 656 segmentsBIt++; 657 } 658 }; 659 660 struct ODataObjectLess 661 { 662 bool operator()(const nlohmann::json& left, 663 const nlohmann::json& right) const 664 { 665 return odataObjectCmp(left, right) < 0; 666 } 667 }; 668 669 // Sort the JSON array by |element[key]|. 670 // Elements without |key| or type of |element[key]| is not string are smaller 671 // those whose |element[key]| is string. 672 inline void sortJsonArrayByOData(nlohmann::json::array_t& array) 673 { 674 std::ranges::sort(array, ODataObjectLess()); 675 } 676 677 // Returns the estimated size of the JSON value 678 // The implementation walks through every key and every value, accumulates the 679 // total size of keys and values. 680 // Ideally, we should use a custom allocator that nlohmann JSON supports. 681 682 // Assumption made: 683 // 1. number: 8 characters 684 // 2. boolean: 5 characters (False) 685 // 3. string: len(str) + 2 characters (quote) 686 // 4. bytes: len(bytes) characters 687 // 5. null: 4 characters (null) 688 uint64_t getEstimatedJsonSize(const nlohmann::json& root); 689 690 } // namespace json_util 691 } // namespace redfish 692