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 <crow/http_request.h> 19 #include <crow/http_response.h> 20 21 #include <bitset> 22 #include <error_messages.hpp> 23 #include <nlohmann/json.hpp> 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> struct is_optional : std::false_type 48 { 49 }; 50 51 template <typename Type> 52 struct is_optional<std::optional<Type>> : std::true_type 53 { 54 }; 55 56 template <typename Type> 57 constexpr bool is_optional_v = is_optional<Type>::value; 58 59 template <typename Type> struct is_vector : std::false_type 60 { 61 }; 62 63 template <typename Type> struct is_vector<std::vector<Type>> : std::true_type 64 { 65 }; 66 67 template <typename Type> constexpr bool is_vector_v = is_vector<Type>::value; 68 69 template <typename Type> struct is_std_array : std::false_type 70 { 71 }; 72 73 template <typename Type, std::size_t size> 74 struct is_std_array<std::array<Type, size>> : std::true_type 75 { 76 }; 77 78 template <typename Type> 79 constexpr bool is_std_array_v = is_std_array<Type>::value; 80 81 template <typename ToType, typename FromType> 82 bool checkRange(const FromType* from, const std::string& key, 83 nlohmann::json& jsonValue, crow::Response& res) 84 { 85 if (from == nullptr) 86 { 87 BMCWEB_LOG_DEBUG << "Value for key " << key 88 << " was incorrect type: " << __PRETTY_FUNCTION__; 89 messages::propertyValueTypeError(res, jsonValue.dump(), key); 90 return false; 91 } 92 93 if (*from > std::numeric_limits<ToType>::max()) 94 { 95 BMCWEB_LOG_DEBUG << "Value for key " << key 96 << " was greater than max: " << __PRETTY_FUNCTION__; 97 messages::propertyValueNotInList(res, jsonValue.dump(), key); 98 return false; 99 } 100 if (*from < std::numeric_limits<ToType>::lowest()) 101 { 102 BMCWEB_LOG_DEBUG << "Value for key " << key 103 << " was less than min: " << __PRETTY_FUNCTION__; 104 messages::propertyValueNotInList(res, jsonValue.dump(), key); 105 return false; 106 } 107 if constexpr (std::is_floating_point_v<ToType>) 108 { 109 if (std::isnan(*from)) 110 { 111 BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN"; 112 messages::propertyValueNotInList(res, jsonValue.dump(), key); 113 return false; 114 } 115 } 116 117 return true; 118 } 119 120 template <typename Type> 121 void unpackValue(nlohmann::json& jsonValue, const std::string& key, 122 crow::Response& res, Type& value) 123 { 124 if constexpr (std::is_floating_point_v<Type>) 125 { 126 double helper = 0; 127 double* jsonPtr = jsonValue.get_ptr<double*>(); 128 129 if (jsonPtr == nullptr) 130 { 131 int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); 132 if (intPtr != nullptr) 133 { 134 helper = static_cast<double>(*intPtr); 135 jsonPtr = &helper; 136 } 137 } 138 if (!checkRange<Type>(jsonPtr, key, jsonValue, res)) 139 { 140 return; 141 } 142 value = static_cast<Type>(*jsonPtr); 143 } 144 145 else if constexpr (std::is_signed_v<Type>) 146 { 147 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); 148 if (!checkRange<Type>(jsonPtr, key, jsonValue, res)) 149 { 150 return; 151 } 152 value = static_cast<Type>(*jsonPtr); 153 } 154 155 else if constexpr (std::is_unsigned_v<Type>) 156 { 157 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); 158 if (!checkRange<Type>(jsonPtr, key, jsonValue, res)) 159 { 160 return; 161 } 162 value = static_cast<Type>(*jsonPtr); 163 } 164 165 else if constexpr (is_optional_v<Type>) 166 { 167 value.emplace(); 168 unpackValue<typename Type::value_type>(jsonValue, key, res, *value); 169 } 170 else if constexpr (std::is_same_v<nlohmann::json, Type>) 171 { 172 // Must be a complex type. Simple types (int string etc) should be 173 // unpacked directly 174 if (!jsonValue.is_object() && !jsonValue.is_array()) 175 { 176 messages::propertyValueTypeError(res, jsonValue.dump(), key); 177 return; 178 } 179 180 value = std::move(jsonValue); 181 } 182 else if constexpr (is_std_array_v<Type>) 183 { 184 if (!jsonValue.is_array()) 185 { 186 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 187 return; 188 } 189 if (jsonValue.size() != value.size()) 190 { 191 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 192 return; 193 } 194 size_t index = 0; 195 for (const auto& val : jsonValue.items()) 196 { 197 unpackValue<typename Type::value_type>(val.value(), key, res, 198 value[index++]); 199 } 200 } 201 else if constexpr (is_vector_v<Type>) 202 { 203 if (!jsonValue.is_array()) 204 { 205 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 206 return; 207 } 208 209 for (const auto& val : jsonValue.items()) 210 { 211 value.emplace_back(); 212 unpackValue<typename Type::value_type>(val.value(), key, res, 213 value.back()); 214 } 215 } 216 else 217 { 218 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 219 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 220 if (jsonPtr == nullptr) 221 { 222 BMCWEB_LOG_DEBUG 223 << "Value for key " << key 224 << " was incorrect type: " << jsonValue.type_name(); 225 messages::propertyValueTypeError(res, jsonValue.dump(), key); 226 return; 227 } 228 value = std::move(*jsonPtr); 229 } 230 } 231 232 template <size_t Count, size_t Index> 233 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 234 crow::Response& res, std::bitset<Count>& handled) 235 { 236 BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key; 237 messages::propertyUnknown(res, key); 238 } 239 240 template <size_t Count, size_t Index, typename ValueType, 241 typename... UnpackTypes> 242 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 243 crow::Response& res, std::bitset<Count>& handled, 244 const char* keyToCheck, ValueType& valueToFill, 245 UnpackTypes&... in) 246 { 247 if (key != keyToCheck) 248 { 249 readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...); 250 return; 251 } 252 253 handled.set(Index); 254 255 unpackValue<ValueType>(jsonValue, key, res, valueToFill); 256 } 257 258 template <size_t Index = 0, size_t Count> 259 void handleMissing(std::bitset<Count>& handled, crow::Response& res) 260 { 261 } 262 263 template <size_t Index = 0, size_t Count, typename ValueType, 264 typename... UnpackTypes> 265 void handleMissing(std::bitset<Count>& handled, crow::Response& res, 266 const char* key, ValueType& unused, UnpackTypes&... in) 267 { 268 if (!handled.test(Index) && !is_optional_v<ValueType>) 269 { 270 messages::propertyMissing(res, key); 271 } 272 details::handleMissing<Index + 1, Count>(handled, res, in...); 273 } 274 } // namespace details 275 276 template <typename... UnpackTypes> 277 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key, 278 UnpackTypes&... in) 279 { 280 if (!jsonRequest.is_object()) 281 { 282 BMCWEB_LOG_DEBUG << "Json value is not an object"; 283 messages::unrecognizedRequestBody(res); 284 return false; 285 } 286 287 if (jsonRequest.empty()) 288 { 289 BMCWEB_LOG_DEBUG << "Json value is empty"; 290 messages::emptyJSON(res); 291 return false; 292 } 293 294 std::bitset<(sizeof...(in) + 1) / 2> handled(0); 295 for (const auto& item : jsonRequest.items()) 296 { 297 details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>( 298 item.key(), item.value(), res, handled, key, in...); 299 } 300 301 details::handleMissing(handled, res, key, in...); 302 303 return res.result() == boost::beast::http::status::ok; 304 } 305 306 template <typename... UnpackTypes> 307 bool readJson(const crow::Request& req, crow::Response& res, const char* key, 308 UnpackTypes&... in) 309 { 310 nlohmann::json jsonRequest; 311 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 312 { 313 BMCWEB_LOG_DEBUG << "Json value not readable"; 314 return false; 315 } 316 return readJson(jsonRequest, res, key, in...); 317 } 318 319 } // namespace json_util 320 } // namespace redfish 321