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