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