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