/* // 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_connection.hpp" #include "http_request.hpp" #include "http_response.hpp" #include "human_sort.hpp" #include "logging.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: no_include // 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 struct IsOptional : std::false_type {}; template struct IsOptional> : std::true_type {}; template struct IsVector : std::false_type {}; template struct IsVector> : std::true_type {}; template struct IsStdArray : std::false_type {}; template struct IsStdArray> : std::true_type {}; enum class UnpackErrorCode { success, invalidType, outOfRange }; template bool checkRange(const FromType& from, std::string_view key) { if (from > std::numeric_limits::max()) { BMCWEB_LOG_DEBUG("Value for key {} was greater than max: {}", key, __PRETTY_FUNCTION__); return false; } if (from < std::numeric_limits::lowest()) { BMCWEB_LOG_DEBUG("Value for key {} was less than min: {}", key, __PRETTY_FUNCTION__); return false; } if constexpr (std::is_floating_point_v) { if (std::isnan(from)) { BMCWEB_LOG_DEBUG("Value for key {} was NAN", key); return false; } } return true; } template UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, std::string_view key, Type& value) { UnpackErrorCode ret = UnpackErrorCode::success; if constexpr (std::is_floating_point_v) { double helper = 0; double* jsonPtr = jsonValue.get_ptr(); if (jsonPtr == nullptr) { int64_t* intPtr = jsonValue.get_ptr(); if (intPtr != nullptr) { helper = static_cast(*intPtr); jsonPtr = &helper; } } if (jsonPtr == nullptr) { return UnpackErrorCode::invalidType; } if (!checkRange(*jsonPtr, key)) { return UnpackErrorCode::outOfRange; } value = static_cast(*jsonPtr); } else if constexpr (std::is_signed_v) { int64_t* jsonPtr = jsonValue.get_ptr(); if (jsonPtr == nullptr) { return UnpackErrorCode::invalidType; } if (!checkRange(*jsonPtr, key)) { return UnpackErrorCode::outOfRange; } value = static_cast(*jsonPtr); } else if constexpr ((std::is_unsigned_v)&&( !std::is_same_v)) { uint64_t* jsonPtr = jsonValue.get_ptr(); if (jsonPtr == nullptr) { return UnpackErrorCode::invalidType; } if (!checkRange(*jsonPtr, key)) { return UnpackErrorCode::outOfRange; } value = static_cast(*jsonPtr); } else if constexpr (std::is_same_v) { value = std::move(jsonValue); } else { using JsonType = std::add_const_t>; JsonType jsonPtr = jsonValue.get_ptr(); if (jsonPtr == nullptr) { BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key, jsonValue.type_name()); return UnpackErrorCode::invalidType; } value = std::move(*jsonPtr); } return ret; } template bool unpackValue(nlohmann::json& jsonValue, std::string_view key, crow::Response& res, Type& value) { bool ret = true; if constexpr (IsOptional::value) { value.emplace(); ret = unpackValue(jsonValue, key, res, *value) && ret; } else if constexpr (IsStdArray::value) { if (!jsonValue.is_array()) { messages::propertyValueTypeError(res, res.jsonValue, key); return false; } if (jsonValue.size() != value.size()) { messages::propertyValueTypeError(res, res.jsonValue, key); return false; } size_t index = 0; for (const auto& val : jsonValue.items()) { ret = unpackValue(val.value(), key, res, value[index++]) && ret; } } else if constexpr (IsVector::value) { if (!jsonValue.is_array()) { messages::propertyValueTypeError(res, res.jsonValue, key); return false; } for (const auto& val : jsonValue.items()) { value.emplace_back(); ret = unpackValue(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, key); } else if (ec == UnpackErrorCode::outOfRange) { messages::propertyValueNotInList(res, jsonValue, key); } return false; } } return ret; } template bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value) { bool ret = true; if constexpr (IsOptional::value) { value.emplace(); ret = unpackValue(jsonValue, key, *value) && ret; } else if constexpr (IsStdArray::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(val.value(), key, value[index++]) && ret; } } else if constexpr (IsVector::value) { if (!jsonValue.is_array()) { return false; } for (const auto& val : jsonValue.items()) { value.emplace_back(); ret = unpackValue(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*, nlohmann::json::object_t*, std::vector*, std::vector*, std::vector*, std::vector*, std::vector*, std::vector*, std::vector*, //std::vector*, std::vector*, std::vector*, std::vector*, std::vector*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional*, std::optional>*, std::optional>*, std::optional>*, std::optional>*, std::optional>*, std::optional>*, std::optional>*, //std::optional>*, std::optional>*, std::optional>*, std::optional>*, std::optional>* >; // 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 toUnpack); inline bool readJsonHelperObject(nlohmann::json::object_t& obj, crow::Response& res, std::span toUnpack) { bool result = true; 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(item.second, key, res, j) && result; if (!result) { return result; } std::vector 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>; return details::unpackValue( 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>; return details::IsOptional::value; }, perUnpack.value); if (isOptional) { continue; } messages::propertyMissing(res, perUnpack.key); result = false; } } return result; } inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res, std::span toUnpack) { nlohmann::json::object_t* obj = jsonRequest.get_ptr(); if (obj == nullptr) { BMCWEB_LOG_DEBUG("Json value is not an object"); messages::unrecognizedRequestBody(res); return false; } return readJsonHelperObject(*obj, res, toUnpack); } inline void packVariant(std::span /*toPack*/) {} template void packVariant(std::span 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(in)...); } template bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res, std::string_view key, FirstType&& first, UnpackTypes&&... in) { const std::size_t n = sizeof...(UnpackTypes) + 2; std::array toUnpack2; packVariant(toUnpack2, key, first, std::forward(in)...); return readJsonHelperObject(jsonRequest, res, toUnpack2); } template bool readJson(nlohmann::json& jsonRequest, crow::Response& res, std::string_view key, FirstType&& first, UnpackTypes&&... in) { nlohmann::json::object_t* obj = jsonRequest.get_ptr(); if (obj == nullptr) { BMCWEB_LOG_DEBUG("Json value is not an object"); messages::unrecognizedRequestBody(res); return false; } return readJsonObject(*obj, res, key, std::forward(first), std::forward(in)...); } inline std::optional 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(); 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& 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(*object)}; } template bool readJsonPatch(const crow::Request& req, crow::Response& res, std::string_view key, UnpackTypes&&... in) { std::optional jsonRequest = readJsonPatchHelper(req, res); if (!jsonRequest) { return false; } nlohmann::json::object_t* object = jsonRequest->get_ptr(); if (object == nullptr) { BMCWEB_LOG_DEBUG("Json value is empty"); messages::emptyJSON(res); return false; } return readJsonObject(*object, res, key, std::forward(in)...); } template 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; } nlohmann::json::object_t* object = jsonRequest.get_ptr(); if (object == nullptr) { BMCWEB_LOG_DEBUG("Json value is empty"); messages::emptyJSON(res); return false; } return readJsonObject(*object, res, key, std::forward(in)...); } // Determines if two json objects are less, based on the presence of the // @odata.id key inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b) { using object_t = nlohmann::json::object_t; const object_t* aObj = a.get_ptr(); const object_t* bObj = b.get_ptr(); if (aObj == nullptr) { if (bObj == nullptr) { return 0; } return -1; } if (bObj == nullptr) { return 1; } object_t::const_iterator aIt = aObj->find("@odata.id"); object_t::const_iterator bIt = bObj->find("@odata.id"); // If either object doesn't have the key, they get "sorted" to the end. if (aIt == aObj->end()) { if (bIt == bObj->end()) { return 0; } return -1; } if (bIt == bObj->end()) { return 1; } const nlohmann::json::string_t* nameA = aIt->second.get_ptr(); const nlohmann::json::string_t* nameB = bIt->second.get_ptr(); // If either object doesn't have a string as the key, they get "sorted" to // the end. if (nameA == nullptr) { if (nameB == nullptr) { return 0; } return -1; } if (nameB == nullptr) { return 1; } boost::urls::url_view aUrl(*nameA); boost::urls::url_view bUrl(*nameB); auto segmentsAIt = aUrl.segments().begin(); auto segmentsBIt = bUrl.segments().begin(); while (true) { if (segmentsAIt == aUrl.segments().end()) { if (segmentsBIt == bUrl.segments().end()) { return 0; } return -1; } if (segmentsBIt == bUrl.segments().end()) { return 1; } int res = alphanumComp(*segmentsAIt, *segmentsBIt); if (res != 0) { return res; } segmentsAIt++; segmentsBIt++; } }; struct ODataObjectLess { bool operator()(const nlohmann::json& left, const nlohmann::json& right) const { return odataObjectCmp(left, right) < 0; } }; // Sort the JSON array by |element[key]|. // Elements without |key| or type of |element[key]| is not string are smaller // those whose |element[key]| is string. inline void sortJsonArrayByOData(nlohmann::json::array_t& array) { std::ranges::sort(array, ODataObjectLess()); } // Returns the estimated size of the JSON value // The implementation walks through every key and every value, accumulates the // total size of keys and values. // Ideally, we should use a custom allocator that nlohmann JSON supports. // Assumption made: // 1. number: 8 characters // 2. boolean: 5 characters (False) // 3. string: len(str) + 2 characters (quote) // 4. bytes: len(bytes) characters // 5. null: 4 characters (null) uint64_t getEstimatedJsonSize(const nlohmann::json& root); } // namespace json_util } // namespace redfish