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