1//
2// httplib.h
3//
4// Copyright (c) 2025 Yuji Hirose. All rights reserved.
5// MIT License
6//
7
8#ifndef CPPHTTPLIB_HTTPLIB_H
9#define CPPHTTPLIB_HTTPLIB_H
10
11#define CPPHTTPLIB_VERSION "0.27.0"
12#define CPPHTTPLIB_VERSION_NUM "0x001B00"
13
14/*
15 * Platform compatibility check
16 */
17
18#if defined(_WIN32) && !defined(_WIN64)
19#if defined(_MSC_VER)
20#pragma message( \
21 "cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler.")
22#else
23#warning \
24 "cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler."
25#endif
26#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8
27#warning \
28 "cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler."
29#elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8
30#warning \
31 "cpp-httplib doesn't support platforms where size_t is less than 64 bits."
32#endif
33
34#ifdef _WIN32
35#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
36#error \
37 "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later."
38#endif
39#endif
40
41/*
42 * Configuration
43 */
44
45#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
46#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
47#endif
48
49#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
50#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
51#endif
52
53#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
54#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
55#endif
56
57#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
58#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
59#endif
60
61#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
62#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
63#endif
64
65#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
66#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
67#endif
68
69#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
70#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
71#endif
72
73#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
74#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
75#endif
76
77#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
78#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
79#endif
80
81#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
82#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
83#endif
84
85#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
86#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
87#endif
88
89#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
90#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
91#endif
92
93#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
94#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
95#endif
96
97#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
98#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
99#endif
100
101#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
102#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
103#endif
104
105#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
106#ifdef _WIN32
107#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000
108#else
109#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
110#endif
111#endif
112
113#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
114#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
115#endif
116
117#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
118#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
119#endif
120
121#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
122#define CPPHTTPLIB_HEADER_MAX_COUNT 100
123#endif
124
125#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
126#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
127#endif
128
129#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
130#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
131#endif
132
133#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
134#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits<size_t>::max)())
135#endif
136
137#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
138#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
139#endif
140
141#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
142#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
143#endif
144
145#ifndef CPPHTTPLIB_TCP_NODELAY
146#define CPPHTTPLIB_TCP_NODELAY false
147#endif
148
149#ifndef CPPHTTPLIB_IPV6_V6ONLY
150#define CPPHTTPLIB_IPV6_V6ONLY false
151#endif
152
153#ifndef CPPHTTPLIB_RECV_BUFSIZ
154#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
155#endif
156
157#ifndef CPPHTTPLIB_SEND_BUFSIZ
158#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u)
159#endif
160
161#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
162#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
163#endif
164
165#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
166#define CPPHTTPLIB_THREAD_POOL_COUNT \
167 ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
168 ? std::thread::hardware_concurrency() - 1 \
169 : 0))
170#endif
171
172#ifndef CPPHTTPLIB_RECV_FLAGS
173#define CPPHTTPLIB_RECV_FLAGS 0
174#endif
175
176#ifndef CPPHTTPLIB_SEND_FLAGS
177#define CPPHTTPLIB_SEND_FLAGS 0
178#endif
179
180#ifndef CPPHTTPLIB_LISTEN_BACKLOG
181#define CPPHTTPLIB_LISTEN_BACKLOG 5
182#endif
183
184#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
185#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
186#endif
187
188/*
189 * Headers
190 */
191
192#ifdef _WIN32
193#ifndef _CRT_SECURE_NO_WARNINGS
194#define _CRT_SECURE_NO_WARNINGS
195#endif //_CRT_SECURE_NO_WARNINGS
196
197#ifndef _CRT_NONSTDC_NO_DEPRECATE
198#define _CRT_NONSTDC_NO_DEPRECATE
199#endif //_CRT_NONSTDC_NO_DEPRECATE
200
201#if defined(_MSC_VER)
202#if _MSC_VER < 1900
203#error Sorry, Visual Studio versions prior to 2015 are not supported
204#endif
205
206#pragma comment(lib, "ws2_32.lib")
207
208using ssize_t = __int64;
209#endif // _MSC_VER
210
211#ifndef S_ISREG
212#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
213#endif // S_ISREG
214
215#ifndef S_ISDIR
216#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
217#endif // S_ISDIR
218
219#ifndef NOMINMAX
220#define NOMINMAX
221#endif // NOMINMAX
222
223#include <io.h>
224#include <winsock2.h>
225#include <ws2tcpip.h>
226
227#if defined(__has_include)
228#if __has_include(<afunix.h>)
229// afunix.h uses types declared in winsock2.h, so has to be included after it.
230#include <afunix.h>
231#define CPPHTTPLIB_HAVE_AFUNIX_H 1
232#endif
233#endif
234
235#ifndef WSA_FLAG_NO_HANDLE_INHERIT
236#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
237#endif
238
239using nfds_t = unsigned long;
240using socket_t = SOCKET;
241using socklen_t = int;
242
243#else // not _WIN32
244
245#include <arpa/inet.h>
246#if !defined(_AIX) && !defined(__MVS__)
247#include <ifaddrs.h>
248#endif
249#ifdef __MVS__
250#include <strings.h>
251#ifndef NI_MAXHOST
252#define NI_MAXHOST 1025
253#endif
254#endif
255#include <net/if.h>
256#include <netdb.h>
257#include <netinet/in.h>
258#ifdef __linux__
259#include <resolv.h>
260#endif
261#include <csignal>
262#include <netinet/tcp.h>
263#include <poll.h>
264#include <pthread.h>
265#include <sys/mman.h>
266#include <sys/socket.h>
267#include <sys/un.h>
268#include <unistd.h>
269
270using socket_t = int;
271#ifndef INVALID_SOCKET
272#define INVALID_SOCKET (-1)
273#endif
274#endif //_WIN32
275
276#if defined(__APPLE__)
277#include <TargetConditionals.h>
278#endif
279
280#include <algorithm>
281#include <array>
282#include <atomic>
283#include <cassert>
284#include <cctype>
285#include <climits>
286#include <condition_variable>
287#include <cstring>
288#include <errno.h>
289#include <exception>
290#include <fcntl.h>
291#include <functional>
292#include <iomanip>
293#include <iostream>
294#include <list>
295#include <map>
296#include <memory>
297#include <mutex>
298#include <random>
299#include <regex>
300#include <set>
301#include <sstream>
302#include <string>
303#include <sys/stat.h>
304#include <thread>
305#include <unordered_map>
306#include <unordered_set>
307#include <utility>
308
309#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
310 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
311#if TARGET_OS_MAC
312#include <CFNetwork/CFHost.h>
313#include <CoreFoundation/CoreFoundation.h>
314#endif
315#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or
316 // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
317
318#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
319#ifdef _WIN32
320#include <wincrypt.h>
321
322// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
323// used
324#undef X509_NAME
325#undef X509_CERT_PAIR
326#undef X509_EXTENSIONS
327#undef PKCS7_SIGNER_INFO
328
329#ifdef _MSC_VER
330#pragma comment(lib, "crypt32.lib")
331#endif
332#endif // _WIN32
333
334#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
335#if TARGET_OS_MAC
336#include <Security/Security.h>
337#endif
338#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
339
340#include <openssl/err.h>
341#include <openssl/evp.h>
342#include <openssl/ssl.h>
343#include <openssl/x509v3.h>
344
345#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
346#include <openssl/applink.c>
347#endif
348
349#include <iostream>
350#include <sstream>
351
352#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
353#if OPENSSL_VERSION_NUMBER < 0x1010107f
354#error Please use OpenSSL or a current version of BoringSSL
355#endif
356#define SSL_get1_peer_certificate SSL_get_peer_certificate
357#elif OPENSSL_VERSION_NUMBER < 0x30000000L
358#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
359#endif
360
361#endif // CPPHTTPLIB_OPENSSL_SUPPORT
362
363#ifdef CPPHTTPLIB_ZLIB_SUPPORT
364#include <zlib.h>
365#endif
366
367#ifdef CPPHTTPLIB_BROTLI_SUPPORT
368#include <brotli/decode.h>
369#include <brotli/encode.h>
370#endif
371
372#ifdef CPPHTTPLIB_ZSTD_SUPPORT
373#include <zstd.h>
374#endif
375
376/*
377 * Declaration
378 */
379namespace httplib {
380
381namespace detail {
382
383/*
384 * Backport std::make_unique from C++14.
385 *
386 * NOTE: This code came up with the following stackoverflow post:
387 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
388 *
389 */
390
391template <class T, class... Args>
392typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
393make_unique(Args &&...args) {
394 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
395}
396
397template <class T>
398typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
399make_unique(std::size_t n) {
400 typedef typename std::remove_extent<T>::type RT;
401 return std::unique_ptr<T>(new RT[n]);
402}
403
404namespace case_ignore {
405
406inline unsigned char to_lower(int c) {
407 const static unsigned char table[256] = {
408 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
409 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
410 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
411 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
412 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
413 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
414 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
415 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
416 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
417 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
418 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
419 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
420 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
421 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
422 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
423 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
424 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
425 255,
426 };
427 return table[(unsigned char)(char)c];
428}
429
430inline bool equal(const std::string &a, const std::string &b) {
431 return a.size() == b.size() &&
432 std::equal(first1: a.begin(), last1: a.end(), first2: b.begin(), binary_pred: [](char ca, char cb) {
433 return to_lower(c: ca) == to_lower(c: cb);
434 });
435}
436
437struct equal_to {
438 bool operator()(const std::string &a, const std::string &b) const {
439 return equal(a, b);
440 }
441};
442
443struct hash {
444 size_t operator()(const std::string &key) const {
445 return hash_core(s: key.data(), l: key.size(), h: 0);
446 }
447
448 size_t hash_core(const char *s, size_t l, size_t h) const {
449 return (l == 0) ? h
450 : hash_core(s: s + 1, l: l - 1,
451 // Unsets the 6 high bits of h, therefore no
452 // overflow happens
453 h: (((std::numeric_limits<size_t>::max)() >> 6) &
454 h * 33) ^
455 static_cast<unsigned char>(to_lower(c: *s)));
456 }
457};
458
459template <typename T>
460using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
461 detail::case_ignore::equal_to>;
462
463} // namespace case_ignore
464
465// This is based on
466// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
467
468struct scope_exit {
469 explicit scope_exit(std::function<void(void)> &&f)
470 : exit_function(std::move(f)), execute_on_destruction{true} {}
471
472 scope_exit(scope_exit &&rhs) noexcept
473 : exit_function(std::move(rhs.exit_function)),
474 execute_on_destruction{rhs.execute_on_destruction} {
475 rhs.release();
476 }
477
478 ~scope_exit() {
479 if (execute_on_destruction) { this->exit_function(); }
480 }
481
482 void release() { this->execute_on_destruction = false; }
483
484private:
485 scope_exit(const scope_exit &) = delete;
486 void operator=(const scope_exit &) = delete;
487 scope_exit &operator=(scope_exit &&) = delete;
488
489 std::function<void(void)> exit_function;
490 bool execute_on_destruction;
491};
492
493} // namespace detail
494
495enum SSLVerifierResponse {
496 // no decision has been made, use the built-in certificate verifier
497 NoDecisionMade,
498 // connection certificate is verified and accepted
499 CertificateAccepted,
500 // connection certificate was processed but is rejected
501 CertificateRejected
502};
503
504enum StatusCode {
505 // Information responses
506 Continue_100 = 100,
507 SwitchingProtocol_101 = 101,
508 Processing_102 = 102,
509 EarlyHints_103 = 103,
510
511 // Successful responses
512 OK_200 = 200,
513 Created_201 = 201,
514 Accepted_202 = 202,
515 NonAuthoritativeInformation_203 = 203,
516 NoContent_204 = 204,
517 ResetContent_205 = 205,
518 PartialContent_206 = 206,
519 MultiStatus_207 = 207,
520 AlreadyReported_208 = 208,
521 IMUsed_226 = 226,
522
523 // Redirection messages
524 MultipleChoices_300 = 300,
525 MovedPermanently_301 = 301,
526 Found_302 = 302,
527 SeeOther_303 = 303,
528 NotModified_304 = 304,
529 UseProxy_305 = 305,
530 unused_306 = 306,
531 TemporaryRedirect_307 = 307,
532 PermanentRedirect_308 = 308,
533
534 // Client error responses
535 BadRequest_400 = 400,
536 Unauthorized_401 = 401,
537 PaymentRequired_402 = 402,
538 Forbidden_403 = 403,
539 NotFound_404 = 404,
540 MethodNotAllowed_405 = 405,
541 NotAcceptable_406 = 406,
542 ProxyAuthenticationRequired_407 = 407,
543 RequestTimeout_408 = 408,
544 Conflict_409 = 409,
545 Gone_410 = 410,
546 LengthRequired_411 = 411,
547 PreconditionFailed_412 = 412,
548 PayloadTooLarge_413 = 413,
549 UriTooLong_414 = 414,
550 UnsupportedMediaType_415 = 415,
551 RangeNotSatisfiable_416 = 416,
552 ExpectationFailed_417 = 417,
553 ImATeapot_418 = 418,
554 MisdirectedRequest_421 = 421,
555 UnprocessableContent_422 = 422,
556 Locked_423 = 423,
557 FailedDependency_424 = 424,
558 TooEarly_425 = 425,
559 UpgradeRequired_426 = 426,
560 PreconditionRequired_428 = 428,
561 TooManyRequests_429 = 429,
562 RequestHeaderFieldsTooLarge_431 = 431,
563 UnavailableForLegalReasons_451 = 451,
564
565 // Server error responses
566 InternalServerError_500 = 500,
567 NotImplemented_501 = 501,
568 BadGateway_502 = 502,
569 ServiceUnavailable_503 = 503,
570 GatewayTimeout_504 = 504,
571 HttpVersionNotSupported_505 = 505,
572 VariantAlsoNegotiates_506 = 506,
573 InsufficientStorage_507 = 507,
574 LoopDetected_508 = 508,
575 NotExtended_510 = 510,
576 NetworkAuthenticationRequired_511 = 511,
577};
578
579using Headers =
580 std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
581 detail::case_ignore::equal_to>;
582
583using Params = std::multimap<std::string, std::string>;
584using Match = std::smatch;
585
586using DownloadProgress = std::function<bool(size_t current, size_t total)>;
587using UploadProgress = std::function<bool(size_t current, size_t total)>;
588
589struct Response;
590using ResponseHandler = std::function<bool(const Response &response)>;
591
592struct FormData {
593 std::string name;
594 std::string content;
595 std::string filename;
596 std::string content_type;
597 Headers headers;
598};
599
600struct FormField {
601 std::string name;
602 std::string content;
603 Headers headers;
604};
605using FormFields = std::multimap<std::string, FormField>;
606
607using FormFiles = std::multimap<std::string, FormData>;
608
609struct MultipartFormData {
610 FormFields fields; // Text fields from multipart
611 FormFiles files; // Files from multipart
612
613 // Text field access
614 std::string get_field(const std::string &key, size_t id = 0) const;
615 std::vector<std::string> get_fields(const std::string &key) const;
616 bool has_field(const std::string &key) const;
617 size_t get_field_count(const std::string &key) const;
618
619 // File access
620 FormData get_file(const std::string &key, size_t id = 0) const;
621 std::vector<FormData> get_files(const std::string &key) const;
622 bool has_file(const std::string &key) const;
623 size_t get_file_count(const std::string &key) const;
624};
625
626struct UploadFormData {
627 std::string name;
628 std::string content;
629 std::string filename;
630 std::string content_type;
631};
632using UploadFormDataItems = std::vector<UploadFormData>;
633
634class DataSink {
635public:
636 DataSink() : os(&sb_), sb_(*this) {}
637
638 DataSink(const DataSink &) = delete;
639 DataSink &operator=(const DataSink &) = delete;
640 DataSink(DataSink &&) = delete;
641 DataSink &operator=(DataSink &&) = delete;
642
643 std::function<bool(const char *data, size_t data_len)> write;
644 std::function<bool()> is_writable;
645 std::function<void()> done;
646 std::function<void(const Headers &trailer)> done_with_trailer;
647 std::ostream os;
648
649private:
650 class data_sink_streambuf final : public std::streambuf {
651 public:
652 explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
653
654 protected:
655 std::streamsize xsputn(const char *s, std::streamsize n) override {
656 sink_.write(s, static_cast<size_t>(n));
657 return n;
658 }
659
660 private:
661 DataSink &sink_;
662 };
663
664 data_sink_streambuf sb_;
665};
666
667using ContentProvider =
668 std::function<bool(size_t offset, size_t length, DataSink &sink)>;
669
670using ContentProviderWithoutLength =
671 std::function<bool(size_t offset, DataSink &sink)>;
672
673using ContentProviderResourceReleaser = std::function<void(bool success)>;
674
675struct FormDataProvider {
676 std::string name;
677 ContentProviderWithoutLength provider;
678 std::string filename;
679 std::string content_type;
680};
681using FormDataProviderItems = std::vector<FormDataProvider>;
682
683using ContentReceiverWithProgress = std::function<bool(
684 const char *data, size_t data_length, size_t offset, size_t total_length)>;
685
686using ContentReceiver =
687 std::function<bool(const char *data, size_t data_length)>;
688
689using FormDataHeader = std::function<bool(const FormData &file)>;
690
691class ContentReader {
692public:
693 using Reader = std::function<bool(ContentReceiver receiver)>;
694 using FormDataReader =
695 std::function<bool(FormDataHeader header, ContentReceiver receiver)>;
696
697 ContentReader(Reader reader, FormDataReader multipart_reader)
698 : reader_(std::move(reader)),
699 formdata_reader_(std::move(multipart_reader)) {}
700
701 bool operator()(FormDataHeader header, ContentReceiver receiver) const {
702 return formdata_reader_(std::move(header), std::move(receiver));
703 }
704
705 bool operator()(ContentReceiver receiver) const {
706 return reader_(std::move(receiver));
707 }
708
709 Reader reader_;
710 FormDataReader formdata_reader_;
711};
712
713using Range = std::pair<ssize_t, ssize_t>;
714using Ranges = std::vector<Range>;
715
716struct Request {
717 std::string method;
718 std::string path;
719 std::string matched_route;
720 Params params;
721 Headers headers;
722 Headers trailers;
723 std::string body;
724
725 std::string remote_addr;
726 int remote_port = -1;
727 std::string local_addr;
728 int local_port = -1;
729
730 // for server
731 std::string version;
732 std::string target;
733 MultipartFormData form;
734 Ranges ranges;
735 Match matches;
736 std::unordered_map<std::string, std::string> path_params;
737 std::function<bool()> is_connection_closed = []() { return true; };
738
739 // for client
740 std::vector<std::string> accept_content_types;
741 ResponseHandler response_handler;
742 ContentReceiverWithProgress content_receiver;
743 DownloadProgress download_progress;
744 UploadProgress upload_progress;
745#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
746 const SSL *ssl = nullptr;
747#endif
748
749 bool has_header(const std::string &key) const;
750 std::string get_header_value(const std::string &key, const char *def = "",
751 size_t id = 0) const;
752 size_t get_header_value_u64(const std::string &key, size_t def = 0,
753 size_t id = 0) const;
754 size_t get_header_value_count(const std::string &key) const;
755 void set_header(const std::string &key, const std::string &val);
756
757 bool has_trailer(const std::string &key) const;
758 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
759 size_t get_trailer_value_count(const std::string &key) const;
760
761 bool has_param(const std::string &key) const;
762 std::string get_param_value(const std::string &key, size_t id = 0) const;
763 size_t get_param_value_count(const std::string &key) const;
764
765 bool is_multipart_form_data() const;
766
767 // private members...
768 size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
769 size_t content_length_ = 0;
770 ContentProvider content_provider_;
771 bool is_chunked_content_provider_ = false;
772 size_t authorization_count_ = 0;
773 std::chrono::time_point<std::chrono::steady_clock> start_time_ =
774 (std::chrono::steady_clock::time_point::min)();
775};
776
777struct Response {
778 std::string version;
779 int status = -1;
780 std::string reason;
781 Headers headers;
782 Headers trailers;
783 std::string body;
784 std::string location; // Redirect location
785
786 bool has_header(const std::string &key) const;
787 std::string get_header_value(const std::string &key, const char *def = "",
788 size_t id = 0) const;
789 size_t get_header_value_u64(const std::string &key, size_t def = 0,
790 size_t id = 0) const;
791 size_t get_header_value_count(const std::string &key) const;
792 void set_header(const std::string &key, const std::string &val);
793
794 bool has_trailer(const std::string &key) const;
795 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
796 size_t get_trailer_value_count(const std::string &key) const;
797
798 void set_redirect(const std::string &url, int status = StatusCode::Found_302);
799 void set_content(const char *s, size_t n, const std::string &content_type);
800 void set_content(const std::string &s, const std::string &content_type);
801 void set_content(std::string &&s, const std::string &content_type);
802
803 void set_content_provider(
804 size_t length, const std::string &content_type, ContentProvider provider,
805 ContentProviderResourceReleaser resource_releaser = nullptr);
806
807 void set_content_provider(
808 const std::string &content_type, ContentProviderWithoutLength provider,
809 ContentProviderResourceReleaser resource_releaser = nullptr);
810
811 void set_chunked_content_provider(
812 const std::string &content_type, ContentProviderWithoutLength provider,
813 ContentProviderResourceReleaser resource_releaser = nullptr);
814
815 void set_file_content(const std::string &path,
816 const std::string &content_type);
817 void set_file_content(const std::string &path);
818
819 Response() = default;
820 Response(const Response &) = default;
821 Response &operator=(const Response &) = default;
822 Response(Response &&) = default;
823 Response &operator=(Response &&) = default;
824 ~Response() {
825 if (content_provider_resource_releaser_) {
826 content_provider_resource_releaser_(content_provider_success_);
827 }
828 }
829
830 // private members...
831 size_t content_length_ = 0;
832 ContentProvider content_provider_;
833 ContentProviderResourceReleaser content_provider_resource_releaser_;
834 bool is_chunked_content_provider_ = false;
835 bool content_provider_success_ = false;
836 std::string file_content_path_;
837 std::string file_content_content_type_;
838};
839
840class Stream {
841public:
842 virtual ~Stream() = default;
843
844 virtual bool is_readable() const = 0;
845 virtual bool wait_readable() const = 0;
846 virtual bool wait_writable() const = 0;
847
848 virtual ssize_t read(char *ptr, size_t size) = 0;
849 virtual ssize_t write(const char *ptr, size_t size) = 0;
850 virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
851 virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
852 virtual socket_t socket() const = 0;
853
854 virtual time_t duration() const = 0;
855
856 ssize_t write(const char *ptr);
857 ssize_t write(const std::string &s);
858};
859
860class TaskQueue {
861public:
862 TaskQueue() = default;
863 virtual ~TaskQueue() = default;
864
865 virtual bool enqueue(std::function<void()> fn) = 0;
866 virtual void shutdown() = 0;
867
868 virtual void on_idle() {}
869};
870
871class ThreadPool final : public TaskQueue {
872public:
873 explicit ThreadPool(size_t n, size_t mqr = 0)
874 : shutdown_(false), max_queued_requests_(mqr) {
875 while (n) {
876 threads_.emplace_back(args: worker(*this));
877 n--;
878 }
879 }
880
881 ThreadPool(const ThreadPool &) = delete;
882 ~ThreadPool() override = default;
883
884 bool enqueue(std::function<void()> fn) override {
885 {
886 std::unique_lock<std::mutex> lock(mutex_);
887 if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
888 return false;
889 }
890 jobs_.push_back(x: std::move(fn));
891 }
892
893 cond_.notify_one();
894 return true;
895 }
896
897 void shutdown() override {
898 // Stop all worker threads...
899 {
900 std::unique_lock<std::mutex> lock(mutex_);
901 shutdown_ = true;
902 }
903
904 cond_.notify_all();
905
906 // Join...
907 for (auto &t : threads_) {
908 t.join();
909 }
910 }
911
912private:
913 struct worker {
914 explicit worker(ThreadPool &pool) : pool_(pool) {}
915
916 void operator()() {
917 for (;;) {
918 std::function<void()> fn;
919 {
920 std::unique_lock<std::mutex> lock(pool_.mutex_);
921
922 pool_.cond_.wait(
923 lock&: lock, p: [&] { return !pool_.jobs_.empty() || pool_.shutdown_; });
924
925 if (pool_.shutdown_ && pool_.jobs_.empty()) { break; }
926
927 fn = pool_.jobs_.front();
928 pool_.jobs_.pop_front();
929 }
930
931 assert(true == static_cast<bool>(fn));
932 fn();
933 }
934
935#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
936 !defined(LIBRESSL_VERSION_NUMBER)
937 OPENSSL_thread_stop();
938#endif
939 }
940
941 ThreadPool &pool_;
942 };
943 friend struct worker;
944
945 std::vector<std::thread> threads_;
946 std::list<std::function<void()>> jobs_;
947
948 bool shutdown_;
949 size_t max_queued_requests_ = 0;
950
951 std::condition_variable cond_;
952 std::mutex mutex_;
953};
954
955using Logger = std::function<void(const Request &, const Response &)>;
956
957// Forward declaration for Error type
958enum class Error;
959using ErrorLogger = std::function<void(const Error &, const Request *)>;
960
961using SocketOptions = std::function<void(socket_t sock)>;
962
963namespace detail {
964
965bool set_socket_opt_impl(socket_t sock, int level, int optname,
966 const void *optval, socklen_t optlen);
967bool set_socket_opt(socket_t sock, int level, int optname, int opt);
968bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
969 time_t usec);
970
971} // namespace detail
972
973void default_socket_options(socket_t sock);
974
975const char *status_message(int status);
976
977std::string get_bearer_token_auth(const Request &req);
978
979namespace detail {
980
981class MatcherBase {
982public:
983 MatcherBase(std::string pattern) : pattern_(pattern) {}
984 virtual ~MatcherBase() = default;
985
986 const std::string &pattern() const { return pattern_; }
987
988 // Match request path and populate its matches and
989 virtual bool match(Request &request) const = 0;
990
991private:
992 std::string pattern_;
993};
994
995/**
996 * Captures parameters in request path and stores them in Request::path_params
997 *
998 * Capture name is a substring of a pattern from : to /.
999 * The rest of the pattern is matched against the request path directly
1000 * Parameters are captured starting from the next character after
1001 * the end of the last matched static pattern fragment until the next /.
1002 *
1003 * Example pattern:
1004 * "/path/fragments/:capture/more/fragments/:second_capture"
1005 * Static fragments:
1006 * "/path/fragments/", "more/fragments/"
1007 *
1008 * Given the following request path:
1009 * "/path/fragments/:1/more/fragments/:2"
1010 * the resulting capture will be
1011 * {{"capture", "1"}, {"second_capture", "2"}}
1012 */
1013class PathParamsMatcher final : public MatcherBase {
1014public:
1015 PathParamsMatcher(const std::string &pattern);
1016
1017 bool match(Request &request) const override;
1018
1019private:
1020 // Treat segment separators as the end of path parameter capture
1021 // Does not need to handle query parameters as they are parsed before path
1022 // matching
1023 static constexpr char separator = '/';
1024
1025 // Contains static path fragments to match against, excluding the '/' after
1026 // path params
1027 // Fragments are separated by path params
1028 std::vector<std::string> static_fragments_;
1029 // Stores the names of the path parameters to be used as keys in the
1030 // Request::path_params map
1031 std::vector<std::string> param_names_;
1032};
1033
1034/**
1035 * Performs std::regex_match on request path
1036 * and stores the result in Request::matches
1037 *
1038 * Note that regex match is performed directly on the whole request.
1039 * This means that wildcard patterns may match multiple path segments with /:
1040 * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
1041 */
1042class RegexMatcher final : public MatcherBase {
1043public:
1044 RegexMatcher(const std::string &pattern)
1045 : MatcherBase(pattern), regex_(pattern) {}
1046
1047 bool match(Request &request) const override;
1048
1049private:
1050 std::regex regex_;
1051};
1052
1053ssize_t write_headers(Stream &strm, const Headers &headers);
1054
1055std::string make_host_and_port_string(const std::string &host, int port,
1056 bool is_ssl);
1057
1058} // namespace detail
1059
1060class Server {
1061public:
1062 using Handler = std::function<void(const Request &, Response &)>;
1063
1064 using ExceptionHandler =
1065 std::function<void(const Request &, Response &, std::exception_ptr ep)>;
1066
1067 enum class HandlerResponse {
1068 Handled,
1069 Unhandled,
1070 };
1071 using HandlerWithResponse =
1072 std::function<HandlerResponse(const Request &, Response &)>;
1073
1074 using HandlerWithContentReader = std::function<void(
1075 const Request &, Response &, const ContentReader &content_reader)>;
1076
1077 using Expect100ContinueHandler =
1078 std::function<int(const Request &, Response &)>;
1079
1080 Server();
1081
1082 virtual ~Server();
1083
1084 virtual bool is_valid() const;
1085
1086 Server &Get(const std::string &pattern, Handler handler);
1087 Server &Post(const std::string &pattern, Handler handler);
1088 Server &Post(const std::string &pattern, HandlerWithContentReader handler);
1089 Server &Put(const std::string &pattern, Handler handler);
1090 Server &Put(const std::string &pattern, HandlerWithContentReader handler);
1091 Server &Patch(const std::string &pattern, Handler handler);
1092 Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
1093 Server &Delete(const std::string &pattern, Handler handler);
1094 Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
1095 Server &Options(const std::string &pattern, Handler handler);
1096
1097 bool set_base_dir(const std::string &dir,
1098 const std::string &mount_point = std::string());
1099 bool set_mount_point(const std::string &mount_point, const std::string &dir,
1100 Headers headers = Headers());
1101 bool remove_mount_point(const std::string &mount_point);
1102 Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
1103 const std::string &mime);
1104 Server &set_default_file_mimetype(const std::string &mime);
1105 Server &set_file_request_handler(Handler handler);
1106
1107 template <class ErrorHandlerFunc>
1108 Server &set_error_handler(ErrorHandlerFunc &&handler) {
1109 return set_error_handler_core(
1110 std::forward<ErrorHandlerFunc>(handler),
1111 std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
1112 }
1113
1114 Server &set_exception_handler(ExceptionHandler handler);
1115
1116 Server &set_pre_routing_handler(HandlerWithResponse handler);
1117 Server &set_post_routing_handler(Handler handler);
1118
1119 Server &set_pre_request_handler(HandlerWithResponse handler);
1120
1121 Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
1122 Server &set_logger(Logger logger);
1123 Server &set_pre_compression_logger(Logger logger);
1124 Server &set_error_logger(ErrorLogger error_logger);
1125
1126 Server &set_address_family(int family);
1127 Server &set_tcp_nodelay(bool on);
1128 Server &set_ipv6_v6only(bool on);
1129 Server &set_socket_options(SocketOptions socket_options);
1130
1131 Server &set_default_headers(Headers headers);
1132 Server &
1133 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1134
1135 Server &set_trusted_proxies(const std::vector<std::string> &proxies);
1136
1137 Server &set_keep_alive_max_count(size_t count);
1138 Server &set_keep_alive_timeout(time_t sec);
1139
1140 Server &set_read_timeout(time_t sec, time_t usec = 0);
1141 template <class Rep, class Period>
1142 Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1143
1144 Server &set_write_timeout(time_t sec, time_t usec = 0);
1145 template <class Rep, class Period>
1146 Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1147
1148 Server &set_idle_interval(time_t sec, time_t usec = 0);
1149 template <class Rep, class Period>
1150 Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
1151
1152 Server &set_payload_max_length(size_t length);
1153
1154 bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
1155 int bind_to_any_port(const std::string &host, int socket_flags = 0);
1156 bool listen_after_bind();
1157
1158 bool listen(const std::string &host, int port, int socket_flags = 0);
1159
1160 bool is_running() const;
1161 void wait_until_ready() const;
1162 void stop();
1163 void decommission();
1164
1165 std::function<TaskQueue *(void)> new_task_queue;
1166
1167protected:
1168 bool process_request(Stream &strm, const std::string &remote_addr,
1169 int remote_port, const std::string &local_addr,
1170 int local_port, bool close_connection,
1171 bool &connection_closed,
1172 const std::function<void(Request &)> &setup_request);
1173
1174 std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
1175
1176 std::vector<std::string> trusted_proxies_;
1177
1178 size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
1179 time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
1180 time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
1181 time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
1182 time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
1183 time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
1184 time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
1185 time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
1186 size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
1187
1188private:
1189 using Handlers =
1190 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
1191 using HandlersForContentReader =
1192 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
1193 HandlerWithContentReader>>;
1194
1195 static std::unique_ptr<detail::MatcherBase>
1196 make_matcher(const std::string &pattern);
1197
1198 Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
1199 Server &set_error_handler_core(Handler handler, std::false_type);
1200
1201 socket_t create_server_socket(const std::string &host, int port,
1202 int socket_flags,
1203 SocketOptions socket_options) const;
1204 int bind_internal(const std::string &host, int port, int socket_flags);
1205 bool listen_internal();
1206
1207 bool routing(Request &req, Response &res, Stream &strm);
1208 bool handle_file_request(const Request &req, Response &res);
1209 bool dispatch_request(Request &req, Response &res,
1210 const Handlers &handlers) const;
1211 bool dispatch_request_for_content_reader(
1212 Request &req, Response &res, ContentReader content_reader,
1213 const HandlersForContentReader &handlers) const;
1214
1215 bool parse_request_line(const char *s, Request &req) const;
1216 void apply_ranges(const Request &req, Response &res,
1217 std::string &content_type, std::string &boundary) const;
1218 bool write_response(Stream &strm, bool close_connection, Request &req,
1219 Response &res);
1220 bool write_response_with_content(Stream &strm, bool close_connection,
1221 const Request &req, Response &res);
1222 bool write_response_core(Stream &strm, bool close_connection,
1223 const Request &req, Response &res,
1224 bool need_apply_ranges);
1225 bool write_content_with_provider(Stream &strm, const Request &req,
1226 Response &res, const std::string &boundary,
1227 const std::string &content_type);
1228 bool read_content(Stream &strm, Request &req, Response &res);
1229 bool read_content_with_content_receiver(Stream &strm, Request &req,
1230 Response &res,
1231 ContentReceiver receiver,
1232 FormDataHeader multipart_header,
1233 ContentReceiver multipart_receiver);
1234 bool read_content_core(Stream &strm, Request &req, Response &res,
1235 ContentReceiver receiver,
1236 FormDataHeader multipart_header,
1237 ContentReceiver multipart_receiver) const;
1238
1239 virtual bool process_and_close_socket(socket_t sock);
1240
1241 void output_log(const Request &req, const Response &res) const;
1242 void output_pre_compression_log(const Request &req,
1243 const Response &res) const;
1244 void output_error_log(const Error &err, const Request *req) const;
1245
1246 std::atomic<bool> is_running_{false};
1247 std::atomic<bool> is_decommissioned{false};
1248
1249 struct MountPointEntry {
1250 std::string mount_point;
1251 std::string base_dir;
1252 Headers headers;
1253 };
1254 std::vector<MountPointEntry> base_dirs_;
1255 std::map<std::string, std::string> file_extension_and_mimetype_map_;
1256 std::string default_file_mimetype_ = "application/octet-stream";
1257 Handler file_request_handler_;
1258
1259 Handlers get_handlers_;
1260 Handlers post_handlers_;
1261 HandlersForContentReader post_handlers_for_content_reader_;
1262 Handlers put_handlers_;
1263 HandlersForContentReader put_handlers_for_content_reader_;
1264 Handlers patch_handlers_;
1265 HandlersForContentReader patch_handlers_for_content_reader_;
1266 Handlers delete_handlers_;
1267 HandlersForContentReader delete_handlers_for_content_reader_;
1268 Handlers options_handlers_;
1269
1270 HandlerWithResponse error_handler_;
1271 ExceptionHandler exception_handler_;
1272 HandlerWithResponse pre_routing_handler_;
1273 Handler post_routing_handler_;
1274 HandlerWithResponse pre_request_handler_;
1275 Expect100ContinueHandler expect_100_continue_handler_;
1276
1277 mutable std::mutex logger_mutex_;
1278 Logger logger_;
1279 Logger pre_compression_logger_;
1280 ErrorLogger error_logger_;
1281
1282 int address_family_ = AF_UNSPEC;
1283 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
1284 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
1285 SocketOptions socket_options_ = default_socket_options;
1286
1287 Headers default_headers_;
1288 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
1289 detail::write_headers;
1290};
1291
1292enum class Error {
1293 Success = 0,
1294 Unknown,
1295 Connection,
1296 BindIPAddress,
1297 Read,
1298 Write,
1299 ExceedRedirectCount,
1300 Canceled,
1301 SSLConnection,
1302 SSLLoadingCerts,
1303 SSLServerVerification,
1304 SSLServerHostnameVerification,
1305 UnsupportedMultipartBoundaryChars,
1306 Compression,
1307 ConnectionTimeout,
1308 ProxyConnection,
1309 ResourceExhaustion,
1310 TooManyFormDataFiles,
1311 ExceedMaxPayloadSize,
1312 ExceedUriMaxLength,
1313 ExceedMaxSocketDescriptorCount,
1314 InvalidRequestLine,
1315 InvalidHTTPMethod,
1316 InvalidHTTPVersion,
1317 InvalidHeaders,
1318 MultipartParsing,
1319 OpenFile,
1320 Listen,
1321 GetSockName,
1322 UnsupportedAddressFamily,
1323 HTTPParsing,
1324 InvalidRangeHeader,
1325
1326 // For internal use only
1327 SSLPeerCouldBeClosed_,
1328};
1329
1330std::string to_string(Error error);
1331
1332std::ostream &operator<<(std::ostream &os, const Error &obj);
1333
1334class Result {
1335public:
1336 Result() = default;
1337 Result(std::unique_ptr<Response> &&res, Error err,
1338 Headers &&request_headers = Headers{})
1339 : res_(std::move(res)), err_(err),
1340 request_headers_(std::move(request_headers)) {}
1341#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1342 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1343 int ssl_error)
1344 : res_(std::move(res)), err_(err),
1345 request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
1346 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1347 int ssl_error, unsigned long ssl_openssl_error)
1348 : res_(std::move(res)), err_(err),
1349 request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
1350 ssl_openssl_error_(ssl_openssl_error) {}
1351#endif
1352 // Response
1353 operator bool() const { return res_ != nullptr; }
1354 bool operator==(std::nullptr_t) const { return res_ == nullptr; }
1355 bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
1356 const Response &value() const { return *res_; }
1357 Response &value() { return *res_; }
1358 const Response &operator*() const { return *res_; }
1359 Response &operator*() { return *res_; }
1360 const Response *operator->() const { return res_.get(); }
1361 Response *operator->() { return res_.get(); }
1362
1363 // Error
1364 Error error() const { return err_; }
1365
1366#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1367 // SSL Error
1368 int ssl_error() const { return ssl_error_; }
1369 // OpenSSL Error
1370 unsigned long ssl_openssl_error() const { return ssl_openssl_error_; }
1371#endif
1372
1373 // Request Headers
1374 bool has_request_header(const std::string &key) const;
1375 std::string get_request_header_value(const std::string &key,
1376 const char *def = "",
1377 size_t id = 0) const;
1378 size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
1379 size_t id = 0) const;
1380 size_t get_request_header_value_count(const std::string &key) const;
1381
1382private:
1383 std::unique_ptr<Response> res_;
1384 Error err_ = Error::Unknown;
1385 Headers request_headers_;
1386#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1387 int ssl_error_ = 0;
1388 unsigned long ssl_openssl_error_ = 0;
1389#endif
1390};
1391
1392class ClientImpl {
1393public:
1394 explicit ClientImpl(const std::string &host);
1395
1396 explicit ClientImpl(const std::string &host, int port);
1397
1398 explicit ClientImpl(const std::string &host, int port,
1399 const std::string &client_cert_path,
1400 const std::string &client_key_path);
1401
1402 virtual ~ClientImpl();
1403
1404 virtual bool is_valid() const;
1405
1406 // clang-format off
1407 Result Get(const std::string &path, DownloadProgress progress = nullptr);
1408 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1409 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1410 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
1411 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1412 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1413 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
1414 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1415 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1416
1417 Result Head(const std::string &path);
1418 Result Head(const std::string &path, const Headers &headers);
1419
1420 Result Post(const std::string &path);
1421 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1422 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1423 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1424 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1425 Result Post(const std::string &path, const Params &params);
1426 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1427 Result Post(const std::string &path, const Headers &headers);
1428 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1429 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1430 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1431 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1432 Result Post(const std::string &path, const Headers &headers, const Params &params);
1433 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1434 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1435 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1436 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1437
1438 Result Put(const std::string &path);
1439 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1440 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1441 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1442 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1443 Result Put(const std::string &path, const Params &params);
1444 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1445 Result Put(const std::string &path, const Headers &headers);
1446 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1447 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1448 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1449 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1450 Result Put(const std::string &path, const Headers &headers, const Params &params);
1451 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1452 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1453 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1454 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1455
1456 Result Patch(const std::string &path);
1457 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1458 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1459 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1460 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1461 Result Patch(const std::string &path, const Params &params);
1462 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1463 Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
1464 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1465 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1466 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1467 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1468 Result Patch(const std::string &path, const Headers &headers, const Params &params);
1469 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1470 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1471 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1472 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1473
1474 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
1475 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
1476 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
1477 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
1478 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
1479 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
1480 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
1481 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
1482
1483 Result Options(const std::string &path);
1484 Result Options(const std::string &path, const Headers &headers);
1485 // clang-format on
1486
1487 bool send(Request &req, Response &res, Error &error);
1488 Result send(const Request &req);
1489
1490 void stop();
1491
1492 std::string host() const;
1493 int port() const;
1494
1495 size_t is_socket_open() const;
1496 socket_t socket() const;
1497
1498 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
1499
1500 void set_default_headers(Headers headers);
1501
1502 void
1503 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1504
1505 void set_address_family(int family);
1506 void set_tcp_nodelay(bool on);
1507 void set_ipv6_v6only(bool on);
1508 void set_socket_options(SocketOptions socket_options);
1509
1510 void set_connection_timeout(time_t sec, time_t usec = 0);
1511 template <class Rep, class Period>
1512 void
1513 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
1514
1515 void set_read_timeout(time_t sec, time_t usec = 0);
1516 template <class Rep, class Period>
1517 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1518
1519 void set_write_timeout(time_t sec, time_t usec = 0);
1520 template <class Rep, class Period>
1521 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1522
1523 void set_max_timeout(time_t msec);
1524 template <class Rep, class Period>
1525 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
1526
1527 void set_basic_auth(const std::string &username, const std::string &password);
1528 void set_bearer_token_auth(const std::string &token);
1529#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1530 void set_digest_auth(const std::string &username,
1531 const std::string &password);
1532#endif
1533
1534 void set_keep_alive(bool on);
1535 void set_follow_location(bool on);
1536
1537 void set_path_encode(bool on);
1538
1539 void set_compress(bool on);
1540
1541 void set_decompress(bool on);
1542
1543 void set_interface(const std::string &intf);
1544
1545 void set_proxy(const std::string &host, int port);
1546 void set_proxy_basic_auth(const std::string &username,
1547 const std::string &password);
1548 void set_proxy_bearer_token_auth(const std::string &token);
1549#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1550 void set_proxy_digest_auth(const std::string &username,
1551 const std::string &password);
1552#endif
1553
1554#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1555 void set_ca_cert_path(const std::string &ca_cert_file_path,
1556 const std::string &ca_cert_dir_path = std::string());
1557 void set_ca_cert_store(X509_STORE *ca_cert_store);
1558 X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const;
1559#endif
1560
1561#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1562 void enable_server_certificate_verification(bool enabled);
1563 void enable_server_hostname_verification(bool enabled);
1564 void set_server_certificate_verifier(
1565 std::function<SSLVerifierResponse(SSL *ssl)> verifier);
1566#endif
1567
1568 void set_logger(Logger logger);
1569 void set_error_logger(ErrorLogger error_logger);
1570
1571protected:
1572 struct Socket {
1573 socket_t sock = INVALID_SOCKET;
1574#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1575 SSL *ssl = nullptr;
1576#endif
1577
1578 bool is_open() const { return sock != INVALID_SOCKET; }
1579 };
1580
1581 virtual bool create_and_connect_socket(Socket &socket, Error &error);
1582
1583 // All of:
1584 // shutdown_ssl
1585 // shutdown_socket
1586 // close_socket
1587 // should ONLY be called when socket_mutex_ is locked.
1588 // Also, shutdown_ssl and close_socket should also NOT be called concurrently
1589 // with a DIFFERENT thread sending requests using that socket.
1590 virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
1591 void shutdown_socket(Socket &socket) const;
1592 void close_socket(Socket &socket);
1593
1594 bool process_request(Stream &strm, Request &req, Response &res,
1595 bool close_connection, Error &error);
1596
1597 bool write_content_with_provider(Stream &strm, const Request &req,
1598 Error &error) const;
1599
1600 void copy_settings(const ClientImpl &rhs);
1601
1602 void output_log(const Request &req, const Response &res) const;
1603 void output_error_log(const Error &err, const Request *req) const;
1604
1605 // Socket endpoint information
1606 const std::string host_;
1607 const int port_;
1608 const std::string host_and_port_;
1609
1610 // Current open socket
1611 Socket socket_;
1612 mutable std::mutex socket_mutex_;
1613 std::recursive_mutex request_mutex_;
1614
1615 // These are all protected under socket_mutex
1616 size_t socket_requests_in_flight_ = 0;
1617 std::thread::id socket_requests_are_from_thread_ = std::thread::id();
1618 bool socket_should_be_closed_when_request_is_done_ = false;
1619
1620 // Hostname-IP map
1621 std::map<std::string, std::string> addr_map_;
1622
1623 // Default headers
1624 Headers default_headers_;
1625
1626 // Header writer
1627 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
1628 detail::write_headers;
1629
1630 // Settings
1631 std::string client_cert_path_;
1632 std::string client_key_path_;
1633
1634 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
1635 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
1636 time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
1637 time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
1638 time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
1639 time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
1640 time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;
1641
1642 std::string basic_auth_username_;
1643 std::string basic_auth_password_;
1644 std::string bearer_token_auth_token_;
1645#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1646 std::string digest_auth_username_;
1647 std::string digest_auth_password_;
1648#endif
1649
1650 bool keep_alive_ = false;
1651 bool follow_location_ = false;
1652
1653 bool path_encode_ = true;
1654
1655 int address_family_ = AF_UNSPEC;
1656 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
1657 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
1658 SocketOptions socket_options_ = nullptr;
1659
1660 bool compress_ = false;
1661 bool decompress_ = true;
1662
1663 std::string interface_;
1664
1665 std::string proxy_host_;
1666 int proxy_port_ = -1;
1667
1668 std::string proxy_basic_auth_username_;
1669 std::string proxy_basic_auth_password_;
1670 std::string proxy_bearer_token_auth_token_;
1671#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1672 std::string proxy_digest_auth_username_;
1673 std::string proxy_digest_auth_password_;
1674#endif
1675
1676#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1677 std::string ca_cert_file_path_;
1678 std::string ca_cert_dir_path_;
1679
1680 X509_STORE *ca_cert_store_ = nullptr;
1681#endif
1682
1683#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1684 bool server_certificate_verification_ = true;
1685 bool server_hostname_verification_ = true;
1686 std::function<SSLVerifierResponse(SSL *ssl)> server_certificate_verifier_;
1687#endif
1688
1689 mutable std::mutex logger_mutex_;
1690 Logger logger_;
1691 ErrorLogger error_logger_;
1692
1693#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1694 int last_ssl_error_ = 0;
1695 unsigned long last_openssl_error_ = 0;
1696#endif
1697
1698private:
1699 bool send_(Request &req, Response &res, Error &error);
1700 Result send_(Request &&req);
1701
1702 socket_t create_client_socket(Error &error) const;
1703 bool read_response_line(Stream &strm, const Request &req,
1704 Response &res) const;
1705 bool write_request(Stream &strm, Request &req, bool close_connection,
1706 Error &error);
1707 bool redirect(Request &req, Response &res, Error &error);
1708 bool create_redirect_client(const std::string &scheme,
1709 const std::string &host, int port, Request &req,
1710 Response &res, const std::string &path,
1711 const std::string &location, Error &error);
1712 template <typename ClientType> void setup_redirect_client(ClientType &client);
1713 bool handle_request(Stream &strm, Request &req, Response &res,
1714 bool close_connection, Error &error);
1715 std::unique_ptr<Response> send_with_content_provider(
1716 Request &req, const char *body, size_t content_length,
1717 ContentProvider content_provider,
1718 ContentProviderWithoutLength content_provider_without_length,
1719 const std::string &content_type, Error &error);
1720 Result send_with_content_provider(
1721 const std::string &method, const std::string &path,
1722 const Headers &headers, const char *body, size_t content_length,
1723 ContentProvider content_provider,
1724 ContentProviderWithoutLength content_provider_without_length,
1725 const std::string &content_type, UploadProgress progress);
1726 ContentProviderWithoutLength get_multipart_content_provider(
1727 const std::string &boundary, const UploadFormDataItems &items,
1728 const FormDataProviderItems &provider_items) const;
1729
1730 virtual bool
1731 process_socket(const Socket &socket,
1732 std::chrono::time_point<std::chrono::steady_clock> start_time,
1733 std::function<bool(Stream &strm)> callback);
1734 virtual bool is_ssl() const;
1735};
1736
1737class Client {
1738public:
1739 // Universal interface
1740 explicit Client(const std::string &scheme_host_port);
1741
1742 explicit Client(const std::string &scheme_host_port,
1743 const std::string &client_cert_path,
1744 const std::string &client_key_path);
1745
1746 // HTTP only interface
1747 explicit Client(const std::string &host, int port);
1748
1749 explicit Client(const std::string &host, int port,
1750 const std::string &client_cert_path,
1751 const std::string &client_key_path);
1752
1753 Client(Client &&) = default;
1754 Client &operator=(Client &&) = default;
1755
1756 ~Client();
1757
1758 bool is_valid() const;
1759
1760 // clang-format off
1761 Result Get(const std::string &path, DownloadProgress progress = nullptr);
1762 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1763 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1764 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
1765 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1766 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1767 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
1768 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1769 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1770
1771 Result Head(const std::string &path);
1772 Result Head(const std::string &path, const Headers &headers);
1773
1774 Result Post(const std::string &path);
1775 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1776 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1777 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1778 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1779 Result Post(const std::string &path, const Params &params);
1780 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1781 Result Post(const std::string &path, const Headers &headers);
1782 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1783 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1784 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1785 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1786 Result Post(const std::string &path, const Headers &headers, const Params &params);
1787 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1788 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1789 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1790 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1791
1792 Result Put(const std::string &path);
1793 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1794 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1795 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1796 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1797 Result Put(const std::string &path, const Params &params);
1798 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1799 Result Put(const std::string &path, const Headers &headers);
1800 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1801 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1802 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1803 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1804 Result Put(const std::string &path, const Headers &headers, const Params &params);
1805 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1806 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1807 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1808 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1809
1810 Result Patch(const std::string &path);
1811 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1812 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1813 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1814 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1815 Result Patch(const std::string &path, const Params &params);
1816 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1817 Result Patch(const std::string &path, const Headers &headers);
1818 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
1819 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
1820 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1821 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
1822 Result Patch(const std::string &path, const Headers &headers, const Params &params);
1823 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
1824 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
1825 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
1826 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
1827
1828 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
1829 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
1830 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
1831 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
1832 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
1833 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
1834 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
1835 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
1836
1837 Result Options(const std::string &path);
1838 Result Options(const std::string &path, const Headers &headers);
1839 // clang-format on
1840
1841 bool send(Request &req, Response &res, Error &error);
1842 Result send(const Request &req);
1843
1844 void stop();
1845
1846 std::string host() const;
1847 int port() const;
1848
1849 size_t is_socket_open() const;
1850 socket_t socket() const;
1851
1852 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
1853
1854 void set_default_headers(Headers headers);
1855
1856 void
1857 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1858
1859 void set_address_family(int family);
1860 void set_tcp_nodelay(bool on);
1861 void set_socket_options(SocketOptions socket_options);
1862
1863 void set_connection_timeout(time_t sec, time_t usec = 0);
1864 template <class Rep, class Period>
1865 void
1866 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
1867
1868 void set_read_timeout(time_t sec, time_t usec = 0);
1869 template <class Rep, class Period>
1870 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1871
1872 void set_write_timeout(time_t sec, time_t usec = 0);
1873 template <class Rep, class Period>
1874 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1875
1876 void set_max_timeout(time_t msec);
1877 template <class Rep, class Period>
1878 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
1879
1880 void set_basic_auth(const std::string &username, const std::string &password);
1881 void set_bearer_token_auth(const std::string &token);
1882#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1883 void set_digest_auth(const std::string &username,
1884 const std::string &password);
1885#endif
1886
1887 void set_keep_alive(bool on);
1888 void set_follow_location(bool on);
1889
1890 void set_path_encode(bool on);
1891 void set_url_encode(bool on);
1892
1893 void set_compress(bool on);
1894
1895 void set_decompress(bool on);
1896
1897 void set_interface(const std::string &intf);
1898
1899 void set_proxy(const std::string &host, int port);
1900 void set_proxy_basic_auth(const std::string &username,
1901 const std::string &password);
1902 void set_proxy_bearer_token_auth(const std::string &token);
1903#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1904 void set_proxy_digest_auth(const std::string &username,
1905 const std::string &password);
1906#endif
1907
1908#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1909 void enable_server_certificate_verification(bool enabled);
1910 void enable_server_hostname_verification(bool enabled);
1911 void set_server_certificate_verifier(
1912 std::function<SSLVerifierResponse(SSL *ssl)> verifier);
1913#endif
1914
1915 void set_logger(Logger logger);
1916 void set_error_logger(ErrorLogger error_logger);
1917
1918 // SSL
1919#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1920 void set_ca_cert_path(const std::string &ca_cert_file_path,
1921 const std::string &ca_cert_dir_path = std::string());
1922
1923 void set_ca_cert_store(X509_STORE *ca_cert_store);
1924 void load_ca_cert_store(const char *ca_cert, std::size_t size);
1925
1926 long get_openssl_verify_result() const;
1927
1928 SSL_CTX *ssl_context() const;
1929#endif
1930
1931private:
1932 std::unique_ptr<ClientImpl> cli_;
1933
1934#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1935 bool is_ssl_ = false;
1936#endif
1937};
1938
1939#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
1940class SSLServer : public Server {
1941public:
1942 SSLServer(const char *cert_path, const char *private_key_path,
1943 const char *client_ca_cert_file_path = nullptr,
1944 const char *client_ca_cert_dir_path = nullptr,
1945 const char *private_key_password = nullptr);
1946
1947 SSLServer(X509 *cert, EVP_PKEY *private_key,
1948 X509_STORE *client_ca_cert_store = nullptr);
1949
1950 SSLServer(
1951 const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback);
1952
1953 ~SSLServer() override;
1954
1955 bool is_valid() const override;
1956
1957 SSL_CTX *ssl_context() const;
1958
1959 void update_certs(X509 *cert, EVP_PKEY *private_key,
1960 X509_STORE *client_ca_cert_store = nullptr);
1961
1962 int ssl_last_error() const { return last_ssl_error_; }
1963
1964private:
1965 bool process_and_close_socket(socket_t sock) override;
1966
1967 STACK_OF(X509_NAME) * extract_ca_names_from_x509_store(X509_STORE *store);
1968
1969 SSL_CTX *ctx_;
1970 std::mutex ctx_mutex_;
1971
1972 int last_ssl_error_ = 0;
1973};
1974
1975class SSLClient final : public ClientImpl {
1976public:
1977 explicit SSLClient(const std::string &host);
1978
1979 explicit SSLClient(const std::string &host, int port);
1980
1981 explicit SSLClient(const std::string &host, int port,
1982 const std::string &client_cert_path,
1983 const std::string &client_key_path,
1984 const std::string &private_key_password = std::string());
1985
1986 explicit SSLClient(const std::string &host, int port, X509 *client_cert,
1987 EVP_PKEY *client_key,
1988 const std::string &private_key_password = std::string());
1989
1990 ~SSLClient() override;
1991
1992 bool is_valid() const override;
1993
1994 void set_ca_cert_store(X509_STORE *ca_cert_store);
1995 void load_ca_cert_store(const char *ca_cert, std::size_t size);
1996
1997 long get_openssl_verify_result() const;
1998
1999 SSL_CTX *ssl_context() const;
2000
2001private:
2002 bool create_and_connect_socket(Socket &socket, Error &error) override;
2003 void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
2004 void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
2005
2006 bool
2007 process_socket(const Socket &socket,
2008 std::chrono::time_point<std::chrono::steady_clock> start_time,
2009 std::function<bool(Stream &strm)> callback) override;
2010 bool is_ssl() const override;
2011
2012 bool connect_with_proxy(
2013 Socket &sock,
2014 std::chrono::time_point<std::chrono::steady_clock> start_time,
2015 Response &res, bool &success, Error &error);
2016 bool initialize_ssl(Socket &socket, Error &error);
2017
2018 bool load_certs();
2019
2020 bool verify_host(X509 *server_cert) const;
2021 bool verify_host_with_subject_alt_name(X509 *server_cert) const;
2022 bool verify_host_with_common_name(X509 *server_cert) const;
2023 bool check_host_name(const char *pattern, size_t pattern_len) const;
2024
2025 SSL_CTX *ctx_;
2026 std::mutex ctx_mutex_;
2027 std::once_flag initialize_cert_;
2028
2029 std::vector<std::string> host_components_;
2030
2031 long verify_result_ = 0;
2032
2033 friend class ClientImpl;
2034};
2035#endif
2036
2037/*
2038 * Implementation of template methods.
2039 */
2040
2041namespace detail {
2042
2043template <typename T, typename U>
2044inline void duration_to_sec_and_usec(const T &duration, U callback) {
2045 auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
2046 auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
2047 duration - std::chrono::seconds(sec))
2048 .count();
2049 callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
2050}
2051
2052template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2053 return N - 1;
2054}
2055
2056inline bool is_numeric(const std::string &str) {
2057 return !str.empty() &&
2058 std::all_of(first: str.cbegin(), last: str.cend(),
2059 pred: [](unsigned char c) { return std::isdigit(c); });
2060}
2061
2062inline size_t get_header_value_u64(const Headers &headers,
2063 const std::string &key, size_t def,
2064 size_t id, bool &is_invalid_value) {
2065 is_invalid_value = false;
2066 auto rng = headers.equal_range(x: key);
2067 auto it = rng.first;
2068 std::advance(i&: it, n: static_cast<ssize_t>(id));
2069 if (it != rng.second) {
2070 if (is_numeric(str: it->second)) {
2071 return std::strtoull(nptr: it->second.data(), endptr: nullptr, base: 10);
2072 } else {
2073 is_invalid_value = true;
2074 }
2075 }
2076 return def;
2077}
2078
2079inline size_t get_header_value_u64(const Headers &headers,
2080 const std::string &key, size_t def,
2081 size_t id) {
2082 auto dummy = false;
2083 return get_header_value_u64(headers, key, def, id, is_invalid_value&: dummy);
2084}
2085
2086} // namespace detail
2087
2088inline size_t Request::get_header_value_u64(const std::string &key, size_t def,
2089 size_t id) const {
2090 return detail::get_header_value_u64(headers, key, def, id);
2091}
2092
2093inline size_t Response::get_header_value_u64(const std::string &key, size_t def,
2094 size_t id) const {
2095 return detail::get_header_value_u64(headers, key, def, id);
2096}
2097
2098namespace detail {
2099
2100inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
2101 const void *optval, socklen_t optlen) {
2102 return setsockopt(fd: sock, level: level, optname: optname,
2103#ifdef _WIN32
2104 reinterpret_cast<const char *>(optval),
2105#else
2106 optval: optval,
2107#endif
2108 optlen: optlen) == 0;
2109}
2110
2111inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
2112 return set_socket_opt_impl(sock, level, optname, optval: &optval, optlen: sizeof(optval));
2113}
2114
2115inline bool set_socket_opt_time(socket_t sock, int level, int optname,
2116 time_t sec, time_t usec) {
2117#ifdef _WIN32
2118 auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
2119#else
2120 timeval timeout;
2121 timeout.tv_sec = static_cast<long>(sec);
2122 timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
2123#endif
2124 return set_socket_opt_impl(sock, level, optname, optval: &timeout, optlen: sizeof(timeout));
2125}
2126
2127} // namespace detail
2128
2129inline void default_socket_options(socket_t sock) {
2130 detail::set_socket_opt(sock, SOL_SOCKET,
2131#ifdef SO_REUSEPORT
2132 SO_REUSEPORT,
2133#else
2134 SO_REUSEADDR,
2135#endif
2136 optval: 1);
2137}
2138
2139inline const char *status_message(int status) {
2140 switch (status) {
2141 case StatusCode::Continue_100: return "Continue";
2142 case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
2143 case StatusCode::Processing_102: return "Processing";
2144 case StatusCode::EarlyHints_103: return "Early Hints";
2145 case StatusCode::OK_200: return "OK";
2146 case StatusCode::Created_201: return "Created";
2147 case StatusCode::Accepted_202: return "Accepted";
2148 case StatusCode::NonAuthoritativeInformation_203:
2149 return "Non-Authoritative Information";
2150 case StatusCode::NoContent_204: return "No Content";
2151 case StatusCode::ResetContent_205: return "Reset Content";
2152 case StatusCode::PartialContent_206: return "Partial Content";
2153 case StatusCode::MultiStatus_207: return "Multi-Status";
2154 case StatusCode::AlreadyReported_208: return "Already Reported";
2155 case StatusCode::IMUsed_226: return "IM Used";
2156 case StatusCode::MultipleChoices_300: return "Multiple Choices";
2157 case StatusCode::MovedPermanently_301: return "Moved Permanently";
2158 case StatusCode::Found_302: return "Found";
2159 case StatusCode::SeeOther_303: return "See Other";
2160 case StatusCode::NotModified_304: return "Not Modified";
2161 case StatusCode::UseProxy_305: return "Use Proxy";
2162 case StatusCode::unused_306: return "unused";
2163 case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
2164 case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
2165 case StatusCode::BadRequest_400: return "Bad Request";
2166 case StatusCode::Unauthorized_401: return "Unauthorized";
2167 case StatusCode::PaymentRequired_402: return "Payment Required";
2168 case StatusCode::Forbidden_403: return "Forbidden";
2169 case StatusCode::NotFound_404: return "Not Found";
2170 case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
2171 case StatusCode::NotAcceptable_406: return "Not Acceptable";
2172 case StatusCode::ProxyAuthenticationRequired_407:
2173 return "Proxy Authentication Required";
2174 case StatusCode::RequestTimeout_408: return "Request Timeout";
2175 case StatusCode::Conflict_409: return "Conflict";
2176 case StatusCode::Gone_410: return "Gone";
2177 case StatusCode::LengthRequired_411: return "Length Required";
2178 case StatusCode::PreconditionFailed_412: return "Precondition Failed";
2179 case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
2180 case StatusCode::UriTooLong_414: return "URI Too Long";
2181 case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
2182 case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
2183 case StatusCode::ExpectationFailed_417: return "Expectation Failed";
2184 case StatusCode::ImATeapot_418: return "I'm a teapot";
2185 case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
2186 case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
2187 case StatusCode::Locked_423: return "Locked";
2188 case StatusCode::FailedDependency_424: return "Failed Dependency";
2189 case StatusCode::TooEarly_425: return "Too Early";
2190 case StatusCode::UpgradeRequired_426: return "Upgrade Required";
2191 case StatusCode::PreconditionRequired_428: return "Precondition Required";
2192 case StatusCode::TooManyRequests_429: return "Too Many Requests";
2193 case StatusCode::RequestHeaderFieldsTooLarge_431:
2194 return "Request Header Fields Too Large";
2195 case StatusCode::UnavailableForLegalReasons_451:
2196 return "Unavailable For Legal Reasons";
2197 case StatusCode::NotImplemented_501: return "Not Implemented";
2198 case StatusCode::BadGateway_502: return "Bad Gateway";
2199 case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
2200 case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
2201 case StatusCode::HttpVersionNotSupported_505:
2202 return "HTTP Version Not Supported";
2203 case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
2204 case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
2205 case StatusCode::LoopDetected_508: return "Loop Detected";
2206 case StatusCode::NotExtended_510: return "Not Extended";
2207 case StatusCode::NetworkAuthenticationRequired_511:
2208 return "Network Authentication Required";
2209
2210 default:
2211 case StatusCode::InternalServerError_500: return "Internal Server Error";
2212 }
2213}
2214
2215inline std::string get_bearer_token_auth(const Request &req) {
2216 if (req.has_header(key: "Authorization")) {
2217 constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
2218 return req.get_header_value(key: "Authorization")
2219 .substr(pos: bearer_header_prefix_len);
2220 }
2221 return "";
2222}
2223
2224template <class Rep, class Period>
2225inline Server &
2226Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2227 detail::duration_to_sec_and_usec(
2228 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2229 return *this;
2230}
2231
2232template <class Rep, class Period>
2233inline Server &
2234Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2235 detail::duration_to_sec_and_usec(
2236 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2237 return *this;
2238}
2239
2240template <class Rep, class Period>
2241inline Server &
2242Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
2243 detail::duration_to_sec_and_usec(
2244 duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
2245 return *this;
2246}
2247
2248inline std::string to_string(const Error error) {
2249 switch (error) {
2250 case Error::Success: return "Success (no error)";
2251 case Error::Unknown: return "Unknown";
2252 case Error::Connection: return "Could not establish connection";
2253 case Error::BindIPAddress: return "Failed to bind IP address";
2254 case Error::Read: return "Failed to read connection";
2255 case Error::Write: return "Failed to write connection";
2256 case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
2257 case Error::Canceled: return "Connection handling canceled";
2258 case Error::SSLConnection: return "SSL connection failed";
2259 case Error::SSLLoadingCerts: return "SSL certificate loading failed";
2260 case Error::SSLServerVerification: return "SSL server verification failed";
2261 case Error::SSLServerHostnameVerification:
2262 return "SSL server hostname verification failed";
2263 case Error::UnsupportedMultipartBoundaryChars:
2264 return "Unsupported HTTP multipart boundary characters";
2265 case Error::Compression: return "Compression failed";
2266 case Error::ConnectionTimeout: return "Connection timed out";
2267 case Error::ProxyConnection: return "Proxy connection failed";
2268 case Error::ResourceExhaustion: return "Resource exhaustion";
2269 case Error::TooManyFormDataFiles: return "Too many form data files";
2270 case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
2271 case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
2272 case Error::ExceedMaxSocketDescriptorCount:
2273 return "Exceeded maximum socket descriptor count";
2274 case Error::InvalidRequestLine: return "Invalid request line";
2275 case Error::InvalidHTTPMethod: return "Invalid HTTP method";
2276 case Error::InvalidHTTPVersion: return "Invalid HTTP version";
2277 case Error::InvalidHeaders: return "Invalid headers";
2278 case Error::MultipartParsing: return "Multipart parsing failed";
2279 case Error::OpenFile: return "Failed to open file";
2280 case Error::Listen: return "Failed to listen on socket";
2281 case Error::GetSockName: return "Failed to get socket name";
2282 case Error::UnsupportedAddressFamily: return "Unsupported address family";
2283 case Error::HTTPParsing: return "HTTP parsing failed";
2284 case Error::InvalidRangeHeader: return "Invalid Range header";
2285 default: break;
2286 }
2287
2288 return "Invalid";
2289}
2290
2291inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
2292 os << to_string(error: obj);
2293 os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
2294 return os;
2295}
2296
2297inline size_t Result::get_request_header_value_u64(const std::string &key,
2298 size_t def,
2299 size_t id) const {
2300 return detail::get_header_value_u64(headers: request_headers_, key, def, id);
2301}
2302
2303template <class Rep, class Period>
2304inline void ClientImpl::set_connection_timeout(
2305 const std::chrono::duration<Rep, Period> &duration) {
2306 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
2307 set_connection_timeout(sec, usec);
2308 });
2309}
2310
2311template <class Rep, class Period>
2312inline void ClientImpl::set_read_timeout(
2313 const std::chrono::duration<Rep, Period> &duration) {
2314 detail::duration_to_sec_and_usec(
2315 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2316}
2317
2318template <class Rep, class Period>
2319inline void ClientImpl::set_write_timeout(
2320 const std::chrono::duration<Rep, Period> &duration) {
2321 detail::duration_to_sec_and_usec(
2322 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2323}
2324
2325template <class Rep, class Period>
2326inline void ClientImpl::set_max_timeout(
2327 const std::chrono::duration<Rep, Period> &duration) {
2328 auto msec =
2329 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
2330 set_max_timeout(msec);
2331}
2332
2333template <class Rep, class Period>
2334inline void Client::set_connection_timeout(
2335 const std::chrono::duration<Rep, Period> &duration) {
2336 cli_->set_connection_timeout(duration);
2337}
2338
2339template <class Rep, class Period>
2340inline void
2341Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2342 cli_->set_read_timeout(duration);
2343}
2344
2345template <class Rep, class Period>
2346inline void
2347Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2348 cli_->set_write_timeout(duration);
2349}
2350
2351inline void Client::set_max_timeout(time_t msec) {
2352 cli_->set_max_timeout(msec);
2353}
2354
2355template <class Rep, class Period>
2356inline void
2357Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
2358 cli_->set_max_timeout(duration);
2359}
2360
2361/*
2362 * Forward declarations and types that will be part of the .h file if split into
2363 * .h + .cc.
2364 */
2365
2366std::string hosted_at(const std::string &hostname);
2367
2368void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
2369
2370// JavaScript-style URL encoding/decoding functions
2371std::string encode_uri_component(const std::string &value);
2372std::string encode_uri(const std::string &value);
2373std::string decode_uri_component(const std::string &value);
2374std::string decode_uri(const std::string &value);
2375
2376// RFC 3986 compliant URL component encoding/decoding functions
2377std::string encode_path_component(const std::string &component);
2378std::string decode_path_component(const std::string &component);
2379std::string encode_query_component(const std::string &component,
2380 bool space_as_plus = true);
2381std::string decode_query_component(const std::string &component,
2382 bool plus_as_space = true);
2383
2384std::string append_query_params(const std::string &path, const Params &params);
2385
2386std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
2387
2388std::pair<std::string, std::string>
2389make_basic_authentication_header(const std::string &username,
2390 const std::string &password,
2391 bool is_proxy = false);
2392
2393namespace detail {
2394
2395#if defined(_WIN32)
2396inline std::wstring u8string_to_wstring(const char *s) {
2397 std::wstring ws;
2398 auto len = static_cast<int>(strlen(s));
2399 auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
2400 if (wlen > 0) {
2401 ws.resize(wlen);
2402 wlen = ::MultiByteToWideChar(
2403 CP_UTF8, 0, s, len,
2404 const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
2405 if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
2406 }
2407 return ws;
2408}
2409#endif
2410
2411struct FileStat {
2412 FileStat(const std::string &path);
2413 bool is_file() const;
2414 bool is_dir() const;
2415
2416private:
2417#if defined(_WIN32)
2418 struct _stat st_;
2419#else
2420 struct stat st_;
2421#endif
2422 int ret_ = -1;
2423};
2424
2425std::string trim_copy(const std::string &s);
2426
2427void divide(
2428 const char *data, std::size_t size, char d,
2429 std::function<void(const char *, std::size_t, const char *, std::size_t)>
2430 fn);
2431
2432void divide(
2433 const std::string &str, char d,
2434 std::function<void(const char *, std::size_t, const char *, std::size_t)>
2435 fn);
2436
2437void split(const char *b, const char *e, char d,
2438 std::function<void(const char *, const char *)> fn);
2439
2440void split(const char *b, const char *e, char d, size_t m,
2441 std::function<void(const char *, const char *)> fn);
2442
2443bool process_client_socket(
2444 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
2445 time_t write_timeout_sec, time_t write_timeout_usec,
2446 time_t max_timeout_msec,
2447 std::chrono::time_point<std::chrono::steady_clock> start_time,
2448 std::function<bool(Stream &)> callback);
2449
2450socket_t create_client_socket(const std::string &host, const std::string &ip,
2451 int port, int address_family, bool tcp_nodelay,
2452 bool ipv6_v6only, SocketOptions socket_options,
2453 time_t connection_timeout_sec,
2454 time_t connection_timeout_usec,
2455 time_t read_timeout_sec, time_t read_timeout_usec,
2456 time_t write_timeout_sec,
2457 time_t write_timeout_usec,
2458 const std::string &intf, Error &error);
2459
2460const char *get_header_value(const Headers &headers, const std::string &key,
2461 const char *def, size_t id);
2462
2463std::string params_to_query_str(const Params &params);
2464
2465void parse_query_text(const char *data, std::size_t size, Params &params);
2466
2467void parse_query_text(const std::string &s, Params &params);
2468
2469bool parse_multipart_boundary(const std::string &content_type,
2470 std::string &boundary);
2471
2472bool parse_range_header(const std::string &s, Ranges &ranges);
2473
2474bool parse_accept_header(const std::string &s,
2475 std::vector<std::string> &content_types);
2476
2477int close_socket(socket_t sock);
2478
2479ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
2480
2481ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
2482
2483enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
2484
2485EncodingType encoding_type(const Request &req, const Response &res);
2486
2487class BufferStream final : public Stream {
2488public:
2489 BufferStream() = default;
2490 ~BufferStream() override = default;
2491
2492 bool is_readable() const override;
2493 bool wait_readable() const override;
2494 bool wait_writable() const override;
2495 ssize_t read(char *ptr, size_t size) override;
2496 ssize_t write(const char *ptr, size_t size) override;
2497 void get_remote_ip_and_port(std::string &ip, int &port) const override;
2498 void get_local_ip_and_port(std::string &ip, int &port) const override;
2499 socket_t socket() const override;
2500 time_t duration() const override;
2501
2502 const std::string &get_buffer() const;
2503
2504private:
2505 std::string buffer;
2506 size_t position = 0;
2507};
2508
2509class compressor {
2510public:
2511 virtual ~compressor() = default;
2512
2513 typedef std::function<bool(const char *data, size_t data_len)> Callback;
2514 virtual bool compress(const char *data, size_t data_length, bool last,
2515 Callback callback) = 0;
2516};
2517
2518class decompressor {
2519public:
2520 virtual ~decompressor() = default;
2521
2522 virtual bool is_valid() const = 0;
2523
2524 typedef std::function<bool(const char *data, size_t data_len)> Callback;
2525 virtual bool decompress(const char *data, size_t data_length,
2526 Callback callback) = 0;
2527};
2528
2529class nocompressor final : public compressor {
2530public:
2531 ~nocompressor() override = default;
2532
2533 bool compress(const char *data, size_t data_length, bool /*last*/,
2534 Callback callback) override;
2535};
2536
2537#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2538class gzip_compressor final : public compressor {
2539public:
2540 gzip_compressor();
2541 ~gzip_compressor() override;
2542
2543 bool compress(const char *data, size_t data_length, bool last,
2544 Callback callback) override;
2545
2546private:
2547 bool is_valid_ = false;
2548 z_stream strm_;
2549};
2550
2551class gzip_decompressor final : public decompressor {
2552public:
2553 gzip_decompressor();
2554 ~gzip_decompressor() override;
2555
2556 bool is_valid() const override;
2557
2558 bool decompress(const char *data, size_t data_length,
2559 Callback callback) override;
2560
2561private:
2562 bool is_valid_ = false;
2563 z_stream strm_;
2564};
2565#endif
2566
2567#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2568class brotli_compressor final : public compressor {
2569public:
2570 brotli_compressor();
2571 ~brotli_compressor();
2572
2573 bool compress(const char *data, size_t data_length, bool last,
2574 Callback callback) override;
2575
2576private:
2577 BrotliEncoderState *state_ = nullptr;
2578};
2579
2580class brotli_decompressor final : public decompressor {
2581public:
2582 brotli_decompressor();
2583 ~brotli_decompressor();
2584
2585 bool is_valid() const override;
2586
2587 bool decompress(const char *data, size_t data_length,
2588 Callback callback) override;
2589
2590private:
2591 BrotliDecoderResult decoder_r;
2592 BrotliDecoderState *decoder_s = nullptr;
2593};
2594#endif
2595
2596#ifdef CPPHTTPLIB_ZSTD_SUPPORT
2597class zstd_compressor : public compressor {
2598public:
2599 zstd_compressor();
2600 ~zstd_compressor();
2601
2602 bool compress(const char *data, size_t data_length, bool last,
2603 Callback callback) override;
2604
2605private:
2606 ZSTD_CCtx *ctx_ = nullptr;
2607};
2608
2609class zstd_decompressor : public decompressor {
2610public:
2611 zstd_decompressor();
2612 ~zstd_decompressor();
2613
2614 bool is_valid() const override;
2615
2616 bool decompress(const char *data, size_t data_length,
2617 Callback callback) override;
2618
2619private:
2620 ZSTD_DCtx *ctx_ = nullptr;
2621};
2622#endif
2623
2624// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
2625// to store data. The call can set memory on stack for performance.
2626class stream_line_reader {
2627public:
2628 stream_line_reader(Stream &strm, char *fixed_buffer,
2629 size_t fixed_buffer_size);
2630 const char *ptr() const;
2631 size_t size() const;
2632 bool end_with_crlf() const;
2633 bool getline();
2634
2635private:
2636 void append(char c);
2637
2638 Stream &strm_;
2639 char *fixed_buffer_;
2640 const size_t fixed_buffer_size_;
2641 size_t fixed_buffer_used_size_ = 0;
2642 std::string growable_buffer_;
2643};
2644
2645class mmap {
2646public:
2647 mmap(const char *path);
2648 ~mmap();
2649
2650 bool open(const char *path);
2651 void close();
2652
2653 bool is_open() const;
2654 size_t size() const;
2655 const char *data() const;
2656
2657private:
2658#if defined(_WIN32)
2659 HANDLE hFile_ = NULL;
2660 HANDLE hMapping_ = NULL;
2661#else
2662 int fd_ = -1;
2663#endif
2664 size_t size_ = 0;
2665 void *addr_ = nullptr;
2666 bool is_open_empty_file = false;
2667};
2668
2669// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
2670namespace fields {
2671
2672inline bool is_token_char(char c) {
2673 return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
2674 c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
2675 c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
2676}
2677
2678inline bool is_token(const std::string &s) {
2679 if (s.empty()) { return false; }
2680 for (auto c : s) {
2681 if (!is_token_char(c)) { return false; }
2682 }
2683 return true;
2684}
2685
2686inline bool is_field_name(const std::string &s) { return is_token(s); }
2687
2688inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
2689
2690inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
2691
2692inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
2693
2694inline bool is_field_content(const std::string &s) {
2695 if (s.empty()) { return true; }
2696
2697 if (s.size() == 1) {
2698 return is_field_vchar(c: s[0]);
2699 } else if (s.size() == 2) {
2700 return is_field_vchar(c: s[0]) && is_field_vchar(c: s[1]);
2701 } else {
2702 size_t i = 0;
2703
2704 if (!is_field_vchar(c: s[i])) { return false; }
2705 i++;
2706
2707 while (i < s.size() - 1) {
2708 auto c = s[i++];
2709 if (c == ' ' || c == '\t' || is_field_vchar(c)) {
2710 } else {
2711 return false;
2712 }
2713 }
2714
2715 return is_field_vchar(c: s[i]);
2716 }
2717}
2718
2719inline bool is_field_value(const std::string &s) { return is_field_content(s); }
2720
2721} // namespace fields
2722
2723} // namespace detail
2724
2725// ----------------------------------------------------------------------------
2726
2727/*
2728 * Implementation that will be part of the .cc file if split into .h + .cc.
2729 */
2730
2731namespace detail {
2732
2733inline bool is_hex(char c, int &v) {
2734 if (0x20 <= c && isdigit(c)) {
2735 v = c - '0';
2736 return true;
2737 } else if ('A' <= c && c <= 'F') {
2738 v = c - 'A' + 10;
2739 return true;
2740 } else if ('a' <= c && c <= 'f') {
2741 v = c - 'a' + 10;
2742 return true;
2743 }
2744 return false;
2745}
2746
2747inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
2748 int &val) {
2749 if (i >= s.size()) { return false; }
2750
2751 val = 0;
2752 for (; cnt; i++, cnt--) {
2753 if (!s[i]) { return false; }
2754 auto v = 0;
2755 if (is_hex(c: s[i], v)) {
2756 val = val * 16 + v;
2757 } else {
2758 return false;
2759 }
2760 }
2761 return true;
2762}
2763
2764inline std::string from_i_to_hex(size_t n) {
2765 static const auto charset = "0123456789abcdef";
2766 std::string ret;
2767 do {
2768 ret = charset[n & 15] + ret;
2769 n >>= 4;
2770 } while (n > 0);
2771 return ret;
2772}
2773
2774inline size_t to_utf8(int code, char *buff) {
2775 if (code < 0x0080) {
2776 buff[0] = static_cast<char>(code & 0x7F);
2777 return 1;
2778 } else if (code < 0x0800) {
2779 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
2780 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
2781 return 2;
2782 } else if (code < 0xD800) {
2783 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
2784 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
2785 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
2786 return 3;
2787 } else if (code < 0xE000) { // D800 - DFFF is invalid...
2788 return 0;
2789 } else if (code < 0x10000) {
2790 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
2791 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
2792 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
2793 return 3;
2794 } else if (code < 0x110000) {
2795 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
2796 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
2797 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
2798 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
2799 return 4;
2800 }
2801
2802 // NOTREACHED
2803 return 0;
2804}
2805
2806// NOTE: This code came up with the following stackoverflow post:
2807// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
2808inline std::string base64_encode(const std::string &in) {
2809 static const auto lookup =
2810 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2811
2812 std::string out;
2813 out.reserve(res_arg: in.size());
2814
2815 auto val = 0;
2816 auto valb = -6;
2817
2818 for (auto c : in) {
2819 val = (val << 8) + static_cast<uint8_t>(c);
2820 valb += 8;
2821 while (valb >= 0) {
2822 out.push_back(c: lookup[(val >> valb) & 0x3F]);
2823 valb -= 6;
2824 }
2825 }
2826
2827 if (valb > -6) { out.push_back(c: lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
2828
2829 while (out.size() % 4) {
2830 out.push_back(c: '=');
2831 }
2832
2833 return out;
2834}
2835
2836inline bool is_valid_path(const std::string &path) {
2837 size_t level = 0;
2838 size_t i = 0;
2839
2840 // Skip slash
2841 while (i < path.size() && path[i] == '/') {
2842 i++;
2843 }
2844
2845 while (i < path.size()) {
2846 // Read component
2847 auto beg = i;
2848 while (i < path.size() && path[i] != '/') {
2849 if (path[i] == '\0') {
2850 return false;
2851 } else if (path[i] == '\\') {
2852 return false;
2853 }
2854 i++;
2855 }
2856
2857 auto len = i - beg;
2858 assert(len > 0);
2859
2860 if (!path.compare(pos: beg, n1: len, s: ".")) {
2861 ;
2862 } else if (!path.compare(pos: beg, n1: len, s: "..")) {
2863 if (level == 0) { return false; }
2864 level--;
2865 } else {
2866 level++;
2867 }
2868
2869 // Skip slash
2870 while (i < path.size() && path[i] == '/') {
2871 i++;
2872 }
2873 }
2874
2875 return true;
2876}
2877
2878inline FileStat::FileStat(const std::string &path) {
2879#if defined(_WIN32)
2880 auto wpath = u8string_to_wstring(path.c_str());
2881 ret_ = _wstat(wpath.c_str(), &st_);
2882#else
2883 ret_ = stat(file: path.c_str(), buf: &st_);
2884#endif
2885}
2886inline bool FileStat::is_file() const {
2887 return ret_ >= 0 && S_ISREG(st_.st_mode);
2888}
2889inline bool FileStat::is_dir() const {
2890 return ret_ >= 0 && S_ISDIR(st_.st_mode);
2891}
2892
2893inline std::string encode_path(const std::string &s) {
2894 std::string result;
2895 result.reserve(res_arg: s.size());
2896
2897 for (size_t i = 0; s[i]; i++) {
2898 switch (s[i]) {
2899 case ' ': result += "%20"; break;
2900 case '+': result += "%2B"; break;
2901 case '\r': result += "%0D"; break;
2902 case '\n': result += "%0A"; break;
2903 case '\'': result += "%27"; break;
2904 case ',': result += "%2C"; break;
2905 // case ':': result += "%3A"; break; // ok? probably...
2906 case ';': result += "%3B"; break;
2907 default:
2908 auto c = static_cast<uint8_t>(s[i]);
2909 if (c >= 0x80) {
2910 result += '%';
2911 char hex[4];
2912 auto len = snprintf(s: hex, maxlen: sizeof(hex) - 1, format: "%02X", c);
2913 assert(len == 2);
2914 result.append(s: hex, n: static_cast<size_t>(len));
2915 } else {
2916 result += s[i];
2917 }
2918 break;
2919 }
2920 }
2921
2922 return result;
2923}
2924
2925inline std::string file_extension(const std::string &path) {
2926 std::smatch m;
2927 thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
2928 if (std::regex_search(s: path, m&: m, e: re)) { return m[1].str(); }
2929 return std::string();
2930}
2931
2932inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
2933
2934inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
2935 size_t right) {
2936 while (b + left < e && is_space_or_tab(c: b[left])) {
2937 left++;
2938 }
2939 while (right > 0 && is_space_or_tab(c: b[right - 1])) {
2940 right--;
2941 }
2942 return std::make_pair(x&: left, y&: right);
2943}
2944
2945inline std::string trim_copy(const std::string &s) {
2946 auto r = trim(b: s.data(), e: s.data() + s.size(), left: 0, right: s.size());
2947 return s.substr(pos: r.first, n: r.second - r.first);
2948}
2949
2950inline std::string trim_double_quotes_copy(const std::string &s) {
2951 if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
2952 return s.substr(pos: 1, n: s.size() - 2);
2953 }
2954 return s;
2955}
2956
2957inline void
2958divide(const char *data, std::size_t size, char d,
2959 std::function<void(const char *, std::size_t, const char *, std::size_t)>
2960 fn) {
2961 const auto it = std::find(first: data, last: data + size, val: d);
2962 const auto found = static_cast<std::size_t>(it != data + size);
2963 const auto lhs_data = data;
2964 const auto lhs_size = static_cast<std::size_t>(it - data);
2965 const auto rhs_data = it + found;
2966 const auto rhs_size = size - lhs_size - found;
2967
2968 fn(lhs_data, lhs_size, rhs_data, rhs_size);
2969}
2970
2971inline void
2972divide(const std::string &str, char d,
2973 std::function<void(const char *, std::size_t, const char *, std::size_t)>
2974 fn) {
2975 divide(data: str.data(), size: str.size(), d, fn: std::move(fn));
2976}
2977
2978inline void split(const char *b, const char *e, char d,
2979 std::function<void(const char *, const char *)> fn) {
2980 return split(b, e, d, m: (std::numeric_limits<size_t>::max)(), fn: std::move(fn));
2981}
2982
2983inline void split(const char *b, const char *e, char d, size_t m,
2984 std::function<void(const char *, const char *)> fn) {
2985 size_t i = 0;
2986 size_t beg = 0;
2987 size_t count = 1;
2988
2989 while (e ? (b + i < e) : (b[i] != '\0')) {
2990 if (b[i] == d && count < m) {
2991 auto r = trim(b, e, left: beg, right: i);
2992 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
2993 beg = i + 1;
2994 count++;
2995 }
2996 i++;
2997 }
2998
2999 if (i) {
3000 auto r = trim(b, e, left: beg, right: i);
3001 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
3002 }
3003}
3004
3005inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
3006 size_t fixed_buffer_size)
3007 : strm_(strm), fixed_buffer_(fixed_buffer),
3008 fixed_buffer_size_(fixed_buffer_size) {}
3009
3010inline const char *stream_line_reader::ptr() const {
3011 if (growable_buffer_.empty()) {
3012 return fixed_buffer_;
3013 } else {
3014 return growable_buffer_.data();
3015 }
3016}
3017
3018inline size_t stream_line_reader::size() const {
3019 if (growable_buffer_.empty()) {
3020 return fixed_buffer_used_size_;
3021 } else {
3022 return growable_buffer_.size();
3023 }
3024}
3025
3026inline bool stream_line_reader::end_with_crlf() const {
3027 auto end = ptr() + size();
3028 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
3029}
3030
3031inline bool stream_line_reader::getline() {
3032 fixed_buffer_used_size_ = 0;
3033 growable_buffer_.clear();
3034
3035#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
3036 char prev_byte = 0;
3037#endif
3038
3039 for (size_t i = 0;; i++) {
3040 if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
3041 // Treat exceptionally long lines as an error to
3042 // prevent infinite loops/memory exhaustion
3043 return false;
3044 }
3045 char byte;
3046 auto n = strm_.read(ptr: &byte, size: 1);
3047
3048 if (n < 0) {
3049 return false;
3050 } else if (n == 0) {
3051 if (i == 0) {
3052 return false;
3053 } else {
3054 break;
3055 }
3056 }
3057
3058 append(c: byte);
3059
3060#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
3061 if (byte == '\n') { break; }
3062#else
3063 if (prev_byte == '\r' && byte == '\n') { break; }
3064 prev_byte = byte;
3065#endif
3066 }
3067
3068 return true;
3069}
3070
3071inline void stream_line_reader::append(char c) {
3072 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
3073 fixed_buffer_[fixed_buffer_used_size_++] = c;
3074 fixed_buffer_[fixed_buffer_used_size_] = '\0';
3075 } else {
3076 if (growable_buffer_.empty()) {
3077 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
3078 growable_buffer_.assign(s: fixed_buffer_, n: fixed_buffer_used_size_);
3079 }
3080 growable_buffer_ += c;
3081 }
3082}
3083
3084inline mmap::mmap(const char *path) { open(path); }
3085
3086inline mmap::~mmap() { close(); }
3087
3088inline bool mmap::open(const char *path) {
3089 close();
3090
3091#if defined(_WIN32)
3092 auto wpath = u8string_to_wstring(path);
3093 if (wpath.empty()) { return false; }
3094
3095 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
3096 OPEN_EXISTING, NULL);
3097
3098 if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
3099
3100 LARGE_INTEGER size{};
3101 if (!::GetFileSizeEx(hFile_, &size)) { return false; }
3102 // If the following line doesn't compile due to QuadPart, update Windows SDK.
3103 // See:
3104 // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
3105 if (static_cast<ULONGLONG>(size.QuadPart) >
3106 (std::numeric_limits<decltype(size_)>::max)()) {
3107 // `size_t` might be 32-bits, on 32-bits Windows.
3108 return false;
3109 }
3110 size_ = static_cast<size_t>(size.QuadPart);
3111
3112 hMapping_ =
3113 ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
3114
3115 // Special treatment for an empty file...
3116 if (hMapping_ == NULL && size_ == 0) {
3117 close();
3118 is_open_empty_file = true;
3119 return true;
3120 }
3121
3122 if (hMapping_ == NULL) {
3123 close();
3124 return false;
3125 }
3126
3127 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
3128
3129 if (addr_ == nullptr) {
3130 close();
3131 return false;
3132 }
3133#else
3134 fd_ = ::open(file: path, O_RDONLY);
3135 if (fd_ == -1) { return false; }
3136
3137 struct stat sb;
3138 if (fstat(fd: fd_, buf: &sb) == -1) {
3139 close();
3140 return false;
3141 }
3142 size_ = static_cast<size_t>(sb.st_size);
3143
3144 addr_ = ::mmap(NULL, len: size_, PROT_READ, MAP_PRIVATE, fd: fd_, offset: 0);
3145
3146 // Special treatment for an empty file...
3147 if (addr_ == MAP_FAILED && size_ == 0) {
3148 close();
3149 is_open_empty_file = true;
3150 return false;
3151 }
3152#endif
3153
3154 return true;
3155}
3156
3157inline bool mmap::is_open() const {
3158 return is_open_empty_file ? true : addr_ != nullptr;
3159}
3160
3161inline size_t mmap::size() const { return size_; }
3162
3163inline const char *mmap::data() const {
3164 return is_open_empty_file ? "" : static_cast<const char *>(addr_);
3165}
3166
3167inline void mmap::close() {
3168#if defined(_WIN32)
3169 if (addr_) {
3170 ::UnmapViewOfFile(addr_);
3171 addr_ = nullptr;
3172 }
3173
3174 if (hMapping_) {
3175 ::CloseHandle(hMapping_);
3176 hMapping_ = NULL;
3177 }
3178
3179 if (hFile_ != INVALID_HANDLE_VALUE) {
3180 ::CloseHandle(hFile_);
3181 hFile_ = INVALID_HANDLE_VALUE;
3182 }
3183
3184 is_open_empty_file = false;
3185#else
3186 if (addr_ != nullptr) {
3187 munmap(addr: addr_, len: size_);
3188 addr_ = nullptr;
3189 }
3190
3191 if (fd_ != -1) {
3192 ::close(fd: fd_);
3193 fd_ = -1;
3194 }
3195#endif
3196 size_ = 0;
3197}
3198inline int close_socket(socket_t sock) {
3199#ifdef _WIN32
3200 return closesocket(sock);
3201#else
3202 return close(fd: sock);
3203#endif
3204}
3205
3206template <typename T> inline ssize_t handle_EINTR(T fn) {
3207 ssize_t res = 0;
3208 while (true) {
3209 res = fn();
3210 if (res < 0 && errno == EINTR) {
3211 std::this_thread::sleep_for(rtime: std::chrono::microseconds{1});
3212 continue;
3213 }
3214 break;
3215 }
3216 return res;
3217}
3218
3219inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
3220 return handle_EINTR(fn: [&]() {
3221 return recv(fd: sock,
3222#ifdef _WIN32
3223 static_cast<char *>(ptr), static_cast<int>(size),
3224#else
3225 buf: ptr, n: size,
3226#endif
3227 flags: flags);
3228 });
3229}
3230
3231inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
3232 int flags) {
3233 return handle_EINTR(fn: [&]() {
3234 return send(fd: sock,
3235#ifdef _WIN32
3236 static_cast<const char *>(ptr), static_cast<int>(size),
3237#else
3238 buf: ptr, n: size,
3239#endif
3240 flags: flags);
3241 });
3242}
3243
3244inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
3245#ifdef _WIN32
3246 return ::WSAPoll(fds, nfds, timeout);
3247#else
3248 return ::poll(fds: fds, nfds: nfds, timeout: timeout);
3249#endif
3250}
3251
3252template <bool Read>
3253inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
3254#ifdef __APPLE__
3255 if (sock >= FD_SETSIZE) { return -1; }
3256
3257 fd_set fds, *rfds, *wfds;
3258 FD_ZERO(&fds);
3259 FD_SET(sock, &fds);
3260 rfds = (Read ? &fds : nullptr);
3261 wfds = (Read ? nullptr : &fds);
3262
3263 timeval tv;
3264 tv.tv_sec = static_cast<long>(sec);
3265 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
3266
3267 return handle_EINTR([&]() {
3268 return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
3269 });
3270#else
3271 struct pollfd pfd;
3272 pfd.fd = sock;
3273 pfd.events = (Read ? POLLIN : POLLOUT);
3274
3275 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
3276
3277 return handle_EINTR([&]() { return poll_wrapper(fds: &pfd, nfds: 1, timeout); });
3278#endif
3279}
3280
3281inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
3282 return select_impl<true>(sock, sec, usec);
3283}
3284
3285inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
3286 return select_impl<false>(sock, sec, usec);
3287}
3288
3289inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
3290 time_t usec) {
3291#ifdef __APPLE__
3292 if (sock >= FD_SETSIZE) { return Error::Connection; }
3293
3294 fd_set fdsr, fdsw;
3295 FD_ZERO(&fdsr);
3296 FD_ZERO(&fdsw);
3297 FD_SET(sock, &fdsr);
3298 FD_SET(sock, &fdsw);
3299
3300 timeval tv;
3301 tv.tv_sec = static_cast<long>(sec);
3302 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
3303
3304 auto ret = handle_EINTR([&]() {
3305 return select(static_cast<int>(sock + 1), &fdsr, &fdsw, nullptr, &tv);
3306 });
3307
3308 if (ret == 0) { return Error::ConnectionTimeout; }
3309
3310 if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
3311 auto error = 0;
3312 socklen_t len = sizeof(error);
3313 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
3314 reinterpret_cast<char *>(&error), &len);
3315 auto successful = res >= 0 && !error;
3316 return successful ? Error::Success : Error::Connection;
3317 }
3318
3319 return Error::Connection;
3320#else
3321 struct pollfd pfd_read;
3322 pfd_read.fd = sock;
3323 pfd_read.events = POLLIN | POLLOUT;
3324
3325 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
3326
3327 auto poll_res =
3328 handle_EINTR(fn: [&]() { return poll_wrapper(fds: &pfd_read, nfds: 1, timeout); });
3329
3330 if (poll_res == 0) { return Error::ConnectionTimeout; }
3331
3332 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
3333 auto error = 0;
3334 socklen_t len = sizeof(error);
3335 auto res = getsockopt(fd: sock, SOL_SOCKET, SO_ERROR,
3336 optval: reinterpret_cast<char *>(&error), optlen: &len);
3337 auto successful = res >= 0 && !error;
3338 return successful ? Error::Success : Error::Connection;
3339 }
3340
3341 return Error::Connection;
3342#endif
3343}
3344
3345inline bool is_socket_alive(socket_t sock) {
3346 const auto val = detail::select_read(sock, sec: 0, usec: 0);
3347 if (val == 0) {
3348 return true;
3349 } else if (val < 0 && errno == EBADF) {
3350 return false;
3351 }
3352 char buf[1];
3353 return detail::read_socket(sock, ptr: &buf[0], size: sizeof(buf), MSG_PEEK) > 0;
3354}
3355
3356class SocketStream final : public Stream {
3357public:
3358 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3359 time_t write_timeout_sec, time_t write_timeout_usec,
3360 time_t max_timeout_msec = 0,
3361 std::chrono::time_point<std::chrono::steady_clock> start_time =
3362 (std::chrono::steady_clock::time_point::min)());
3363 ~SocketStream() override;
3364
3365 bool is_readable() const override;
3366 bool wait_readable() const override;
3367 bool wait_writable() const override;
3368 ssize_t read(char *ptr, size_t size) override;
3369 ssize_t write(const char *ptr, size_t size) override;
3370 void get_remote_ip_and_port(std::string &ip, int &port) const override;
3371 void get_local_ip_and_port(std::string &ip, int &port) const override;
3372 socket_t socket() const override;
3373 time_t duration() const override;
3374
3375private:
3376 socket_t sock_;
3377 time_t read_timeout_sec_;
3378 time_t read_timeout_usec_;
3379 time_t write_timeout_sec_;
3380 time_t write_timeout_usec_;
3381 time_t max_timeout_msec_;
3382 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
3383
3384 std::vector<char> read_buff_;
3385 size_t read_buff_off_ = 0;
3386 size_t read_buff_content_size_ = 0;
3387
3388 static const size_t read_buff_size_ = 1024l * 4;
3389};
3390
3391#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3392class SSLSocketStream final : public Stream {
3393public:
3394 SSLSocketStream(
3395 socket_t sock, SSL *ssl, time_t read_timeout_sec,
3396 time_t read_timeout_usec, time_t write_timeout_sec,
3397 time_t write_timeout_usec, time_t max_timeout_msec = 0,
3398 std::chrono::time_point<std::chrono::steady_clock> start_time =
3399 (std::chrono::steady_clock::time_point::min)());
3400 ~SSLSocketStream() override;
3401
3402 bool is_readable() const override;
3403 bool wait_readable() const override;
3404 bool wait_writable() const override;
3405 ssize_t read(char *ptr, size_t size) override;
3406 ssize_t write(const char *ptr, size_t size) override;
3407 void get_remote_ip_and_port(std::string &ip, int &port) const override;
3408 void get_local_ip_and_port(std::string &ip, int &port) const override;
3409 socket_t socket() const override;
3410 time_t duration() const override;
3411
3412private:
3413 socket_t sock_;
3414 SSL *ssl_;
3415 time_t read_timeout_sec_;
3416 time_t read_timeout_usec_;
3417 time_t write_timeout_sec_;
3418 time_t write_timeout_usec_;
3419 time_t max_timeout_msec_;
3420 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
3421};
3422#endif
3423
3424inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
3425 time_t keep_alive_timeout_sec) {
3426 using namespace std::chrono;
3427
3428 const auto interval_usec =
3429 CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
3430
3431 // Avoid expensive `steady_clock::now()` call for the first time
3432 if (select_read(sock, sec: 0, usec: interval_usec) > 0) { return true; }
3433
3434 const auto start = steady_clock::now() - microseconds{interval_usec};
3435 const auto timeout = seconds{keep_alive_timeout_sec};
3436
3437 while (true) {
3438 if (svr_sock == INVALID_SOCKET) {
3439 break; // Server socket is closed
3440 }
3441
3442 auto val = select_read(sock, sec: 0, usec: interval_usec);
3443 if (val < 0) {
3444 break; // Ssocket error
3445 } else if (val == 0) {
3446 if (steady_clock::now() - start > timeout) {
3447 break; // Timeout
3448 }
3449 } else {
3450 return true; // Ready for read
3451 }
3452 }
3453
3454 return false;
3455}
3456
3457template <typename T>
3458inline bool
3459process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
3460 size_t keep_alive_max_count,
3461 time_t keep_alive_timeout_sec, T callback) {
3462 assert(keep_alive_max_count > 0);
3463 auto ret = false;
3464 auto count = keep_alive_max_count;
3465 while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
3466 auto close_connection = count == 1;
3467 auto connection_closed = false;
3468 ret = callback(close_connection, connection_closed);
3469 if (!ret || connection_closed) { break; }
3470 count--;
3471 }
3472 return ret;
3473}
3474
3475template <typename T>
3476inline bool
3477process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
3478 size_t keep_alive_max_count,
3479 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
3480 time_t read_timeout_usec, time_t write_timeout_sec,
3481 time_t write_timeout_usec, T callback) {
3482 return process_server_socket_core(
3483 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
3484 [&](bool close_connection, bool &connection_closed) {
3485 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
3486 write_timeout_sec, write_timeout_usec);
3487 return callback(strm, close_connection, connection_closed);
3488 });
3489}
3490
3491inline bool process_client_socket(
3492 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3493 time_t write_timeout_sec, time_t write_timeout_usec,
3494 time_t max_timeout_msec,
3495 std::chrono::time_point<std::chrono::steady_clock> start_time,
3496 std::function<bool(Stream &)> callback) {
3497 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
3498 write_timeout_sec, write_timeout_usec, max_timeout_msec,
3499 start_time);
3500 return callback(strm);
3501}
3502
3503inline int shutdown_socket(socket_t sock) {
3504#ifdef _WIN32
3505 return shutdown(sock, SD_BOTH);
3506#else
3507 return shutdown(fd: sock, SHUT_RDWR);
3508#endif
3509}
3510
3511inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
3512 if (s.size() > 1 && s[0] == '\0') {
3513 auto ret = s;
3514 ret[0] = '@';
3515 return ret;
3516 }
3517 return s;
3518}
3519
3520inline std::string
3521unescape_abstract_namespace_unix_domain(const std::string &s) {
3522 if (s.size() > 1 && s[0] == '@') {
3523 auto ret = s;
3524 ret[0] = '\0';
3525 return ret;
3526 }
3527 return s;
3528}
3529
3530inline int getaddrinfo_with_timeout(const char *node, const char *service,
3531 const struct addrinfo *hints,
3532 struct addrinfo **res, time_t timeout_sec) {
3533#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
3534 if (timeout_sec <= 0) {
3535 // No timeout specified, use standard getaddrinfo
3536 return getaddrinfo(node, service, hints, res);
3537 }
3538
3539#ifdef _WIN32
3540 // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
3541 OVERLAPPED overlapped = {0};
3542 HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
3543 if (!event) { return EAI_FAIL; }
3544
3545 overlapped.hEvent = event;
3546
3547 PADDRINFOEXW result_addrinfo = nullptr;
3548 HANDLE cancel_handle = nullptr;
3549
3550 ADDRINFOEXW hints_ex = {0};
3551 if (hints) {
3552 hints_ex.ai_flags = hints->ai_flags;
3553 hints_ex.ai_family = hints->ai_family;
3554 hints_ex.ai_socktype = hints->ai_socktype;
3555 hints_ex.ai_protocol = hints->ai_protocol;
3556 }
3557
3558 auto wnode = u8string_to_wstring(node);
3559 auto wservice = u8string_to_wstring(service);
3560
3561 auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
3562 hints ? &hints_ex : nullptr, &result_addrinfo,
3563 nullptr, &overlapped, nullptr, &cancel_handle);
3564
3565 if (ret == WSA_IO_PENDING) {
3566 auto wait_result =
3567 ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
3568 if (wait_result == WAIT_TIMEOUT) {
3569 if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
3570 ::CloseHandle(event);
3571 return EAI_AGAIN;
3572 }
3573
3574 DWORD bytes_returned;
3575 if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
3576 &bytes_returned, FALSE)) {
3577 ::CloseHandle(event);
3578 return ::WSAGetLastError();
3579 }
3580 }
3581
3582 ::CloseHandle(event);
3583
3584 if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
3585 *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
3586 return 0;
3587 }
3588
3589 return ret;
3590#elif TARGET_OS_MAC
3591 // macOS implementation using CFHost API for asynchronous DNS resolution
3592 CFStringRef hostname_ref = CFStringCreateWithCString(
3593 kCFAllocatorDefault, node, kCFStringEncodingUTF8);
3594 if (!hostname_ref) { return EAI_MEMORY; }
3595
3596 CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
3597 CFRelease(hostname_ref);
3598 if (!host_ref) { return EAI_MEMORY; }
3599
3600 // Set up context for callback
3601 struct CFHostContext {
3602 bool completed = false;
3603 bool success = false;
3604 CFArrayRef addresses = nullptr;
3605 std::mutex mutex;
3606 std::condition_variable cv;
3607 } context;
3608
3609 CFHostClientContext client_context;
3610 memset(&client_context, 0, sizeof(client_context));
3611 client_context.info = &context;
3612
3613 // Set callback
3614 auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
3615 const CFStreamError *error, void *info) {
3616 auto ctx = static_cast<CFHostContext *>(info);
3617 std::lock_guard<std::mutex> lock(ctx->mutex);
3618
3619 if (error && error->error != 0) {
3620 ctx->success = false;
3621 } else {
3622 Boolean hasBeenResolved;
3623 ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
3624 if (ctx->addresses && hasBeenResolved) {
3625 CFRetain(ctx->addresses);
3626 ctx->success = true;
3627 } else {
3628 ctx->success = false;
3629 }
3630 }
3631 ctx->completed = true;
3632 ctx->cv.notify_one();
3633 };
3634
3635 if (!CFHostSetClient(host_ref, callback, &client_context)) {
3636 CFRelease(host_ref);
3637 return EAI_SYSTEM;
3638 }
3639
3640 // Schedule on run loop
3641 CFRunLoopRef run_loop = CFRunLoopGetCurrent();
3642 CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
3643
3644 // Start resolution
3645 CFStreamError stream_error;
3646 if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
3647 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
3648 CFRelease(host_ref);
3649 return EAI_FAIL;
3650 }
3651
3652 // Wait for completion with timeout
3653 auto timeout_time =
3654 std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
3655 bool timed_out = false;
3656
3657 {
3658 std::unique_lock<std::mutex> lock(context.mutex);
3659
3660 while (!context.completed) {
3661 auto now = std::chrono::steady_clock::now();
3662 if (now >= timeout_time) {
3663 timed_out = true;
3664 break;
3665 }
3666
3667 // Run the runloop for a short time
3668 lock.unlock();
3669 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
3670 lock.lock();
3671 }
3672 }
3673
3674 // Clean up
3675 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
3676 CFHostSetClient(host_ref, nullptr, nullptr);
3677
3678 if (timed_out || !context.completed) {
3679 CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
3680 CFRelease(host_ref);
3681 return EAI_AGAIN;
3682 }
3683
3684 if (!context.success || !context.addresses) {
3685 CFRelease(host_ref);
3686 return EAI_NODATA;
3687 }
3688
3689 // Convert CFArray to addrinfo
3690 CFIndex count = CFArrayGetCount(context.addresses);
3691 if (count == 0) {
3692 CFRelease(context.addresses);
3693 CFRelease(host_ref);
3694 return EAI_NODATA;
3695 }
3696
3697 struct addrinfo *result_addrinfo = nullptr;
3698 struct addrinfo **current = &result_addrinfo;
3699
3700 for (CFIndex i = 0; i < count; i++) {
3701 CFDataRef addr_data =
3702 static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
3703 if (!addr_data) continue;
3704
3705 const struct sockaddr *sockaddr_ptr =
3706 reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
3707 socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
3708
3709 // Allocate addrinfo structure
3710 *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
3711 if (!*current) {
3712 freeaddrinfo(result_addrinfo);
3713 CFRelease(context.addresses);
3714 CFRelease(host_ref);
3715 return EAI_MEMORY;
3716 }
3717
3718 memset(*current, 0, sizeof(struct addrinfo));
3719
3720 // Set up addrinfo fields
3721 (*current)->ai_family = sockaddr_ptr->sa_family;
3722 (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
3723 (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
3724 (*current)->ai_addrlen = sockaddr_len;
3725
3726 // Copy sockaddr
3727 (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
3728 if (!(*current)->ai_addr) {
3729 freeaddrinfo(result_addrinfo);
3730 CFRelease(context.addresses);
3731 CFRelease(host_ref);
3732 return EAI_MEMORY;
3733 }
3734 memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
3735
3736 // Set port if service is specified
3737 if (service && strlen(service) > 0) {
3738 int port = atoi(service);
3739 if (port > 0) {
3740 if (sockaddr_ptr->sa_family == AF_INET) {
3741 reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
3742 ->sin_port = htons(static_cast<uint16_t>(port));
3743 } else if (sockaddr_ptr->sa_family == AF_INET6) {
3744 reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
3745 ->sin6_port = htons(static_cast<uint16_t>(port));
3746 }
3747 }
3748 }
3749
3750 current = &((*current)->ai_next);
3751 }
3752
3753 CFRelease(context.addresses);
3754 CFRelease(host_ref);
3755
3756 *res = result_addrinfo;
3757 return 0;
3758#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \
3759 (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
3760 // Linux implementation using getaddrinfo_a for asynchronous DNS resolution
3761 struct gaicb request;
3762 struct gaicb *requests[1] = {&request};
3763 struct sigevent sevp;
3764 struct timespec timeout;
3765
3766 // Initialize the request structure
3767 memset(&request, 0, sizeof(request));
3768 request.ar_name = node;
3769 request.ar_service = service;
3770 request.ar_request = hints;
3771
3772 // Set up timeout
3773 timeout.tv_sec = timeout_sec;
3774 timeout.tv_nsec = 0;
3775
3776 // Initialize sigevent structure (not used, but required)
3777 memset(&sevp, 0, sizeof(sevp));
3778 sevp.sigev_notify = SIGEV_NONE;
3779
3780 // Start asynchronous resolution
3781 int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
3782 if (start_result != 0) { return start_result; }
3783
3784 // Wait for completion with timeout
3785 int wait_result =
3786 gai_suspend((const struct gaicb *const *)requests, 1, &timeout);
3787
3788 if (wait_result == 0 || wait_result == EAI_ALLDONE) {
3789 // Completed successfully, get the result
3790 int gai_result = gai_error(&request);
3791 if (gai_result == 0) {
3792 *res = request.ar_result;
3793 return 0;
3794 } else {
3795 // Clean up on error
3796 if (request.ar_result) { freeaddrinfo(request.ar_result); }
3797 return gai_result;
3798 }
3799 } else if (wait_result == EAI_AGAIN) {
3800 // Timeout occurred, cancel the request
3801 gai_cancel(&request);
3802 return EAI_AGAIN;
3803 } else {
3804 // Other error occurred
3805 gai_cancel(&request);
3806 return wait_result;
3807 }
3808#else
3809 // Fallback implementation using thread-based timeout for other Unix systems
3810
3811 struct GetAddrInfoState {
3812 std::mutex mutex;
3813 std::condition_variable result_cv;
3814 bool completed = false;
3815 int result = EAI_SYSTEM;
3816 std::string node = node;
3817 std::string service = service;
3818 struct addrinfo hints = hints;
3819 struct addrinfo *info = nullptr;
3820 };
3821
3822 // Allocate on the heap, so the resolver thread can keep using the data.
3823 auto state = std::make_shared<GetAddrInfoState>();
3824
3825 std::thread resolve_thread([=]() {
3826 auto thread_result = getaddrinfo(
3827 state->node.c_str(), state->service.c_str(), hints, &state->info);
3828
3829 std::lock_guard<std::mutex> lock(state->mutex);
3830 state->result = thread_result;
3831 state->completed = true;
3832 state->result_cv.notify_one();
3833 });
3834
3835 // Wait for completion or timeout
3836 std::unique_lock<std::mutex> lock(state->mutex);
3837 auto finished =
3838 state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
3839 [&] { return state->completed; });
3840
3841 if (finished) {
3842 // Operation completed within timeout
3843 resolve_thread.join();
3844 *res = state->info;
3845 return state->result;
3846 } else {
3847 // Timeout occurred
3848 resolve_thread.detach(); // Let the thread finish in background
3849 return EAI_AGAIN; // Return timeout error
3850 }
3851#endif
3852#else
3853 (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
3854 return getaddrinfo(name: node, service: service, req: hints, pai: res);
3855#endif
3856}
3857
3858template <typename BindOrConnect>
3859socket_t create_socket(const std::string &host, const std::string &ip, int port,
3860 int address_family, int socket_flags, bool tcp_nodelay,
3861 bool ipv6_v6only, SocketOptions socket_options,
3862 BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
3863 // Get address info
3864 const char *node = nullptr;
3865 struct addrinfo hints;
3866 struct addrinfo *result;
3867
3868 memset(s: &hints, c: 0, n: sizeof(struct addrinfo));
3869 hints.ai_socktype = SOCK_STREAM;
3870 hints.ai_protocol = IPPROTO_IP;
3871
3872 if (!ip.empty()) {
3873 node = ip.c_str();
3874 // Ask getaddrinfo to convert IP in c-string to address
3875 hints.ai_family = AF_UNSPEC;
3876 hints.ai_flags = AI_NUMERICHOST;
3877 } else {
3878 if (!host.empty()) { node = host.c_str(); }
3879 hints.ai_family = address_family;
3880 hints.ai_flags = socket_flags;
3881 }
3882
3883#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
3884 if (hints.ai_family == AF_UNIX) {
3885 const auto addrlen = host.length();
3886 if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
3887
3888#ifdef SOCK_CLOEXEC
3889 auto sock = socket(domain: hints.ai_family, type: hints.ai_socktype | SOCK_CLOEXEC,
3890 protocol: hints.ai_protocol);
3891#else
3892 auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
3893#endif
3894
3895 if (sock != INVALID_SOCKET) {
3896 sockaddr_un addr{};
3897 addr.sun_family = AF_UNIX;
3898
3899 auto unescaped_host = unescape_abstract_namespace_unix_domain(s: host);
3900 std::copy(first: unescaped_host.begin(), last: unescaped_host.end(), result: addr.sun_path);
3901
3902 hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
3903 hints.ai_addrlen = static_cast<socklen_t>(
3904 sizeof(addr) - sizeof(addr.sun_path) + addrlen);
3905
3906#ifndef SOCK_CLOEXEC
3907#ifndef _WIN32
3908 fcntl(sock, F_SETFD, FD_CLOEXEC);
3909#endif
3910#endif
3911
3912 if (socket_options) { socket_options(sock); }
3913
3914#ifdef _WIN32
3915 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
3916 // remove the option.
3917 detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
3918#endif
3919
3920 bool dummy;
3921 if (!bind_or_connect(sock, hints, dummy)) {
3922 close_socket(sock);
3923 sock = INVALID_SOCKET;
3924 }
3925 }
3926 return sock;
3927 }
3928#endif
3929
3930 auto service = std::to_string(val: port);
3931
3932 if (getaddrinfo_with_timeout(node, service: service.c_str(), hints: &hints, res: &result,
3933 timeout_sec)) {
3934#if defined __linux__ && !defined __ANDROID__
3935 res_init();
3936#endif
3937 return INVALID_SOCKET;
3938 }
3939 auto se = detail::scope_exit([&] { freeaddrinfo(ai: result); });
3940
3941 for (auto rp = result; rp; rp = rp->ai_next) {
3942 // Create a socket
3943#ifdef _WIN32
3944 auto sock =
3945 WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
3946 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
3947 /**
3948 * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
3949 * and above the socket creation fails on older Windows Systems.
3950 *
3951 * Let's try to create a socket the old way in this case.
3952 *
3953 * Reference:
3954 * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
3955 *
3956 * WSA_FLAG_NO_HANDLE_INHERIT:
3957 * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
3958 * SP1, and later
3959 *
3960 */
3961 if (sock == INVALID_SOCKET) {
3962 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3963 }
3964#else
3965
3966#ifdef SOCK_CLOEXEC
3967 auto sock =
3968 socket(domain: rp->ai_family, type: rp->ai_socktype | SOCK_CLOEXEC, protocol: rp->ai_protocol);
3969#else
3970 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
3971#endif
3972
3973#endif
3974 if (sock == INVALID_SOCKET) { continue; }
3975
3976#if !defined _WIN32 && !defined SOCK_CLOEXEC
3977 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
3978 close_socket(sock);
3979 continue;
3980 }
3981#endif
3982
3983 if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, optval: 1); }
3984
3985 if (rp->ai_family == AF_INET6) {
3986 set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, optval: ipv6_v6only ? 1 : 0);
3987 }
3988
3989 if (socket_options) { socket_options(sock); }
3990
3991 // bind or connect
3992 auto quit = false;
3993 if (bind_or_connect(sock, *rp, quit)) { return sock; }
3994
3995 close_socket(sock);
3996
3997 if (quit) { break; }
3998 }
3999
4000 return INVALID_SOCKET;
4001}
4002
4003inline void set_nonblocking(socket_t sock, bool nonblocking) {
4004#ifdef _WIN32
4005 auto flags = nonblocking ? 1UL : 0UL;
4006 ioctlsocket(sock, FIONBIO, &flags);
4007#else
4008 auto flags = fcntl(fd: sock, F_GETFL, 0);
4009 fcntl(fd: sock, F_SETFL,
4010 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
4011#endif
4012}
4013
4014inline bool is_connection_error() {
4015#ifdef _WIN32
4016 return WSAGetLastError() != WSAEWOULDBLOCK;
4017#else
4018 return errno != EINPROGRESS;
4019#endif
4020}
4021
4022inline bool bind_ip_address(socket_t sock, const std::string &host) {
4023 struct addrinfo hints;
4024 struct addrinfo *result;
4025
4026 memset(s: &hints, c: 0, n: sizeof(struct addrinfo));
4027 hints.ai_family = AF_UNSPEC;
4028 hints.ai_socktype = SOCK_STREAM;
4029 hints.ai_protocol = 0;
4030
4031 if (getaddrinfo_with_timeout(node: host.c_str(), service: "0", hints: &hints, res: &result, timeout_sec: 0)) {
4032 return false;
4033 }
4034
4035 auto se = detail::scope_exit([&] { freeaddrinfo(ai: result); });
4036
4037 auto ret = false;
4038 for (auto rp = result; rp; rp = rp->ai_next) {
4039 const auto &ai = *rp;
4040 if (!::bind(fd: sock, addr: ai.ai_addr, len: static_cast<socklen_t>(ai.ai_addrlen))) {
4041 ret = true;
4042 break;
4043 }
4044 }
4045
4046 return ret;
4047}
4048
4049#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
4050#define USE_IF2IP
4051#endif
4052
4053#ifdef USE_IF2IP
4054inline std::string if2ip(int address_family, const std::string &ifn) {
4055 struct ifaddrs *ifap;
4056 getifaddrs(ifap: &ifap);
4057 auto se = detail::scope_exit([&] { freeifaddrs(ifa: ifap); });
4058
4059 std::string addr_candidate;
4060 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
4061 if (ifa->ifa_addr && ifn == ifa->ifa_name &&
4062 (AF_UNSPEC == address_family ||
4063 ifa->ifa_addr->sa_family == address_family)) {
4064 if (ifa->ifa_addr->sa_family == AF_INET) {
4065 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
4066 char buf[INET_ADDRSTRLEN];
4067 if (inet_ntop(AF_INET, cp: &sa->sin_addr, buf: buf, INET_ADDRSTRLEN)) {
4068 return std::string(buf, INET_ADDRSTRLEN);
4069 }
4070 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
4071 auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
4072 if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
4073 char buf[INET6_ADDRSTRLEN] = {};
4074 if (inet_ntop(AF_INET6, cp: &sa->sin6_addr, buf: buf, INET6_ADDRSTRLEN)) {
4075 // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
4076 auto s6_addr_head = sa->sin6_addr.s6_addr[0];
4077 if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
4078 addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
4079 } else {
4080 return std::string(buf, INET6_ADDRSTRLEN);
4081 }
4082 }
4083 }
4084 }
4085 }
4086 }
4087 return addr_candidate;
4088}
4089#endif
4090
4091inline socket_t create_client_socket(
4092 const std::string &host, const std::string &ip, int port,
4093 int address_family, bool tcp_nodelay, bool ipv6_v6only,
4094 SocketOptions socket_options, time_t connection_timeout_sec,
4095 time_t connection_timeout_usec, time_t read_timeout_sec,
4096 time_t read_timeout_usec, time_t write_timeout_sec,
4097 time_t write_timeout_usec, const std::string &intf, Error &error) {
4098 auto sock = create_socket(
4099 host, ip, port, address_family, socket_flags: 0, tcp_nodelay, ipv6_v6only,
4100 socket_options: std::move(socket_options),
4101 bind_or_connect: [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
4102 if (!intf.empty()) {
4103#ifdef USE_IF2IP
4104 auto ip_from_if = if2ip(address_family, ifn: intf);
4105 if (ip_from_if.empty()) { ip_from_if = intf; }
4106 if (!bind_ip_address(sock: sock2, host: ip_from_if)) {
4107 error = Error::BindIPAddress;
4108 return false;
4109 }
4110#endif
4111 }
4112
4113 set_nonblocking(sock: sock2, nonblocking: true);
4114
4115 auto ret =
4116 ::connect(fd: sock2, addr: ai.ai_addr, len: static_cast<socklen_t>(ai.ai_addrlen));
4117
4118 if (ret < 0) {
4119 if (is_connection_error()) {
4120 error = Error::Connection;
4121 return false;
4122 }
4123 error = wait_until_socket_is_ready(sock: sock2, sec: connection_timeout_sec,
4124 usec: connection_timeout_usec);
4125 if (error != Error::Success) {
4126 if (error == Error::ConnectionTimeout) { quit = true; }
4127 return false;
4128 }
4129 }
4130
4131 set_nonblocking(sock: sock2, nonblocking: false);
4132 set_socket_opt_time(sock: sock2, SOL_SOCKET, SO_RCVTIMEO, sec: read_timeout_sec,
4133 usec: read_timeout_usec);
4134 set_socket_opt_time(sock: sock2, SOL_SOCKET, SO_SNDTIMEO, sec: write_timeout_sec,
4135 usec: write_timeout_usec);
4136
4137 error = Error::Success;
4138 return true;
4139 },
4140 timeout_sec: connection_timeout_sec); // Pass DNS timeout
4141
4142 if (sock != INVALID_SOCKET) {
4143 error = Error::Success;
4144 } else {
4145 if (error == Error::Success) { error = Error::Connection; }
4146 }
4147
4148 return sock;
4149}
4150
4151inline bool get_ip_and_port(const struct sockaddr_storage &addr,
4152 socklen_t addr_len, std::string &ip, int &port) {
4153 if (addr.ss_family == AF_INET) {
4154 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
4155 } else if (addr.ss_family == AF_INET6) {
4156 port =
4157 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
4158 } else {
4159 return false;
4160 }
4161
4162 std::array<char, NI_MAXHOST> ipstr{};
4163 if (getnameinfo(sa: reinterpret_cast<const struct sockaddr *>(&addr), salen: addr_len,
4164 host: ipstr.data(), hostlen: static_cast<socklen_t>(ipstr.size()), serv: nullptr,
4165 servlen: 0, NI_NUMERICHOST)) {
4166 return false;
4167 }
4168
4169 ip = ipstr.data();
4170 return true;
4171}
4172
4173inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
4174 struct sockaddr_storage addr;
4175 socklen_t addr_len = sizeof(addr);
4176 if (!getsockname(fd: sock, addr: reinterpret_cast<struct sockaddr *>(&addr),
4177 len: &addr_len)) {
4178 get_ip_and_port(addr, addr_len, ip, port);
4179 }
4180}
4181
4182inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
4183 struct sockaddr_storage addr;
4184 socklen_t addr_len = sizeof(addr);
4185
4186 if (!getpeername(fd: sock, addr: reinterpret_cast<struct sockaddr *>(&addr),
4187 len: &addr_len)) {
4188#ifndef _WIN32
4189 if (addr.ss_family == AF_UNIX) {
4190#if defined(__linux__)
4191 struct ucred ucred;
4192 socklen_t len = sizeof(ucred);
4193 if (getsockopt(fd: sock, SOL_SOCKET, SO_PEERCRED, optval: &ucred, optlen: &len) == 0) {
4194 port = ucred.pid;
4195 }
4196#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
4197 pid_t pid;
4198 socklen_t len = sizeof(pid);
4199 if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
4200 port = pid;
4201 }
4202#endif
4203 return;
4204 }
4205#endif
4206 get_ip_and_port(addr, addr_len, ip, port);
4207 }
4208}
4209
4210inline constexpr unsigned int str2tag_core(const char *s, size_t l,
4211 unsigned int h) {
4212 return (l == 0)
4213 ? h
4214 : str2tag_core(
4215 s: s + 1, l: l - 1,
4216 // Unsets the 6 high bits of h, therefore no overflow happens
4217 h: (((std::numeric_limits<unsigned int>::max)() >> 6) &
4218 h * 33) ^
4219 static_cast<unsigned char>(*s));
4220}
4221
4222inline unsigned int str2tag(const std::string &s) {
4223 return str2tag_core(s: s.data(), l: s.size(), h: 0);
4224}
4225
4226namespace udl {
4227
4228inline constexpr unsigned int operator""_t(const char *s, size_t l) {
4229 return str2tag_core(s, l, h: 0);
4230}
4231
4232} // namespace udl
4233
4234inline std::string
4235find_content_type(const std::string &path,
4236 const std::map<std::string, std::string> &user_data,
4237 const std::string &default_content_type) {
4238 auto ext = file_extension(path);
4239
4240 auto it = user_data.find(x: ext);
4241 if (it != user_data.end()) { return it->second; }
4242
4243 using udl::operator""_t;
4244
4245 switch (str2tag(s: ext)) {
4246 default: return default_content_type;
4247
4248 case "css"_t: return "text/css";
4249 case "csv"_t: return "text/csv";
4250 case "htm"_t:
4251 case "html"_t: return "text/html";
4252 case "js"_t:
4253 case "mjs"_t: return "text/javascript";
4254 case "txt"_t: return "text/plain";
4255 case "vtt"_t: return "text/vtt";
4256
4257 case "apng"_t: return "image/apng";
4258 case "avif"_t: return "image/avif";
4259 case "bmp"_t: return "image/bmp";
4260 case "gif"_t: return "image/gif";
4261 case "png"_t: return "image/png";
4262 case "svg"_t: return "image/svg+xml";
4263 case "webp"_t: return "image/webp";
4264 case "ico"_t: return "image/x-icon";
4265 case "tif"_t: return "image/tiff";
4266 case "tiff"_t: return "image/tiff";
4267 case "jpg"_t:
4268 case "jpeg"_t: return "image/jpeg";
4269
4270 case "mp4"_t: return "video/mp4";
4271 case "mpeg"_t: return "video/mpeg";
4272 case "webm"_t: return "video/webm";
4273
4274 case "mp3"_t: return "audio/mp3";
4275 case "mpga"_t: return "audio/mpeg";
4276 case "weba"_t: return "audio/webm";
4277 case "wav"_t: return "audio/wave";
4278
4279 case "otf"_t: return "font/otf";
4280 case "ttf"_t: return "font/ttf";
4281 case "woff"_t: return "font/woff";
4282 case "woff2"_t: return "font/woff2";
4283
4284 case "7z"_t: return "application/x-7z-compressed";
4285 case "atom"_t: return "application/atom+xml";
4286 case "pdf"_t: return "application/pdf";
4287 case "json"_t: return "application/json";
4288 case "rss"_t: return "application/rss+xml";
4289 case "tar"_t: return "application/x-tar";
4290 case "xht"_t:
4291 case "xhtml"_t: return "application/xhtml+xml";
4292 case "xslt"_t: return "application/xslt+xml";
4293 case "xml"_t: return "application/xml";
4294 case "gz"_t: return "application/gzip";
4295 case "zip"_t: return "application/zip";
4296 case "wasm"_t: return "application/wasm";
4297 }
4298}
4299
4300inline bool can_compress_content_type(const std::string &content_type) {
4301 using udl::operator""_t;
4302
4303 auto tag = str2tag(s: content_type);
4304
4305 switch (tag) {
4306 case "image/svg+xml"_t:
4307 case "application/javascript"_t:
4308 case "application/json"_t:
4309 case "application/xml"_t:
4310 case "application/protobuf"_t:
4311 case "application/xhtml+xml"_t: return true;
4312
4313 case "text/event-stream"_t: return false;
4314
4315 default: return !content_type.rfind(s: "text/", pos: 0);
4316 }
4317}
4318
4319inline EncodingType encoding_type(const Request &req, const Response &res) {
4320 auto ret =
4321 detail::can_compress_content_type(content_type: res.get_header_value(key: "Content-Type"));
4322 if (!ret) { return EncodingType::None; }
4323
4324 const auto &s = req.get_header_value(key: "Accept-Encoding");
4325 (void)(s);
4326
4327#ifdef CPPHTTPLIB_BROTLI_SUPPORT
4328 // TODO: 'Accept-Encoding' has br, not br;q=0
4329 ret = s.find("br") != std::string::npos;
4330 if (ret) { return EncodingType::Brotli; }
4331#endif
4332
4333#ifdef CPPHTTPLIB_ZLIB_SUPPORT
4334 // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
4335 ret = s.find("gzip") != std::string::npos;
4336 if (ret) { return EncodingType::Gzip; }
4337#endif
4338
4339#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4340 // TODO: 'Accept-Encoding' has zstd, not zstd;q=0
4341 ret = s.find("zstd") != std::string::npos;
4342 if (ret) { return EncodingType::Zstd; }
4343#endif
4344
4345 return EncodingType::None;
4346}
4347
4348inline bool nocompressor::compress(const char *data, size_t data_length,
4349 bool /*last*/, Callback callback) {
4350 if (!data_length) { return true; }
4351 return callback(data, data_length);
4352}
4353
4354#ifdef CPPHTTPLIB_ZLIB_SUPPORT
4355inline gzip_compressor::gzip_compressor() {
4356 std::memset(&strm_, 0, sizeof(strm_));
4357 strm_.zalloc = Z_NULL;
4358 strm_.zfree = Z_NULL;
4359 strm_.opaque = Z_NULL;
4360
4361 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
4362 Z_DEFAULT_STRATEGY) == Z_OK;
4363}
4364
4365inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
4366
4367inline bool gzip_compressor::compress(const char *data, size_t data_length,
4368 bool last, Callback callback) {
4369 assert(is_valid_);
4370
4371 do {
4372 constexpr size_t max_avail_in =
4373 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
4374
4375 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
4376 (std::min)(data_length, max_avail_in));
4377 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
4378
4379 data_length -= strm_.avail_in;
4380 data += strm_.avail_in;
4381
4382 auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
4383 auto ret = Z_OK;
4384
4385 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4386 do {
4387 strm_.avail_out = static_cast<uInt>(buff.size());
4388 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
4389
4390 ret = deflate(&strm_, flush);
4391 if (ret == Z_STREAM_ERROR) { return false; }
4392
4393 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
4394 return false;
4395 }
4396 } while (strm_.avail_out == 0);
4397
4398 assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
4399 (flush == Z_NO_FLUSH && ret == Z_OK));
4400 assert(strm_.avail_in == 0);
4401 } while (data_length > 0);
4402
4403 return true;
4404}
4405
4406inline gzip_decompressor::gzip_decompressor() {
4407 std::memset(&strm_, 0, sizeof(strm_));
4408 strm_.zalloc = Z_NULL;
4409 strm_.zfree = Z_NULL;
4410 strm_.opaque = Z_NULL;
4411
4412 // 15 is the value of wbits, which should be at the maximum possible value
4413 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
4414 // that the stream type should be automatically detected either gzip or
4415 // deflate.
4416 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
4417}
4418
4419inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
4420
4421inline bool gzip_decompressor::is_valid() const { return is_valid_; }
4422
4423inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
4424 Callback callback) {
4425 assert(is_valid_);
4426
4427 auto ret = Z_OK;
4428
4429 do {
4430 constexpr size_t max_avail_in =
4431 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
4432
4433 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
4434 (std::min)(data_length, max_avail_in));
4435 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
4436
4437 data_length -= strm_.avail_in;
4438 data += strm_.avail_in;
4439
4440 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4441 while (strm_.avail_in > 0 && ret == Z_OK) {
4442 strm_.avail_out = static_cast<uInt>(buff.size());
4443 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
4444
4445 ret = inflate(&strm_, Z_NO_FLUSH);
4446
4447 assert(ret != Z_STREAM_ERROR);
4448 switch (ret) {
4449 case Z_NEED_DICT:
4450 case Z_DATA_ERROR:
4451 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
4452 }
4453
4454 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
4455 return false;
4456 }
4457 }
4458
4459 if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
4460
4461 } while (data_length > 0);
4462
4463 return true;
4464}
4465#endif
4466
4467#ifdef CPPHTTPLIB_BROTLI_SUPPORT
4468inline brotli_compressor::brotli_compressor() {
4469 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
4470}
4471
4472inline brotli_compressor::~brotli_compressor() {
4473 BrotliEncoderDestroyInstance(state_);
4474}
4475
4476inline bool brotli_compressor::compress(const char *data, size_t data_length,
4477 bool last, Callback callback) {
4478 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4479
4480 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
4481 auto available_in = data_length;
4482 auto next_in = reinterpret_cast<const uint8_t *>(data);
4483
4484 for (;;) {
4485 if (last) {
4486 if (BrotliEncoderIsFinished(state_)) { break; }
4487 } else {
4488 if (!available_in) { break; }
4489 }
4490
4491 auto available_out = buff.size();
4492 auto next_out = buff.data();
4493
4494 if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
4495 &available_out, &next_out, nullptr)) {
4496 return false;
4497 }
4498
4499 auto output_bytes = buff.size() - available_out;
4500 if (output_bytes) {
4501 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
4502 }
4503 }
4504
4505 return true;
4506}
4507
4508inline brotli_decompressor::brotli_decompressor() {
4509 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
4510 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
4511 : BROTLI_DECODER_RESULT_ERROR;
4512}
4513
4514inline brotli_decompressor::~brotli_decompressor() {
4515 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
4516}
4517
4518inline bool brotli_decompressor::is_valid() const { return decoder_s; }
4519
4520inline bool brotli_decompressor::decompress(const char *data,
4521 size_t data_length,
4522 Callback callback) {
4523 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
4524 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
4525 return 0;
4526 }
4527
4528 auto next_in = reinterpret_cast<const uint8_t *>(data);
4529 size_t avail_in = data_length;
4530 size_t total_out;
4531
4532 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
4533
4534 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4535 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
4536 char *next_out = buff.data();
4537 size_t avail_out = buff.size();
4538
4539 decoder_r = BrotliDecoderDecompressStream(
4540 decoder_s, &avail_in, &next_in, &avail_out,
4541 reinterpret_cast<uint8_t **>(&next_out), &total_out);
4542
4543 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
4544
4545 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
4546 }
4547
4548 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
4549 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
4550}
4551#endif
4552
4553#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4554inline zstd_compressor::zstd_compressor() {
4555 ctx_ = ZSTD_createCCtx();
4556 ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
4557}
4558
4559inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
4560
4561inline bool zstd_compressor::compress(const char *data, size_t data_length,
4562 bool last, Callback callback) {
4563 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4564
4565 ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
4566 ZSTD_inBuffer input = {data, data_length, 0};
4567
4568 bool finished;
4569 do {
4570 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
4571 size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
4572
4573 if (ZSTD_isError(remaining)) { return false; }
4574
4575 if (!callback(buff.data(), output.pos)) { return false; }
4576
4577 finished = last ? (remaining == 0) : (input.pos == input.size);
4578
4579 } while (!finished);
4580
4581 return true;
4582}
4583
4584inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
4585
4586inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
4587
4588inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
4589
4590inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
4591 Callback callback) {
4592 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
4593 ZSTD_inBuffer input = {data, data_length, 0};
4594
4595 while (input.pos < input.size) {
4596 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
4597 size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
4598
4599 if (ZSTD_isError(remaining)) { return false; }
4600
4601 if (!callback(buff.data(), output.pos)) { return false; }
4602 }
4603
4604 return true;
4605}
4606#endif
4607
4608inline bool is_prohibited_header_name(const std::string &name) {
4609 using udl::operator""_t;
4610
4611 switch (str2tag(s: name)) {
4612 case "REMOTE_ADDR"_t:
4613 case "REMOTE_PORT"_t:
4614 case "LOCAL_ADDR"_t:
4615 case "LOCAL_PORT"_t: return true;
4616 default: return false;
4617 }
4618}
4619
4620inline bool has_header(const Headers &headers, const std::string &key) {
4621 if (is_prohibited_header_name(name: key)) { return false; }
4622 return headers.find(x: key) != headers.end();
4623}
4624
4625inline const char *get_header_value(const Headers &headers,
4626 const std::string &key, const char *def,
4627 size_t id) {
4628 if (is_prohibited_header_name(name: key)) {
4629#ifndef CPPHTTPLIB_NO_EXCEPTIONS
4630 std::string msg = "Prohibited header name '" + key + "' is specified.";
4631 throw std::invalid_argument(msg);
4632#else
4633 return "";
4634#endif
4635 }
4636
4637 auto rng = headers.equal_range(x: key);
4638 auto it = rng.first;
4639 std::advance(i&: it, n: static_cast<ssize_t>(id));
4640 if (it != rng.second) { return it->second.c_str(); }
4641 return def;
4642}
4643
4644template <typename T>
4645inline bool parse_header(const char *beg, const char *end, T fn) {
4646 // Skip trailing spaces and tabs.
4647 while (beg < end && is_space_or_tab(c: end[-1])) {
4648 end--;
4649 }
4650
4651 auto p = beg;
4652 while (p < end && *p != ':') {
4653 p++;
4654 }
4655
4656 auto name = std::string(beg, p);
4657 if (!detail::fields::is_field_name(s: name)) { return false; }
4658
4659 if (p == end) { return false; }
4660
4661 auto key_end = p;
4662
4663 if (*p++ != ':') { return false; }
4664
4665 while (p < end && is_space_or_tab(c: *p)) {
4666 p++;
4667 }
4668
4669 if (p <= end) {
4670 auto key_len = key_end - beg;
4671 if (!key_len) { return false; }
4672
4673 auto key = std::string(beg, key_end);
4674 auto val = std::string(p, end);
4675
4676 if (!detail::fields::is_field_value(s: val)) { return false; }
4677
4678 if (case_ignore::equal(a: key, b: "Location") ||
4679 case_ignore::equal(a: key, b: "Referer")) {
4680 fn(key, val);
4681 } else {
4682 fn(key, decode_path_component(component: val));
4683 }
4684
4685 return true;
4686 }
4687
4688 return false;
4689}
4690
4691inline bool read_headers(Stream &strm, Headers &headers) {
4692 const auto bufsiz = 2048;
4693 char buf[bufsiz];
4694 stream_line_reader line_reader(strm, buf, bufsiz);
4695
4696 size_t header_count = 0;
4697
4698 for (;;) {
4699 if (!line_reader.getline()) { return false; }
4700
4701 // Check if the line ends with CRLF.
4702 auto line_terminator_len = 2;
4703 if (line_reader.end_with_crlf()) {
4704 // Blank line indicates end of headers.
4705 if (line_reader.size() == 2) { break; }
4706 } else {
4707#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
4708 // Blank line indicates end of headers.
4709 if (line_reader.size() == 1) { break; }
4710 line_terminator_len = 1;
4711#else
4712 continue; // Skip invalid line.
4713#endif
4714 }
4715
4716 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
4717
4718 // Check header count limit
4719 if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
4720
4721 // Exclude line terminator
4722 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
4723
4724 if (!parse_header(beg: line_reader.ptr(), end,
4725 fn: [&](const std::string &key, const std::string &val) {
4726 headers.emplace(args: key, args: val);
4727 })) {
4728 return false;
4729 }
4730
4731 header_count++;
4732 }
4733
4734 return true;
4735}
4736
4737inline bool read_content_with_length(Stream &strm, size_t len,
4738 DownloadProgress progress,
4739 ContentReceiverWithProgress out) {
4740 char buf[CPPHTTPLIB_RECV_BUFSIZ];
4741
4742 size_t r = 0;
4743 while (r < len) {
4744 auto read_len = static_cast<size_t>(len - r);
4745 auto n = strm.read(ptr: buf, size: (std::min)(a: read_len, CPPHTTPLIB_RECV_BUFSIZ));
4746 if (n <= 0) { return false; }
4747
4748 if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
4749 r += static_cast<size_t>(n);
4750
4751 if (progress) {
4752 if (!progress(r, len)) { return false; }
4753 }
4754 }
4755
4756 return true;
4757}
4758
4759inline void skip_content_with_length(Stream &strm, size_t len) {
4760 char buf[CPPHTTPLIB_RECV_BUFSIZ];
4761 size_t r = 0;
4762 while (r < len) {
4763 auto read_len = static_cast<size_t>(len - r);
4764 auto n = strm.read(ptr: buf, size: (std::min)(a: read_len, CPPHTTPLIB_RECV_BUFSIZ));
4765 if (n <= 0) { return; }
4766 r += static_cast<size_t>(n);
4767 }
4768}
4769
4770enum class ReadContentResult {
4771 Success, // Successfully read the content
4772 PayloadTooLarge, // The content exceeds the specified payload limit
4773 Error // An error occurred while reading the content
4774};
4775
4776inline ReadContentResult
4777read_content_without_length(Stream &strm, size_t payload_max_length,
4778 ContentReceiverWithProgress out) {
4779 char buf[CPPHTTPLIB_RECV_BUFSIZ];
4780 size_t r = 0;
4781 for (;;) {
4782 auto n = strm.read(ptr: buf, CPPHTTPLIB_RECV_BUFSIZ);
4783 if (n == 0) { return ReadContentResult::Success; }
4784 if (n < 0) { return ReadContentResult::Error; }
4785
4786 // Check if adding this data would exceed the payload limit
4787 if (r > payload_max_length ||
4788 payload_max_length - r < static_cast<size_t>(n)) {
4789 return ReadContentResult::PayloadTooLarge;
4790 }
4791
4792 if (!out(buf, static_cast<size_t>(n), r, 0)) {
4793 return ReadContentResult::Error;
4794 }
4795 r += static_cast<size_t>(n);
4796 }
4797
4798 return ReadContentResult::Success;
4799}
4800
4801template <typename T>
4802inline ReadContentResult read_content_chunked(Stream &strm, T &x,
4803 size_t payload_max_length,
4804 ContentReceiverWithProgress out) {
4805 const auto bufsiz = 16;
4806 char buf[bufsiz];
4807
4808 stream_line_reader line_reader(strm, buf, bufsiz);
4809
4810 if (!line_reader.getline()) { return ReadContentResult::Error; }
4811
4812 unsigned long chunk_len;
4813 size_t total_len = 0;
4814 while (true) {
4815 char *end_ptr;
4816
4817 chunk_len = std::strtoul(nptr: line_reader.ptr(), endptr: &end_ptr, base: 16);
4818
4819 if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; }
4820 if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; }
4821
4822 if (chunk_len == 0) { break; }
4823
4824 // Check if adding this chunk would exceed the payload limit
4825 if (total_len > payload_max_length ||
4826 payload_max_length - total_len < chunk_len) {
4827 return ReadContentResult::PayloadTooLarge;
4828 }
4829
4830 total_len += chunk_len;
4831
4832 if (!read_content_with_length(strm, len: chunk_len, progress: nullptr, out)) {
4833 return ReadContentResult::Error;
4834 }
4835
4836 if (!line_reader.getline()) { return ReadContentResult::Error; }
4837
4838 if (strcmp(s1: line_reader.ptr(), s2: "\r\n") != 0) {
4839 return ReadContentResult::Error;
4840 }
4841
4842 if (!line_reader.getline()) { return ReadContentResult::Error; }
4843 }
4844
4845 assert(chunk_len == 0);
4846
4847 // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
4848 // transfer coding is complete when a chunk with a chunk-size of zero is
4849 // received, possibly followed by a trailer section, and finally terminated by
4850 // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
4851 //
4852 // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
4853 // does't care for the existence of the final CRLF. In other words, it seems
4854 // to be ok whether the final CRLF exists or not in the chunked data.
4855 // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
4856 //
4857 // According to the reference code in RFC 9112, cpp-httplib now allows
4858 // chunked transfer coding data without the final CRLF.
4859 if (!line_reader.getline()) { return ReadContentResult::Success; }
4860
4861 // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
4862 thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
4863 // Message framing
4864 "transfer-encoding", "content-length",
4865
4866 // Routing
4867 "host",
4868
4869 // Authentication
4870 "authorization", "www-authenticate", "proxy-authenticate",
4871 "proxy-authorization", "cookie", "set-cookie",
4872
4873 // Request modifiers
4874 "cache-control", "expect", "max-forwards", "pragma", "range", "te",
4875
4876 // Response control
4877 "age", "expires", "date", "location", "retry-after", "vary", "warning",
4878
4879 // Payload processing
4880 "content-encoding", "content-type", "content-range", "trailer"};
4881
4882 // Parse declared trailer headers once for performance
4883 case_ignore::unordered_set<std::string> declared_trailers;
4884 if (has_header(x.headers, "Trailer")) {
4885 auto trailer_header = get_header_value(x.headers, "Trailer", "", 0);
4886 auto len = std::strlen(s: trailer_header);
4887
4888 split(trailer_header, trailer_header + len, ',',
4889 [&](const char *b, const char *e) {
4890 std::string key(b, e);
4891 if (prohibited_trailers.find(x: key) == prohibited_trailers.end()) {
4892 declared_trailers.insert(x: key);
4893 }
4894 });
4895 }
4896
4897 size_t trailer_header_count = 0;
4898 while (strcmp(s1: line_reader.ptr(), s2: "\r\n") != 0) {
4899 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) {
4900 return ReadContentResult::Error;
4901 }
4902
4903 // Check trailer header count limit
4904 if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) {
4905 return ReadContentResult::Error;
4906 }
4907
4908 // Exclude line terminator
4909 constexpr auto line_terminator_len = 2;
4910 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
4911
4912 parse_header(line_reader.ptr(), end,
4913 [&](const std::string &key, const std::string &val) {
4914 if (declared_trailers.find(x: key) != declared_trailers.end()) {
4915 x.trailers.emplace(key, val);
4916 trailer_header_count++;
4917 }
4918 });
4919
4920 if (!line_reader.getline()) { return ReadContentResult::Error; }
4921 }
4922
4923 return ReadContentResult::Success;
4924}
4925
4926inline bool is_chunked_transfer_encoding(const Headers &headers) {
4927 return case_ignore::equal(
4928 a: get_header_value(headers, key: "Transfer-Encoding", def: "", id: 0), b: "chunked");
4929}
4930
4931template <typename T, typename U>
4932bool prepare_content_receiver(T &x, int &status,
4933 ContentReceiverWithProgress receiver,
4934 bool decompress, U callback) {
4935 if (decompress) {
4936 std::string encoding = x.get_header_value("Content-Encoding");
4937 std::unique_ptr<decompressor> decompressor;
4938
4939 if (encoding == "gzip" || encoding == "deflate") {
4940#ifdef CPPHTTPLIB_ZLIB_SUPPORT
4941 decompressor = detail::make_unique<gzip_decompressor>();
4942#else
4943 status = StatusCode::UnsupportedMediaType_415;
4944 return false;
4945#endif
4946 } else if (encoding.find(s: "br") != std::string::npos) {
4947#ifdef CPPHTTPLIB_BROTLI_SUPPORT
4948 decompressor = detail::make_unique<brotli_decompressor>();
4949#else
4950 status = StatusCode::UnsupportedMediaType_415;
4951 return false;
4952#endif
4953 } else if (encoding == "zstd") {
4954#ifdef CPPHTTPLIB_ZSTD_SUPPORT
4955 decompressor = detail::make_unique<zstd_decompressor>();
4956#else
4957 status = StatusCode::UnsupportedMediaType_415;
4958 return false;
4959#endif
4960 }
4961
4962 if (decompressor) {
4963 if (decompressor->is_valid()) {
4964 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
4965 size_t off, size_t len) {
4966 return decompressor->decompress(data: buf, data_length: n,
4967 callback: [&](const char *buf2, size_t n2) {
4968 return receiver(buf2, n2, off, len);
4969 });
4970 };
4971 return callback(std::move(out));
4972 } else {
4973 status = StatusCode::InternalServerError_500;
4974 return false;
4975 }
4976 }
4977 }
4978
4979 ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
4980 size_t len) {
4981 return receiver(buf, n, off, len);
4982 };
4983 return callback(std::move(out));
4984}
4985
4986template <typename T>
4987bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
4988 DownloadProgress progress,
4989 ContentReceiverWithProgress receiver, bool decompress) {
4990 return prepare_content_receiver(
4991 x, status, std::move(receiver), decompress,
4992 [&](const ContentReceiverWithProgress &out) {
4993 auto ret = true;
4994 auto exceed_payload_max_length = false;
4995
4996 if (is_chunked_transfer_encoding(x.headers)) {
4997 auto result = read_content_chunked(strm, x, payload_max_length, out);
4998 if (result == ReadContentResult::Success) {
4999 ret = true;
5000 } else if (result == ReadContentResult::PayloadTooLarge) {
5001 exceed_payload_max_length = true;
5002 ret = false;
5003 } else {
5004 ret = false;
5005 }
5006 } else if (!has_header(x.headers, "Content-Length")) {
5007 auto result =
5008 read_content_without_length(strm, payload_max_length, out);
5009 if (result == ReadContentResult::Success) {
5010 ret = true;
5011 } else if (result == ReadContentResult::PayloadTooLarge) {
5012 exceed_payload_max_length = true;
5013 ret = false;
5014 } else {
5015 ret = false;
5016 }
5017 } else {
5018 auto is_invalid_value = false;
5019 auto len = get_header_value_u64(x.headers, "Content-Length",
5020 (std::numeric_limits<size_t>::max)(),
5021 0, is_invalid_value);
5022
5023 if (is_invalid_value) {
5024 ret = false;
5025 } else if (len > payload_max_length) {
5026 exceed_payload_max_length = true;
5027 skip_content_with_length(strm, len);
5028 ret = false;
5029 } else if (len > 0) {
5030 ret = read_content_with_length(strm, len, std::move(progress), out);
5031 }
5032 }
5033
5034 if (!ret) {
5035 status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
5036 : StatusCode::BadRequest_400;
5037 }
5038 return ret;
5039 });
5040}
5041
5042inline ssize_t write_request_line(Stream &strm, const std::string &method,
5043 const std::string &path) {
5044 std::string s = method;
5045 s += " ";
5046 s += path;
5047 s += " HTTP/1.1\r\n";
5048 return strm.write(ptr: s.data(), size: s.size());
5049}
5050
5051inline ssize_t write_response_line(Stream &strm, int status) {
5052 std::string s = "HTTP/1.1 ";
5053 s += std::to_string(val: status);
5054 s += " ";
5055 s += httplib::status_message(status);
5056 s += "\r\n";
5057 return strm.write(ptr: s.data(), size: s.size());
5058}
5059
5060inline ssize_t write_headers(Stream &strm, const Headers &headers) {
5061 ssize_t write_len = 0;
5062 for (const auto &x : headers) {
5063 std::string s;
5064 s = x.first;
5065 s += ": ";
5066 s += x.second;
5067 s += "\r\n";
5068
5069 auto len = strm.write(ptr: s.data(), size: s.size());
5070 if (len < 0) { return len; }
5071 write_len += len;
5072 }
5073 auto len = strm.write(ptr: "\r\n");
5074 if (len < 0) { return len; }
5075 write_len += len;
5076 return write_len;
5077}
5078
5079inline bool write_data(Stream &strm, const char *d, size_t l) {
5080 size_t offset = 0;
5081 while (offset < l) {
5082 auto length = strm.write(ptr: d + offset, size: l - offset);
5083 if (length < 0) { return false; }
5084 offset += static_cast<size_t>(length);
5085 }
5086 return true;
5087}
5088
5089template <typename T>
5090inline bool write_content_with_progress(Stream &strm,
5091 const ContentProvider &content_provider,
5092 size_t offset, size_t length,
5093 T is_shutting_down,
5094 const UploadProgress &upload_progress,
5095 Error &error) {
5096 size_t end_offset = offset + length;
5097 size_t start_offset = offset;
5098 auto ok = true;
5099 DataSink data_sink;
5100
5101 data_sink.write = [&](const char *d, size_t l) -> bool {
5102 if (ok) {
5103 if (write_data(strm, d, l)) {
5104 offset += l;
5105
5106 if (upload_progress && length > 0) {
5107 size_t current_written = offset - start_offset;
5108 if (!upload_progress(current_written, length)) {
5109 ok = false;
5110 return false;
5111 }
5112 }
5113 } else {
5114 ok = false;
5115 }
5116 }
5117 return ok;
5118 };
5119
5120 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
5121
5122 while (offset < end_offset && !is_shutting_down()) {
5123 if (!strm.wait_writable()) {
5124 error = Error::Write;
5125 return false;
5126 } else if (!content_provider(offset, end_offset - offset, data_sink)) {
5127 error = Error::Canceled;
5128 return false;
5129 } else if (!ok) {
5130 error = Error::Write;
5131 return false;
5132 }
5133 }
5134
5135 error = Error::Success;
5136 return true;
5137}
5138
5139template <typename T>
5140inline bool write_content(Stream &strm, const ContentProvider &content_provider,
5141 size_t offset, size_t length, T is_shutting_down,
5142 Error &error) {
5143 return write_content_with_progress<T>(strm, content_provider, offset, length,
5144 is_shutting_down, nullptr, error);
5145}
5146
5147template <typename T>
5148inline bool write_content(Stream &strm, const ContentProvider &content_provider,
5149 size_t offset, size_t length,
5150 const T &is_shutting_down) {
5151 auto error = Error::Success;
5152 return write_content(strm, content_provider, offset, length, is_shutting_down,
5153 error);
5154}
5155
5156template <typename T>
5157inline bool
5158write_content_without_length(Stream &strm,
5159 const ContentProvider &content_provider,
5160 const T &is_shutting_down) {
5161 size_t offset = 0;
5162 auto data_available = true;
5163 auto ok = true;
5164 DataSink data_sink;
5165
5166 data_sink.write = [&](const char *d, size_t l) -> bool {
5167 if (ok) {
5168 offset += l;
5169 if (!write_data(strm, d, l)) { ok = false; }
5170 }
5171 return ok;
5172 };
5173
5174 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
5175
5176 data_sink.done = [&](void) { data_available = false; };
5177
5178 while (data_available && !is_shutting_down()) {
5179 if (!strm.wait_writable()) {
5180 return false;
5181 } else if (!content_provider(offset, 0, data_sink)) {
5182 return false;
5183 } else if (!ok) {
5184 return false;
5185 }
5186 }
5187 return true;
5188}
5189
5190template <typename T, typename U>
5191inline bool
5192write_content_chunked(Stream &strm, const ContentProvider &content_provider,
5193 const T &is_shutting_down, U &compressor, Error &error) {
5194 size_t offset = 0;
5195 auto data_available = true;
5196 auto ok = true;
5197 DataSink data_sink;
5198
5199 data_sink.write = [&](const char *d, size_t l) -> bool {
5200 if (ok) {
5201 data_available = l > 0;
5202 offset += l;
5203
5204 std::string payload;
5205 if (compressor.compress(d, l, false,
5206 [&](const char *data, size_t data_len) {
5207 payload.append(s: data, n: data_len);
5208 return true;
5209 })) {
5210 if (!payload.empty()) {
5211 // Emit chunked response header and footer for each chunk
5212 auto chunk =
5213 from_i_to_hex(n: payload.size()) + "\r\n" + payload + "\r\n";
5214 if (!write_data(strm, d: chunk.data(), l: chunk.size())) { ok = false; }
5215 }
5216 } else {
5217 ok = false;
5218 }
5219 }
5220 return ok;
5221 };
5222
5223 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
5224
5225 auto done_with_trailer = [&](const Headers *trailer) {
5226 if (!ok) { return; }
5227
5228 data_available = false;
5229
5230 std::string payload;
5231 if (!compressor.compress(nullptr, 0, true,
5232 [&](const char *data, size_t data_len) {
5233 payload.append(s: data, n: data_len);
5234 return true;
5235 })) {
5236 ok = false;
5237 return;
5238 }
5239
5240 if (!payload.empty()) {
5241 // Emit chunked response header and footer for each chunk
5242 auto chunk = from_i_to_hex(n: payload.size()) + "\r\n" + payload + "\r\n";
5243 if (!write_data(strm, d: chunk.data(), l: chunk.size())) {
5244 ok = false;
5245 return;
5246 }
5247 }
5248
5249 constexpr const char done_marker[] = "0\r\n";
5250 if (!write_data(strm, d: done_marker, l: str_len(done_marker))) { ok = false; }
5251
5252 // Trailer
5253 if (trailer) {
5254 for (const auto &kv : *trailer) {
5255 std::string field_line = kv.first + ": " + kv.second + "\r\n";
5256 if (!write_data(strm, d: field_line.data(), l: field_line.size())) {
5257 ok = false;
5258 }
5259 }
5260 }
5261
5262 constexpr const char crlf[] = "\r\n";
5263 if (!write_data(strm, d: crlf, l: str_len(crlf))) { ok = false; }
5264 };
5265
5266 data_sink.done = [&](void) { done_with_trailer(nullptr); };
5267
5268 data_sink.done_with_trailer = [&](const Headers &trailer) {
5269 done_with_trailer(&trailer);
5270 };
5271
5272 while (data_available && !is_shutting_down()) {
5273 if (!strm.wait_writable()) {
5274 error = Error::Write;
5275 return false;
5276 } else if (!content_provider(offset, 0, data_sink)) {
5277 error = Error::Canceled;
5278 return false;
5279 } else if (!ok) {
5280 error = Error::Write;
5281 return false;
5282 }
5283 }
5284
5285 error = Error::Success;
5286 return true;
5287}
5288
5289template <typename T, typename U>
5290inline bool write_content_chunked(Stream &strm,
5291 const ContentProvider &content_provider,
5292 const T &is_shutting_down, U &compressor) {
5293 auto error = Error::Success;
5294 return write_content_chunked(strm, content_provider, is_shutting_down,
5295 compressor, error);
5296}
5297
5298template <typename T>
5299inline bool redirect(T &cli, Request &req, Response &res,
5300 const std::string &path, const std::string &location,
5301 Error &error) {
5302 Request new_req = req;
5303 new_req.path = path;
5304 new_req.redirect_count_ -= 1;
5305
5306 if (res.status == StatusCode::SeeOther_303 &&
5307 (req.method != "GET" && req.method != "HEAD")) {
5308 new_req.method = "GET";
5309 new_req.body.clear();
5310 new_req.headers.clear();
5311 }
5312
5313 Response new_res;
5314
5315 auto ret = cli.send(new_req, new_res, error);
5316 if (ret) {
5317 req = new_req;
5318 res = new_res;
5319
5320 if (res.location.empty()) { res.location = location; }
5321 }
5322 return ret;
5323}
5324
5325inline std::string params_to_query_str(const Params &params) {
5326 std::string query;
5327
5328 for (auto it = params.begin(); it != params.end(); ++it) {
5329 if (it != params.begin()) { query += "&"; }
5330 query += encode_query_component(component: it->first);
5331 query += "=";
5332 query += encode_query_component(component: it->second);
5333 }
5334 return query;
5335}
5336
5337inline void parse_query_text(const char *data, std::size_t size,
5338 Params &params) {
5339 std::set<std::string> cache;
5340 split(b: data, e: data + size, d: '&', fn: [&](const char *b, const char *e) {
5341 std::string kv(b, e);
5342 if (cache.find(x: kv) != cache.end()) { return; }
5343 cache.insert(x: std::move(kv));
5344
5345 std::string key;
5346 std::string val;
5347 divide(data: b, size: static_cast<std::size_t>(e - b), d: '=',
5348 fn: [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
5349 std::size_t rhs_size) {
5350 key.assign(s: lhs_data, n: lhs_size);
5351 val.assign(s: rhs_data, n: rhs_size);
5352 });
5353
5354 if (!key.empty()) {
5355 params.emplace(args: decode_query_component(component: key), args: decode_query_component(component: val));
5356 }
5357 });
5358}
5359
5360inline void parse_query_text(const std::string &s, Params &params) {
5361 parse_query_text(data: s.data(), size: s.size(), params);
5362}
5363
5364inline bool parse_multipart_boundary(const std::string &content_type,
5365 std::string &boundary) {
5366 auto boundary_keyword = "boundary=";
5367 auto pos = content_type.find(s: boundary_keyword);
5368 if (pos == std::string::npos) { return false; }
5369 auto end = content_type.find(c: ';', pos: pos);
5370 auto beg = pos + strlen(s: boundary_keyword);
5371 boundary = trim_double_quotes_copy(s: content_type.substr(pos: beg, n: end - beg));
5372 return !boundary.empty();
5373}
5374
5375inline void parse_disposition_params(const std::string &s, Params &params) {
5376 std::set<std::string> cache;
5377 split(b: s.data(), e: s.data() + s.size(), d: ';', fn: [&](const char *b, const char *e) {
5378 std::string kv(b, e);
5379 if (cache.find(x: kv) != cache.end()) { return; }
5380 cache.insert(x: kv);
5381
5382 std::string key;
5383 std::string val;
5384 split(b, e, d: '=', fn: [&](const char *b2, const char *e2) {
5385 if (key.empty()) {
5386 key.assign(first: b2, last: e2);
5387 } else {
5388 val.assign(first: b2, last: e2);
5389 }
5390 });
5391
5392 if (!key.empty()) {
5393 params.emplace(args: trim_double_quotes_copy(s: (key)),
5394 args: trim_double_quotes_copy(s: (val)));
5395 }
5396 });
5397}
5398
5399#ifdef CPPHTTPLIB_NO_EXCEPTIONS
5400inline bool parse_range_header(const std::string &s, Ranges &ranges) {
5401#else
5402inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
5403#endif
5404 auto is_valid = [](const std::string &str) {
5405 return std::all_of(first: str.cbegin(), last: str.cend(),
5406 pred: [](unsigned char c) { return std::isdigit(c); });
5407 };
5408
5409 if (s.size() > 7 && s.compare(pos: 0, n1: 6, s: "bytes=") == 0) {
5410 const auto pos = static_cast<size_t>(6);
5411 const auto len = static_cast<size_t>(s.size() - 6);
5412 auto all_valid_ranges = true;
5413 split(b: &s[pos], e: &s[pos + len], d: ',', fn: [&](const char *b, const char *e) {
5414 if (!all_valid_ranges) { return; }
5415
5416 const auto it = std::find(first: b, last: e, val: '-');
5417 if (it == e) {
5418 all_valid_ranges = false;
5419 return;
5420 }
5421
5422 const auto lhs = std::string(b, it);
5423 const auto rhs = std::string(it + 1, e);
5424 if (!is_valid(lhs) || !is_valid(rhs)) {
5425 all_valid_ranges = false;
5426 return;
5427 }
5428
5429 const auto first =
5430 static_cast<ssize_t>(lhs.empty() ? -1 : std::stoll(str: lhs));
5431 const auto last =
5432 static_cast<ssize_t>(rhs.empty() ? -1 : std::stoll(str: rhs));
5433 if ((first == -1 && last == -1) ||
5434 (first != -1 && last != -1 && first > last)) {
5435 all_valid_ranges = false;
5436 return;
5437 }
5438
5439 ranges.emplace_back(args: first, args: last);
5440 });
5441 return all_valid_ranges && !ranges.empty();
5442 }
5443 return false;
5444#ifdef CPPHTTPLIB_NO_EXCEPTIONS
5445}
5446#else
5447} catch (...) { return false; }
5448#endif
5449
5450inline bool parse_accept_header(const std::string &s,
5451 std::vector<std::string> &content_types) {
5452 content_types.clear();
5453
5454 // Empty string is considered valid (no preference)
5455 if (s.empty()) { return true; }
5456
5457 // Check for invalid patterns: leading/trailing commas or consecutive commas
5458 if (s.front() == ',' || s.back() == ',' ||
5459 s.find(s: ",,") != std::string::npos) {
5460 return false;
5461 }
5462
5463 struct AcceptEntry {
5464 std::string media_type;
5465 double quality;
5466 int order; // Original order in header
5467 };
5468
5469 std::vector<AcceptEntry> entries;
5470 int order = 0;
5471 bool has_invalid_entry = false;
5472
5473 // Split by comma and parse each entry
5474 split(b: s.data(), e: s.data() + s.size(), d: ',', fn: [&](const char *b, const char *e) {
5475 std::string entry(b, e);
5476 entry = trim_copy(s: entry);
5477
5478 if (entry.empty()) {
5479 has_invalid_entry = true;
5480 return;
5481 }
5482
5483 AcceptEntry accept_entry;
5484 accept_entry.quality = 1.0; // Default quality
5485 accept_entry.order = order++;
5486
5487 // Find q= parameter
5488 auto q_pos = entry.find(s: ";q=");
5489 if (q_pos == std::string::npos) { q_pos = entry.find(s: "; q="); }
5490
5491 if (q_pos != std::string::npos) {
5492 // Extract media type (before q parameter)
5493 accept_entry.media_type = trim_copy(s: entry.substr(pos: 0, n: q_pos));
5494
5495 // Extract quality value
5496 auto q_start = entry.find(c: '=', pos: q_pos) + 1;
5497 auto q_end = entry.find(c: ';', pos: q_start);
5498 if (q_end == std::string::npos) { q_end = entry.length(); }
5499
5500 std::string quality_str =
5501 trim_copy(s: entry.substr(pos: q_start, n: q_end - q_start));
5502 if (quality_str.empty()) {
5503 has_invalid_entry = true;
5504 return;
5505 }
5506
5507#ifdef CPPHTTPLIB_NO_EXCEPTIONS
5508 {
5509 std::istringstream iss(quality_str);
5510 iss >> accept_entry.quality;
5511
5512 // Check if conversion was successful and entire string was consumed
5513 if (iss.fail() || !iss.eof()) {
5514 has_invalid_entry = true;
5515 return;
5516 }
5517 }
5518#else
5519 try {
5520 accept_entry.quality = std::stod(str: quality_str);
5521 } catch (...) {
5522 has_invalid_entry = true;
5523 return;
5524 }
5525#endif
5526 // Check if quality is in valid range [0.0, 1.0]
5527 if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
5528 has_invalid_entry = true;
5529 return;
5530 }
5531 } else {
5532 // No quality parameter, use entire entry as media type
5533 accept_entry.media_type = entry;
5534 }
5535
5536 // Remove additional parameters from media type
5537 auto param_pos = accept_entry.media_type.find(c: ';');
5538 if (param_pos != std::string::npos) {
5539 accept_entry.media_type =
5540 trim_copy(s: accept_entry.media_type.substr(pos: 0, n: param_pos));
5541 }
5542
5543 // Basic validation of media type format
5544 if (accept_entry.media_type.empty()) {
5545 has_invalid_entry = true;
5546 return;
5547 }
5548
5549 // Check for basic media type format (should contain '/' or be '*')
5550 if (accept_entry.media_type != "*" &&
5551 accept_entry.media_type.find(c: '/') == std::string::npos) {
5552 has_invalid_entry = true;
5553 return;
5554 }
5555
5556 entries.push_back(x: accept_entry);
5557 });
5558
5559 // Return false if any invalid entry was found
5560 if (has_invalid_entry) { return false; }
5561
5562 // Sort by quality (descending), then by original order (ascending)
5563 std::sort(first: entries.begin(), last: entries.end(),
5564 comp: [](const AcceptEntry &a, const AcceptEntry &b) {
5565 if (a.quality != b.quality) {
5566 return a.quality > b.quality; // Higher quality first
5567 }
5568 return a.order < b.order; // Earlier order first for same quality
5569 });
5570
5571 // Extract sorted media types
5572 content_types.reserve(n: entries.size());
5573 for (const auto &entry : entries) {
5574 content_types.push_back(x: entry.media_type);
5575 }
5576
5577 return true;
5578}
5579
5580class FormDataParser {
5581public:
5582 FormDataParser() = default;
5583
5584 void set_boundary(std::string &&boundary) {
5585 boundary_ = boundary;
5586 dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
5587 crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
5588 }
5589
5590 bool is_valid() const { return is_valid_; }
5591
5592 bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
5593 const ContentReceiver &content_callback) {
5594
5595 buf_append(data: buf, n);
5596
5597 while (buf_size() > 0) {
5598 switch (state_) {
5599 case 0: { // Initial boundary
5600 auto pos = buf_find(s: dash_boundary_crlf_);
5601 if (pos == buf_size()) { return true; }
5602 buf_erase(size: pos + dash_boundary_crlf_.size());
5603 state_ = 1;
5604 break;
5605 }
5606 case 1: { // New entry
5607 clear_file_info();
5608 state_ = 2;
5609 break;
5610 }
5611 case 2: { // Headers
5612 auto pos = buf_find(s: crlf_);
5613 if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
5614 while (pos < buf_size()) {
5615 // Empty line
5616 if (pos == 0) {
5617 if (!header_callback(file_)) {
5618 is_valid_ = false;
5619 return false;
5620 }
5621 buf_erase(size: crlf_.size());
5622 state_ = 3;
5623 break;
5624 }
5625
5626 const auto header = buf_head(l: pos);
5627
5628 if (!parse_header(beg: header.data(), end: header.data() + header.size(),
5629 fn: [&](const std::string &, const std::string &) {})) {
5630 is_valid_ = false;
5631 return false;
5632 }
5633
5634 // Parse and emplace space trimmed headers into a map
5635 if (!parse_header(
5636 beg: header.data(), end: header.data() + header.size(),
5637 fn: [&](const std::string &key, const std::string &val) {
5638 file_.headers.emplace(args: key, args: val);
5639 })) {
5640 is_valid_ = false;
5641 return false;
5642 }
5643
5644 constexpr const char header_content_type[] = "Content-Type:";
5645
5646 if (start_with_case_ignore(a: header, b: header_content_type)) {
5647 file_.content_type =
5648 trim_copy(s: header.substr(pos: str_len(header_content_type)));
5649 } else {
5650 thread_local const std::regex re_content_disposition(
5651 R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
5652 std::regex_constants::icase);
5653
5654 std::smatch m;
5655 if (std::regex_match(s: header, m&: m, re: re_content_disposition)) {
5656 Params params;
5657 parse_disposition_params(s: m[1], params);
5658
5659 auto it = params.find(x: "name");
5660 if (it != params.end()) {
5661 file_.name = it->second;
5662 } else {
5663 is_valid_ = false;
5664 return false;
5665 }
5666
5667 it = params.find(x: "filename");
5668 if (it != params.end()) { file_.filename = it->second; }
5669
5670 it = params.find(x: "filename*");
5671 if (it != params.end()) {
5672 // Only allow UTF-8 encoding...
5673 thread_local const std::regex re_rfc5987_encoding(
5674 R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
5675
5676 std::smatch m2;
5677 if (std::regex_match(s: it->second, m&: m2, re: re_rfc5987_encoding)) {
5678 file_.filename = decode_path_component(component: m2[1]); // override...
5679 } else {
5680 is_valid_ = false;
5681 return false;
5682 }
5683 }
5684 }
5685 }
5686 buf_erase(size: pos + crlf_.size());
5687 pos = buf_find(s: crlf_);
5688 }
5689 if (state_ != 3) { return true; }
5690 break;
5691 }
5692 case 3: { // Body
5693 if (crlf_dash_boundary_.size() > buf_size()) { return true; }
5694 auto pos = buf_find(s: crlf_dash_boundary_);
5695 if (pos < buf_size()) {
5696 if (!content_callback(buf_data(), pos)) {
5697 is_valid_ = false;
5698 return false;
5699 }
5700 buf_erase(size: pos + crlf_dash_boundary_.size());
5701 state_ = 4;
5702 } else {
5703 auto len = buf_size() - crlf_dash_boundary_.size();
5704 if (len > 0) {
5705 if (!content_callback(buf_data(), len)) {
5706 is_valid_ = false;
5707 return false;
5708 }
5709 buf_erase(size: len);
5710 }
5711 return true;
5712 }
5713 break;
5714 }
5715 case 4: { // Boundary
5716 if (crlf_.size() > buf_size()) { return true; }
5717 if (buf_start_with(s: crlf_)) {
5718 buf_erase(size: crlf_.size());
5719 state_ = 1;
5720 } else {
5721 if (dash_.size() > buf_size()) { return true; }
5722 if (buf_start_with(s: dash_)) {
5723 buf_erase(size: dash_.size());
5724 is_valid_ = true;
5725 buf_erase(size: buf_size()); // Remove epilogue
5726 } else {
5727 return true;
5728 }
5729 }
5730 break;
5731 }
5732 }
5733 }
5734
5735 return true;
5736 }
5737
5738private:
5739 void clear_file_info() {
5740 file_.name.clear();
5741 file_.filename.clear();
5742 file_.content_type.clear();
5743 file_.headers.clear();
5744 }
5745
5746 bool start_with_case_ignore(const std::string &a, const char *b) const {
5747 const auto b_len = strlen(s: b);
5748 if (a.size() < b_len) { return false; }
5749 for (size_t i = 0; i < b_len; i++) {
5750 if (case_ignore::to_lower(c: a[i]) != case_ignore::to_lower(c: b[i])) {
5751 return false;
5752 }
5753 }
5754 return true;
5755 }
5756
5757 const std::string dash_ = "--";
5758 const std::string crlf_ = "\r\n";
5759 std::string boundary_;
5760 std::string dash_boundary_crlf_;
5761 std::string crlf_dash_boundary_;
5762
5763 size_t state_ = 0;
5764 bool is_valid_ = false;
5765 FormData file_;
5766
5767 // Buffer
5768 bool start_with(const std::string &a, size_t spos, size_t epos,
5769 const std::string &b) const {
5770 if (epos - spos < b.size()) { return false; }
5771 for (size_t i = 0; i < b.size(); i++) {
5772 if (a[i + spos] != b[i]) { return false; }
5773 }
5774 return true;
5775 }
5776
5777 size_t buf_size() const { return buf_epos_ - buf_spos_; }
5778
5779 const char *buf_data() const { return &buf_[buf_spos_]; }
5780
5781 std::string buf_head(size_t l) const { return buf_.substr(pos: buf_spos_, n: l); }
5782
5783 bool buf_start_with(const std::string &s) const {
5784 return start_with(a: buf_, spos: buf_spos_, epos: buf_epos_, b: s);
5785 }
5786
5787 size_t buf_find(const std::string &s) const {
5788 auto c = s.front();
5789
5790 size_t off = buf_spos_;
5791 while (off < buf_epos_) {
5792 auto pos = off;
5793 while (true) {
5794 if (pos == buf_epos_) { return buf_size(); }
5795 if (buf_[pos] == c) { break; }
5796 pos++;
5797 }
5798
5799 auto remaining_size = buf_epos_ - pos;
5800 if (s.size() > remaining_size) { return buf_size(); }
5801
5802 if (start_with(a: buf_, spos: pos, epos: buf_epos_, b: s)) { return pos - buf_spos_; }
5803
5804 off = pos + 1;
5805 }
5806
5807 return buf_size();
5808 }
5809
5810 void buf_append(const char *data, size_t n) {
5811 auto remaining_size = buf_size();
5812 if (remaining_size > 0 && buf_spos_ > 0) {
5813 for (size_t i = 0; i < remaining_size; i++) {
5814 buf_[i] = buf_[buf_spos_ + i];
5815 }
5816 }
5817 buf_spos_ = 0;
5818 buf_epos_ = remaining_size;
5819
5820 if (remaining_size + n > buf_.size()) { buf_.resize(n: remaining_size + n); }
5821
5822 for (size_t i = 0; i < n; i++) {
5823 buf_[buf_epos_ + i] = data[i];
5824 }
5825 buf_epos_ += n;
5826 }
5827
5828 void buf_erase(size_t size) { buf_spos_ += size; }
5829
5830 std::string buf_;
5831 size_t buf_spos_ = 0;
5832 size_t buf_epos_ = 0;
5833};
5834
5835inline std::string random_string(size_t length) {
5836 constexpr const char data[] =
5837 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
5838
5839 thread_local auto engine([]() {
5840 // std::random_device might actually be deterministic on some
5841 // platforms, but due to lack of support in the c++ standard library,
5842 // doing better requires either some ugly hacks or breaking portability.
5843 std::random_device seed_gen;
5844 // Request 128 bits of entropy for initialization
5845 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
5846 return std::mt19937(seed_sequence);
5847 }());
5848
5849 std::string result;
5850 for (size_t i = 0; i < length; i++) {
5851 result += data[engine() % (sizeof(data) - 1)];
5852 }
5853 return result;
5854}
5855
5856inline std::string make_multipart_data_boundary() {
5857 return "--cpp-httplib-multipart-data-" + detail::random_string(length: 16);
5858}
5859
5860inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
5861 auto valid = true;
5862 for (size_t i = 0; i < boundary.size(); i++) {
5863 auto c = boundary[i];
5864 if (!std::isalnum(c) && c != '-' && c != '_') {
5865 valid = false;
5866 break;
5867 }
5868 }
5869 return valid;
5870}
5871
5872template <typename T>
5873inline std::string
5874serialize_multipart_formdata_item_begin(const T &item,
5875 const std::string &boundary) {
5876 std::string body = "--" + boundary + "\r\n";
5877 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
5878 if (!item.filename.empty()) {
5879 body += "; filename=\"" + item.filename + "\"";
5880 }
5881 body += "\r\n";
5882 if (!item.content_type.empty()) {
5883 body += "Content-Type: " + item.content_type + "\r\n";
5884 }
5885 body += "\r\n";
5886
5887 return body;
5888}
5889
5890inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
5891
5892inline std::string
5893serialize_multipart_formdata_finish(const std::string &boundary) {
5894 return "--" + boundary + "--\r\n";
5895}
5896
5897inline std::string
5898serialize_multipart_formdata_get_content_type(const std::string &boundary) {
5899 return "multipart/form-data; boundary=" + boundary;
5900}
5901
5902inline std::string
5903serialize_multipart_formdata(const UploadFormDataItems &items,
5904 const std::string &boundary, bool finish = true) {
5905 std::string body;
5906
5907 for (const auto &item : items) {
5908 body += serialize_multipart_formdata_item_begin(item, boundary);
5909 body += item.content + serialize_multipart_formdata_item_end();
5910 }
5911
5912 if (finish) { body += serialize_multipart_formdata_finish(boundary); }
5913
5914 return body;
5915}
5916
5917inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
5918 if (ranges.size() <= 1) return;
5919
5920 // Sort ranges by start position
5921 std::sort(first: ranges.begin(), last: ranges.end(),
5922 comp: [](const Range &a, const Range &b) { return a.first < b.first; });
5923
5924 Ranges coalesced;
5925 coalesced.reserve(n: ranges.size());
5926
5927 for (auto &r : ranges) {
5928 auto first_pos = r.first;
5929 auto last_pos = r.second;
5930
5931 // Handle special cases like in range_error
5932 if (first_pos == -1 && last_pos == -1) {
5933 first_pos = 0;
5934 last_pos = static_cast<ssize_t>(content_length);
5935 }
5936
5937 if (first_pos == -1) {
5938 first_pos = static_cast<ssize_t>(content_length) - last_pos;
5939 last_pos = static_cast<ssize_t>(content_length) - 1;
5940 }
5941
5942 if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
5943 last_pos = static_cast<ssize_t>(content_length) - 1;
5944 }
5945
5946 // Skip invalid ranges
5947 if (!(0 <= first_pos && first_pos <= last_pos &&
5948 last_pos < static_cast<ssize_t>(content_length))) {
5949 continue;
5950 }
5951
5952 // Coalesce with previous range if overlapping or adjacent (but not
5953 // identical)
5954 if (!coalesced.empty()) {
5955 auto &prev = coalesced.back();
5956 // Check if current range overlaps or is adjacent to previous range
5957 // but don't coalesce identical ranges (allow duplicates)
5958 if (first_pos <= prev.second + 1 &&
5959 !(first_pos == prev.first && last_pos == prev.second)) {
5960 // Extend the previous range
5961 prev.second = (std::max)(a: prev.second, b: last_pos);
5962 continue;
5963 }
5964 }
5965
5966 // Add new range
5967 coalesced.emplace_back(args&: first_pos, args&: last_pos);
5968 }
5969
5970 ranges = std::move(coalesced);
5971}
5972
5973inline bool range_error(Request &req, Response &res) {
5974 if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
5975 ssize_t content_len = static_cast<ssize_t>(
5976 res.content_length_ ? res.content_length_ : res.body.size());
5977
5978 std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
5979 size_t overwrapping_count = 0;
5980
5981 // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
5982 // 'HTTP Semantics' to avoid potential denial-of-service attacks.
5983 // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
5984
5985 // Too many ranges
5986 if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
5987
5988 for (auto &r : req.ranges) {
5989 auto &first_pos = r.first;
5990 auto &last_pos = r.second;
5991
5992 if (first_pos == -1 && last_pos == -1) {
5993 first_pos = 0;
5994 last_pos = content_len;
5995 }
5996
5997 if (first_pos == -1) {
5998 first_pos = content_len - last_pos;
5999 last_pos = content_len - 1;
6000 }
6001
6002 // NOTE: RFC-9110 '14.1.2. Byte Ranges':
6003 // A client can limit the number of bytes requested without knowing the
6004 // size of the selected representation. If the last-pos value is absent,
6005 // or if the value is greater than or equal to the current length of the
6006 // representation data, the byte range is interpreted as the remainder of
6007 // the representation (i.e., the server replaces the value of last-pos
6008 // with a value that is one less than the current length of the selected
6009 // representation).
6010 // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
6011 if (last_pos == -1 || last_pos >= content_len) {
6012 last_pos = content_len - 1;
6013 }
6014
6015 // Range must be within content length
6016 if (!(0 <= first_pos && first_pos <= last_pos &&
6017 last_pos <= content_len - 1)) {
6018 return true;
6019 }
6020
6021 // Request must not have more than two overlapping ranges
6022 for (const auto &processed_range : processed_ranges) {
6023 if (!(last_pos < processed_range.first ||
6024 first_pos > processed_range.second)) {
6025 overwrapping_count++;
6026 if (overwrapping_count > 2) { return true; }
6027 break; // Only count once per range
6028 }
6029 }
6030
6031 processed_ranges.emplace_back(args&: first_pos, args&: last_pos);
6032 }
6033
6034 // After validation, coalesce overlapping ranges as per RFC 9110
6035 coalesce_ranges(ranges&: req.ranges, content_length: static_cast<size_t>(content_len));
6036 }
6037
6038 return false;
6039}
6040
6041inline std::pair<size_t, size_t>
6042get_range_offset_and_length(Range r, size_t content_length) {
6043 assert(r.first != -1 && r.second != -1);
6044 assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
6045 assert(r.first <= r.second &&
6046 r.second < static_cast<ssize_t>(content_length));
6047 (void)(content_length);
6048 return std::make_pair(x&: r.first, y: static_cast<size_t>(r.second - r.first) + 1);
6049}
6050
6051inline std::string make_content_range_header_field(
6052 const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
6053 auto st = offset_and_length.first;
6054 auto ed = st + offset_and_length.second - 1;
6055
6056 std::string field = "bytes ";
6057 field += std::to_string(val: st);
6058 field += "-";
6059 field += std::to_string(val: ed);
6060 field += "/";
6061 field += std::to_string(val: content_length);
6062 return field;
6063}
6064
6065template <typename SToken, typename CToken, typename Content>
6066bool process_multipart_ranges_data(const Request &req,
6067 const std::string &boundary,
6068 const std::string &content_type,
6069 size_t content_length, SToken stoken,
6070 CToken ctoken, Content content) {
6071 for (size_t i = 0; i < req.ranges.size(); i++) {
6072 ctoken("--");
6073 stoken(boundary);
6074 ctoken("\r\n");
6075 if (!content_type.empty()) {
6076 ctoken("Content-Type: ");
6077 stoken(content_type);
6078 ctoken("\r\n");
6079 }
6080
6081 auto offset_and_length =
6082 get_range_offset_and_length(r: req.ranges[i], content_length);
6083
6084 ctoken("Content-Range: ");
6085 stoken(make_content_range_header_field(offset_and_length, content_length));
6086 ctoken("\r\n");
6087 ctoken("\r\n");
6088
6089 if (!content(offset_and_length.first, offset_and_length.second)) {
6090 return false;
6091 }
6092 ctoken("\r\n");
6093 }
6094
6095 ctoken("--");
6096 stoken(boundary);
6097 ctoken("--");
6098
6099 return true;
6100}
6101
6102inline void make_multipart_ranges_data(const Request &req, Response &res,
6103 const std::string &boundary,
6104 const std::string &content_type,
6105 size_t content_length,
6106 std::string &data) {
6107 process_multipart_ranges_data(
6108 req, boundary, content_type, content_length,
6109 stoken: [&](const std::string &token) { data += token; },
6110 ctoken: [&](const std::string &token) { data += token; },
6111 content: [&](size_t offset, size_t length) {
6112 assert(offset + length <= content_length);
6113 data += res.body.substr(pos: offset, n: length);
6114 return true;
6115 });
6116}
6117
6118inline size_t get_multipart_ranges_data_length(const Request &req,
6119 const std::string &boundary,
6120 const std::string &content_type,
6121 size_t content_length) {
6122 size_t data_length = 0;
6123
6124 process_multipart_ranges_data(
6125 req, boundary, content_type, content_length,
6126 stoken: [&](const std::string &token) { data_length += token.size(); },
6127 ctoken: [&](const std::string &token) { data_length += token.size(); },
6128 content: [&](size_t /*offset*/, size_t length) {
6129 data_length += length;
6130 return true;
6131 });
6132
6133 return data_length;
6134}
6135
6136template <typename T>
6137inline bool
6138write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
6139 const std::string &boundary,
6140 const std::string &content_type,
6141 size_t content_length, const T &is_shutting_down) {
6142 return process_multipart_ranges_data(
6143 req, boundary, content_type, content_length,
6144 [&](const std::string &token) { strm.write(s: token); },
6145 [&](const std::string &token) { strm.write(s: token); },
6146 [&](size_t offset, size_t length) {
6147 return write_content(strm, res.content_provider_, offset, length,
6148 is_shutting_down);
6149 });
6150}
6151
6152inline bool expect_content(const Request &req) {
6153 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
6154 req.method == "DELETE") {
6155 return true;
6156 }
6157 if (req.has_header(key: "Content-Length") &&
6158 req.get_header_value_u64(key: "Content-Length") > 0) {
6159 return true;
6160 }
6161 if (is_chunked_transfer_encoding(headers: req.headers)) { return true; }
6162 return false;
6163}
6164
6165inline bool has_crlf(const std::string &s) {
6166 auto p = s.c_str();
6167 while (*p) {
6168 if (*p == '\r' || *p == '\n') { return true; }
6169 p++;
6170 }
6171 return false;
6172}
6173
6174#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6175inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
6176 auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
6177 EVP_MD_CTX_new(), EVP_MD_CTX_free);
6178
6179 unsigned int hash_length = 0;
6180 unsigned char hash[EVP_MAX_MD_SIZE];
6181
6182 EVP_DigestInit_ex(context.get(), algo, nullptr);
6183 EVP_DigestUpdate(context.get(), s.c_str(), s.size());
6184 EVP_DigestFinal_ex(context.get(), hash, &hash_length);
6185
6186 std::stringstream ss;
6187 for (auto i = 0u; i < hash_length; ++i) {
6188 ss << std::hex << std::setw(2) << std::setfill('0')
6189 << static_cast<unsigned int>(hash[i]);
6190 }
6191
6192 return ss.str();
6193}
6194
6195inline std::string MD5(const std::string &s) {
6196 return message_digest(s, EVP_md5());
6197}
6198
6199inline std::string SHA_256(const std::string &s) {
6200 return message_digest(s, EVP_sha256());
6201}
6202
6203inline std::string SHA_512(const std::string &s) {
6204 return message_digest(s, EVP_sha512());
6205}
6206
6207inline std::pair<std::string, std::string> make_digest_authentication_header(
6208 const Request &req, const std::map<std::string, std::string> &auth,
6209 size_t cnonce_count, const std::string &cnonce, const std::string &username,
6210 const std::string &password, bool is_proxy = false) {
6211 std::string nc;
6212 {
6213 std::stringstream ss;
6214 ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
6215 nc = ss.str();
6216 }
6217
6218 std::string qop;
6219 if (auth.find("qop") != auth.end()) {
6220 qop = auth.at("qop");
6221 if (qop.find("auth-int") != std::string::npos) {
6222 qop = "auth-int";
6223 } else if (qop.find("auth") != std::string::npos) {
6224 qop = "auth";
6225 } else {
6226 qop.clear();
6227 }
6228 }
6229
6230 std::string algo = "MD5";
6231 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
6232
6233 std::string response;
6234 {
6235 auto H = algo == "SHA-256" ? detail::SHA_256
6236 : algo == "SHA-512" ? detail::SHA_512
6237 : detail::MD5;
6238
6239 auto A1 = username + ":" + auth.at("realm") + ":" + password;
6240
6241 auto A2 = req.method + ":" + req.path;
6242 if (qop == "auth-int") { A2 += ":" + H(req.body); }
6243
6244 if (qop.empty()) {
6245 response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
6246 } else {
6247 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
6248 ":" + qop + ":" + H(A2));
6249 }
6250 }
6251
6252 auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
6253
6254 auto field = "Digest username=\"" + username + "\", realm=\"" +
6255 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
6256 "\", uri=\"" + req.path + "\", algorithm=" + algo +
6257 (qop.empty() ? ", response=\""
6258 : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
6259 cnonce + "\", response=\"") +
6260 response + "\"" +
6261 (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
6262
6263 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
6264 return std::make_pair(key, field);
6265}
6266
6267inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
6268 detail::set_nonblocking(sock, true);
6269 auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
6270
6271 char buf[1];
6272 return !SSL_peek(ssl, buf, 1) &&
6273 SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
6274}
6275
6276#ifdef _WIN32
6277// NOTE: This code came up with the following stackoverflow post:
6278// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
6279inline bool load_system_certs_on_windows(X509_STORE *store) {
6280 auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
6281 if (!hStore) { return false; }
6282
6283 auto result = false;
6284 PCCERT_CONTEXT pContext = NULL;
6285 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
6286 nullptr) {
6287 auto encoded_cert =
6288 static_cast<const unsigned char *>(pContext->pbCertEncoded);
6289
6290 auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
6291 if (x509) {
6292 X509_STORE_add_cert(store, x509);
6293 X509_free(x509);
6294 result = true;
6295 }
6296 }
6297
6298 CertFreeCertificateContext(pContext);
6299 CertCloseStore(hStore, 0);
6300
6301 return result;
6302}
6303#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
6304template <typename T>
6305using CFObjectPtr =
6306 std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
6307
6308inline void cf_object_ptr_deleter(CFTypeRef obj) {
6309 if (obj) { CFRelease(obj); }
6310}
6311
6312inline bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
6313 CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
6314 CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
6315 kCFBooleanTrue};
6316
6317 CFObjectPtr<CFDictionaryRef> query(
6318 CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
6319 sizeof(keys) / sizeof(keys[0]),
6320 &kCFTypeDictionaryKeyCallBacks,
6321 &kCFTypeDictionaryValueCallBacks),
6322 cf_object_ptr_deleter);
6323
6324 if (!query) { return false; }
6325
6326 CFTypeRef security_items = nullptr;
6327 if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
6328 CFArrayGetTypeID() != CFGetTypeID(security_items)) {
6329 return false;
6330 }
6331
6332 certs.reset(reinterpret_cast<CFArrayRef>(security_items));
6333 return true;
6334}
6335
6336inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
6337 CFArrayRef root_security_items = nullptr;
6338 if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
6339 return false;
6340 }
6341
6342 certs.reset(root_security_items);
6343 return true;
6344}
6345
6346inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
6347 auto result = false;
6348 for (auto i = 0; i < CFArrayGetCount(certs); ++i) {
6349 const auto cert = reinterpret_cast<const __SecCertificate *>(
6350 CFArrayGetValueAtIndex(certs, i));
6351
6352 if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
6353
6354 CFDataRef cert_data = nullptr;
6355 if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
6356 errSecSuccess) {
6357 continue;
6358 }
6359
6360 CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
6361
6362 auto encoded_cert = static_cast<const unsigned char *>(
6363 CFDataGetBytePtr(cert_data_ptr.get()));
6364
6365 auto x509 =
6366 d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
6367
6368 if (x509) {
6369 X509_STORE_add_cert(store, x509);
6370 X509_free(x509);
6371 result = true;
6372 }
6373 }
6374
6375 return result;
6376}
6377
6378inline bool load_system_certs_on_macos(X509_STORE *store) {
6379 auto result = false;
6380 CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
6381 if (retrieve_certs_from_keychain(certs) && certs) {
6382 result = add_certs_to_x509_store(certs.get(), store);
6383 }
6384
6385 if (retrieve_root_certs_from_keychain(certs) && certs) {
6386 result = add_certs_to_x509_store(certs.get(), store) || result;
6387 }
6388
6389 return result;
6390}
6391#endif // _WIN32
6392#endif // CPPHTTPLIB_OPENSSL_SUPPORT
6393
6394#ifdef _WIN32
6395class WSInit {
6396public:
6397 WSInit() {
6398 WSADATA wsaData;
6399 if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
6400 }
6401
6402 ~WSInit() {
6403 if (is_valid_) WSACleanup();
6404 }
6405
6406 bool is_valid_ = false;
6407};
6408
6409static WSInit wsinit_;
6410#endif
6411
6412inline bool parse_www_authenticate(const Response &res,
6413 std::map<std::string, std::string> &auth,
6414 bool is_proxy) {
6415 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
6416 if (res.has_header(key: auth_key)) {
6417 thread_local auto re =
6418 std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
6419 auto s = res.get_header_value(key: auth_key);
6420 auto pos = s.find(c: ' ');
6421 if (pos != std::string::npos) {
6422 auto type = s.substr(pos: 0, n: pos);
6423 if (type == "Basic") {
6424 return false;
6425 } else if (type == "Digest") {
6426 s = s.substr(pos: pos + 1);
6427 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
6428 for (auto i = beg; i != std::sregex_iterator(); ++i) {
6429 const auto &m = *i;
6430 auto key = s.substr(pos: static_cast<size_t>(m.position(sub: 1)),
6431 n: static_cast<size_t>(m.length(sub: 1)));
6432 auto val = m.length(sub: 2) > 0
6433 ? s.substr(pos: static_cast<size_t>(m.position(sub: 2)),
6434 n: static_cast<size_t>(m.length(sub: 2)))
6435 : s.substr(pos: static_cast<size_t>(m.position(sub: 3)),
6436 n: static_cast<size_t>(m.length(sub: 3)));
6437 auth[key] = val;
6438 }
6439 return true;
6440 }
6441 }
6442 }
6443 return false;
6444}
6445
6446class ContentProviderAdapter {
6447public:
6448 explicit ContentProviderAdapter(
6449 ContentProviderWithoutLength &&content_provider)
6450 : content_provider_(content_provider) {}
6451
6452 bool operator()(size_t offset, size_t, DataSink &sink) {
6453 return content_provider_(offset, sink);
6454 }
6455
6456private:
6457 ContentProviderWithoutLength content_provider_;
6458};
6459
6460} // namespace detail
6461
6462inline std::string hosted_at(const std::string &hostname) {
6463 std::vector<std::string> addrs;
6464 hosted_at(hostname, addrs);
6465 if (addrs.empty()) { return std::string(); }
6466 return addrs[0];
6467}
6468
6469inline void hosted_at(const std::string &hostname,
6470 std::vector<std::string> &addrs) {
6471 struct addrinfo hints;
6472 struct addrinfo *result;
6473
6474 memset(s: &hints, c: 0, n: sizeof(struct addrinfo));
6475 hints.ai_family = AF_UNSPEC;
6476 hints.ai_socktype = SOCK_STREAM;
6477 hints.ai_protocol = 0;
6478
6479 if (detail::getaddrinfo_with_timeout(node: hostname.c_str(), service: nullptr, hints: &hints,
6480 res: &result, timeout_sec: 0)) {
6481#if defined __linux__ && !defined __ANDROID__
6482 res_init();
6483#endif
6484 return;
6485 }
6486 auto se = detail::scope_exit([&] { freeaddrinfo(ai: result); });
6487
6488 for (auto rp = result; rp; rp = rp->ai_next) {
6489 const auto &addr =
6490 *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
6491 std::string ip;
6492 auto dummy = -1;
6493 if (detail::get_ip_and_port(addr, addr_len: sizeof(struct sockaddr_storage), ip,
6494 port&: dummy)) {
6495 addrs.push_back(x: ip);
6496 }
6497 }
6498}
6499
6500inline std::string encode_uri_component(const std::string &value) {
6501 std::ostringstream escaped;
6502 escaped.fill(ch: '0');
6503 escaped << std::hex;
6504
6505 for (auto c : value) {
6506 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
6507 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
6508 c == ')') {
6509 escaped << c;
6510 } else {
6511 escaped << std::uppercase;
6512 escaped << '%' << std::setw(2)
6513 << static_cast<int>(static_cast<unsigned char>(c));
6514 escaped << std::nouppercase;
6515 }
6516 }
6517
6518 return escaped.str();
6519}
6520
6521inline std::string encode_uri(const std::string &value) {
6522 std::ostringstream escaped;
6523 escaped.fill(ch: '0');
6524 escaped << std::hex;
6525
6526 for (auto c : value) {
6527 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
6528 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
6529 c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
6530 c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
6531 escaped << c;
6532 } else {
6533 escaped << std::uppercase;
6534 escaped << '%' << std::setw(2)
6535 << static_cast<int>(static_cast<unsigned char>(c));
6536 escaped << std::nouppercase;
6537 }
6538 }
6539
6540 return escaped.str();
6541}
6542
6543inline std::string decode_uri_component(const std::string &value) {
6544 std::string result;
6545
6546 for (size_t i = 0; i < value.size(); i++) {
6547 if (value[i] == '%' && i + 2 < value.size()) {
6548 auto val = 0;
6549 if (detail::from_hex_to_i(s: value, i: i + 1, cnt: 2, val)) {
6550 result += static_cast<char>(val);
6551 i += 2;
6552 } else {
6553 result += value[i];
6554 }
6555 } else {
6556 result += value[i];
6557 }
6558 }
6559
6560 return result;
6561}
6562
6563inline std::string decode_uri(const std::string &value) {
6564 std::string result;
6565
6566 for (size_t i = 0; i < value.size(); i++) {
6567 if (value[i] == '%' && i + 2 < value.size()) {
6568 auto val = 0;
6569 if (detail::from_hex_to_i(s: value, i: i + 1, cnt: 2, val)) {
6570 result += static_cast<char>(val);
6571 i += 2;
6572 } else {
6573 result += value[i];
6574 }
6575 } else {
6576 result += value[i];
6577 }
6578 }
6579
6580 return result;
6581}
6582
6583inline std::string encode_path_component(const std::string &component) {
6584 std::string result;
6585 result.reserve(res_arg: component.size() * 3);
6586
6587 for (size_t i = 0; i < component.size(); i++) {
6588 auto c = static_cast<unsigned char>(component[i]);
6589
6590 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
6591 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
6592 result += static_cast<char>(c);
6593 }
6594 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
6595 // "," / ";" / "="
6596 else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
6597 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
6598 c == '=') {
6599 result += static_cast<char>(c);
6600 }
6601 // Colon is allowed in path segments except first segment
6602 else if (c == ':') {
6603 result += static_cast<char>(c);
6604 }
6605 // @ is allowed in path
6606 else if (c == '@') {
6607 result += static_cast<char>(c);
6608 } else {
6609 result += '%';
6610 char hex[3];
6611 snprintf(s: hex, maxlen: sizeof(hex), format: "%02X", c);
6612 result.append(s: hex, n: 2);
6613 }
6614 }
6615 return result;
6616}
6617
6618inline std::string decode_path_component(const std::string &component) {
6619 std::string result;
6620 result.reserve(res_arg: component.size());
6621
6622 for (size_t i = 0; i < component.size(); i++) {
6623 if (component[i] == '%' && i + 1 < component.size()) {
6624 if (component[i + 1] == 'u') {
6625 // Unicode %uXXXX encoding
6626 auto val = 0;
6627 if (detail::from_hex_to_i(s: component, i: i + 2, cnt: 4, val)) {
6628 // 4 digits Unicode codes
6629 char buff[4];
6630 size_t len = detail::to_utf8(code: val, buff);
6631 if (len > 0) { result.append(s: buff, n: len); }
6632 i += 5; // 'u0000'
6633 } else {
6634 result += component[i];
6635 }
6636 } else {
6637 // Standard %XX encoding
6638 auto val = 0;
6639 if (detail::from_hex_to_i(s: component, i: i + 1, cnt: 2, val)) {
6640 // 2 digits hex codes
6641 result += static_cast<char>(val);
6642 i += 2; // 'XX'
6643 } else {
6644 result += component[i];
6645 }
6646 }
6647 } else {
6648 result += component[i];
6649 }
6650 }
6651 return result;
6652}
6653
6654inline std::string encode_query_component(const std::string &component,
6655 bool space_as_plus) {
6656 std::string result;
6657 result.reserve(res_arg: component.size() * 3);
6658
6659 for (size_t i = 0; i < component.size(); i++) {
6660 auto c = static_cast<unsigned char>(component[i]);
6661
6662 // Unreserved characters per RFC 3986
6663 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
6664 result += static_cast<char>(c);
6665 }
6666 // Space handling
6667 else if (c == ' ') {
6668 if (space_as_plus) {
6669 result += '+';
6670 } else {
6671 result += "%20";
6672 }
6673 }
6674 // Plus sign handling
6675 else if (c == '+') {
6676 if (space_as_plus) {
6677 result += "%2B";
6678 } else {
6679 result += static_cast<char>(c);
6680 }
6681 }
6682 // Query-safe sub-delimiters (excluding & and = which are query delimiters)
6683 else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
6684 c == '*' || c == ',' || c == ';') {
6685 result += static_cast<char>(c);
6686 }
6687 // Colon and @ are allowed in query
6688 else if (c == ':' || c == '@') {
6689 result += static_cast<char>(c);
6690 }
6691 // Forward slash is allowed in query values
6692 else if (c == '/') {
6693 result += static_cast<char>(c);
6694 }
6695 // Question mark is allowed in query values (after first ?)
6696 else if (c == '?') {
6697 result += static_cast<char>(c);
6698 } else {
6699 result += '%';
6700 char hex[3];
6701 snprintf(s: hex, maxlen: sizeof(hex), format: "%02X", c);
6702 result.append(s: hex, n: 2);
6703 }
6704 }
6705 return result;
6706}
6707
6708inline std::string decode_query_component(const std::string &component,
6709 bool plus_as_space) {
6710 std::string result;
6711 result.reserve(res_arg: component.size());
6712
6713 for (size_t i = 0; i < component.size(); i++) {
6714 if (component[i] == '%' && i + 2 < component.size()) {
6715 std::string hex = component.substr(pos: i + 1, n: 2);
6716 char *end;
6717 unsigned long value = std::strtoul(nptr: hex.c_str(), endptr: &end, base: 16);
6718 if (end == hex.c_str() + 2) {
6719 result += static_cast<char>(value);
6720 i += 2;
6721 } else {
6722 result += component[i];
6723 }
6724 } else if (component[i] == '+' && plus_as_space) {
6725 result += ' '; // + becomes space in form-urlencoded
6726 } else {
6727 result += component[i];
6728 }
6729 }
6730 return result;
6731}
6732
6733inline std::string append_query_params(const std::string &path,
6734 const Params &params) {
6735 std::string path_with_query = path;
6736 thread_local const std::regex re("[^?]+\\?.*");
6737 auto delm = std::regex_match(s: path, re: re) ? '&' : '?';
6738 path_with_query += delm + detail::params_to_query_str(params);
6739 return path_with_query;
6740}
6741
6742// Header utilities
6743inline std::pair<std::string, std::string>
6744make_range_header(const Ranges &ranges) {
6745 std::string field = "bytes=";
6746 auto i = 0;
6747 for (const auto &r : ranges) {
6748 if (i != 0) { field += ", "; }
6749 if (r.first != -1) { field += std::to_string(val: r.first); }
6750 field += '-';
6751 if (r.second != -1) { field += std::to_string(val: r.second); }
6752 i++;
6753 }
6754 return std::make_pair(x: "Range", y: std::move(field));
6755}
6756
6757inline std::pair<std::string, std::string>
6758make_basic_authentication_header(const std::string &username,
6759 const std::string &password, bool is_proxy) {
6760 auto field = "Basic " + detail::base64_encode(in: username + ":" + password);
6761 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
6762 return std::make_pair(x&: key, y: std::move(field));
6763}
6764
6765inline std::pair<std::string, std::string>
6766make_bearer_token_authentication_header(const std::string &token,
6767 bool is_proxy = false) {
6768 auto field = "Bearer " + token;
6769 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
6770 return std::make_pair(x&: key, y: std::move(field));
6771}
6772
6773// Request implementation
6774inline bool Request::has_header(const std::string &key) const {
6775 return detail::has_header(headers, key);
6776}
6777
6778inline std::string Request::get_header_value(const std::string &key,
6779 const char *def, size_t id) const {
6780 return detail::get_header_value(headers, key, def, id);
6781}
6782
6783inline size_t Request::get_header_value_count(const std::string &key) const {
6784 auto r = headers.equal_range(x: key);
6785 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6786}
6787
6788inline void Request::set_header(const std::string &key,
6789 const std::string &val) {
6790 if (detail::fields::is_field_name(s: key) &&
6791 detail::fields::is_field_value(s: val)) {
6792 headers.emplace(args: key, args: val);
6793 }
6794}
6795
6796inline bool Request::has_trailer(const std::string &key) const {
6797 return trailers.find(x: key) != trailers.end();
6798}
6799
6800inline std::string Request::get_trailer_value(const std::string &key,
6801 size_t id) const {
6802 auto rng = trailers.equal_range(x: key);
6803 auto it = rng.first;
6804 std::advance(i&: it, n: static_cast<ssize_t>(id));
6805 if (it != rng.second) { return it->second; }
6806 return std::string();
6807}
6808
6809inline size_t Request::get_trailer_value_count(const std::string &key) const {
6810 auto r = trailers.equal_range(x: key);
6811 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6812}
6813
6814inline bool Request::has_param(const std::string &key) const {
6815 return params.find(x: key) != params.end();
6816}
6817
6818inline std::string Request::get_param_value(const std::string &key,
6819 size_t id) const {
6820 auto rng = params.equal_range(x: key);
6821 auto it = rng.first;
6822 std::advance(i&: it, n: static_cast<ssize_t>(id));
6823 if (it != rng.second) { return it->second; }
6824 return std::string();
6825}
6826
6827inline size_t Request::get_param_value_count(const std::string &key) const {
6828 auto r = params.equal_range(x: key);
6829 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6830}
6831
6832inline bool Request::is_multipart_form_data() const {
6833 const auto &content_type = get_header_value(key: "Content-Type");
6834 return !content_type.rfind(s: "multipart/form-data", pos: 0);
6835}
6836
6837// Multipart FormData implementation
6838inline std::string MultipartFormData::get_field(const std::string &key,
6839 size_t id) const {
6840 auto rng = fields.equal_range(x: key);
6841 auto it = rng.first;
6842 std::advance(i&: it, n: static_cast<ssize_t>(id));
6843 if (it != rng.second) { return it->second.content; }
6844 return std::string();
6845}
6846
6847inline std::vector<std::string>
6848MultipartFormData::get_fields(const std::string &key) const {
6849 std::vector<std::string> values;
6850 auto rng = fields.equal_range(x: key);
6851 for (auto it = rng.first; it != rng.second; it++) {
6852 values.push_back(x: it->second.content);
6853 }
6854 return values;
6855}
6856
6857inline bool MultipartFormData::has_field(const std::string &key) const {
6858 return fields.find(x: key) != fields.end();
6859}
6860
6861inline size_t MultipartFormData::get_field_count(const std::string &key) const {
6862 auto r = fields.equal_range(x: key);
6863 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6864}
6865
6866inline FormData MultipartFormData::get_file(const std::string &key,
6867 size_t id) const {
6868 auto rng = files.equal_range(x: key);
6869 auto it = rng.first;
6870 std::advance(i&: it, n: static_cast<ssize_t>(id));
6871 if (it != rng.second) { return it->second; }
6872 return FormData();
6873}
6874
6875inline std::vector<FormData>
6876MultipartFormData::get_files(const std::string &key) const {
6877 std::vector<FormData> values;
6878 auto rng = files.equal_range(x: key);
6879 for (auto it = rng.first; it != rng.second; it++) {
6880 values.push_back(x: it->second);
6881 }
6882 return values;
6883}
6884
6885inline bool MultipartFormData::has_file(const std::string &key) const {
6886 return files.find(x: key) != files.end();
6887}
6888
6889inline size_t MultipartFormData::get_file_count(const std::string &key) const {
6890 auto r = files.equal_range(x: key);
6891 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6892}
6893
6894// Response implementation
6895inline bool Response::has_header(const std::string &key) const {
6896 return headers.find(x: key) != headers.end();
6897}
6898
6899inline std::string Response::get_header_value(const std::string &key,
6900 const char *def,
6901 size_t id) const {
6902 return detail::get_header_value(headers, key, def, id);
6903}
6904
6905inline size_t Response::get_header_value_count(const std::string &key) const {
6906 auto r = headers.equal_range(x: key);
6907 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6908}
6909
6910inline void Response::set_header(const std::string &key,
6911 const std::string &val) {
6912 if (detail::fields::is_field_name(s: key) &&
6913 detail::fields::is_field_value(s: val)) {
6914 headers.emplace(args: key, args: val);
6915 }
6916}
6917inline bool Response::has_trailer(const std::string &key) const {
6918 return trailers.find(x: key) != trailers.end();
6919}
6920
6921inline std::string Response::get_trailer_value(const std::string &key,
6922 size_t id) const {
6923 auto rng = trailers.equal_range(x: key);
6924 auto it = rng.first;
6925 std::advance(i&: it, n: static_cast<ssize_t>(id));
6926 if (it != rng.second) { return it->second; }
6927 return std::string();
6928}
6929
6930inline size_t Response::get_trailer_value_count(const std::string &key) const {
6931 auto r = trailers.equal_range(x: key);
6932 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
6933}
6934
6935inline void Response::set_redirect(const std::string &url, int stat) {
6936 if (detail::fields::is_field_value(s: url)) {
6937 set_header(key: "Location", val: url);
6938 if (300 <= stat && stat < 400) {
6939 this->status = stat;
6940 } else {
6941 this->status = StatusCode::Found_302;
6942 }
6943 }
6944}
6945
6946inline void Response::set_content(const char *s, size_t n,
6947 const std::string &content_type) {
6948 body.assign(s: s, n: n);
6949
6950 auto rng = headers.equal_range(x: "Content-Type");
6951 headers.erase(first: rng.first, last: rng.second);
6952 set_header(key: "Content-Type", val: content_type);
6953}
6954
6955inline void Response::set_content(const std::string &s,
6956 const std::string &content_type) {
6957 set_content(s: s.data(), n: s.size(), content_type);
6958}
6959
6960inline void Response::set_content(std::string &&s,
6961 const std::string &content_type) {
6962 body = std::move(s);
6963
6964 auto rng = headers.equal_range(x: "Content-Type");
6965 headers.erase(first: rng.first, last: rng.second);
6966 set_header(key: "Content-Type", val: content_type);
6967}
6968
6969inline void Response::set_content_provider(
6970 size_t in_length, const std::string &content_type, ContentProvider provider,
6971 ContentProviderResourceReleaser resource_releaser) {
6972 set_header(key: "Content-Type", val: content_type);
6973 content_length_ = in_length;
6974 if (in_length > 0) { content_provider_ = std::move(provider); }
6975 content_provider_resource_releaser_ = std::move(resource_releaser);
6976 is_chunked_content_provider_ = false;
6977}
6978
6979inline void Response::set_content_provider(
6980 const std::string &content_type, ContentProviderWithoutLength provider,
6981 ContentProviderResourceReleaser resource_releaser) {
6982 set_header(key: "Content-Type", val: content_type);
6983 content_length_ = 0;
6984 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
6985 content_provider_resource_releaser_ = std::move(resource_releaser);
6986 is_chunked_content_provider_ = false;
6987}
6988
6989inline void Response::set_chunked_content_provider(
6990 const std::string &content_type, ContentProviderWithoutLength provider,
6991 ContentProviderResourceReleaser resource_releaser) {
6992 set_header(key: "Content-Type", val: content_type);
6993 content_length_ = 0;
6994 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
6995 content_provider_resource_releaser_ = std::move(resource_releaser);
6996 is_chunked_content_provider_ = true;
6997}
6998
6999inline void Response::set_file_content(const std::string &path,
7000 const std::string &content_type) {
7001 file_content_path_ = path;
7002 file_content_content_type_ = content_type;
7003}
7004
7005inline void Response::set_file_content(const std::string &path) {
7006 file_content_path_ = path;
7007}
7008
7009// Result implementation
7010inline bool Result::has_request_header(const std::string &key) const {
7011 return request_headers_.find(x: key) != request_headers_.end();
7012}
7013
7014inline std::string Result::get_request_header_value(const std::string &key,
7015 const char *def,
7016 size_t id) const {
7017 return detail::get_header_value(headers: request_headers_, key, def, id);
7018}
7019
7020inline size_t
7021Result::get_request_header_value_count(const std::string &key) const {
7022 auto r = request_headers_.equal_range(x: key);
7023 return static_cast<size_t>(std::distance(first: r.first, last: r.second));
7024}
7025
7026// Stream implementation
7027inline ssize_t Stream::write(const char *ptr) {
7028 return write(ptr, size: strlen(s: ptr));
7029}
7030
7031inline ssize_t Stream::write(const std::string &s) {
7032 return write(ptr: s.data(), size: s.size());
7033}
7034
7035namespace detail {
7036
7037inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
7038 time_t timeout_sec, time_t timeout_usec,
7039 time_t &actual_timeout_sec,
7040 time_t &actual_timeout_usec) {
7041 auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
7042
7043 auto actual_timeout_msec =
7044 (std::min)(a: max_timeout_msec - duration_msec, b: timeout_msec);
7045
7046 if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
7047
7048 actual_timeout_sec = actual_timeout_msec / 1000;
7049 actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
7050}
7051
7052// Socket stream implementation
7053inline SocketStream::SocketStream(
7054 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
7055 time_t write_timeout_sec, time_t write_timeout_usec,
7056 time_t max_timeout_msec,
7057 std::chrono::time_point<std::chrono::steady_clock> start_time)
7058 : sock_(sock), read_timeout_sec_(read_timeout_sec),
7059 read_timeout_usec_(read_timeout_usec),
7060 write_timeout_sec_(write_timeout_sec),
7061 write_timeout_usec_(write_timeout_usec),
7062 max_timeout_msec_(max_timeout_msec), start_time_(start_time),
7063 read_buff_(read_buff_size_, 0) {}
7064
7065inline SocketStream::~SocketStream() = default;
7066
7067inline bool SocketStream::is_readable() const {
7068 return read_buff_off_ < read_buff_content_size_;
7069}
7070
7071inline bool SocketStream::wait_readable() const {
7072 if (max_timeout_msec_ <= 0) {
7073 return select_read(sock: sock_, sec: read_timeout_sec_, usec: read_timeout_usec_) > 0;
7074 }
7075
7076 time_t read_timeout_sec;
7077 time_t read_timeout_usec;
7078 calc_actual_timeout(max_timeout_msec: max_timeout_msec_, duration_msec: duration(), timeout_sec: read_timeout_sec_,
7079 timeout_usec: read_timeout_usec_, actual_timeout_sec&: read_timeout_sec, actual_timeout_usec&: read_timeout_usec);
7080
7081 return select_read(sock: sock_, sec: read_timeout_sec, usec: read_timeout_usec) > 0;
7082}
7083
7084inline bool SocketStream::wait_writable() const {
7085 return select_write(sock: sock_, sec: write_timeout_sec_, usec: write_timeout_usec_) > 0 &&
7086 is_socket_alive(sock: sock_);
7087}
7088
7089inline ssize_t SocketStream::read(char *ptr, size_t size) {
7090#ifdef _WIN32
7091 size =
7092 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
7093#else
7094 size = (std::min)(a: size,
7095 b: static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
7096#endif
7097
7098 if (read_buff_off_ < read_buff_content_size_) {
7099 auto remaining_size = read_buff_content_size_ - read_buff_off_;
7100 if (size <= remaining_size) {
7101 memcpy(dest: ptr, src: read_buff_.data() + read_buff_off_, n: size);
7102 read_buff_off_ += size;
7103 return static_cast<ssize_t>(size);
7104 } else {
7105 memcpy(dest: ptr, src: read_buff_.data() + read_buff_off_, n: remaining_size);
7106 read_buff_off_ += remaining_size;
7107 return static_cast<ssize_t>(remaining_size);
7108 }
7109 }
7110
7111 if (!wait_readable()) { return -1; }
7112
7113 read_buff_off_ = 0;
7114 read_buff_content_size_ = 0;
7115
7116 if (size < read_buff_size_) {
7117 auto n = read_socket(sock: sock_, ptr: read_buff_.data(), size: read_buff_size_,
7118 CPPHTTPLIB_RECV_FLAGS);
7119 if (n <= 0) {
7120 return n;
7121 } else if (n <= static_cast<ssize_t>(size)) {
7122 memcpy(dest: ptr, src: read_buff_.data(), n: static_cast<size_t>(n));
7123 return n;
7124 } else {
7125 memcpy(dest: ptr, src: read_buff_.data(), n: size);
7126 read_buff_off_ = size;
7127 read_buff_content_size_ = static_cast<size_t>(n);
7128 return static_cast<ssize_t>(size);
7129 }
7130 } else {
7131 return read_socket(sock: sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
7132 }
7133}
7134
7135inline ssize_t SocketStream::write(const char *ptr, size_t size) {
7136 if (!wait_writable()) { return -1; }
7137
7138#if defined(_WIN32) && !defined(_WIN64)
7139 size =
7140 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
7141#endif
7142
7143 return send_socket(sock: sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
7144}
7145
7146inline void SocketStream::get_remote_ip_and_port(std::string &ip,
7147 int &port) const {
7148 return detail::get_remote_ip_and_port(sock: sock_, ip, port);
7149}
7150
7151inline void SocketStream::get_local_ip_and_port(std::string &ip,
7152 int &port) const {
7153 return detail::get_local_ip_and_port(sock: sock_, ip, port);
7154}
7155
7156inline socket_t SocketStream::socket() const { return sock_; }
7157
7158inline time_t SocketStream::duration() const {
7159 return std::chrono::duration_cast<std::chrono::milliseconds>(
7160 d: std::chrono::steady_clock::now() - start_time_)
7161 .count();
7162}
7163
7164// Buffer stream implementation
7165inline bool BufferStream::is_readable() const { return true; }
7166
7167inline bool BufferStream::wait_readable() const { return true; }
7168
7169inline bool BufferStream::wait_writable() const { return true; }
7170
7171inline ssize_t BufferStream::read(char *ptr, size_t size) {
7172#if defined(_MSC_VER) && _MSC_VER < 1910
7173 auto len_read = buffer._Copy_s(ptr, size, size, position);
7174#else
7175 auto len_read = buffer.copy(s: ptr, n: size, pos: position);
7176#endif
7177 position += static_cast<size_t>(len_read);
7178 return static_cast<ssize_t>(len_read);
7179}
7180
7181inline ssize_t BufferStream::write(const char *ptr, size_t size) {
7182 buffer.append(s: ptr, n: size);
7183 return static_cast<ssize_t>(size);
7184}
7185
7186inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
7187 int & /*port*/) const {}
7188
7189inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
7190 int & /*port*/) const {}
7191
7192inline socket_t BufferStream::socket() const { return 0; }
7193
7194inline time_t BufferStream::duration() const { return 0; }
7195
7196inline const std::string &BufferStream::get_buffer() const { return buffer; }
7197
7198inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
7199 : MatcherBase(pattern) {
7200 constexpr const char marker[] = "/:";
7201
7202 // One past the last ending position of a path param substring
7203 std::size_t last_param_end = 0;
7204
7205#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7206 // Needed to ensure that parameter names are unique during matcher
7207 // construction
7208 // If exceptions are disabled, only last duplicate path
7209 // parameter will be set
7210 std::unordered_set<std::string> param_name_set;
7211#endif
7212
7213 while (true) {
7214 const auto marker_pos = pattern.find(
7215 s: marker, pos: last_param_end == 0 ? last_param_end : last_param_end - 1);
7216 if (marker_pos == std::string::npos) { break; }
7217
7218 static_fragments_.push_back(
7219 x: pattern.substr(pos: last_param_end, n: marker_pos - last_param_end + 1));
7220
7221 const auto param_name_start = marker_pos + str_len(marker);
7222
7223 auto sep_pos = pattern.find(c: separator, pos: param_name_start);
7224 if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
7225
7226 auto param_name =
7227 pattern.substr(pos: param_name_start, n: sep_pos - param_name_start);
7228
7229#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7230 if (param_name_set.find(x: param_name) != param_name_set.cend()) {
7231 std::string msg = "Encountered path parameter '" + param_name +
7232 "' multiple times in route pattern '" + pattern + "'.";
7233 throw std::invalid_argument(msg);
7234 }
7235#endif
7236
7237 param_names_.push_back(x: std::move(param_name));
7238
7239 last_param_end = sep_pos + 1;
7240 }
7241
7242 if (last_param_end < pattern.length()) {
7243 static_fragments_.push_back(x: pattern.substr(pos: last_param_end));
7244 }
7245}
7246
7247inline bool PathParamsMatcher::match(Request &request) const {
7248 request.matches = std::smatch();
7249 request.path_params.clear();
7250 request.path_params.reserve(n: param_names_.size());
7251
7252 // One past the position at which the path matched the pattern last time
7253 std::size_t starting_pos = 0;
7254 for (size_t i = 0; i < static_fragments_.size(); ++i) {
7255 const auto &fragment = static_fragments_[i];
7256
7257 if (starting_pos + fragment.length() > request.path.length()) {
7258 return false;
7259 }
7260
7261 // Avoid unnecessary allocation by using strncmp instead of substr +
7262 // comparison
7263 if (std::strncmp(s1: request.path.c_str() + starting_pos, s2: fragment.c_str(),
7264 n: fragment.length()) != 0) {
7265 return false;
7266 }
7267
7268 starting_pos += fragment.length();
7269
7270 // Should only happen when we have a static fragment after a param
7271 // Example: '/users/:id/subscriptions'
7272 // The 'subscriptions' fragment here does not have a corresponding param
7273 if (i >= param_names_.size()) { continue; }
7274
7275 auto sep_pos = request.path.find(c: separator, pos: starting_pos);
7276 if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
7277
7278 const auto &param_name = param_names_[i];
7279
7280 request.path_params.emplace(
7281 args: param_name, args: request.path.substr(pos: starting_pos, n: sep_pos - starting_pos));
7282
7283 // Mark everything up to '/' as matched
7284 starting_pos = sep_pos + 1;
7285 }
7286 // Returns false if the path is longer than the pattern
7287 return starting_pos >= request.path.length();
7288}
7289
7290inline bool RegexMatcher::match(Request &request) const {
7291 request.path_params.clear();
7292 return std::regex_match(s: request.path, m&: request.matches, re: regex_);
7293}
7294
7295inline std::string make_host_and_port_string(const std::string &host, int port,
7296 bool is_ssl) {
7297 std::string result;
7298
7299 // Enclose IPv6 address in brackets (but not if already enclosed)
7300 if (host.find(c: ':') == std::string::npos ||
7301 (!host.empty() && host[0] == '[')) {
7302 // IPv4, hostname, or already bracketed IPv6
7303 result = host;
7304 } else {
7305 // IPv6 address without brackets
7306 result = "[" + host + "]";
7307 }
7308
7309 // Append port if not default
7310 if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
7311 ; // do nothing
7312 } else {
7313 result += ":" + std::to_string(val: port);
7314 }
7315
7316 return result;
7317}
7318
7319} // namespace detail
7320
7321// HTTP server implementation
7322inline Server::Server()
7323 : new_task_queue(
7324 [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) {
7325#ifndef _WIN32
7326 signal(SIGPIPE, SIG_IGN);
7327#endif
7328}
7329
7330inline Server::~Server() = default;
7331
7332inline std::unique_ptr<detail::MatcherBase>
7333Server::make_matcher(const std::string &pattern) {
7334 if (pattern.find(s: "/:") != std::string::npos) {
7335 return detail::make_unique<detail::PathParamsMatcher>(args: pattern);
7336 } else {
7337 return detail::make_unique<detail::RegexMatcher>(args: pattern);
7338 }
7339}
7340
7341inline Server &Server::Get(const std::string &pattern, Handler handler) {
7342 get_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7343 return *this;
7344}
7345
7346inline Server &Server::Post(const std::string &pattern, Handler handler) {
7347 post_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7348 return *this;
7349}
7350
7351inline Server &Server::Post(const std::string &pattern,
7352 HandlerWithContentReader handler) {
7353 post_handlers_for_content_reader_.emplace_back(args: make_matcher(pattern),
7354 args: std::move(handler));
7355 return *this;
7356}
7357
7358inline Server &Server::Put(const std::string &pattern, Handler handler) {
7359 put_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7360 return *this;
7361}
7362
7363inline Server &Server::Put(const std::string &pattern,
7364 HandlerWithContentReader handler) {
7365 put_handlers_for_content_reader_.emplace_back(args: make_matcher(pattern),
7366 args: std::move(handler));
7367 return *this;
7368}
7369
7370inline Server &Server::Patch(const std::string &pattern, Handler handler) {
7371 patch_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7372 return *this;
7373}
7374
7375inline Server &Server::Patch(const std::string &pattern,
7376 HandlerWithContentReader handler) {
7377 patch_handlers_for_content_reader_.emplace_back(args: make_matcher(pattern),
7378 args: std::move(handler));
7379 return *this;
7380}
7381
7382inline Server &Server::Delete(const std::string &pattern, Handler handler) {
7383 delete_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7384 return *this;
7385}
7386
7387inline Server &Server::Delete(const std::string &pattern,
7388 HandlerWithContentReader handler) {
7389 delete_handlers_for_content_reader_.emplace_back(args: make_matcher(pattern),
7390 args: std::move(handler));
7391 return *this;
7392}
7393
7394inline Server &Server::Options(const std::string &pattern, Handler handler) {
7395 options_handlers_.emplace_back(args: make_matcher(pattern), args: std::move(handler));
7396 return *this;
7397}
7398
7399inline bool Server::set_base_dir(const std::string &dir,
7400 const std::string &mount_point) {
7401 return set_mount_point(mount_point, dir);
7402}
7403
7404inline bool Server::set_mount_point(const std::string &mount_point,
7405 const std::string &dir, Headers headers) {
7406 detail::FileStat stat(dir);
7407 if (stat.is_dir()) {
7408 std::string mnt = !mount_point.empty() ? mount_point : "/";
7409 if (!mnt.empty() && mnt[0] == '/') {
7410 base_dirs_.push_back(x: {.mount_point: mnt, .base_dir: dir, .headers: std::move(headers)});
7411 return true;
7412 }
7413 }
7414 return false;
7415}
7416
7417inline bool Server::remove_mount_point(const std::string &mount_point) {
7418 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
7419 if (it->mount_point == mount_point) {
7420 base_dirs_.erase(position: it);
7421 return true;
7422 }
7423 }
7424 return false;
7425}
7426
7427inline Server &
7428Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
7429 const std::string &mime) {
7430 file_extension_and_mimetype_map_[ext] = mime;
7431 return *this;
7432}
7433
7434inline Server &Server::set_default_file_mimetype(const std::string &mime) {
7435 default_file_mimetype_ = mime;
7436 return *this;
7437}
7438
7439inline Server &Server::set_file_request_handler(Handler handler) {
7440 file_request_handler_ = std::move(handler);
7441 return *this;
7442}
7443
7444inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
7445 std::true_type) {
7446 error_handler_ = std::move(handler);
7447 return *this;
7448}
7449
7450inline Server &Server::set_error_handler_core(Handler handler,
7451 std::false_type) {
7452 error_handler_ = [handler](const Request &req, Response &res) {
7453 handler(req, res);
7454 return HandlerResponse::Handled;
7455 };
7456 return *this;
7457}
7458
7459inline Server &Server::set_exception_handler(ExceptionHandler handler) {
7460 exception_handler_ = std::move(handler);
7461 return *this;
7462}
7463
7464inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
7465 pre_routing_handler_ = std::move(handler);
7466 return *this;
7467}
7468
7469inline Server &Server::set_post_routing_handler(Handler handler) {
7470 post_routing_handler_ = std::move(handler);
7471 return *this;
7472}
7473
7474inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
7475 pre_request_handler_ = std::move(handler);
7476 return *this;
7477}
7478
7479inline Server &Server::set_logger(Logger logger) {
7480 logger_ = std::move(logger);
7481 return *this;
7482}
7483
7484inline Server &Server::set_error_logger(ErrorLogger error_logger) {
7485 error_logger_ = std::move(error_logger);
7486 return *this;
7487}
7488
7489inline Server &Server::set_pre_compression_logger(Logger logger) {
7490 pre_compression_logger_ = std::move(logger);
7491 return *this;
7492}
7493
7494inline Server &
7495Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
7496 expect_100_continue_handler_ = std::move(handler);
7497 return *this;
7498}
7499
7500inline Server &Server::set_address_family(int family) {
7501 address_family_ = family;
7502 return *this;
7503}
7504
7505inline Server &Server::set_tcp_nodelay(bool on) {
7506 tcp_nodelay_ = on;
7507 return *this;
7508}
7509
7510inline Server &Server::set_ipv6_v6only(bool on) {
7511 ipv6_v6only_ = on;
7512 return *this;
7513}
7514
7515inline Server &Server::set_socket_options(SocketOptions socket_options) {
7516 socket_options_ = std::move(socket_options);
7517 return *this;
7518}
7519
7520inline Server &Server::set_default_headers(Headers headers) {
7521 default_headers_ = std::move(headers);
7522 return *this;
7523}
7524
7525inline Server &Server::set_header_writer(
7526 std::function<ssize_t(Stream &, Headers &)> const &writer) {
7527 header_writer_ = writer;
7528 return *this;
7529}
7530
7531inline Server &
7532Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
7533 trusted_proxies_ = proxies;
7534 return *this;
7535}
7536
7537inline Server &Server::set_keep_alive_max_count(size_t count) {
7538 keep_alive_max_count_ = count;
7539 return *this;
7540}
7541
7542inline Server &Server::set_keep_alive_timeout(time_t sec) {
7543 keep_alive_timeout_sec_ = sec;
7544 return *this;
7545}
7546
7547inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
7548 read_timeout_sec_ = sec;
7549 read_timeout_usec_ = usec;
7550 return *this;
7551}
7552
7553inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
7554 write_timeout_sec_ = sec;
7555 write_timeout_usec_ = usec;
7556 return *this;
7557}
7558
7559inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
7560 idle_interval_sec_ = sec;
7561 idle_interval_usec_ = usec;
7562 return *this;
7563}
7564
7565inline Server &Server::set_payload_max_length(size_t length) {
7566 payload_max_length_ = length;
7567 return *this;
7568}
7569
7570inline bool Server::bind_to_port(const std::string &host, int port,
7571 int socket_flags) {
7572 auto ret = bind_internal(host, port, socket_flags);
7573 if (ret == -1) { is_decommissioned = true; }
7574 return ret >= 0;
7575}
7576inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
7577 auto ret = bind_internal(host, port: 0, socket_flags);
7578 if (ret == -1) { is_decommissioned = true; }
7579 return ret;
7580}
7581
7582inline bool Server::listen_after_bind() { return listen_internal(); }
7583
7584inline bool Server::listen(const std::string &host, int port,
7585 int socket_flags) {
7586 return bind_to_port(host, port, socket_flags) && listen_internal();
7587}
7588
7589inline bool Server::is_running() const { return is_running_; }
7590
7591inline void Server::wait_until_ready() const {
7592 while (!is_running_ && !is_decommissioned) {
7593 std::this_thread::sleep_for(rtime: std::chrono::milliseconds{1});
7594 }
7595}
7596
7597inline void Server::stop() {
7598 if (is_running_) {
7599 assert(svr_sock_ != INVALID_SOCKET);
7600 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
7601 detail::shutdown_socket(sock);
7602 detail::close_socket(sock);
7603 }
7604 is_decommissioned = false;
7605}
7606
7607inline void Server::decommission() { is_decommissioned = true; }
7608
7609inline bool Server::parse_request_line(const char *s, Request &req) const {
7610 auto len = strlen(s: s);
7611 if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
7612 len -= 2;
7613
7614 {
7615 size_t count = 0;
7616
7617 detail::split(b: s, e: s + len, d: ' ', fn: [&](const char *b, const char *e) {
7618 switch (count) {
7619 case 0: req.method = std::string(b, e); break;
7620 case 1: req.target = std::string(b, e); break;
7621 case 2: req.version = std::string(b, e); break;
7622 default: break;
7623 }
7624 count++;
7625 });
7626
7627 if (count != 3) { return false; }
7628 }
7629
7630 thread_local const std::set<std::string> methods{
7631 "GET", "HEAD", "POST", "PUT", "DELETE",
7632 "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
7633
7634 if (methods.find(x: req.method) == methods.end()) {
7635 output_error_log(err: Error::InvalidHTTPMethod, req: &req);
7636 return false;
7637 }
7638
7639 if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
7640 output_error_log(err: Error::InvalidHTTPVersion, req: &req);
7641 return false;
7642 }
7643
7644 {
7645 // Skip URL fragment
7646 for (size_t i = 0; i < req.target.size(); i++) {
7647 if (req.target[i] == '#') {
7648 req.target.erase(pos: i);
7649 break;
7650 }
7651 }
7652
7653 detail::divide(str: req.target, d: '?',
7654 fn: [&](const char *lhs_data, std::size_t lhs_size,
7655 const char *rhs_data, std::size_t rhs_size) {
7656 req.path =
7657 decode_path_component(component: std::string(lhs_data, lhs_size));
7658 detail::parse_query_text(data: rhs_data, size: rhs_size, params&: req.params);
7659 });
7660 }
7661
7662 return true;
7663}
7664
7665inline bool Server::write_response(Stream &strm, bool close_connection,
7666 Request &req, Response &res) {
7667 // NOTE: `req.ranges` should be empty, otherwise it will be applied
7668 // incorrectly to the error content.
7669 req.ranges.clear();
7670 return write_response_core(strm, close_connection, req, res, need_apply_ranges: false);
7671}
7672
7673inline bool Server::write_response_with_content(Stream &strm,
7674 bool close_connection,
7675 const Request &req,
7676 Response &res) {
7677 return write_response_core(strm, close_connection, req, res, need_apply_ranges: true);
7678}
7679
7680inline bool Server::write_response_core(Stream &strm, bool close_connection,
7681 const Request &req, Response &res,
7682 bool need_apply_ranges) {
7683 assert(res.status != -1);
7684
7685 if (400 <= res.status && error_handler_ &&
7686 error_handler_(req, res) == HandlerResponse::Handled) {
7687 need_apply_ranges = true;
7688 }
7689
7690 std::string content_type;
7691 std::string boundary;
7692 if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
7693
7694 // Prepare additional headers
7695 if (close_connection || req.get_header_value(key: "Connection") == "close") {
7696 res.set_header(key: "Connection", val: "close");
7697 } else {
7698 std::string s = "timeout=";
7699 s += std::to_string(val: keep_alive_timeout_sec_);
7700 s += ", max=";
7701 s += std::to_string(val: keep_alive_max_count_);
7702 res.set_header(key: "Keep-Alive", val: s);
7703 }
7704
7705 if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
7706 !res.has_header(key: "Content-Type")) {
7707 res.set_header(key: "Content-Type", val: "text/plain");
7708 }
7709
7710 if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
7711 !res.has_header(key: "Content-Length")) {
7712 res.set_header(key: "Content-Length", val: "0");
7713 }
7714
7715 if (req.method == "HEAD" && !res.has_header(key: "Accept-Ranges")) {
7716 res.set_header(key: "Accept-Ranges", val: "bytes");
7717 }
7718
7719 if (post_routing_handler_) { post_routing_handler_(req, res); }
7720
7721 // Response line and headers
7722 {
7723 detail::BufferStream bstrm;
7724 if (!detail::write_response_line(strm&: bstrm, status: res.status)) { return false; }
7725 if (!header_writer_(bstrm, res.headers)) { return false; }
7726
7727 // Flush buffer
7728 auto &data = bstrm.get_buffer();
7729 detail::write_data(strm, d: data.data(), l: data.size());
7730 }
7731
7732 // Body
7733 auto ret = true;
7734 if (req.method != "HEAD") {
7735 if (!res.body.empty()) {
7736 if (!detail::write_data(strm, d: res.body.data(), l: res.body.size())) {
7737 ret = false;
7738 }
7739 } else if (res.content_provider_) {
7740 if (write_content_with_provider(strm, req, res, boundary, content_type)) {
7741 res.content_provider_success_ = true;
7742 } else {
7743 ret = false;
7744 }
7745 }
7746 }
7747
7748 // Log
7749 output_log(req, res);
7750
7751 return ret;
7752}
7753
7754inline bool
7755Server::write_content_with_provider(Stream &strm, const Request &req,
7756 Response &res, const std::string &boundary,
7757 const std::string &content_type) {
7758 auto is_shutting_down = [this]() {
7759 return this->svr_sock_ == INVALID_SOCKET;
7760 };
7761
7762 if (res.content_length_ > 0) {
7763 if (req.ranges.empty()) {
7764 return detail::write_content(strm, content_provider: res.content_provider_, offset: 0,
7765 length: res.content_length_, is_shutting_down);
7766 } else if (req.ranges.size() == 1) {
7767 auto offset_and_length = detail::get_range_offset_and_length(
7768 r: req.ranges[0], content_length: res.content_length_);
7769
7770 return detail::write_content(strm, content_provider: res.content_provider_,
7771 offset: offset_and_length.first,
7772 length: offset_and_length.second, is_shutting_down);
7773 } else {
7774 return detail::write_multipart_ranges_data(
7775 strm, req, res, boundary, content_type, content_length: res.content_length_,
7776 is_shutting_down);
7777 }
7778 } else {
7779 if (res.is_chunked_content_provider_) {
7780 auto type = detail::encoding_type(req, res);
7781
7782 std::unique_ptr<detail::compressor> compressor;
7783 if (type == detail::EncodingType::Gzip) {
7784#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7785 compressor = detail::make_unique<detail::gzip_compressor>();
7786#endif
7787 } else if (type == detail::EncodingType::Brotli) {
7788#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7789 compressor = detail::make_unique<detail::brotli_compressor>();
7790#endif
7791 } else if (type == detail::EncodingType::Zstd) {
7792#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7793 compressor = detail::make_unique<detail::zstd_compressor>();
7794#endif
7795 } else {
7796 compressor = detail::make_unique<detail::nocompressor>();
7797 }
7798 assert(compressor != nullptr);
7799
7800 return detail::write_content_chunked(strm, content_provider: res.content_provider_,
7801 is_shutting_down, compressor&: *compressor);
7802 } else {
7803 return detail::write_content_without_length(strm, content_provider: res.content_provider_,
7804 is_shutting_down);
7805 }
7806 }
7807}
7808
7809inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
7810 FormFields::iterator cur_field;
7811 FormFiles::iterator cur_file;
7812 auto is_text_field = false;
7813 size_t count = 0;
7814 if (read_content_core(
7815 strm, req, res,
7816 // Regular
7817 receiver: [&](const char *buf, size_t n) {
7818 if (req.body.size() + n > req.body.max_size()) { return false; }
7819 req.body.append(s: buf, n: n);
7820 return true;
7821 },
7822 // Multipart FormData
7823 multipart_header: [&](const FormData &file) {
7824 if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
7825 output_error_log(err: Error::TooManyFormDataFiles, req: &req);
7826 return false;
7827 }
7828
7829 if (file.filename.empty()) {
7830 cur_field = req.form.fields.emplace(
7831 args: file.name, args: FormField{.name: file.name, .content: file.content, .headers: file.headers});
7832 is_text_field = true;
7833 } else {
7834 cur_file = req.form.files.emplace(args: file.name, args: file);
7835 is_text_field = false;
7836 }
7837 return true;
7838 },
7839 multipart_receiver: [&](const char *buf, size_t n) {
7840 if (is_text_field) {
7841 auto &content = cur_field->second.content;
7842 if (content.size() + n > content.max_size()) { return false; }
7843 content.append(s: buf, n: n);
7844 } else {
7845 auto &content = cur_file->second.content;
7846 if (content.size() + n > content.max_size()) { return false; }
7847 content.append(s: buf, n: n);
7848 }
7849 return true;
7850 })) {
7851 const auto &content_type = req.get_header_value(key: "Content-Type");
7852 if (!content_type.find(s: "application/x-www-form-urlencoded")) {
7853 if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
7854 res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
7855 output_error_log(err: Error::ExceedMaxPayloadSize, req: &req);
7856 return false;
7857 }
7858 detail::parse_query_text(s: req.body, params&: req.params);
7859 }
7860 return true;
7861 }
7862 return false;
7863}
7864
7865inline bool Server::read_content_with_content_receiver(
7866 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
7867 FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
7868 return read_content_core(strm, req, res, receiver: std::move(receiver),
7869 multipart_header: std::move(multipart_header),
7870 multipart_receiver: std::move(multipart_receiver));
7871}
7872
7873inline bool Server::read_content_core(
7874 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
7875 FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
7876 detail::FormDataParser multipart_form_data_parser;
7877 ContentReceiverWithProgress out;
7878
7879 if (req.is_multipart_form_data()) {
7880 const auto &content_type = req.get_header_value(key: "Content-Type");
7881 std::string boundary;
7882 if (!detail::parse_multipart_boundary(content_type, boundary)) {
7883 res.status = StatusCode::BadRequest_400;
7884 output_error_log(err: Error::MultipartParsing, req: &req);
7885 return false;
7886 }
7887
7888 multipart_form_data_parser.set_boundary(std::move(boundary));
7889 out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
7890 return multipart_form_data_parser.parse(buf, n, header_callback: multipart_header,
7891 content_callback: multipart_receiver);
7892 };
7893 } else {
7894 out = [receiver](const char *buf, size_t n, size_t /*off*/,
7895 size_t /*len*/) { return receiver(buf, n); };
7896 }
7897
7898 if (req.method == "DELETE" && !req.has_header(key: "Content-Length")) {
7899 return true;
7900 }
7901
7902 if (!detail::read_content(strm, x&: req, payload_max_length: payload_max_length_, status&: res.status, progress: nullptr,
7903 receiver: out, decompress: true)) {
7904 return false;
7905 }
7906
7907 if (req.is_multipart_form_data()) {
7908 if (!multipart_form_data_parser.is_valid()) {
7909 res.status = StatusCode::BadRequest_400;
7910 output_error_log(err: Error::MultipartParsing, req: &req);
7911 return false;
7912 }
7913 }
7914
7915 return true;
7916}
7917
7918inline bool Server::handle_file_request(const Request &req, Response &res) {
7919 for (const auto &entry : base_dirs_) {
7920 // Prefix match
7921 if (!req.path.compare(pos: 0, n: entry.mount_point.size(), str: entry.mount_point)) {
7922 std::string sub_path = "/" + req.path.substr(pos: entry.mount_point.size());
7923 if (detail::is_valid_path(path: sub_path)) {
7924 auto path = entry.base_dir + sub_path;
7925 if (path.back() == '/') { path += "index.html"; }
7926
7927 detail::FileStat stat(path);
7928
7929 if (stat.is_dir()) {
7930 res.set_redirect(url: sub_path + "/", stat: StatusCode::MovedPermanently_301);
7931 return true;
7932 }
7933
7934 if (stat.is_file()) {
7935 for (const auto &kv : entry.headers) {
7936 res.set_header(key: kv.first, val: kv.second);
7937 }
7938
7939 auto mm = std::make_shared<detail::mmap>(args: path.c_str());
7940 if (!mm->is_open()) {
7941 output_error_log(err: Error::OpenFile, req: &req);
7942 return false;
7943 }
7944
7945 res.set_content_provider(
7946 in_length: mm->size(),
7947 content_type: detail::find_content_type(path, user_data: file_extension_and_mimetype_map_,
7948 default_content_type: default_file_mimetype_),
7949 provider: [mm](size_t offset, size_t length, DataSink &sink) -> bool {
7950 sink.write(mm->data() + offset, length);
7951 return true;
7952 });
7953
7954 if (req.method != "HEAD" && file_request_handler_) {
7955 file_request_handler_(req, res);
7956 }
7957
7958 return true;
7959 } else {
7960 output_error_log(err: Error::OpenFile, req: &req);
7961 }
7962 }
7963 }
7964 }
7965 return false;
7966}
7967
7968inline socket_t
7969Server::create_server_socket(const std::string &host, int port,
7970 int socket_flags,
7971 SocketOptions socket_options) const {
7972 return detail::create_socket(
7973 host, ip: std::string(), port, address_family: address_family_, socket_flags, tcp_nodelay: tcp_nodelay_,
7974 ipv6_v6only: ipv6_v6only_, socket_options: std::move(socket_options),
7975 bind_or_connect: [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
7976 if (::bind(fd: sock, addr: ai.ai_addr, len: static_cast<socklen_t>(ai.ai_addrlen))) {
7977 output_error_log(err: Error::BindIPAddress, req: nullptr);
7978 return false;
7979 }
7980 if (::listen(fd: sock, CPPHTTPLIB_LISTEN_BACKLOG)) {
7981 output_error_log(err: Error::Listen, req: nullptr);
7982 return false;
7983 }
7984 return true;
7985 });
7986}
7987
7988inline int Server::bind_internal(const std::string &host, int port,
7989 int socket_flags) {
7990 if (is_decommissioned) { return -1; }
7991
7992 if (!is_valid()) { return -1; }
7993
7994 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options: socket_options_);
7995 if (svr_sock_ == INVALID_SOCKET) { return -1; }
7996
7997 if (port == 0) {
7998 struct sockaddr_storage addr;
7999 socklen_t addr_len = sizeof(addr);
8000 if (getsockname(fd: svr_sock_, addr: reinterpret_cast<struct sockaddr *>(&addr),
8001 len: &addr_len) == -1) {
8002 output_error_log(err: Error::GetSockName, req: nullptr);
8003 return -1;
8004 }
8005 if (addr.ss_family == AF_INET) {
8006 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
8007 } else if (addr.ss_family == AF_INET6) {
8008 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
8009 } else {
8010 output_error_log(err: Error::UnsupportedAddressFamily, req: nullptr);
8011 return -1;
8012 }
8013 } else {
8014 return port;
8015 }
8016}
8017
8018inline bool Server::listen_internal() {
8019 if (is_decommissioned) { return false; }
8020
8021 auto ret = true;
8022 is_running_ = true;
8023 auto se = detail::scope_exit([&]() { is_running_ = false; });
8024
8025 {
8026 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
8027
8028 while (svr_sock_ != INVALID_SOCKET) {
8029#ifndef _WIN32
8030 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
8031#endif
8032 auto val = detail::select_read(sock: svr_sock_, sec: idle_interval_sec_,
8033 usec: idle_interval_usec_);
8034 if (val == 0) { // Timeout
8035 task_queue->on_idle();
8036 continue;
8037 }
8038#ifndef _WIN32
8039 }
8040#endif
8041
8042#if defined _WIN32
8043 // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
8044 // OVERLAPPED
8045 socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
8046#elif defined SOCK_CLOEXEC
8047 socket_t sock = accept4(fd: svr_sock_, addr: nullptr, addr_len: nullptr, SOCK_CLOEXEC);
8048#else
8049 socket_t sock = accept(svr_sock_, nullptr, nullptr);
8050#endif
8051
8052 if (sock == INVALID_SOCKET) {
8053 if (errno == EMFILE) {
8054 // The per-process limit of open file descriptors has been reached.
8055 // Try to accept new connections after a short sleep.
8056 std::this_thread::sleep_for(rtime: std::chrono::microseconds{1});
8057 continue;
8058 } else if (errno == EINTR || errno == EAGAIN) {
8059 continue;
8060 }
8061 if (svr_sock_ != INVALID_SOCKET) {
8062 detail::close_socket(sock: svr_sock_);
8063 ret = false;
8064 output_error_log(err: Error::Connection, req: nullptr);
8065 } else {
8066 ; // The server socket was closed by user.
8067 }
8068 break;
8069 }
8070
8071 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
8072 sec: read_timeout_sec_, usec: read_timeout_usec_);
8073 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
8074 sec: write_timeout_sec_, usec: write_timeout_usec_);
8075
8076 if (!task_queue->enqueue(
8077 fn: [this, sock]() { process_and_close_socket(sock); })) {
8078 output_error_log(err: Error::ResourceExhaustion, req: nullptr);
8079 detail::shutdown_socket(sock);
8080 detail::close_socket(sock);
8081 }
8082 }
8083
8084 task_queue->shutdown();
8085 }
8086
8087 is_decommissioned = !ret;
8088 return ret;
8089}
8090
8091inline bool Server::routing(Request &req, Response &res, Stream &strm) {
8092 if (pre_routing_handler_ &&
8093 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
8094 return true;
8095 }
8096
8097 // File handler
8098 if ((req.method == "GET" || req.method == "HEAD") &&
8099 handle_file_request(req, res)) {
8100 return true;
8101 }
8102
8103 if (detail::expect_content(req)) {
8104 // Content reader handler
8105 {
8106 ContentReader reader(
8107 [&](ContentReceiver receiver) {
8108 auto result = read_content_with_content_receiver(
8109 strm, req, res, receiver: std::move(receiver), multipart_header: nullptr, multipart_receiver: nullptr);
8110 if (!result) { output_error_log(err: Error::Read, req: &req); }
8111 return result;
8112 },
8113 [&](FormDataHeader header, ContentReceiver receiver) {
8114 auto result = read_content_with_content_receiver(
8115 strm, req, res, receiver: nullptr, multipart_header: std::move(header),
8116 multipart_receiver: std::move(receiver));
8117 if (!result) { output_error_log(err: Error::Read, req: &req); }
8118 return result;
8119 });
8120
8121 if (req.method == "POST") {
8122 if (dispatch_request_for_content_reader(
8123 req, res, content_reader: std::move(reader),
8124 handlers: post_handlers_for_content_reader_)) {
8125 return true;
8126 }
8127 } else if (req.method == "PUT") {
8128 if (dispatch_request_for_content_reader(
8129 req, res, content_reader: std::move(reader),
8130 handlers: put_handlers_for_content_reader_)) {
8131 return true;
8132 }
8133 } else if (req.method == "PATCH") {
8134 if (dispatch_request_for_content_reader(
8135 req, res, content_reader: std::move(reader),
8136 handlers: patch_handlers_for_content_reader_)) {
8137 return true;
8138 }
8139 } else if (req.method == "DELETE") {
8140 if (dispatch_request_for_content_reader(
8141 req, res, content_reader: std::move(reader),
8142 handlers: delete_handlers_for_content_reader_)) {
8143 return true;
8144 }
8145 }
8146 }
8147
8148 // Read content into `req.body`
8149 if (!read_content(strm, req, res)) {
8150 output_error_log(err: Error::Read, req: &req);
8151 return false;
8152 }
8153 }
8154
8155 // Regular handler
8156 if (req.method == "GET" || req.method == "HEAD") {
8157 return dispatch_request(req, res, handlers: get_handlers_);
8158 } else if (req.method == "POST") {
8159 return dispatch_request(req, res, handlers: post_handlers_);
8160 } else if (req.method == "PUT") {
8161 return dispatch_request(req, res, handlers: put_handlers_);
8162 } else if (req.method == "DELETE") {
8163 return dispatch_request(req, res, handlers: delete_handlers_);
8164 } else if (req.method == "OPTIONS") {
8165 return dispatch_request(req, res, handlers: options_handlers_);
8166 } else if (req.method == "PATCH") {
8167 return dispatch_request(req, res, handlers: patch_handlers_);
8168 }
8169
8170 res.status = StatusCode::BadRequest_400;
8171 return false;
8172}
8173
8174inline bool Server::dispatch_request(Request &req, Response &res,
8175 const Handlers &handlers) const {
8176 for (const auto &x : handlers) {
8177 const auto &matcher = x.first;
8178 const auto &handler = x.second;
8179
8180 if (matcher->match(request&: req)) {
8181 req.matched_route = matcher->pattern();
8182 if (!pre_request_handler_ ||
8183 pre_request_handler_(req, res) != HandlerResponse::Handled) {
8184 handler(req, res);
8185 }
8186 return true;
8187 }
8188 }
8189 return false;
8190}
8191
8192inline void Server::apply_ranges(const Request &req, Response &res,
8193 std::string &content_type,
8194 std::string &boundary) const {
8195 if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
8196 auto it = res.headers.find(x: "Content-Type");
8197 if (it != res.headers.end()) {
8198 content_type = it->second;
8199 res.headers.erase(position: it);
8200 }
8201
8202 boundary = detail::make_multipart_data_boundary();
8203
8204 res.set_header(key: "Content-Type",
8205 val: "multipart/byteranges; boundary=" + boundary);
8206 }
8207
8208 auto type = detail::encoding_type(req, res);
8209
8210 if (res.body.empty()) {
8211 if (res.content_length_ > 0) {
8212 size_t length = 0;
8213 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
8214 length = res.content_length_;
8215 } else if (req.ranges.size() == 1) {
8216 auto offset_and_length = detail::get_range_offset_and_length(
8217 r: req.ranges[0], content_length: res.content_length_);
8218
8219 length = offset_and_length.second;
8220
8221 auto content_range = detail::make_content_range_header_field(
8222 offset_and_length, content_length: res.content_length_);
8223 res.set_header(key: "Content-Range", val: content_range);
8224 } else {
8225 length = detail::get_multipart_ranges_data_length(
8226 req, boundary, content_type, content_length: res.content_length_);
8227 }
8228 res.set_header(key: "Content-Length", val: std::to_string(val: length));
8229 } else {
8230 if (res.content_provider_) {
8231 if (res.is_chunked_content_provider_) {
8232 res.set_header(key: "Transfer-Encoding", val: "chunked");
8233 if (type == detail::EncodingType::Gzip) {
8234 res.set_header(key: "Content-Encoding", val: "gzip");
8235 } else if (type == detail::EncodingType::Brotli) {
8236 res.set_header(key: "Content-Encoding", val: "br");
8237 } else if (type == detail::EncodingType::Zstd) {
8238 res.set_header(key: "Content-Encoding", val: "zstd");
8239 }
8240 }
8241 }
8242 }
8243 } else {
8244 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
8245 ;
8246 } else if (req.ranges.size() == 1) {
8247 auto offset_and_length =
8248 detail::get_range_offset_and_length(r: req.ranges[0], content_length: res.body.size());
8249 auto offset = offset_and_length.first;
8250 auto length = offset_and_length.second;
8251
8252 auto content_range = detail::make_content_range_header_field(
8253 offset_and_length, content_length: res.body.size());
8254 res.set_header(key: "Content-Range", val: content_range);
8255
8256 assert(offset + length <= res.body.size());
8257 res.body = res.body.substr(pos: offset, n: length);
8258 } else {
8259 std::string data;
8260 detail::make_multipart_ranges_data(req, res, boundary, content_type,
8261 content_length: res.body.size(), data);
8262 res.body.swap(s&: data);
8263 }
8264
8265 if (type != detail::EncodingType::None) {
8266 output_pre_compression_log(req, res);
8267
8268 std::unique_ptr<detail::compressor> compressor;
8269 std::string content_encoding;
8270
8271 if (type == detail::EncodingType::Gzip) {
8272#ifdef CPPHTTPLIB_ZLIB_SUPPORT
8273 compressor = detail::make_unique<detail::gzip_compressor>();
8274 content_encoding = "gzip";
8275#endif
8276 } else if (type == detail::EncodingType::Brotli) {
8277#ifdef CPPHTTPLIB_BROTLI_SUPPORT
8278 compressor = detail::make_unique<detail::brotli_compressor>();
8279 content_encoding = "br";
8280#endif
8281 } else if (type == detail::EncodingType::Zstd) {
8282#ifdef CPPHTTPLIB_ZSTD_SUPPORT
8283 compressor = detail::make_unique<detail::zstd_compressor>();
8284 content_encoding = "zstd";
8285#endif
8286 }
8287
8288 if (compressor) {
8289 std::string compressed;
8290 if (compressor->compress(data: res.body.data(), data_length: res.body.size(), last: true,
8291 callback: [&](const char *data, size_t data_len) {
8292 compressed.append(s: data, n: data_len);
8293 return true;
8294 })) {
8295 res.body.swap(s&: compressed);
8296 res.set_header(key: "Content-Encoding", val: content_encoding);
8297 }
8298 }
8299 }
8300
8301 auto length = std::to_string(val: res.body.size());
8302 res.set_header(key: "Content-Length", val: length);
8303 }
8304}
8305
8306inline bool Server::dispatch_request_for_content_reader(
8307 Request &req, Response &res, ContentReader content_reader,
8308 const HandlersForContentReader &handlers) const {
8309 for (const auto &x : handlers) {
8310 const auto &matcher = x.first;
8311 const auto &handler = x.second;
8312
8313 if (matcher->match(request&: req)) {
8314 req.matched_route = matcher->pattern();
8315 if (!pre_request_handler_ ||
8316 pre_request_handler_(req, res) != HandlerResponse::Handled) {
8317 handler(req, res, content_reader);
8318 }
8319 return true;
8320 }
8321 }
8322 return false;
8323}
8324
8325inline std::string
8326get_client_ip(const std::string &x_forwarded_for,
8327 const std::vector<std::string> &trusted_proxies) {
8328 // X-Forwarded-For is a comma-separated list per RFC 7239
8329 std::vector<std::string> ip_list;
8330 detail::split(b: x_forwarded_for.data(),
8331 e: x_forwarded_for.data() + x_forwarded_for.size(), d: ',',
8332 fn: [&](const char *b, const char *e) {
8333 auto r = detail::trim(b, e, left: 0, right: static_cast<size_t>(e - b));
8334 ip_list.emplace_back(args: std::string(b + r.first, b + r.second));
8335 });
8336
8337 for (size_t i = 0; i < ip_list.size(); ++i) {
8338 auto ip = ip_list[i];
8339
8340 auto is_trusted_proxy =
8341 std::any_of(first: trusted_proxies.begin(), last: trusted_proxies.end(),
8342 pred: [&](const std::string &proxy) { return ip == proxy; });
8343
8344 if (is_trusted_proxy) {
8345 if (i == 0) {
8346 // If the trusted proxy is the first IP, there's no preceding client IP
8347 return ip;
8348 } else {
8349 // Return the IP immediately before the trusted proxy
8350 return ip_list[i - 1];
8351 }
8352 }
8353 }
8354
8355 // If no trusted proxy is found, return the first IP in the list
8356 return ip_list.front();
8357}
8358
8359inline bool
8360Server::process_request(Stream &strm, const std::string &remote_addr,
8361 int remote_port, const std::string &local_addr,
8362 int local_port, bool close_connection,
8363 bool &connection_closed,
8364 const std::function<void(Request &)> &setup_request) {
8365 std::array<char, 2048> buf{};
8366
8367 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
8368
8369 // Connection has been closed on client
8370 if (!line_reader.getline()) { return false; }
8371
8372 Request req;
8373 req.start_time_ = std::chrono::steady_clock::now();
8374
8375 Response res;
8376 res.version = "HTTP/1.1";
8377 res.headers = default_headers_;
8378
8379#ifdef __APPLE__
8380 // Socket file descriptor exceeded FD_SETSIZE...
8381 if (strm.socket() >= FD_SETSIZE) {
8382 Headers dummy;
8383 detail::read_headers(strm, dummy);
8384 res.status = StatusCode::InternalServerError_500;
8385 output_error_log(Error::ExceedMaxSocketDescriptorCount, &req);
8386 return write_response(strm, close_connection, req, res);
8387 }
8388#endif
8389
8390 // Request line and headers
8391 if (!parse_request_line(s: line_reader.ptr(), req)) {
8392 res.status = StatusCode::BadRequest_400;
8393 output_error_log(err: Error::InvalidRequestLine, req: &req);
8394 return write_response(strm, close_connection, req, res);
8395 }
8396
8397 // Request headers
8398 if (!detail::read_headers(strm, headers&: req.headers)) {
8399 res.status = StatusCode::BadRequest_400;
8400 output_error_log(err: Error::InvalidHeaders, req: &req);
8401 return write_response(strm, close_connection, req, res);
8402 }
8403
8404 // Check if the request URI doesn't exceed the limit
8405 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
8406 Headers dummy;
8407 detail::read_headers(strm, headers&: dummy);
8408 res.status = StatusCode::UriTooLong_414;
8409 output_error_log(err: Error::ExceedUriMaxLength, req: &req);
8410 return write_response(strm, close_connection, req, res);
8411 }
8412
8413 if (req.get_header_value(key: "Connection") == "close") {
8414 connection_closed = true;
8415 }
8416
8417 if (req.version == "HTTP/1.0" &&
8418 req.get_header_value(key: "Connection") != "Keep-Alive") {
8419 connection_closed = true;
8420 }
8421
8422 if (!trusted_proxies_.empty() && req.has_header(key: "X-Forwarded-For")) {
8423 auto x_forwarded_for = req.get_header_value(key: "X-Forwarded-For");
8424 req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies: trusted_proxies_);
8425 } else {
8426 req.remote_addr = remote_addr;
8427 }
8428 req.remote_port = remote_port;
8429
8430 req.local_addr = local_addr;
8431 req.local_port = local_port;
8432
8433 if (req.has_header(key: "Accept")) {
8434 const auto &accept_header = req.get_header_value(key: "Accept");
8435 if (!detail::parse_accept_header(s: accept_header, content_types&: req.accept_content_types)) {
8436 res.status = StatusCode::BadRequest_400;
8437 output_error_log(err: Error::HTTPParsing, req: &req);
8438 return write_response(strm, close_connection, req, res);
8439 }
8440 }
8441
8442 if (req.has_header(key: "Range")) {
8443 const auto &range_header_value = req.get_header_value(key: "Range");
8444 if (!detail::parse_range_header(s: range_header_value, ranges&: req.ranges)) {
8445 res.status = StatusCode::RangeNotSatisfiable_416;
8446 output_error_log(err: Error::InvalidRangeHeader, req: &req);
8447 return write_response(strm, close_connection, req, res);
8448 }
8449 }
8450
8451 if (setup_request) { setup_request(req); }
8452
8453 if (req.get_header_value(key: "Expect") == "100-continue") {
8454 int status = StatusCode::Continue_100;
8455 if (expect_100_continue_handler_) {
8456 status = expect_100_continue_handler_(req, res);
8457 }
8458 switch (status) {
8459 case StatusCode::Continue_100:
8460 case StatusCode::ExpectationFailed_417:
8461 detail::write_response_line(strm, status);
8462 strm.write(ptr: "\r\n");
8463 break;
8464 default:
8465 connection_closed = true;
8466 return write_response(strm, close_connection: true, req, res);
8467 }
8468 }
8469
8470 // Setup `is_connection_closed` method
8471 auto sock = strm.socket();
8472 req.is_connection_closed = [sock]() {
8473 return !detail::is_socket_alive(sock);
8474 };
8475
8476 // Routing
8477 auto routed = false;
8478#ifdef CPPHTTPLIB_NO_EXCEPTIONS
8479 routed = routing(req, res, strm);
8480#else
8481 try {
8482 routed = routing(req, res, strm);
8483 } catch (std::exception &e) {
8484 if (exception_handler_) {
8485 auto ep = std::current_exception();
8486 exception_handler_(req, res, ep);
8487 routed = true;
8488 } else {
8489 res.status = StatusCode::InternalServerError_500;
8490 std::string val;
8491 auto s = e.what();
8492 for (size_t i = 0; s[i]; i++) {
8493 switch (s[i]) {
8494 case '\r': val += "\\r"; break;
8495 case '\n': val += "\\n"; break;
8496 default: val += s[i]; break;
8497 }
8498 }
8499 res.set_header(key: "EXCEPTION_WHAT", val);
8500 }
8501 } catch (...) {
8502 if (exception_handler_) {
8503 auto ep = std::current_exception();
8504 exception_handler_(req, res, ep);
8505 routed = true;
8506 } else {
8507 res.status = StatusCode::InternalServerError_500;
8508 res.set_header(key: "EXCEPTION_WHAT", val: "UNKNOWN");
8509 }
8510 }
8511#endif
8512 if (routed) {
8513 if (res.status == -1) {
8514 res.status = req.ranges.empty() ? StatusCode::OK_200
8515 : StatusCode::PartialContent_206;
8516 }
8517
8518 // Serve file content by using a content provider
8519 if (!res.file_content_path_.empty()) {
8520 const auto &path = res.file_content_path_;
8521 auto mm = std::make_shared<detail::mmap>(args: path.c_str());
8522 if (!mm->is_open()) {
8523 res.body.clear();
8524 res.content_length_ = 0;
8525 res.content_provider_ = nullptr;
8526 res.status = StatusCode::NotFound_404;
8527 output_error_log(err: Error::OpenFile, req: &req);
8528 return write_response(strm, close_connection, req, res);
8529 }
8530
8531 auto content_type = res.file_content_content_type_;
8532 if (content_type.empty()) {
8533 content_type = detail::find_content_type(
8534 path, user_data: file_extension_and_mimetype_map_, default_content_type: default_file_mimetype_);
8535 }
8536
8537 res.set_content_provider(
8538 in_length: mm->size(), content_type,
8539 provider: [mm](size_t offset, size_t length, DataSink &sink) -> bool {
8540 sink.write(mm->data() + offset, length);
8541 return true;
8542 });
8543 }
8544
8545 if (detail::range_error(req, res)) {
8546 res.body.clear();
8547 res.content_length_ = 0;
8548 res.content_provider_ = nullptr;
8549 res.status = StatusCode::RangeNotSatisfiable_416;
8550 return write_response(strm, close_connection, req, res);
8551 }
8552
8553 return write_response_with_content(strm, close_connection, req, res);
8554 } else {
8555 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
8556
8557 return write_response(strm, close_connection, req, res);
8558 }
8559}
8560
8561inline bool Server::is_valid() const { return true; }
8562
8563inline bool Server::process_and_close_socket(socket_t sock) {
8564 std::string remote_addr;
8565 int remote_port = 0;
8566 detail::get_remote_ip_and_port(sock, ip&: remote_addr, port&: remote_port);
8567
8568 std::string local_addr;
8569 int local_port = 0;
8570 detail::get_local_ip_and_port(sock, ip&: local_addr, port&: local_port);
8571
8572 auto ret = detail::process_server_socket(
8573 svr_sock: svr_sock_, sock, keep_alive_max_count: keep_alive_max_count_, keep_alive_timeout_sec: keep_alive_timeout_sec_,
8574 read_timeout_sec: read_timeout_sec_, read_timeout_usec: read_timeout_usec_, write_timeout_sec: write_timeout_sec_,
8575 write_timeout_usec: write_timeout_usec_,
8576 callback: [&](Stream &strm, bool close_connection, bool &connection_closed) {
8577 return process_request(strm, remote_addr, remote_port, local_addr,
8578 local_port, close_connection, connection_closed,
8579 setup_request: nullptr);
8580 });
8581
8582 detail::shutdown_socket(sock);
8583 detail::close_socket(sock);
8584 return ret;
8585}
8586
8587inline void Server::output_log(const Request &req, const Response &res) const {
8588 if (logger_) {
8589 std::lock_guard<std::mutex> guard(logger_mutex_);
8590 logger_(req, res);
8591 }
8592}
8593
8594inline void Server::output_pre_compression_log(const Request &req,
8595 const Response &res) const {
8596 if (pre_compression_logger_) {
8597 std::lock_guard<std::mutex> guard(logger_mutex_);
8598 pre_compression_logger_(req, res);
8599 }
8600}
8601
8602inline void Server::output_error_log(const Error &err,
8603 const Request *req) const {
8604 if (error_logger_) {
8605 std::lock_guard<std::mutex> guard(logger_mutex_);
8606 error_logger_(err, req);
8607 }
8608}
8609
8610// HTTP client implementation
8611inline ClientImpl::ClientImpl(const std::string &host)
8612 : ClientImpl(host, 80, std::string(), std::string()) {}
8613
8614inline ClientImpl::ClientImpl(const std::string &host, int port)
8615 : ClientImpl(host, port, std::string(), std::string()) {}
8616
8617inline ClientImpl::ClientImpl(const std::string &host, int port,
8618 const std::string &client_cert_path,
8619 const std::string &client_key_path)
8620 : host_(detail::escape_abstract_namespace_unix_domain(s: host)), port_(port),
8621 host_and_port_(detail::make_host_and_port_string(host: host_, port, is_ssl: is_ssl())),
8622 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
8623
8624inline ClientImpl::~ClientImpl() {
8625 // Wait until all the requests in flight are handled.
8626 size_t retry_count = 10;
8627 while (retry_count-- > 0) {
8628 {
8629 std::lock_guard<std::mutex> guard(socket_mutex_);
8630 if (socket_requests_in_flight_ == 0) { break; }
8631 }
8632 std::this_thread::sleep_for(rtime: std::chrono::milliseconds{1});
8633 }
8634
8635 std::lock_guard<std::mutex> guard(socket_mutex_);
8636 shutdown_socket(socket&: socket_);
8637 close_socket(socket&: socket_);
8638}
8639
8640inline bool ClientImpl::is_valid() const { return true; }
8641
8642inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
8643 client_cert_path_ = rhs.client_cert_path_;
8644 client_key_path_ = rhs.client_key_path_;
8645 connection_timeout_sec_ = rhs.connection_timeout_sec_;
8646 read_timeout_sec_ = rhs.read_timeout_sec_;
8647 read_timeout_usec_ = rhs.read_timeout_usec_;
8648 write_timeout_sec_ = rhs.write_timeout_sec_;
8649 write_timeout_usec_ = rhs.write_timeout_usec_;
8650 max_timeout_msec_ = rhs.max_timeout_msec_;
8651 basic_auth_username_ = rhs.basic_auth_username_;
8652 basic_auth_password_ = rhs.basic_auth_password_;
8653 bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
8654#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8655 digest_auth_username_ = rhs.digest_auth_username_;
8656 digest_auth_password_ = rhs.digest_auth_password_;
8657#endif
8658 keep_alive_ = rhs.keep_alive_;
8659 follow_location_ = rhs.follow_location_;
8660 path_encode_ = rhs.path_encode_;
8661 address_family_ = rhs.address_family_;
8662 tcp_nodelay_ = rhs.tcp_nodelay_;
8663 ipv6_v6only_ = rhs.ipv6_v6only_;
8664 socket_options_ = rhs.socket_options_;
8665 compress_ = rhs.compress_;
8666 decompress_ = rhs.decompress_;
8667 interface_ = rhs.interface_;
8668 proxy_host_ = rhs.proxy_host_;
8669 proxy_port_ = rhs.proxy_port_;
8670 proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
8671 proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
8672 proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
8673#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8674 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
8675 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
8676#endif
8677#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8678 ca_cert_file_path_ = rhs.ca_cert_file_path_;
8679 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
8680 ca_cert_store_ = rhs.ca_cert_store_;
8681#endif
8682#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8683 server_certificate_verification_ = rhs.server_certificate_verification_;
8684 server_hostname_verification_ = rhs.server_hostname_verification_;
8685 server_certificate_verifier_ = rhs.server_certificate_verifier_;
8686#endif
8687 logger_ = rhs.logger_;
8688 error_logger_ = rhs.error_logger_;
8689}
8690
8691inline socket_t ClientImpl::create_client_socket(Error &error) const {
8692 if (!proxy_host_.empty() && proxy_port_ != -1) {
8693 return detail::create_client_socket(
8694 host: proxy_host_, ip: std::string(), port: proxy_port_, address_family: address_family_, tcp_nodelay: tcp_nodelay_,
8695 ipv6_v6only: ipv6_v6only_, socket_options: socket_options_, connection_timeout_sec: connection_timeout_sec_,
8696 connection_timeout_usec: connection_timeout_usec_, read_timeout_sec: read_timeout_sec_, read_timeout_usec: read_timeout_usec_,
8697 write_timeout_sec: write_timeout_sec_, write_timeout_usec: write_timeout_usec_, intf: interface_, error);
8698 }
8699
8700 // Check is custom IP specified for host_
8701 std::string ip;
8702 auto it = addr_map_.find(x: host_);
8703 if (it != addr_map_.end()) { ip = it->second; }
8704
8705 return detail::create_client_socket(
8706 host: host_, ip, port: port_, address_family: address_family_, tcp_nodelay: tcp_nodelay_, ipv6_v6only: ipv6_v6only_,
8707 socket_options: socket_options_, connection_timeout_sec: connection_timeout_sec_, connection_timeout_usec: connection_timeout_usec_,
8708 read_timeout_sec: read_timeout_sec_, read_timeout_usec: read_timeout_usec_, write_timeout_sec: write_timeout_sec_,
8709 write_timeout_usec: write_timeout_usec_, intf: interface_, error);
8710}
8711
8712inline bool ClientImpl::create_and_connect_socket(Socket &socket,
8713 Error &error) {
8714 auto sock = create_client_socket(error);
8715 if (sock == INVALID_SOCKET) { return false; }
8716 socket.sock = sock;
8717 return true;
8718}
8719
8720inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
8721 bool /*shutdown_gracefully*/) {
8722 // If there are any requests in flight from threads other than us, then it's
8723 // a thread-unsafe race because individual ssl* objects are not thread-safe.
8724 assert(socket_requests_in_flight_ == 0 ||
8725 socket_requests_are_from_thread_ == std::this_thread::get_id());
8726}
8727
8728inline void ClientImpl::shutdown_socket(Socket &socket) const {
8729 if (socket.sock == INVALID_SOCKET) { return; }
8730 detail::shutdown_socket(sock: socket.sock);
8731}
8732
8733inline void ClientImpl::close_socket(Socket &socket) {
8734 // If there are requests in flight in another thread, usually closing
8735 // the socket will be fine and they will simply receive an error when
8736 // using the closed socket, but it is still a bug since rarely the OS
8737 // may reassign the socket id to be used for a new socket, and then
8738 // suddenly they will be operating on a live socket that is different
8739 // than the one they intended!
8740 assert(socket_requests_in_flight_ == 0 ||
8741 socket_requests_are_from_thread_ == std::this_thread::get_id());
8742
8743 // It is also a bug if this happens while SSL is still active
8744#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8745 assert(socket.ssl == nullptr);
8746#endif
8747 if (socket.sock == INVALID_SOCKET) { return; }
8748 detail::close_socket(sock: socket.sock);
8749 socket.sock = INVALID_SOCKET;
8750}
8751
8752inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
8753 Response &res) const {
8754 std::array<char, 2048> buf{};
8755
8756 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
8757
8758 if (!line_reader.getline()) { return false; }
8759
8760#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
8761 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
8762#else
8763 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
8764#endif
8765
8766 std::cmatch m;
8767 if (!std::regex_match(s: line_reader.ptr(), m&: m, re: re)) {
8768 return req.method == "CONNECT";
8769 }
8770 res.version = std::string(m[1]);
8771 res.status = std::stoi(str: std::string(m[2]));
8772 res.reason = std::string(m[3]);
8773
8774 // Ignore '100 Continue'
8775 while (res.status == StatusCode::Continue_100) {
8776 if (!line_reader.getline()) { return false; } // CRLF
8777 if (!line_reader.getline()) { return false; } // next response line
8778
8779 if (!std::regex_match(s: line_reader.ptr(), m&: m, re: re)) { return false; }
8780 res.version = std::string(m[1]);
8781 res.status = std::stoi(str: std::string(m[2]));
8782 res.reason = std::string(m[3]);
8783 }
8784
8785 return true;
8786}
8787
8788inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
8789 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
8790 auto ret = send_(req, res, error);
8791 if (error == Error::SSLPeerCouldBeClosed_) {
8792 assert(!ret);
8793 ret = send_(req, res, error);
8794 }
8795 return ret;
8796}
8797
8798inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
8799 {
8800 std::lock_guard<std::mutex> guard(socket_mutex_);
8801
8802 // Set this to false immediately - if it ever gets set to true by the end
8803 // of the request, we know another thread instructed us to close the
8804 // socket.
8805 socket_should_be_closed_when_request_is_done_ = false;
8806
8807 auto is_alive = false;
8808 if (socket_.is_open()) {
8809 is_alive = detail::is_socket_alive(sock: socket_.sock);
8810
8811#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8812 if (is_alive && is_ssl()) {
8813 if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
8814 is_alive = false;
8815 }
8816 }
8817#endif
8818
8819 if (!is_alive) {
8820 // Attempt to avoid sigpipe by shutting down non-gracefully if it
8821 // seems like the other side has already closed the connection Also,
8822 // there cannot be any requests in flight from other threads since we
8823 // locked request_mutex_, so safe to close everything immediately
8824 const bool shutdown_gracefully = false;
8825 shutdown_ssl(socket_, shutdown_gracefully);
8826 shutdown_socket(socket&: socket_);
8827 close_socket(socket&: socket_);
8828 }
8829 }
8830
8831 if (!is_alive) {
8832 if (!create_and_connect_socket(socket&: socket_, error)) {
8833 output_error_log(err: error, req: &req);
8834 return false;
8835 }
8836
8837#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8838 // TODO: refactoring
8839 if (is_ssl()) {
8840 auto &scli = static_cast<SSLClient &>(*this);
8841 if (!proxy_host_.empty() && proxy_port_ != -1) {
8842 auto success = false;
8843 if (!scli.connect_with_proxy(socket_, req.start_time_, res, success,
8844 error)) {
8845 if (!success) { output_error_log(error, &req); }
8846 return success;
8847 }
8848 }
8849
8850 if (!scli.initialize_ssl(socket_, error)) {
8851 output_error_log(error, &req);
8852 return false;
8853 }
8854 }
8855#endif
8856 }
8857
8858 // Mark the current socket as being in use so that it cannot be closed by
8859 // anyone else while this request is ongoing, even though we will be
8860 // releasing the mutex.
8861 if (socket_requests_in_flight_ > 1) {
8862 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
8863 }
8864 socket_requests_in_flight_ += 1;
8865 socket_requests_are_from_thread_ = std::this_thread::get_id();
8866 }
8867
8868 for (const auto &header : default_headers_) {
8869 if (req.headers.find(x: header.first) == req.headers.end()) {
8870 req.headers.insert(x: header);
8871 }
8872 }
8873
8874 auto ret = false;
8875 auto close_connection = !keep_alive_;
8876
8877 auto se = detail::scope_exit([&]() {
8878 // Briefly lock mutex in order to mark that a request is no longer ongoing
8879 std::lock_guard<std::mutex> guard(socket_mutex_);
8880 socket_requests_in_flight_ -= 1;
8881 if (socket_requests_in_flight_ <= 0) {
8882 assert(socket_requests_in_flight_ == 0);
8883 socket_requests_are_from_thread_ = std::thread::id();
8884 }
8885
8886 if (socket_should_be_closed_when_request_is_done_ || close_connection ||
8887 !ret) {
8888 shutdown_ssl(socket_, true);
8889 shutdown_socket(socket&: socket_);
8890 close_socket(socket&: socket_);
8891 }
8892 });
8893
8894 ret = process_socket(socket: socket_, start_time: req.start_time_, callback: [&](Stream &strm) {
8895 return handle_request(strm, req, res, close_connection, error);
8896 });
8897
8898 if (!ret) {
8899 if (error == Error::Success) {
8900 error = Error::Unknown;
8901 output_error_log(err: error, req: &req);
8902 }
8903 }
8904
8905 return ret;
8906}
8907
8908inline Result ClientImpl::send(const Request &req) {
8909 auto req2 = req;
8910 return send_(req: std::move(req2));
8911}
8912
8913inline Result ClientImpl::send_(Request &&req) {
8914 auto res = detail::make_unique<Response>();
8915 auto error = Error::Success;
8916 auto ret = send(req, res&: *res, error);
8917#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8918 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
8919 last_ssl_error_, last_openssl_error_};
8920#else
8921 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
8922#endif
8923}
8924
8925inline bool ClientImpl::handle_request(Stream &strm, Request &req,
8926 Response &res, bool close_connection,
8927 Error &error) {
8928 if (req.path.empty()) {
8929 error = Error::Connection;
8930 output_error_log(err: error, req: &req);
8931 return false;
8932 }
8933
8934 auto req_save = req;
8935
8936 bool ret;
8937
8938 if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
8939 auto req2 = req;
8940 req2.path = "http://" + host_and_port_ + req.path;
8941 ret = process_request(strm, req&: req2, res, close_connection, error);
8942 req = req2;
8943 req.path = req_save.path;
8944 } else {
8945 ret = process_request(strm, req, res, close_connection, error);
8946 }
8947
8948 if (!ret) { return false; }
8949
8950 if (res.get_header_value(key: "Connection") == "close" ||
8951 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
8952 // TODO this requires a not-entirely-obvious chain of calls to be correct
8953 // for this to be safe.
8954
8955 // This is safe to call because handle_request is only called by send_
8956 // which locks the request mutex during the process. It would be a bug
8957 // to call it from a different thread since it's a thread-safety issue
8958 // to do these things to the socket if another thread is using the socket.
8959 std::lock_guard<std::mutex> guard(socket_mutex_);
8960 shutdown_ssl(socket_, true);
8961 shutdown_socket(socket&: socket_);
8962 close_socket(socket&: socket_);
8963 }
8964
8965 if (300 < res.status && res.status < 400 && follow_location_) {
8966 req = req_save;
8967 ret = redirect(req, res, error);
8968 }
8969
8970#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8971 if ((res.status == StatusCode::Unauthorized_401 ||
8972 res.status == StatusCode::ProxyAuthenticationRequired_407) &&
8973 req.authorization_count_ < 5) {
8974 auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
8975 const auto &username =
8976 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
8977 const auto &password =
8978 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
8979
8980 if (!username.empty() && !password.empty()) {
8981 std::map<std::string, std::string> auth;
8982 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
8983 Request new_req = req;
8984 new_req.authorization_count_ += 1;
8985 new_req.headers.erase(is_proxy ? "Proxy-Authorization"
8986 : "Authorization");
8987 new_req.headers.insert(detail::make_digest_authentication_header(
8988 req, auth, new_req.authorization_count_, detail::random_string(10),
8989 username, password, is_proxy));
8990
8991 Response new_res;
8992
8993 ret = send(new_req, new_res, error);
8994 if (ret) { res = new_res; }
8995 }
8996 }
8997 }
8998#endif
8999
9000 return ret;
9001}
9002
9003inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
9004 if (req.redirect_count_ == 0) {
9005 error = Error::ExceedRedirectCount;
9006 output_error_log(err: error, req: &req);
9007 return false;
9008 }
9009
9010 auto location = res.get_header_value(key: "location");
9011 if (location.empty()) { return false; }
9012
9013 thread_local const std::regex re(
9014 R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
9015
9016 std::smatch m;
9017 if (!std::regex_match(s: location, m&: m, re: re)) { return false; }
9018
9019 auto scheme = is_ssl() ? "https" : "http";
9020
9021 auto next_scheme = m[1].str();
9022 auto next_host = m[2].str();
9023 if (next_host.empty()) { next_host = m[3].str(); }
9024 auto port_str = m[4].str();
9025 auto next_path = m[5].str();
9026 auto next_query = m[6].str();
9027
9028 auto next_port = port_;
9029 if (!port_str.empty()) {
9030 next_port = std::stoi(str: port_str);
9031 } else if (!next_scheme.empty()) {
9032 next_port = next_scheme == "https" ? 443 : 80;
9033 }
9034
9035 if (next_scheme.empty()) { next_scheme = scheme; }
9036 if (next_host.empty()) { next_host = host_; }
9037 if (next_path.empty()) { next_path = "/"; }
9038
9039 auto path = decode_query_component(component: next_path, plus_as_space: true) + next_query;
9040
9041 // Same host redirect - use current client
9042 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
9043 return detail::redirect(cli&: *this, req, res, path, location, error);
9044 }
9045
9046 // Cross-host/scheme redirect - create new client with robust setup
9047 return create_redirect_client(scheme: next_scheme, host: next_host, port: next_port, req, res,
9048 path, location, error);
9049}
9050
9051// New method for robust redirect client creation
9052inline bool ClientImpl::create_redirect_client(
9053 const std::string &scheme, const std::string &host, int port, Request &req,
9054 Response &res, const std::string &path, const std::string &location,
9055 Error &error) {
9056 // Determine if we need SSL
9057 auto need_ssl = (scheme == "https");
9058
9059 // Clean up request headers that are host/client specific
9060 // Remove headers that should not be carried over to new host
9061 auto headers_to_remove =
9062 std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
9063
9064 for (const auto &header_name : headers_to_remove) {
9065 auto it = req.headers.find(x: header_name);
9066 while (it != req.headers.end()) {
9067 it = req.headers.erase(position: it);
9068 it = req.headers.find(x: header_name);
9069 }
9070 }
9071
9072 // Create appropriate client type and handle redirect
9073 if (need_ssl) {
9074#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9075 // Create SSL client for HTTPS redirect
9076 SSLClient redirect_client(host, port);
9077
9078 // Setup basic client configuration first
9079 setup_redirect_client(redirect_client);
9080
9081 // SSL-specific configuration for proxy environments
9082 if (!proxy_host_.empty() && proxy_port_ != -1) {
9083 // Critical: Disable SSL verification for proxy environments
9084 redirect_client.enable_server_certificate_verification(false);
9085 redirect_client.enable_server_hostname_verification(false);
9086 } else {
9087 // For direct SSL connections, copy SSL verification settings
9088 redirect_client.enable_server_certificate_verification(
9089 server_certificate_verification_);
9090 redirect_client.enable_server_hostname_verification(
9091 server_hostname_verification_);
9092 }
9093
9094 // Handle CA certificate store and paths if available
9095 if (ca_cert_store_ && X509_STORE_up_ref(ca_cert_store_)) {
9096 redirect_client.set_ca_cert_store(ca_cert_store_);
9097 }
9098 if (!ca_cert_file_path_.empty()) {
9099 redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
9100 }
9101
9102 // Client certificates are set through constructor for SSLClient
9103 // NOTE: SSLClient constructor already takes client_cert_path and
9104 // client_key_path so we need to create it properly if client certs are
9105 // needed
9106
9107 // Execute the redirect
9108 return detail::redirect(redirect_client, req, res, path, location, error);
9109#else
9110 // SSL not supported - set appropriate error
9111 error = Error::SSLConnection;
9112 output_error_log(err: error, req: &req);
9113 return false;
9114#endif
9115 } else {
9116 // HTTP redirect
9117 ClientImpl redirect_client(host, port);
9118
9119 // Setup client with robust configuration
9120 setup_redirect_client(redirect_client);
9121
9122 // Execute the redirect
9123 return detail::redirect(cli&: redirect_client, req, res, path, location, error);
9124 }
9125}
9126
9127// New method for robust client setup (based on basic_manual_redirect.cpp
9128// logic)
9129template <typename ClientType>
9130inline void ClientImpl::setup_redirect_client(ClientType &client) {
9131 // Copy basic settings first
9132 client.set_connection_timeout(connection_timeout_sec_);
9133 client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
9134 client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
9135 client.set_keep_alive(keep_alive_);
9136 client.set_follow_location(
9137 true); // Enable redirects to handle multi-step redirects
9138 client.set_path_encode(path_encode_);
9139 client.set_compress(compress_);
9140 client.set_decompress(decompress_);
9141
9142 // Copy authentication settings BEFORE proxy setup
9143 if (!basic_auth_username_.empty()) {
9144 client.set_basic_auth(basic_auth_username_, basic_auth_password_);
9145 }
9146 if (!bearer_token_auth_token_.empty()) {
9147 client.set_bearer_token_auth(bearer_token_auth_token_);
9148 }
9149#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9150 if (!digest_auth_username_.empty()) {
9151 client.set_digest_auth(digest_auth_username_, digest_auth_password_);
9152 }
9153#endif
9154
9155 // Setup proxy configuration (CRITICAL ORDER - proxy must be set
9156 // before proxy auth)
9157 if (!proxy_host_.empty() && proxy_port_ != -1) {
9158 // First set proxy host and port
9159 client.set_proxy(proxy_host_, proxy_port_);
9160
9161 // Then set proxy authentication (order matters!)
9162 if (!proxy_basic_auth_username_.empty()) {
9163 client.set_proxy_basic_auth(proxy_basic_auth_username_,
9164 proxy_basic_auth_password_);
9165 }
9166 if (!proxy_bearer_token_auth_token_.empty()) {
9167 client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
9168 }
9169#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9170 if (!proxy_digest_auth_username_.empty()) {
9171 client.set_proxy_digest_auth(proxy_digest_auth_username_,
9172 proxy_digest_auth_password_);
9173 }
9174#endif
9175 }
9176
9177 // Copy network and socket settings
9178 client.set_address_family(address_family_);
9179 client.set_tcp_nodelay(tcp_nodelay_);
9180 client.set_ipv6_v6only(ipv6_v6only_);
9181 if (socket_options_) { client.set_socket_options(socket_options_); }
9182 if (!interface_.empty()) { client.set_interface(interface_); }
9183
9184 // Copy logging and headers
9185 if (logger_) { client.set_logger(logger_); }
9186 if (error_logger_) { client.set_error_logger(error_logger_); }
9187
9188 // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
9189 // Each new client should generate its own headers based on its target host
9190}
9191
9192inline bool ClientImpl::write_content_with_provider(Stream &strm,
9193 const Request &req,
9194 Error &error) const {
9195 auto is_shutting_down = []() { return false; };
9196
9197 if (req.is_chunked_content_provider_) {
9198 // TODO: Brotli support
9199 std::unique_ptr<detail::compressor> compressor;
9200#ifdef CPPHTTPLIB_ZLIB_SUPPORT
9201 if (compress_) {
9202 compressor = detail::make_unique<detail::gzip_compressor>();
9203 } else
9204#endif
9205 {
9206 compressor = detail::make_unique<detail::nocompressor>();
9207 }
9208
9209 return detail::write_content_chunked(strm, content_provider: req.content_provider_,
9210 is_shutting_down, compressor&: *compressor, error);
9211 } else {
9212 return detail::write_content_with_progress(
9213 strm, content_provider: req.content_provider_, offset: 0, length: req.content_length_, is_shutting_down,
9214 upload_progress: req.upload_progress, error);
9215 }
9216}
9217
9218inline bool ClientImpl::write_request(Stream &strm, Request &req,
9219 bool close_connection, Error &error) {
9220 // Prepare additional headers
9221 if (close_connection) {
9222 if (!req.has_header(key: "Connection")) {
9223 req.set_header(key: "Connection", val: "close");
9224 }
9225 }
9226
9227 if (!req.has_header(key: "Host")) {
9228 // For Unix socket connections, use "localhost" as Host header (similar to
9229 // curl behavior)
9230 if (address_family_ == AF_UNIX) {
9231 req.set_header(key: "Host", val: "localhost");
9232 } else {
9233 req.set_header(key: "Host", val: host_and_port_);
9234 }
9235 }
9236
9237 if (!req.has_header(key: "Accept")) { req.set_header(key: "Accept", val: "*/*"); }
9238
9239 if (!req.content_receiver) {
9240 if (!req.has_header(key: "Accept-Encoding")) {
9241 std::string accept_encoding;
9242#ifdef CPPHTTPLIB_BROTLI_SUPPORT
9243 accept_encoding = "br";
9244#endif
9245#ifdef CPPHTTPLIB_ZLIB_SUPPORT
9246 if (!accept_encoding.empty()) { accept_encoding += ", "; }
9247 accept_encoding += "gzip, deflate";
9248#endif
9249#ifdef CPPHTTPLIB_ZSTD_SUPPORT
9250 if (!accept_encoding.empty()) { accept_encoding += ", "; }
9251 accept_encoding += "zstd";
9252#endif
9253 req.set_header(key: "Accept-Encoding", val: accept_encoding);
9254 }
9255
9256#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
9257 if (!req.has_header(key: "User-Agent")) {
9258 auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
9259 req.set_header(key: "User-Agent", val: agent);
9260 }
9261#endif
9262 };
9263
9264 if (req.body.empty()) {
9265 if (req.content_provider_) {
9266 if (!req.is_chunked_content_provider_) {
9267 if (!req.has_header(key: "Content-Length")) {
9268 auto length = std::to_string(val: req.content_length_);
9269 req.set_header(key: "Content-Length", val: length);
9270 }
9271 }
9272 } else {
9273 if (req.method == "POST" || req.method == "PUT" ||
9274 req.method == "PATCH") {
9275 req.set_header(key: "Content-Length", val: "0");
9276 }
9277 }
9278 } else {
9279 if (!req.has_header(key: "Content-Type")) {
9280 req.set_header(key: "Content-Type", val: "text/plain");
9281 }
9282
9283 if (!req.has_header(key: "Content-Length")) {
9284 auto length = std::to_string(val: req.body.size());
9285 req.set_header(key: "Content-Length", val: length);
9286 }
9287 }
9288
9289 if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
9290 if (!req.has_header(key: "Authorization")) {
9291 req.headers.insert(x: make_basic_authentication_header(
9292 username: basic_auth_username_, password: basic_auth_password_, is_proxy: false));
9293 }
9294 }
9295
9296 if (!proxy_basic_auth_username_.empty() &&
9297 !proxy_basic_auth_password_.empty()) {
9298 if (!req.has_header(key: "Proxy-Authorization")) {
9299 req.headers.insert(x: make_basic_authentication_header(
9300 username: proxy_basic_auth_username_, password: proxy_basic_auth_password_, is_proxy: true));
9301 }
9302 }
9303
9304 if (!bearer_token_auth_token_.empty()) {
9305 if (!req.has_header(key: "Authorization")) {
9306 req.headers.insert(x: make_bearer_token_authentication_header(
9307 token: bearer_token_auth_token_, is_proxy: false));
9308 }
9309 }
9310
9311 if (!proxy_bearer_token_auth_token_.empty()) {
9312 if (!req.has_header(key: "Proxy-Authorization")) {
9313 req.headers.insert(x: make_bearer_token_authentication_header(
9314 token: proxy_bearer_token_auth_token_, is_proxy: true));
9315 }
9316 }
9317
9318 // Request line and headers
9319 {
9320 detail::BufferStream bstrm;
9321
9322 // Extract path and query from req.path
9323 std::string path_part, query_part;
9324 auto query_pos = req.path.find(c: '?');
9325 if (query_pos != std::string::npos) {
9326 path_part = req.path.substr(pos: 0, n: query_pos);
9327 query_part = req.path.substr(pos: query_pos + 1);
9328 } else {
9329 path_part = req.path;
9330 query_part = "";
9331 }
9332
9333 // Encode path and query
9334 auto path_with_query =
9335 path_encode_ ? detail::encode_path(s: path_part) : path_part;
9336
9337 detail::parse_query_text(s: query_part, params&: req.params);
9338 if (!req.params.empty()) {
9339 path_with_query = append_query_params(path: path_with_query, params: req.params);
9340 }
9341
9342 // Write request line and headers
9343 detail::write_request_line(strm&: bstrm, method: req.method, path: path_with_query);
9344 header_writer_(bstrm, req.headers);
9345
9346 // Flush buffer
9347 auto &data = bstrm.get_buffer();
9348 if (!detail::write_data(strm, d: data.data(), l: data.size())) {
9349 error = Error::Write;
9350 output_error_log(err: error, req: &req);
9351 return false;
9352 }
9353 }
9354
9355 // Body
9356 if (req.body.empty()) {
9357 return write_content_with_provider(strm, req, error);
9358 }
9359
9360 if (req.upload_progress) {
9361 auto body_size = req.body.size();
9362 size_t written = 0;
9363 auto data = req.body.data();
9364
9365 while (written < body_size) {
9366 size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, b: body_size - written);
9367 if (!detail::write_data(strm, d: data + written, l: to_write)) {
9368 error = Error::Write;
9369 output_error_log(err: error, req: &req);
9370 return false;
9371 }
9372 written += to_write;
9373
9374 if (!req.upload_progress(written, body_size)) {
9375 error = Error::Canceled;
9376 output_error_log(err: error, req: &req);
9377 return false;
9378 }
9379 }
9380 } else {
9381 if (!detail::write_data(strm, d: req.body.data(), l: req.body.size())) {
9382 error = Error::Write;
9383 output_error_log(err: error, req: &req);
9384 return false;
9385 }
9386 }
9387
9388 return true;
9389}
9390
9391inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
9392 Request &req, const char *body, size_t content_length,
9393 ContentProvider content_provider,
9394 ContentProviderWithoutLength content_provider_without_length,
9395 const std::string &content_type, Error &error) {
9396 if (!content_type.empty()) { req.set_header(key: "Content-Type", val: content_type); }
9397
9398#ifdef CPPHTTPLIB_ZLIB_SUPPORT
9399 if (compress_) { req.set_header("Content-Encoding", "gzip"); }
9400#endif
9401
9402#ifdef CPPHTTPLIB_ZLIB_SUPPORT
9403 if (compress_ && !content_provider_without_length) {
9404 // TODO: Brotli support
9405 detail::gzip_compressor compressor;
9406
9407 if (content_provider) {
9408 auto ok = true;
9409 size_t offset = 0;
9410 DataSink data_sink;
9411
9412 data_sink.write = [&](const char *data, size_t data_len) -> bool {
9413 if (ok) {
9414 auto last = offset + data_len == content_length;
9415
9416 auto ret = compressor.compress(
9417 data, data_len, last,
9418 [&](const char *compressed_data, size_t compressed_data_len) {
9419 req.body.append(compressed_data, compressed_data_len);
9420 return true;
9421 });
9422
9423 if (ret) {
9424 offset += data_len;
9425 } else {
9426 ok = false;
9427 }
9428 }
9429 return ok;
9430 };
9431
9432 while (ok && offset < content_length) {
9433 if (!content_provider(offset, content_length - offset, data_sink)) {
9434 error = Error::Canceled;
9435 output_error_log(error, &req);
9436 return nullptr;
9437 }
9438 }
9439 } else {
9440 if (!compressor.compress(body, content_length, true,
9441 [&](const char *data, size_t data_len) {
9442 req.body.append(data, data_len);
9443 return true;
9444 })) {
9445 error = Error::Compression;
9446 output_error_log(error, &req);
9447 return nullptr;
9448 }
9449 }
9450 } else
9451#endif
9452 {
9453 if (content_provider) {
9454 req.content_length_ = content_length;
9455 req.content_provider_ = std::move(content_provider);
9456 req.is_chunked_content_provider_ = false;
9457 } else if (content_provider_without_length) {
9458 req.content_length_ = 0;
9459 req.content_provider_ = detail::ContentProviderAdapter(
9460 std::move(content_provider_without_length));
9461 req.is_chunked_content_provider_ = true;
9462 req.set_header(key: "Transfer-Encoding", val: "chunked");
9463 } else {
9464 req.body.assign(s: body, n: content_length);
9465 }
9466 }
9467
9468 auto res = detail::make_unique<Response>();
9469 return send(req, res&: *res, error) ? std::move(res) : nullptr;
9470}
9471
9472inline Result ClientImpl::send_with_content_provider(
9473 const std::string &method, const std::string &path, const Headers &headers,
9474 const char *body, size_t content_length, ContentProvider content_provider,
9475 ContentProviderWithoutLength content_provider_without_length,
9476 const std::string &content_type, UploadProgress progress) {
9477 Request req;
9478 req.method = method;
9479 req.headers = headers;
9480 req.path = path;
9481 req.upload_progress = std::move(progress);
9482 if (max_timeout_msec_ > 0) {
9483 req.start_time_ = std::chrono::steady_clock::now();
9484 }
9485
9486 auto error = Error::Success;
9487
9488 auto res = send_with_content_provider(
9489 req, body, content_length, content_provider: std::move(content_provider),
9490 content_provider_without_length: std::move(content_provider_without_length), content_type, error);
9491
9492#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9493 return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
9494 last_openssl_error_};
9495#else
9496 return Result{std::move(res), error, std::move(req.headers)};
9497#endif
9498}
9499
9500inline void ClientImpl::output_log(const Request &req,
9501 const Response &res) const {
9502 if (logger_) {
9503 std::lock_guard<std::mutex> guard(logger_mutex_);
9504 logger_(req, res);
9505 }
9506}
9507
9508inline void ClientImpl::output_error_log(const Error &err,
9509 const Request *req) const {
9510 if (error_logger_) {
9511 std::lock_guard<std::mutex> guard(logger_mutex_);
9512 error_logger_(err, req);
9513 }
9514}
9515
9516inline bool ClientImpl::process_request(Stream &strm, Request &req,
9517 Response &res, bool close_connection,
9518 Error &error) {
9519 // Send request
9520 if (!write_request(strm, req, close_connection, error)) { return false; }
9521
9522#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9523 if (is_ssl()) {
9524 auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
9525 if (!is_proxy_enabled) {
9526 if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
9527 error = Error::SSLPeerCouldBeClosed_;
9528 output_error_log(error, &req);
9529 return false;
9530 }
9531 }
9532 }
9533#endif
9534
9535 // Receive response and headers
9536 if (!read_response_line(strm, req, res) ||
9537 !detail::read_headers(strm, headers&: res.headers)) {
9538 error = Error::Read;
9539 output_error_log(err: error, req: &req);
9540 return false;
9541 }
9542
9543 // Body
9544 if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
9545 req.method != "CONNECT") {
9546 auto redirect = 300 < res.status && res.status < 400 &&
9547 res.status != StatusCode::NotModified_304 &&
9548 follow_location_;
9549
9550 if (req.response_handler && !redirect) {
9551 if (!req.response_handler(res)) {
9552 error = Error::Canceled;
9553 output_error_log(err: error, req: &req);
9554 return false;
9555 }
9556 }
9557
9558 auto out =
9559 req.content_receiver
9560 ? static_cast<ContentReceiverWithProgress>(
9561 [&](const char *buf, size_t n, size_t off, size_t len) {
9562 if (redirect) { return true; }
9563 auto ret = req.content_receiver(buf, n, off, len);
9564 if (!ret) {
9565 error = Error::Canceled;
9566 output_error_log(err: error, req: &req);
9567 }
9568 return ret;
9569 })
9570 : static_cast<ContentReceiverWithProgress>(
9571 [&](const char *buf, size_t n, size_t /*off*/,
9572 size_t /*len*/) {
9573 assert(res.body.size() + n <= res.body.max_size());
9574 res.body.append(s: buf, n: n);
9575 return true;
9576 });
9577
9578 auto progress = [&](size_t current, size_t total) {
9579 if (!req.download_progress || redirect) { return true; }
9580 auto ret = req.download_progress(current, total);
9581 if (!ret) {
9582 error = Error::Canceled;
9583 output_error_log(err: error, req: &req);
9584 }
9585 return ret;
9586 };
9587
9588 if (res.has_header(key: "Content-Length")) {
9589 if (!req.content_receiver) {
9590 auto len = res.get_header_value_u64(key: "Content-Length");
9591 if (len > res.body.max_size()) {
9592 error = Error::Read;
9593 output_error_log(err: error, req: &req);
9594 return false;
9595 }
9596 res.body.reserve(res_arg: static_cast<size_t>(len));
9597 }
9598 }
9599
9600 if (res.status != StatusCode::NotModified_304) {
9601 int dummy_status;
9602 if (!detail::read_content(strm, x&: res, payload_max_length: (std::numeric_limits<size_t>::max)(),
9603 status&: dummy_status, progress: std::move(progress),
9604 receiver: std::move(out), decompress: decompress_)) {
9605 if (error != Error::Canceled) { error = Error::Read; }
9606 output_error_log(err: error, req: &req);
9607 return false;
9608 }
9609 }
9610 }
9611
9612 // Log
9613 output_log(req, res);
9614
9615 return true;
9616}
9617
9618inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
9619 const std::string &boundary, const UploadFormDataItems &items,
9620 const FormDataProviderItems &provider_items) const {
9621 size_t cur_item = 0;
9622 size_t cur_start = 0;
9623 // cur_item and cur_start are copied to within the std::function and
9624 // maintain state between successive calls
9625 return [&, cur_item, cur_start](size_t offset,
9626 DataSink &sink) mutable -> bool {
9627 if (!offset && !items.empty()) {
9628 sink.os << detail::serialize_multipart_formdata(items, boundary, finish: false);
9629 return true;
9630 } else if (cur_item < provider_items.size()) {
9631 if (!cur_start) {
9632 const auto &begin = detail::serialize_multipart_formdata_item_begin(
9633 item: provider_items[cur_item], boundary);
9634 offset += begin.size();
9635 cur_start = offset;
9636 sink.os << begin;
9637 }
9638
9639 DataSink cur_sink;
9640 auto has_data = true;
9641 cur_sink.write = sink.write;
9642 cur_sink.done = [&]() { has_data = false; };
9643
9644 if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
9645 return false;
9646 }
9647
9648 if (!has_data) {
9649 sink.os << detail::serialize_multipart_formdata_item_end();
9650 cur_item++;
9651 cur_start = 0;
9652 }
9653 return true;
9654 } else {
9655 sink.os << detail::serialize_multipart_formdata_finish(boundary);
9656 sink.done();
9657 return true;
9658 }
9659 };
9660}
9661
9662inline bool ClientImpl::process_socket(
9663 const Socket &socket,
9664 std::chrono::time_point<std::chrono::steady_clock> start_time,
9665 std::function<bool(Stream &strm)> callback) {
9666 return detail::process_client_socket(
9667 sock: socket.sock, read_timeout_sec: read_timeout_sec_, read_timeout_usec: read_timeout_usec_, write_timeout_sec: write_timeout_sec_,
9668 write_timeout_usec: write_timeout_usec_, max_timeout_msec: max_timeout_msec_, start_time, callback: std::move(callback));
9669}
9670
9671inline bool ClientImpl::is_ssl() const { return false; }
9672
9673inline Result ClientImpl::Get(const std::string &path,
9674 DownloadProgress progress) {
9675 return Get(path, headers: Headers(), progress: std::move(progress));
9676}
9677
9678inline Result ClientImpl::Get(const std::string &path, const Params &params,
9679 const Headers &headers,
9680 DownloadProgress progress) {
9681 if (params.empty()) { return Get(path, headers); }
9682
9683 std::string path_with_query = append_query_params(path, params);
9684 return Get(path: path_with_query, headers, progress: std::move(progress));
9685}
9686
9687inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
9688 DownloadProgress progress) {
9689 Request req;
9690 req.method = "GET";
9691 req.path = path;
9692 req.headers = headers;
9693 req.download_progress = std::move(progress);
9694 if (max_timeout_msec_ > 0) {
9695 req.start_time_ = std::chrono::steady_clock::now();
9696 }
9697
9698 return send_(req: std::move(req));
9699}
9700
9701inline Result ClientImpl::Get(const std::string &path,
9702 ContentReceiver content_receiver,
9703 DownloadProgress progress) {
9704 return Get(path, headers: Headers(), response_handler: nullptr, content_receiver: std::move(content_receiver),
9705 progress: std::move(progress));
9706}
9707
9708inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
9709 ContentReceiver content_receiver,
9710 DownloadProgress progress) {
9711 return Get(path, headers, response_handler: nullptr, content_receiver: std::move(content_receiver),
9712 progress: std::move(progress));
9713}
9714
9715inline Result ClientImpl::Get(const std::string &path,
9716 ResponseHandler response_handler,
9717 ContentReceiver content_receiver,
9718 DownloadProgress progress) {
9719 return Get(path, headers: Headers(), response_handler: std::move(response_handler),
9720 content_receiver: std::move(content_receiver), progress: std::move(progress));
9721}
9722
9723inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
9724 ResponseHandler response_handler,
9725 ContentReceiver content_receiver,
9726 DownloadProgress progress) {
9727 Request req;
9728 req.method = "GET";
9729 req.path = path;
9730 req.headers = headers;
9731 req.response_handler = std::move(response_handler);
9732 req.content_receiver =
9733 [content_receiver](const char *data, size_t data_length,
9734 size_t /*offset*/, size_t /*total_length*/) {
9735 return content_receiver(data, data_length);
9736 };
9737 req.download_progress = std::move(progress);
9738 if (max_timeout_msec_ > 0) {
9739 req.start_time_ = std::chrono::steady_clock::now();
9740 }
9741
9742 return send_(req: std::move(req));
9743}
9744
9745inline Result ClientImpl::Get(const std::string &path, const Params &params,
9746 const Headers &headers,
9747 ContentReceiver content_receiver,
9748 DownloadProgress progress) {
9749 return Get(path, params, headers, response_handler: nullptr, content_receiver: std::move(content_receiver),
9750 progress: std::move(progress));
9751}
9752
9753inline Result ClientImpl::Get(const std::string &path, const Params &params,
9754 const Headers &headers,
9755 ResponseHandler response_handler,
9756 ContentReceiver content_receiver,
9757 DownloadProgress progress) {
9758 if (params.empty()) {
9759 return Get(path, headers, response_handler: std::move(response_handler),
9760 content_receiver: std::move(content_receiver), progress: std::move(progress));
9761 }
9762
9763 std::string path_with_query = append_query_params(path, params);
9764 return Get(path: path_with_query, headers, response_handler: std::move(response_handler),
9765 content_receiver: std::move(content_receiver), progress: std::move(progress));
9766}
9767
9768inline Result ClientImpl::Head(const std::string &path) {
9769 return Head(path, headers: Headers());
9770}
9771
9772inline Result ClientImpl::Head(const std::string &path,
9773 const Headers &headers) {
9774 Request req;
9775 req.method = "HEAD";
9776 req.headers = headers;
9777 req.path = path;
9778 if (max_timeout_msec_ > 0) {
9779 req.start_time_ = std::chrono::steady_clock::now();
9780 }
9781
9782 return send_(req: std::move(req));
9783}
9784
9785inline Result ClientImpl::Post(const std::string &path) {
9786 return Post(path, body: std::string(), content_type: std::string());
9787}
9788
9789inline Result ClientImpl::Post(const std::string &path,
9790 const Headers &headers) {
9791 return Post(path, headers, body: nullptr, content_length: 0, content_type: std::string());
9792}
9793
9794inline Result ClientImpl::Post(const std::string &path, const char *body,
9795 size_t content_length,
9796 const std::string &content_type,
9797 UploadProgress progress) {
9798 return Post(path, headers: Headers(), body, content_length, content_type, progress);
9799}
9800
9801inline Result ClientImpl::Post(const std::string &path, const std::string &body,
9802 const std::string &content_type,
9803 UploadProgress progress) {
9804 return Post(path, headers: Headers(), body, content_type, progress);
9805}
9806
9807inline Result ClientImpl::Post(const std::string &path, const Params &params) {
9808 return Post(path, headers: Headers(), params);
9809}
9810
9811inline Result ClientImpl::Post(const std::string &path, size_t content_length,
9812 ContentProvider content_provider,
9813 const std::string &content_type,
9814 UploadProgress progress) {
9815 return Post(path, headers: Headers(), content_length, content_provider: std::move(content_provider),
9816 content_type, progress);
9817}
9818
9819inline Result ClientImpl::Post(const std::string &path,
9820 ContentProviderWithoutLength content_provider,
9821 const std::string &content_type,
9822 UploadProgress progress) {
9823 return Post(path, headers: Headers(), content_provider: std::move(content_provider), content_type,
9824 progress);
9825}
9826
9827inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9828 const Params &params) {
9829 auto query = detail::params_to_query_str(params);
9830 return Post(path, headers, body: query, content_type: "application/x-www-form-urlencoded");
9831}
9832
9833inline Result ClientImpl::Post(const std::string &path,
9834 const UploadFormDataItems &items,
9835 UploadProgress progress) {
9836 return Post(path, headers: Headers(), items, progress);
9837}
9838
9839inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9840 const UploadFormDataItems &items,
9841 UploadProgress progress) {
9842 const auto &boundary = detail::make_multipart_data_boundary();
9843 const auto &content_type =
9844 detail::serialize_multipart_formdata_get_content_type(boundary);
9845 const auto &body = detail::serialize_multipart_formdata(items, boundary);
9846 return Post(path, headers, body, content_type, progress);
9847}
9848
9849inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9850 const UploadFormDataItems &items,
9851 const std::string &boundary,
9852 UploadProgress progress) {
9853 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
9854 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
9855 }
9856
9857 const auto &content_type =
9858 detail::serialize_multipart_formdata_get_content_type(boundary);
9859 const auto &body = detail::serialize_multipart_formdata(items, boundary);
9860 return Post(path, headers, body, content_type, progress);
9861}
9862
9863inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9864 const char *body, size_t content_length,
9865 const std::string &content_type,
9866 UploadProgress progress) {
9867 return send_with_content_provider(method: "POST", path, headers, body, content_length,
9868 content_provider: nullptr, content_provider_without_length: nullptr, content_type, progress);
9869}
9870
9871inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9872 const std::string &body,
9873 const std::string &content_type,
9874 UploadProgress progress) {
9875 return send_with_content_provider(method: "POST", path, headers, body: body.data(),
9876 content_length: body.size(), content_provider: nullptr, content_provider_without_length: nullptr, content_type,
9877 progress);
9878}
9879
9880inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9881 size_t content_length,
9882 ContentProvider content_provider,
9883 const std::string &content_type,
9884 UploadProgress progress) {
9885 return send_with_content_provider(method: "POST", path, headers, body: nullptr,
9886 content_length, content_provider: std::move(content_provider),
9887 content_provider_without_length: nullptr, content_type, progress);
9888}
9889
9890inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9891 ContentProviderWithoutLength content_provider,
9892 const std::string &content_type,
9893 UploadProgress progress) {
9894 return send_with_content_provider(method: "POST", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
9895 content_provider_without_length: std::move(content_provider), content_type,
9896 progress);
9897}
9898
9899inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9900 const UploadFormDataItems &items,
9901 const FormDataProviderItems &provider_items,
9902 UploadProgress progress) {
9903 const auto &boundary = detail::make_multipart_data_boundary();
9904 const auto &content_type =
9905 detail::serialize_multipart_formdata_get_content_type(boundary);
9906 return send_with_content_provider(
9907 method: "POST", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
9908 content_provider_without_length: get_multipart_content_provider(boundary, items, provider_items),
9909 content_type, progress);
9910}
9911
9912inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
9913 const std::string &body,
9914 const std::string &content_type,
9915 ContentReceiver content_receiver,
9916 DownloadProgress progress) {
9917 Request req;
9918 req.method = "POST";
9919 req.path = path;
9920 req.headers = headers;
9921 req.body = body;
9922 req.content_receiver =
9923 [content_receiver](const char *data, size_t data_length,
9924 size_t /*offset*/, size_t /*total_length*/) {
9925 return content_receiver(data, data_length);
9926 };
9927 req.download_progress = std::move(progress);
9928
9929 if (max_timeout_msec_ > 0) {
9930 req.start_time_ = std::chrono::steady_clock::now();
9931 }
9932
9933 if (!content_type.empty()) { req.set_header(key: "Content-Type", val: content_type); }
9934
9935 return send_(req: std::move(req));
9936}
9937
9938inline Result ClientImpl::Put(const std::string &path) {
9939 return Put(path, body: std::string(), content_type: std::string());
9940}
9941
9942inline Result ClientImpl::Put(const std::string &path, const Headers &headers) {
9943 return Put(path, headers, body: nullptr, content_length: 0, content_type: std::string());
9944}
9945
9946inline Result ClientImpl::Put(const std::string &path, const char *body,
9947 size_t content_length,
9948 const std::string &content_type,
9949 UploadProgress progress) {
9950 return Put(path, headers: Headers(), body, content_length, content_type, progress);
9951}
9952
9953inline Result ClientImpl::Put(const std::string &path, const std::string &body,
9954 const std::string &content_type,
9955 UploadProgress progress) {
9956 return Put(path, headers: Headers(), body, content_type, progress);
9957}
9958
9959inline Result ClientImpl::Put(const std::string &path, const Params &params) {
9960 return Put(path, headers: Headers(), params);
9961}
9962
9963inline Result ClientImpl::Put(const std::string &path, size_t content_length,
9964 ContentProvider content_provider,
9965 const std::string &content_type,
9966 UploadProgress progress) {
9967 return Put(path, headers: Headers(), content_length, content_provider: std::move(content_provider),
9968 content_type, progress);
9969}
9970
9971inline Result ClientImpl::Put(const std::string &path,
9972 ContentProviderWithoutLength content_provider,
9973 const std::string &content_type,
9974 UploadProgress progress) {
9975 return Put(path, headers: Headers(), content_provider: std::move(content_provider), content_type,
9976 progress);
9977}
9978
9979inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
9980 const Params &params) {
9981 auto query = detail::params_to_query_str(params);
9982 return Put(path, headers, body: query, content_type: "application/x-www-form-urlencoded");
9983}
9984
9985inline Result ClientImpl::Put(const std::string &path,
9986 const UploadFormDataItems &items,
9987 UploadProgress progress) {
9988 return Put(path, headers: Headers(), items, progress);
9989}
9990
9991inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
9992 const UploadFormDataItems &items,
9993 UploadProgress progress) {
9994 const auto &boundary = detail::make_multipart_data_boundary();
9995 const auto &content_type =
9996 detail::serialize_multipart_formdata_get_content_type(boundary);
9997 const auto &body = detail::serialize_multipart_formdata(items, boundary);
9998 return Put(path, headers, body, content_type, progress);
9999}
10000
10001inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10002 const UploadFormDataItems &items,
10003 const std::string &boundary,
10004 UploadProgress progress) {
10005 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
10006 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
10007 }
10008
10009 const auto &content_type =
10010 detail::serialize_multipart_formdata_get_content_type(boundary);
10011 const auto &body = detail::serialize_multipart_formdata(items, boundary);
10012 return Put(path, headers, body, content_type, progress);
10013}
10014
10015inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10016 const char *body, size_t content_length,
10017 const std::string &content_type,
10018 UploadProgress progress) {
10019 return send_with_content_provider(method: "PUT", path, headers, body, content_length,
10020 content_provider: nullptr, content_provider_without_length: nullptr, content_type, progress);
10021}
10022
10023inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10024 const std::string &body,
10025 const std::string &content_type,
10026 UploadProgress progress) {
10027 return send_with_content_provider(method: "PUT", path, headers, body: body.data(),
10028 content_length: body.size(), content_provider: nullptr, content_provider_without_length: nullptr, content_type,
10029 progress);
10030}
10031
10032inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10033 size_t content_length,
10034 ContentProvider content_provider,
10035 const std::string &content_type,
10036 UploadProgress progress) {
10037 return send_with_content_provider(method: "PUT", path, headers, body: nullptr,
10038 content_length, content_provider: std::move(content_provider),
10039 content_provider_without_length: nullptr, content_type, progress);
10040}
10041
10042inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10043 ContentProviderWithoutLength content_provider,
10044 const std::string &content_type,
10045 UploadProgress progress) {
10046 return send_with_content_provider(method: "PUT", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
10047 content_provider_without_length: std::move(content_provider), content_type,
10048 progress);
10049}
10050
10051inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10052 const UploadFormDataItems &items,
10053 const FormDataProviderItems &provider_items,
10054 UploadProgress progress) {
10055 const auto &boundary = detail::make_multipart_data_boundary();
10056 const auto &content_type =
10057 detail::serialize_multipart_formdata_get_content_type(boundary);
10058 return send_with_content_provider(
10059 method: "PUT", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
10060 content_provider_without_length: get_multipart_content_provider(boundary, items, provider_items),
10061 content_type, progress);
10062}
10063
10064inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
10065 const std::string &body,
10066 const std::string &content_type,
10067 ContentReceiver content_receiver,
10068 DownloadProgress progress) {
10069 Request req;
10070 req.method = "PUT";
10071 req.path = path;
10072 req.headers = headers;
10073 req.body = body;
10074 req.content_receiver =
10075 [content_receiver](const char *data, size_t data_length,
10076 size_t /*offset*/, size_t /*total_length*/) {
10077 return content_receiver(data, data_length);
10078 };
10079 req.download_progress = std::move(progress);
10080
10081 if (max_timeout_msec_ > 0) {
10082 req.start_time_ = std::chrono::steady_clock::now();
10083 }
10084
10085 if (!content_type.empty()) { req.set_header(key: "Content-Type", val: content_type); }
10086
10087 return send_(req: std::move(req));
10088}
10089
10090inline Result ClientImpl::Patch(const std::string &path) {
10091 return Patch(path, body: std::string(), content_type: std::string());
10092}
10093
10094inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10095 UploadProgress progress) {
10096 return Patch(path, headers, body: nullptr, content_length: 0, content_type: std::string(), progress);
10097}
10098
10099inline Result ClientImpl::Patch(const std::string &path, const char *body,
10100 size_t content_length,
10101 const std::string &content_type,
10102 UploadProgress progress) {
10103 return Patch(path, headers: Headers(), body, content_length, content_type, progress);
10104}
10105
10106inline Result ClientImpl::Patch(const std::string &path,
10107 const std::string &body,
10108 const std::string &content_type,
10109 UploadProgress progress) {
10110 return Patch(path, headers: Headers(), body, content_type, progress);
10111}
10112
10113inline Result ClientImpl::Patch(const std::string &path, const Params &params) {
10114 return Patch(path, headers: Headers(), params);
10115}
10116
10117inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
10118 ContentProvider content_provider,
10119 const std::string &content_type,
10120 UploadProgress progress) {
10121 return Patch(path, headers: Headers(), content_length, content_provider: std::move(content_provider),
10122 content_type, progress);
10123}
10124
10125inline Result ClientImpl::Patch(const std::string &path,
10126 ContentProviderWithoutLength content_provider,
10127 const std::string &content_type,
10128 UploadProgress progress) {
10129 return Patch(path, headers: Headers(), content_provider: std::move(content_provider), content_type,
10130 progress);
10131}
10132
10133inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10134 const Params &params) {
10135 auto query = detail::params_to_query_str(params);
10136 return Patch(path, headers, body: query, content_type: "application/x-www-form-urlencoded");
10137}
10138
10139inline Result ClientImpl::Patch(const std::string &path,
10140 const UploadFormDataItems &items,
10141 UploadProgress progress) {
10142 return Patch(path, headers: Headers(), items, progress);
10143}
10144
10145inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10146 const UploadFormDataItems &items,
10147 UploadProgress progress) {
10148 const auto &boundary = detail::make_multipart_data_boundary();
10149 const auto &content_type =
10150 detail::serialize_multipart_formdata_get_content_type(boundary);
10151 const auto &body = detail::serialize_multipart_formdata(items, boundary);
10152 return Patch(path, headers, body, content_type, progress);
10153}
10154
10155inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10156 const UploadFormDataItems &items,
10157 const std::string &boundary,
10158 UploadProgress progress) {
10159 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
10160 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
10161 }
10162
10163 const auto &content_type =
10164 detail::serialize_multipart_formdata_get_content_type(boundary);
10165 const auto &body = detail::serialize_multipart_formdata(items, boundary);
10166 return Patch(path, headers, body, content_type, progress);
10167}
10168
10169inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10170 const char *body, size_t content_length,
10171 const std::string &content_type,
10172 UploadProgress progress) {
10173 return send_with_content_provider(method: "PATCH", path, headers, body,
10174 content_length, content_provider: nullptr, content_provider_without_length: nullptr,
10175 content_type, progress);
10176}
10177
10178inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10179 const std::string &body,
10180 const std::string &content_type,
10181 UploadProgress progress) {
10182 return send_with_content_provider(method: "PATCH", path, headers, body: body.data(),
10183 content_length: body.size(), content_provider: nullptr, content_provider_without_length: nullptr, content_type,
10184 progress);
10185}
10186
10187inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10188 size_t content_length,
10189 ContentProvider content_provider,
10190 const std::string &content_type,
10191 UploadProgress progress) {
10192 return send_with_content_provider(method: "PATCH", path, headers, body: nullptr,
10193 content_length, content_provider: std::move(content_provider),
10194 content_provider_without_length: nullptr, content_type, progress);
10195}
10196
10197inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10198 ContentProviderWithoutLength content_provider,
10199 const std::string &content_type,
10200 UploadProgress progress) {
10201 return send_with_content_provider(method: "PATCH", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
10202 content_provider_without_length: std::move(content_provider), content_type,
10203 progress);
10204}
10205
10206inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10207 const UploadFormDataItems &items,
10208 const FormDataProviderItems &provider_items,
10209 UploadProgress progress) {
10210 const auto &boundary = detail::make_multipart_data_boundary();
10211 const auto &content_type =
10212 detail::serialize_multipart_formdata_get_content_type(boundary);
10213 return send_with_content_provider(
10214 method: "PATCH", path, headers, body: nullptr, content_length: 0, content_provider: nullptr,
10215 content_provider_without_length: get_multipart_content_provider(boundary, items, provider_items),
10216 content_type, progress);
10217}
10218
10219inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
10220 const std::string &body,
10221 const std::string &content_type,
10222 ContentReceiver content_receiver,
10223 DownloadProgress progress) {
10224 Request req;
10225 req.method = "PATCH";
10226 req.path = path;
10227 req.headers = headers;
10228 req.body = body;
10229 req.content_receiver =
10230 [content_receiver](const char *data, size_t data_length,
10231 size_t /*offset*/, size_t /*total_length*/) {
10232 return content_receiver(data, data_length);
10233 };
10234 req.download_progress = std::move(progress);
10235
10236 if (max_timeout_msec_ > 0) {
10237 req.start_time_ = std::chrono::steady_clock::now();
10238 }
10239
10240 if (!content_type.empty()) { req.set_header(key: "Content-Type", val: content_type); }
10241
10242 return send_(req: std::move(req));
10243}
10244
10245inline Result ClientImpl::Delete(const std::string &path,
10246 DownloadProgress progress) {
10247 return Delete(path, headers: Headers(), body: std::string(), content_type: std::string(), progress);
10248}
10249
10250inline Result ClientImpl::Delete(const std::string &path,
10251 const Headers &headers,
10252 DownloadProgress progress) {
10253 return Delete(path, headers, body: std::string(), content_type: std::string(), progress);
10254}
10255
10256inline Result ClientImpl::Delete(const std::string &path, const char *body,
10257 size_t content_length,
10258 const std::string &content_type,
10259 DownloadProgress progress) {
10260 return Delete(path, headers: Headers(), body, content_length, content_type, progress);
10261}
10262
10263inline Result ClientImpl::Delete(const std::string &path,
10264 const std::string &body,
10265 const std::string &content_type,
10266 DownloadProgress progress) {
10267 return Delete(path, headers: Headers(), body: body.data(), content_length: body.size(), content_type,
10268 progress);
10269}
10270
10271inline Result ClientImpl::Delete(const std::string &path,
10272 const Headers &headers,
10273 const std::string &body,
10274 const std::string &content_type,
10275 DownloadProgress progress) {
10276 return Delete(path, headers, body: body.data(), content_length: body.size(), content_type,
10277 progress);
10278}
10279
10280inline Result ClientImpl::Delete(const std::string &path, const Params &params,
10281 DownloadProgress progress) {
10282 return Delete(path, headers: Headers(), params, progress);
10283}
10284
10285inline Result ClientImpl::Delete(const std::string &path,
10286 const Headers &headers, const Params &params,
10287 DownloadProgress progress) {
10288 auto query = detail::params_to_query_str(params);
10289 return Delete(path, headers, body: query, content_type: "application/x-www-form-urlencoded",
10290 progress);
10291}
10292
10293inline Result ClientImpl::Delete(const std::string &path,
10294 const Headers &headers, const char *body,
10295 size_t content_length,
10296 const std::string &content_type,
10297 DownloadProgress progress) {
10298 Request req;
10299 req.method = "DELETE";
10300 req.headers = headers;
10301 req.path = path;
10302 req.download_progress = std::move(progress);
10303 if (max_timeout_msec_ > 0) {
10304 req.start_time_ = std::chrono::steady_clock::now();
10305 }
10306
10307 if (!content_type.empty()) { req.set_header(key: "Content-Type", val: content_type); }
10308 req.body.assign(s: body, n: content_length);
10309
10310 return send_(req: std::move(req));
10311}
10312
10313inline Result ClientImpl::Options(const std::string &path) {
10314 return Options(path, headers: Headers());
10315}
10316
10317inline Result ClientImpl::Options(const std::string &path,
10318 const Headers &headers) {
10319 Request req;
10320 req.method = "OPTIONS";
10321 req.headers = headers;
10322 req.path = path;
10323 if (max_timeout_msec_ > 0) {
10324 req.start_time_ = std::chrono::steady_clock::now();
10325 }
10326
10327 return send_(req: std::move(req));
10328}
10329
10330inline void ClientImpl::stop() {
10331 std::lock_guard<std::mutex> guard(socket_mutex_);
10332
10333 // If there is anything ongoing right now, the ONLY thread-safe thing we can
10334 // do is to shutdown_socket, so that threads using this socket suddenly
10335 // discover they can't read/write any more and error out. Everything else
10336 // (closing the socket, shutting ssl down) is unsafe because these actions
10337 // are not thread-safe.
10338 if (socket_requests_in_flight_ > 0) {
10339 shutdown_socket(socket&: socket_);
10340
10341 // Aside from that, we set a flag for the socket to be closed when we're
10342 // done.
10343 socket_should_be_closed_when_request_is_done_ = true;
10344 return;
10345 }
10346
10347 // Otherwise, still holding the mutex, we can shut everything down ourselves
10348 shutdown_ssl(socket_, true);
10349 shutdown_socket(socket&: socket_);
10350 close_socket(socket&: socket_);
10351}
10352
10353inline std::string ClientImpl::host() const { return host_; }
10354
10355inline int ClientImpl::port() const { return port_; }
10356
10357inline size_t ClientImpl::is_socket_open() const {
10358 std::lock_guard<std::mutex> guard(socket_mutex_);
10359 return socket_.is_open();
10360}
10361
10362inline socket_t ClientImpl::socket() const { return socket_.sock; }
10363
10364inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
10365 connection_timeout_sec_ = sec;
10366 connection_timeout_usec_ = usec;
10367}
10368
10369inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
10370 read_timeout_sec_ = sec;
10371 read_timeout_usec_ = usec;
10372}
10373
10374inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
10375 write_timeout_sec_ = sec;
10376 write_timeout_usec_ = usec;
10377}
10378
10379inline void ClientImpl::set_max_timeout(time_t msec) {
10380 max_timeout_msec_ = msec;
10381}
10382
10383inline void ClientImpl::set_basic_auth(const std::string &username,
10384 const std::string &password) {
10385 basic_auth_username_ = username;
10386 basic_auth_password_ = password;
10387}
10388
10389inline void ClientImpl::set_bearer_token_auth(const std::string &token) {
10390 bearer_token_auth_token_ = token;
10391}
10392
10393#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10394inline void ClientImpl::set_digest_auth(const std::string &username,
10395 const std::string &password) {
10396 digest_auth_username_ = username;
10397 digest_auth_password_ = password;
10398}
10399#endif
10400
10401inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
10402
10403inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
10404
10405inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
10406
10407inline void
10408ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
10409 addr_map_ = std::move(addr_map);
10410}
10411
10412inline void ClientImpl::set_default_headers(Headers headers) {
10413 default_headers_ = std::move(headers);
10414}
10415
10416inline void ClientImpl::set_header_writer(
10417 std::function<ssize_t(Stream &, Headers &)> const &writer) {
10418 header_writer_ = writer;
10419}
10420
10421inline void ClientImpl::set_address_family(int family) {
10422 address_family_ = family;
10423}
10424
10425inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
10426
10427inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
10428
10429inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
10430 socket_options_ = std::move(socket_options);
10431}
10432
10433inline void ClientImpl::set_compress(bool on) { compress_ = on; }
10434
10435inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
10436
10437inline void ClientImpl::set_interface(const std::string &intf) {
10438 interface_ = intf;
10439}
10440
10441inline void ClientImpl::set_proxy(const std::string &host, int port) {
10442 proxy_host_ = host;
10443 proxy_port_ = port;
10444}
10445
10446inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
10447 const std::string &password) {
10448 proxy_basic_auth_username_ = username;
10449 proxy_basic_auth_password_ = password;
10450}
10451
10452inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
10453 proxy_bearer_token_auth_token_ = token;
10454}
10455
10456#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10457inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
10458 const std::string &password) {
10459 proxy_digest_auth_username_ = username;
10460 proxy_digest_auth_password_ = password;
10461}
10462
10463inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
10464 const std::string &ca_cert_dir_path) {
10465 ca_cert_file_path_ = ca_cert_file_path;
10466 ca_cert_dir_path_ = ca_cert_dir_path;
10467}
10468
10469inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
10470 if (ca_cert_store && ca_cert_store != ca_cert_store_) {
10471 ca_cert_store_ = ca_cert_store;
10472 }
10473}
10474
10475inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
10476 std::size_t size) const {
10477 auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
10478 auto se = detail::scope_exit([&] { BIO_free_all(mem); });
10479 if (!mem) { return nullptr; }
10480
10481 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
10482 if (!inf) { return nullptr; }
10483
10484 auto cts = X509_STORE_new();
10485 if (cts) {
10486 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
10487 auto itmp = sk_X509_INFO_value(inf, i);
10488 if (!itmp) { continue; }
10489
10490 if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
10491 if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
10492 }
10493 }
10494
10495 sk_X509_INFO_pop_free(inf, X509_INFO_free);
10496 return cts;
10497}
10498
10499inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
10500 server_certificate_verification_ = enabled;
10501}
10502
10503inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
10504 server_hostname_verification_ = enabled;
10505}
10506
10507inline void ClientImpl::set_server_certificate_verifier(
10508 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
10509 server_certificate_verifier_ = verifier;
10510}
10511#endif
10512
10513inline void ClientImpl::set_logger(Logger logger) {
10514 logger_ = std::move(logger);
10515}
10516
10517inline void ClientImpl::set_error_logger(ErrorLogger error_logger) {
10518 error_logger_ = std::move(error_logger);
10519}
10520
10521/*
10522 * SSL Implementation
10523 */
10524#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10525namespace detail {
10526
10527inline bool is_ip_address(const std::string &host) {
10528 struct in_addr addr4;
10529 struct in6_addr addr6;
10530 return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
10531 inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
10532}
10533
10534template <typename U, typename V>
10535inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
10536 U SSL_connect_or_accept, V setup) {
10537 SSL *ssl = nullptr;
10538 {
10539 std::lock_guard<std::mutex> guard(ctx_mutex);
10540 ssl = SSL_new(ctx);
10541 }
10542
10543 if (ssl) {
10544 set_nonblocking(sock, true);
10545 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
10546 BIO_set_nbio(bio, 1);
10547 SSL_set_bio(ssl, bio, bio);
10548
10549 if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
10550 SSL_shutdown(ssl);
10551 {
10552 std::lock_guard<std::mutex> guard(ctx_mutex);
10553 SSL_free(ssl);
10554 }
10555 set_nonblocking(sock, false);
10556 return nullptr;
10557 }
10558 BIO_set_nbio(bio, 0);
10559 set_nonblocking(sock, false);
10560 }
10561
10562 return ssl;
10563}
10564
10565inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
10566 bool shutdown_gracefully) {
10567 // sometimes we may want to skip this to try to avoid SIGPIPE if we know
10568 // the remote has closed the network connection
10569 // Note that it is not always possible to avoid SIGPIPE, this is merely a
10570 // best-efforts.
10571 if (shutdown_gracefully) {
10572 (void)(sock);
10573 // SSL_shutdown() returns 0 on first call (indicating close_notify alert
10574 // sent) and 1 on subsequent call (indicating close_notify alert received)
10575 if (SSL_shutdown(ssl) == 0) {
10576 // Expected to return 1, but even if it doesn't, we free ssl
10577 SSL_shutdown(ssl);
10578 }
10579 }
10580
10581 std::lock_guard<std::mutex> guard(ctx_mutex);
10582 SSL_free(ssl);
10583}
10584
10585template <typename U>
10586bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
10587 U ssl_connect_or_accept,
10588 time_t timeout_sec, time_t timeout_usec,
10589 int *ssl_error) {
10590 auto res = 0;
10591 while ((res = ssl_connect_or_accept(ssl)) != 1) {
10592 auto err = SSL_get_error(ssl, res);
10593 switch (err) {
10594 case SSL_ERROR_WANT_READ:
10595 if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; }
10596 break;
10597 case SSL_ERROR_WANT_WRITE:
10598 if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; }
10599 break;
10600 default: break;
10601 }
10602 if (ssl_error) { *ssl_error = err; }
10603 return false;
10604 }
10605 return true;
10606}
10607
10608template <typename T>
10609inline bool process_server_socket_ssl(
10610 const std::atomic<socket_t> &svr_sock, SSL *ssl, socket_t sock,
10611 size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
10612 time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
10613 time_t write_timeout_usec, T callback) {
10614 return process_server_socket_core(
10615 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
10616 [&](bool close_connection, bool &connection_closed) {
10617 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
10618 write_timeout_sec, write_timeout_usec);
10619 return callback(strm, close_connection, connection_closed);
10620 });
10621}
10622
10623template <typename T>
10624inline bool process_client_socket_ssl(
10625 SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
10626 time_t write_timeout_sec, time_t write_timeout_usec,
10627 time_t max_timeout_msec,
10628 std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
10629 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
10630 write_timeout_sec, write_timeout_usec, max_timeout_msec,
10631 start_time);
10632 return callback(strm);
10633}
10634
10635// SSL socket stream implementation
10636inline SSLSocketStream::SSLSocketStream(
10637 socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec,
10638 time_t write_timeout_sec, time_t write_timeout_usec,
10639 time_t max_timeout_msec,
10640 std::chrono::time_point<std::chrono::steady_clock> start_time)
10641 : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
10642 read_timeout_usec_(read_timeout_usec),
10643 write_timeout_sec_(write_timeout_sec),
10644 write_timeout_usec_(write_timeout_usec),
10645 max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
10646 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
10647}
10648
10649inline SSLSocketStream::~SSLSocketStream() = default;
10650
10651inline bool SSLSocketStream::is_readable() const {
10652 return SSL_pending(ssl_) > 0;
10653}
10654
10655inline bool SSLSocketStream::wait_readable() const {
10656 if (max_timeout_msec_ <= 0) {
10657 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10658 }
10659
10660 time_t read_timeout_sec;
10661 time_t read_timeout_usec;
10662 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10663 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10664
10665 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10666}
10667
10668inline bool SSLSocketStream::wait_writable() const {
10669 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
10670 is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
10671}
10672
10673inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
10674 if (SSL_pending(ssl_) > 0) {
10675 return SSL_read(ssl_, ptr, static_cast<int>(size));
10676 } else if (wait_readable()) {
10677 auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
10678 if (ret < 0) {
10679 auto err = SSL_get_error(ssl_, ret);
10680 auto n = 1000;
10681#ifdef _WIN32
10682 while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
10683 (err == SSL_ERROR_SYSCALL &&
10684 WSAGetLastError() == WSAETIMEDOUT))) {
10685#else
10686 while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
10687#endif
10688 if (SSL_pending(ssl_) > 0) {
10689 return SSL_read(ssl_, ptr, static_cast<int>(size));
10690 } else if (wait_readable()) {
10691 std::this_thread::sleep_for(std::chrono::microseconds{10});
10692 ret = SSL_read(ssl_, ptr, static_cast<int>(size));
10693 if (ret >= 0) { return ret; }
10694 err = SSL_get_error(ssl_, ret);
10695 } else {
10696 break;
10697 }
10698 }
10699 assert(ret < 0);
10700 }
10701 return ret;
10702 } else {
10703 return -1;
10704 }
10705}
10706
10707inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
10708 if (wait_writable()) {
10709 auto handle_size = static_cast<int>(
10710 std::min<size_t>(size, (std::numeric_limits<int>::max)()));
10711
10712 auto ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
10713 if (ret < 0) {
10714 auto err = SSL_get_error(ssl_, ret);
10715 auto n = 1000;
10716#ifdef _WIN32
10717 while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
10718 (err == SSL_ERROR_SYSCALL &&
10719 WSAGetLastError() == WSAETIMEDOUT))) {
10720#else
10721 while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
10722#endif
10723 if (wait_writable()) {
10724 std::this_thread::sleep_for(std::chrono::microseconds{10});
10725 ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
10726 if (ret >= 0) { return ret; }
10727 err = SSL_get_error(ssl_, ret);
10728 } else {
10729 break;
10730 }
10731 }
10732 assert(ret < 0);
10733 }
10734 return ret;
10735 }
10736 return -1;
10737}
10738
10739inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
10740 int &port) const {
10741 detail::get_remote_ip_and_port(sock_, ip, port);
10742}
10743
10744inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
10745 int &port) const {
10746 detail::get_local_ip_and_port(sock_, ip, port);
10747}
10748
10749inline socket_t SSLSocketStream::socket() const { return sock_; }
10750
10751inline time_t SSLSocketStream::duration() const {
10752 return std::chrono::duration_cast<std::chrono::milliseconds>(
10753 std::chrono::steady_clock::now() - start_time_)
10754 .count();
10755}
10756
10757} // namespace detail
10758
10759// SSL HTTP server implementation
10760inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
10761 const char *client_ca_cert_file_path,
10762 const char *client_ca_cert_dir_path,
10763 const char *private_key_password) {
10764 ctx_ = SSL_CTX_new(TLS_server_method());
10765
10766 if (ctx_) {
10767 SSL_CTX_set_options(ctx_,
10768 SSL_OP_NO_COMPRESSION |
10769 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
10770
10771 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
10772
10773 if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
10774 SSL_CTX_set_default_passwd_cb_userdata(
10775 ctx_,
10776 reinterpret_cast<void *>(const_cast<char *>(private_key_password)));
10777 }
10778
10779 if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
10780 SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
10781 1 ||
10782 SSL_CTX_check_private_key(ctx_) != 1) {
10783 last_ssl_error_ = static_cast<int>(ERR_get_error());
10784 SSL_CTX_free(ctx_);
10785 ctx_ = nullptr;
10786 } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
10787 SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
10788 client_ca_cert_dir_path);
10789
10790 // Set client CA list to be sent to clients during TLS handshake
10791 if (client_ca_cert_file_path) {
10792 auto ca_list = SSL_load_client_CA_file(client_ca_cert_file_path);
10793 if (ca_list != nullptr) {
10794 SSL_CTX_set_client_CA_list(ctx_, ca_list);
10795 } else {
10796 // Failed to load client CA list, but we continue since
10797 // SSL_CTX_load_verify_locations already succeeded and
10798 // certificate verification will still work
10799 last_ssl_error_ = static_cast<int>(ERR_get_error());
10800 }
10801 }
10802
10803 SSL_CTX_set_verify(
10804 ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
10805 }
10806 }
10807}
10808
10809inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
10810 X509_STORE *client_ca_cert_store) {
10811 ctx_ = SSL_CTX_new(TLS_server_method());
10812
10813 if (ctx_) {
10814 SSL_CTX_set_options(ctx_,
10815 SSL_OP_NO_COMPRESSION |
10816 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
10817
10818 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
10819
10820 if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
10821 SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
10822 SSL_CTX_free(ctx_);
10823 ctx_ = nullptr;
10824 } else if (client_ca_cert_store) {
10825 SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
10826
10827 // Extract CA names from the store and set them as the client CA list
10828 auto ca_list = extract_ca_names_from_x509_store(client_ca_cert_store);
10829 if (ca_list) {
10830 SSL_CTX_set_client_CA_list(ctx_, ca_list);
10831 } else {
10832 // Failed to extract CA names, record the error
10833 last_ssl_error_ = static_cast<int>(ERR_get_error());
10834 }
10835
10836 SSL_CTX_set_verify(
10837 ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
10838 }
10839 }
10840}
10841
10842inline SSLServer::SSLServer(
10843 const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback) {
10844 ctx_ = SSL_CTX_new(TLS_method());
10845 if (ctx_) {
10846 if (!setup_ssl_ctx_callback(*ctx_)) {
10847 SSL_CTX_free(ctx_);
10848 ctx_ = nullptr;
10849 }
10850 }
10851}
10852
10853inline SSLServer::~SSLServer() {
10854 if (ctx_) { SSL_CTX_free(ctx_); }
10855}
10856
10857inline bool SSLServer::is_valid() const { return ctx_; }
10858
10859inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
10860
10861inline void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
10862 X509_STORE *client_ca_cert_store) {
10863
10864 std::lock_guard<std::mutex> guard(ctx_mutex_);
10865
10866 SSL_CTX_use_certificate(ctx_, cert);
10867 SSL_CTX_use_PrivateKey(ctx_, private_key);
10868
10869 if (client_ca_cert_store != nullptr) {
10870 SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
10871 }
10872}
10873
10874inline bool SSLServer::process_and_close_socket(socket_t sock) {
10875 auto ssl = detail::ssl_new(
10876 sock, ctx_, ctx_mutex_,
10877 [&](SSL *ssl2) {
10878 return detail::ssl_connect_or_accept_nonblocking(
10879 sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_,
10880 &last_ssl_error_);
10881 },
10882 [](SSL * /*ssl2*/) { return true; });
10883
10884 auto ret = false;
10885 if (ssl) {
10886 std::string remote_addr;
10887 int remote_port = 0;
10888 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
10889
10890 std::string local_addr;
10891 int local_port = 0;
10892 detail::get_local_ip_and_port(sock, local_addr, local_port);
10893
10894 ret = detail::process_server_socket_ssl(
10895 svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
10896 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
10897 write_timeout_usec_,
10898 [&](Stream &strm, bool close_connection, bool &connection_closed) {
10899 return process_request(strm, remote_addr, remote_port, local_addr,
10900 local_port, close_connection,
10901 connection_closed,
10902 [&](Request &req) { req.ssl = ssl; });
10903 });
10904
10905 // Shutdown gracefully if the result seemed successful, non-gracefully if
10906 // the connection appeared to be closed.
10907 const bool shutdown_gracefully = ret;
10908 detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully);
10909 }
10910
10911 detail::shutdown_socket(sock);
10912 detail::close_socket(sock);
10913 return ret;
10914}
10915
10916inline STACK_OF(X509_NAME) * SSLServer::extract_ca_names_from_x509_store(
10917 X509_STORE *store) {
10918 if (!store) { return nullptr; }
10919
10920 auto ca_list = sk_X509_NAME_new_null();
10921 if (!ca_list) { return nullptr; }
10922
10923 // Get all objects from the store
10924 auto objs = X509_STORE_get0_objects(store);
10925 if (!objs) {
10926 sk_X509_NAME_free(ca_list);
10927 return nullptr;
10928 }
10929
10930 // Iterate through objects and extract certificate subject names
10931 for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) {
10932 auto obj = sk_X509_OBJECT_value(objs, i);
10933 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
10934 auto cert = X509_OBJECT_get0_X509(obj);
10935 if (cert) {
10936 auto subject = X509_get_subject_name(cert);
10937 if (subject) {
10938 auto name_dup = X509_NAME_dup(subject);
10939 if (name_dup) { sk_X509_NAME_push(ca_list, name_dup); }
10940 }
10941 }
10942 }
10943 }
10944
10945 // If no names were extracted, free the list and return nullptr
10946 if (sk_X509_NAME_num(ca_list) == 0) {
10947 sk_X509_NAME_free(ca_list);
10948 return nullptr;
10949 }
10950
10951 return ca_list;
10952}
10953
10954// SSL HTTP client implementation
10955inline SSLClient::SSLClient(const std::string &host)
10956 : SSLClient(host, 443, std::string(), std::string()) {}
10957
10958inline SSLClient::SSLClient(const std::string &host, int port)
10959 : SSLClient(host, port, std::string(), std::string()) {}
10960
10961inline SSLClient::SSLClient(const std::string &host, int port,
10962 const std::string &client_cert_path,
10963 const std::string &client_key_path,
10964 const std::string &private_key_password)
10965 : ClientImpl(host, port, client_cert_path, client_key_path) {
10966 ctx_ = SSL_CTX_new(TLS_client_method());
10967
10968 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
10969
10970 detail::split(&host_[0], &host_[host_.size()], '.',
10971 [&](const char *b, const char *e) {
10972 host_components_.emplace_back(b, e);
10973 });
10974
10975 if (!client_cert_path.empty() && !client_key_path.empty()) {
10976 if (!private_key_password.empty()) {
10977 SSL_CTX_set_default_passwd_cb_userdata(
10978 ctx_, reinterpret_cast<void *>(
10979 const_cast<char *>(private_key_password.c_str())));
10980 }
10981
10982 if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
10983 SSL_FILETYPE_PEM) != 1 ||
10984 SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
10985 SSL_FILETYPE_PEM) != 1) {
10986 last_openssl_error_ = ERR_get_error();
10987 SSL_CTX_free(ctx_);
10988 ctx_ = nullptr;
10989 }
10990 }
10991}
10992
10993inline SSLClient::SSLClient(const std::string &host, int port,
10994 X509 *client_cert, EVP_PKEY *client_key,
10995 const std::string &private_key_password)
10996 : ClientImpl(host, port) {
10997 ctx_ = SSL_CTX_new(TLS_client_method());
10998
10999 detail::split(&host_[0], &host_[host_.size()], '.',
11000 [&](const char *b, const char *e) {
11001 host_components_.emplace_back(b, e);
11002 });
11003
11004 if (client_cert != nullptr && client_key != nullptr) {
11005 if (!private_key_password.empty()) {
11006 SSL_CTX_set_default_passwd_cb_userdata(
11007 ctx_, reinterpret_cast<void *>(
11008 const_cast<char *>(private_key_password.c_str())));
11009 }
11010
11011 if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
11012 SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
11013 last_openssl_error_ = ERR_get_error();
11014 SSL_CTX_free(ctx_);
11015 ctx_ = nullptr;
11016 }
11017 }
11018}
11019
11020inline SSLClient::~SSLClient() {
11021 if (ctx_) { SSL_CTX_free(ctx_); }
11022 // Make sure to shut down SSL since shutdown_ssl will resolve to the
11023 // base function rather than the derived function once we get to the
11024 // base class destructor, and won't free the SSL (causing a leak).
11025 shutdown_ssl_impl(socket_, true);
11026}
11027
11028inline bool SSLClient::is_valid() const { return ctx_; }
11029
11030inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
11031 if (ca_cert_store) {
11032 if (ctx_) {
11033 if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
11034 // Free memory allocated for old cert and use new store
11035 // `ca_cert_store`
11036 SSL_CTX_set_cert_store(ctx_, ca_cert_store);
11037 ca_cert_store_ = ca_cert_store;
11038 }
11039 } else {
11040 X509_STORE_free(ca_cert_store);
11041 }
11042 }
11043}
11044
11045inline void SSLClient::load_ca_cert_store(const char *ca_cert,
11046 std::size_t size) {
11047 set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size));
11048}
11049
11050inline long SSLClient::get_openssl_verify_result() const {
11051 return verify_result_;
11052}
11053
11054inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
11055
11056inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
11057 if (!is_valid()) {
11058 error = Error::SSLConnection;
11059 return false;
11060 }
11061 return ClientImpl::create_and_connect_socket(socket, error);
11062}
11063
11064// Assumes that socket_mutex_ is locked and that there are no requests in
11065// flight
11066inline bool SSLClient::connect_with_proxy(
11067 Socket &socket,
11068 std::chrono::time_point<std::chrono::steady_clock> start_time,
11069 Response &res, bool &success, Error &error) {
11070 success = true;
11071 Response proxy_res;
11072 if (!detail::process_client_socket(
11073 socket.sock, read_timeout_sec_, read_timeout_usec_,
11074 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
11075 start_time, [&](Stream &strm) {
11076 Request req2;
11077 req2.method = "CONNECT";
11078 req2.path = host_and_port_;
11079 if (max_timeout_msec_ > 0) {
11080 req2.start_time_ = std::chrono::steady_clock::now();
11081 }
11082 return process_request(strm, req2, proxy_res, false, error);
11083 })) {
11084 // Thread-safe to close everything because we are assuming there are no
11085 // requests in flight
11086 shutdown_ssl(socket, true);
11087 shutdown_socket(socket);
11088 close_socket(socket);
11089 success = false;
11090 return false;
11091 }
11092
11093 if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
11094 if (!proxy_digest_auth_username_.empty() &&
11095 !proxy_digest_auth_password_.empty()) {
11096 std::map<std::string, std::string> auth;
11097 if (detail::parse_www_authenticate(proxy_res, auth, true)) {
11098 // Close the current socket and create a new one for the authenticated
11099 // request
11100 shutdown_ssl(socket, true);
11101 shutdown_socket(socket);
11102 close_socket(socket);
11103
11104 // Create a new socket for the authenticated CONNECT request
11105 if (!create_and_connect_socket(socket, error)) {
11106 success = false;
11107 output_error_log(error, nullptr);
11108 return false;
11109 }
11110
11111 proxy_res = Response();
11112 if (!detail::process_client_socket(
11113 socket.sock, read_timeout_sec_, read_timeout_usec_,
11114 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
11115 start_time, [&](Stream &strm) {
11116 Request req3;
11117 req3.method = "CONNECT";
11118 req3.path = host_and_port_;
11119 req3.headers.insert(detail::make_digest_authentication_header(
11120 req3, auth, 1, detail::random_string(10),
11121 proxy_digest_auth_username_, proxy_digest_auth_password_,
11122 true));
11123 if (max_timeout_msec_ > 0) {
11124 req3.start_time_ = std::chrono::steady_clock::now();
11125 }
11126 return process_request(strm, req3, proxy_res, false, error);
11127 })) {
11128 // Thread-safe to close everything because we are assuming there are
11129 // no requests in flight
11130 shutdown_ssl(socket, true);
11131 shutdown_socket(socket);
11132 close_socket(socket);
11133 success = false;
11134 return false;
11135 }
11136 }
11137 }
11138 }
11139
11140 // If status code is not 200, proxy request is failed.
11141 // Set error to ProxyConnection and return proxy response
11142 // as the response of the request
11143 if (proxy_res.status != StatusCode::OK_200) {
11144 error = Error::ProxyConnection;
11145 output_error_log(error, nullptr);
11146 res = std::move(proxy_res);
11147 // Thread-safe to close everything because we are assuming there are
11148 // no requests in flight
11149 shutdown_ssl(socket, true);
11150 shutdown_socket(socket);
11151 close_socket(socket);
11152 return false;
11153 }
11154
11155 return true;
11156}
11157
11158inline bool SSLClient::load_certs() {
11159 auto ret = true;
11160
11161 std::call_once(initialize_cert_, [&]() {
11162 std::lock_guard<std::mutex> guard(ctx_mutex_);
11163 if (!ca_cert_file_path_.empty()) {
11164 if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
11165 nullptr)) {
11166 last_openssl_error_ = ERR_get_error();
11167 ret = false;
11168 }
11169 } else if (!ca_cert_dir_path_.empty()) {
11170 if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
11171 ca_cert_dir_path_.c_str())) {
11172 last_openssl_error_ = ERR_get_error();
11173 ret = false;
11174 }
11175 } else {
11176 auto loaded = false;
11177#ifdef _WIN32
11178 loaded =
11179 detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
11180#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
11181 loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
11182#endif // _WIN32
11183 if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
11184 }
11185 });
11186
11187 return ret;
11188}
11189
11190inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
11191 auto ssl = detail::ssl_new(
11192 socket.sock, ctx_, ctx_mutex_,
11193 [&](SSL *ssl2) {
11194 if (server_certificate_verification_) {
11195 if (!load_certs()) {
11196 error = Error::SSLLoadingCerts;
11197 output_error_log(error, nullptr);
11198 return false;
11199 }
11200 SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr);
11201 }
11202
11203 if (!detail::ssl_connect_or_accept_nonblocking(
11204 socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
11205 connection_timeout_usec_, &last_ssl_error_)) {
11206 error = Error::SSLConnection;
11207 output_error_log(error, nullptr);
11208 return false;
11209 }
11210
11211 if (server_certificate_verification_) {
11212 auto verification_status = SSLVerifierResponse::NoDecisionMade;
11213
11214 if (server_certificate_verifier_) {
11215 verification_status = server_certificate_verifier_(ssl2);
11216 }
11217
11218 if (verification_status == SSLVerifierResponse::CertificateRejected) {
11219 last_openssl_error_ = ERR_get_error();
11220 error = Error::SSLServerVerification;
11221 output_error_log(error, nullptr);
11222 return false;
11223 }
11224
11225 if (verification_status == SSLVerifierResponse::NoDecisionMade) {
11226 verify_result_ = SSL_get_verify_result(ssl2);
11227
11228 if (verify_result_ != X509_V_OK) {
11229 last_openssl_error_ = static_cast<unsigned long>(verify_result_);
11230 error = Error::SSLServerVerification;
11231 output_error_log(error, nullptr);
11232 return false;
11233 }
11234
11235 auto server_cert = SSL_get1_peer_certificate(ssl2);
11236 auto se = detail::scope_exit([&] { X509_free(server_cert); });
11237
11238 if (server_cert == nullptr) {
11239 last_openssl_error_ = ERR_get_error();
11240 error = Error::SSLServerVerification;
11241 output_error_log(error, nullptr);
11242 return false;
11243 }
11244
11245 if (server_hostname_verification_) {
11246 if (!verify_host(server_cert)) {
11247 last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
11248 error = Error::SSLServerHostnameVerification;
11249 output_error_log(error, nullptr);
11250 return false;
11251 }
11252 }
11253 }
11254 }
11255
11256 return true;
11257 },
11258 [&](SSL *ssl2) {
11259 // Set SNI only if host is not IP address
11260 if (!detail::is_ip_address(host_)) {
11261#if defined(OPENSSL_IS_BORINGSSL)
11262 SSL_set_tlsext_host_name(ssl2, host_.c_str());
11263#else
11264 // NOTE: Direct call instead of using the OpenSSL macro to suppress
11265 // -Wold-style-cast warning
11266 SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME,
11267 TLSEXT_NAMETYPE_host_name,
11268 static_cast<void *>(const_cast<char *>(host_.c_str())));
11269#endif
11270 }
11271 return true;
11272 });
11273
11274 if (ssl) {
11275 socket.ssl = ssl;
11276 return true;
11277 }
11278
11279 if (ctx_ == nullptr) {
11280 error = Error::SSLConnection;
11281 last_openssl_error_ = ERR_get_error();
11282 }
11283
11284 shutdown_socket(socket);
11285 close_socket(socket);
11286 return false;
11287}
11288
11289inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
11290 shutdown_ssl_impl(socket, shutdown_gracefully);
11291}
11292
11293inline void SSLClient::shutdown_ssl_impl(Socket &socket,
11294 bool shutdown_gracefully) {
11295 if (socket.sock == INVALID_SOCKET) {
11296 assert(socket.ssl == nullptr);
11297 return;
11298 }
11299 if (socket.ssl) {
11300 detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock,
11301 shutdown_gracefully);
11302 socket.ssl = nullptr;
11303 }
11304 assert(socket.ssl == nullptr);
11305}
11306
11307inline bool SSLClient::process_socket(
11308 const Socket &socket,
11309 std::chrono::time_point<std::chrono::steady_clock> start_time,
11310 std::function<bool(Stream &strm)> callback) {
11311 assert(socket.ssl);
11312 return detail::process_client_socket_ssl(
11313 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
11314 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
11315 std::move(callback));
11316}
11317
11318inline bool SSLClient::is_ssl() const { return true; }
11319
11320inline bool SSLClient::verify_host(X509 *server_cert) const {
11321 /* Quote from RFC2818 section 3.1 "Server Identity"
11322
11323 If a subjectAltName extension of type dNSName is present, that MUST
11324 be used as the identity. Otherwise, the (most specific) Common Name
11325 field in the Subject field of the certificate MUST be used. Although
11326 the use of the Common Name is existing practice, it is deprecated and
11327 Certification Authorities are encouraged to use the dNSName instead.
11328
11329 Matching is performed using the matching rules specified by
11330 [RFC2459]. If more than one identity of a given type is present in
11331 the certificate (e.g., more than one dNSName name, a match in any one
11332 of the set is considered acceptable.) Names may contain the wildcard
11333 character * which is considered to match any single domain name
11334 component or component fragment. E.g., *.a.com matches foo.a.com but
11335 not bar.foo.a.com. f*.com matches foo.com but not bar.com.
11336
11337 In some cases, the URI is specified as an IP address rather than a
11338 hostname. In this case, the iPAddress subjectAltName must be present
11339 in the certificate and must exactly match the IP in the URI.
11340
11341 */
11342 return verify_host_with_subject_alt_name(server_cert) ||
11343 verify_host_with_common_name(server_cert);
11344}
11345
11346inline bool
11347SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
11348 auto ret = false;
11349
11350 auto type = GEN_DNS;
11351
11352 struct in6_addr addr6 = {};
11353 struct in_addr addr = {};
11354 size_t addr_len = 0;
11355
11356#ifndef __MINGW32__
11357 if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
11358 type = GEN_IPADD;
11359 addr_len = sizeof(struct in6_addr);
11360 } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
11361 type = GEN_IPADD;
11362 addr_len = sizeof(struct in_addr);
11363 }
11364#endif
11365
11366 auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
11367 X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
11368
11369 if (alt_names) {
11370 auto dsn_matched = false;
11371 auto ip_matched = false;
11372
11373 auto count = sk_GENERAL_NAME_num(alt_names);
11374
11375 for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
11376 auto val = sk_GENERAL_NAME_value(alt_names, i);
11377 if (!val || val->type != type) { continue; }
11378
11379 auto name =
11380 reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
11381 if (name == nullptr) { continue; }
11382
11383 auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
11384
11385 switch (type) {
11386 case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
11387
11388 case GEN_IPADD:
11389 if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
11390 ip_matched = true;
11391 }
11392 break;
11393 }
11394 }
11395
11396 if (dsn_matched || ip_matched) { ret = true; }
11397 }
11398
11399 GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
11400 reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
11401 return ret;
11402}
11403
11404inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
11405 const auto subject_name = X509_get_subject_name(server_cert);
11406
11407 if (subject_name != nullptr) {
11408 char name[BUFSIZ];
11409 auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
11410 name, sizeof(name));
11411
11412 if (name_len != -1) {
11413 return check_host_name(name, static_cast<size_t>(name_len));
11414 }
11415 }
11416
11417 return false;
11418}
11419
11420inline bool SSLClient::check_host_name(const char *pattern,
11421 size_t pattern_len) const {
11422 if (host_.size() == pattern_len && host_ == pattern) { return true; }
11423
11424 // Wildcard match
11425 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
11426 std::vector<std::string> pattern_components;
11427 detail::split(&pattern[0], &pattern[pattern_len], '.',
11428 [&](const char *b, const char *e) {
11429 pattern_components.emplace_back(b, e);
11430 });
11431
11432 if (host_components_.size() != pattern_components.size()) { return false; }
11433
11434 auto itr = pattern_components.begin();
11435 for (const auto &h : host_components_) {
11436 auto &p = *itr;
11437 if (p != h && p != "*") {
11438 auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' &&
11439 !p.compare(0, p.size() - 1, h));
11440 if (!partial_match) { return false; }
11441 }
11442 ++itr;
11443 }
11444
11445 return true;
11446}
11447#endif
11448
11449// Universal client implementation
11450inline Client::Client(const std::string &scheme_host_port)
11451 : Client(scheme_host_port, std::string(), std::string()) {}
11452
11453inline Client::Client(const std::string &scheme_host_port,
11454 const std::string &client_cert_path,
11455 const std::string &client_key_path) {
11456 const static std::regex re(
11457 R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
11458
11459 std::smatch m;
11460 if (std::regex_match(s: scheme_host_port, m&: m, re: re)) {
11461 auto scheme = m[1].str();
11462
11463#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
11464 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
11465#else
11466 if (!scheme.empty() && scheme != "http") {
11467#endif
11468#ifndef CPPHTTPLIB_NO_EXCEPTIONS
11469 std::string msg = "'" + scheme + "' scheme is not supported.";
11470 throw std::invalid_argument(msg);
11471#endif
11472 return;
11473 }
11474
11475 auto is_ssl = scheme == "https";
11476
11477 auto host = m[2].str();
11478 if (host.empty()) { host = m[3].str(); }
11479
11480 auto port_str = m[4].str();
11481 auto port = !port_str.empty() ? std::stoi(str: port_str) : (is_ssl ? 443 : 80);
11482
11483 if (is_ssl) {
11484#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
11485 cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
11486 client_key_path);
11487 is_ssl_ = is_ssl;
11488#endif
11489 } else {
11490 cli_ = detail::make_unique<ClientImpl>(args&: host, args&: port, args: client_cert_path,
11491 args: client_key_path);
11492 }
11493 } else {
11494 // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
11495 // if port param below changes.
11496 cli_ = detail::make_unique<ClientImpl>(args: scheme_host_port, args: 80,
11497 args: client_cert_path, args: client_key_path);
11498 }
11499} // namespace detail
11500
11501inline Client::Client(const std::string &host, int port)
11502 : cli_(detail::make_unique<ClientImpl>(args: host, args&: port)) {}
11503
11504inline Client::Client(const std::string &host, int port,
11505 const std::string &client_cert_path,
11506 const std::string &client_key_path)
11507 : cli_(detail::make_unique<ClientImpl>(args: host, args&: port, args: client_cert_path,
11508 args: client_key_path)) {}
11509
11510inline Client::~Client() = default;
11511
11512inline bool Client::is_valid() const {
11513 return cli_ != nullptr && cli_->is_valid();
11514}
11515
11516inline Result Client::Get(const std::string &path, DownloadProgress progress) {
11517 return cli_->Get(path, progress: std::move(progress));
11518}
11519inline Result Client::Get(const std::string &path, const Headers &headers,
11520 DownloadProgress progress) {
11521 return cli_->Get(path, headers, progress: std::move(progress));
11522}
11523inline Result Client::Get(const std::string &path,
11524 ContentReceiver content_receiver,
11525 DownloadProgress progress) {
11526 return cli_->Get(path, content_receiver: std::move(content_receiver), progress: std::move(progress));
11527}
11528inline Result Client::Get(const std::string &path, const Headers &headers,
11529 ContentReceiver content_receiver,
11530 DownloadProgress progress) {
11531 return cli_->Get(path, headers, content_receiver: std::move(content_receiver),
11532 progress: std::move(progress));
11533}
11534inline Result Client::Get(const std::string &path,
11535 ResponseHandler response_handler,
11536 ContentReceiver content_receiver,
11537 DownloadProgress progress) {
11538 return cli_->Get(path, response_handler: std::move(response_handler),
11539 content_receiver: std::move(content_receiver), progress: std::move(progress));
11540}
11541inline Result Client::Get(const std::string &path, const Headers &headers,
11542 ResponseHandler response_handler,
11543 ContentReceiver content_receiver,
11544 DownloadProgress progress) {
11545 return cli_->Get(path, headers, response_handler: std::move(response_handler),
11546 content_receiver: std::move(content_receiver), progress: std::move(progress));
11547}
11548inline Result Client::Get(const std::string &path, const Params &params,
11549 const Headers &headers, DownloadProgress progress) {
11550 return cli_->Get(path, params, headers, progress: std::move(progress));
11551}
11552inline Result Client::Get(const std::string &path, const Params &params,
11553 const Headers &headers,
11554 ContentReceiver content_receiver,
11555 DownloadProgress progress) {
11556 return cli_->Get(path, params, headers, content_receiver: std::move(content_receiver),
11557 progress: std::move(progress));
11558}
11559inline Result Client::Get(const std::string &path, const Params &params,
11560 const Headers &headers,
11561 ResponseHandler response_handler,
11562 ContentReceiver content_receiver,
11563 DownloadProgress progress) {
11564 return cli_->Get(path, params, headers, response_handler: std::move(response_handler),
11565 content_receiver: std::move(content_receiver), progress: std::move(progress));
11566}
11567
11568inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
11569inline Result Client::Head(const std::string &path, const Headers &headers) {
11570 return cli_->Head(path, headers);
11571}
11572
11573inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
11574inline Result Client::Post(const std::string &path, const Headers &headers) {
11575 return cli_->Post(path, headers);
11576}
11577inline Result Client::Post(const std::string &path, const char *body,
11578 size_t content_length,
11579 const std::string &content_type,
11580 UploadProgress progress) {
11581 return cli_->Post(path, body, content_length, content_type, progress);
11582}
11583inline Result Client::Post(const std::string &path, const Headers &headers,
11584 const char *body, size_t content_length,
11585 const std::string &content_type,
11586 UploadProgress progress) {
11587 return cli_->Post(path, headers, body, content_length, content_type,
11588 progress);
11589}
11590inline Result Client::Post(const std::string &path, const std::string &body,
11591 const std::string &content_type,
11592 UploadProgress progress) {
11593 return cli_->Post(path, body, content_type, progress);
11594}
11595inline Result Client::Post(const std::string &path, const Headers &headers,
11596 const std::string &body,
11597 const std::string &content_type,
11598 UploadProgress progress) {
11599 return cli_->Post(path, headers, body, content_type, progress);
11600}
11601inline Result Client::Post(const std::string &path, size_t content_length,
11602 ContentProvider content_provider,
11603 const std::string &content_type,
11604 UploadProgress progress) {
11605 return cli_->Post(path, content_length, content_provider: std::move(content_provider),
11606 content_type, progress);
11607}
11608inline Result Client::Post(const std::string &path,
11609 ContentProviderWithoutLength content_provider,
11610 const std::string &content_type,
11611 UploadProgress progress) {
11612 return cli_->Post(path, content_provider: std::move(content_provider), content_type, progress);
11613}
11614inline Result Client::Post(const std::string &path, const Headers &headers,
11615 size_t content_length,
11616 ContentProvider content_provider,
11617 const std::string &content_type,
11618 UploadProgress progress) {
11619 return cli_->Post(path, headers, content_length, content_provider: std::move(content_provider),
11620 content_type, progress);
11621}
11622inline Result Client::Post(const std::string &path, const Headers &headers,
11623 ContentProviderWithoutLength content_provider,
11624 const std::string &content_type,
11625 UploadProgress progress) {
11626 return cli_->Post(path, headers, content_provider: std::move(content_provider), content_type,
11627 progress);
11628}
11629inline Result Client::Post(const std::string &path, const Params &params) {
11630 return cli_->Post(path, params);
11631}
11632inline Result Client::Post(const std::string &path, const Headers &headers,
11633 const Params &params) {
11634 return cli_->Post(path, headers, params);
11635}
11636inline Result Client::Post(const std::string &path,
11637 const UploadFormDataItems &items,
11638 UploadProgress progress) {
11639 return cli_->Post(path, items, progress);
11640}
11641inline Result Client::Post(const std::string &path, const Headers &headers,
11642 const UploadFormDataItems &items,
11643 UploadProgress progress) {
11644 return cli_->Post(path, headers, items, progress);
11645}
11646inline Result Client::Post(const std::string &path, const Headers &headers,
11647 const UploadFormDataItems &items,
11648 const std::string &boundary,
11649 UploadProgress progress) {
11650 return cli_->Post(path, headers, items, boundary, progress);
11651}
11652inline Result Client::Post(const std::string &path, const Headers &headers,
11653 const UploadFormDataItems &items,
11654 const FormDataProviderItems &provider_items,
11655 UploadProgress progress) {
11656 return cli_->Post(path, headers, items, provider_items, progress);
11657}
11658inline Result Client::Post(const std::string &path, const Headers &headers,
11659 const std::string &body,
11660 const std::string &content_type,
11661 ContentReceiver content_receiver,
11662 DownloadProgress progress) {
11663 return cli_->Post(path, headers, body, content_type, content_receiver,
11664 progress);
11665}
11666
11667inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
11668inline Result Client::Put(const std::string &path, const Headers &headers) {
11669 return cli_->Put(path, headers);
11670}
11671inline Result Client::Put(const std::string &path, const char *body,
11672 size_t content_length,
11673 const std::string &content_type,
11674 UploadProgress progress) {
11675 return cli_->Put(path, body, content_length, content_type, progress);
11676}
11677inline Result Client::Put(const std::string &path, const Headers &headers,
11678 const char *body, size_t content_length,
11679 const std::string &content_type,
11680 UploadProgress progress) {
11681 return cli_->Put(path, headers, body, content_length, content_type, progress);
11682}
11683inline Result Client::Put(const std::string &path, const std::string &body,
11684 const std::string &content_type,
11685 UploadProgress progress) {
11686 return cli_->Put(path, body, content_type, progress);
11687}
11688inline Result Client::Put(const std::string &path, const Headers &headers,
11689 const std::string &body,
11690 const std::string &content_type,
11691 UploadProgress progress) {
11692 return cli_->Put(path, headers, body, content_type, progress);
11693}
11694inline Result Client::Put(const std::string &path, size_t content_length,
11695 ContentProvider content_provider,
11696 const std::string &content_type,
11697 UploadProgress progress) {
11698 return cli_->Put(path, content_length, content_provider: std::move(content_provider),
11699 content_type, progress);
11700}
11701inline Result Client::Put(const std::string &path,
11702 ContentProviderWithoutLength content_provider,
11703 const std::string &content_type,
11704 UploadProgress progress) {
11705 return cli_->Put(path, content_provider: std::move(content_provider), content_type, progress);
11706}
11707inline Result Client::Put(const std::string &path, const Headers &headers,
11708 size_t content_length,
11709 ContentProvider content_provider,
11710 const std::string &content_type,
11711 UploadProgress progress) {
11712 return cli_->Put(path, headers, content_length, content_provider: std::move(content_provider),
11713 content_type, progress);
11714}
11715inline Result Client::Put(const std::string &path, const Headers &headers,
11716 ContentProviderWithoutLength content_provider,
11717 const std::string &content_type,
11718 UploadProgress progress) {
11719 return cli_->Put(path, headers, content_provider: std::move(content_provider), content_type,
11720 progress);
11721}
11722inline Result Client::Put(const std::string &path, const Params &params) {
11723 return cli_->Put(path, params);
11724}
11725inline Result Client::Put(const std::string &path, const Headers &headers,
11726 const Params &params) {
11727 return cli_->Put(path, headers, params);
11728}
11729inline Result Client::Put(const std::string &path,
11730 const UploadFormDataItems &items,
11731 UploadProgress progress) {
11732 return cli_->Put(path, items, progress);
11733}
11734inline Result Client::Put(const std::string &path, const Headers &headers,
11735 const UploadFormDataItems &items,
11736 UploadProgress progress) {
11737 return cli_->Put(path, headers, items, progress);
11738}
11739inline Result Client::Put(const std::string &path, const Headers &headers,
11740 const UploadFormDataItems &items,
11741 const std::string &boundary,
11742 UploadProgress progress) {
11743 return cli_->Put(path, headers, items, boundary, progress);
11744}
11745inline Result Client::Put(const std::string &path, const Headers &headers,
11746 const UploadFormDataItems &items,
11747 const FormDataProviderItems &provider_items,
11748 UploadProgress progress) {
11749 return cli_->Put(path, headers, items, provider_items, progress);
11750}
11751inline Result Client::Put(const std::string &path, const Headers &headers,
11752 const std::string &body,
11753 const std::string &content_type,
11754 ContentReceiver content_receiver,
11755 DownloadProgress progress) {
11756 return cli_->Put(path, headers, body, content_type, content_receiver,
11757 progress);
11758}
11759
11760inline Result Client::Patch(const std::string &path) {
11761 return cli_->Patch(path);
11762}
11763inline Result Client::Patch(const std::string &path, const Headers &headers) {
11764 return cli_->Patch(path, headers);
11765}
11766inline Result Client::Patch(const std::string &path, const char *body,
11767 size_t content_length,
11768 const std::string &content_type,
11769 UploadProgress progress) {
11770 return cli_->Patch(path, body, content_length, content_type, progress);
11771}
11772inline Result Client::Patch(const std::string &path, const Headers &headers,
11773 const char *body, size_t content_length,
11774 const std::string &content_type,
11775 UploadProgress progress) {
11776 return cli_->Patch(path, headers, body, content_length, content_type,
11777 progress);
11778}
11779inline Result Client::Patch(const std::string &path, const std::string &body,
11780 const std::string &content_type,
11781 UploadProgress progress) {
11782 return cli_->Patch(path, body, content_type, progress);
11783}
11784inline Result Client::Patch(const std::string &path, const Headers &headers,
11785 const std::string &body,
11786 const std::string &content_type,
11787 UploadProgress progress) {
11788 return cli_->Patch(path, headers, body, content_type, progress);
11789}
11790inline Result Client::Patch(const std::string &path, size_t content_length,
11791 ContentProvider content_provider,
11792 const std::string &content_type,
11793 UploadProgress progress) {
11794 return cli_->Patch(path, content_length, content_provider: std::move(content_provider),
11795 content_type, progress);
11796}
11797inline Result Client::Patch(const std::string &path,
11798 ContentProviderWithoutLength content_provider,
11799 const std::string &content_type,
11800 UploadProgress progress) {
11801 return cli_->Patch(path, content_provider: std::move(content_provider), content_type, progress);
11802}
11803inline Result Client::Patch(const std::string &path, const Headers &headers,
11804 size_t content_length,
11805 ContentProvider content_provider,
11806 const std::string &content_type,
11807 UploadProgress progress) {
11808 return cli_->Patch(path, headers, content_length, content_provider: std::move(content_provider),
11809 content_type, progress);
11810}
11811inline Result Client::Patch(const std::string &path, const Headers &headers,
11812 ContentProviderWithoutLength content_provider,
11813 const std::string &content_type,
11814 UploadProgress progress) {
11815 return cli_->Patch(path, headers, content_provider: std::move(content_provider), content_type,
11816 progress);
11817}
11818inline Result Client::Patch(const std::string &path, const Params &params) {
11819 return cli_->Patch(path, params);
11820}
11821inline Result Client::Patch(const std::string &path, const Headers &headers,
11822 const Params &params) {
11823 return cli_->Patch(path, headers, params);
11824}
11825inline Result Client::Patch(const std::string &path,
11826 const UploadFormDataItems &items,
11827 UploadProgress progress) {
11828 return cli_->Patch(path, items, progress);
11829}
11830inline Result Client::Patch(const std::string &path, const Headers &headers,
11831 const UploadFormDataItems &items,
11832 UploadProgress progress) {
11833 return cli_->Patch(path, headers, items, progress);
11834}
11835inline Result Client::Patch(const std::string &path, const Headers &headers,
11836 const UploadFormDataItems &items,
11837 const std::string &boundary,
11838 UploadProgress progress) {
11839 return cli_->Patch(path, headers, items, boundary, progress);
11840}
11841inline Result Client::Patch(const std::string &path, const Headers &headers,
11842 const UploadFormDataItems &items,
11843 const FormDataProviderItems &provider_items,
11844 UploadProgress progress) {
11845 return cli_->Patch(path, headers, items, provider_items, progress);
11846}
11847inline Result Client::Patch(const std::string &path, const Headers &headers,
11848 const std::string &body,
11849 const std::string &content_type,
11850 ContentReceiver content_receiver,
11851 DownloadProgress progress) {
11852 return cli_->Patch(path, headers, body, content_type, content_receiver,
11853 progress);
11854}
11855
11856inline Result Client::Delete(const std::string &path,
11857 DownloadProgress progress) {
11858 return cli_->Delete(path, progress);
11859}
11860inline Result Client::Delete(const std::string &path, const Headers &headers,
11861 DownloadProgress progress) {
11862 return cli_->Delete(path, headers, progress);
11863}
11864inline Result Client::Delete(const std::string &path, const char *body,
11865 size_t content_length,
11866 const std::string &content_type,
11867 DownloadProgress progress) {
11868 return cli_->Delete(path, body, content_length, content_type, progress);
11869}
11870inline Result Client::Delete(const std::string &path, const Headers &headers,
11871 const char *body, size_t content_length,
11872 const std::string &content_type,
11873 DownloadProgress progress) {
11874 return cli_->Delete(path, headers, body, content_length, content_type,
11875 progress);
11876}
11877inline Result Client::Delete(const std::string &path, const std::string &body,
11878 const std::string &content_type,
11879 DownloadProgress progress) {
11880 return cli_->Delete(path, body, content_type, progress);
11881}
11882inline Result Client::Delete(const std::string &path, const Headers &headers,
11883 const std::string &body,
11884 const std::string &content_type,
11885 DownloadProgress progress) {
11886 return cli_->Delete(path, headers, body, content_type, progress);
11887}
11888inline Result Client::Delete(const std::string &path, const Params &params,
11889 DownloadProgress progress) {
11890 return cli_->Delete(path, params, progress);
11891}
11892inline Result Client::Delete(const std::string &path, const Headers &headers,
11893 const Params &params, DownloadProgress progress) {
11894 return cli_->Delete(path, headers, params, progress);
11895}
11896
11897inline Result Client::Options(const std::string &path) {
11898 return cli_->Options(path);
11899}
11900inline Result Client::Options(const std::string &path, const Headers &headers) {
11901 return cli_->Options(path, headers);
11902}
11903
11904inline bool Client::send(Request &req, Response &res, Error &error) {
11905 return cli_->send(req, res, error);
11906}
11907
11908inline Result Client::send(const Request &req) { return cli_->send(req); }
11909
11910inline void Client::stop() { cli_->stop(); }
11911
11912inline std::string Client::host() const { return cli_->host(); }
11913
11914inline int Client::port() const { return cli_->port(); }
11915
11916inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
11917
11918inline socket_t Client::socket() const { return cli_->socket(); }
11919
11920inline void
11921Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
11922 cli_->set_hostname_addr_map(std::move(addr_map));
11923}
11924
11925inline void Client::set_default_headers(Headers headers) {
11926 cli_->set_default_headers(std::move(headers));
11927}
11928
11929inline void Client::set_header_writer(
11930 std::function<ssize_t(Stream &, Headers &)> const &writer) {
11931 cli_->set_header_writer(writer);
11932}
11933
11934inline void Client::set_address_family(int family) {
11935 cli_->set_address_family(family);
11936}
11937
11938inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
11939
11940inline void Client::set_socket_options(SocketOptions socket_options) {
11941 cli_->set_socket_options(std::move(socket_options));
11942}
11943
11944inline void Client::set_connection_timeout(time_t sec, time_t usec) {
11945 cli_->set_connection_timeout(sec, usec);
11946}
11947
11948inline void Client::set_read_timeout(time_t sec, time_t usec) {
11949 cli_->set_read_timeout(sec, usec);
11950}
11951
11952inline void Client::set_write_timeout(time_t sec, time_t usec) {
11953 cli_->set_write_timeout(sec, usec);
11954}
11955
11956inline void Client::set_basic_auth(const std::string &username,
11957 const std::string &password) {
11958 cli_->set_basic_auth(username, password);
11959}
11960inline void Client::set_bearer_token_auth(const std::string &token) {
11961 cli_->set_bearer_token_auth(token);
11962}
11963#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
11964inline void Client::set_digest_auth(const std::string &username,
11965 const std::string &password) {
11966 cli_->set_digest_auth(username, password);
11967}
11968#endif
11969
11970inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
11971inline void Client::set_follow_location(bool on) {
11972 cli_->set_follow_location(on);
11973}
11974
11975inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
11976
11977[[deprecated("Use set_path_encode instead")]]
11978inline void Client::set_url_encode(bool on) {
11979 cli_->set_path_encode(on);
11980}
11981
11982inline void Client::set_compress(bool on) { cli_->set_compress(on); }
11983
11984inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
11985
11986inline void Client::set_interface(const std::string &intf) {
11987 cli_->set_interface(intf);
11988}
11989
11990inline void Client::set_proxy(const std::string &host, int port) {
11991 cli_->set_proxy(host, port);
11992}
11993inline void Client::set_proxy_basic_auth(const std::string &username,
11994 const std::string &password) {
11995 cli_->set_proxy_basic_auth(username, password);
11996}
11997inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
11998 cli_->set_proxy_bearer_token_auth(token);
11999}
12000#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
12001inline void Client::set_proxy_digest_auth(const std::string &username,
12002 const std::string &password) {
12003 cli_->set_proxy_digest_auth(username, password);
12004}
12005#endif
12006
12007#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
12008inline void Client::enable_server_certificate_verification(bool enabled) {
12009 cli_->enable_server_certificate_verification(enabled);
12010}
12011
12012inline void Client::enable_server_hostname_verification(bool enabled) {
12013 cli_->enable_server_hostname_verification(enabled);
12014}
12015
12016inline void Client::set_server_certificate_verifier(
12017 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
12018 cli_->set_server_certificate_verifier(verifier);
12019}
12020#endif
12021
12022inline void Client::set_logger(Logger logger) {
12023 cli_->set_logger(std::move(logger));
12024}
12025
12026inline void Client::set_error_logger(ErrorLogger error_logger) {
12027 cli_->set_error_logger(std::move(error_logger));
12028}
12029
12030#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
12031inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
12032 const std::string &ca_cert_dir_path) {
12033 cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
12034}
12035
12036inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
12037 if (is_ssl_) {
12038 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
12039 } else {
12040 cli_->set_ca_cert_store(ca_cert_store);
12041 }
12042}
12043
12044inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
12045 set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size));
12046}
12047
12048inline long Client::get_openssl_verify_result() const {
12049 if (is_ssl_) {
12050 return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
12051 }
12052 return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
12053}
12054
12055inline SSL_CTX *Client::ssl_context() const {
12056 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
12057 return nullptr;
12058}
12059#endif
12060
12061// ----------------------------------------------------------------------------
12062
12063} // namespace httplib
12064
12065#endif // CPPHTTPLIB_HTTPLIB_H
12066