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