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(res, res.jsonValue.dump(), key);
211             return false;
212         }
213         if (jsonValue.size() != value.size())
214         {
215             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
216             return false;
217         }
218         size_t index = 0;
219         for (const auto& val : jsonValue.items())
220         {
221             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
222                                                          value[index++]) &&
223                   ret;
224         }
225     }
226     else if constexpr (IsVector<Type>::value)
227     {
228         if (!jsonValue.is_array())
229         {
230             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
231             return false;
232         }
233 
234         for (const auto& val : jsonValue.items())
235         {
236             value.emplace_back();
237             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
238                                                          value.back()) &&
239                   ret;
240         }
241     }
242     else
243     {
244         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
245         if (ec != UnpackErrorCode::success)
246         {
247             if (ec == UnpackErrorCode::invalidType)
248             {
249                 messages::propertyValueTypeError(res, jsonValue.dump(), key);
250             }
251             else if (ec == UnpackErrorCode::outOfRange)
252             {
253                 messages::propertyValueNotInList(res, jsonValue.dump(), key);
254             }
255             return false;
256         }
257     }
258 
259     return ret;
260 }
261 
262 template <typename Type>
263 bool unpackValue(nlohmann::json& jsonValue, const std::string& key, Type& value)
264 {
265     bool ret = true;
266     if constexpr (IsOptional<Type>::value)
267     {
268         value.emplace();
269         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
270               ret;
271     }
272     else if constexpr (IsStdArray<Type>::value)
273     {
274         if (!jsonValue.is_array())
275         {
276             return false;
277         }
278         if (jsonValue.size() != value.size())
279         {
280             return false;
281         }
282         size_t index = 0;
283         for (const auto& val : jsonValue.items())
284         {
285             ret = unpackValue<typename Type::value_type>(val.value(), key,
286                                                          value[index++]) &&
287                   ret;
288         }
289     }
290     else if constexpr (IsVector<Type>::value)
291     {
292         if (!jsonValue.is_array())
293         {
294             return false;
295         }
296 
297         for (const auto& val : jsonValue.items())
298         {
299             value.emplace_back();
300             ret = unpackValue<typename Type::value_type>(val.value(), key,
301                                                          value.back()) &&
302                   ret;
303         }
304     }
305     else
306     {
307         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
308         if (ec != UnpackErrorCode::success)
309         {
310             return false;
311         }
312     }
313 
314     return ret;
315 }
316 
317 template <size_t Count, size_t Index>
318 bool readJsonValues(const std::string& key, nlohmann::json&,
319                     crow::Response& res, std::bitset<Count>&)
320 {
321     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
322     messages::propertyUnknown(res, key);
323     return false;
324 }
325 
326 template <size_t Count, size_t Index, typename ValueType,
327           typename... UnpackTypes>
328 bool 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     bool ret = true;
334     if (key != keyToCheck)
335     {
336         ret = readJsonValues<Count, Index + 1>(key, jsonValue, res, handled,
337                                                in...) &&
338               ret;
339         return ret;
340     }
341 
342     handled.set(Index);
343 
344     return unpackValue<ValueType>(jsonValue, key, res, valueToFill) && ret;
345 }
346 
347 template <size_t Index = 0, size_t Count>
348 bool handleMissing(std::bitset<Count>&, crow::Response&)
349 {
350     return true;
351 }
352 
353 template <size_t Index = 0, size_t Count, typename ValueType,
354           typename... UnpackTypes>
355 bool handleMissing(std::bitset<Count>& handled, crow::Response& res,
356                    const char* key, ValueType&, UnpackTypes&... in)
357 {
358     bool ret = true;
359     if (!handled.test(Index) && !IsOptional<ValueType>::value)
360     {
361         ret = false;
362         messages::propertyMissing(res, key);
363     }
364     return details::handleMissing<Index + 1, Count>(handled, res, in...) && ret;
365 }
366 } // namespace details
367 
368 template <typename... UnpackTypes>
369 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
370               UnpackTypes&... in)
371 {
372     bool result = true;
373     if (!jsonRequest.is_object())
374     {
375         BMCWEB_LOG_DEBUG << "Json value is not an object";
376         messages::unrecognizedRequestBody(res);
377         return false;
378     }
379 
380     if (jsonRequest.empty())
381     {
382         BMCWEB_LOG_DEBUG << "Json value is empty";
383         messages::emptyJSON(res);
384         return false;
385     }
386 
387     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
388     for (const auto& item : jsonRequest.items())
389     {
390         result =
391             details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
392                 item.key(), item.value(), res, handled, key, in...) &&
393             result;
394     }
395 
396     BMCWEB_LOG_DEBUG << "JSON result is: " << result;
397 
398     return details::handleMissing(handled, res, key, in...) && result;
399 }
400 
401 template <typename... UnpackTypes>
402 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
403               UnpackTypes&... in)
404 {
405     nlohmann::json jsonRequest;
406     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
407     {
408         BMCWEB_LOG_DEBUG << "Json value not readable";
409         return false;
410     }
411     return readJson(jsonRequest, res, key, in...);
412 }
413 
414 template <typename Type>
415 bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key,
416                             Type& value)
417 {
418     nlohmann::json::iterator it = jsonData.find(key);
419     if (it == jsonData.end())
420     {
421         BMCWEB_LOG_DEBUG << "Key " << key << " not exist";
422         return false;
423     }
424 
425     return details::unpackValue(*it, key, value);
426 }
427 } // namespace json_util
428 } // namespace redfish
429