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