/* // 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 #include #include #include #include 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, const std::string& key) { if (from > std::numeric_limits::max()) { BMCWEB_LOG_DEBUG << "Value for key " << key << " was greater than max: " << __PRETTY_FUNCTION__; return false; } if (from < std::numeric_limits::lowest()) { BMCWEB_LOG_DEBUG << "Value for key " << key << " was less than min: " << __PRETTY_FUNCTION__; return false; } if constexpr (std::is_floating_point_v) { if (std::isnan(from)) { BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN"; return false; } } return true; } template UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, const std::string& 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) { // Must be a complex type. Simple types (int string etc) should be // unpacked directly if (!jsonValue.is_object() && !jsonValue.is_array() && !jsonValue.is_null()) { return UnpackErrorCode::invalidType; } 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 " << key << " was incorrect type: " << jsonValue.type_name(); return UnpackErrorCode::invalidType; } value = std::move(*jsonPtr); } return ret; } template bool unpackValue(nlohmann::json& jsonValue, const std::string& 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.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(val.value(), key, res, value[index++]) && ret; } } else if constexpr (IsVector::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(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 bool unpackValue(nlohmann::json& jsonValue, const std::string& 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; } template bool readJsonValues(const std::string& key, nlohmann::json&, crow::Response& res, std::bitset&) { BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key; messages::propertyUnknown(res, key); return false; } template bool readJsonValues(const std::string& key, nlohmann::json& jsonValue, crow::Response& res, std::bitset& handled, const char* keyToCheck, ValueType& valueToFill, UnpackTypes&... in) { bool ret = true; if (key != keyToCheck) { ret = readJsonValues(key, jsonValue, res, handled, in...) && ret; return ret; } handled.set(Index); return unpackValue(jsonValue, key, res, valueToFill) && ret; } template bool handleMissing(std::bitset&, crow::Response&) { return true; } template bool handleMissing(std::bitset& handled, crow::Response& res, const char* key, ValueType&, UnpackTypes&... in) { bool ret = true; if (!handled.test(Index) && !IsOptional::value) { ret = false; messages::propertyMissing(res, key); } return details::handleMissing(handled, res, in...) && ret; } } // namespace details template bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key, UnpackTypes&... in) { bool result = true; if (!jsonRequest.is_object()) { BMCWEB_LOG_DEBUG << "Json value is not an object"; messages::unrecognizedRequestBody(res); return false; } if (jsonRequest.empty()) { BMCWEB_LOG_DEBUG << "Json value is empty"; messages::emptyJSON(res); return false; } std::bitset<(sizeof...(in) + 1) / 2> handled(0); for (const auto& item : jsonRequest.items()) { result = details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>( item.key(), item.value(), res, handled, key, in...) && result; } BMCWEB_LOG_DEBUG << "JSON result is: " << result; return details::handleMissing(handled, res, key, in...) && result; } template bool readJson(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, in...); } template bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key, Type& value) { nlohmann::json::iterator it = jsonData.find(key); if (it == jsonData.end()) { BMCWEB_LOG_DEBUG << "Key " << key << " not exist"; return false; } return details::unpackValue(*it, key, value); } } // namespace json_util } // namespace redfish