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