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(
282                     res,
283                     jsonValue.dump(2, ' ', true,
284                                    nlohmann::json::error_handler_t::replace),
285                     key);
286             }
287             return false;
288         }
289     }
290 
291     return ret;
292 }
293 
294 template <typename Type>
295 bool unpackValue(nlohmann::json& jsonValue, std::string_view key, Type& value)
296 {
297     bool ret = true;
298     if constexpr (IsOptional<Type>::value)
299     {
300         value.emplace();
301         ret = unpackValue<typename Type::value_type>(jsonValue, key, *value) &&
302               ret;
303     }
304     else if constexpr (IsStdArray<Type>::value)
305     {
306         if (!jsonValue.is_array())
307         {
308             return false;
309         }
310         if (jsonValue.size() != value.size())
311         {
312             return false;
313         }
314         size_t index = 0;
315         for (const auto& val : jsonValue.items())
316         {
317             ret = unpackValue<typename Type::value_type>(val.value(), key,
318                                                          value[index++]) &&
319                   ret;
320         }
321     }
322     else if constexpr (IsVector<Type>::value)
323     {
324         if (!jsonValue.is_array())
325         {
326             return false;
327         }
328 
329         for (const auto& val : jsonValue.items())
330         {
331             value.emplace_back();
332             ret = unpackValue<typename Type::value_type>(val.value(), key,
333                                                          value.back()) &&
334                   ret;
335         }
336     }
337     else
338     {
339         UnpackErrorCode ec = unpackValueWithErrorCode(jsonValue, key, value);
340         if (ec != UnpackErrorCode::success)
341         {
342             return false;
343         }
344     }
345 
346     return ret;
347 }
348 } // namespace details
349 
350 // clang-format off
351 using UnpackVariant = std::variant<
352     uint8_t*,
353     uint16_t*,
354     int16_t*,
355     uint32_t*,
356     int32_t*,
357     uint64_t*,
358     int64_t*,
359     bool*,
360     double*,
361     std::string*,
362     nlohmann::json*,
363     std::vector<uint8_t>*,
364     std::vector<uint16_t>*,
365     std::vector<int16_t>*,
366     std::vector<uint32_t>*,
367     std::vector<int32_t>*,
368     std::vector<uint64_t>*,
369     std::vector<int64_t>*,
370     //std::vector<bool>*,
371     std::vector<double>*,
372     std::vector<std::string>*,
373     std::vector<nlohmann::json>*,
374     std::optional<uint8_t>*,
375     std::optional<uint16_t>*,
376     std::optional<int16_t>*,
377     std::optional<uint32_t>*,
378     std::optional<int32_t>*,
379     std::optional<uint64_t>*,
380     std::optional<int64_t>*,
381     std::optional<bool>*,
382     std::optional<double>*,
383     std::optional<std::string>*,
384     std::optional<nlohmann::json>*,
385     std::optional<std::vector<uint8_t>>*,
386     std::optional<std::vector<uint16_t>>*,
387     std::optional<std::vector<int16_t>>*,
388     std::optional<std::vector<uint32_t>>*,
389     std::optional<std::vector<int32_t>>*,
390     std::optional<std::vector<uint64_t>>*,
391     std::optional<std::vector<int64_t>>*,
392     //std::optional<std::vector<bool>>*,
393     std::optional<std::vector<double>>*,
394     std::optional<std::vector<std::string>>*,
395     std::optional<std::vector<nlohmann::json>>*
396 >;
397 // clang-format on
398 
399 struct PerUnpack
400 {
401     std::string_view key;
402     UnpackVariant value;
403     bool complete = false;
404 };
405 
406 inline bool readJsonHelper(nlohmann::json& jsonRequest, crow::Response& res,
407                            std::span<PerUnpack> toUnpack)
408 {
409     bool result = true;
410     nlohmann::json::object_t* obj =
411         jsonRequest.get_ptr<nlohmann::json::object_t*>();
412     if (obj == nullptr)
413     {
414         BMCWEB_LOG_DEBUG << "Json value is not an object";
415         messages::unrecognizedRequestBody(res);
416         return false;
417     }
418     for (auto& item : *obj)
419     {
420         size_t unpackIndex = 0;
421         for (; unpackIndex < toUnpack.size(); unpackIndex++)
422         {
423             PerUnpack& unpackSpec = toUnpack[unpackIndex];
424             std::string_view key = unpackSpec.key;
425             size_t keysplitIndex = key.find('/');
426             std::string_view leftover;
427             if (keysplitIndex != std::string_view::npos)
428             {
429                 leftover = key.substr(keysplitIndex + 1);
430                 key = key.substr(0, keysplitIndex);
431             }
432 
433             if (key != item.first || unpackSpec.complete)
434             {
435                 continue;
436             }
437 
438             // Sublevel key
439             if (!leftover.empty())
440             {
441                 // Include the slash in the key so we can compare later
442                 key = unpackSpec.key.substr(0, keysplitIndex + 1);
443                 nlohmann::json j;
444                 result = details::unpackValue<nlohmann::json>(item.second, key,
445                                                               res, j) &&
446                          result;
447                 if (!result)
448                 {
449                     return result;
450                 }
451 
452                 std::vector<PerUnpack> nextLevel;
453                 for (PerUnpack& p : toUnpack)
454                 {
455                     if (!p.key.starts_with(key))
456                     {
457                         continue;
458                     }
459                     std::string_view thisLeftover = p.key.substr(key.size());
460                     nextLevel.push_back({thisLeftover, p.value, false});
461                     p.complete = true;
462                 }
463 
464                 result = readJsonHelper(j, res, nextLevel) && result;
465                 break;
466             }
467 
468             result = std::visit(
469                          [&item, &unpackSpec, &res](auto&& val) {
470                 using ContainedT =
471                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
472                 return details::unpackValue<ContainedT>(
473                     item.second, unpackSpec.key, res, *val);
474                          },
475                          unpackSpec.value) &&
476                 result;
477 
478             unpackSpec.complete = true;
479             break;
480         }
481 
482         if (unpackIndex == toUnpack.size())
483         {
484             messages::propertyUnknown(res, item.first);
485             result = false;
486         }
487     }
488 
489     for (PerUnpack& perUnpack : toUnpack)
490     {
491         if (!perUnpack.complete)
492         {
493             bool isOptional = std::visit(
494                 [](auto&& val) {
495                 using ContainedType =
496                     std::remove_pointer_t<std::decay_t<decltype(val)>>;
497                 return details::IsOptional<ContainedType>::value;
498                 },
499                 perUnpack.value);
500             if (isOptional)
501             {
502                 continue;
503             }
504             messages::propertyMissing(res, perUnpack.key);
505             result = false;
506         }
507     }
508     return result;
509 }
510 
511 inline void packVariant(std::span<PerUnpack> /*toPack*/) {}
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 // Determines if two json objects are less, based on the presence of the
597 // @odata.id key
598 inline int odataObjectCmp(const nlohmann::json& a, const nlohmann::json& b)
599 {
600     using object_t = nlohmann::json::object_t;
601     const object_t* aObj = a.get_ptr<const object_t*>();
602     const object_t* bObj = b.get_ptr<const object_t*>();
603 
604     if (aObj == nullptr)
605     {
606         if (bObj == nullptr)
607         {
608             return 0;
609         }
610         return -1;
611     }
612     if (bObj == nullptr)
613     {
614         return 1;
615     }
616     object_t::const_iterator aIt = aObj->find("@odata.id");
617     object_t::const_iterator bIt = bObj->find("@odata.id");
618     // If either object doesn't have the key, they get "sorted" to the end.
619     if (aIt == aObj->end())
620     {
621         if (bIt == bObj->end())
622         {
623             return 0;
624         }
625         return -1;
626     }
627     if (bIt == bObj->end())
628     {
629         return 1;
630     }
631     const nlohmann::json::string_t* nameA =
632         aIt->second.get_ptr<const std::string*>();
633     const nlohmann::json::string_t* nameB =
634         bIt->second.get_ptr<const std::string*>();
635     // If either object doesn't have a string as the key, they get "sorted" to
636     // the end.
637     if (nameA == nullptr)
638     {
639         if (nameB == nullptr)
640         {
641             return 0;
642         }
643         return -1;
644     }
645     if (nameB == nullptr)
646     {
647         return 1;
648     }
649     boost::urls::url_view aUrl(*nameA);
650     boost::urls::url_view bUrl(*nameB);
651     auto segmentsAIt = aUrl.segments().begin();
652     auto segmentsBIt = bUrl.segments().begin();
653 
654     while (true)
655     {
656         if (segmentsAIt == aUrl.segments().end())
657         {
658             if (segmentsBIt == bUrl.segments().end())
659             {
660                 return 0;
661             }
662             return -1;
663         }
664         if (segmentsBIt == bUrl.segments().end())
665         {
666             return 1;
667         }
668         int res = alphanumComp(*segmentsAIt, *segmentsBIt);
669         if (res != 0)
670         {
671             return res;
672         }
673 
674         segmentsAIt++;
675         segmentsBIt++;
676     }
677 };
678 
679 struct ODataObjectLess
680 {
681     bool operator()(const nlohmann::json& left,
682                     const nlohmann::json& right) const
683     {
684         return odataObjectCmp(left, right) < 0;
685     }
686 };
687 
688 // Sort the JSON array by |element[key]|.
689 // Elements without |key| or type of |element[key]| is not string are smaller
690 // those whose |element[key]| is string.
691 inline void sortJsonArrayByOData(nlohmann::json::array_t& array)
692 {
693     std::sort(array.begin(), array.end(), ODataObjectLess());
694 }
695 
696 } // namespace json_util
697 } // namespace redfish
698