xref: /openbmc/bmcweb/http/utility.hpp (revision ed76121b)
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     urlFromPiecesDetail(const std::initializer_list<std::string_view> args)
566 {
567     boost::urls::url url("/");
568     for (const std::string_view& arg : args)
569     {
570         url.segments().push_back(arg);
571     }
572     return url;
573 }
574 } // namespace details
575 
576 class OrMorePaths
577 {};
578 
579 template <typename... AV>
580 inline boost::urls::url urlFromPieces(const AV... args)
581 {
582     return details::urlFromPiecesDetail({args...});
583 }
584 
585 namespace details
586 {
587 
588 // std::reference_wrapper<std::string> - extracts segment to variable
589 //                    std::string_view - checks if segment is equal to variable
590 using UrlSegment = std::variant<std::reference_wrapper<std::string>,
591                                 std::string_view, OrMorePaths>;
592 
593 enum class UrlParseResult
594 {
595     Continue,
596     Fail,
597     Done,
598 };
599 
600 class UrlSegmentMatcherVisitor
601 {
602   public:
603     UrlParseResult operator()(std::string& output)
604     {
605         output = std::string_view(segment.data(), segment.size());
606         return UrlParseResult::Continue;
607     }
608 
609     UrlParseResult operator()(std::string_view expected)
610     {
611         if (std::string_view(segment.data(), segment.size()) == expected)
612         {
613             return UrlParseResult::Continue;
614         }
615         return UrlParseResult::Fail;
616     }
617 
618     UrlParseResult operator()(OrMorePaths /*unused*/)
619     {
620         return UrlParseResult::Done;
621     }
622 
623     explicit UrlSegmentMatcherVisitor(
624         const boost::urls::string_value& segmentIn) :
625         segment(segmentIn)
626     {}
627 
628   private:
629     const boost::urls::string_value& segment;
630 };
631 
632 inline bool readUrlSegments(const boost::urls::url_view& urlView,
633                             std::initializer_list<UrlSegment>&& segments)
634 {
635     const boost::urls::segments_view& urlSegments = urlView.segments();
636 
637     if (!urlSegments.is_absolute())
638     {
639         return false;
640     }
641 
642     boost::urls::segments_view::iterator it = urlSegments.begin();
643     boost::urls::segments_view::iterator end = urlSegments.end();
644 
645     for (const auto& segment : segments)
646     {
647         if (it == end)
648         {
649             // If the request ends with an "any" path, this was successful
650             return std::holds_alternative<OrMorePaths>(segment);
651         }
652         UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
653         if (res == UrlParseResult::Done)
654         {
655             return true;
656         }
657         if (res == UrlParseResult::Fail)
658         {
659             return false;
660         }
661         it++;
662     }
663 
664     // There will be an empty segment at the end if the URI ends with a "/"
665     // e.g. /redfish/v1/Chassis/
666     if ((it != end) && urlSegments.back().empty())
667     {
668         it++;
669     }
670     return it == end;
671 }
672 
673 } // namespace details
674 
675 template <typename... Args>
676 inline bool readUrlSegments(const boost::urls::url_view& urlView,
677                             Args&&... args)
678 {
679     return details::readUrlSegments(urlView, {std::forward<Args>(args)...});
680 }
681 
682 inline boost::urls::url replaceUrlSegment(const boost::urls::url_view& urlView,
683                                           const uint replaceLoc,
684                                           const std::string_view newSegment)
685 {
686     const boost::urls::segments_view& urlSegments = urlView.segments();
687     boost::urls::url url("/");
688 
689     if (!urlSegments.is_absolute())
690     {
691         return url;
692     }
693 
694     boost::urls::segments_view::iterator it = urlSegments.begin();
695     boost::urls::segments_view::iterator end = urlSegments.end();
696 
697     for (uint idx = 0; it != end; it++, idx++)
698     {
699         if (idx == replaceLoc)
700         {
701             url.segments().push_back(newSegment);
702         }
703         else
704         {
705             url.segments().push_back(*it);
706         }
707     }
708 
709     return url;
710 }
711 
712 inline std::string setProtocolDefaults(const boost::urls::url_view& url)
713 {
714     if (url.scheme() == "https")
715     {
716         return "https";
717     }
718     if (url.scheme() == "http")
719     {
720         if (bmcwebInsecureEnableHttpPushStyleEventing)
721         {
722             return "http";
723         }
724         return "";
725     }
726     return "";
727 }
728 
729 inline uint16_t setPortDefaults(const boost::urls::url_view& url)
730 {
731     uint16_t port = url.port_number();
732     if (port != 0)
733     {
734         // user picked a port already.
735         return port;
736     }
737 
738     // If the user hasn't explicitly stated a port, pick one explicitly for them
739     // based on the protocol defaults
740     if (url.scheme() == "http")
741     {
742         return 80;
743     }
744     if (url.scheme() == "https")
745     {
746         return 443;
747     }
748     return 0;
749 }
750 
751 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto,
752                                 std::string& host, uint16_t& port,
753                                 std::string& path)
754 {
755     boost::string_view urlBoost(destUrl.data(), destUrl.size());
756     boost::urls::result<boost::urls::url_view> url =
757         boost::urls::parse_uri(urlBoost);
758     if (!url)
759     {
760         return false;
761     }
762     urlProto = setProtocolDefaults(url.value());
763     if (urlProto.empty())
764     {
765         return false;
766     }
767 
768     port = setPortDefaults(url.value());
769 
770     host = std::string_view(url->encoded_host().data(),
771                             url->encoded_host().size());
772 
773     path = std::string_view(url->encoded_path().data(),
774                             url->encoded_path().size());
775     if (path.empty())
776     {
777         path = "/";
778     }
779     if (url->has_fragment())
780     {
781         path += '#';
782         path += std::string_view(url->encoded_fragment().data(),
783                                  url->encoded_fragment().size());
784     }
785 
786     if (url->has_query())
787     {
788         path += '?';
789         path += std::string_view(url->encoded_query().data(),
790                                  url->encoded_query().size());
791     }
792 
793     return true;
794 }
795 
796 } // namespace utility
797 } // namespace crow
798 
799 namespace nlohmann
800 {
801 template <>
802 struct adl_serializer<boost::urls::url>
803 {
804     // nlohmann requires a specific casing to look these up in adl
805     // NOLINTNEXTLINE(readability-identifier-naming)
806     static void to_json(json& j, const boost::urls::url& url)
807     {
808         j = url.string();
809     }
810 };
811 
812 template <>
813 struct adl_serializer<boost::urls::url_view>
814 {
815     // NOLINTNEXTLINE(readability-identifier-naming)
816     static void to_json(json& j, const boost::urls::url_view& url)
817     {
818         j = url.string();
819     }
820 };
821 } // namespace nlohmann
822