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 = url.substr(urlSegmentIndex, 56 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(std::numeric_limits<decltype(code)>::max() < 243 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 { // non base64 character 260 return false; 261 } 262 if (!(++i < inputLength)) 263 { // we need at least two input bytes for first 264 // byte output 265 return false; 266 } 267 base64code1 = getCodeValue(input[i]); 268 if (base64code1 == nop) 269 { // non base64 character 270 return false; 271 } 272 output += 273 static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3)); 274 275 if (++i < inputLength) 276 { 277 char c = input[i]; 278 if (c == '=') 279 { // padding , end of input 280 return (base64code1 & 0x0f) == 0; 281 } 282 base64code2 = getCodeValue(input[i]); 283 if (base64code2 == nop) 284 { // non base64 character 285 return false; 286 } 287 output += static_cast<char>(((base64code1 << 4) & 0xf0) | 288 ((base64code2 >> 2) & 0x0f)); 289 } 290 291 if (++i < inputLength) 292 { 293 char c = input[i]; 294 if (c == '=') 295 { // padding , end of input 296 return (base64code2 & 0x03) == 0; 297 } 298 char base64code3 = getCodeValue(input[i]); 299 if (base64code3 == nop) 300 { // non base64 character 301 return false; 302 } 303 output += 304 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3)); 305 } 306 } 307 308 return true; 309 } 310 311 namespace details 312 { 313 inline boost::urls::url 314 appendUrlPieces(boost::urls::url& url, 315 const std::initializer_list<std::string_view> args) 316 { 317 for (std::string_view arg : args) 318 { 319 url.segments().push_back(arg); 320 } 321 return url; 322 } 323 324 } // namespace details 325 326 class OrMorePaths 327 {}; 328 329 template <typename... AV> 330 inline void appendUrlPieces(boost::urls::url& url, const AV... args) 331 { 332 details::appendUrlPieces(url, {args...}); 333 } 334 335 namespace details 336 { 337 338 // std::reference_wrapper<std::string> - extracts segment to variable 339 // std::string_view - checks if segment is equal to variable 340 using UrlSegment = std::variant<std::reference_wrapper<std::string>, 341 std::string_view, OrMorePaths>; 342 343 enum class UrlParseResult 344 { 345 Continue, 346 Fail, 347 Done, 348 }; 349 350 class UrlSegmentMatcherVisitor 351 { 352 public: 353 UrlParseResult operator()(std::string& output) 354 { 355 output = segment; 356 return UrlParseResult::Continue; 357 } 358 359 UrlParseResult operator()(std::string_view expected) 360 { 361 if (segment == expected) 362 { 363 return UrlParseResult::Continue; 364 } 365 return UrlParseResult::Fail; 366 } 367 368 UrlParseResult operator()(OrMorePaths /*unused*/) 369 { 370 return UrlParseResult::Done; 371 } 372 373 explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) : 374 segment(segmentIn) 375 {} 376 377 private: 378 std::string_view segment; 379 }; 380 381 inline bool readUrlSegments(const boost::urls::url_view_base& url, 382 std::initializer_list<UrlSegment> segments) 383 { 384 const boost::urls::segments_view& urlSegments = url.segments(); 385 386 if (!urlSegments.is_absolute()) 387 { 388 return false; 389 } 390 391 boost::urls::segments_view::const_iterator it = urlSegments.begin(); 392 boost::urls::segments_view::const_iterator end = urlSegments.end(); 393 394 for (const auto& segment : segments) 395 { 396 if (it == end) 397 { 398 // If the request ends with an "any" path, this was successful 399 return std::holds_alternative<OrMorePaths>(segment); 400 } 401 UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment); 402 if (res == UrlParseResult::Done) 403 { 404 return true; 405 } 406 if (res == UrlParseResult::Fail) 407 { 408 return false; 409 } 410 it++; 411 } 412 413 // There will be an empty segment at the end if the URI ends with a "/" 414 // e.g. /redfish/v1/Chassis/ 415 if ((it != end) && urlSegments.back().empty()) 416 { 417 it++; 418 } 419 return it == end; 420 } 421 422 } // namespace details 423 424 template <typename... Args> 425 inline bool readUrlSegments(const boost::urls::url_view_base& url, 426 Args&&... args) 427 { 428 return details::readUrlSegments(url, {std::forward<Args>(args)...}); 429 } 430 431 inline boost::urls::url 432 replaceUrlSegment(const boost::urls::url_view_base& urlView, 433 const uint replaceLoc, std::string_view newSegment) 434 { 435 const boost::urls::segments_view& urlSegments = urlView.segments(); 436 boost::urls::url url("/"); 437 438 if (!urlSegments.is_absolute()) 439 { 440 return url; 441 } 442 443 boost::urls::segments_view::iterator it = urlSegments.begin(); 444 boost::urls::segments_view::iterator end = urlSegments.end(); 445 446 for (uint idx = 0; it != end; it++, idx++) 447 { 448 if (idx == replaceLoc) 449 { 450 url.segments().push_back(newSegment); 451 } 452 else 453 { 454 url.segments().push_back(*it); 455 } 456 } 457 458 return url; 459 } 460 461 inline void setProtocolDefaults(boost::urls::url& url, 462 std::string_view protocol) 463 { 464 if (url.has_scheme()) 465 { 466 return; 467 } 468 if (protocol == "Redfish" || protocol.empty()) 469 { 470 if (url.port_number() == 443) 471 { 472 url.set_scheme("https"); 473 } 474 if (url.port_number() == 80) 475 { 476 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION) 477 { 478 url.set_scheme("http"); 479 } 480 } 481 } 482 else if (protocol == "SNMPv2c") 483 { 484 url.set_scheme("snmp"); 485 } 486 } 487 488 inline void setPortDefaults(boost::urls::url& url) 489 { 490 uint16_t port = url.port_number(); 491 if (port != 0) 492 { 493 return; 494 } 495 496 // If the user hasn't explicitly stated a port, pick one explicitly for them 497 // based on the protocol defaults 498 if (url.scheme() == "http") 499 { 500 url.set_port_number(80); 501 } 502 if (url.scheme() == "https") 503 { 504 url.set_port_number(443); 505 } 506 if (url.scheme() == "snmp") 507 { 508 url.set_port_number(162); 509 } 510 } 511 512 } // namespace utility 513 } // namespace crow 514 515 namespace nlohmann 516 { 517 template <std::derived_from<boost::urls::url_view_base> URL> 518 struct adl_serializer<URL> 519 { 520 // NOLINTNEXTLINE(readability-identifier-naming) 521 static void to_json(json& j, const URL& url) 522 { 523 j = url.buffer(); 524 } 525 }; 526 } // namespace nlohmann 527