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 <span>
36 #include <string>
37 #include <string_view>
38 #include <type_traits>
39 #include <utility>
40 #include <variant>
41 #include <vector>
42 
43 // IWYU pragma: no_include <stdint.h>
44 // IWYU pragma: no_forward_declare crow::Request
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 {} was greater than max: {}", key,
105                          __PRETTY_FUNCTION__);
106         return false;
107     }
108     if (from < std::numeric_limits<ToType>::lowest())
109     {
110         BMCWEB_LOG_DEBUG("Value for key {} was less than min: {}", key,
111                          __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 {} was NAN", key);
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("Value for key {} was incorrect type: {}", key,
197                              jsonValue.type_name());
198             return UnpackErrorCode::invalidType;
199         }
200         value = std::move(*jsonPtr);
201     }
202     return ret;
203 }
204 
205 template <typename Type>
206 bool unpackValue(nlohmann::json& jsonValue, std::string_view key,
207                  crow::Response& res, Type& value)
208 {
209     bool ret = true;
210 
211     if constexpr (IsOptional<Type>::value)
212     {
213         value.emplace();
214         ret = unpackValue<typename Type::value_type>(jsonValue, key, res,
215                                                      *value) &&
216               ret;
217     }
218     else if constexpr (IsStdArray<Type>::value)
219     {
220         if (!jsonValue.is_array())
221         {
222             messages::propertyValueTypeError(res, res.jsonValue, key);
223             return false;
224         }
225         if (jsonValue.size() != value.size())
226         {
227             messages::propertyValueTypeError(res, res.jsonValue, key);
228             return false;
229         }
230         size_t index = 0;
231         for (const auto& val : jsonValue.items())
232         {
233             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
234                                                          value[index++]) &&
235                   ret;
236         }
237     }
238     else if constexpr (IsVector<Type>::value)
239     {
240         if (!jsonValue.is_array())
241         {
242             messages::propertyValueTypeError(res, res.jsonValue, key);
243             return false;
244         }
245 
246         for (const auto& val : jsonValue.items())
247         {
248             value.emplace_back();
249             ret = unpackValue<typename Type::value_type>(val.value(), key, res,
250                                                          value.back()) &&
251                   ret;
252         }
253     }
254     else
255     {
256         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
257         if (ec != UnpackErrorCode::success)
258         {
259             if (ec == UnpackErrorCode::invalidType)
260             {
261                 messages::propertyValueTypeError(res, jsonValue, key);
262             }
263             else if (ec == UnpackErrorCode::outOfRange)
264             {
265                 messages::propertyValueNotInList(res, jsonValue, 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     nlohmann::json::object_t* obj =
391         jsonRequest.get_ptr<nlohmann::json::object_t*>();
392     if (obj == nullptr)
393     {
394         BMCWEB_LOG_DEBUG("Json value is not an object");
395         messages::unrecognizedRequestBody(res);
396         return false;
397     }
398     for (auto& item : *obj)
399     {
400         size_t unpackIndex = 0;
401         for (; unpackIndex < toUnpack.size(); unpackIndex++)
402         {
403             PerUnpack& unpackSpec = toUnpack[unpackIndex];
404             std::string_view key = unpackSpec.key;
405             size_t keysplitIndex = key.find('/');
406             std::string_view leftover;
407             if (keysplitIndex != std::string_view::npos)
408             {
409                 leftover = key.substr(keysplitIndex + 1);
410                 key = key.substr(0, keysplitIndex);
411             }
412 
413             if (key != item.first || unpackSpec.complete)
414             {
415                 continue;
416             }
417 
418             // Sublevel key
419             if (!leftover.empty())
420             {
421                 // Include the slash in the key so we can compare later
422                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
423                 nlohmann::json j;
424                 result = details::unpackValue<nlohmann::json>(item.second, key,
425                                                               res, j) &&
426                          result;
427                 if (!result)
428                 {
429                     return result;
430                 }
431 
432                 std::vector<PerUnpack> nextLevel;
433                 for (PerUnpack& p : toUnpack)
434                 {
435                     if (!p.key.starts_with(key))
436                     {
437                         continue;
438                     }
439                     std::string_view thisLeftover = p.key.substr(key.size());
440                     nextLevel.push_back({thisLeftover, p.value, false});
441                     p.complete = true;
442                 }
443 
444                 result = readJsonHelper(j, res, nextLevel) && result;
445                 break;
446             }
447 
448             result = std::visit(
449                          [&item, &unpackSpec, &res](auto&& val) {
450                 using ContainedT =
451                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
452                 return details::unpackValue<ContainedT>(
453                     item.second, unpackSpec.key, res, *val);
454                          },
455                          unpackSpec.value) &&
456                 result;
457 
458             unpackSpec.complete = true;
459             break;
460         }
461 
462         if (unpackIndex == toUnpack.size())
463         {
464             messages::propertyUnknown(res, item.first);
465             result = false;
466         }
467     }
468 
469     for (PerUnpack& perUnpack : toUnpack)
470     {
471         if (!perUnpack.complete)
472         {
473             bool isOptional = std::visit(
474                 [](auto&& val) {
475                 using ContainedType =
476                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
477                 return details::IsOptional<ContainedType>::value;
478                 },
479                 perUnpack.value);
480             if (isOptional)
481             {
482                 continue;
483             }
484             messages::propertyMissing(res, perUnpack.key);
485             result = false;
486         }
487     }
488     return result;
489 }
490 
491 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
492 
493 template <typename FirstType, typename... UnpackTypes>
494 void packVariant(std::span<PerUnpack> toPack, std::string_view key,
495                  FirstType& first, UnpackTypes&&... in)
496 {
497     if (toPack.empty())
498     {
499         return;
500     }
501     toPack[0].key = key;
502     toPack[0].value = &first;
503     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
504     packVariant(toPack.subspan(1), std::forward<UnpackTypes&&>(in)...);
505 }
506 
507 template <typename FirstType, typename... UnpackTypes>
508 bool readJson(nlohmann::json& jsonRequest, crow::Response& res,
509               std::string_view key, FirstType&& first, UnpackTypes&&... in)
510 {
511     const std::size_t n = sizeof...(UnpackTypes) + 2;
512     std::array<PerUnpack, n / 2> toUnpack2;
513     packVariant(toUnpack2, key, first, std::forward<UnpackTypes&&>(in)...);
514     return readJsonHelper(jsonRequest, res, toUnpack2);
515 }
516 
517 inline std::optional<nlohmann::json>
518     readJsonPatchHelper(const crow::Request& req, crow::Response& res)
519 {
520     nlohmann::json jsonRequest;
521     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
522     {
523         BMCWEB_LOG_DEBUG("Json value not readable");
524         return std::nullopt;
525     }
526     nlohmann::json::object_t* object =
527         jsonRequest.get_ptr<nlohmann::json::object_t*>();
528     if (object == nullptr || object->empty())
529     {
530         BMCWEB_LOG_DEBUG("Json value is empty");
531         messages::emptyJSON(res);
532         return std::nullopt;
533     }
534     std::erase_if(*object,
535                   [](const std::pair<std::string, nlohmann::json>& item) {
536         return item.first.starts_with("@odata.");
537     });
538     if (object->empty())
539     {
540         //  If the update request only contains OData annotations, the service
541         //  should return the HTTP 400 Bad Request status code with the
542         //  NoOperation message from the Base Message Registry, ...
543         messages::noOperation(res);
544         return std::nullopt;
545     }
546 
547     return {std::move(jsonRequest)};
548 }
549 
550 template <typename... UnpackTypes>
551 bool readJsonPatch(const crow::Request& req, crow::Response& res,
552                    std::string_view key, UnpackTypes&&... in)
553 {
554     std::optional<nlohmann::json> jsonRequest = readJsonPatchHelper(req, res);
555     if (!jsonRequest)
556     {
557         return false;
558     }
559 
560     return readJson(*jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
561 }
562 
563 template <typename... UnpackTypes>
564 bool readJsonAction(const crow::Request& req, crow::Response& res,
565                     const char* key, UnpackTypes&&... in)
566 {
567     nlohmann::json jsonRequest;
568     if (!json_util::processJsonFromRequest(res, req, jsonRequest))
569     {
570         BMCWEB_LOG_DEBUG("Json value not readable");
571         return false;
572     }
573     return readJson(jsonRequest, res, key, std::forward<UnpackTypes&&>(in)...);
574 }
575 
576 // Determines if two json objects are less, based on the presence of the
577 // @odata.id key
578 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
579 {
580     using object_t = nlohmann::json::object_t;
581     const object_t* aObj = a.get_ptr<const object_t*>();
582     const object_t* bObj = b.get_ptr<const object_t*>();
583 
584     if (aObj == nullptr)
585     {
586         if (bObj == nullptr)
587         {
588             return 0;
589         }
590         return -1;
591     }
592     if (bObj == nullptr)
593     {
594         return 1;
595     }
596     object_t::const_iterator aIt = aObj->find("@odata.id");
597     object_t::const_iterator bIt = bObj->find("@odata.id");
598     // If either object doesn't have the key, they get "sorted" to the end.
599     if (aIt == aObj->end())
600     {
601         if (bIt == bObj->end())
602         {
603             return 0;
604         }
605         return -1;
606     }
607     if (bIt == bObj->end())
608     {
609         return 1;
610     }
611     const nlohmann::json::string_t* nameA =
612         aIt->second.get_ptr<const std::string*>();
613     const nlohmann::json::string_t* nameB =
614         bIt->second.get_ptr<const std::string*>();
615     // If either object doesn't have a string as the key, they get "sorted" to
616     // the end.
617     if (nameA == nullptr)
618     {
619         if (nameB == nullptr)
620         {
621             return 0;
622         }
623         return -1;
624     }
625     if (nameB == nullptr)
626     {
627         return 1;
628     }
629     boost::urls::url_view aUrl(*nameA);
630     boost::urls::url_view bUrl(*nameB);
631     auto segmentsAIt = aUrl.segments().begin();
632     auto segmentsBIt = bUrl.segments().begin();
633 
634     while (true)
635     {
636         if (segmentsAIt == aUrl.segments().end())
637         {
638             if (segmentsBIt == bUrl.segments().end())
639             {
640                 return 0;
641             }
642             return -1;
643         }
644         if (segmentsBIt == bUrl.segments().end())
645         {
646             return 1;
647         }
648         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
649         if (res != 0)
650         {
651             return res;
652         }
653 
654         segmentsAIt++;
655         segmentsBIt++;
656     }
657 };
658 
659 struct ODataObjectLess
660 {
661     bool operator()(const nlohmann::json& left,
662                     const nlohmann::json& right) const
663     {
664         return odataObjectCmp(left, right) < 0;
665     }
666 };
667 
668 // Sort the JSON array by |element[key]|.
669 // Elements without |key| or type of |element[key]| is not string are smaller
670 // those whose |element[key]| is string.
671 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
672 {
673     std::sort(array.begin(), array.end(), ODataObjectLess());
674 }
675 
676 // Returns the estimated size of the JSON value
677 // The implementation walks through every key and every value, accumulates the
678 //  total size of keys and values.
679 // Ideally, we should use a custom allocator that nlohmann JSON supports.
680 
681 // Assumption made:
682 //  1. number: 8 characters
683 //  2. boolean: 5 characters (False)
684 //  3. string: len(str) + 2 characters (quote)
685 //  4. bytes: len(bytes) characters
686 //  5. null: 4 characters (null)
687 uint64_t getEstimatedJsonSize(const nlohmann::json& root);
688 
689 } // namespace json_util
690 } // namespace redfish
691