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 "logging.hpp"
22 #include "nlohmann/json.hpp"
23 
24 #include <array>
25 #include <cmath>
26 #include <cstddef>
27 #include <cstdint>
28 #include <limits>
29 #include <map>
30 #include <optional>
31 #include <span>
32 #include <string>
33 #include <string_view>
34 #include <type_traits>
35 #include <utility>
36 #include <variant>
37 #include <vector>
38 
39 // IWYU pragma: no_include <stdint.h>
40 
41 namespace crow
42 {
43 struct Request;
44 } // namespace crow
45 
46 namespace redfish
47 {
48 
49 namespace json_util
50 {
51 
52 /**
53  * @brief Processes request to extract JSON from its body. If it fails, adds
54  *       MalformedJSON message to response and ends it.
55  *
56  * @param[io]  res       Response object
57  * @param[in]  req       Request object
58  * @param[out] reqJson   JSON object extracted from request's body
59  *
60  * @return true if JSON is valid, false when JSON is invalid and response has
61  *         been filled with message and ended.
62  */
63 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
64                             nlohmann::json& reqJson);
65 namespace details
66 {
67 
68 template <typename Type>
69 struct IsOptional : std::false_type
70 {};
71 
72 template <typename Type>
73 struct IsOptional<std::optional<Type>> : std::true_type
74 {};
75 
76 template <typename Type>
77 struct IsVector : std::false_type
78 {};
79 
80 template <typename Type>
81 struct IsVector<std::vector<Type>> : std::true_type
82 {};
83 
84 template <typename Type>
85 struct IsStdArray : std::false_type
86 {};
87 
88 template <typename Type, std::size_t size>
89 struct IsStdArray<std::array<Type, size>> : std::true_type
90 {};
91 
92 enum class UnpackErrorCode
93 {
94     success,
95     invalidType,
96     outOfRange
97 };
98 
99 template <typename ToType, typename FromType>
100 bool checkRange(const FromType& from, std::string_view key)
101 {
102     if (from > std::numeric_limits<ToType>::max())
103     {
104         BMCWEB_LOG_DEBUG << "Value for key " << key
105                          << " was greater than max: " << __PRETTY_FUNCTION__;
106         return false;
107     }
108     if (from < std::numeric_limits<ToType>::lowest())
109     {
110         BMCWEB_LOG_DEBUG << "Value for key " << key
111                          << " was less than min: " << __PRETTY_FUNCTION__;
112         return false;
113     }
114     if constexpr (std::is_floating_point_v<ToType>)
115     {
116         if (std::isnan(from))
117         {
118             BMCWEB_LOG_DEBUG << "Value for key " << key << " was NAN";
119             return false;
120         }
121     }
122 
123     return true;
124 }
125 
126 template <typename Type>
127 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
128                                          std::string_view key, Type& value)
129 {
130     UnpackErrorCode ret = UnpackErrorCode::success;
131 
132     if constexpr (std::is_floating_point_v<Type>)
133     {
134         double helper = 0;
135         double* jsonPtr = jsonValue.get_ptr<double*>();
136 
137         if (jsonPtr == nullptr)
138         {
139             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
140             if (intPtr != nullptr)
141             {
142                 helper = static_cast<double>(*intPtr);
143                 jsonPtr = &helper;
144             }
145         }
146         if (jsonPtr == nullptr)
147         {
148             return UnpackErrorCode::invalidType;
149         }
150         if (!checkRange<Type>(*jsonPtr, key))
151         {
152             return UnpackErrorCode::outOfRange;
153         }
154         value = static_cast<Type>(*jsonPtr);
155     }
156 
157     else if constexpr (std::is_signed_v<Type>)
158     {
159         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
160         if (jsonPtr == nullptr)
161         {
162             return UnpackErrorCode::invalidType;
163         }
164         if (!checkRange<Type>(*jsonPtr, key))
165         {
166             return UnpackErrorCode::outOfRange;
167         }
168         value = static_cast<Type>(*jsonPtr);
169     }
170 
171     else if constexpr ((std::is_unsigned_v<Type>)&&(
172                            !std::is_same_v<bool, Type>))
173     {
174         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
175         if (jsonPtr == nullptr)
176         {
177             return UnpackErrorCode::invalidType;
178         }
179         if (!checkRange<Type>(*jsonPtr, key))
180         {
181             return UnpackErrorCode::outOfRange;
182         }
183         value = static_cast<Type>(*jsonPtr);
184     }
185 
186     else if constexpr (std::is_same_v<nlohmann::json, Type>)
187     {
188         value = std::move(jsonValue);
189     }
190     else
191     {
192         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
193         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
194         if (jsonPtr == nullptr)
195         {
196             BMCWEB_LOG_DEBUG
197                 << "Value for key " << key
198                 << " was incorrect type: " << jsonValue.type_name();
199             return UnpackErrorCode::invalidType;
200         }
201         value = std::move(*jsonPtr);
202     }
203     return ret;
204 }
205 
206 template <typename Type>
207 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
208                  crow::Response& res, Type& value)
209 {
210     bool ret = true;
211 
212     if constexpr (IsOptional<Type>::value)
213     {
214         value.emplace();
215         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
216                                                      *value) &&
217               ret;
218     }
219     else if constexpr (IsStdArray<Type>::value)
220     {
221         if (!jsonValue.is_array())
222         {
223             messages::propertyValueTypeError(
224                 res,
225                 res.jsonValue.dump(2, ' ', true,
226                                    nlohmann::json::error_handler_t::replace),
227                 key);
228             return false;
229         }
230         if (jsonValue.size() != value.size())
231         {
232             messages::propertyValueTypeError(
233                 res,
234                 res.jsonValue.dump(2, ' ', true,
235                                    nlohmann::json::error_handler_t::replace),
236                 key);
237             return false;
238         }
239         size_t index = 0;
240         for (const auto& val : jsonValue.items())
241         {
242             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
243                                                          value[index++]) &&
244                   ret;
245         }
246     }
247     else if constexpr (IsVector<Type>::value)
248     {
249         if (!jsonValue.is_array())
250         {
251             messages::propertyValueTypeError(
252                 res,
253                 res.jsonValue.dump(2, ' ', true,
254                                    nlohmann::json::error_handler_t::replace),
255                 key);
256             return false;
257         }
258 
259         for (const auto& val : jsonValue.items())
260         {
261             value.emplace_back();
262             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
263                                                          value.back()) &&
264                   ret;
265         }
266     }
267     else
268     {
269         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
270         if (ec != UnpackErrorCode::success)
271         {
272             if (ec == UnpackErrorCode::invalidType)
273             {
274                 messages::propertyValueTypeError(
275                     res,
276                     jsonValue.dump(2, ' ', true,
277                                    nlohmann::json::error_handler_t::replace),
278                     key);
279             }
280             else if (ec == UnpackErrorCode::outOfRange)
281             {
282                 messages::propertyValueNotInList(
283                     res,
284                     jsonValue.dump(2, ' ', true,
285                                    nlohmann::json::error_handler_t::replace),
286                     key);
287             }
288             return false;
289         }
290     }
291 
292     return ret;
293 }
294 
295 template <typename Type>
296 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
297 {
298     bool ret = true;
299     if constexpr (IsOptional<Type>::value)
300     {
301         value.emplace();
302         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
303               ret;
304     }
305     else if constexpr (IsStdArray<Type>::value)
306     {
307         if (!jsonValue.is_array())
308         {
309             return false;
310         }
311         if (jsonValue.size() != value.size())
312         {
313             return false;
314         }
315         size_t index = 0;
316         for (const auto& val : jsonValue.items())
317         {
318             ret = unpackValue<typename Type::value_type>(val.value(), key,
319                                                          value[index++]) &&
320                   ret;
321         }
322     }
323     else if constexpr (IsVector<Type>::value)
324     {
325         if (!jsonValue.is_array())
326         {
327             return false;
328         }
329 
330         for (const auto& val : jsonValue.items())
331         {
332             value.emplace_back();
333             ret = unpackValue<typename Type::value_type>(val.value(), key,
334                                                          value.back()) &&
335                   ret;
336         }
337     }
338     else
339     {
340         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
341         if (ec != UnpackErrorCode::success)
342         {
343             return false;
344         }
345     }
346 
347     return ret;
348 }
349 } // namespace details
350 
351 // clang-format off
352 using UnpackVariant = std::variant<
353     uint8_t*,
354     uint16_t*,
355     int16_t*,
356     uint32_t*,
357     int32_t*,
358     uint64_t*,
359     int64_t*,
360     bool*,
361     double*,
362     std::string*,
363     nlohmann::json*,
364     std::vector<uint8_t>*,
365     std::vector<uint16_t>*,
366     std::vector<int16_t>*,
367     std::vector<uint32_t>*,
368     std::vector<int32_t>*,
369     std::vector<uint64_t>*,
370     std::vector<int64_t>*,
371     //std::vector<bool>*,
372     std::vector<double>*,
373     std::vector<std::string>*,
374     std::vector<nlohmann::json>*,
375     std::optional<uint8_t>*,
376     std::optional<uint16_t>*,
377     std::optional<int16_t>*,
378     std::optional<uint32_t>*,
379     std::optional<int32_t>*,
380     std::optional<uint64_t>*,
381     std::optional<int64_t>*,
382     std::optional<bool>*,
383     std::optional<double>*,
384     std::optional<std::string>*,
385     std::optional<nlohmann::json>*,
386     std::optional<std::vector<uint8_t>>*,
387     std::optional<std::vector<uint16_t>>*,
388     std::optional<std::vector<int16_t>>*,
389     std::optional<std::vector<uint32_t>>*,
390     std::optional<std::vector<int32_t>>*,
391     std::optional<std::vector<uint64_t>>*,
392     std::optional<std::vector<int64_t>>*,
393     //std::optional<std::vector<bool>>*,
394     std::optional<std::vector<double>>*,
395     std::optional<std::vector<std::string>>*,
396     std::optional<std::vector<nlohmann::json>>*
397 >;
398 // clang-format on
399 
400 struct PerUnpack
401 {
402     std::string_view key;
403     UnpackVariant value;
404     bool complete = false;
405 };
406 
407 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
408                            std::span<PerUnpack> toUnpack)
409 {
410     bool result = true;
411     if (!jsonRequest.is_object())
412     {
413         BMCWEB_LOG_DEBUG << "Json value is not an object";
414         messages::unrecognizedRequestBody(res);
415         return false;
416     }
417     for (auto& item : jsonRequest.items())
418     {
419         size_t unpackIndex = 0;
420         for (; unpackIndex < toUnpack.size(); unpackIndex++)
421         {
422             PerUnpack& unpackSpec = toUnpack[unpackIndex];
423             std::string_view key = unpackSpec.key;
424             size_t keysplitIndex = key.find('/');
425             std::string_view leftover;
426             if (keysplitIndex != std::string_view::npos)
427             {
428                 leftover = key.substr(keysplitIndex + 1);
429                 key = key.substr(0, keysplitIndex);
430             }
431 
432             if (key != item.key() || unpackSpec.complete)
433             {
434                 continue;
435             }
436 
437             // Sublevel key
438             if (!leftover.empty())
439             {
440                 // Include the slash in the key so we can compare later
441                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
442                 nlohmann::json j;
443                 result = details::unpackValue<nlohmann::json>(item.value(), key,
444                                                               res, j) &&
445                          result;
446                 if (!result)
447                 {
448                     return result;
449                 }
450 
451                 std::vector<PerUnpack> nextLevel;
452                 for (PerUnpack& p : toUnpack)
453                 {
454                     if (!p.key.starts_with(key))
455                     {
456                         continue;
457                     }
458                     std::string_view thisLeftover = p.key.substr(key.size());
459                     nextLevel.push_back({thisLeftover, p.value, false});
460                     p.complete = true;
461                 }
462 
463                 result = readJsonHelper(j, res, nextLevel) && result;
464                 break;
465             }
466 
467             result = std::visit(
468                          [&item, &unpackSpec, &res](auto&& val) {
469                 using ContainedT =
470                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
471                 return details::unpackValue<ContainedT>(
472                     item.value(), unpackSpec.key, res, *val);
473                          },
474                          unpackSpec.value) &&
475                 result;
476 
477             unpackSpec.complete = true;
478             break;
479         }
480 
481         if (unpackIndex == toUnpack.size())
482         {
483             messages::propertyUnknown(res, item.key());
484             result = false;
485         }
486     }
487 
488     for (PerUnpack& perUnpack : toUnpack)
489     {
490         if (!perUnpack.complete)
491         {
492             bool isOptional = std::visit(
493                 [](auto&& val) {
494                 using ContainedType =
495                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
496                 return details::IsOptional<ContainedType>::value;
497                 },
498                 perUnpack.value);
499             if (isOptional)
500             {
501                 continue;
502             }
503             messages::propertyMissing(res, perUnpack.key);
504             result = false;
505         }
506     }
507     return result;
508 }
509 
510 inline void packVariant(std::span<PerUnpack> /*toPack*/)
511 {}
512 
513 template <typename FirstType, typename... UnpackTypes>
514 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
515                  FirstType& first, UnpackTypes&&... in)
516 {
517     if (toPack.empty())
518     {
519         return;
520     }
521     toPack[0].key = key;
522     toPack[0].value = &first;
523     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
524     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
525 }
526 
527 template <typename FirstType, typename... UnpackTypes>
528 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
529               std::string_view key, FirstType&& first, UnpackTypes&&... in)
530 {
531     const std::size_t n = sizeof...(UnpackTypes) + 2;
532     std::array<PerUnpack, n / 2> toUnpack2;
533     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
534     return readJsonHelper(jsonRequest, res, toUnpack2);
535 }
536 
537 inline std::optional<nlohmann::json>
538     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
539 {
540     nlohmann::json jsonRequest;
541     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
542     {
543         BMCWEB_LOG_DEBUG << "Json value not readable";
544         return std::nullopt;
545     }
546     nlohmann::json::object_t* object =
547         jsonRequest.get_ptr<nlohmann::json::object_t*>();
548     if (object == nullptr || object->empty())
549     {
550         BMCWEB_LOG_DEBUG << "Json value is empty";
551         messages::emptyJSON(res);
552         return std::nullopt;
553     }
554     std::erase_if(*object,
555                   [](const std::pair<std::string, nlohmann::json>& item) {
556         return item.first.starts_with("@odata.");
557     });
558     if (object->empty())
559     {
560         //  If the update request only contains OData annotations, the service
561         //  should return the HTTP 400 Bad Request status code with the
562         //  NoOperation message from the Base Message Registry, ...
563         messages::noOperation(res);
564         return std::nullopt;
565     }
566 
567     return {std::move(jsonRequest)};
568 }
569 
570 template <typename... UnpackTypes>
571 bool readJsonPatch(const crow::Request& req, crow::Response& res,
572                    std::string_view key, UnpackTypes&&... in)
573 {
574     std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res);
575     if (jsonRequest == std::nullopt)
576     {
577         return false;
578     }
579 
580     return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
581 }
582 
583 template <typename... UnpackTypes>
584 bool readJsonAction(const crow::Request& req, crow::Response& res,
585                     const char* key, UnpackTypes&&... in)
586 {
587     nlohmann::json jsonRequest;
588     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
589     {
590         BMCWEB_LOG_DEBUG << "Json value not readable";
591         return false;
592     }
593     return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
594 }
595 
596 } // namespace json_util
597 } // namespace redfish
598