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     std::vector<uint8_t>*,
345     std::vector<uint16_t>*,
346     std::vector<int16_t>*,
347     std::vector<uint32_t>*,
348     std::vector<int32_t>*,
349     std::vector<uint64_t>*,
350     std::vector<int64_t>*,
351     //std::vector<bool>*,
352     std::vector<double>*,
353     std::vector<std::string>*,
354     std::vector<nlohmann::json>*,
355     std::optional<uint8_t>*,
356     std::optional<uint16_t>*,
357     std::optional<int16_t>*,
358     std::optional<uint32_t>*,
359     std::optional<int32_t>*,
360     std::optional<uint64_t>*,
361     std::optional<int64_t>*,
362     std::optional<bool>*,
363     std::optional<double>*,
364     std::optional<std::string>*,
365     std::optional<nlohmann::json>*,
366     std::optional<std::vector<uint8_t>>*,
367     std::optional<std::vector<uint16_t>>*,
368     std::optional<std::vector<int16_t>>*,
369     std::optional<std::vector<uint32_t>>*,
370     std::optional<std::vector<int32_t>>*,
371     std::optional<std::vector<uint64_t>>*,
372     std::optional<std::vector<int64_t>>*,
373     //std::optional<std::vector<bool>>*,
374     std::optional<std::vector<double>>*,
375     std::optional<std::vector<std::string>>*,
376     std::optional<std::vector<nlohmann::json>>*
377 >;
378 // clang-format on
379 
380 struct PerUnpack
381 {
382     std::string_view key;
383     UnpackVariant value;
384     bool complete = false;
385 };
386 
387 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
388                            std::span<PerUnpack> toUnpack)
389 {
390     bool result = true;
391     nlohmann::json::object_t* obj =
392         jsonRequest.get_ptr<nlohmann::json::object_t*>();
393     if (obj == nullptr)
394     {
395         BMCWEB_LOG_DEBUG("Json value is not an object");
396         messages::unrecognizedRequestBody(res);
397         return false;
398     }
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 void packVariant(std::span<PerUnpack> /*toPack*/) {}
493 
494 template <typename FirstType, typename... UnpackTypes>
495 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
496                  FirstType& first, UnpackTypes&&... in)
497 {
498     if (toPack.empty())
499     {
500         return;
501     }
502     toPack[0].key = key;
503     toPack[0].value = &first;
504     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
505     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
506 }
507 
508 template <typename FirstType, typename... UnpackTypes>
509 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
510               std::string_view key, FirstType&& first, UnpackTypes&&... in)
511 {
512     const std::size_t n = sizeof...(UnpackTypes) + 2;
513     std::array<PerUnpack, n / 2> toUnpack2;
514     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
515     return readJsonHelper(jsonRequest, res, toUnpack2);
516 }
517 
518 inline std::optional<nlohmann::json>
519     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
520 {
521     nlohmann::json jsonRequest;
522     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
523     {
524         BMCWEB_LOG_DEBUG("Json value not readable");
525         return std::nullopt;
526     }
527     nlohmann::json::object_t* object =
528         jsonRequest.get_ptr<nlohmann::json::object_t*>();
529     if (object == nullptr || object->empty())
530     {
531         BMCWEB_LOG_DEBUG("Json value is empty");
532         messages::emptyJSON(res);
533         return std::nullopt;
534     }
535     std::erase_if(*object,
536                   [](const std::pair<std::string, nlohmann::json>& item) {
537         return item.first.starts_with("@odata.");
538     });
539     if (object->empty())
540     {
541         //  If the update request only contains OData annotations, the service
542         //  should return the HTTP 400 Bad Request status code with the
543         //  NoOperation message from the Base Message Registry, ...
544         messages::noOperation(res);
545         return std::nullopt;
546     }
547 
548     return {std::move(jsonRequest)};
549 }
550 
551 template <typename... UnpackTypes>
552 bool readJsonPatch(const crow::Request& req, crow::Response& res,
553                    std::string_view key, UnpackTypes&&... in)
554 {
555     std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res);
556     if (!jsonRequest)
557     {
558         return false;
559     }
560 
561     return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
562 }
563 
564 template <typename... UnpackTypes>
565 bool readJsonAction(const crow::Request& req, crow::Response& res,
566                     const char* key, UnpackTypes&&... in)
567 {
568     nlohmann::json jsonRequest;
569     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
570     {
571         BMCWEB_LOG_DEBUG("Json value not readable");
572         return false;
573     }
574     return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
575 }
576 
577 // Determines if two json objects are less, based on the presence of the
578 // @odata.id key
579 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
580 {
581     using object_t = nlohmann::json::object_t;
582     const object_t* aObj = a.get_ptr<const object_t*>();
583     const object_t* bObj = b.get_ptr<const object_t*>();
584 
585     if (aObj == nullptr)
586     {
587         if (bObj == nullptr)
588         {
589             return 0;
590         }
591         return -1;
592     }
593     if (bObj == nullptr)
594     {
595         return 1;
596     }
597     object_t::const_iterator aIt = aObj->find("@odata.id");
598     object_t::const_iterator bIt = bObj->find("@odata.id");
599     // If either object doesn't have the key, they get "sorted" to the end.
600     if (aIt == aObj->end())
601     {
602         if (bIt == bObj->end())
603         {
604             return 0;
605         }
606         return -1;
607     }
608     if (bIt == bObj->end())
609     {
610         return 1;
611     }
612     const nlohmann::json::string_t* nameA =
613         aIt->second.get_ptr<const std::string*>();
614     const nlohmann::json::string_t* nameB =
615         bIt->second.get_ptr<const std::string*>();
616     // If either object doesn't have a string as the key, they get "sorted" to
617     // the end.
618     if (nameA == nullptr)
619     {
620         if (nameB == nullptr)
621         {
622             return 0;
623         }
624         return -1;
625     }
626     if (nameB == nullptr)
627     {
628         return 1;
629     }
630     boost::urls::url_view aUrl(*nameA);
631     boost::urls::url_view bUrl(*nameB);
632     auto segmentsAIt = aUrl.segments().begin();
633     auto segmentsBIt = bUrl.segments().begin();
634 
635     while (true)
636     {
637         if (segmentsAIt == aUrl.segments().end())
638         {
639             if (segmentsBIt == bUrl.segments().end())
640             {
641                 return 0;
642             }
643             return -1;
644         }
645         if (segmentsBIt == bUrl.segments().end())
646         {
647             return 1;
648         }
649         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
650         if (res != 0)
651         {
652             return res;
653         }
654 
655         segmentsAIt++;
656         segmentsBIt++;
657     }
658 };
659 
660 struct ODataObjectLess
661 {
662     bool operator()(const nlohmann::json& left,
663                     const nlohmann::json& right) const
664     {
665         return odataObjectCmp(left, right) < 0;
666     }
667 };
668 
669 // Sort the JSON array by |element[key]|.
670 // Elements without |key| or type of |element[key]| is not string are smaller
671 // those whose |element[key]| is string.
672 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
673 {
674     std::ranges::sort(array, ODataObjectLess());
675 }
676 
677 // Returns the estimated size of the JSON value
678 // The implementation walks through every key and every value, accumulates the
679 //  total size of keys and values.
680 // Ideally, we should use a custom allocator that nlohmann JSON supports.
681 
682 // Assumption made:
683 //  1. number: 8 characters
684 //  2. boolean: 5 characters (False)
685 //  3. string: len(str) + 2 characters (quote)
686 //  4. bytes: len(bytes) characters
687 //  5. null: 4 characters (null)
688 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
689 
690 } // namespace json_util
691 } // namespace redfish
692