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