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 namespace redfish 25 { 26 27 namespace json_util 28 { 29 30 /** 31 * @brief Defines JSON utils operation status 32 */ 33 enum class Result 34 { 35 SUCCESS, 36 NOT_EXIST, 37 WRONG_TYPE, 38 NULL_POINTER 39 }; 40 41 /** 42 * @brief Describes JSON utils messages requirement 43 */ 44 enum class MessageSetting 45 { 46 NONE = 0x0, ///< No messages will be added 47 MISSING = 0x1, ///< PropertyMissing message will be added 48 TYPE_ERROR = 0x2 ///< PropertyValueTypeError message will be added 49 }; 50 51 /** 52 * @brief Wrapper function for extracting string from JSON object without 53 * throwing exceptions 54 * 55 * @param[in] fieldName Name of requested field 56 * @param[in] json JSON object from which field should be extracted 57 * @param[out] output Variable to which extracted will be written in case 58 * of success 59 * 60 * @return Result informing about operation status, output will be 61 * written only in case of Result::SUCCESS 62 */ 63 Result getString(const char* fieldName, const nlohmann::json& json, 64 const std::string*& output); 65 66 /** 67 * @brief Wrapper function for extracting object from JSON object without 68 * throwing exceptions 69 * 70 * @param[in] fieldName Name of requested field 71 * @param[in] json JSON object from which field should be extracted 72 * @param[out] output Variable to which extracted will be written in case 73 * of success 74 * 75 * @return Result informing about operation status, output will be 76 * written only in case of Result::SUCCESS 77 */ 78 Result getObject(const char* fieldName, const nlohmann::json& json, 79 nlohmann::json* output); 80 81 /** 82 * @brief Wrapper function for extracting array from JSON object without 83 * throwing exceptions 84 * 85 * @param[in] fieldName Name of requested field 86 * @param[in] json JSON object from which field should be extracted 87 * @param[out] output Variable to which extracted will be written in case 88 * of success 89 * 90 * @return Result informing about operation status, output will be 91 * written only in case of Result::SUCCESS 92 */ 93 Result getArray(const char* fieldName, const nlohmann::json& json, 94 nlohmann::json* output); 95 96 /** 97 * @brief Wrapper function for extracting int from JSON object without 98 * throwing exceptions 99 * 100 * @param[in] fieldName Name of requested field 101 * @param[in] json JSON object from which field should be extracted 102 * @param[out] output Variable to which extracted will be written in case 103 * of success 104 * 105 * @return Result informing about operation status, output will be 106 * written only in case of Result::SUCCESS 107 */ 108 Result getInt(const char* fieldName, const nlohmann::json& json, 109 int64_t& output); 110 111 /** 112 * @brief Wrapper function for extracting uint from JSON object without 113 * throwing exceptions 114 * 115 * @param[in] fieldName Name of requested field 116 * @param[in] json JSON object from which field should be extracted 117 * @param[out] output Variable to which extracted will be written in case 118 * of success 119 * 120 * @return Result informing about operation status, output will be 121 * written only in case of Result::SUCCESS 122 */ 123 Result getUnsigned(const char* fieldName, const nlohmann::json& json, 124 uint64_t& output); 125 126 /** 127 * @brief Wrapper function for extracting bool from JSON object without 128 * throwing exceptions 129 * 130 * @param[in] fieldName Name of requested field 131 * @param[in] json JSON object from which field should be extracted 132 * @param[out] output Variable to which extracted will be written in case 133 * of success 134 * 135 * @return Result informing about operation status, output will be 136 * written only in case of Result::SUCCESS 137 */ 138 Result getBool(const char* fieldName, const nlohmann::json& json, bool& output); 139 140 /** 141 * @brief Wrapper function for extracting float from JSON object without 142 * throwing exceptions (nlohmann stores JSON floats as C++ doubles) 143 * 144 * @param[in] fieldName Name of requested field 145 * @param[in] json JSON object from which field should be extracted 146 * @param[out] output Variable to which extracted will be written in case 147 * of success 148 * 149 * @return Result informing about operation status, output will be 150 * written only in case of Result::SUCCESS 151 */ 152 Result getDouble(const char* fieldName, const nlohmann::json& json, 153 double& output); 154 155 /** 156 * @brief Wrapper function for extracting string from JSON object without 157 * throwing exceptions 158 * 159 * @param[in] fieldName Name of requested field 160 * @param[in] json JSON object from which field should be extracted 161 * @param[out] output Variable to which extracted will be written in case 162 * of success 163 * @param[in] msgCfgMap Map for message addition settings 164 * @param[out] msgJson JSON to which error messages will be added 165 * @param[in] fieldPath Field path in JSON 166 * 167 * @return Result informing about operation status, output will be 168 * written only in case of Result::SUCCESS 169 */ 170 Result getString(const char* fieldName, const nlohmann::json& json, 171 const std::string*& output, uint8_t msgCfgMap, 172 nlohmann::json& msgJson, const std::string&& fieldPath); 173 174 /** 175 * @brief Wrapper function for extracting object from JSON object without 176 * throwing exceptions 177 * 178 * @param[in] fieldName Name of requested field 179 * @param[in] json JSON object from which field should be extracted 180 * @param[out] output Variable to which extracted will be written in case 181 * of success 182 * @param[in] msgCfgMap Map for message addition settings 183 * @param[out] msgJson JSON to which error messages will be added 184 * @param[in] fieldPath Field path in JSON 185 * 186 * @return Result informing about operation status, output will be 187 * written only in case of Result::SUCCESS 188 */ 189 Result getObject(const char* fieldName, const nlohmann::json& json, 190 nlohmann::json* output, uint8_t msgCfgMap, 191 nlohmann::json& msgJson, const std::string&& fieldPath); 192 193 /** 194 * @brief Wrapper function for extracting array from JSON object without 195 * throwing exceptions 196 * 197 * @param[in] fieldName Name of requested field 198 * @param[in] json JSON object from which field should be extracted 199 * @param[out] output Variable to which extracted will be written in case 200 * of success 201 * @param[in] msgCfgMap Map for message addition settings 202 * @param[out] msgJson JSON to which error messages will be added 203 * @param[in] fieldPath Field path in JSON 204 * 205 * @return Result informing about operation status, output will be 206 * written only in case of Result::SUCCESS 207 */ 208 Result getArray(const char* fieldName, const nlohmann::json& json, 209 nlohmann::json* output, uint8_t msgCfgMap, 210 nlohmann::json& msgJson, const std::string&& fieldPath); 211 212 /** 213 * @brief Wrapper function for extracting int from JSON object without 214 * throwing exceptions 215 * 216 * @param[in] fieldName Name of requested field 217 * @param[in] json JSON object from which field should be extracted 218 * @param[out] output Variable to which extracted will be written in case 219 * of success 220 * @param[in] msgCfgMap Map for message addition settings 221 * @param[out] msgJson JSON to which error messages will be added 222 * @param[in] fieldPath Field path in JSON 223 * 224 * @return Result informing about operation status, output will be 225 * written only in case of Result::SUCCESS 226 */ 227 Result getInt(const char* fieldName, const nlohmann::json& json, 228 int64_t& output, uint8_t msgCfgMap, nlohmann::json& msgJson, 229 const std::string&& fieldPath); 230 231 /** 232 * @brief Wrapper function for extracting uint from JSON object without 233 * throwing exceptions 234 * 235 * @param[in] fieldName Name of requested field 236 * @param[in] json JSON object from which field should be extracted 237 * @param[out] output Variable to which extracted will be written in case 238 * of success 239 * @param[in] msgCfgMap Map for message addition settings 240 * @param[out] msgJson JSON to which error messages will be added 241 * @param[in] fieldPath Field path in JSON 242 * 243 * @return Result informing about operation status, output will be 244 * written only in case of Result::SUCCESS 245 */ 246 Result getUnsigned(const char* fieldName, const nlohmann::json& json, 247 uint64_t& output, uint8_t msgCfgMap, nlohmann::json& msgJson, 248 const std::string&& fieldPath); 249 250 /** 251 * @brief Wrapper function for extracting bool from JSON object without 252 * throwing exceptions 253 * 254 * @param[in] fieldName Name of requested field 255 * @param[in] json JSON object from which field should be extracted 256 * @param[out] output Variable to which extracted will be written in case 257 * of success 258 * @param[in] msgCfgMap Map for message addition settings 259 * @param[out] msgJson JSON to which error messages will be added 260 * @param[in] fieldPath Field path in JSON 261 * 262 * @return Result informing about operation status, output will be 263 * written only in case of Result::SUCCESS 264 */ 265 Result getBool(const char* fieldName, const nlohmann::json& json, bool& output, 266 uint8_t msgCfgMap, nlohmann::json& msgJson, 267 const std::string&& fieldPath); 268 269 /** 270 * @brief Wrapper function for extracting float from JSON object without 271 * throwing exceptions (nlohmann stores JSON floats as C++ doubles) 272 * 273 * @param[in] fieldName Name of requested field 274 * @param[in] json JSON object from which field should be extracted 275 * @param[out] output Variable to which extracted will be written in case 276 * of success 277 * @param[in] msgCfgMap Map for message addition settings 278 * @param[out] msgJson JSON to which error messages will be added 279 * @param[in] fieldPath Field path in JSON 280 * 281 * @return Result informing about operation status, output will be 282 * written only in case of Result::SUCCESS 283 */ 284 Result getDouble(const char* fieldName, const nlohmann::json& json, 285 double& output, uint8_t msgCfgMap, nlohmann::json& msgJson, 286 const std::string&& fieldPath); 287 288 /** 289 * @brief Processes request to extract JSON from its body. If it fails, adds 290 * MalformedJSON message to response and ends it. 291 * 292 * @param[io] res Response object 293 * @param[in] req Request object 294 * @param[out] reqJson JSON object extracted from request's body 295 * 296 * @return true if JSON is valid, false when JSON is invalid and response has 297 * been filled with message and ended. 298 */ 299 bool processJsonFromRequest(crow::Response& res, const crow::Request& req, 300 nlohmann::json& reqJson); 301 namespace details 302 { 303 template <typename Type> struct unpackValue 304 { 305 using isRequired = std::true_type; 306 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 307 }; 308 309 template <typename OptionalType> 310 struct unpackValue<boost::optional<OptionalType>> 311 { 312 using isRequired = std::false_type; 313 using JsonType = std::add_const_t<std::add_pointer_t<OptionalType>>; 314 }; 315 316 template <size_t Count, size_t Index> 317 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 318 crow::Response& res, std::bitset<Count>& handled) 319 { 320 BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key; 321 messages::addMessageToErrorJson(res.jsonValue, 322 messages::propertyUnknown(key)); 323 res.result(boost::beast::http::status::bad_request); 324 } 325 326 template <size_t Count, size_t Index, typename ValueType, 327 typename... UnpackTypes> 328 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 329 crow::Response& res, std::bitset<Count>& handled, 330 const char* keyToCheck, ValueType& valueToFill, 331 UnpackTypes&... in) 332 { 333 if (key != keyToCheck) 334 { 335 readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...); 336 return; 337 } 338 339 handled.set(Index); 340 341 using UnpackType = typename unpackValue<ValueType>::JsonType; 342 UnpackType value = jsonValue.get_ptr<UnpackType>(); 343 if (value == nullptr) 344 { 345 BMCWEB_LOG_DEBUG << "Value for key " << key 346 << " was incorrect type: " << jsonValue.type_name(); 347 messages::addMessageToErrorJson( 348 res.jsonValue, 349 messages::propertyValueTypeError(jsonValue.dump(), key)); 350 res.result(boost::beast::http::status::bad_request); 351 352 return; 353 } 354 355 valueToFill = *value; 356 } 357 358 template <size_t Index = 0, size_t Count> 359 void handleMissing(std::bitset<Count>& handled, crow::Response& res) 360 { 361 } 362 363 template <size_t Index = 0, size_t Count, typename ValueType, 364 typename... UnpackTypes> 365 void handleMissing(std::bitset<Count>& handled, crow::Response& res, 366 const char* key, ValueType& unused, UnpackTypes&... in) 367 { 368 if (!handled.test(Index) && unpackValue<ValueType>::isRequired::value) 369 { 370 messages::addMessageToErrorJson(res.jsonValue, 371 messages::propertyMissing(key)); 372 res.result(boost::beast::http::status::bad_request); 373 } 374 details::handleMissing<Index + 1, Count>(handled, res, in...); 375 } 376 } // namespace details 377 378 template <typename... UnpackTypes> 379 bool readJson(const crow::Request& req, crow::Response& res, const char* key, 380 UnpackTypes&... in) 381 { 382 nlohmann::json jsonRequest; 383 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 384 { 385 BMCWEB_LOG_DEBUG << "Json value not readable"; 386 return false; 387 } 388 if (!jsonRequest.is_object()) 389 { 390 BMCWEB_LOG_DEBUG << "Json value is not an object"; 391 messages::addMessageToErrorJson(res.jsonValue, 392 messages::unrecognizedRequestBody()); 393 res.result(boost::beast::http::status::bad_request); 394 return false; 395 } 396 397 if (jsonRequest.empty()) 398 { 399 BMCWEB_LOG_DEBUG << "Json value is empty"; 400 messages::addMessageToErrorJson(res.jsonValue, messages::emptyJSON()); 401 res.result(boost::beast::http::status::bad_request); 402 return false; 403 } 404 405 std::bitset<(sizeof...(in) + 1) / 2> handled(0); 406 for (const auto& item : jsonRequest.items()) 407 { 408 details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>( 409 item.key(), item.value(), res, handled, key, in...); 410 } 411 412 if (!handled.all()) 413 { 414 details::handleMissing(handled, res, key, in...); 415 416 return false; 417 } 418 return res.result() == boost::beast::http::status::ok; 419 } 420 421 } // namespace json_util 422 } // namespace redfish 423