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