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