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 #include "nlohmann/json.hpp" 23 24 #include <array> 25 #include <cmath> 26 #include <cstddef> 27 #include <cstdint> 28 #include <limits> 29 #include <map> 30 #include <optional> 31 #include <span> 32 #include <string> 33 #include <string_view> 34 #include <type_traits> 35 #include <utility> 36 #include <variant> 37 #include <vector> 38 39 // IWYU pragma: no_include <stdint.h> 40 41 namespace crow 42 { 43 struct Request; 44 } // namespace crow 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 " << key 105 << " was greater than max: " << __PRETTY_FUNCTION__; 106 return false; 107 } 108 if (from < std::numeric_limits<ToType>::lowest()) 109 { 110 BMCWEB_LOG_DEBUG << "Value for key " << key 111 << " was less than min: " << __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 " << key << " was NAN"; 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 197 << "Value for key " << key 198 << " was incorrect type: " << 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( 224 res, 225 res.jsonValue.dump(2, ' ', true, 226 nlohmann::json::error_handler_t::replace), 227 key); 228 return false; 229 } 230 if (jsonValue.size() != value.size()) 231 { 232 messages::propertyValueTypeError( 233 res, 234 res.jsonValue.dump(2, ' ', true, 235 nlohmann::json::error_handler_t::replace), 236 key); 237 return false; 238 } 239 size_t index = 0; 240 for (const auto& val : jsonValue.items()) 241 { 242 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 243 value[index++]) && 244 ret; 245 } 246 } 247 else if constexpr (IsVector<Type>::value) 248 { 249 if (!jsonValue.is_array()) 250 { 251 messages::propertyValueTypeError( 252 res, 253 res.jsonValue.dump(2, ' ', true, 254 nlohmann::json::error_handler_t::replace), 255 key); 256 return false; 257 } 258 259 for (const auto& val : jsonValue.items()) 260 { 261 value.emplace_back(); 262 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 263 value.back()) && 264 ret; 265 } 266 } 267 else 268 { 269 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 270 if (ec != UnpackErrorCode::success) 271 { 272 if (ec == UnpackErrorCode::invalidType) 273 { 274 messages::propertyValueTypeError( 275 res, 276 jsonValue.dump(2, ' ', true, 277 nlohmann::json::error_handler_t::replace), 278 key); 279 } 280 else if (ec == UnpackErrorCode::outOfRange) 281 { 282 messages::propertyValueNotInList( 283 res, 284 jsonValue.dump(2, ' ', true, 285 nlohmann::json::error_handler_t::replace), 286 key); 287 } 288 return false; 289 } 290 } 291 292 return ret; 293 } 294 295 template <typename Type> 296 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) 297 { 298 bool ret = true; 299 if constexpr (IsOptional<Type>::value) 300 { 301 value.emplace(); 302 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 303 ret; 304 } 305 else if constexpr (IsStdArray<Type>::value) 306 { 307 if (!jsonValue.is_array()) 308 { 309 return false; 310 } 311 if (jsonValue.size() != value.size()) 312 { 313 return false; 314 } 315 size_t index = 0; 316 for (const auto& val : jsonValue.items()) 317 { 318 ret = unpackValue<typename Type::value_type>(val.value(), key, 319 value[index++]) && 320 ret; 321 } 322 } 323 else if constexpr (IsVector<Type>::value) 324 { 325 if (!jsonValue.is_array()) 326 { 327 return false; 328 } 329 330 for (const auto& val : jsonValue.items()) 331 { 332 value.emplace_back(); 333 ret = unpackValue<typename Type::value_type>(val.value(), key, 334 value.back()) && 335 ret; 336 } 337 } 338 else 339 { 340 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 341 if (ec != UnpackErrorCode::success) 342 { 343 return false; 344 } 345 } 346 347 return ret; 348 } 349 } // namespace details 350 351 // clang-format off 352 using UnpackVariant = std::variant< 353 uint8_t*, 354 uint16_t*, 355 int16_t*, 356 uint32_t*, 357 int32_t*, 358 uint64_t*, 359 int64_t*, 360 bool*, 361 double*, 362 std::string*, 363 nlohmann::json*, 364 std::vector<uint8_t>*, 365 std::vector<uint16_t>*, 366 std::vector<int16_t>*, 367 std::vector<uint32_t>*, 368 std::vector<int32_t>*, 369 std::vector<uint64_t>*, 370 std::vector<int64_t>*, 371 //std::vector<bool>*, 372 std::vector<double>*, 373 std::vector<std::string>*, 374 std::vector<nlohmann::json>*, 375 std::optional<uint8_t>*, 376 std::optional<uint16_t>*, 377 std::optional<int16_t>*, 378 std::optional<uint32_t>*, 379 std::optional<int32_t>*, 380 std::optional<uint64_t>*, 381 std::optional<int64_t>*, 382 std::optional<bool>*, 383 std::optional<double>*, 384 std::optional<std::string>*, 385 std::optional<nlohmann::json>*, 386 std::optional<std::vector<uint8_t>>*, 387 std::optional<std::vector<uint16_t>>*, 388 std::optional<std::vector<int16_t>>*, 389 std::optional<std::vector<uint32_t>>*, 390 std::optional<std::vector<int32_t>>*, 391 std::optional<std::vector<uint64_t>>*, 392 std::optional<std::vector<int64_t>>*, 393 //std::optional<std::vector<bool>>*, 394 std::optional<std::vector<double>>*, 395 std::optional<std::vector<std::string>>*, 396 std::optional<std::vector<nlohmann::json>>* 397 >; 398 // clang-format on 399 400 struct PerUnpack 401 { 402 std::string_view key; 403 UnpackVariant value; 404 bool complete = false; 405 }; 406 407 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 408 std::span<PerUnpack> toUnpack) 409 { 410 bool result = true; 411 if (!jsonRequest.is_object()) 412 { 413 BMCWEB_LOG_DEBUG << "Json value is not an object"; 414 messages::unrecognizedRequestBody(res); 415 return false; 416 } 417 for (auto& item : jsonRequest.items()) 418 { 419 size_t unpackIndex = 0; 420 for (; unpackIndex < toUnpack.size(); unpackIndex++) 421 { 422 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 423 std::string_view key = unpackSpec.key; 424 size_t keysplitIndex = key.find('/'); 425 std::string_view leftover; 426 if (keysplitIndex != std::string_view::npos) 427 { 428 leftover = key.substr(keysplitIndex + 1); 429 key = key.substr(0, keysplitIndex); 430 } 431 432 if (key != item.key() || unpackSpec.complete) 433 { 434 continue; 435 } 436 437 // Sublevel key 438 if (!leftover.empty()) 439 { 440 // Include the slash in the key so we can compare later 441 key = unpackSpec.key.substr(0, keysplitIndex + 1); 442 nlohmann::json j; 443 result = details::unpackValue<nlohmann::json>(item.value(), key, 444 res, j) && 445 result; 446 if (!result) 447 { 448 return result; 449 } 450 451 std::vector<PerUnpack> nextLevel; 452 for (PerUnpack& p : toUnpack) 453 { 454 if (!p.key.starts_with(key)) 455 { 456 continue; 457 } 458 std::string_view thisLeftover = p.key.substr(key.size()); 459 nextLevel.push_back({thisLeftover, p.value, false}); 460 p.complete = true; 461 } 462 463 result = readJsonHelper(j, res, nextLevel) && result; 464 break; 465 } 466 467 result = std::visit( 468 [&item, &unpackSpec, &res](auto&& val) { 469 using ContainedT = 470 std::remove_pointer_t<std::decay_t<decltype(val)>>; 471 return details::unpackValue<ContainedT>( 472 item.value(), unpackSpec.key, res, *val); 473 }, 474 unpackSpec.value) && 475 result; 476 477 unpackSpec.complete = true; 478 break; 479 } 480 481 if (unpackIndex == toUnpack.size()) 482 { 483 messages::propertyUnknown(res, item.key()); 484 result = false; 485 } 486 } 487 488 for (PerUnpack& perUnpack : toUnpack) 489 { 490 if (!perUnpack.complete) 491 { 492 bool isOptional = std::visit( 493 [](auto&& val) { 494 using ContainedType = 495 std::remove_pointer_t<std::decay_t<decltype(val)>>; 496 return details::IsOptional<ContainedType>::value; 497 }, 498 perUnpack.value); 499 if (isOptional) 500 { 501 continue; 502 } 503 messages::propertyMissing(res, perUnpack.key); 504 result = false; 505 } 506 } 507 return result; 508 } 509 510 inline void packVariant(std::span<PerUnpack> /*toPack*/) 511 {} 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 } // namespace json_util 597 } // namespace redfish 598