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