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