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, const std::string& key)
90 {
91     if (from > std::numeric_limits<ToType>::max())
92     {
93         BMCWEB_LOG_DEBUG << "Value for key " << key
94                          << " was greater than max: " << __PRETTY_FUNCTION__;
95         return false;
96     }
97     if (from < std::numeric_limits<ToType>::lowest())
98     {
99         BMCWEB_LOG_DEBUG << "Value for key " << key
100                          << " was less than min: " << __PRETTY_FUNCTION__;
101         return false;
102     }
103     if constexpr (std::is_floating_point_v<ToType>)
104     {
105         if (std::isnan(from))
106         {
107             BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN";
108             return false;
109         }
110     }
111 
112     return true;
113 }
114 
115 template <typename Type>
116 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
117                                          const std::string& key, Type& value)
118 {
119     UnpackErrorCode ret = UnpackErrorCode::success;
120 
121     if constexpr (std::is_floating_point_v<Type>)
122     {
123         double helper = 0;
124         double* jsonPtr = jsonValue.get_ptr<double*>();
125 
126         if (jsonPtr == nullptr)
127         {
128             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
129             if (intPtr != nullptr)
130             {
131                 helper = static_cast<double>(*intPtr);
132                 jsonPtr = &helper;
133             }
134         }
135         if (jsonPtr == nullptr)
136         {
137             return UnpackErrorCode::invalidType;
138         }
139         if (!checkRange<Type>(*jsonPtr, key))
140         {
141             return UnpackErrorCode::outOfRange;
142         }
143         value = static_cast<Type>(*jsonPtr);
144     }
145 
146     else if constexpr (std::is_signed_v<Type>)
147     {
148         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
149         if (jsonPtr == nullptr)
150         {
151             return UnpackErrorCode::invalidType;
152         }
153         if (!checkRange<Type>(*jsonPtr, key))
154         {
155             return UnpackErrorCode::outOfRange;
156         }
157         value = static_cast<Type>(*jsonPtr);
158     }
159 
160     else if constexpr ((std::is_unsigned_v<Type>)&&(
161                            !std::is_same_v<bool, Type>))
162     {
163         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
164         if (jsonPtr == nullptr)
165         {
166             return UnpackErrorCode::invalidType;
167         }
168         if (!checkRange<Type>(*jsonPtr, key))
169         {
170             return UnpackErrorCode::outOfRange;
171         }
172         value = static_cast<Type>(*jsonPtr);
173     }
174 
175     else if constexpr (std::is_same_v<nlohmann::json, Type>)
176     {
177         // Must be a complex type.  Simple types (int string etc) should be
178         // unpacked directly
179         if (!jsonValue.is_object() && !jsonValue.is_array() &&
180             !jsonValue.is_null())
181         {
182             return UnpackErrorCode::invalidType;
183         }
184 
185         value = std::move(jsonValue);
186     }
187     else
188     {
189         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
190         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
191         if (jsonPtr == nullptr)
192         {
193             BMCWEB_LOG_DEBUG
194                 << "Value for key " << key
195                 << " was incorrect type: " << jsonValue.type_name();
196             return UnpackErrorCode::invalidType;
197         }
198         value = std::move(*jsonPtr);
199     }
200     return ret;
201 }
202 
203 template <typename Type>
204 bool unpackValue(nlohmann::json& jsonValue, const std::string& key,
205                  crow::Response& res, Type& value)
206 {
207     bool ret = true;
208 
209     if constexpr (is_optional_v<Type>)
210     {
211         value.emplace();
212         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
213                                                      *value) &&
214               ret;
215     }
216     else if constexpr (is_std_array_v<Type>)
217     {
218         if (!jsonValue.is_array())
219         {
220             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
221             return false;
222         }
223         if (jsonValue.size() != value.size())
224         {
225             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
226             return false;
227         }
228         size_t index = 0;
229         for (const auto& val : jsonValue.items())
230         {
231             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
232                                                          value[index++]) &&
233                   ret;
234         }
235     }
236     else if constexpr (is_vector_v<Type>)
237     {
238         if (!jsonValue.is_array())
239         {
240             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
241             return false;
242         }
243 
244         for (const auto& val : jsonValue.items())
245         {
246             value.emplace_back();
247             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
248                                                          value.back()) &&
249                   ret;
250         }
251     }
252     else
253     {
254         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
255         if (ec != UnpackErrorCode::success)
256         {
257             if (ec == UnpackErrorCode::invalidType)
258             {
259                 messages::propertyValueTypeError(res, jsonValue.dump(), key);
260             }
261             else if (ec == UnpackErrorCode::outOfRange)
262             {
263                 messages::propertyValueNotInList(res, jsonValue.dump(), key);
264             }
265             return false;
266         }
267     }
268 
269     return ret;
270 }
271 
272 template <typename Type>
273 bool unpackValue(nlohmann::json& jsonValue, const std::string& key, Type& value)
274 {
275     bool ret = true;
276     if constexpr (is_optional_v<Type>)
277     {
278         value.emplace();
279         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
280               ret;
281     }
282     else if constexpr (is_std_array_v<Type>)
283     {
284         if (!jsonValue.is_array())
285         {
286             return false;
287         }
288         if (jsonValue.size() != value.size())
289         {
290             return false;
291         }
292         size_t index = 0;
293         for (const auto& val : jsonValue.items())
294         {
295             ret = unpackValue<typename Type::value_type>(val.value(), key,
296                                                          value[index++]) &&
297                   ret;
298         }
299     }
300     else if constexpr (is_vector_v<Type>)
301     {
302         if (!jsonValue.is_array())
303         {
304             return false;
305         }
306 
307         for (const auto& val : jsonValue.items())
308         {
309             value.emplace_back();
310             ret = unpackValue<typename Type::value_type>(val.value(), key,
311                                                          value.back()) &&
312                   ret;
313         }
314     }
315     else
316     {
317         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
318         if (ec != UnpackErrorCode::success)
319         {
320             return false;
321         }
322     }
323 
324     return ret;
325 }
326 
327 template <size_t Count, size_t Index>
328 bool readJsonValues(const std::string& key, nlohmann::json&,
329                     crow::Response& res, std::bitset<Count>&)
330 {
331     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
332     messages::propertyUnknown(res, key);
333     return false;
334 }
335 
336 template <size_t Count, size_t Index, typename ValueType,
337           typename... UnpackTypes>
338 bool readJsonValues(const std::string& key, nlohmann::json& jsonValue,
339                     crow::Response& res, std::bitset<Count>& handled,
340                     const char* keyToCheck, ValueType& valueToFill,
341                     UnpackTypes&... in)
342 {
343     bool ret = true;
344     if (key != keyToCheck)
345     {
346         ret = readJsonValues<Count, Index + 1>(key, jsonValue, res, handled,
347                                                in...) &&
348               ret;
349         return ret;
350     }
351 
352     handled.set(Index);
353 
354     return unpackValue<ValueType>(jsonValue, key, res, valueToFill) && ret;
355 }
356 
357 template <size_t Index = 0, size_t Count>
358 bool handleMissing(std::bitset<Count>&, crow::Response&)
359 {
360     return true;
361 }
362 
363 template <size_t Index = 0, size_t Count, typename ValueType,
364           typename... UnpackTypes>
365 bool handleMissing(std::bitset<Count>& handled, crow::Response& res,
366                    const char* key, ValueType&, UnpackTypes&... in)
367 {
368     bool ret = true;
369     if (!handled.test(Index) && !is_optional_v<ValueType>)
370     {
371         ret = false;
372         messages::propertyMissing(res, key);
373     }
374     return details::handleMissing<Index + 1, Count>(handled, res, in...) && ret;
375 }
376 } // namespace details
377 
378 template <typename... UnpackTypes>
379 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
380               UnpackTypes&... in)
381 {
382     bool result = true;
383     if (!jsonRequest.is_object())
384     {
385         BMCWEB_LOG_DEBUG << "Json value is not an object";
386         messages::unrecognizedRequestBody(res);
387         return false;
388     }
389 
390     if (jsonRequest.empty())
391     {
392         BMCWEB_LOG_DEBUG << "Json value is empty";
393         messages::emptyJSON(res);
394         return false;
395     }
396 
397     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
398     for (const auto& item : jsonRequest.items())
399     {
400         result =
401             details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
402                 item.key(), item.value(), res, handled, key, in...) &&
403             result;
404     }
405 
406     BMCWEB_LOG_DEBUG << "JSON result is: " << result;
407 
408     return details::handleMissing(handled, res, key, in...) && result;
409 }
410 
411 template <typename... UnpackTypes>
412 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
413               UnpackTypes&... in)
414 {
415     nlohmann::json jsonRequest;
416     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
417     {
418         BMCWEB_LOG_DEBUG << "Json value not readable";
419         return false;
420     }
421     return readJson(jsonRequest, res, key, in...);
422 }
423 
424 template <typename Type>
425 bool getValueFromJsonObject(nlohmann::json& jsonData, const std::string& key,
426                             Type& value)
427 {
428     nlohmann::json jsonValue = jsonData[key];
429     if (jsonValue.is_null())
430     {
431         BMCWEB_LOG_DEBUG << "Key " << key << " not exist";
432         return false;
433     }
434 
435     return details::unpackValue(jsonValue, key, value);
436 }
437 
438 } // namespace json_util
439 } // namespace redfish
440