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