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 explicit UrlSegmentMatcherVisitor( 659 const boost::urls::string_value& segmentIn) : 660 segment(segmentIn) 661 {} 662 663 private: 664 const boost::urls::string_value& segment; 665 }; 666 667 inline bool readUrlSegments(const boost::urls::url_view& urlView, 668 std::initializer_list<UrlSegment>&& segments) 669 { 670 const boost::urls::segments_view& urlSegments = urlView.segments(); 671 672 if (!urlSegments.is_absolute() || segments.size() != urlSegments.size()) 673 { 674 return false; 675 } 676 677 boost::urls::segments_view::iterator it = urlSegments.begin(); 678 boost::urls::segments_view::iterator end = urlSegments.end(); 679 680 for (const auto& segment : segments) 681 { 682 if (!std::visit(UrlSegmentMatcherVisitor(*it), segment)) 683 { 684 return false; 685 } 686 it++; 687 } 688 return true; 689 } 690 691 } // namespace details 692 693 template <typename... Args> 694 inline bool readUrlSegments(const boost::urls::url_view& urlView, 695 Args&&... args) 696 { 697 return details::readUrlSegments(urlView, {std::forward<Args>(args)...}); 698 } 699 700 inline std::string setProtocolDefaults(const boost::urls::url_view& url) 701 { 702 if (url.scheme() == "https") 703 { 704 return "https"; 705 } 706 if (url.scheme() == "http") 707 { 708 if (bmcwebInsecureEnableHttpPushStyleEventing) 709 { 710 return "http"; 711 } 712 return ""; 713 } 714 return ""; 715 } 716 717 inline uint16_t setPortDefaults(const boost::urls::url_view& url) 718 { 719 uint16_t port = url.port_number(); 720 if (port != 0) 721 { 722 // user picked a port already. 723 return port; 724 } 725 726 // If the user hasn't explicitly stated a port, pick one explicitly for them 727 // based on the protocol defaults 728 if (url.scheme() == "http") 729 { 730 return 80; 731 } 732 if (url.scheme() == "https") 733 { 734 return 443; 735 } 736 return 0; 737 } 738 739 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto, 740 std::string& host, uint16_t& port, 741 std::string& path) 742 { 743 boost::string_view urlBoost(destUrl.data(), destUrl.size()); 744 boost::urls::result<boost::urls::url_view> url = 745 boost::urls::parse_uri(urlBoost); 746 if (!url) 747 { 748 return false; 749 } 750 urlProto = setProtocolDefaults(url.value()); 751 if (urlProto.empty()) 752 { 753 return false; 754 } 755 756 port = setPortDefaults(url.value()); 757 758 host = std::string_view(url->encoded_host().data(), 759 url->encoded_host().size()); 760 761 path = std::string_view(url->encoded_path().data(), 762 url->encoded_path().size()); 763 if (path.empty()) 764 { 765 path = "/"; 766 } 767 if (url->has_fragment()) 768 { 769 path += '#'; 770 path += std::string_view(url->encoded_fragment().data(), 771 url->encoded_fragment().size()); 772 } 773 774 if (url->has_query()) 775 { 776 path += '?'; 777 path += std::string_view(url->encoded_query().data(), 778 url->encoded_query().size()); 779 } 780 781 return true; 782 } 783 784 } // namespace utility 785 } // namespace crow 786 787 namespace nlohmann 788 { 789 template <> 790 struct adl_serializer<boost::urls::url> 791 { 792 // nlohmann requires a specific casing to look these up in adl 793 // NOLINTNEXTLINE(readability-identifier-naming) 794 static void to_json(json& j, const boost::urls::url& url) 795 { 796 j = url.string(); 797 } 798 }; 799 800 template <> 801 struct adl_serializer<boost::urls::url_view> 802 { 803 // NOLINTNEXTLINE(readability-identifier-naming) 804 static void to_json(json& j, const boost::urls::url_view& url) 805 { 806 j = url.string(); 807 } 808 }; 809 } // namespace nlohmann 810