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