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 <span>
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, std::string_view 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                                          std::string_view 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         value = std::move(jsonValue);
168     }
169     else
170     {
171         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
172         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
173         if (jsonPtr == nullptr)
174         {
175             BMCWEB_LOG_DEBUG
176                 << "Value for key " << key
177                 << " was incorrect type: " << jsonValue.type_name();
178             return UnpackErrorCode::invalidType;
179         }
180         value = std::move(*jsonPtr);
181     }
182     return ret;
183 }
184 
185 template <typename Type>
186 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
187                  crow::Response& res, Type& value)
188 {
189     bool ret = true;
190 
191     if constexpr (IsOptional<Type>::value)
192     {
193         value.emplace();
194         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
195                                                      *value) &&
196               ret;
197     }
198     else if constexpr (IsStdArray<Type>::value)
199     {
200         if (!jsonValue.is_array())
201         {
202             messages::propertyValueTypeError(
203                 res,
204                 res.jsonValue.dump(2, ' ', true,
205                                    nlohmann::json::error_handler_t::replace),
206                 key);
207             return false;
208         }
209         if (jsonValue.size() != value.size())
210         {
211             messages::propertyValueTypeError(
212                 res,
213                 res.jsonValue.dump(2, ' ', true,
214                                    nlohmann::json::error_handler_t::replace),
215                 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(
231                 res,
232                 res.jsonValue.dump(2, ' ', true,
233                                    nlohmann::json::error_handler_t::replace),
234                 key);
235             return false;
236         }
237 
238         for (const auto& val : jsonValue.items())
239         {
240             value.emplace_back();
241             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
242                                                          value.back()) &&
243                   ret;
244         }
245     }
246     else
247     {
248         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
249         if (ec != UnpackErrorCode::success)
250         {
251             if (ec == UnpackErrorCode::invalidType)
252             {
253                 messages::propertyValueTypeError(
254                     res,
255                     jsonValue.dump(2, ' ', true,
256                                    nlohmann::json::error_handler_t::replace),
257                     key);
258             }
259             else if (ec == UnpackErrorCode::outOfRange)
260             {
261                 messages::propertyValueNotInList(
262                     res,
263                     jsonValue.dump(2, ' ', true,
264                                    nlohmann::json::error_handler_t::replace),
265                     key);
266             }
267             return false;
268         }
269     }
270 
271     return ret;
272 }
273 
274 template <typename Type>
275 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
276 {
277     bool ret = true;
278     if constexpr (IsOptional<Type>::value)
279     {
280         value.emplace();
281         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
282               ret;
283     }
284     else if constexpr (IsStdArray<Type>::value)
285     {
286         if (!jsonValue.is_array())
287         {
288             return false;
289         }
290         if (jsonValue.size() != value.size())
291         {
292             return false;
293         }
294         size_t index = 0;
295         for (const auto& val : jsonValue.items())
296         {
297             ret = unpackValue<typename Type::value_type>(val.value(), key,
298                                                          value[index++]) &&
299                   ret;
300         }
301     }
302     else if constexpr (IsVector<Type>::value)
303     {
304         if (!jsonValue.is_array())
305         {
306             return false;
307         }
308 
309         for (const auto& val : jsonValue.items())
310         {
311             value.emplace_back();
312             ret = unpackValue<typename Type::value_type>(val.value(), key,
313                                                          value.back()) &&
314                   ret;
315         }
316     }
317     else
318     {
319         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
320         if (ec != UnpackErrorCode::success)
321         {
322             return false;
323         }
324     }
325 
326     return ret;
327 }
328 } // namespace details
329 
330 // clang-format off
331 using UnpackVariant = std::variant<
332     uint8_t*,
333     uint16_t*,
334     int16_t*,
335     uint32_t*,
336     int32_t*,
337     uint64_t*,
338     int64_t*,
339     bool*,
340     double*,
341     std::string*,
342     nlohmann::json*,
343     std::vector<uint8_t>*,
344     std::vector<uint16_t>*,
345     std::vector<int16_t>*,
346     std::vector<uint32_t>*,
347     std::vector<int32_t>*,
348     std::vector<uint64_t>*,
349     std::vector<int64_t>*,
350     //std::vector<bool>*,
351     std::vector<double>*,
352     std::vector<std::string>*,
353     std::vector<nlohmann::json>*,
354     std::optional<uint8_t>*,
355     std::optional<uint16_t>*,
356     std::optional<int16_t>*,
357     std::optional<uint32_t>*,
358     std::optional<int32_t>*,
359     std::optional<uint64_t>*,
360     std::optional<int64_t>*,
361     std::optional<bool>*,
362     std::optional<double>*,
363     std::optional<std::string>*,
364     std::optional<nlohmann::json>*,
365     std::optional<std::vector<uint8_t>>*,
366     std::optional<std::vector<uint16_t>>*,
367     std::optional<std::vector<int16_t>>*,
368     std::optional<std::vector<uint32_t>>*,
369     std::optional<std::vector<int32_t>>*,
370     std::optional<std::vector<uint64_t>>*,
371     std::optional<std::vector<int64_t>>*,
372     //std::optional<std::vector<bool>>*,
373     std::optional<std::vector<double>>*,
374     std::optional<std::vector<std::string>>*,
375     std::optional<std::vector<nlohmann::json>>*
376 >;
377 // clang-format on
378 
379 struct PerUnpack
380 {
381     std::string_view key;
382     UnpackVariant value;
383     bool complete = false;
384 };
385 
386 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
387                            std::span<PerUnpack> toUnpack)
388 {
389     bool result = true;
390     if (!jsonRequest.is_object())
391     {
392         BMCWEB_LOG_DEBUG << "Json value is not an object";
393         messages::unrecognizedRequestBody(res);
394         return false;
395     }
396     for (auto& item : jsonRequest.items())
397     {
398         size_t unpackIndex = 0;
399         for (; unpackIndex < toUnpack.size(); unpackIndex++)
400         {
401             PerUnpack& unpackSpec = toUnpack[unpackIndex];
402             std::string_view key = unpackSpec.key;
403             size_t keysplitIndex = key.find('/');
404             std::string_view leftover;
405             if (keysplitIndex != std::string_view::npos)
406             {
407                 leftover = key.substr(keysplitIndex + 1);
408                 key = key.substr(0, keysplitIndex);
409             }
410 
411             if (key != item.key() || unpackSpec.complete)
412             {
413                 continue;
414             }
415 
416             // Sublevel key
417             if (!leftover.empty())
418             {
419                 // Include the slash in the key so we can compare later
420                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
421                 nlohmann::json j;
422                 result = details::unpackValue<nlohmann::json>(item.value(), key,
423                                                               res, j) &&
424                          result;
425                 if (!result)
426                 {
427                     return result;
428                 }
429 
430                 std::vector<PerUnpack> nextLevel;
431                 for (PerUnpack& p : toUnpack)
432                 {
433                     if (!p.key.starts_with(key))
434                     {
435                         continue;
436                     }
437                     std::string_view thisLeftover = p.key.substr(key.size());
438                     nextLevel.push_back({thisLeftover, p.value, false});
439                     p.complete = true;
440                 }
441 
442                 result = readJsonHelper(j, res, nextLevel) && result;
443                 break;
444             }
445 
446             result = std::visit(
447                          [&item, &unpackSpec, &res](auto&& val) {
448                 using ContainedT =
449                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
450                 return details::unpackValue<ContainedT>(
451                     item.value(), unpackSpec.key, res, *val);
452                          },
453                          unpackSpec.value) &&
454                 result;
455 
456             unpackSpec.complete = true;
457             break;
458         }
459 
460         if (unpackIndex == toUnpack.size())
461         {
462             messages::propertyUnknown(res, item.key());
463             result = false;
464         }
465     }
466 
467     for (PerUnpack& perUnpack : toUnpack)
468     {
469         if (!perUnpack.complete)
470         {
471             bool isOptional = std::visit(
472                 [](auto&& val) {
473                 using ContainedType =
474                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
475                 return details::IsOptional<ContainedType>::value;
476                 },
477                 perUnpack.value);
478             if (isOptional)
479             {
480                 continue;
481             }
482             messages::propertyMissing(res, perUnpack.key);
483             result = false;
484         }
485     }
486     return result;
487 }
488 
489 inline void packVariant(std::span<PerUnpack> /*toPack*/)
490 {}
491 
492 template <typename FirstType, typename... UnpackTypes>
493 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
494                  FirstType& first, UnpackTypes&&... in)
495 {
496     if (toPack.empty())
497     {
498         return;
499     }
500     toPack[0].key = key;
501     toPack[0].value = &first;
502     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
503     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
504 }
505 
506 template <typename FirstType, typename... UnpackTypes>
507 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
508               std::string_view key, FirstType&& first, UnpackTypes&&... in)
509 {
510     const std::size_t n = sizeof...(UnpackTypes) + 2;
511     std::array<PerUnpack, n / 2> toUnpack2;
512     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
513     return readJsonHelper(jsonRequest, res, toUnpack2);
514 }
515 
516 inline std::optional<nlohmann::json>
517     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
518 {
519     nlohmann::json jsonRequest;
520     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
521     {
522         BMCWEB_LOG_DEBUG << "Json value not readable";
523         return std::nullopt;
524     }
525     nlohmann::json::object_t* object =
526         jsonRequest.get_ptr<nlohmann::json::object_t*>();
527     if (object == nullptr || object->empty())
528     {
529         BMCWEB_LOG_DEBUG << "Json value is empty";
530         messages::emptyJSON(res);
531         return std::nullopt;
532     }
533     std::erase_if(*object,
534                   [](const std::pair<std::string, nlohmann::json>& item) {
535         return item.first.starts_with("@odata.");
536     });
537     if (object->empty())
538     {
539         //  If the update request only contains OData annotations, the service
540         //  should return the HTTP 400 Bad Request status code with the
541         //  NoOperation message from the Base Message Registry, ...
542         messages::noOperation(res);
543         return std::nullopt;
544     }
545 
546     return {std::move(jsonRequest)};
547 }
548 
549 template <typename... UnpackTypes>
550 bool readJsonPatch(const crow::Request& req, crow::Response& res,
551                    std::string_view key, UnpackTypes&&... in)
552 {
553     std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res);
554     if (jsonRequest == std::nullopt)
555     {
556         return false;
557     }
558 
559     return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
560 }
561 
562 template <typename... UnpackTypes>
563 bool readJsonAction(const crow::Request& req, crow::Response& res,
564                     const char* key, UnpackTypes&&... in)
565 {
566     nlohmann::json jsonRequest;
567     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
568     {
569         BMCWEB_LOG_DEBUG << "Json value not readable";
570         return false;
571     }
572     return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
573 }
574 
575 } // namespace json_util
576 } // namespace redfish
577