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