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