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 template <bool urlsafe = false> 236 inline bool base64Decode(std::string_view input, std::string& output) 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 static constexpr auto decodingData = getDecodeTable(urlsafe); 245 246 auto getCodeValue = [](char c) { 247 auto code = static_cast<unsigned char>(c); 248 // Ensure we cannot index outside the bounds of the decoding array 249 static_assert( 250 std::numeric_limits<decltype(code)>::max() < decodingData.size()); 251 return decodingData[code]; 252 }; 253 254 // for each 4-bytes sequence from the input, extract 4 6-bits sequences by 255 // dropping first two bits 256 // and regenerate into 3 8-bits sequences 257 258 for (size_t i = 0; i < inputLength; i++) 259 { 260 char base64code0 = 0; 261 char base64code1 = 0; 262 char base64code2 = 0; // initialized to 0 to suppress warnings 263 264 base64code0 = getCodeValue(input[i]); 265 if (base64code0 == nop) 266 { 267 // non base64 character 268 return false; 269 } 270 if (!(++i < inputLength)) 271 { 272 // we need at least two input bytes for first byte output 273 return false; 274 } 275 base64code1 = getCodeValue(input[i]); 276 if (base64code1 == nop) 277 { 278 // non base64 character 279 return false; 280 } 281 output += 282 static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3)); 283 284 if (++i < inputLength) 285 { 286 char c = input[i]; 287 if (c == '=') 288 { 289 // padding , end of input 290 return (base64code1 & 0x0f) == 0; 291 } 292 base64code2 = getCodeValue(input[i]); 293 if (base64code2 == nop) 294 { 295 // non base64 character 296 return false; 297 } 298 output += static_cast<char>( 299 ((base64code1 << 4) & 0xf0) | ((base64code2 >> 2) & 0x0f)); 300 } 301 302 if (++i < inputLength) 303 { 304 char c = input[i]; 305 if (c == '=') 306 { 307 // padding , end of input 308 return (base64code2 & 0x03) == 0; 309 } 310 char base64code3 = getCodeValue(input[i]); 311 if (base64code3 == nop) 312 { 313 // non base64 character 314 return false; 315 } 316 output += 317 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3)); 318 } 319 } 320 321 return true; 322 } 323 324 class OrMorePaths 325 {}; 326 327 template <typename... AV> 328 inline void appendUrlPieces(boost::urls::url& url, AV&&... args) 329 { 330 // Unclear the correct fix here. 331 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) 332 for (const std::string_view arg : {args...}) 333 { 334 url.segments().push_back(arg); 335 } 336 } 337 338 namespace details 339 { 340 341 // std::reference_wrapper<std::string> - extracts segment to variable 342 // std::string_view - checks if segment is equal to variable 343 using UrlSegment = std::variant<std::reference_wrapper<std::string>, 344 std::string_view, OrMorePaths>; 345 346 enum class UrlParseResult 347 { 348 Continue, 349 Fail, 350 Done, 351 }; 352 353 class UrlSegmentMatcherVisitor 354 { 355 public: 356 UrlParseResult operator()(std::string& output) 357 { 358 output = segment; 359 return UrlParseResult::Continue; 360 } 361 362 UrlParseResult operator()(std::string_view expected) 363 { 364 if (segment == expected) 365 { 366 return UrlParseResult::Continue; 367 } 368 return UrlParseResult::Fail; 369 } 370 371 UrlParseResult operator()(OrMorePaths /*unused*/) 372 { 373 return UrlParseResult::Done; 374 } 375 376 explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) : 377 segment(segmentIn) 378 {} 379 380 private: 381 std::string_view segment; 382 }; 383 384 inline bool readUrlSegments(const boost::urls::url_view_base& url, 385 std::initializer_list<UrlSegment> segments) 386 { 387 const boost::urls::segments_view& urlSegments = url.segments(); 388 389 if (!urlSegments.is_absolute()) 390 { 391 return false; 392 } 393 394 boost::urls::segments_view::const_iterator it = urlSegments.begin(); 395 boost::urls::segments_view::const_iterator end = urlSegments.end(); 396 397 std::string fragment = url.fragment(); 398 std::vector<std::string> fragmentParts; 399 bmcweb::split(fragmentParts, fragment, '/'); 400 auto fragIt = fragmentParts.begin(); 401 auto fragEnd = fragmentParts.end(); 402 403 // Url fragments start with a /, so we need to skip the first empty string 404 if (fragIt != fragEnd) 405 { 406 if (fragIt->empty()) 407 { 408 fragIt++; 409 } 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 end--; 417 } 418 419 for (const auto& segment : segments) 420 { 421 UrlParseResult res = UrlParseResult::Fail; 422 if (it == end) 423 { 424 if (fragIt == fragEnd) 425 { 426 return std::holds_alternative<OrMorePaths>(segment); 427 } 428 res = std::visit(UrlSegmentMatcherVisitor(*fragIt), segment); 429 fragIt++; 430 } 431 else 432 { 433 res = std::visit(UrlSegmentMatcherVisitor(*it), segment); 434 it++; 435 } 436 if (res == UrlParseResult::Done) 437 { 438 return true; 439 } 440 if (res == UrlParseResult::Fail) 441 { 442 return false; 443 } 444 } 445 446 return it == end; 447 } 448 449 } // namespace details 450 451 template <typename... Args> 452 inline bool readUrlSegments(const boost::urls::url_view_base& url, 453 Args&&... args) 454 { 455 return details::readUrlSegments(url, {std::forward<Args>(args)...}); 456 } 457 458 inline boost::urls::url replaceUrlSegment( 459 const boost::urls::url_view_base& urlView, const uint replaceLoc, 460 std::string_view newSegment) 461 { 462 const boost::urls::segments_view& urlSegments = urlView.segments(); 463 boost::urls::url url("/"); 464 465 if (!urlSegments.is_absolute()) 466 { 467 return url; 468 } 469 470 boost::urls::segments_view::iterator it = urlSegments.begin(); 471 boost::urls::segments_view::iterator end = urlSegments.end(); 472 473 for (uint idx = 0; it != end; it++, idx++) 474 { 475 if (idx == replaceLoc) 476 { 477 url.segments().push_back(newSegment); 478 } 479 else 480 { 481 url.segments().push_back(*it); 482 } 483 } 484 485 return url; 486 } 487 488 inline void setProtocolDefaults(boost::urls::url& url, 489 std::string_view protocol) 490 { 491 if (url.has_scheme()) 492 { 493 return; 494 } 495 if (protocol == "Redfish" || protocol.empty()) 496 { 497 if (url.port_number() == 443) 498 { 499 url.set_scheme("https"); 500 } 501 if (url.port_number() == 80) 502 { 503 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION) 504 { 505 url.set_scheme("http"); 506 } 507 } 508 } 509 else if (protocol == "SNMPv2c") 510 { 511 url.set_scheme("snmp"); 512 } 513 } 514 515 inline void setPortDefaults(boost::urls::url& url) 516 { 517 uint16_t port = url.port_number(); 518 if (port != 0) 519 { 520 return; 521 } 522 523 // If the user hasn't explicitly stated a port, pick one explicitly for them 524 // based on the protocol defaults 525 if (url.scheme() == "http") 526 { 527 url.set_port_number(80); 528 } 529 if (url.scheme() == "https") 530 { 531 url.set_port_number(443); 532 } 533 if (url.scheme() == "snmp") 534 { 535 url.set_port_number(162); 536 } 537 } 538 539 } // namespace utility 540 } // namespace crow 541 542 namespace nlohmann 543 { 544 template <std::derived_from<boost::urls::url_view_base> URL> 545 struct adl_serializer<URL> 546 { 547 // NOLINTNEXTLINE(readability-identifier-naming) 548 static void to_json(json& j, const URL& url) 549 { 550 j = url.buffer(); 551 } 552 }; 553 } // namespace nlohmann 554