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_connection.hpp"
20 #include "http_request.hpp"
21 #include "http_response.hpp"
22 #include "human_sort.hpp"
23 #include "logging.hpp"
24 
25 #include <nlohmann/json.hpp>
26 
27 #include <algorithm>
28 #include <array>
29 #include <cmath>
30 #include <cstddef>
31 #include <cstdint>
32 #include <limits>
33 #include <map>
34 #include <optional>
35 #include <ranges>
36 #include <span>
37 #include <string>
38 #include <string_view>
39 #include <type_traits>
40 #include <utility>
41 #include <variant>
42 #include <vector>
43 
44 // IWYU pragma: no_include <stdint.h>
45 // IWYU pragma: no_forward_declare crow::Request
46 
47 namespace redfish
48 {
49 
50 namespace json_util
51 {
52 
53 /**
54  * @brief Processes request to extract JSON from its body. If it fails, adds
55  *       MalformedJSON message to response and ends it.
56  *
57  * @param[io]  res       Response object
58  * @param[in]  req       Request object
59  * @param[out] reqJson   JSON object extracted from request's body
60  *
61  * @return true if JSON is valid, false when JSON is invalid and response has
62  *         been filled with message and ended.
63  */
64 bool processJsonFromRequest(crow::Response& res, const crow::Request& req,
65                             nlohmann::json& reqJson);
66 namespace details
67 {
68 
69 template <typename Type>
70 struct IsOptional : std::false_type
71 {};
72 
73 template <typename Type>
74 struct IsOptional<std::optional<Type>> : std::true_type
75 {};
76 
77 template <typename Type>
78 struct IsVector : std::false_type
79 {};
80 
81 template <typename Type>
82 struct IsVector<std::vector<Type>> : std::true_type
83 {};
84 
85 template <typename Type>
86 struct IsStdArray : std::false_type
87 {};
88 
89 template <typename Type, std::size_t size>
90 struct IsStdArray<std::array<Type, size>> : std::true_type
91 {};
92 
93 enum class UnpackErrorCode
94 {
95     success,
96     invalidType,
97     outOfRange
98 };
99 
100 template <typename ToType, typename FromType>
101 bool checkRange(const FromType& from, std::string_view key)
102 {
103     if (from > std::numeric_limits<ToType>::max())
104     {
105         BMCWEB_LOG_DEBUG("Value for key {} was greater than max: {}", key,
106                          __PRETTY_FUNCTION__);
107         return false;
108     }
109     if (from < std::numeric_limits<ToType>::lowest())
110     {
111         BMCWEB_LOG_DEBUG("Value for key {} was less than min: {}", key,
112                          __PRETTY_FUNCTION__);
113         return false;
114     }
115     if constexpr (std::is_floating_point_v<ToType>)
116     {
117         if (std::isnan(from))
118         {
119             BMCWEB_LOG_DEBUG("Value for key {} was NAN", key);
120             return false;
121         }
122     }
123 
124     return true;
125 }
126 
127 template <typename Type>
128 UnpackErrorCode unpackValueWithErrorCode(nlohmann::json& jsonValue,
129                                          std::string_view key, Type& value)
130 {
131     UnpackErrorCode ret = UnpackErrorCode::success;
132 
133     if constexpr (std::is_floating_point_v<Type>)
134     {
135         double helper = 0;
136         double* jsonPtr = jsonValue.get_ptr<double*>();
137 
138         if (jsonPtr == nullptr)
139         {
140             int64_t* intPtr = jsonValue.get_ptr<int64_t*>();
141             if (intPtr != nullptr)
142             {
143                 helper = static_cast<double>(*intPtr);
144                 jsonPtr = &helper;
145             }
146         }
147         if (jsonPtr == nullptr)
148         {
149             return UnpackErrorCode::invalidType;
150         }
151         if (!checkRange<Type>(*jsonPtr, key))
152         {
153             return UnpackErrorCode::outOfRange;
154         }
155         value = static_cast<Type>(*jsonPtr);
156     }
157 
158     else if constexpr (std::is_signed_v<Type>)
159     {
160         int64_t* jsonPtr = jsonValue.get_ptr<int64_t*>();
161         if (jsonPtr == nullptr)
162         {
163             return UnpackErrorCode::invalidType;
164         }
165         if (!checkRange<Type>(*jsonPtr, key))
166         {
167             return UnpackErrorCode::outOfRange;
168         }
169         value = static_cast<Type>(*jsonPtr);
170     }
171 
172     else if constexpr ((std::is_unsigned_v<Type>)&&(
173                            !std::is_same_v<bool, Type>))
174     {
175         uint64_t* jsonPtr = jsonValue.get_ptr<uint64_t*>();
176         if (jsonPtr == nullptr)
177         {
178             return UnpackErrorCode::invalidType;
179         }
180         if (!checkRange<Type>(*jsonPtr, key))
181         {
182             return UnpackErrorCode::outOfRange;
183         }
184         value = static_cast<Type>(*jsonPtr);
185     }
186 
187     else if constexpr (std::is_same_v<nlohmann::json, Type>)
188     {
189         value = std::move(jsonValue);
190     }
191     else
192     {
193         using JsonType = std::add_const_t<std::add_pointer_t<Type>>;
194         JsonType jsonPtr = jsonValue.get_ptr<JsonType>();
195         if (jsonPtr == nullptr)
196         {
197             BMCWEB_LOG_DEBUG("Value for key {} was incorrect type: {}", key,
198                              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(res, res.jsonValue, key);
224             return false;
225         }
226         if (jsonValue.size() != value.size())
227         {
228             messages::propertyValueTypeError(res, res.jsonValue, key);
229             return false;
230         }
231         size_t index = 0;
232         for (const auto& val : jsonValue.items())
233         {
234             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
235                                                          value[index++]) &&
236                   ret;
237         }
238     }
239     else if constexpr (IsVector<Type>::value)
240     {
241         if (!jsonValue.is_array())
242         {
243             messages::propertyValueTypeError(res, res.jsonValue, key);
244             return false;
245         }
246 
247         for (const auto& val : jsonValue.items())
248         {
249             value.emplace_back();
250             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
251                                                          value.back()) &&
252                   ret;
253         }
254     }
255     else
256     {
257         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
258         if (ec != UnpackErrorCode::success)
259         {
260             if (ec == UnpackErrorCode::invalidType)
261             {
262                 messages::propertyValueTypeError(res, jsonValue, key);
263             }
264             else if (ec == UnpackErrorCode::outOfRange)
265             {
266                 messages::propertyValueNotInList(res, jsonValue, key);
267             }
268             return false;
269         }
270     }
271 
272     return ret;
273 }
274 
275 template <typename Type>
276 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
277 {
278     bool ret = true;
279     if constexpr (IsOptional<Type>::value)
280     {
281         value.emplace();
282         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
283               ret;
284     }
285     else if constexpr (IsStdArray<Type>::value)
286     {
287         if (!jsonValue.is_array())
288         {
289             return false;
290         }
291         if (jsonValue.size() != value.size())
292         {
293             return false;
294         }
295         size_t index = 0;
296         for (const auto& val : jsonValue.items())
297         {
298             ret = unpackValue<typename Type::value_type>(val.value(), key,
299                                                          value[index++]) &&
300                   ret;
301         }
302     }
303     else if constexpr (IsVector<Type>::value)
304     {
305         if (!jsonValue.is_array())
306         {
307             return false;
308         }
309 
310         for (const auto& val : jsonValue.items())
311         {
312             value.emplace_back();
313             ret = unpackValue<typename Type::value_type>(val.value(), key,
314                                                          value.back()) &&
315                   ret;
316         }
317     }
318     else
319     {
320         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
321         if (ec != UnpackErrorCode::success)
322         {
323             return false;
324         }
325     }
326 
327     return ret;
328 }
329 } // namespace details
330 
331 // clang-format off
332 using UnpackVariant = std::variant<
333     uint8_t*,
334     uint16_t*,
335     int16_t*,
336     uint32_t*,
337     int32_t*,
338     uint64_t*,
339     int64_t*,
340     bool*,
341     double*,
342     std::string*,
343     nlohmann::json*,
344     nlohmann::json::object_t*,
345     std::vector<uint8_t>*,
346     std::vector<uint16_t>*,
347     std::vector<int16_t>*,
348     std::vector<uint32_t>*,
349     std::vector<int32_t>*,
350     std::vector<uint64_t>*,
351     std::vector<int64_t>*,
352     //std::vector<bool>*,
353     std::vector<double>*,
354     std::vector<std::string>*,
355     std::vector<nlohmann::json>*,
356     std::vector<nlohmann::json::object_t>*,
357     std::optional<uint8_t>*,
358     std::optional<uint16_t>*,
359     std::optional<int16_t>*,
360     std::optional<uint32_t>*,
361     std::optional<int32_t>*,
362     std::optional<uint64_t>*,
363     std::optional<int64_t>*,
364     std::optional<bool>*,
365     std::optional<double>*,
366     std::optional<std::string>*,
367     std::optional<nlohmann::json>*,
368     std::optional<nlohmann::json::object_t>*,
369     std::optional<std::vector<uint8_t>>*,
370     std::optional<std::vector<uint16_t>>*,
371     std::optional<std::vector<int16_t>>*,
372     std::optional<std::vector<uint32_t>>*,
373     std::optional<std::vector<int32_t>>*,
374     std::optional<std::vector<uint64_t>>*,
375     std::optional<std::vector<int64_t>>*,
376     //std::optional<std::vector<bool>>*,
377     std::optional<std::vector<double>>*,
378     std::optional<std::vector<std::string>>*,
379     std::optional<std::vector<nlohmann::json>>*,
380     std::optional<std::vector<nlohmann::json::object_t>>*
381 >;
382 // clang-format on
383 
384 struct PerUnpack
385 {
386     std::string_view key;
387     UnpackVariant value;
388     bool complete = false;
389 };
390 
391 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
392                            std::span<PerUnpack> toUnpack);
393 
394 inline bool readJsonHelperObject(nlohmann::json::object_t& obj,
395                                  crow::Response& res,
396                                  std::span<PerUnpack> toUnpack)
397 {
398     bool result = true;
399     for (auto& item : obj)
400     {
401         size_t unpackIndex = 0;
402         for (; unpackIndex < toUnpack.size(); unpackIndex++)
403         {
404             PerUnpack& unpackSpec = toUnpack[unpackIndex];
405             std::string_view key = unpackSpec.key;
406             size_t keysplitIndex = key.find('/');
407             std::string_view leftover;
408             if (keysplitIndex != std::string_view::npos)
409             {
410                 leftover = key.substr(keysplitIndex + 1);
411                 key = key.substr(0, keysplitIndex);
412             }
413 
414             if (key != item.first || unpackSpec.complete)
415             {
416                 continue;
417             }
418 
419             // Sublevel key
420             if (!leftover.empty())
421             {
422                 // Include the slash in the key so we can compare later
423                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
424                 nlohmann::json j;
425                 result = details::unpackValue<nlohmann::json>(item.second, key,
426                                                               res, j) &&
427                          result;
428                 if (!result)
429                 {
430                     return result;
431                 }
432 
433                 std::vector<PerUnpack> nextLevel;
434                 for (PerUnpack& p : toUnpack)
435                 {
436                     if (!p.key.starts_with(key))
437                     {
438                         continue;
439                     }
440                     std::string_view thisLeftover = p.key.substr(key.size());
441                     nextLevel.push_back({thisLeftover, p.value, false});
442                     p.complete = true;
443                 }
444 
445                 result = readJsonHelper(j, res, nextLevel) && result;
446                 break;
447             }
448 
449             result = std::visit(
450                          [&item, &unpackSpec, &res](auto&& val) {
451                 using ContainedT =
452                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
453                 return details::unpackValue<ContainedT>(
454                     item.second, unpackSpec.key, res, *val);
455             },
456                          unpackSpec.value) &&
457                      result;
458 
459             unpackSpec.complete = true;
460             break;
461         }
462 
463         if (unpackIndex == toUnpack.size())
464         {
465             messages::propertyUnknown(res, item.first);
466             result = false;
467         }
468     }
469 
470     for (PerUnpack& perUnpack : toUnpack)
471     {
472         if (!perUnpack.complete)
473         {
474             bool isOptional = std::visit(
475                 [](auto&& val) {
476                 using ContainedType =
477                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
478                 return details::IsOptional<ContainedType>::value;
479             },
480                 perUnpack.value);
481             if (isOptional)
482             {
483                 continue;
484             }
485             messages::propertyMissing(res, perUnpack.key);
486             result = false;
487         }
488     }
489     return result;
490 }
491 
492 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
493                            std::span<PerUnpack> toUnpack)
494 {
495     nlohmann::json::object_t* obj =
496         jsonRequest.get_ptr<nlohmann::json::object_t*>();
497     if (obj == nullptr)
498     {
499         BMCWEB_LOG_DEBUG("Json value is not an object");
500         messages::unrecognizedRequestBody(res);
501         return false;
502     }
503     return readJsonHelperObject(*obj, res, toUnpack);
504 }
505 
506 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
507 
508 template <typename FirstType, typename... UnpackTypes>
509 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
510                  FirstType& first, UnpackTypes&&... in)
511 {
512     if (toPack.empty())
513     {
514         return;
515     }
516     toPack[0].key = key;
517     toPack[0].value = &first;
518     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
519     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
520 }
521 
522 template <typename FirstType, typename... UnpackTypes>
523 bool readJsonObject(nlohmann::json::object_t& jsonRequest, crow::Response& res,
524                     std::string_view key, FirstType&& first,
525                     UnpackTypes&&... in)
526 {
527     const std::size_t n = sizeof...(UnpackTypes) + 2;
528     std::array<PerUnpack, n / 2> toUnpack2;
529     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
530     return readJsonHelperObject(jsonRequest, res, toUnpack2);
531 }
532 
533 template <typename FirstType, typename... UnpackTypes>
534 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
535               std::string_view key, FirstType&& first, UnpackTypes&&... in)
536 {
537     nlohmann::json::object_t* obj =
538         jsonRequest.get_ptr<nlohmann::json::object_t*>();
539     if (obj == nullptr)
540     {
541         BMCWEB_LOG_DEBUG("Json value is not an object");
542         messages::unrecognizedRequestBody(res);
543         return false;
544     }
545     return readJsonObject(*obj, res, key, first, in...);
546 }
547 
548 inline std::optional<nlohmann::json::object_t>
549     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
550 {
551     nlohmann::json jsonRequest;
552     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
553     {
554         BMCWEB_LOG_DEBUG("Json value not readable");
555         return std::nullopt;
556     }
557     nlohmann::json::object_t* object =
558         jsonRequest.get_ptr<nlohmann::json::object_t*>();
559     if (object == nullptr || object->empty())
560     {
561         BMCWEB_LOG_DEBUG("Json value is empty");
562         messages::emptyJSON(res);
563         return std::nullopt;
564     }
565     std::erase_if(*object,
566                   [](const std::pair<std::string, nlohmann::json>& item) {
567         return item.first.starts_with("@odata.");
568     });
569     if (object->empty())
570     {
571         //  If the update request only contains OData annotations, the service
572         //  should return the HTTP 400 Bad Request status code with the
573         //  NoOperation message from the Base Message Registry, ...
574         messages::noOperation(res);
575         return std::nullopt;
576     }
577 
578     return {std::move(*object)};
579 }
580 
581 template <typename... UnpackTypes>
582 bool readJsonPatch(const crow::Request& req, crow::Response& res,
583                    std::string_view key, UnpackTypes&&... in)
584 {
585     std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res);
586     if (!jsonRequest)
587     {
588         return false;
589     }
590     nlohmann::json::object_t* object =
591         jsonRequest->get_ptr<nlohmann::json::object_t*>();
592     if (object == nullptr)
593     {
594         BMCWEB_LOG_DEBUG("Json value is empty");
595         messages::emptyJSON(res);
596         return false;
597     }
598 
599     return readJsonObject(*object, res, key,
600                           std::forward<UnpackTypes&&>(in)...);
601 }
602 
603 template <typename... UnpackTypes>
604 bool readJsonAction(const crow::Request& req, crow::Response& res,
605                     const char* key, UnpackTypes&&... in)
606 {
607     nlohmann::json jsonRequest;
608     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
609     {
610         BMCWEB_LOG_DEBUG("Json value not readable");
611         return false;
612     }
613     nlohmann::json::object_t* object =
614         jsonRequest.get_ptr<nlohmann::json::object_t*>();
615     if (object == nullptr)
616     {
617         BMCWEB_LOG_DEBUG("Json value is empty");
618         messages::emptyJSON(res);
619         return false;
620     }
621     return readJsonObject(*object, res, key,
622                           std::forward<UnpackTypes&&>(in)...);
623 }
624 
625 // Determines if two json objects are less, based on the presence of the
626 // @odata.id key
627 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
628 {
629     using object_t = nlohmann::json::object_t;
630     const object_t* aObj = a.get_ptr<const object_t*>();
631     const object_t* bObj = b.get_ptr<const object_t*>();
632 
633     if (aObj == nullptr)
634     {
635         if (bObj == nullptr)
636         {
637             return 0;
638         }
639         return -1;
640     }
641     if (bObj == nullptr)
642     {
643         return 1;
644     }
645     object_t::const_iterator aIt = aObj->find("@odata.id");
646     object_t::const_iterator bIt = bObj->find("@odata.id");
647     // If either object doesn't have the key, they get "sorted" to the end.
648     if (aIt == aObj->end())
649     {
650         if (bIt == bObj->end())
651         {
652             return 0;
653         }
654         return -1;
655     }
656     if (bIt == bObj->end())
657     {
658         return 1;
659     }
660     const nlohmann::json::string_t* nameA =
661         aIt->second.get_ptr<const std::string*>();
662     const nlohmann::json::string_t* nameB =
663         bIt->second.get_ptr<const std::string*>();
664     // If either object doesn't have a string as the key, they get "sorted" to
665     // the end.
666     if (nameA == nullptr)
667     {
668         if (nameB == nullptr)
669         {
670             return 0;
671         }
672         return -1;
673     }
674     if (nameB == nullptr)
675     {
676         return 1;
677     }
678     boost::urls::url_view aUrl(*nameA);
679     boost::urls::url_view bUrl(*nameB);
680     auto segmentsAIt = aUrl.segments().begin();
681     auto segmentsBIt = bUrl.segments().begin();
682 
683     while (true)
684     {
685         if (segmentsAIt == aUrl.segments().end())
686         {
687             if (segmentsBIt == bUrl.segments().end())
688             {
689                 return 0;
690             }
691             return -1;
692         }
693         if (segmentsBIt == bUrl.segments().end())
694         {
695             return 1;
696         }
697         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
698         if (res != 0)
699         {
700             return res;
701         }
702 
703         segmentsAIt++;
704         segmentsBIt++;
705     }
706 };
707 
708 struct ODataObjectLess
709 {
710     bool operator()(const nlohmann::json& left,
711                     const nlohmann::json& right) const
712     {
713         return odataObjectCmp(left, right) < 0;
714     }
715 };
716 
717 // Sort the JSON array by |element[key]|.
718 // Elements without |key| or type of |element[key]| is not string are smaller
719 // those whose |element[key]| is string.
720 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
721 {
722     std::ranges::sort(array, ODataObjectLess());
723 }
724 
725 // Returns the estimated size of the JSON value
726 // The implementation walks through every key and every value, accumulates the
727 //  total size of keys and values.
728 // Ideally, we should use a custom allocator that nlohmann JSON supports.
729 
730 // Assumption made:
731 //  1. number: 8 characters
732 //  2. boolean: 5 characters (False)
733 //  3. string: len(str) + 2 characters (quote)
734 //  4. bytes: len(bytes) characters
735 //  5. null: 4 characters (null)
736 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
737 
738 } // namespace json_util
739 } // namespace redfish
740