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 } // namespace details 405 406 class OrMorePaths 407 {}; 408 409 template <typename... AV> 410 inline void appendUrlPieces(boost::urls::url& url, const AV... args) 411 { 412 details::appendUrlPieces(url, {args...}); 413 } 414 415 namespace details 416 { 417 418 // std::reference_wrapper<std::string> - extracts segment to variable 419 // std::string_view - checks if segment is equal to variable 420 using UrlSegment = std::variant<std::reference_wrapper<std::string>, 421 std::string_view, OrMorePaths>; 422 423 enum class UrlParseResult 424 { 425 Continue, 426 Fail, 427 Done, 428 }; 429 430 class UrlSegmentMatcherVisitor 431 { 432 public: 433 UrlParseResult operator()(std::string& output) 434 { 435 output = segment; 436 return UrlParseResult::Continue; 437 } 438 439 UrlParseResult operator()(std::string_view expected) 440 { 441 if (segment == expected) 442 { 443 return UrlParseResult::Continue; 444 } 445 return UrlParseResult::Fail; 446 } 447 448 UrlParseResult operator()(OrMorePaths /*unused*/) 449 { 450 return UrlParseResult::Done; 451 } 452 453 explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) : 454 segment(segmentIn) 455 {} 456 457 private: 458 std::string_view segment; 459 }; 460 461 inline bool readUrlSegments(boost::urls::url_view url, 462 std::initializer_list<UrlSegment>&& segments) 463 { 464 boost::urls::segments_view urlSegments = url.segments(); 465 466 if (!urlSegments.is_absolute()) 467 { 468 return false; 469 } 470 471 boost::urls::segments_view::iterator it = urlSegments.begin(); 472 boost::urls::segments_view::iterator end = urlSegments.end(); 473 474 for (const auto& segment : segments) 475 { 476 if (it == end) 477 { 478 // If the request ends with an "any" path, this was successful 479 return std::holds_alternative<OrMorePaths>(segment); 480 } 481 UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment); 482 if (res == UrlParseResult::Done) 483 { 484 return true; 485 } 486 if (res == UrlParseResult::Fail) 487 { 488 return false; 489 } 490 it++; 491 } 492 493 // There will be an empty segment at the end if the URI ends with a "/" 494 // e.g. /redfish/v1/Chassis/ 495 if ((it != end) && urlSegments.back().empty()) 496 { 497 it++; 498 } 499 return it == end; 500 } 501 502 } // namespace details 503 504 template <typename... Args> 505 inline bool readUrlSegments(boost::urls::url_view url, Args&&... args) 506 { 507 return details::readUrlSegments(url, {std::forward<Args>(args)...}); 508 } 509 510 inline boost::urls::url replaceUrlSegment(boost::urls::url_view urlView, 511 const uint replaceLoc, 512 std::string_view newSegment) 513 { 514 boost::urls::segments_view urlSegments = urlView.segments(); 515 boost::urls::url url("/"); 516 517 if (!urlSegments.is_absolute()) 518 { 519 return url; 520 } 521 522 boost::urls::segments_view::iterator it = urlSegments.begin(); 523 boost::urls::segments_view::iterator end = urlSegments.end(); 524 525 for (uint idx = 0; it != end; it++, idx++) 526 { 527 if (idx == replaceLoc) 528 { 529 url.segments().push_back(newSegment); 530 } 531 else 532 { 533 url.segments().push_back(*it); 534 } 535 } 536 537 return url; 538 } 539 540 inline std::string setProtocolDefaults(boost::urls::url_view urlView) 541 { 542 if (urlView.scheme() == "https") 543 { 544 return "https"; 545 } 546 if (urlView.scheme() == "http") 547 { 548 if (bmcwebInsecureEnableHttpPushStyleEventing) 549 { 550 return "http"; 551 } 552 return ""; 553 } 554 return ""; 555 } 556 557 inline uint16_t setPortDefaults(boost::urls::url_view url) 558 { 559 uint16_t port = url.port_number(); 560 if (port != 0) 561 { 562 // user picked a port already. 563 return port; 564 } 565 566 // If the user hasn't explicitly stated a port, pick one explicitly for them 567 // based on the protocol defaults 568 if (url.scheme() == "http") 569 { 570 return 80; 571 } 572 if (url.scheme() == "https") 573 { 574 return 443; 575 } 576 return 0; 577 } 578 579 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto, 580 std::string& host, uint16_t& port, 581 std::string& path) 582 { 583 boost::urls::result<boost::urls::url_view> url = 584 boost::urls::parse_uri(destUrl); 585 if (!url) 586 { 587 return false; 588 } 589 urlProto = setProtocolDefaults(url.value()); 590 if (urlProto.empty()) 591 { 592 return false; 593 } 594 595 port = setPortDefaults(url.value()); 596 597 host = url->encoded_host(); 598 599 path = url->encoded_path(); 600 if (path.empty()) 601 { 602 path = "/"; 603 } 604 if (url->has_fragment()) 605 { 606 path += '#'; 607 path += url->encoded_fragment(); 608 } 609 610 if (url->has_query()) 611 { 612 path += '?'; 613 path += url->encoded_query(); 614 } 615 616 return true; 617 } 618 619 } // namespace utility 620 } // namespace crow 621 622 namespace nlohmann 623 { 624 template <> 625 struct adl_serializer<boost::urls::url> 626 { 627 // nlohmann requires a specific casing to look these up in adl 628 // NOLINTNEXTLINE(readability-identifier-naming) 629 static void to_json(json& j, const boost::urls::url& url) 630 { 631 j = url.buffer(); 632 } 633 }; 634 635 template <> 636 struct adl_serializer<boost::urls::url_view> 637 { 638 // NOLINTNEXTLINE(readability-identifier-naming) 639 static void to_json(json& j, boost::urls::url_view url) 640 { 641 j = url.buffer(); 642 } 643 }; 644 } // namespace nlohmann 645