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