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