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