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