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