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