| /* |
| // Copyright (c) 2018 Intel Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| */ |
| #pragma once |
| |
| #include "error_messages.hpp" |
| #include "http_request.hpp" |
| #include "http_response.hpp" |
| #include "human_sort.hpp" |
| #include "logging.hpp" |
| #include "nlohmann/json.hpp" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <limits> |
| #include <map> |
| #include <optional> |
| #include <span> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| // IWYU pragma: no_include <stdint.h> |
| // IWYU pragma: no_forward_declare crow::Request |
| |
| namespace redfish |
| { |
| |
| namespace json_util |
| { |
| |
| /** |
| * @brief Processes request to extract JSON from its body. If it fails, adds |
| * MalformedJSON message to response and ends it. |
| * |
| * @param[io] res Response object |
| * @param[in] req Request object |
| * @param[out] reqJson JSON object extracted from request's body |
| * |
| * @return true if JSON is valid, false when JSON is invalid and response has |
| * been filled with message and ended. |
| */ |
| bool processJsonFromRequest(crow::Response& res, const crow::Request& req, |
| nlohmann::json& reqJson); |
| namespace details |
| { |
| |
| template <typename Type> |
| struct IsOptional : std::false_type |
| {}; |
| |
| template <typename Type> |
| struct IsOptional<std::optional<Type>> : std::true_type |
| {}; |
| |
| template <typename Type> |
| struct IsVector : std::false_type |
| {}; |
| |
| template <typename Type> |
| struct IsVector<std::vector<Type>> : std::true_type |
| {}; |
| |
| template <typename Type> |
| struct IsStdArray : std::false_type |
| {}; |
| |
| template <typename Type, std::size_t size> |
| struct IsStdArray<std::array<Type, size>> : std::true_type |
| {}; |
| |
| enum class UnpackErrorCode : std::uint8_t |
| { |
| success, |
| invalidType, |
| outOfRange |
| }; |
| |
| template <typename ToType, typename FromType> |
| bool checkRange(const FromType& from, std::string_view key) |
| { |
| if (from > std::numeric_limits<ToType>::max()) |
| { |
| BMCWEB_LOG_DEBUG << "Value for key " << key |
| << " was greater than max: " << __PRETTY_FUNCTION__; |
| return false; |
| } |
| if (from < std::numeric_limits<ToType>::lowest()) |
| { |
| BMCWEB_LOG_DEBUG << "Value for key " << key |
| << " was less than min: " << __PRETTY_FUNCTION__; |
| return false; |
| } |
| if constexpr (std::is_floating_point_v<ToType>) |
| { |
| if (std::isnan(from)) |
| { |
| BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| template <typename Type> |
| UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, |
| std::string_view key, Type& value) |
| { |
| UnpackErrorCode ret = UnpackErrorCode::success; |
| |
| if constexpr (std::is_floating_point_v<Type>) |
| { |
| double helper = 0; |
| double* jsonPtr = jsonValue.get_ptr<double*>(); |
| |
| if (jsonPtr == nullptr) |
| { |
| int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); |
| if (intPtr != nullptr) |
| { |
| helper = static_cast<double>(*intPtr); |
| jsonPtr = &helper; |
| } |
| } |
| if (jsonPtr == nullptr) |
| { |
| return UnpackErrorCode::invalidType; |
| } |
| if (!checkRange<Type>(*jsonPtr, key)) |
| { |
| return UnpackErrorCode::outOfRange; |
| } |
| value = static_cast<Type>(*jsonPtr); |
| } |
| |
| else if constexpr (std::is_signed_v<Type>) |
| { |
| int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); |
| if (jsonPtr == nullptr) |
| { |
| return UnpackErrorCode::invalidType; |
| } |
| if (!checkRange<Type>(*jsonPtr, key)) |
| { |
| return UnpackErrorCode::outOfRange; |
| } |
| value = static_cast<Type>(*jsonPtr); |
| } |
| |
| else if constexpr ((std::is_unsigned_v<Type>)&&( |
| !std::is_same_v<bool, Type>)) |
| { |
| uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); |
| if (jsonPtr == nullptr) |
| { |
| return UnpackErrorCode::invalidType; |
| } |
| if (!checkRange<Type>(*jsonPtr, key)) |
| { |
| return UnpackErrorCode::outOfRange; |
| } |
| value = static_cast<Type>(*jsonPtr); |
| } |
| |
| else if constexpr (std::is_same_v<nlohmann::json, Type>) |
| { |
| value = std::move(jsonValue); |
| } |
| else |
| { |
| using JsonType = std::add_const_t<std::add_pointer_t<Type>>; |
| JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); |
| if (jsonPtr == nullptr) |
| { |
| BMCWEB_LOG_DEBUG |
| << "Value for key " << key |
| << " was incorrect type: " << jsonValue.type_name(); |
| return UnpackErrorCode::invalidType; |
| } |
| value = std::move(*jsonPtr); |
| } |
| return ret; |
| } |
| |
| template <typename Type> |
| bool unpackValue(nlohmann::json& jsonValue, std::string_view key, |
| crow::Response& res, Type& value) |
| { |
| bool ret = true; |
| |
| if constexpr (IsOptional<Type>::value) |
| { |
| value.emplace(); |
| ret = unpackValue<typename Type::value_type>(jsonValue, key, res, |
| *value) && |
| ret; |
| } |
| else if constexpr (IsStdArray<Type>::value) |
| { |
| if (!jsonValue.is_array()) |
| { |
| messages::propertyValueTypeError( |
| res, |
| res.jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace), |
| key); |
| return false; |
| } |
| if (jsonValue.size() != value.size()) |
| { |
| messages::propertyValueTypeError( |
| res, |
| res.jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace), |
| key); |
| return false; |
| } |
| size_t index = 0; |
| for (const auto& val : jsonValue.items()) |
| { |
| ret = unpackValue<typename Type::value_type>(val.value(), key, res, |
| value[index++]) && |
| ret; |
| } |
| } |
| else if constexpr (IsVector<Type>::value) |
| { |
| if (!jsonValue.is_array()) |
| { |
| messages::propertyValueTypeError( |
| res, |
| res.jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace), |
| key); |
| return false; |
| } |
| |
| for (const auto& val : jsonValue.items()) |
| { |
| value.emplace_back(); |
| ret = unpackValue<typename Type::value_type>(val.value(), key, res, |
| value.back()) && |
| ret; |
| } |
| } |
| else |
| { |
| UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); |
| if (ec != UnpackErrorCode::success) |
| { |
| if (ec == UnpackErrorCode::invalidType) |
| { |
| messages::propertyValueTypeError( |
| res, |
| jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace), |
| key); |
| } |
| else if (ec == UnpackErrorCode::outOfRange) |
| { |
| messages::propertyValueNotInList( |
| res, |
| jsonValue.dump(2, ' ', true, |
| nlohmann::json::error_handler_t::replace), |
| key); |
| } |
| return false; |
| } |
| } |
| |
| return ret; |
| } |
| |
| template <typename Type> |
| bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) |
| { |
| bool ret = true; |
| if constexpr (IsOptional<Type>::value) |
| { |
| value.emplace(); |
| ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && |
| ret; |
| } |
| else if constexpr (IsStdArray<Type>::value) |
| { |
| if (!jsonValue.is_array()) |
| { |
| return false; |
| } |
| if (jsonValue.size() != value.size()) |
| { |
| return false; |
| } |
| size_t index = 0; |
| for (const auto& val : jsonValue.items()) |
| { |
| ret = unpackValue<typename Type::value_type>(val.value(), key, |
| value[index++]) && |
| ret; |
| } |
| } |
| else if constexpr (IsVector<Type>::value) |
| { |
| if (!jsonValue.is_array()) |
| { |
| return false; |
| } |
| |
| for (const auto& val : jsonValue.items()) |
| { |
| value.emplace_back(); |
| ret = unpackValue<typename Type::value_type>(val.value(), key, |
| value.back()) && |
| ret; |
| } |
| } |
| else |
| { |
| UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); |
| if (ec != UnpackErrorCode::success) |
| { |
| return false; |
| } |
| } |
| |
| return ret; |
| } |
| } // namespace details |
| |
| // clang-format off |
| using UnpackVariant = std::variant< |
| uint8_t*, |
| uint16_t*, |
| int16_t*, |
| uint32_t*, |
| int32_t*, |
| uint64_t*, |
| int64_t*, |
| bool*, |
| double*, |
| std::string*, |
| nlohmann::json*, |
| std::vector<uint8_t>*, |
| std::vector<uint16_t>*, |
| std::vector<int16_t>*, |
| std::vector<uint32_t>*, |
| std::vector<int32_t>*, |
| std::vector<uint64_t>*, |
| std::vector<int64_t>*, |
| //std::vector<bool>*, |
| std::vector<double>*, |
| std::vector<std::string>*, |
| std::vector<nlohmann::json>*, |
| std::optional<uint8_t>*, |
| std::optional<uint16_t>*, |
| std::optional<int16_t>*, |
| std::optional<uint32_t>*, |
| std::optional<int32_t>*, |
| std::optional<uint64_t>*, |
| std::optional<int64_t>*, |
| std::optional<bool>*, |
| std::optional<double>*, |
| std::optional<std::string>*, |
| std::optional<nlohmann::json>*, |
| std::optional<std::vector<uint8_t>>*, |
| std::optional<std::vector<uint16_t>>*, |
| std::optional<std::vector<int16_t>>*, |
| std::optional<std::vector<uint32_t>>*, |
| std::optional<std::vector<int32_t>>*, |
| std::optional<std::vector<uint64_t>>*, |
| std::optional<std::vector<int64_t>>*, |
| //std::optional<std::vector<bool>>*, |
| std::optional<std::vector<double>>*, |
| std::optional<std::vector<std::string>>*, |
| std::optional<std::vector<nlohmann::json>>* |
| >; |
| // clang-format on |
| |
| struct PerUnpack |
| { |
| std::string_view key; |
| UnpackVariant value; |
| bool complete = false; |
| }; |
| |
| inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, |
| std::span<PerUnpack> toUnpack) |
| { |
| bool result = true; |
| nlohmann::json::object_t* obj = |
| jsonRequest.get_ptr<nlohmann::json::object_t*>(); |
| if (obj == nullptr) |
| { |
| BMCWEB_LOG_DEBUG << "Json value is not an object"; |
| messages::unrecognizedRequestBody(res); |
| return false; |
| } |
| for (auto& item : *obj) |
| { |
| size_t unpackIndex = 0; |
| for (; unpackIndex < toUnpack.size(); unpackIndex++) |
| { |
| PerUnpack& unpackSpec = toUnpack[unpackIndex]; |
| std::string_view key = unpackSpec.key; |
| size_t keysplitIndex = key.find('/'); |
| std::string_view leftover; |
| if (keysplitIndex != std::string_view::npos) |
| { |
| leftover = key.substr(keysplitIndex + 1); |
| key = key.substr(0, keysplitIndex); |
| } |
| |
| if (key != item.first || unpackSpec.complete) |
| { |
| continue; |
| } |
| |
| // Sublevel key |
| if (!leftover.empty()) |
| { |
| // Include the slash in the key so we can compare later |
| key = unpackSpec.key.substr(0, keysplitIndex + 1); |
| nlohmann::json j; |
| result = details::unpackValue<nlohmann::json>(item.second, key, |
| res, j) && |
| result; |
| if (!result) |
| { |
| return result; |
| } |
| |
| std::vector<PerUnpack> nextLevel; |
| for (PerUnpack& p : toUnpack) |
| { |
| if (!p.key.starts_with(key)) |
| { |
| continue; |
| } |
| std::string_view thisLeftover = p.key.substr(key.size()); |
| nextLevel.push_back({thisLeftover, p.value, false}); |
| p.complete = true; |
| } |
| |
| result = readJsonHelper(j, res, nextLevel) && result; |
| break; |
| } |
| |
| result = std::visit( |
| [&item, &unpackSpec, &res](auto&& val) { |
| using ContainedT = |
| std::remove_pointer_t<std::decay_t<decltype(val)>>; |
| return details::unpackValue<ContainedT>( |
| item.second, unpackSpec.key, res, *val); |
| }, |
| unpackSpec.value) && |
| result; |
| |
| unpackSpec.complete = true; |
| break; |
| } |
| |
| if (unpackIndex == toUnpack.size()) |
| { |
| messages::propertyUnknown(res, item.first); |
| result = false; |
| } |
| } |
| |
| for (PerUnpack& perUnpack : toUnpack) |
| { |
| if (!perUnpack.complete) |
| { |
| bool isOptional = std::visit( |
| [](auto&& val) { |
| using ContainedType = |
| std::remove_pointer_t<std::decay_t<decltype(val)>>; |
| return details::IsOptional<ContainedType>::value; |
| }, |
| perUnpack.value); |
| if (isOptional) |
| { |
| continue; |
| } |
| messages::propertyMissing(res, perUnpack.key); |
| result = false; |
| } |
| } |
| return result; |
| } |
| |
| inline void packVariant(std::span<PerUnpack> /*toPack*/) |
| {} |
| |
| template <typename FirstType, typename... UnpackTypes> |
| void packVariant(std::span<PerUnpack> toPack, std::string_view key, |
| FirstType& first, UnpackTypes&&... in) |
| { |
| if (toPack.empty()) |
| { |
| return; |
| } |
| toPack[0].key = key; |
| toPack[0].value = &first; |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) |
| packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...); |
| } |
| |
| template <typename FirstType, typename... UnpackTypes> |
| bool readJson(nlohmann::json& jsonRequest, crow::Response& res, |
| std::string_view key, FirstType&& first, UnpackTypes&&... in) |
| { |
| const std::size_t n = sizeof...(UnpackTypes) + 2; |
| std::array<PerUnpack, n / 2> toUnpack2; |
| packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...); |
| return readJsonHelper(jsonRequest, res, toUnpack2); |
| } |
| |
| inline std::optional<nlohmann::json> |
| readJsonPatchHelper(const crow::Request& req, crow::Response& res) |
| { |
| nlohmann::json jsonRequest; |
| if (!json_util::processJsonFromRequest(res, req, jsonRequest)) |
| { |
| BMCWEB_LOG_DEBUG << "Json value not readable"; |
| return std::nullopt; |
| } |
| nlohmann::json::object_t* object = |
| jsonRequest.get_ptr<nlohmann::json::object_t*>(); |
| if (object == nullptr || object->empty()) |
| { |
| BMCWEB_LOG_DEBUG << "Json value is empty"; |
| messages::emptyJSON(res); |
| return std::nullopt; |
| } |
| std::erase_if(*object, |
| [](const std::pair<std::string, nlohmann::json>& item) { |
| return item.first.starts_with("@odata."); |
| }); |
| if (object->empty()) |
| { |
| // If the update request only contains OData annotations, the service |
| // should return the HTTP 400 Bad Request status code with the |
| // NoOperation message from the Base Message Registry, ... |
| messages::noOperation(res); |
| return std::nullopt; |
| } |
| |
| return {std::move(jsonRequest)}; |
| } |
| |
| template <typename... UnpackTypes> |
| bool readJsonPatch(const crow::Request& req, crow::Response& res, |
| std::string_view key, UnpackTypes&&... in) |
| { |
| std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res); |
| if (jsonRequest == std::nullopt) |
| { |
| return false; |
| } |
| |
| return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); |
| } |
| |
| /* readJsonAction |
| * |
| * returns false if argument parsing failed |
| */ |
| |
| template <typename... UnpackTypes> |
| bool readJsonAction(const crow::Request& req, crow::Response& res, |
| const char* key, UnpackTypes&&... in) |
| { |
| nlohmann::json jsonRequest; |
| if (!json_util::processJsonFromRequest(res, req, jsonRequest)) |
| { |
| BMCWEB_LOG_DEBUG << "Json value not readable"; |
| return false; |
| } |
| return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...); |
| } |
| |
| // Sort the JSON array by |element[key]|. |
| // Each element shall contains |key| and the corresponding value shall be of |
| // type |nlohmann::json::string_t|; otherwise, returns false. |
| // Elements without |key| or type of |element[key]| is not string are smaller |
| // those whose |element[key]| is string. |
| inline bool sortJsonArrayByKey(const std::string& key, |
| nlohmann::json::array_t& array) |
| { |
| bool succeeded = true; |
| std::sort(array.begin(), array.end(), |
| [comparator = AlphanumLess<nlohmann::json::string_t>(), |
| &succeeded, |
| &key](const nlohmann::json& a, const nlohmann::json& b) mutable { |
| auto aIt = a.find(key); |
| auto bIt = b.find(key); |
| if (aIt == a.end()) |
| { |
| succeeded = false; |
| return true; |
| } |
| if (bIt == b.end()) |
| { |
| succeeded = false; |
| return false; |
| } |
| const nlohmann::json::string_t* nameA = |
| aIt->get_ptr<const nlohmann::json::string_t*>(); |
| const nlohmann::json::string_t* nameB = |
| bIt->get_ptr<const nlohmann::json::string_t*>(); |
| if (nameA == nullptr) |
| { |
| succeeded = false; |
| return true; |
| } |
| if (nameB == nullptr) |
| { |
| succeeded = false; |
| return false; |
| } |
| return comparator(*nameA, *nameB); |
| }); |
| return succeeded; |
| } |
| |
| } // namespace json_util |
| } // namespace redfish |