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 value = std::move(jsonValue); 168 } 169 else 170 { 171 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 172 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 173 if (jsonPtr == nullptr) 174 { 175 BMCWEB_LOG_DEBUG 176 << "Value for key " << key 177 << " was incorrect type: " << jsonValue.type_name(); 178 return UnpackErrorCode::invalidType; 179 } 180 value = std::move(*jsonPtr); 181 } 182 return ret; 183 } 184 185 template <typename Type> 186 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, 187 crow::Response& res, Type& value) 188 { 189 bool ret = true; 190 191 if constexpr (IsOptional<Type>::value) 192 { 193 value.emplace(); 194 ret = unpackValue<typename Type::value_type>(jsonValue, key, res, 195 *value) && 196 ret; 197 } 198 else if constexpr (IsStdArray<Type>::value) 199 { 200 if (!jsonValue.is_array()) 201 { 202 messages::propertyValueTypeError( 203 res, 204 res.jsonValue.dump(2, ' ', true, 205 nlohmann::json::error_handler_t::replace), 206 key); 207 return false; 208 } 209 if (jsonValue.size() != value.size()) 210 { 211 messages::propertyValueTypeError( 212 res, 213 res.jsonValue.dump(2, ' ', true, 214 nlohmann::json::error_handler_t::replace), 215 key); 216 return false; 217 } 218 size_t index = 0; 219 for (const auto& val : jsonValue.items()) 220 { 221 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 222 value[index++]) && 223 ret; 224 } 225 } 226 else if constexpr (IsVector<Type>::value) 227 { 228 if (!jsonValue.is_array()) 229 { 230 messages::propertyValueTypeError( 231 res, 232 res.jsonValue.dump(2, ' ', true, 233 nlohmann::json::error_handler_t::replace), 234 key); 235 return false; 236 } 237 238 for (const auto& val : jsonValue.items()) 239 { 240 value.emplace_back(); 241 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 242 value.back()) && 243 ret; 244 } 245 } 246 else 247 { 248 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 249 if (ec != UnpackErrorCode::success) 250 { 251 if (ec == UnpackErrorCode::invalidType) 252 { 253 messages::propertyValueTypeError( 254 res, 255 jsonValue.dump(2, ' ', true, 256 nlohmann::json::error_handler_t::replace), 257 key); 258 } 259 else if (ec == UnpackErrorCode::outOfRange) 260 { 261 messages::propertyValueNotInList( 262 res, 263 jsonValue.dump(2, ' ', true, 264 nlohmann::json::error_handler_t::replace), 265 key); 266 } 267 return false; 268 } 269 } 270 271 return ret; 272 } 273 274 template <typename Type> 275 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) 276 { 277 bool ret = true; 278 if constexpr (IsOptional<Type>::value) 279 { 280 value.emplace(); 281 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 282 ret; 283 } 284 else if constexpr (IsStdArray<Type>::value) 285 { 286 if (!jsonValue.is_array()) 287 { 288 return false; 289 } 290 if (jsonValue.size() != value.size()) 291 { 292 return false; 293 } 294 size_t index = 0; 295 for (const auto& val : jsonValue.items()) 296 { 297 ret = unpackValue<typename Type::value_type>(val.value(), key, 298 value[index++]) && 299 ret; 300 } 301 } 302 else if constexpr (IsVector<Type>::value) 303 { 304 if (!jsonValue.is_array()) 305 { 306 return false; 307 } 308 309 for (const auto& val : jsonValue.items()) 310 { 311 value.emplace_back(); 312 ret = unpackValue<typename Type::value_type>(val.value(), key, 313 value.back()) && 314 ret; 315 } 316 } 317 else 318 { 319 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 320 if (ec != UnpackErrorCode::success) 321 { 322 return false; 323 } 324 } 325 326 return ret; 327 } 328 } // namespace details 329 330 // clang-format off 331 using UnpackVariant = std::variant< 332 uint8_t*, 333 uint16_t*, 334 int16_t*, 335 uint32_t*, 336 int32_t*, 337 uint64_t*, 338 int64_t*, 339 bool*, 340 double*, 341 std::string*, 342 nlohmann::json*, 343 std::vector<uint8_t>*, 344 std::vector<uint16_t>*, 345 std::vector<int16_t>*, 346 std::vector<uint32_t>*, 347 std::vector<int32_t>*, 348 std::vector<uint64_t>*, 349 std::vector<int64_t>*, 350 //std::vector<bool>*, 351 std::vector<double>*, 352 std::vector<std::string>*, 353 std::vector<nlohmann::json>*, 354 std::optional<uint8_t>*, 355 std::optional<uint16_t>*, 356 std::optional<int16_t>*, 357 std::optional<uint32_t>*, 358 std::optional<int32_t>*, 359 std::optional<uint64_t>*, 360 std::optional<int64_t>*, 361 std::optional<bool>*, 362 std::optional<double>*, 363 std::optional<std::string>*, 364 std::optional<nlohmann::json>*, 365 std::optional<std::vector<uint8_t>>*, 366 std::optional<std::vector<uint16_t>>*, 367 std::optional<std::vector<int16_t>>*, 368 std::optional<std::vector<uint32_t>>*, 369 std::optional<std::vector<int32_t>>*, 370 std::optional<std::vector<uint64_t>>*, 371 std::optional<std::vector<int64_t>>*, 372 //std::optional<std::vector<bool>>*, 373 std::optional<std::vector<double>>*, 374 std::optional<std::vector<std::string>>*, 375 std::optional<std::vector<nlohmann::json>>* 376 >; 377 // clang-format on 378 379 struct PerUnpack 380 { 381 std::string_view key; 382 UnpackVariant value; 383 bool complete = false; 384 }; 385 386 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, 387 std::span<PerUnpack> toUnpack) 388 { 389 bool result = true; 390 if (!jsonRequest.is_object()) 391 { 392 BMCWEB_LOG_DEBUG << "Json value is not an object"; 393 messages::unrecognizedRequestBody(res); 394 return false; 395 } 396 for (auto& item : jsonRequest.items()) 397 { 398 size_t unpackIndex = 0; 399 for (; unpackIndex < toUnpack.size(); unpackIndex++) 400 { 401 PerUnpack& unpackSpec = toUnpack[unpackIndex]; 402 std::string_view key = unpackSpec.key; 403 size_t keysplitIndex = key.find('/'); 404 std::string_view leftover; 405 if (keysplitIndex != std::string_view::npos) 406 { 407 leftover = key.substr(keysplitIndex + 1); 408 key = key.substr(0, keysplitIndex); 409 } 410 411 if (key != item.key() || unpackSpec.complete) 412 { 413 continue; 414 } 415 416 // Sublevel key 417 if (!leftover.empty()) 418 { 419 // Include the slash in the key so we can compare later 420 key = unpackSpec.key.substr(0, keysplitIndex + 1); 421 nlohmann::json j; 422 result = details::unpackValue<nlohmann::json>(item.value(), key, 423 res, j) && 424 result; 425 if (!result) 426 { 427 return result; 428 } 429 430 std::vector<PerUnpack> nextLevel; 431 for (PerUnpack& p : toUnpack) 432 { 433 if (!p.key.starts_with(key)) 434 { 435 continue; 436 } 437 std::string_view thisLeftover = p.key.substr(key.size()); 438 nextLevel.push_back({thisLeftover, p.value, false}); 439 p.complete = true; 440 } 441 442 result = readJsonHelper(j, res, nextLevel) && result; 443 break; 444 } 445 446 result = std::visit( 447 [&item, &unpackSpec, &res](auto&& val) { 448 using ContainedT = 449 std::remove_pointer_t<std::decay_t<decltype(val)>>; 450 return details::unpackValue<ContainedT>( 451 item.value(), unpackSpec.key, res, *val); 452 }, 453 unpackSpec.value) && 454 result; 455 456 unpackSpec.complete = true; 457 break; 458 } 459 460 if (unpackIndex == toUnpack.size()) 461 { 462 messages::propertyUnknown(res, item.key()); 463 result = false; 464 } 465 } 466 467 for (PerUnpack& perUnpack : toUnpack) 468 { 469 if (!perUnpack.complete) 470 { 471 bool isOptional = std::visit( 472 [](auto&& val) { 473 using ContainedType = 474 std::remove_pointer_t<std::decay_t<decltype(val)>>; 475 return details::IsOptional<ContainedType>::value; 476 }, 477 perUnpack.value); 478 if (isOptional) 479 { 480 continue; 481 } 482 messages::propertyMissing(res, perUnpack.key); 483 result = false; 484 } 485 } 486 return result; 487 } 488 489 inline void packVariant(std::span<PerUnpack> /*toPack*/) 490 {} 491 492 template <typename FirstType, typename... UnpackTypes> 493 void packVariant(std::span<PerUnpack> toPack, std::string_view key, 494 FirstType& first, UnpackTypes&&... in) 495 { 496 if (toPack.empty()) 497 { 498 return; 499 } 500 toPack[0].key = key; 501 toPack[0].value = &first; 502 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 503 packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); 504 } 505 506 template <typename FirstType, typename... UnpackTypes> 507 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, 508 std::string_view key, FirstType&& first, UnpackTypes&&... in) 509 { 510 const std::size_t n = sizeof...(UnpackTypes) + 2; 511 std::array<PerUnpack, n / 2> toUnpack2; 512 packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...); 513 return readJsonHelper(jsonRequest, res, toUnpack2); 514 } 515 516 inline std::optional<nlohmann::json> 517 readJsonPatchHelper(const crow::Request& req, crow::Response& res) 518 { 519 nlohmann::json jsonRequest; 520 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 521 { 522 BMCWEB_LOG_DEBUG << "Json value not readable"; 523 return std::nullopt; 524 } 525 nlohmann::json::object_t* object = 526 jsonRequest.get_ptr<nlohmann::json::object_t*>(); 527 if (object == nullptr || object->empty()) 528 { 529 BMCWEB_LOG_DEBUG << "Json value is empty"; 530 messages::emptyJSON(res); 531 return std::nullopt; 532 } 533 std::erase_if(*object, 534 [](const std::pair<std::string, nlohmann::json>& item) { 535 return item.first.starts_with("@odata."); 536 }); 537 if (object->empty()) 538 { 539 // If the update request only contains OData annotations, the service 540 // should return the HTTP 400 Bad Request status code with the 541 // NoOperation message from the Base Message Registry, ... 542 messages::noOperation(res); 543 return std::nullopt; 544 } 545 546 return {std::move(jsonRequest)}; 547 } 548 549 template <typename... UnpackTypes> 550 bool readJsonPatch(const crow::Request& req, crow::Response& res, 551 std::string_view key, UnpackTypes&&... in) 552 { 553 std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); 554 if (jsonRequest == std::nullopt) 555 { 556 return false; 557 } 558 559 return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 560 } 561 562 template <typename... UnpackTypes> 563 bool readJsonAction(const crow::Request& req, crow::Response& res, 564 const char* key, UnpackTypes&&... in) 565 { 566 nlohmann::json jsonRequest; 567 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 568 { 569 BMCWEB_LOG_DEBUG << "Json value not readable"; 570 return false; 571 } 572 return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); 573 } 574 575 } // namespace json_util 576 } // namespace redfish 577