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