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