1 #pragma once 2 3 #include <bmcweb_config.h> 4 #include <openssl/crypto.h> 5 6 #include <boost/callable_traits.hpp> 7 #include <boost/date_time/posix_time/posix_time.hpp> 8 #include <boost/url/url.hpp> 9 #include <nlohmann/json.hpp> 10 11 #include <array> 12 #include <chrono> 13 #include <cstddef> 14 #include <cstdint> 15 #include <ctime> 16 #include <functional> 17 #include <limits> 18 #include <stdexcept> 19 #include <string> 20 #include <string_view> 21 #include <tuple> 22 #include <type_traits> 23 #include <utility> 24 #include <variant> 25 26 namespace crow 27 { 28 namespace black_magic 29 { 30 31 enum class TypeCode : uint8_t 32 { 33 Unspecified = 0, 34 Integer = 1, 35 UnsignedInteger = 2, 36 Float = 3, 37 String = 4, 38 Path = 5, 39 Max = 6, 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 template <typename T> 50 constexpr TypeCode getParameterTag() 51 { 52 if constexpr (std::is_same_v<int, T>) 53 { 54 return TypeCode::Integer; 55 } 56 if constexpr (std::is_same_v<char, T>) 57 { 58 return TypeCode::Integer; 59 } 60 if constexpr (std::is_same_v<short, T>) 61 { 62 return TypeCode::Integer; 63 } 64 if constexpr (std::is_same_v<long, T>) 65 { 66 return TypeCode::Integer; 67 } 68 if constexpr (std::is_same_v<long long, T>) 69 { 70 return TypeCode::Integer; 71 } 72 if constexpr (std::is_same_v<unsigned int, T>) 73 { 74 return TypeCode::UnsignedInteger; 75 } 76 if constexpr (std::is_same_v<unsigned char, T>) 77 { 78 return TypeCode::UnsignedInteger; 79 } 80 if constexpr (std::is_same_v<unsigned short, T>) 81 { 82 return TypeCode::UnsignedInteger; 83 } 84 if constexpr (std::is_same_v<unsigned long, T>) 85 { 86 return TypeCode::UnsignedInteger; 87 } 88 if constexpr (std::is_same_v<unsigned long long, T>) 89 { 90 return TypeCode::UnsignedInteger; 91 } 92 if constexpr (std::is_same_v<double, T>) 93 { 94 return TypeCode::Float; 95 } 96 if constexpr (std::is_same_v<std::string, T>) 97 { 98 return TypeCode::String; 99 } 100 return TypeCode::Unspecified; 101 } 102 103 template <typename... Args> 104 struct computeParameterTagFromArgsList; 105 106 template <> 107 struct computeParameterTagFromArgsList<> 108 { 109 static constexpr int value = 0; 110 }; 111 112 template <typename Arg, typename... Args> 113 struct computeParameterTagFromArgsList<Arg, Args...> 114 { 115 static constexpr int subValue = 116 computeParameterTagFromArgsList<Args...>::value; 117 static constexpr int value = 118 getParameterTag<typename std::decay<Arg>::type>() != 119 TypeCode::Unspecified 120 ? static_cast<unsigned long>(subValue * 121 toUnderlying(TypeCode::Max)) + 122 static_cast<uint64_t>( 123 getParameterTag<typename std::decay<Arg>::type>()) 124 : subValue; 125 }; 126 127 inline bool isParameterTagCompatible(uint64_t a, uint64_t b) 128 { 129 while (true) 130 { 131 if (a == 0 && b == 0) 132 { 133 // Both tags were equivalent, parameters are compatible 134 return true; 135 } 136 if (a == 0 || b == 0) 137 { 138 // one of the tags had more parameters than the other 139 return false; 140 } 141 TypeCode sa = static_cast<TypeCode>(a % toUnderlying(TypeCode::Max)); 142 TypeCode sb = static_cast<TypeCode>(b % toUnderlying(TypeCode::Max)); 143 144 if (sa == TypeCode::Path) 145 { 146 sa = TypeCode::String; 147 } 148 if (sb == TypeCode::Path) 149 { 150 sb = TypeCode::String; 151 } 152 if (sa != sb) 153 { 154 return false; 155 } 156 a /= toUnderlying(TypeCode::Max); 157 b /= toUnderlying(TypeCode::Max); 158 } 159 return false; 160 } 161 162 constexpr inline uint64_t getParameterTag(std::string_view url) 163 { 164 uint64_t tagValue = 0; 165 size_t urlSegmentIndex = std::string_view::npos; 166 167 size_t paramIndex = 0; 168 169 for (size_t urlIndex = 0; urlIndex < url.size(); urlIndex++) 170 { 171 char character = url[urlIndex]; 172 if (character == '<') 173 { 174 if (urlSegmentIndex != std::string_view::npos) 175 { 176 return 0; 177 } 178 urlSegmentIndex = urlIndex; 179 } 180 if (character == '>') 181 { 182 if (urlSegmentIndex == std::string_view::npos) 183 { 184 return 0; 185 } 186 std::string_view tag = 187 url.substr(urlSegmentIndex, urlIndex + 1 - urlSegmentIndex); 188 189 // Note, this is a really lame way to do std::pow(6, paramIndex) 190 // std::pow doesn't work in constexpr in clang. 191 // Ideally in the future we'd move this to use a power of 2 packing 192 // (probably 8 instead of 6) so that these just become bit shifts 193 uint64_t insertIndex = 1; 194 for (size_t unused = 0; unused < paramIndex; unused++) 195 { 196 insertIndex *= 6; 197 } 198 199 if (tag == "<int>") 200 { 201 tagValue += insertIndex * toUnderlying(TypeCode::Integer); 202 } 203 if (tag == "<uint>") 204 { 205 tagValue += 206 insertIndex * toUnderlying(TypeCode::UnsignedInteger); 207 } 208 if (tag == "<float>" || tag == "<double>") 209 { 210 tagValue += insertIndex * toUnderlying(TypeCode::Float); 211 } 212 if (tag == "<str>" || tag == "<string>") 213 { 214 tagValue += insertIndex * toUnderlying(TypeCode::String); 215 } 216 if (tag == "<path>") 217 { 218 tagValue += insertIndex * toUnderlying(TypeCode::Path); 219 } 220 paramIndex++; 221 urlSegmentIndex = std::string_view::npos; 222 } 223 } 224 if (urlSegmentIndex != std::string_view::npos) 225 { 226 return 0; 227 } 228 return tagValue; 229 } 230 231 template <typename... T> 232 struct S 233 { 234 template <typename U> 235 using push = S<U, T...>; 236 template <typename U> 237 using push_back = S<T..., U>; 238 template <template <typename... Args> class U> 239 using rebind = U<T...>; 240 }; 241 242 template <typename F, typename Set> 243 struct CallHelper; 244 245 template <typename F, typename... Args> 246 struct CallHelper<F, S<Args...>> 247 { 248 template <typename F1, typename... Args1, 249 typename = decltype(std::declval<F1>()(std::declval<Args1>()...))> 250 static char test(int); 251 252 template <typename...> 253 static int test(...); 254 255 static constexpr bool value = sizeof(test<F, Args...>(0)) == sizeof(char); 256 }; 257 258 template <uint64_t N> 259 struct SingleTagToType 260 {}; 261 262 template <> 263 struct SingleTagToType<1> 264 { 265 using type = int64_t; 266 }; 267 268 template <> 269 struct SingleTagToType<2> 270 { 271 using type = uint64_t; 272 }; 273 274 template <> 275 struct SingleTagToType<3> 276 { 277 using type = double; 278 }; 279 280 template <> 281 struct SingleTagToType<4> 282 { 283 using type = std::string; 284 }; 285 286 template <> 287 struct SingleTagToType<5> 288 { 289 using type = std::string; 290 }; 291 292 template <uint64_t Tag> 293 struct Arguments 294 { 295 using subarguments = typename Arguments<Tag / 6>::type; 296 using type = typename subarguments::template push< 297 typename SingleTagToType<Tag % 6>::type>; 298 }; 299 300 template <> 301 struct Arguments<0> 302 { 303 using type = S<>; 304 }; 305 306 template <typename T> 307 struct Promote 308 { 309 using type = T; 310 }; 311 312 template <typename T> 313 using PromoteT = typename Promote<T>::type; 314 315 template <> 316 struct Promote<char> 317 { 318 using type = int64_t; 319 }; 320 template <> 321 struct Promote<short> 322 { 323 using type = int64_t; 324 }; 325 template <> 326 struct Promote<int> 327 { 328 using type = int64_t; 329 }; 330 template <> 331 struct Promote<long> 332 { 333 using type = int64_t; 334 }; 335 template <> 336 struct Promote<long long> 337 { 338 using type = int64_t; 339 }; 340 template <> 341 struct Promote<unsigned char> 342 { 343 using type = uint64_t; 344 }; 345 template <> 346 struct Promote<unsigned short> 347 { 348 using type = uint64_t; 349 }; 350 template <> 351 struct Promote<unsigned int> 352 { 353 using type = uint64_t; 354 }; 355 template <> 356 struct Promote<unsigned long> 357 { 358 using type = uint64_t; 359 }; 360 template <> 361 struct Promote<unsigned long long> 362 { 363 using type = uint64_t; 364 }; 365 366 } // namespace black_magic 367 368 namespace utility 369 { 370 371 template <typename T> 372 struct FunctionTraits 373 { 374 template <size_t i> 375 using arg = std::tuple_element_t<i, boost::callable_traits::args_t<T>>; 376 }; 377 378 inline std::string base64encode(const std::string_view data) 379 { 380 const std::array<char, 64> key = { 381 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 382 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 383 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 384 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 385 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; 386 387 size_t size = data.size(); 388 std::string ret; 389 ret.resize((size + 2) / 3 * 4); 390 auto it = ret.begin(); 391 392 size_t i = 0; 393 while (i < size) 394 { 395 size_t keyIndex = 0; 396 397 keyIndex = static_cast<size_t>(data[i] & 0xFC) >> 2; 398 *it++ = key[keyIndex]; 399 400 if (i + 1 < size) 401 { 402 keyIndex = static_cast<size_t>(data[i] & 0x03) << 4; 403 keyIndex += static_cast<size_t>(data[i + 1] & 0xF0) >> 4; 404 *it++ = key[keyIndex]; 405 406 if (i + 2 < size) 407 { 408 keyIndex = static_cast<size_t>(data[i + 1] & 0x0F) << 2; 409 keyIndex += static_cast<size_t>(data[i + 2] & 0xC0) >> 6; 410 *it++ = key[keyIndex]; 411 412 keyIndex = static_cast<size_t>(data[i + 2] & 0x3F); 413 *it++ = key[keyIndex]; 414 } 415 else 416 { 417 keyIndex = static_cast<size_t>(data[i + 1] & 0x0F) << 2; 418 *it++ = key[keyIndex]; 419 *it++ = '='; 420 } 421 } 422 else 423 { 424 keyIndex = static_cast<size_t>(data[i] & 0x03) << 4; 425 *it++ = key[keyIndex]; 426 *it++ = '='; 427 *it++ = '='; 428 } 429 430 i += 3; 431 } 432 433 return ret; 434 } 435 436 // TODO this is temporary and should be deleted once base64 is refactored out of 437 // crow 438 inline bool base64Decode(const std::string_view input, std::string& output) 439 { 440 static const char nop = static_cast<char>(-1); 441 // See note on encoding_data[] in above function 442 static const std::array<char, 256> decodingData = { 443 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 444 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 445 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 446 nop, 62, nop, nop, nop, 63, 52, 53, 54, 55, 56, 57, 58, 59, 447 60, 61, nop, nop, nop, nop, nop, nop, nop, 0, 1, 2, 3, 4, 448 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 449 19, 20, 21, 22, 23, 24, 25, nop, nop, nop, nop, nop, nop, 26, 450 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 451 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nop, nop, nop, 452 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 453 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 454 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 455 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 456 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 457 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 458 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 459 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 460 nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 461 nop, nop, nop, nop}; 462 463 size_t inputLength = input.size(); 464 465 // allocate space for output string 466 output.clear(); 467 output.reserve(((inputLength + 2) / 3) * 4); 468 469 auto getCodeValue = [](char c) { 470 auto code = static_cast<unsigned char>(c); 471 // Ensure we cannot index outside the bounds of the decoding array 472 static_assert(std::numeric_limits<decltype(code)>::max() < 473 decodingData.size()); 474 return decodingData[code]; 475 }; 476 477 // for each 4-bytes sequence from the input, extract 4 6-bits sequences by 478 // dropping first two bits 479 // and regenerate into 3 8-bits sequences 480 481 for (size_t i = 0; i < inputLength; i++) 482 { 483 char base64code0 = 0; 484 char base64code1 = 0; 485 char base64code2 = 0; // initialized to 0 to suppress warnings 486 char base64code3 = 0; 487 488 base64code0 = getCodeValue(input[i]); 489 if (base64code0 == nop) 490 { // non base64 character 491 return false; 492 } 493 if (!(++i < inputLength)) 494 { // we need at least two input bytes for first 495 // byte output 496 return false; 497 } 498 base64code1 = getCodeValue(input[i]); 499 if (base64code1 == nop) 500 { // non base64 character 501 return false; 502 } 503 output += 504 static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3)); 505 506 if (++i < inputLength) 507 { 508 char c = input[i]; 509 if (c == '=') 510 { // padding , end of input 511 return (base64code1 & 0x0f) == 0; 512 } 513 base64code2 = getCodeValue(input[i]); 514 if (base64code2 == nop) 515 { // non base64 character 516 return false; 517 } 518 output += static_cast<char>(((base64code1 << 4) & 0xf0) | 519 ((base64code2 >> 2) & 0x0f)); 520 } 521 522 if (++i < inputLength) 523 { 524 char c = input[i]; 525 if (c == '=') 526 { // padding , end of input 527 return (base64code2 & 0x03) == 0; 528 } 529 base64code3 = getCodeValue(input[i]); 530 if (base64code3 == nop) 531 { // non base64 character 532 return false; 533 } 534 output += 535 static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3)); 536 } 537 } 538 539 return true; 540 } 541 542 namespace details 543 { 544 constexpr uint64_t maxMilliSeconds = 253402300799999; 545 constexpr uint64_t maxSeconds = 253402300799; 546 inline std::string getDateTime(boost::posix_time::milliseconds timeSinceEpoch) 547 { 548 boost::posix_time::ptime epoch(boost::gregorian::date(1970, 1, 1)); 549 boost::posix_time::ptime time = epoch + timeSinceEpoch; 550 // append zero offset to the end according to the Redfish spec for Date-Time 551 return boost::posix_time::to_iso_extended_string(time) + "+00:00"; 552 } 553 } // namespace details 554 555 // Returns the formatted date time string. 556 // Note that the maximum supported date is 9999-12-31T23:59:59+00:00, if 557 // the given |secondsSinceEpoch| is too large, we return the maximum supported 558 // date. This behavior is to avoid exceptions throwed by Boost. 559 inline std::string getDateTimeUint(uint64_t secondsSinceEpoch) 560 { 561 secondsSinceEpoch = std::min(secondsSinceEpoch, details::maxSeconds); 562 boost::posix_time::seconds boostSeconds(secondsSinceEpoch); 563 return details::getDateTime( 564 boost::posix_time::milliseconds(boostSeconds.total_milliseconds())); 565 } 566 567 // Returns the formatted date time string. 568 // Note that the maximum supported date is 9999-12-31T23:59:59.999+00:00, if 569 // the given |millisSecondsSinceEpoch| is too large, we return the maximum 570 // supported date. 571 inline std::string getDateTimeUintMs(uint64_t milliSecondsSinceEpoch) 572 { 573 milliSecondsSinceEpoch = 574 std::min(details::maxMilliSeconds, milliSecondsSinceEpoch); 575 return details::getDateTime( 576 boost::posix_time::milliseconds(milliSecondsSinceEpoch)); 577 } 578 579 inline std::string getDateTimeStdtime(std::time_t secondsSinceEpoch) 580 { 581 // secondsSinceEpoch >= maxSeconds 582 if constexpr (std::cmp_less_equal(details::maxSeconds, 583 std::numeric_limits<std::time_t>::max())) 584 { 585 if (std::cmp_greater_equal(secondsSinceEpoch, details::maxSeconds)) 586 { 587 secondsSinceEpoch = details::maxSeconds; 588 } 589 } 590 boost::posix_time::ptime time = 591 boost::posix_time::from_time_t(secondsSinceEpoch); 592 return boost::posix_time::to_iso_extended_string(time) + "+00:00"; 593 } 594 595 /** 596 * Returns the current Date, Time & the local Time Offset 597 * infromation in a pair 598 * 599 * @param[in] None 600 * 601 * @return std::pair<std::string, std::string>, which consist 602 * of current DateTime & the TimeOffset strings respectively. 603 */ 604 inline std::pair<std::string, std::string> getDateTimeOffsetNow() 605 { 606 std::time_t time = std::time(nullptr); 607 std::string dateTime = getDateTimeStdtime(time); 608 609 /* extract the local Time Offset value from the 610 * recevied dateTime string. 611 */ 612 std::string timeOffset("Z00:00"); 613 std::size_t lastPos = dateTime.size(); 614 std::size_t len = timeOffset.size(); 615 if (lastPos > len) 616 { 617 timeOffset = dateTime.substr(lastPos - len); 618 } 619 620 return std::make_pair(dateTime, timeOffset); 621 } 622 623 inline bool constantTimeStringCompare(const std::string_view a, 624 const std::string_view b) 625 { 626 // Important note, this function is ONLY constant time if the two input 627 // sizes are the same 628 if (a.size() != b.size()) 629 { 630 return false; 631 } 632 return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0; 633 } 634 635 struct ConstantTimeCompare 636 { 637 bool operator()(const std::string_view a, const std::string_view b) const 638 { 639 return constantTimeStringCompare(a, b); 640 } 641 }; 642 643 namespace details 644 { 645 inline boost::urls::url 646 urlFromPiecesDetail(const std::initializer_list<std::string_view> args) 647 { 648 boost::urls::url url("/"); 649 for (const std::string_view& arg : args) 650 { 651 url.segments().push_back(arg); 652 } 653 return url; 654 } 655 } // namespace details 656 657 template <typename... AV> 658 inline boost::urls::url urlFromPieces(const AV... args) 659 { 660 return details::urlFromPiecesDetail({args...}); 661 } 662 663 namespace details 664 { 665 666 // std::reference_wrapper<std::string> - extracts segment to variable 667 // std::string_view - checks if segment is equal to variable 668 using UrlSegment = 669 std::variant<std::reference_wrapper<std::string>, std::string_view>; 670 671 class UrlSegmentMatcherVisitor 672 { 673 public: 674 bool operator()(std::string& output) 675 { 676 output = std::string_view(segment.data(), segment.size()); 677 return true; 678 } 679 680 bool operator()(std::string_view expected) 681 { 682 return std::string_view(segment.data(), segment.size()) == expected; 683 } 684 685 explicit UrlSegmentMatcherVisitor( 686 const boost::urls::string_value& segmentIn) : 687 segment(segmentIn) 688 {} 689 690 private: 691 const boost::urls::string_value& segment; 692 }; 693 694 inline bool readUrlSegments(const boost::urls::url_view& urlView, 695 std::initializer_list<UrlSegment>&& segments) 696 { 697 const boost::urls::segments_view& urlSegments = urlView.segments(); 698 699 if (!urlSegments.is_absolute() || segments.size() != urlSegments.size()) 700 { 701 return false; 702 } 703 704 boost::urls::segments_view::iterator it = urlSegments.begin(); 705 boost::urls::segments_view::iterator end = urlSegments.end(); 706 707 for (const auto& segment : segments) 708 { 709 if (!std::visit(UrlSegmentMatcherVisitor(*it), segment)) 710 { 711 return false; 712 } 713 it++; 714 } 715 return true; 716 } 717 718 } // namespace details 719 720 template <typename... Args> 721 inline bool readUrlSegments(const boost::urls::url_view& urlView, 722 Args&&... args) 723 { 724 return details::readUrlSegments(urlView, {std::forward<Args>(args)...}); 725 } 726 727 inline std::string setProtocolDefaults(const boost::urls::url_view& url) 728 { 729 if (url.scheme() == "https") 730 { 731 return "https"; 732 } 733 if (url.scheme() == "http") 734 { 735 if (bmcwebInsecureEnableHttpPushStyleEventing) 736 { 737 return "http"; 738 } 739 return ""; 740 } 741 return ""; 742 } 743 744 inline uint16_t setPortDefaults(const boost::urls::url_view& url) 745 { 746 uint16_t port = url.port_number(); 747 if (port != 0) 748 { 749 // user picked a port already. 750 return port; 751 } 752 753 // If the user hasn't explicitly stated a port, pick one explicitly for them 754 // based on the protocol defaults 755 if (url.scheme() == "http") 756 { 757 return 80; 758 } 759 if (url.scheme() == "https") 760 { 761 return 443; 762 } 763 return 0; 764 } 765 766 inline bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto, 767 std::string& host, uint16_t& port, 768 std::string& path) 769 { 770 boost::string_view urlBoost(destUrl.data(), destUrl.size()); 771 boost::urls::result<boost::urls::url_view> url = 772 boost::urls::parse_uri(urlBoost); 773 if (!url) 774 { 775 return false; 776 } 777 urlProto = setProtocolDefaults(url.value()); 778 if (urlProto.empty()) 779 { 780 return false; 781 } 782 783 port = setPortDefaults(url.value()); 784 785 host = std::string_view(url->encoded_host().data(), 786 url->encoded_host().size()); 787 788 path = std::string_view(url->encoded_path().data(), 789 url->encoded_path().size()); 790 if (path.empty()) 791 { 792 path = "/"; 793 } 794 if (url->has_fragment()) 795 { 796 path += '#'; 797 path += std::string_view(url->encoded_fragment().data(), 798 url->encoded_fragment().size()); 799 } 800 801 if (url->has_query()) 802 { 803 path += '?'; 804 path += std::string_view(url->encoded_query().data(), 805 url->encoded_query().size()); 806 } 807 808 return true; 809 } 810 811 } // namespace utility 812 } // namespace crow 813 814 namespace nlohmann 815 { 816 template <> 817 struct adl_serializer<boost::urls::url> 818 { 819 // nlohmann requires a specific casing to look these up in adl 820 // NOLINTNEXTLINE(readability-identifier-naming) 821 static void to_json(json& j, const boost::urls::url& url) 822 { 823 j = url.string(); 824 } 825 }; 826 827 template <> 828 struct adl_serializer<boost::urls::url_view> 829 { 830 // NOLINTNEXTLINE(readability-identifier-naming) 831 static void to_json(json& j, const boost::urls::url_view& url) 832 { 833 j = url.string(); 834 } 835 }; 836 } // namespace nlohmann 837