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