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