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 nlohmann::json::object_t* obj = 408 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 409 if (obj == nullptr) 410 { 411 BMCWEB_LOG_DEBUG << "Json value is not an object"; 412 messages::unrecognizedRequestBody(res); 413 return false; 414 } 415 for (auto& item : *obj) 416 { 417 size_t unpackIndex = 0; 418 for (; unpackIndex < toUnpack.size(); unpackIndex++) 419 { 420 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 421 std::string_view key = unpackSpec.key; 422 size_t keysplitIndex = key.find('/'); 423 std::string_view leftover; 424 if (keysplitIndex != std::string_view::npos) 425 { 426 leftover = key.substr(keysplitIndex + 1); 427 key = key.substr(0, keysplitIndex); 428 } 429 430 if (key != item.first || unpackSpec.complete) 431 { 432 continue; 433 } 434 435 // Sublevel key 436 if (!leftover.empty()) 437 { 438 // Include the slash in the key so we can compare later 439 key = unpackSpec.key.substr(0, keysplitIndex + 1); 440 nlohmann::json j; 441 result = details::unpackValue<nlohmann::json>(item.second, key, 442 res, j) && 443 result; 444 if (!result) 445 { 446 return result; 447 } 448 449 std::vector<PerUnpack> nextLevel; 450 for (PerUnpack& p : toUnpack) 451 { 452 if (!p.key.starts_with(key)) 453 { 454 continue; 455 } 456 std::string_view thisLeftover = p.key.substr(key.size()); 457 nextLevel.push_back({thisLeftover, p.value, false}); 458 p.complete = true; 459 } 460 461 result = readJsonHelper(j, res, nextLevel) && result; 462 break; 463 } 464 465 result = std::visit( 466 [&item, &unpackSpec, &res](auto&& val) { 467 using ContainedT = 468 std::remove_pointer_t<std::decay_t<decltype(val)>>; 469 return details::unpackValue<ContainedT>( 470 item.second, unpackSpec.key, res, *val); 471 }, 472 unpackSpec.value) && 473 result; 474 475 unpackSpec.complete = true; 476 break; 477 } 478 479 if (unpackIndex == toUnpack.size()) 480 { 481 messages::propertyUnknown(res, item.first); 482 result = false; 483 } 484 } 485 486 for (PerUnpack& perUnpack : toUnpack) 487 { 488 if (!perUnpack.complete) 489 { 490 bool isOptional = std::visit( 491 [](auto&& val) { 492 using ContainedType = 493 std::remove_pointer_t<std::decay_t<decltype(val)>>; 494 return details::IsOptional<ContainedType>::value; 495 }, 496 perUnpack.value); 497 if (isOptional) 498 { 499 continue; 500 } 501 messages::propertyMissing(res, perUnpack.key); 502 result = false; 503 } 504 } 505 return result; 506 } 507 508 inline void packVariant(std::span<PerUnpack> /*toPack*/) {} 509 510 template <typename FirstType, typename... UnpackTypes> 511 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 512 FirstType& first, UnpackTypes&&... in) 513 { 514 if (toPack.empty()) 515 { 516 return; 517 } 518 toPack[0].key = key; 519 toPack[0].value = &first; 520 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 521 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 522 } 523 524 template <typename FirstType, typename... UnpackTypes> 525 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 526 std::string_view key, FirstType&& first, UnpackTypes&&... in) 527 { 528 const std::size_t n = sizeof...(UnpackTypes) + 2; 529 std::array<PerUnpack, n / 2> toUnpack2; 530 packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...); 531 return readJsonHelper(jsonRequest, res, toUnpack2); 532 } 533 534 inline std::optional<nlohmann::json> 535 readJsonPatchHelper(const crow::Request& req, crow::Response& res) 536 { 537 nlohmann::json jsonRequest; 538 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 539 { 540 BMCWEB_LOG_DEBUG << "Json value not readable"; 541 return std::nullopt; 542 } 543 nlohmann::json::object_t* object = 544 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 545 if (object == nullptr || object->empty()) 546 { 547 BMCWEB_LOG_DEBUG << "Json value is empty"; 548 messages::emptyJSON(res); 549 return std::nullopt; 550 } 551 std::erase_if(*object, 552 [](const std::pair<std::string, nlohmann::json>& item) { 553 return item.first.starts_with("@odata."); 554 }); 555 if (object->empty()) 556 { 557 // If the update request only contains OData annotations, the service 558 // should return the HTTP 400 Bad Request status code with the 559 // NoOperation message from the Base Message Registry, ... 560 messages::noOperation(res); 561 return std::nullopt; 562 } 563 564 return {std::move(jsonRequest)}; 565 } 566 567 template <typename... UnpackTypes> 568 bool readJsonPatch(const crow::Request& req, crow::Response& res, 569 std::string_view key, UnpackTypes&&... in) 570 { 571 std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); 572 if (jsonRequest == std::nullopt) 573 { 574 return false; 575 } 576 577 return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 578 } 579 580 template <typename... UnpackTypes> 581 bool readJsonAction(const crow::Request& req, crow::Response& res, 582 const char* key, UnpackTypes&&... in) 583 { 584 nlohmann::json jsonRequest; 585 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 586 { 587 BMCWEB_LOG_DEBUG << "Json value not readable"; 588 return false; 589 } 590 return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 591 } 592 } // namespace json_util 593 } // namespace redfish 594