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