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 Type> 82 void unpackValue(nlohmann::json& jsonValue, const std::string& key, 83 crow::Response& res, Type& value) 84 { 85 if constexpr (std::is_arithmetic_v<Type>) 86 { 87 using NumType = 88 std::conditional_t<std::is_signed_v<Type>, int64_t, uint64_t>; 89 90 NumType* jsonPtr = jsonValue.get_ptr<NumType*>(); 91 if (jsonPtr == nullptr) 92 { 93 BMCWEB_LOG_DEBUG 94 << "Value for key " << key 95 << " was incorrect type: " << jsonValue.type_name(); 96 messages::propertyValueTypeError(res, jsonValue.dump(), key); 97 return; 98 } 99 if (*jsonPtr > std::numeric_limits<Type>::max()) 100 { 101 BMCWEB_LOG_DEBUG << "Value for key " << key 102 << " was out of range: " << jsonValue.type_name(); 103 messages::propertyValueNotInList(res, jsonValue.dump(), key); 104 return; 105 } 106 if (*jsonPtr < std::numeric_limits<Type>::min()) 107 { 108 BMCWEB_LOG_DEBUG << "Value for key " << key 109 << " was out of range: " << jsonValue.type_name(); 110 messages::propertyValueNotInList(res, jsonValue.dump(), key); 111 return; 112 } 113 value = static_cast<Type>(*jsonPtr); 114 } 115 else if constexpr (is_optional_v<Type>) 116 { 117 value.emplace(); 118 unpackValue<typename Type::value_type>(jsonValue, key, res, *value); 119 } 120 else if constexpr (std::is_same_v<nlohmann::json, Type>) 121 { 122 // Must be a complex type. Simple types (int string etc) should be 123 // unpacked directly 124 if (!jsonValue.is_object() && !jsonValue.is_array()) 125 { 126 messages::propertyValueTypeError(res, jsonValue.dump(), key); 127 return; 128 } 129 130 value = std::move(jsonValue); 131 } 132 else if constexpr (is_std_array_v<Type>) 133 { 134 if (!jsonValue.is_array()) 135 { 136 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 137 return; 138 } 139 if (jsonValue.size() != value.size()) 140 { 141 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 142 return; 143 } 144 size_t index = 0; 145 for (const auto& val : jsonValue.items()) 146 { 147 unpackValue<typename Type::value_type>(val.value(), key, res, 148 value[index++]); 149 } 150 } 151 else if constexpr (is_vector_v<Type>) 152 { 153 if (!jsonValue.is_array()) 154 { 155 messages::propertyValueTypeError(res, res.jsonValue.dump(), key); 156 return; 157 } 158 159 for (const auto& val : jsonValue.items()) 160 { 161 value.emplace_back(); 162 unpackValue<typename Type::value_type>(val.value(), key, res, 163 value.back()); 164 } 165 } 166 else 167 { 168 using JsonType = std::add_const_t<std::add_pointer_t<Type>>; 169 JsonType jsonPtr = jsonValue.get_ptr<JsonType>(); 170 if (jsonPtr == nullptr) 171 { 172 BMCWEB_LOG_DEBUG 173 << "Value for key " << key 174 << " was incorrect type: " << jsonValue.type_name(); 175 messages::propertyValueTypeError(res, jsonValue.dump(), key); 176 return; 177 } 178 value = std::move(*jsonPtr); 179 } 180 } 181 182 template <size_t Count, size_t Index> 183 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 184 crow::Response& res, std::bitset<Count>& handled) 185 { 186 BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key; 187 messages::propertyUnknown(res, key); 188 } 189 190 template <size_t Count, size_t Index, typename ValueType, 191 typename... UnpackTypes> 192 void readJsonValues(const std::string& key, nlohmann::json& jsonValue, 193 crow::Response& res, std::bitset<Count>& handled, 194 const char* keyToCheck, ValueType& valueToFill, 195 UnpackTypes&... in) 196 { 197 if (key != keyToCheck) 198 { 199 readJsonValues<Count, Index + 1>(key, jsonValue, res, handled, in...); 200 return; 201 } 202 203 handled.set(Index); 204 205 unpackValue<ValueType>(jsonValue, key, res, valueToFill); 206 } 207 208 template <size_t Index = 0, size_t Count> 209 void handleMissing(std::bitset<Count>& handled, crow::Response& res) 210 { 211 } 212 213 template <size_t Index = 0, size_t Count, typename ValueType, 214 typename... UnpackTypes> 215 void handleMissing(std::bitset<Count>& handled, crow::Response& res, 216 const char* key, ValueType& unused, UnpackTypes&... in) 217 { 218 if (!handled.test(Index) && !is_optional_v<ValueType>) 219 { 220 messages::propertyMissing(res, key); 221 } 222 details::handleMissing<Index + 1, Count>(handled, res, in...); 223 } 224 } // namespace details 225 226 template <typename... UnpackTypes> 227 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key, 228 UnpackTypes&... in) 229 { 230 if (!jsonRequest.is_object()) 231 { 232 BMCWEB_LOG_DEBUG << "Json value is not an object"; 233 messages::unrecognizedRequestBody(res); 234 return false; 235 } 236 237 if (jsonRequest.empty()) 238 { 239 BMCWEB_LOG_DEBUG << "Json value is empty"; 240 messages::emptyJSON(res); 241 return false; 242 } 243 244 std::bitset<(sizeof...(in) + 1) / 2> handled(0); 245 for (const auto& item : jsonRequest.items()) 246 { 247 details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>( 248 item.key(), item.value(), res, handled, key, in...); 249 } 250 251 details::handleMissing(handled, res, key, in...); 252 253 return res.result() == boost::beast::http::status::ok; 254 } 255 256 template <typename... UnpackTypes> 257 bool readJson(const crow::Request& req, crow::Response& res, const char* key, 258 UnpackTypes&... in) 259 { 260 nlohmann::json jsonRequest; 261 if (!json_util::processJsonFromRequest(res, req, jsonRequest)) 262 { 263 BMCWEB_LOG_DEBUG << "Json value not readable"; 264 return false; 265 } 266 return readJson(jsonRequest, res, key, in...); 267 } 268 269 } // namespace json_util 270 } // namespace redfish 271