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 <http_request.h> 19 #include <http_response.h> 20 21 #include <error_messages.hpp> 22 #include <nlohmann/json.hpp> 23 24 #include <bitset> 25 26 namespace redfish 27 { 28 29 namespace json_util 30 { 31 32 /** 33 * @brief Processes request to extract JSON from its body. If it fails, adds 34 * MalformedJSON message to response and ends it. 35 * 36 * @param[io] res Response object 37 * @param[in] req Request object 38 * @param[out] reqJson JSON object extracted from request's body 39 * 40 * @return true if JSON is valid, false when JSON is invalid and response has 41 * been filled with message and ended. 42 */ 43 bool processJsonFromRequest(crow::Response& res, const crow::Request& req, 44 nlohmann::json& reqJson); 45 namespace details 46 { 47 48 template <typename Type> 49 struct is_optional : std::false_type 50 {}; 51 52 template <typename Type> 53 struct is_optional<std::optional<Type>> : std::true_type 54 {}; 55 56 template <typename Type> 57 constexpr bool is_optional_v = is_optional<Type>::value; 58 59 template <typename Type> 60 struct is_vector : std::false_type 61 {}; 62 63 template <typename Type> 64 struct is_vector<std::vector<Type>> : std::true_type 65 {}; 66 67 template <typename Type> 68 constexpr bool is_vector_v = is_vector<Type>::value; 69 70 template <typename Type> 71 struct is_std_array : std::false_type 72 {}; 73 74 template <typename Type, std::size_t size> 75 struct is_std_array<std::array<Type, size>> : std::true_type 76 {}; 77 78 template <typename Type> 79 constexpr bool is_std_array_v = is_std_array<Type>::value; 80 81 enum class UnpackErrorCode 82 { 83 success, 84 invalidType, 85 outOfRange 86 }; 87 88 template <typename ToType, typename FromType> 89 bool checkRange(const FromType& from, const std::string& key) 90 { 91 if (from > std::numeric_limits<ToType>::max()) 92 { 93 BMCWEB_LOG_DEBUG << "Value for key " << key 94 << " was greater than max: " << __PRETTY_FUNCTION__; 95 return false; 96 } 97 if (from < std::numeric_limits<ToType>::lowest()) 98 { 99 BMCWEB_LOG_DEBUG << "Value for key " << key 100 << " was less than min: " << __PRETTY_FUNCTION__; 101 return false; 102 } 103 if constexpr (std::is_floating_point_v<ToType>) 104 { 105 if (std::isnan(from)) 106 { 107 BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN"; 108 return false; 109 } 110 } 111 112 return true; 113 } 114 115 template <typename Type> 116 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue, 117 const std::string& key, Type& value) 118 { 119 UnpackErrorCode ret = UnpackErrorCode::success; 120 121 if constexpr (std::is_floating_point_v<Type>) 122 { 123 double helper = 0; 124 double* jsonPtr = jsonValue.get_ptr<double*>(); 125 126 if (jsonPtr == nullptr) 127 { 128 int64_t* intPtr = jsonValue.get_ptr<int64_t*>(); 129 if (intPtr != nullptr) 130 { 131 helper = static_cast<double>(*intPtr); 132 jsonPtr = &helper; 133 } 134 } 135 if (jsonPtr == nullptr) 136 { 137 return UnpackErrorCode::invalidType; 138 } 139 if (!checkRange<Type>(*jsonPtr, key)) 140 { 141 return UnpackErrorCode::outOfRange; 142 } 143 value = static_cast<Type>(*jsonPtr); 144 } 145 146 else if constexpr (std::is_signed_v<Type>) 147 { 148 int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>(); 149 if (jsonPtr == nullptr) 150 { 151 return UnpackErrorCode::invalidType; 152 } 153 if (!checkRange<Type>(*jsonPtr, key)) 154 { 155 return UnpackErrorCode::outOfRange; 156 } 157 value = static_cast<Type>(*jsonPtr); 158 } 159 160 else if constexpr ((std::is_unsigned_v<Type>)&&( 161 !std::is_same_v<bool, Type>)) 162 { 163 uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>(); 164 if (jsonPtr == nullptr) 165 { 166 return UnpackErrorCode::invalidType; 167 } 168 if (!checkRange<Type>(*jsonPtr, key)) 169 { 170 return UnpackErrorCode::outOfRange; 171 } 172 value = static_cast<Type>(*jsonPtr); 173 } 174 175 else if constexpr (std::is_same_v<nlohmann::json, Type>) 176 { 177 // Must be a complex type. Simple types (int string etc) should be 178 // unpacked directly 179 if (!jsonValue.is_object() && !jsonValue.is_array() && 180 !jsonValue.is_null()) 181 { 182 return UnpackErrorCode::invalidType; 183 } 184 185 value = std::move(jsonValue); 186 } 187 else 188 { 189 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 190 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 191 if (jsonPtr == nullptr) 192 { 193 BMCWEB_LOG_DEBUG 194 << "Value for key " << key 195 << " was incorrect type: " << jsonValue.type_name(); 196 return UnpackErrorCode::invalidType; 197 } 198 value = std::move(*jsonPtr); 199 } 200 return ret; 201 } 202 203 template <typename Type> 204 bool unpackValue(nlohmann::json& jsonValue, const std::string& key, 205 crow::Response& res, Type& value) 206 { 207 bool ret = true; 208 209 if constexpr (is_optional_v<Type>) 210 { 211 value.emplace(); 212 ret = unpackValue<typename Type::value_type>(jsonValue, key, res, 213 *value) && 214 ret; 215 } 216 else if constexpr (is_std_array_v<Type>) 217 { 218 if (!jsonValue.is_array()) 219 { 220 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 221 return false; 222 } 223 if (jsonValue.size() != value.size()) 224 { 225 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 226 return false; 227 } 228 size_t index = 0; 229 for (const auto& val : jsonValue.items()) 230 { 231 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 232 value[index++]) && 233 ret; 234 } 235 } 236 else if constexpr (is_vector_v<Type>) 237 { 238 if (!jsonValue.is_array()) 239 { 240 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 241 return false; 242 } 243 244 for (const auto& val : jsonValue.items()) 245 { 246 value.emplace_back(); 247 ret = unpackValue<typename Type::value_type>(val.value(), key, res, 248 value.back()) && 249 ret; 250 } 251 } 252 else 253 { 254 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 255 if (ec != UnpackErrorCode::success) 256 { 257 if (ec == UnpackErrorCode::invalidType) 258 { 259 messages::propertyValueTypeError(res, jsonValue.dump(), key); 260 } 261 else if (ec == UnpackErrorCode::outOfRange) 262 { 263 messages::propertyValueNotInList(res, jsonValue.dump(), key); 264 } 265 return false; 266 } 267 } 268 269 return ret; 270 } 271 272 template <typename Type> 273 bool unpackValue(nlohmann::json& jsonValue, const std::string& key, Type& value) 274 { 275 bool ret = true; 276 if constexpr (is_optional_v<Type>) 277 { 278 value.emplace(); 279 ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) && 280 ret; 281 } 282 else if constexpr (is_std_array_v<Type>) 283 { 284 if (!jsonValue.is_array()) 285 { 286 return false; 287 } 288 if (jsonValue.size() != value.size()) 289 { 290 return false; 291 } 292 size_t index = 0; 293 for (const auto& val : jsonValue.items()) 294 { 295 ret = unpackValue<typename Type::value_type>(val.value(), key, 296 value[index++]) && 297 ret; 298 } 299 } 300 else if constexpr (is_vector_v<Type>) 301 { 302 if (!jsonValue.is_array()) 303 { 304 return false; 305 } 306 307 for (const auto& val : jsonValue.items()) 308 { 309 value.emplace_back(); 310 ret = unpackValue<typename Type::value_type>(val.value(), key, 311 value.back()) && 312 ret; 313 } 314 } 315 else 316 { 317 UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value); 318 if (ec != UnpackErrorCode::success) 319 { 320 return false; 321 } 322 } 323 324 return ret; 325 } 326 327 template <size_t Count, size_t Index> 328 bool readJsonValues(const std::string& key, nlohmann::json&, 329 crow::Response& res, std::bitset<Count>&) 330 { 331 BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key; 332 messages::propertyUnknown(res, key); 333 return false; 334 } 335 336 template <size_t Count, size_t Index, typename ValueType, 337 typename... UnpackTypes> 338 bool readJsonValues(const std::string& key, nlohmann::json& jsonValue, 339 crow::Response& res, std::bitset<Count>& handled, 340 const char* keyToCheck, ValueType& valueToFill, 341 UnpackTypes&... in) 342 { 343 bool ret = true; 344 if (key != keyToCheck) 345 { 346 ret = readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, 347 in...) && 348 ret; 349 return ret; 350 } 351 352 handled.set(Index); 353 354 return unpackValue<ValueType>(jsonValue, key, res, valueToFill) && ret; 355 } 356 357 template <size_t Index = 0, size_t Count> 358 bool handleMissing(std::bitset<Count>&, crow::Response&) 359 { 360 return true; 361 } 362 363 template <size_t Index = 0, size_t Count, typename ValueType, 364 typename... UnpackTypes> 365 bool handleMissing(std::bitset<Count>& handled, crow::Response& res, 366 const char* key, ValueType&, UnpackTypes&... in) 367 { 368 bool ret = true; 369 if (!handled.test(Index) && !is_optional_v<ValueType>) 370 { 371 ret = false; 372 messages::propertyMissing(res, key); 373 } 374 return details::handleMissing<Index + 1, Count>(handled, res, in...) && ret; 375 } 376 } // namespace details 377 378 template <typename... UnpackTypes> 379 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key, 380 UnpackTypes&... in) 381 { 382 bool result = true; 383 if (!jsonRequest.is_object()) 384 { 385 BMCWEB_LOG_DEBUG << "Json value is not an object"; 386 messages::unrecognizedRequestBody(res); 387 return false; 388 } 389 390 if (jsonRequest.empty()) 391 { 392 BMCWEB_LOG_DEBUG << "Json value is empty"; 393 messages::emptyJSON(res); 394 return false; 395 } 396 397 std::bitset<(sizeof...(in) + 1) / 2> handled(0); 398 for (const auto& item : jsonRequest.items()) 399 { 400 result = 401 details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>( 402 item.key(), item.value(), res, handled, key, in...) && 403 result; 404 } 405 406 BMCWEB_LOG_DEBUG << "JSON result is: " << result; 407 408 return details::handleMissing(handled, res, key, in...) && result; 409 } 410 411 template <typename... UnpackTypes> 412 bool readJson(const crow::Request& req, crow::Response& res, const char* key, 413 UnpackTypes&... in) 414 { 415 nlohmann::json jsonRequest; 416 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 417 { 418 BMCWEB_LOG_DEBUG << "Json value not readable"; 419 return false; 420 } 421 return readJson(jsonRequest, res, key, in...); 422 } 423 424 template <typename Type> 425 bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key, 426 Type& value) 427 { 428 nlohmann::json jsonValue = jsonData[key]; 429 if (jsonValue.is_null()) 430 { 431 BMCWEB_LOG_DEBUG << "Key " << key << " not exist"; 432 return false; 433 } 434 435 return details::unpackValue(jsonValue, key, value); 436 } 437 438 } // namespace json_util 439 } // namespace redfish 440