xref: /openbmc/bmcweb/http/utility.hpp (revision 852432ac)
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