xref: /openbmc/bmcweb/http/utility.hpp (revision 8b2521a5)
1 #pragma once
2 
3 #include "bmcweb_config.h"
4 
5 #include <openssl/crypto.h>
6 
7 #include <boost/callable_traits.hpp>
8 #include <boost/url/parse.hpp>
9 #include <boost/url/url.hpp>
10 #include <boost/url/url_view.hpp>
11 #include <nlohmann/json.hpp>
12 
13 #include <array>
14 #include <chrono>
15 #include <cstddef>
16 #include <cstdint>
17 #include <ctime>
18 #include <functional>
19 #include <iomanip>
20 #include <limits>
21 #include <stdexcept>
22 #include <string>
23 #include <string_view>
24 #include <tuple>
25 #include <type_traits>
26 #include <utility>
27 #include <variant>
28 
29 namespace crow
30 {
31 namespace black_magic
32 {
33 
34 enum class TypeCode : uint8_t
35 {
36     Unspecified = 0,
37     Integer = 1,
38     UnsignedInteger = 2,
39     Float = 3,
40     String = 4,
41     Path = 5,
42     Max = 6,
43 };
44 
45 // Remove when we have c++23
46 template <typename E>
47 constexpr typename std::underlying_type<E>::type toUnderlying(E e) noexcept
48 {
49     return static_cast<typename std::underlying_type<E>::type>(e);
50 }
51 
52 template <typename T>
53 constexpr TypeCode getParameterTag()
54 {
55     if constexpr (std::is_same_v<int, T>)
56     {
57         return TypeCode::Integer;
58     }
59     if constexpr (std::is_same_v<char, T>)
60     {
61         return TypeCode::Integer;
62     }
63     if constexpr (std::is_same_v<short, T>)
64     {
65         return TypeCode::Integer;
66     }
67     if constexpr (std::is_same_v<long, T>)
68     {
69         return TypeCode::Integer;
70     }
71     if constexpr (std::is_same_v<long long, T>)
72     {
73         return TypeCode::Integer;
74     }
75     if constexpr (std::is_same_v<unsigned int, T>)
76     {
77         return TypeCode::UnsignedInteger;
78     }
79     if constexpr (std::is_same_v<unsigned char, T>)
80     {
81         return TypeCode::UnsignedInteger;
82     }
83     if constexpr (std::is_same_v<unsigned short, T>)
84     {
85         return TypeCode::UnsignedInteger;
86     }
87     if constexpr (std::is_same_v<unsigned long, T>)
88     {
89         return TypeCode::UnsignedInteger;
90     }
91     if constexpr (std::is_same_v<unsigned long long, T>)
92     {
93         return TypeCode::UnsignedInteger;
94     }
95     if constexpr (std::is_same_v<double, T>)
96     {
97         return TypeCode::Float;
98     }
99     if constexpr (std::is_same_v<std::string, T>)
100     {
101         return TypeCode::String;
102     }
103     return TypeCode::Unspecified;
104 }
105 
106 template <typename... Args>
107 struct computeParameterTagFromArgsList;
108 
109 template <>
110 struct computeParameterTagFromArgsList<>
111 {
112     static constexpr int value = 0;
113 };
114 
115 template <typename Arg, typename... Args>
116 struct computeParameterTagFromArgsList<Arg, Args...>
117 {
118     static constexpr int subValue =
119         computeParameterTagFromArgsList<Args...>::value;
120     static constexpr int value =
121         getParameterTag<typename std::decay<Arg>::type>() !=
122                 TypeCode::Unspecified
123             ? static_cast<unsigned long>(subValue *
124                                          toUnderlying(TypeCode::Max)) +
125                   static_cast<uint64_t>(
126                       getParameterTag<typename std::decay<Arg>::type>())
127             : subValue;
128 };
129 
130 inline bool isParameterTagCompatible(uint64_t a, uint64_t b)
131 {
132     while (true)
133     {
134         if (a == 0 && b == 0)
135         {
136             // Both tags were equivalent, parameters are compatible
137             return true;
138         }
139         if (a == 0 || b == 0)
140         {
141             // one of the tags had more parameters than the other
142             return false;
143         }
144         TypeCode sa = static_cast<TypeCode>(a % toUnderlying(TypeCode::Max));
145         TypeCode sb = static_cast<TypeCode>(b % toUnderlying(TypeCode::Max));
146 
147         if (sa == TypeCode::Path)
148         {
149             sa = TypeCode::String;
150         }
151         if (sb == TypeCode::Path)
152         {
153             sb = TypeCode::String;
154         }
155         if (sa != sb)
156         {
157             return false;
158         }
159         a /= toUnderlying(TypeCode::Max);
160         b /= toUnderlying(TypeCode::Max);
161     }
162     return false;
163 }
164 
165 constexpr inline uint64_t getParameterTag(std::string_view url)
166 {
167     uint64_t tagValue = 0;
168     size_t urlSegmentIndex = std::string_view::npos;
169 
170     size_t paramIndex = 0;
171 
172     for (size_t urlIndex = 0; urlIndex < url.size(); urlIndex++)
173     {
174         char character = url[urlIndex];
175         if (character == '<')
176         {
177             if (urlSegmentIndex != std::string_view::npos)
178             {
179                 return 0;
180             }
181             urlSegmentIndex = urlIndex;
182         }
183         if (character == '>')
184         {
185             if (urlSegmentIndex == std::string_view::npos)
186             {
187                 return 0;
188             }
189             std::string_view tag =
190                 url.substr(urlSegmentIndex, urlIndex + 1 - urlSegmentIndex);
191 
192             // Note, this is a really lame way to do std::pow(6, paramIndex)
193             // std::pow doesn't work in constexpr in clang.
194             // Ideally in the future we'd move this to use a power of 2 packing
195             // (probably 8 instead of 6) so that these just become bit shifts
196             uint64_t insertIndex = 1;
197             for (size_t unused = 0; unused < paramIndex; unused++)
198             {
199                 insertIndex *= 6;
200             }
201 
202             if (tag == "<int>")
203             {
204                 tagValue += insertIndex * toUnderlying(TypeCode::Integer);
205             }
206             if (tag == "<uint>")
207             {
208                 tagValue +=
209                     insertIndex * toUnderlying(TypeCode::UnsignedInteger);
210             }
211             if (tag == "<float>" || tag == "<double>")
212             {
213                 tagValue += insertIndex * toUnderlying(TypeCode::Float);
214             }
215             if (tag == "<str>" || tag == "<string>")
216             {
217                 tagValue += insertIndex * toUnderlying(TypeCode::String);
218             }
219             if (tag == "<path>")
220             {
221                 tagValue += insertIndex * toUnderlying(TypeCode::Path);
222             }
223             paramIndex++;
224             urlSegmentIndex = std::string_view::npos;
225         }
226     }
227     if (urlSegmentIndex != std::string_view::npos)
228     {
229         return 0;
230     }
231     return tagValue;
232 }
233 
234 template <typename... T>
235 struct S
236 {
237     template <typename U>
238     using push = S<U, T...>;
239     template <typename U>
240     using push_back = S<T..., U>;
241     template <template <typename... Args> class U>
242     using rebind = U<T...>;
243 };
244 
245 template <typename F, typename Set>
246 struct CallHelper;
247 
248 template <typename F, typename... Args>
249 struct CallHelper<F, S<Args...>>
250 {
251     template <typename F1, typename... Args1,
252               typename = decltype(std::declval<F1>()(std::declval<Args1>()...))>
253     static char test(int);
254 
255     template <typename...>
256     static int test(...);
257 
258     static constexpr bool value = sizeof(test<F, Args...>(0)) == sizeof(char);
259 };
260 
261 template <uint64_t N>
262 struct SingleTagToType
263 {};
264 
265 template <>
266 struct SingleTagToType<1>
267 {
268     using type = int64_t;
269 };
270 
271 template <>
272 struct SingleTagToType<2>
273 {
274     using type = uint64_t;
275 };
276 
277 template <>
278 struct SingleTagToType<3>
279 {
280     using type = double;
281 };
282 
283 template <>
284 struct SingleTagToType<4>
285 {
286     using type = std::string;
287 };
288 
289 template <>
290 struct SingleTagToType<5>
291 {
292     using type = std::string;
293 };
294 
295 template <uint64_t Tag>
296 struct Arguments
297 {
298     using subarguments = typename Arguments<Tag / 6>::type;
299     using type = typename subarguments::template push<
300         typename SingleTagToType<Tag % 6>::type>;
301 };
302 
303 template <>
304 struct Arguments<0>
305 {
306     using type = S<>;
307 };
308 
309 template <typename T>
310 struct Promote
311 {
312     using type = T;
313 };
314 
315 template <typename T>
316 using PromoteT = typename Promote<T>::type;
317 
318 template <>
319 struct Promote<char>
320 {
321     using type = int64_t;
322 };
323 template <>
324 struct Promote<short>
325 {
326     using type = int64_t;
327 };
328 template <>
329 struct Promote<int>
330 {
331     using type = int64_t;
332 };
333 template <>
334 struct Promote<long>
335 {
336     using type = int64_t;
337 };
338 template <>
339 struct Promote<long long>
340 {
341     using type = int64_t;
342 };
343 template <>
344 struct Promote<unsigned char>
345 {
346     using type = uint64_t;
347 };
348 template <>
349 struct Promote<unsigned short>
350 {
351     using type = uint64_t;
352 };
353 template <>
354 struct Promote<unsigned int>
355 {
356     using type = uint64_t;
357 };
358 template <>
359 struct Promote<unsigned long>
360 {
361     using type = uint64_t;
362 };
363 template <>
364 struct Promote<unsigned long long>
365 {
366     using type = uint64_t;
367 };
368 
369 } // namespace black_magic
370 
371 namespace utility
372 {
373 
374 template <typename T>
375 struct FunctionTraits
376 {
377     template <size_t i>
378     using arg = std::tuple_element_t<i, boost::callable_traits::args_t<T>>;
379 };
380 
381 inline std::string base64encode(std::string_view data)
382 {
383     const std::array<char, 64> key = {
384         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
385         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
386         'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
387         'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
388         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
389 
390     size_t size = data.size();
391     std::string ret;
392     ret.resize((size + 2) / 3 * 4);
393     auto it = ret.begin();
394 
395     size_t i = 0;
396     while (i < size)
397     {
398         size_t keyIndex = 0;
399 
400         keyIndex = static_cast<size_t>(data[i] & 0xFC) >> 2;
401         *it++ = key[keyIndex];
402 
403         if (i + 1 < size)
404         {
405             keyIndex = static_cast<size_t>(data[i] & 0x03) << 4;
406             keyIndex += static_cast<size_t>(data[i + 1] & 0xF0) >> 4;
407             *it++ = key[keyIndex];
408 
409             if (i + 2 < size)
410             {
411                 keyIndex = static_cast<size_t>(data[i + 1] & 0x0F) << 2;
412                 keyIndex += static_cast<size_t>(data[i + 2] & 0xC0) >> 6;
413                 *it++ = key[keyIndex];
414 
415                 keyIndex = static_cast<size_t>(data[i + 2] & 0x3F);
416                 *it++ = key[keyIndex];
417             }
418             else
419             {
420                 keyIndex = static_cast<size_t>(data[i + 1] & 0x0F) << 2;
421                 *it++ = key[keyIndex];
422                 *it++ = '=';
423             }
424         }
425         else
426         {
427             keyIndex = static_cast<size_t>(data[i] & 0x03) << 4;
428             *it++ = key[keyIndex];
429             *it++ = '=';
430             *it++ = '=';
431         }
432 
433         i += 3;
434     }
435 
436     return ret;
437 }
438 
439 // TODO this is temporary and should be deleted once base64 is refactored out of
440 // crow
441 inline bool base64Decode(std::string_view input, std::string& output)
442 {
443     static const char nop = static_cast<char>(-1);
444     // See note on encoding_data[] in above function
445     static const std::array<char, 256> decodingData = {
446         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
447         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
448         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
449         nop, 62,  nop, nop, nop, 63,  52,  53,  54,  55,  56,  57,  58,  59,
450         60,  61,  nop, nop, nop, nop, nop, nop, nop, 0,   1,   2,   3,   4,
451         5,   6,   7,   8,   9,   10,  11,  12,  13,  14,  15,  16,  17,  18,
452         19,  20,  21,  22,  23,  24,  25,  nop, nop, nop, nop, nop, nop, 26,
453         27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
454         41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  nop, nop, nop,
455         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
456         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
457         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
458         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
459         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
460         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
461         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
462         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
463         nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
464         nop, nop, nop, nop};
465 
466     size_t inputLength = input.size();
467 
468     // allocate space for output string
469     output.clear();
470     output.reserve(((inputLength + 2) / 3) * 4);
471 
472     auto getCodeValue = [](char c) {
473         auto code = static_cast<unsigned char>(c);
474         // Ensure we cannot index outside the bounds of the decoding array
475         static_assert(std::numeric_limits<decltype(code)>::max() <
476                       decodingData.size());
477         return decodingData[code];
478     };
479 
480     // for each 4-bytes sequence from the input, extract 4 6-bits sequences by
481     // dropping first two bits
482     // and regenerate into 3 8-bits sequences
483 
484     for (size_t i = 0; i < inputLength; i++)
485     {
486         char base64code0 = 0;
487         char base64code1 = 0;
488         char base64code2 = 0; // initialized to 0 to suppress warnings
489 
490         base64code0 = getCodeValue(input[i]);
491         if (base64code0 == nop)
492         { // non base64 character
493             return false;
494         }
495         if (!(++i < inputLength))
496         { // we need at least two input bytes for first
497           // byte output
498             return false;
499         }
500         base64code1 = getCodeValue(input[i]);
501         if (base64code1 == nop)
502         { // non base64 character
503             return false;
504         }
505         output +=
506             static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3));
507 
508         if (++i < inputLength)
509         {
510             char c = input[i];
511             if (c == '=')
512             { // padding , end of input
513                 return (base64code1 & 0x0f) == 0;
514             }
515             base64code2 = getCodeValue(input[i]);
516             if (base64code2 == nop)
517             { // non base64 character
518                 return false;
519             }
520             output += static_cast<char>(((base64code1 << 4) & 0xf0) |
521                                         ((base64code2 >> 2) & 0x0f));
522         }
523 
524         if (++i < inputLength)
525         {
526             char c = input[i];
527             if (c == '=')
528             { // padding , end of input
529                 return (base64code2 & 0x03) == 0;
530             }
531             char base64code3 = getCodeValue(input[i]);
532             if (base64code3 == nop)
533             { // non base64 character
534                 return false;
535             }
536             output +=
537                 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
538         }
539     }
540 
541     return true;
542 }
543 
544 inline bool constantTimeStringCompare(std::string_view a, std::string_view b)
545 {
546     // Important note, this function is ONLY constant time if the two input
547     // sizes are the same
548     if (a.size() != b.size())
549     {
550         return false;
551     }
552     return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0;
553 }
554 
555 struct ConstantTimeCompare
556 {
557     bool operator()(std::string_view a, std::string_view b) const
558     {
559         return constantTimeStringCompare(a, b);
560     }
561 };
562 
563 namespace details
564 {
565 inline boost::urls::url
566     appendUrlPieces(boost::urls::url& url,
567                     const std::initializer_list<std::string_view> args)
568 {
569     for (std::string_view arg : args)
570     {
571         url.segments().push_back(arg);
572     }
573     return url;
574 }
575 
576 inline boost::urls::url
577     urlFromPiecesDetail(const std::initializer_list<std::string_view> args)
578 {
579     boost::urls::url url("/");
580     appendUrlPieces(url, args);
581     return url;
582 }
583 } // namespace details
584 
585 class OrMorePaths
586 {};
587 
588 template <typename... AV>
589 inline boost::urls::url urlFromPieces(const AV... args)
590 {
591     return details::urlFromPiecesDetail({args...});
592 }
593 
594 template <typename... AV>
595 inline void appendUrlPieces(boost::urls::url& url, const AV... args)
596 {
597     details::appendUrlPieces(url, {args...});
598 }
599 
600 namespace details
601 {
602 
603 // std::reference_wrapper<std::string> - extracts segment to variable
604 //                    std::string_view - checks if segment is equal to variable
605 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
606                                 std::string_view, OrMorePaths>;
607 
608 enum class UrlParseResult
609 {
610     Continue,
611     Fail,
612     Done,
613 };
614 
615 class UrlSegmentMatcherVisitor
616 {
617   public:
618     UrlParseResult operator()(std::string& output)
619     {
620         output = segment;
621         return UrlParseResult::Continue;
622     }
623 
624     UrlParseResult operator()(std::string_view expected)
625     {
626         if (segment == expected)
627         {
628             return UrlParseResult::Continue;
629         }
630         return UrlParseResult::Fail;
631     }
632 
633     UrlParseResult operator()(OrMorePaths /*unused*/)
634     {
635         return UrlParseResult::Done;
636     }
637 
638     explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
639         segment(segmentIn)
640     {}
641 
642   private:
643     std::string_view segment;
644 };
645 
646 inline bool readUrlSegments(boost::urls::url_view url,
647                             std::initializer_list<UrlSegment>&& segments)
648 {
649     boost::urls::segments_view urlSegments = url.segments();
650 
651     if (!urlSegments.is_absolute())
652     {
653         return false;
654     }
655 
656     boost::urls::segments_view::iterator it = urlSegments.begin();
657     boost::urls::segments_view::iterator end = urlSegments.end();
658 
659     for (const auto& segment : segments)
660     {
661         if (it == end)
662         {
663             // If the request ends with an "any" path, this was successful
664             return std::holds_alternative<OrMorePaths>(segment);
665         }
666         UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
667         if (res == UrlParseResult::Done)
668         {
669             return true;
670         }
671         if (res == UrlParseResult::Fail)
672         {
673             return false;
674         }
675         it++;
676     }
677 
678     // There will be an empty segment at the end if the URI ends with a "/"
679     // e.g. /redfish/v1/Chassis/
680     if ((it != end) && urlSegments.back().empty())
681     {
682         it++;
683     }
684     return it == end;
685 }
686 
687 } // namespace details
688 
689 template <typename... Args>
690 inline bool readUrlSegments(boost::urls::url_view url, Args&&... args)
691 {
692     return details::readUrlSegments(url, {std::forward<Args>(args)...});
693 }
694 
695 inline boost::urls::url replaceUrlSegment(boost::urls::url_view urlView,
696                                           const uint replaceLoc,
697                                           std::string_view newSegment)
698 {
699     boost::urls::segments_view urlSegments = urlView.segments();
700     boost::urls::url url("/");
701 
702     if (!urlSegments.is_absolute())
703     {
704         return url;
705     }
706 
707     boost::urls::segments_view::iterator it = urlSegments.begin();
708     boost::urls::segments_view::iterator end = urlSegments.end();
709 
710     for (uint idx = 0; it != end; it++, idx++)
711     {
712         if (idx == replaceLoc)
713         {
714             url.segments().push_back(newSegment);
715         }
716         else
717         {
718             url.segments().push_back(*it);
719         }
720     }
721 
722     return url;
723 }
724 
725 inline std::string setProtocolDefaults(boost::urls::url_view urlView)
726 {
727     if (urlView.scheme() == "https")
728     {
729         return "https";
730     }
731     if (urlView.scheme() == "http")
732     {
733         if (bmcwebInsecureEnableHttpPushStyleEventing)
734         {
735             return "http";
736         }
737         return "";
738     }
739     return "";
740 }
741 
742 inline uint16_t setPortDefaults(boost::urls::url_view url)
743 {
744     uint16_t port = url.port_number();
745     if (port != 0)
746     {
747         // user picked a port already.
748         return port;
749     }
750 
751     // If the user hasn't explicitly stated a port, pick one explicitly for them
752     // based on the protocol defaults
753     if (url.scheme() == "http")
754     {
755         return 80;
756     }
757     if (url.scheme() == "https")
758     {
759         return 443;
760     }
761     return 0;
762 }
763 
764 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto,
765                                 std::string& host, uint16_t& port,
766                                 std::string& path)
767 {
768     boost::urls::result<boost::urls::url_view> url =
769         boost::urls::parse_uri(destUrl);
770     if (!url)
771     {
772         return false;
773     }
774     urlProto = setProtocolDefaults(url.value());
775     if (urlProto.empty())
776     {
777         return false;
778     }
779 
780     port = setPortDefaults(url.value());
781 
782     host = url->encoded_host();
783 
784     path = url->encoded_path();
785     if (path.empty())
786     {
787         path = "/";
788     }
789     if (url->has_fragment())
790     {
791         path += '#';
792         path += url->encoded_fragment();
793     }
794 
795     if (url->has_query())
796     {
797         path += '?';
798         path += url->encoded_query();
799     }
800 
801     return true;
802 }
803 
804 } // namespace utility
805 } // namespace crow
806 
807 namespace nlohmann
808 {
809 template <>
810 struct adl_serializer<boost::urls::url>
811 {
812     // nlohmann requires a specific casing to look these up in adl
813     // NOLINTNEXTLINE(readability-identifier-naming)
814     static void to_json(json& j, const boost::urls::url& url)
815     {
816         j = url.buffer();
817     }
818 };
819 
820 template <>
821 struct adl_serializer<boost::urls::url_view>
822 {
823     // NOLINTNEXTLINE(readability-identifier-naming)
824     static void to_json(json& j, boost::urls::url_view url)
825     {
826         j = url.buffer();
827     }
828 };
829 } // namespace nlohmann
830