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 <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 ToType, typename FromType>
82 bool checkRange(const FromType& from, const std::string& key,
83                 nlohmann::json& jsonValue, crow::Response& res)
84 {
85     if (from > std::numeric_limits<ToType>::max())
86     {
87         BMCWEB_LOG_DEBUG << "Value for key " << key
88                          << " was greater than max: " << __PRETTY_FUNCTION__;
89         messages::propertyValueNotInList(res, jsonValue.dump(), key);
90         return false;
91     }
92     if (from < std::numeric_limits<ToType>::lowest())
93     {
94         BMCWEB_LOG_DEBUG << "Value for key " << key
95                          << " was less than min: " << __PRETTY_FUNCTION__;
96         messages::propertyValueNotInList(res, jsonValue.dump(), key);
97         return false;
98     }
99     if constexpr (std::is_floating_point_v<ToType>)
100     {
101         if (std::isnan(from))
102         {
103             BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN";
104             messages::propertyValueNotInList(res, jsonValue.dump(), key);
105             return false;
106         }
107     }
108 
109     return true;
110 }
111 
112 template <typename Type>
113 bool unpackValue(nlohmann::json& jsonValue, const std::string& key,
114                  crow::Response& res, Type& value)
115 {
116     bool ret = true;
117 
118     if constexpr (std::is_floating_point_v<Type>)
119     {
120         double helper = 0;
121         double* jsonPtr = jsonValue.get_ptr<double*>();
122 
123         if (jsonPtr == nullptr)
124         {
125             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
126             if (intPtr != nullptr)
127             {
128                 helper = static_cast<double>(*intPtr);
129                 jsonPtr = &helper;
130             }
131         }
132         if (jsonPtr == nullptr)
133         {
134             messages::propertyValueTypeError(res, jsonValue.dump(), key);
135             return false;
136         }
137         if (!checkRange<Type>(*jsonPtr, key, jsonValue, res))
138         {
139             return false;
140         }
141         value = static_cast<Type>(*jsonPtr);
142     }
143 
144     else if constexpr (std::is_signed_v<Type>)
145     {
146         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
147         if (jsonPtr == nullptr)
148         {
149             messages::propertyValueTypeError(res, jsonValue.dump(), key);
150             return false;
151         }
152         if (!checkRange<Type>(*jsonPtr, key, jsonValue, res))
153         {
154             return false;
155         }
156         value = static_cast<Type>(*jsonPtr);
157     }
158 
159     else if constexpr ((std::is_unsigned_v<Type>)&&(
160                            !std::is_same_v<bool, Type>))
161     {
162         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
163         if (jsonPtr == nullptr)
164         {
165             messages::propertyValueTypeError(res, jsonValue.dump(), key);
166             return false;
167         }
168         if (!checkRange<Type>(*jsonPtr, key, jsonValue, res))
169         {
170             return false;
171         }
172         value = static_cast<Type>(*jsonPtr);
173     }
174 
175     else if constexpr (is_optional_v<Type>)
176     {
177         value.emplace();
178         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
179                                                      *value) &&
180               ret;
181     }
182     else if constexpr (std::is_same_v<nlohmann::json, Type>)
183     {
184         // Must be a complex type.  Simple types (int string etc) should be
185         // unpacked directly
186         if (!jsonValue.is_object() && !jsonValue.is_array() &&
187             !jsonValue.is_null())
188         {
189             messages::propertyValueTypeError(res, jsonValue.dump(), key);
190             return false;
191         }
192 
193         value = std::move(jsonValue);
194     }
195     else if constexpr (is_std_array_v<Type>)
196     {
197         if (!jsonValue.is_array())
198         {
199             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
200             return false;
201         }
202         if (jsonValue.size() != value.size())
203         {
204             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
205             return false;
206         }
207         size_t index = 0;
208         for (const auto& val : jsonValue.items())
209         {
210             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
211                                                          value[index++]) &&
212                   ret;
213         }
214     }
215     else if constexpr (is_vector_v<Type>)
216     {
217         if (!jsonValue.is_array())
218         {
219             messages::propertyValueTypeError(res, res.jsonValue.dump(), key);
220             return false;
221         }
222 
223         for (const auto& val : jsonValue.items())
224         {
225             value.emplace_back();
226             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
227                                                          value.back()) &&
228                   ret;
229         }
230     }
231     else
232     {
233         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
234         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
235         if (jsonPtr == nullptr)
236         {
237             BMCWEB_LOG_DEBUG
238                 << "Value for key " << key
239                 << " was incorrect type: " << jsonValue.type_name();
240             messages::propertyValueTypeError(res, jsonValue.dump(), key);
241             return false;
242         }
243         value = std::move(*jsonPtr);
244     }
245     return ret;
246 }
247 
248 template <size_t Count, size_t Index>
249 bool readJsonValues(const std::string& key, nlohmann::json& jsonValue,
250                     crow::Response& res, std::bitset<Count>& handled)
251 {
252     BMCWEB_LOG_DEBUG << "Unable to find variable for key" << key;
253     messages::propertyUnknown(res, key);
254     return false;
255 }
256 
257 template <size_t Count, size_t Index, typename ValueType,
258           typename... UnpackTypes>
259 bool readJsonValues(const std::string& key, nlohmann::json& jsonValue,
260                     crow::Response& res, std::bitset<Count>& handled,
261                     const char* keyToCheck, ValueType& valueToFill,
262                     UnpackTypes&... in)
263 {
264     bool ret = true;
265     if (key != keyToCheck)
266     {
267         ret = readJsonValues<Count, Index + 1>(key, jsonValue, res, handled,
268                                                in...) &&
269               ret;
270         return ret;
271     }
272 
273     handled.set(Index);
274 
275     return unpackValue<ValueType>(jsonValue, key, res, valueToFill) && ret;
276 }
277 
278 template <size_t Index = 0, size_t Count>
279 bool handleMissing(std::bitset<Count>& handled, crow::Response& res)
280 {
281     return true;
282 }
283 
284 template <size_t Index = 0, size_t Count, typename ValueType,
285           typename... UnpackTypes>
286 bool handleMissing(std::bitset<Count>& handled, crow::Response& res,
287                    const char* key, ValueType& unused, UnpackTypes&... in)
288 {
289     bool ret = true;
290     if (!handled.test(Index) && !is_optional_v<ValueType>)
291     {
292         ret = false;
293         messages::propertyMissing(res, key);
294     }
295     return details::handleMissing<Index + 1, Count>(handled, res, in...) && ret;
296 }
297 } // namespace details
298 
299 template <typename... UnpackTypes>
300 bool readJson(nlohmann::json& jsonRequest, crow::Response& res, const char* key,
301               UnpackTypes&... in)
302 {
303     bool result = true;
304     if (!jsonRequest.is_object())
305     {
306         BMCWEB_LOG_DEBUG << "Json value is not an object";
307         messages::unrecognizedRequestBody(res);
308         return false;
309     }
310 
311     if (jsonRequest.empty())
312     {
313         BMCWEB_LOG_DEBUG << "Json value is empty";
314         messages::emptyJSON(res);
315         return false;
316     }
317 
318     std::bitset<(sizeof...(in) + 1) / 2> handled(0);
319     for (const auto& item : jsonRequest.items())
320     {
321         result =
322             details::readJsonValues<(sizeof...(in) + 1) / 2, 0, UnpackTypes...>(
323                 item.key(), item.value(), res, handled, key, in...) &&
324             result;
325     }
326 
327     BMCWEB_LOG_DEBUG << "JSON result is: " << result;
328 
329     return details::handleMissing(handled, res, key, in...) && result;
330 }
331 
332 template <typename... UnpackTypes>
333 bool readJson(const crow::Request& req, crow::Response& res, const char* key,
334               UnpackTypes&... in)
335 {
336     nlohmann::json jsonRequest;
337     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
338     {
339         BMCWEB_LOG_DEBUG << "Json value not readable";
340         return false;
341     }
342     return readJson(jsonRequest, res, key, in...);
343 }
344 
345 } // namespace json_util
346 } // namespace redfish
347