ebpf-blocker 1.0.0
XDP-based packet blocker using eBPF
Loading...
Searching...
No Matches
httplib.h
Go to the documentation of this file.
1//
2// httplib.h
3//
4// Copyright (c) 2026 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.48.0"
12#define CPPHTTPLIB_VERSION_NUM "0x003000"
13
14#ifdef _WIN32
15#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
16#error \
17 "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later."
18#endif
19#endif
20
21/*
22 * Configuration
23 */
24
25#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
26#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
27#endif
28
29#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
30#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
31#endif
32
33#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
34#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
35#endif
36
37#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
38#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
39#endif
40
41#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
42#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
43#endif
44
45#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
46#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
47#endif
48
49#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
50#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
51#endif
52
53#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
54#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
55#endif
56
57#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
58#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
59#endif
60
61#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
62#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
63#endif
64
65#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
66#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
67#endif
68
69#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
70#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
71#endif
72
73#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
74#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
75#endif
76
77#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
78#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
79#endif
80
81#ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD
82#define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024
83#endif
84
85#ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND
86#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000
87#endif
88
89#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD
90#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024)
91#endif
92
93#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
94#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50
95#endif
96
97#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
98#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
99#endif
100
101#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
102#ifdef _WIN32
103#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000
104#else
105#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
106#endif
107#endif
108
109#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
110#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
111#endif
112
113#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
114#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
115#endif
116
117#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
118#define CPPHTTPLIB_HEADER_MAX_COUNT 100
119#endif
120
121#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
122#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
123#endif
124
125#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
126#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
127#endif
128
129#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
130#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
131#endif
132
133#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
134#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
135#endif
136
137#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
138#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
139#endif
140
141#ifndef CPPHTTPLIB_TCP_NODELAY
142#define CPPHTTPLIB_TCP_NODELAY false
143#endif
144
145#ifndef CPPHTTPLIB_IPV6_V6ONLY
146#define CPPHTTPLIB_IPV6_V6ONLY false
147#endif
148
149#ifndef CPPHTTPLIB_RECV_BUFSIZ
150#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
151#endif
152
153#ifndef CPPHTTPLIB_SEND_BUFSIZ
154#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u)
155#endif
156
157#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
158#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
159#endif
160
161#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
162#define CPPHTTPLIB_THREAD_POOL_COUNT \
163 ((std::max)(8u, std::thread::hardware_concurrency() > 0 \
164 ? std::thread::hardware_concurrency() - 1 \
165 : 0))
166#endif
167
168#ifndef CPPHTTPLIB_THREAD_POOL_MAX_COUNT
169#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT (CPPHTTPLIB_THREAD_POOL_COUNT * 4)
170#endif
171
172#ifndef CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT
173#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 3 // seconds
174#endif
175
176#ifndef CPPHTTPLIB_RECV_FLAGS
177#define CPPHTTPLIB_RECV_FLAGS 0
178#endif
179
180#ifndef CPPHTTPLIB_SEND_FLAGS
181#define CPPHTTPLIB_SEND_FLAGS 0
182#endif
183
184#ifndef CPPHTTPLIB_LISTEN_BACKLOG
185#define CPPHTTPLIB_LISTEN_BACKLOG 5
186#endif
187
188#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
189#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
190#endif
191
192#ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
193#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216
194#endif
195
196#ifndef CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND
197#define CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND 300
198#endif
199
200#ifndef CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND
201#define CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND 5
202#endif
203
204#ifndef CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND
205#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30
206#endif
207
208#ifndef CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS
209#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS 0
210#endif
211
212/*
213 * Headers
214 */
215
216#ifdef _WIN32
217#ifndef _CRT_SECURE_NO_WARNINGS
218#define _CRT_SECURE_NO_WARNINGS
219#endif //_CRT_SECURE_NO_WARNINGS
220
221#ifndef _CRT_NONSTDC_NO_DEPRECATE
222#define _CRT_NONSTDC_NO_DEPRECATE
223#endif //_CRT_NONSTDC_NO_DEPRECATE
224
225#if defined(_MSC_VER)
226#if _MSC_VER < 1900
227#error Sorry, Visual Studio versions prior to 2015 are not supported
228#endif
229
230#pragma comment(lib, "ws2_32.lib")
231
232#ifndef _SSIZE_T_DEFINED
233using ssize_t = __int64;
234#define _SSIZE_T_DEFINED
235#endif
236#endif // _MSC_VER
237
238#ifndef S_ISREG
239#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
240#endif // S_ISREG
241
242#ifndef S_ISDIR
243#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
244#endif // S_ISDIR
245
246#ifndef NOMINMAX
247#define NOMINMAX
248#endif // NOMINMAX
249
250#include <io.h>
251#include <winsock2.h>
252#include <ws2tcpip.h>
253
254#if defined(__has_include)
255#if __has_include(<afunix.h>)
256// afunix.h uses types declared in winsock2.h, so has to be included after it.
257#include <afunix.h>
258#define CPPHTTPLIB_HAVE_AFUNIX_H 1
259#endif
260#endif
261
262#ifndef WSA_FLAG_NO_HANDLE_INHERIT
263#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
264#endif
265
266using nfds_t = unsigned long;
267using socket_t = SOCKET;
268using socklen_t = int;
269
270#else // not _WIN32
271
272#include <arpa/inet.h>
273#if !defined(_AIX) && !defined(__MVS__)
274#include <ifaddrs.h>
275#endif
276#ifdef __MVS__
277#include <strings.h>
278#ifndef NI_MAXHOST
279#define NI_MAXHOST 1025
280#endif
281#endif
282#include <net/if.h>
283#include <netdb.h>
284#include <netinet/in.h>
285#ifdef __linux__
286#include <resolv.h>
287#undef _res // Undefine _res macro to avoid conflicts with user code (#2278)
288#endif
289#include <csignal>
290#include <netinet/tcp.h>
291#include <poll.h>
292#include <pthread.h>
293#include <sys/mman.h>
294#include <sys/socket.h>
295#include <sys/un.h>
296#include <unistd.h>
297
298using socket_t = int;
299#ifndef INVALID_SOCKET
300#define INVALID_SOCKET (-1)
301#endif
302#endif //_WIN32
303
304#if defined(__APPLE__)
305#include <TargetConditionals.h>
306#endif
307
308#include <algorithm>
309#include <array>
310#include <atomic>
311#include <cassert>
312#include <cctype>
313#include <chrono>
314#include <climits>
315#include <condition_variable>
316#include <cstdlib>
317#include <cstring>
318#include <errno.h>
319#include <exception>
320#include <fcntl.h>
321#include <fstream>
322#include <functional>
323#include <iomanip>
324#include <iostream>
325#include <list>
326#include <map>
327#include <memory>
328#include <mutex>
329#include <random>
330#include <regex>
331#include <set>
332#include <sstream>
333#include <string>
334#include <sys/stat.h>
335#include <system_error>
336#include <thread>
337#include <unordered_map>
338#include <unordered_set>
339#include <utility>
340
341// On macOS with a TLS backend, enable Keychain root certificates by default
342// unless the user explicitly opts out. Not enabled on iOS/tvOS/watchOS since
343// the SecTrustSettings APIs used to enumerate anchor certificates are macOS
344// only; on those platforms the user must provide a CA bundle explicitly.
345#if defined(__APPLE__) && defined(__clang__) && \
346 !defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) && \
347 (defined(CPPHTTPLIB_OPENSSL_SUPPORT) || \
348 defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || \
349 defined(CPPHTTPLIB_WOLFSSL_SUPPORT))
350#if TARGET_OS_OSX
351#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
352#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
353#endif
354#endif
355#endif
356
357#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \
358 defined(__APPLE__) && !TARGET_OS_OSX
359#error \
360 "CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is only supported on macOS. On iOS/tvOS/watchOS, supply a CA bundle via set_ca_cert_path()."
361#endif
362
363// On Windows, enable Schannel certificate verification by default
364// unless the user explicitly opts out.
365#if defined(_WIN32) && \
366 !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
367#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
368#endif
369
370#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \
371 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
372#if TARGET_OS_MAC && defined(__clang__)
373#include <CFNetwork/CFHost.h>
374#include <CoreFoundation/CoreFoundation.h>
375#endif
376#endif
377
378#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
379#ifdef _WIN32
380#include <wincrypt.h>
381
382// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
383// used
384#undef X509_NAME
385#undef X509_CERT_PAIR
386#undef X509_EXTENSIONS
387#undef PKCS7_SIGNER_INFO
388
389#ifdef _MSC_VER
390#pragma comment(lib, "crypt32.lib")
391#endif
392#endif // _WIN32
393
394#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
395#if TARGET_OS_OSX
396#include <Security/Security.h>
397#endif
398#endif
399
400#include <openssl/err.h>
401#include <openssl/evp.h>
402#include <openssl/ssl.h>
403#include <openssl/x509v3.h>
404
405#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
406#include <openssl/applink.c>
407#endif
408
409#include <iostream>
410#include <sstream>
411
412#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
413#if OPENSSL_VERSION_NUMBER < 0x1010107f
414#error Please use OpenSSL or a current version of BoringSSL
415#endif
416#define SSL_get1_peer_certificate SSL_get_peer_certificate
417#elif OPENSSL_VERSION_NUMBER < 0x30000000L
418#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
419#endif
420
421#endif // CPPHTTPLIB_OPENSSL_SUPPORT
422
423#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
424#include <mbedtls/ctr_drbg.h>
425#include <mbedtls/entropy.h>
426#include <mbedtls/error.h>
427#include <mbedtls/md5.h>
428#include <mbedtls/net_sockets.h>
429#include <mbedtls/oid.h>
430#include <mbedtls/pk.h>
431#include <mbedtls/sha1.h>
432#include <mbedtls/sha256.h>
433#include <mbedtls/sha512.h>
434#include <mbedtls/ssl.h>
435#include <mbedtls/x509_crt.h>
436#ifdef _WIN32
437#include <wincrypt.h>
438#ifdef _MSC_VER
439#pragma comment(lib, "crypt32.lib")
440#endif
441#endif // _WIN32
442#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
443#if TARGET_OS_OSX
444#include <Security/Security.h>
445#endif
446#endif
447
448// Mbed TLS 3.x API compatibility
449#if MBEDTLS_VERSION_MAJOR >= 3
450#define CPPHTTPLIB_MBEDTLS_V3
451#endif
452
453#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
454
455#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
456#include <wolfssl/options.h>
457
458#include <wolfssl/openssl/x509v3.h>
459
460// Fallback definitions for older wolfSSL versions (e.g., 5.6.6)
461#ifndef WOLFSSL_GEN_EMAIL
462#define WOLFSSL_GEN_EMAIL 1
463#endif
464#ifndef WOLFSSL_GEN_DNS
465#define WOLFSSL_GEN_DNS 2
466#endif
467#ifndef WOLFSSL_GEN_URI
468#define WOLFSSL_GEN_URI 6
469#endif
470#ifndef WOLFSSL_GEN_IPADD
471#define WOLFSSL_GEN_IPADD 7
472#endif
473
474#include <wolfssl/ssl.h>
475#include <wolfssl/wolfcrypt/hash.h>
476#include <wolfssl/wolfcrypt/md5.h>
477#include <wolfssl/wolfcrypt/sha256.h>
478#include <wolfssl/wolfcrypt/sha512.h>
479#ifdef _WIN32
480#include <wincrypt.h>
481#ifdef _MSC_VER
482#pragma comment(lib, "crypt32.lib")
483#endif
484#endif // _WIN32
485#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
486#if TARGET_OS_OSX
487#include <Security/Security.h>
488#endif
489#endif
490#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
491
492// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
493#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) || \
494 defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
495#define CPPHTTPLIB_SSL_ENABLED
496#endif
497
498#ifdef CPPHTTPLIB_ZLIB_SUPPORT
499#include <zlib.h>
500#endif
501
502#ifdef CPPHTTPLIB_BROTLI_SUPPORT
503#include <brotli/decode.h>
504#include <brotli/encode.h>
505#endif
506
507#ifdef CPPHTTPLIB_ZSTD_SUPPORT
508#include <zstd.h>
509#endif
510
511/*
512 * Declaration
513 */
514namespace httplib {
515
516namespace ws {
517class WebSocket;
518} // namespace ws
519
520namespace detail {
521
522/*
523 * Backport std::make_unique from C++14.
524 *
525 * NOTE: This code came up with the following stackoverflow post:
526 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
527 *
528 */
529
530template <class T, class... Args>
531typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
532make_unique(Args &&...args) {
533 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
534}
535
536template <class T>
537typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
538make_unique(std::size_t n) {
539 typedef typename std::remove_extent<T>::type RT;
540 return std::unique_ptr<T>(new RT[n]);
541}
542
543namespace case_ignore {
544
545inline unsigned char to_lower(int c) {
546 const static unsigned char table[256] = {
547 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
548 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
549 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
550 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
551 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
552 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
553 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
554 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
555 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
556 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
557 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
558 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
559 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
560 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
561 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
562 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
563 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
564 255,
565 };
566 return table[(unsigned char)(char)c];
567}
568
569inline std::string to_lower(const std::string &s) {
570 std::string result = s;
571 std::transform(
572 result.begin(), result.end(), result.begin(),
573 [](unsigned char c) { return static_cast<char>(to_lower(c)); });
574 return result;
575}
576
577inline bool equal(const std::string &a, const std::string &b) {
578 return a.size() == b.size() &&
579 std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
580 return to_lower(ca) == to_lower(cb);
581 });
582}
583
584struct equal_to {
585 bool operator()(const std::string &a, const std::string &b) const {
586 return equal(a, b);
587 }
588};
589
590struct hash {
591 size_t operator()(const std::string &key) const {
592 return hash_core(key.data(), key.size(), 0);
593 }
594
595 size_t hash_core(const char *s, size_t l, size_t h) const {
596 return (l == 0) ? h
597 : hash_core(s + 1, l - 1,
598 // Unsets the 6 high bits of h, therefore no
599 // overflow happens
600 (((std::numeric_limits<size_t>::max)() >> 6) &
601 h * 33) ^
602 static_cast<unsigned char>(to_lower(*s)));
603 }
604};
605
606template <typename T>
607using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
608 detail::case_ignore::equal_to>;
609
610} // namespace case_ignore
611
612// This is based on
613// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
614
615struct scope_exit {
616 explicit scope_exit(std::function<void(void)> &&f)
617 : exit_function(std::move(f)), execute_on_destruction{true} {}
618
619 scope_exit(scope_exit &&rhs) noexcept
620 : exit_function(std::move(rhs.exit_function)),
621 execute_on_destruction{rhs.execute_on_destruction} {
622 rhs.release();
623 }
624
625 ~scope_exit() {
626 if (execute_on_destruction) { this->exit_function(); }
627 }
628
629 void release() { this->execute_on_destruction = false; }
630
631private:
632 scope_exit(const scope_exit &) = delete;
633 void operator=(const scope_exit &) = delete;
634 scope_exit &operator=(scope_exit &&) = delete;
635
636 std::function<void(void)> exit_function;
637 bool execute_on_destruction;
638};
639
640// Simple from_chars implementation for integer and double types (C++17
641// substitute)
642template <typename T> struct from_chars_result {
643 const char *ptr;
644 std::errc ec;
645};
646
647template <typename T>
648inline from_chars_result<T> from_chars(const char *first, const char *last,
649 T &value, int base = 10) {
650 value = 0;
651 const char *p = first;
652 bool negative = false;
653
654 if (p != last && *p == '-') {
655 negative = true;
656 ++p;
657 }
658 if (p == last) { return {first, std::errc::invalid_argument}; }
659
660 T result = 0;
661 for (; p != last; ++p) {
662 char c = *p;
663 int digit = -1;
664 if ('0' <= c && c <= '9') {
665 digit = c - '0';
666 } else if ('a' <= c && c <= 'z') {
667 digit = c - 'a' + 10;
668 } else if ('A' <= c && c <= 'Z') {
669 digit = c - 'A' + 10;
670 } else {
671 break;
672 }
673
674 if (digit < 0 || digit >= base) { break; }
675 if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
676 return {p, std::errc::result_out_of_range};
677 }
678 result = result * base + digit;
679 }
680
681 if (p == first || (negative && p == first + 1)) {
682 return {first, std::errc::invalid_argument};
683 }
684
685 value = negative ? -result : result;
686 return {p, std::errc{}};
687}
688
689// from_chars for double (hand-written, locale-independent)
690//
691// The only double consumed by this library is the HTTP quality value, whose
692// grammar is (RFC 9110 12.4.2):
693// qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
694// i.e. a non-negative decimal with no sign, exponent, "inf"/"nan", or wide
695// magnitude. So this parser recognizes exactly 1*DIGIT [ "." *DIGIT ] with
696// '.' always the decimal separator (std::strtod would instead read it from the
697// global C locale, mis-parsing q-values once an embedder calls
698// setlocale(LC_ALL, "") into a comma-decimal locale). The caller range-checks
699// the result to [0, 1], so inputs outside that range need not be distinguished
700// here. Allocation-free, single pass, and free of the overflow/rounding edge
701// cases that exponent and wide-range handling would introduce.
702inline from_chars_result<double> from_chars(const char *first, const char *last,
703 double &value) {
704 value = 0.0;
705 const char *p = first;
706
707 // Each 1eN is exactly representable, so a single final division by the
708 // matching entry yields a correctly-rounded result.
709 static const double powers_of_ten[] = {
710 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
711 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18};
712 const int max_frac_digits =
713 static_cast<int>(sizeof(powers_of_ten) / sizeof(powers_of_ten[0])) - 1;
714
715 // Accumulate digits into a 64-bit integer and remember how many were
716 // fractional. Two independent caps keep this bounded and safe:
717 // * accumulation saturates before mantissa could overflow uint64_t, and
718 // * frac_digits is capped at max_frac_digits so it is always a valid index
719 // into powers_of_ten (without this an input like "0.000...0" would never
720 // grow mantissa, so the saturation cap alone would not bound it).
721 // Both caps only drop digits far beyond the precision a q-value needs; any
722 // value they would change is well outside [0, 1] and rejected by the caller.
723 uint64_t mantissa = 0;
724 int frac_digits = 0;
725 bool seen_digit = false;
726
727 const uint64_t limit = ((std::numeric_limits<uint64_t>::max)() - 9) / 10;
728 auto accumulate = [&](char c) {
729 if (mantissa <= limit) {
730 mantissa = mantissa * 10 + static_cast<uint64_t>(c - '0');
731 return true;
732 }
733 return false;
734 };
735
736 for (; p != last && '0' <= *p && *p <= '9'; ++p) {
737 seen_digit = true;
738 accumulate(*p);
739 }
740
741 if (p != last && *p == '.') {
742 ++p;
743 for (; p != last && '0' <= *p && *p <= '9'; ++p) {
744 seen_digit = true;
745 if (frac_digits < max_frac_digits && accumulate(*p)) { ++frac_digits; }
746 }
747 }
748
749 if (!seen_digit) { return {first, std::errc::invalid_argument}; }
750
751 value = static_cast<double>(mantissa) / powers_of_ten[frac_digits];
752 return {p, std::errc{}};
753}
754
755inline bool parse_port(const char *s, size_t len, int &port) {
756 int val = 0;
757 auto r = from_chars(s, s + len, val);
758 if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
759 port = val;
760 return true;
761}
762
763inline bool parse_port(const std::string &s, int &port) {
764 return parse_port(s.data(), s.size(), port);
765}
766
767struct UrlComponents {
768 std::string scheme;
769 std::string host;
770 std::string port;
771 std::string path;
772 std::string query;
773};
774
775inline bool parse_url(const std::string &url, UrlComponents &uc) {
776 uc = {};
777 size_t pos = 0;
778
779 auto sep = url.find("://");
780 if (sep != std::string::npos) {
781 uc.scheme = url.substr(0, sep);
782
783 // Scheme must be [a-z]+ only
784 if (uc.scheme.empty()) { return false; }
785 for (auto c : uc.scheme) {
786 if (c < 'a' || c > 'z') { return false; }
787 }
788
789 pos = sep + 3;
790 } else if (url.compare(0, 2, "//") == 0) {
791 pos = 2;
792 }
793
794 auto has_authority_prefix = pos > 0;
795 auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' &&
796 url[0] != '?' && url[0] != '#');
797 if (has_authority) {
798 if (pos < url.size() && url[pos] == '[') {
799 auto close = url.find(']', pos);
800 if (close == std::string::npos) { return false; }
801 uc.host = url.substr(pos + 1, close - pos - 1);
802
803 // IPv6 host must be [a-fA-F0-9:]+ only
804 if (uc.host.empty()) { return false; }
805 for (auto c : uc.host) {
806 if (!((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
807 (c >= '0' && c <= '9') || c == ':')) {
808 return false;
809 }
810 }
811
812 pos = close + 1;
813 } else {
814 auto end = url.find_first_of(":/?#", pos);
815 if (end == std::string::npos) { end = url.size(); }
816 uc.host = url.substr(pos, end - pos);
817 pos = end;
818 }
819
820 if (pos < url.size() && url[pos] == ':') {
821 ++pos;
822 auto end = url.find_first_of("/?#", pos);
823 if (end == std::string::npos) { end = url.size(); }
824 uc.port = url.substr(pos, end - pos);
825 pos = end;
826 }
827
828 // Without :// or //, the entire input must be consumed as host[:port].
829 // If there is leftover (path, query, etc.), this is not a valid
830 // host[:port] string — clear and reparse as a plain path.
831 if (!has_authority_prefix && pos < url.size()) {
832 uc.host.clear();
833 uc.port.clear();
834 pos = 0;
835 }
836 }
837
838 if (pos < url.size() && url[pos] != '?' && url[pos] != '#') {
839 auto end = url.find_first_of("?#", pos);
840 if (end == std::string::npos) { end = url.size(); }
841 uc.path = url.substr(pos, end - pos);
842 pos = end;
843 }
844
845 if (pos < url.size() && url[pos] == '?') {
846 auto end = url.find('#', pos);
847 if (end == std::string::npos) { end = url.size(); }
848 uc.query = url.substr(pos, end - pos);
849 }
850
851 return true;
852}
853
854} // namespace detail
855
856enum class SSLVerifierResponse {
857 // no decision has been made, use the built-in certificate verifier
858 NoDecisionMade,
859 // connection certificate is verified and accepted
860 CertificateAccepted,
861 // connection certificate was processed but is rejected
862 CertificateRejected
863};
864
865// System CA loading policy for SSL clients. Auto (the default) loads system
866// CA certs only when no custom CA is configured; enable_system_ca() switches
867// to an explicit policy.
868enum class SystemCAMode { Auto, Enabled, Disabled };
869
870enum StatusCode {
871 // Information responses
872 Continue_100 = 100,
873 SwitchingProtocol_101 = 101,
874 Processing_102 = 102,
875 EarlyHints_103 = 103,
876
877 // Successful responses
878 OK_200 = 200,
879 Created_201 = 201,
880 Accepted_202 = 202,
881 NonAuthoritativeInformation_203 = 203,
882 NoContent_204 = 204,
883 ResetContent_205 = 205,
884 PartialContent_206 = 206,
885 MultiStatus_207 = 207,
886 AlreadyReported_208 = 208,
887 IMUsed_226 = 226,
888
889 // Redirection messages
890 MultipleChoices_300 = 300,
891 MovedPermanently_301 = 301,
892 Found_302 = 302,
893 SeeOther_303 = 303,
894 NotModified_304 = 304,
895 UseProxy_305 = 305,
896 unused_306 = 306,
897 TemporaryRedirect_307 = 307,
898 PermanentRedirect_308 = 308,
899
900 // Client error responses
901 BadRequest_400 = 400,
902 Unauthorized_401 = 401,
903 PaymentRequired_402 = 402,
904 Forbidden_403 = 403,
905 NotFound_404 = 404,
906 MethodNotAllowed_405 = 405,
907 NotAcceptable_406 = 406,
908 ProxyAuthenticationRequired_407 = 407,
909 RequestTimeout_408 = 408,
910 Conflict_409 = 409,
911 Gone_410 = 410,
912 LengthRequired_411 = 411,
913 PreconditionFailed_412 = 412,
914 PayloadTooLarge_413 = 413,
915 UriTooLong_414 = 414,
916 UnsupportedMediaType_415 = 415,
917 RangeNotSatisfiable_416 = 416,
918 ExpectationFailed_417 = 417,
919 ImATeapot_418 = 418,
920 MisdirectedRequest_421 = 421,
921 UnprocessableContent_422 = 422,
922 Locked_423 = 423,
923 FailedDependency_424 = 424,
924 TooEarly_425 = 425,
925 UpgradeRequired_426 = 426,
926 PreconditionRequired_428 = 428,
927 TooManyRequests_429 = 429,
928 RequestHeaderFieldsTooLarge_431 = 431,
929 UnavailableForLegalReasons_451 = 451,
930
931 // Server error responses
932 InternalServerError_500 = 500,
933 NotImplemented_501 = 501,
934 BadGateway_502 = 502,
935 ServiceUnavailable_503 = 503,
936 GatewayTimeout_504 = 504,
937 HttpVersionNotSupported_505 = 505,
938 VariantAlsoNegotiates_506 = 506,
939 InsufficientStorage_507 = 507,
940 LoopDetected_508 = 508,
941 NotExtended_510 = 510,
942 NetworkAuthenticationRequired_511 = 511,
943};
944
945using Headers =
946 std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
947 detail::case_ignore::equal_to>;
948
949using Params = std::multimap<std::string, std::string>;
950using Match = std::smatch;
951
952using DownloadProgress = std::function<bool(size_t current, size_t total)>;
953using UploadProgress = std::function<bool(size_t current, size_t total)>;
954
955/*
956 * detail: type-erased storage used by UserData.
957 * ABI-stable regardless of C++ standard — always uses this custom
958 * implementation instead of std::any.
959 */
960namespace detail {
961
962using any_type_id = const void *;
963
964template <typename T> any_type_id any_typeid() noexcept {
965 static const char id = 0;
966 return &id;
967}
968
969struct any_storage {
970 virtual ~any_storage() = default;
971 virtual std::unique_ptr<any_storage> clone() const = 0;
972 virtual any_type_id type_id() const noexcept = 0;
973};
974
975template <typename T> struct any_value final : any_storage {
976 T value;
977 template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
978 std::unique_ptr<any_storage> clone() const override {
979 return std::unique_ptr<any_storage>(new any_value<T>(value));
980 }
981 any_type_id type_id() const noexcept override { return any_typeid<T>(); }
982};
983
984} // namespace detail
985
986class UserData {
987public:
988 UserData() = default;
989 UserData(UserData &&) noexcept = default;
990 UserData &operator=(UserData &&) noexcept = default;
991
992 UserData(const UserData &o) {
993 for (const auto &e : o.entries_) {
994 if (e.second) { entries_[e.first] = e.second->clone(); }
995 }
996 }
997
998 UserData &operator=(const UserData &o) {
999 if (this != &o) {
1000 entries_.clear();
1001 for (const auto &e : o.entries_) {
1002 if (e.second) { entries_[e.first] = e.second->clone(); }
1003 }
1004 }
1005 return *this;
1006 }
1007
1008 template <typename T> void set(const std::string &key, T &&value) {
1009 using D = typename std::decay<T>::type;
1010 entries_[key].reset(new detail::any_value<D>(std::forward<T>(value)));
1011 }
1012
1013 template <typename T> T *get(const std::string &key) noexcept {
1014 auto it = entries_.find(key);
1015 if (it == entries_.end() || !it->second) { return nullptr; }
1016 if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
1017 return &static_cast<detail::any_value<T> *>(it->second.get())->value;
1018 }
1019
1020 template <typename T> const T *get(const std::string &key) const noexcept {
1021 auto it = entries_.find(key);
1022 if (it == entries_.end() || !it->second) { return nullptr; }
1023 if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
1024 return &static_cast<const detail::any_value<T> *>(it->second.get())->value;
1025 }
1026
1027 bool has(const std::string &key) const noexcept {
1028 return entries_.find(key) != entries_.end();
1029 }
1030
1031 void erase(const std::string &key) { entries_.erase(key); }
1032
1033 void clear() noexcept { entries_.clear(); }
1034
1035private:
1036 std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
1037 entries_;
1038};
1039
1040struct Response;
1041using ResponseHandler = std::function<bool(const Response &response)>;
1042
1043struct FormData {
1044 std::string name;
1045 std::string content;
1046 std::string filename;
1047 std::string content_type;
1048 Headers headers;
1049};
1050
1051struct FormField {
1052 std::string name;
1053 std::string content;
1054 Headers headers;
1055};
1056using FormFields = std::multimap<std::string, FormField>;
1057
1058using FormFiles = std::multimap<std::string, FormData>;
1059
1060struct MultipartFormData {
1061 FormFields fields; // Text fields from multipart
1062 FormFiles files; // Files from multipart
1063
1064 // Text field access
1065 std::string get_field(const std::string &key, size_t id = 0) const;
1066 std::vector<std::string> get_fields(const std::string &key) const;
1067 bool has_field(const std::string &key) const;
1068 size_t get_field_count(const std::string &key) const;
1069
1070 // File access
1071 FormData get_file(const std::string &key, size_t id = 0) const;
1072 std::vector<FormData> get_files(const std::string &key) const;
1073 bool has_file(const std::string &key) const;
1074 size_t get_file_count(const std::string &key) const;
1075};
1076
1077struct UploadFormData {
1078 std::string name;
1079 std::string content;
1080 std::string filename;
1081 std::string content_type;
1082};
1083using UploadFormDataItems = std::vector<UploadFormData>;
1084
1085class DataSink {
1086public:
1087 DataSink() : os(&sb_), sb_(*this) {}
1088
1089 DataSink(const DataSink &) = delete;
1090 DataSink &operator=(const DataSink &) = delete;
1091 DataSink(DataSink &&) = delete;
1092 DataSink &operator=(DataSink &&) = delete;
1093
1094 std::function<bool(const char *data, size_t data_len)> write;
1095 std::function<bool()> is_writable;
1096 std::function<void()> done;
1097 std::function<void(const Headers &trailer)> done_with_trailer;
1098 std::ostream os;
1099
1100private:
1101 class data_sink_streambuf final : public std::streambuf {
1102 public:
1103 explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
1104
1105 protected:
1106 std::streamsize xsputn(const char *s, std::streamsize n) override {
1107 if (sink_.write(s, static_cast<size_t>(n))) { return n; }
1108 return 0;
1109 }
1110
1111 private:
1112 DataSink &sink_;
1113 };
1114
1115 data_sink_streambuf sb_;
1116};
1117
1118using ContentProvider =
1119 std::function<bool(size_t offset, size_t length, DataSink &sink)>;
1120
1121using ContentProviderWithoutLength =
1122 std::function<bool(size_t offset, DataSink &sink)>;
1123
1124using ContentProviderResourceReleaser = std::function<void(bool success)>;
1125
1126struct FormDataProvider {
1127 std::string name;
1128 ContentProviderWithoutLength provider;
1129 std::string filename;
1130 std::string content_type;
1131};
1132using FormDataProviderItems = std::vector<FormDataProvider>;
1133
1134inline FormDataProvider
1135make_file_provider(const std::string &name, const std::string &filepath,
1136 const std::string &filename = std::string(),
1137 const std::string &content_type = std::string()) {
1138 FormDataProvider fdp;
1139 fdp.name = name;
1140 fdp.filename = filename.empty() ? filepath : filename;
1141 fdp.content_type = content_type;
1142 fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool {
1143 std::ifstream f(filepath, std::ios::binary);
1144 if (!f) { return false; }
1145 if (offset > 0) {
1146 f.seekg(static_cast<std::streamoff>(offset));
1147 if (!f.good()) {
1148 sink.done();
1149 return true;
1150 }
1151 }
1152 char buf[8192];
1153 f.read(buf, sizeof(buf));
1154 auto n = static_cast<size_t>(f.gcount());
1155 if (n > 0) { return sink.write(buf, n); }
1156 sink.done(); // EOF
1157 return true;
1158 };
1159 return fdp;
1160}
1161
1162inline std::pair<size_t, ContentProvider>
1163make_file_body(const std::string &filepath) {
1164 size_t size = 0;
1165 {
1166 std::ifstream f(filepath, std::ios::binary | std::ios::ate);
1167 if (!f) { return {0, ContentProvider{}}; }
1168 size = static_cast<size_t>(f.tellg());
1169 }
1170
1171 ContentProvider provider = [filepath](size_t offset, size_t length,
1172 DataSink &sink) -> bool {
1173 std::ifstream f(filepath, std::ios::binary);
1174 if (!f) { return false; }
1175 f.seekg(static_cast<std::streamoff>(offset));
1176 if (!f.good()) { return false; }
1177 char buf[8192];
1178 while (length > 0) {
1179 auto to_read = (std::min)(sizeof(buf), length);
1180 f.read(buf, static_cast<std::streamsize>(to_read));
1181 auto n = static_cast<size_t>(f.gcount());
1182 if (n == 0) { break; }
1183 if (!sink.write(buf, n)) { return false; }
1184 length -= n;
1185 }
1186 return true;
1187 };
1188 return {size, std::move(provider)};
1189}
1190
1191using ContentReceiverWithProgress = std::function<bool(
1192 const char *data, size_t data_length, size_t offset, size_t total_length)>;
1193
1194using ContentReceiver =
1195 std::function<bool(const char *data, size_t data_length)>;
1196
1197using FormDataHeader = std::function<bool(const FormData &file)>;
1198
1199class ContentReader {
1200public:
1201 using Reader = std::function<bool(ContentReceiver receiver)>;
1202 using FormDataReader =
1203 std::function<bool(FormDataHeader header, ContentReceiver receiver)>;
1204
1205 ContentReader(Reader reader, FormDataReader multipart_reader)
1206 : reader_(std::move(reader)),
1207 formdata_reader_(std::move(multipart_reader)) {}
1208
1209 bool operator()(FormDataHeader header, ContentReceiver receiver) const {
1210 return formdata_reader_(std::move(header), std::move(receiver));
1211 }
1212
1213 bool operator()(ContentReceiver receiver) const {
1214 return reader_(std::move(receiver));
1215 }
1216
1217 Reader reader_;
1218 FormDataReader formdata_reader_;
1219};
1220
1221using Range = std::pair<ssize_t, ssize_t>;
1222using Ranges = std::vector<Range>;
1223
1224#ifdef CPPHTTPLIB_SSL_ENABLED
1225// TLS abstraction layer - public type definitions and API
1226namespace tls {
1227
1228// Opaque handles (defined as void* for abstraction)
1229using ctx_t = void *;
1230using session_t = void *;
1231using const_session_t = const void *; // For read-only session access
1232using cert_t = void *;
1233using ca_store_t = void *;
1234
1235// TLS versions
1236enum class Version {
1237 TLS1_2 = 0x0303,
1238 TLS1_3 = 0x0304,
1239};
1240
1241// Subject Alternative Names (SAN) entry types
1242enum class SanType { DNS, IP, EMAIL, URI, OTHER };
1243
1244// SAN entry structure
1245struct SanEntry {
1246 SanType type;
1247 std::string value;
1248};
1249
1250// Verification context for certificate verification callback
1251struct VerifyContext {
1252 session_t session; // TLS session handle
1253 cert_t cert; // Current certificate being verified
1254 int depth; // Certificate chain depth (0 = leaf)
1255 bool preverify_ok; // OpenSSL/Mbed TLS pre-verification result
1256 long error_code; // Backend-specific error code (0 = no error)
1257 const char *error_string; // Human-readable error description
1258
1259 // Certificate introspection methods
1260 std::string subject_cn() const;
1261 std::string issuer_name() const;
1262 bool check_hostname(const char *hostname) const;
1263 std::vector<SanEntry> sans() const;
1264 bool validity(time_t &not_before, time_t &not_after) const;
1265 std::string serial() const;
1266};
1267
1268using VerifyCallback = std::function<bool(const VerifyContext &ctx)>;
1269
1270// TlsError codes for TLS operations (backend-independent)
1271enum class ErrorCode : int {
1272 Success = 0,
1273 WantRead, // Non-blocking: need to wait for read
1274 WantWrite, // Non-blocking: need to wait for write
1275 PeerClosed, // Peer closed the connection
1276 Fatal, // Unrecoverable error
1277 SyscallError, // System call error (check sys_errno)
1278 CertVerifyFailed, // Certificate verification failed
1279 HostnameMismatch, // Hostname verification failed
1280};
1281
1282// TLS error information
1283struct TlsError {
1284 ErrorCode code = ErrorCode::Fatal;
1285 uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value
1286 int sys_errno = 0; // errno when SyscallError
1287
1288 // Convert verification error code to human-readable string
1289 static std::string verify_error_to_string(long error_code);
1290};
1291
1292// RAII wrapper for peer certificate
1293class PeerCert {
1294public:
1295 PeerCert();
1296 PeerCert(PeerCert &&other) noexcept;
1297 PeerCert &operator=(PeerCert &&other) noexcept;
1298 ~PeerCert();
1299
1300 PeerCert(const PeerCert &) = delete;
1301 PeerCert &operator=(const PeerCert &) = delete;
1302
1303 explicit operator bool() const;
1304 std::string subject_cn() const;
1305 std::string issuer_name() const;
1306 bool check_hostname(const char *hostname) const;
1307 std::vector<SanEntry> sans() const;
1308 bool validity(time_t &not_before, time_t &not_after) const;
1309 std::string serial() const;
1310
1311private:
1312 explicit PeerCert(cert_t cert);
1313 cert_t cert_ = nullptr;
1314 friend PeerCert get_peer_cert_from_session(const_session_t session);
1315};
1316
1317// Callback for TLS context setup (used by SSLServer constructor)
1318using ContextSetupCallback = std::function<bool(ctx_t ctx)>;
1319
1320} // namespace tls
1321#endif
1322
1323struct Request {
1324 std::string method;
1325 std::string path;
1326 std::string matched_route;
1327 Params params;
1328 Headers headers;
1329 Headers trailers;
1330 std::string body;
1331
1332 std::string remote_addr;
1333 int remote_port = -1;
1334 std::string local_addr;
1335 int local_port = -1;
1336
1337 // for server
1338 std::string version;
1339 std::string target;
1340 MultipartFormData form;
1341 Ranges ranges;
1342 Match matches;
1343 std::unordered_map<std::string, std::string> path_params;
1344 std::function<bool()> is_connection_closed = []() { return true; };
1345
1346 // for client
1347 std::vector<std::string> accept_content_types;
1348 ResponseHandler response_handler;
1349 ContentReceiverWithProgress content_receiver;
1350 DownloadProgress download_progress;
1351 UploadProgress upload_progress;
1352
1353 bool has_header(const std::string &key) const;
1354 std::string get_header_value(const std::string &key, const char *def = "",
1355 size_t id = 0) const;
1356 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1357 size_t id = 0) const;
1358 size_t get_header_value_count(const std::string &key) const;
1359 void set_header(const std::string &key, const std::string &val);
1360
1361 bool has_trailer(const std::string &key) const;
1362 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1363 size_t get_trailer_value_count(const std::string &key) const;
1364
1365 bool has_param(const std::string &key) const;
1366 std::string get_param_value(const std::string &key, size_t id = 0) const;
1367 std::vector<std::string> get_param_values(const std::string &key) const;
1368 size_t get_param_value_count(const std::string &key) const;
1369
1370 bool is_multipart_form_data() const;
1371
1372 // private members...
1373 bool body_consumed_ = false;
1374 size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
1375 size_t content_length_ = 0;
1376 ContentProvider content_provider_;
1377 bool is_chunked_content_provider_ = false;
1378 size_t authorization_count_ = 0;
1379 std::chrono::time_point<std::chrono::steady_clock> start_time_ =
1380 (std::chrono::steady_clock::time_point::min)();
1381
1382#ifdef CPPHTTPLIB_SSL_ENABLED
1383 tls::const_session_t ssl = nullptr;
1384 tls::PeerCert peer_cert() const;
1385 std::string sni() const;
1386#endif
1387};
1388
1389struct Response {
1390 std::string version;
1391 int status = -1;
1392 std::string reason;
1393 Headers headers;
1394 Headers trailers;
1395 std::string body;
1396 std::string location; // Redirect location
1397
1398 // User-defined context — set by pre-routing/pre-request handlers and read
1399 // by route handlers to pass arbitrary data (e.g. decoded auth tokens).
1400 UserData user_data;
1401
1402 bool has_header(const std::string &key) const;
1403 std::string get_header_value(const std::string &key, const char *def = "",
1404 size_t id = 0) const;
1405 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1406 size_t id = 0) const;
1407 size_t get_header_value_count(const std::string &key) const;
1408 void set_header(const std::string &key, const std::string &val);
1409
1410 bool has_trailer(const std::string &key) const;
1411 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1412 size_t get_trailer_value_count(const std::string &key) const;
1413
1414 void set_redirect(const std::string &url, int status = StatusCode::Found_302);
1415 void set_content(const char *s, size_t n, const std::string &content_type);
1416 void set_content(const std::string &s, const std::string &content_type);
1417 void set_content(std::string &&s, const std::string &content_type);
1418
1419 void set_content_provider(
1420 size_t length, const std::string &content_type, ContentProvider provider,
1421 ContentProviderResourceReleaser resource_releaser = nullptr);
1422
1423 void set_content_provider(
1424 const std::string &content_type, ContentProviderWithoutLength provider,
1425 ContentProviderResourceReleaser resource_releaser = nullptr);
1426
1427 void set_chunked_content_provider(
1428 const std::string &content_type, ContentProviderWithoutLength provider,
1429 ContentProviderResourceReleaser resource_releaser = nullptr);
1430
1431 void set_file_content(const std::string &path,
1432 const std::string &content_type);
1433 void set_file_content(const std::string &path);
1434
1435 Response() = default;
1436 Response(const Response &) = default;
1437 Response &operator=(const Response &) = default;
1438 Response(Response &&) = default;
1439 Response &operator=(Response &&) = default;
1440 ~Response() {
1441 if (content_provider_resource_releaser_) {
1442 content_provider_resource_releaser_(content_provider_success_);
1443 }
1444 }
1445
1446 // private members...
1447 size_t content_length_ = 0;
1448 ContentProvider content_provider_;
1449 ContentProviderResourceReleaser content_provider_resource_releaser_;
1450 bool is_chunked_content_provider_ = false;
1451 bool content_provider_success_ = false;
1452 std::string file_content_path_;
1453 std::string file_content_content_type_;
1454};
1455
1456enum class Error {
1457 Success = 0,
1458 Unknown,
1459 Connection,
1460 BindIPAddress,
1461 Read,
1462 Write,
1463 ExceedRedirectCount,
1464 Canceled,
1465 SSLConnection,
1466 SSLLoadingCerts,
1467 SSLServerVerification,
1468 SSLServerHostnameVerification,
1469 UnsupportedMultipartBoundaryChars,
1470 Compression,
1471 ConnectionTimeout,
1472 ProxyConnection,
1473 ConnectionClosed,
1474 Timeout,
1475 ResourceExhaustion,
1476 TooManyFormDataFiles,
1477 ExceedMaxPayloadSize,
1478 ExceedUriMaxLength,
1479 ExceedMaxSocketDescriptorCount,
1480 InvalidRequestLine,
1481 InvalidHTTPMethod,
1482 InvalidHTTPVersion,
1483 InvalidHeaders,
1484 MultipartParsing,
1485 OpenFile,
1486 Listen,
1487 GetSockName,
1488 UnsupportedAddressFamily,
1489 HTTPParsing,
1490 InvalidRangeHeader,
1491
1492 // For internal use only
1493 SSLPeerCouldBeClosed_,
1494};
1495
1496std::string to_string(Error error);
1497
1498std::ostream &operator<<(std::ostream &os, const Error &obj);
1499
1500class Stream {
1501public:
1502 virtual ~Stream() = default;
1503
1504 virtual bool is_readable() const = 0;
1505 virtual bool wait_readable() const = 0;
1506 virtual bool wait_writable() const = 0;
1507 virtual bool is_peer_alive() const { return wait_writable(); }
1508
1509 virtual ssize_t read(char *ptr, size_t size) = 0;
1510 virtual ssize_t write(const char *ptr, size_t size) = 0;
1511 virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
1512 virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
1513 virtual socket_t socket() const = 0;
1514
1515 virtual time_t duration() const = 0;
1516
1517 virtual void set_read_timeout(time_t sec, time_t usec = 0) {
1518 (void)sec;
1519 (void)usec;
1520 }
1521
1522 ssize_t write(const char *ptr);
1523 ssize_t write(const std::string &s);
1524
1525 Error get_error() const { return error_; }
1526
1527protected:
1528 Error error_ = Error::Success;
1529};
1530
1531class TaskQueue {
1532public:
1533 TaskQueue() = default;
1534 virtual ~TaskQueue() = default;
1535
1536 virtual bool enqueue(std::function<void()> fn) = 0;
1537 virtual void shutdown() = 0;
1538
1539 virtual void on_idle() {}
1540};
1541
1542class ThreadPool final : public TaskQueue {
1543public:
1544 explicit ThreadPool(size_t n, size_t max_n = 0, size_t mqr = 0);
1545 ThreadPool(const ThreadPool &) = delete;
1546 ~ThreadPool() override = default;
1547
1548 bool enqueue(std::function<void()> fn) override;
1549 void shutdown() override;
1550
1551private:
1552 void worker(bool is_dynamic);
1553 void move_to_finished(std::thread::id id);
1554 void cleanup_finished_threads();
1555
1556 size_t base_thread_count_;
1557 size_t max_thread_count_;
1558 size_t max_queued_requests_;
1559 size_t idle_thread_count_;
1560
1561 bool shutdown_;
1562
1563 std::list<std::function<void()>> jobs_;
1564 std::vector<std::thread> threads_; // base threads
1565 std::list<std::thread> dynamic_threads_; // dynamic threads
1566 std::vector<std::thread>
1567 finished_threads_; // exited dynamic threads awaiting join
1568
1569 std::condition_variable cond_;
1570 std::mutex mutex_;
1571};
1572
1573using Logger = std::function<void(const Request &, const Response &)>;
1574
1575// Forward declaration for Error type
1576enum class Error;
1577using ErrorLogger = std::function<void(const Error &, const Request *)>;
1578
1579using SocketOptions = std::function<void(socket_t sock)>;
1580
1581void default_socket_options(socket_t sock);
1582
1583bool set_socket_opt(socket_t sock, int level, int optname, int optval);
1584
1585const char *status_message(int status);
1586
1587std::string to_string(Error error);
1588
1589std::ostream &operator<<(std::ostream &os, const Error &obj);
1590
1591std::string get_bearer_token_auth(const Request &req);
1592
1593namespace detail {
1594
1595class MatcherBase {
1596public:
1597 MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {}
1598 virtual ~MatcherBase() = default;
1599
1600 const std::string &pattern() const { return pattern_; }
1601
1602 // Match request path and populate its matches and
1603 virtual bool match(Request &request) const = 0;
1604
1605private:
1606 std::string pattern_;
1607};
1608
1609/**
1610 * Captures parameters in request path and stores them in Request::path_params
1611 *
1612 * Capture name is a substring of a pattern from : to /.
1613 * The rest of the pattern is matched against the request path directly
1614 * Parameters are captured starting from the next character after
1615 * the end of the last matched static pattern fragment until the next /.
1616 *
1617 * Example pattern:
1618 * "/path/fragments/:capture/more/fragments/:second_capture"
1619 * Static fragments:
1620 * "/path/fragments/", "more/fragments/"
1621 *
1622 * Given the following request path:
1623 * "/path/fragments/:1/more/fragments/:2"
1624 * the resulting capture will be
1625 * {{"capture", "1"}, {"second_capture", "2"}}
1626 */
1627class PathParamsMatcher final : public MatcherBase {
1628public:
1629 PathParamsMatcher(const std::string &pattern);
1630
1631 bool match(Request &request) const override;
1632
1633private:
1634 // Treat segment separators as the end of path parameter capture
1635 // Does not need to handle query parameters as they are parsed before path
1636 // matching
1637 static constexpr char separator = '/';
1638
1639 // Contains static path fragments to match against, excluding the '/' after
1640 // path params
1641 // Fragments are separated by path params
1642 std::vector<std::string> static_fragments_;
1643 // Stores the names of the path parameters to be used as keys in the
1644 // Request::path_params map
1645 std::vector<std::string> param_names_;
1646};
1647
1648/**
1649 * Performs std::regex_match on request path
1650 * and stores the result in Request::matches
1651 *
1652 * Note that regex match is performed directly on the whole request.
1653 * This means that wildcard patterns may match multiple path segments with /:
1654 * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
1655 */
1656class RegexMatcher final : public MatcherBase {
1657public:
1658 RegexMatcher(const std::string &pattern)
1659 : MatcherBase(pattern), regex_(pattern) {}
1660
1661 bool match(Request &request) const override;
1662
1663private:
1664 std::regex regex_;
1665};
1666
1667int close_socket(socket_t sock) noexcept;
1668
1669ssize_t write_headers(Stream &strm, const Headers &headers);
1670
1671bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
1672 time_t usec);
1673
1674size_t get_multipart_content_length(const UploadFormDataItems &items,
1675 const std::string &boundary);
1676
1677ContentProvider
1678make_multipart_content_provider(const UploadFormDataItems &items,
1679 const std::string &boundary);
1680
1681} // namespace detail
1682
1683class Server {
1684public:
1685 using Handler = std::function<void(const Request &, Response &)>;
1686
1687 using ExceptionHandler =
1688 std::function<void(const Request &, Response &, std::exception_ptr ep)>;
1689
1690 enum class HandlerResponse {
1691 Handled,
1692 Unhandled,
1693 };
1694 using HandlerWithResponse =
1695 std::function<HandlerResponse(const Request &, Response &)>;
1696
1697 using HandlerWithContentReader = std::function<void(
1698 const Request &, Response &, const ContentReader &content_reader)>;
1699
1700 using Expect100ContinueHandler =
1701 std::function<int(const Request &, Response &)>;
1702
1703 using StartHandler = std::function<void()>;
1704
1705 using WebSocketHandler =
1706 std::function<void(const Request &, ws::WebSocket &)>;
1707 using SubProtocolSelector =
1708 std::function<std::string(const std::vector<std::string> &protocols)>;
1709
1710 Server();
1711
1712 virtual ~Server();
1713
1714 virtual bool is_valid() const;
1715
1716 Server &Get(const std::string &pattern, Handler handler);
1717 Server &Post(const std::string &pattern, Handler handler);
1718 Server &Post(const std::string &pattern, HandlerWithContentReader handler);
1719 Server &Put(const std::string &pattern, Handler handler);
1720 Server &Put(const std::string &pattern, HandlerWithContentReader handler);
1721 Server &Patch(const std::string &pattern, Handler handler);
1722 Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
1723 Server &Delete(const std::string &pattern, Handler handler);
1724 Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
1725 Server &Options(const std::string &pattern, Handler handler);
1726
1727 Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
1728 Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
1729 SubProtocolSelector sub_protocol_selector);
1730
1731 bool set_base_dir(const std::string &dir,
1732 const std::string &mount_point = std::string());
1733 bool set_mount_point(const std::string &mount_point, const std::string &dir,
1734 Headers headers = Headers());
1735 bool remove_mount_point(const std::string &mount_point);
1736 Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
1737 const std::string &mime);
1738 Server &set_default_file_mimetype(const std::string &mime);
1739 Server &set_file_request_handler(Handler handler);
1740
1741 template <class ErrorHandlerFunc>
1742 Server &set_error_handler(ErrorHandlerFunc &&handler) {
1743 return set_error_handler_core(
1744 std::forward<ErrorHandlerFunc>(handler),
1745 std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
1746 }
1747
1748 Server &set_exception_handler(ExceptionHandler handler);
1749
1750 Server &set_pre_routing_handler(HandlerWithResponse handler);
1751 Server &set_post_routing_handler(Handler handler);
1752
1753 Server &set_pre_request_handler(HandlerWithResponse handler);
1754
1755 Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
1756
1757 Server &set_start_handler(StartHandler handler);
1758
1759 Server &set_logger(Logger logger);
1760 Server &set_pre_compression_logger(Logger logger);
1761 Server &set_error_logger(ErrorLogger error_logger);
1762
1763 Server &set_address_family(int family);
1764 Server &set_tcp_nodelay(bool on);
1765 Server &set_ipv6_v6only(bool on);
1766 Server &set_socket_options(SocketOptions socket_options);
1767
1768 Server &set_default_headers(Headers headers);
1769 Server &
1770 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1771
1772 Server &set_trusted_proxies(const std::vector<std::string> &proxies);
1773
1774 Server &set_keep_alive_max_count(size_t count);
1775 Server &set_keep_alive_timeout(time_t sec);
1776 template <class Rep, class Period>
1777 Server &
1778 set_keep_alive_timeout(const std::chrono::duration<Rep, Period> &duration);
1779
1780 Server &set_read_timeout(time_t sec, time_t usec = 0);
1781 template <class Rep, class Period>
1782 Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1783
1784 Server &set_write_timeout(time_t sec, time_t usec = 0);
1785 template <class Rep, class Period>
1786 Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1787
1788 Server &set_idle_interval(time_t sec, time_t usec = 0);
1789 template <class Rep, class Period>
1790 Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
1791
1792 Server &set_payload_max_length(size_t length);
1793
1794 Server &set_websocket_ping_interval(time_t sec);
1795 template <class Rep, class Period>
1796 Server &set_websocket_ping_interval(
1797 const std::chrono::duration<Rep, Period> &duration);
1798
1799 Server &set_websocket_max_missed_pongs(int count);
1800
1801 bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
1802 int bind_to_any_port(const std::string &host, int socket_flags = 0);
1803 bool listen_after_bind();
1804
1805 bool listen(const std::string &host, int port, int socket_flags = 0);
1806
1807 bool is_running() const;
1808 void wait_until_ready() const;
1809 void stop() noexcept;
1810 void decommission();
1811
1812 std::function<TaskQueue *(void)> new_task_queue;
1813
1814protected:
1815 bool process_request(Stream &strm, const std::string &remote_addr,
1816 int remote_port, const std::string &local_addr,
1817 int local_port, bool close_connection,
1818 bool &connection_closed,
1819 const std::function<void(Request &)> &setup_request,
1820 bool *websocket_upgraded = nullptr);
1821
1822 std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
1823
1824 std::vector<std::string> trusted_proxies_;
1825
1826 size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
1827 time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
1828 time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
1829 time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
1830 time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
1831 time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
1832 time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
1833 time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
1834 size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
1835 time_t websocket_ping_interval_sec_ =
1837 int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
1838
1839private:
1840 using Handlers =
1841 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
1842 using HandlersForContentReader =
1843 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
1844 HandlerWithContentReader>>;
1845
1846 static std::unique_ptr<detail::MatcherBase>
1847 make_matcher(const std::string &pattern);
1848
1849 template <typename H>
1850 Server &add_handler(
1851 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, H>> &handlers,
1852 const std::string &pattern, H handler) {
1853 handlers.emplace_back(make_matcher(pattern), std::move(handler));
1854 return *this;
1855 }
1856
1857 Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
1858 Server &set_error_handler_core(Handler handler, std::false_type);
1859
1860 socket_t create_server_socket(const std::string &host, int port,
1861 int socket_flags,
1862 SocketOptions socket_options) const;
1863 int bind_internal(const std::string &host, int port, int socket_flags);
1864 bool listen_internal();
1865
1866 bool routing(Request &req, Response &res, Stream &strm);
1867 bool handle_file_request(Request &req, Response &res);
1868 bool check_if_not_modified(const Request &req, Response &res,
1869 const std::string &etag, time_t mtime) const;
1870 bool check_if_range(Request &req, const std::string &etag,
1871 time_t mtime) const;
1872 bool dispatch_request(Request &req, Response &res, const Handlers &handlers,
1873 Stream &strm);
1874 bool dispatch_request_for_content_reader(
1875 Request &req, Response &res, ContentReader content_reader,
1876 const HandlersForContentReader &handlers) const;
1877
1878 bool parse_request_line(const char *s, Request &req) const;
1879 void apply_ranges(const Request &req, Response &res,
1880 std::string &content_type, std::string &boundary) const;
1881 bool write_response(Stream &strm, bool close_connection, Request &req,
1882 Response &res);
1883 bool write_response_with_content(Stream &strm, bool close_connection,
1884 const Request &req, Response &res);
1885 bool write_response_core(Stream &strm, bool close_connection,
1886 const Request &req, Response &res,
1887 bool need_apply_ranges);
1888 bool write_content_with_provider(Stream &strm, const Request &req,
1889 Response &res, const std::string &boundary,
1890 const std::string &content_type);
1891 bool read_content(Stream &strm, Request &req, Response &res);
1892 bool read_content_with_content_receiver(Stream &strm, Request &req,
1893 Response &res,
1894 ContentReceiver receiver,
1895 FormDataHeader multipart_header,
1896 ContentReceiver multipart_receiver);
1897 bool read_content_core(Stream &strm, Request &req, Response &res,
1898 ContentReceiver receiver,
1899 FormDataHeader multipart_header,
1900 ContentReceiver multipart_receiver) const;
1901
1902 virtual bool process_and_close_socket(socket_t sock);
1903
1904 void output_log(const Request &req, const Response &res) const;
1905 void output_pre_compression_log(const Request &req,
1906 const Response &res) const;
1907 void output_error_log(const Error &err, const Request *req) const;
1908
1909 std::atomic<bool> is_running_{false};
1910 std::atomic<bool> is_decommissioned{false};
1911
1912 struct MountPointEntry {
1913 std::string mount_point;
1914 std::string base_dir;
1915 std::string resolved_base_dir;
1916 Headers headers;
1917 };
1918 std::vector<MountPointEntry> base_dirs_;
1919 std::map<std::string, std::string> file_extension_and_mimetype_map_;
1920 std::string default_file_mimetype_ = "application/octet-stream";
1921 Handler file_request_handler_;
1922
1923 Handlers get_handlers_;
1924 Handlers post_handlers_;
1925 HandlersForContentReader post_handlers_for_content_reader_;
1926 Handlers put_handlers_;
1927 HandlersForContentReader put_handlers_for_content_reader_;
1928 Handlers patch_handlers_;
1929 HandlersForContentReader patch_handlers_for_content_reader_;
1930 Handlers delete_handlers_;
1931 HandlersForContentReader delete_handlers_for_content_reader_;
1932 Handlers options_handlers_;
1933
1934 struct WebSocketHandlerEntry {
1935 std::unique_ptr<detail::MatcherBase> matcher;
1936 WebSocketHandler handler;
1937 SubProtocolSelector sub_protocol_selector;
1938 };
1939 using WebSocketHandlers = std::vector<WebSocketHandlerEntry>;
1940 WebSocketHandlers websocket_handlers_;
1941
1942 HandlerWithResponse error_handler_;
1943 ExceptionHandler exception_handler_;
1944 HandlerWithResponse pre_routing_handler_;
1945 Handler post_routing_handler_;
1946 HandlerWithResponse pre_request_handler_;
1947 Expect100ContinueHandler expect_100_continue_handler_;
1948 StartHandler start_handler_;
1949
1950 mutable std::mutex logger_mutex_;
1951 Logger logger_;
1952 Logger pre_compression_logger_;
1953 ErrorLogger error_logger_;
1954
1955 int address_family_ = AF_UNSPEC;
1956 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
1957 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
1958 SocketOptions socket_options_ = default_socket_options;
1959
1960 Headers default_headers_;
1961 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
1962 detail::write_headers;
1963};
1964
1965class Result {
1966public:
1967 Result() = default;
1968 Result(std::unique_ptr<Response> &&res, Error err,
1969 Headers &&request_headers = Headers{})
1970 : res_(std::move(res)), err_(err),
1971 request_headers_(std::move(request_headers)) {}
1972 // Response
1973 operator bool() const { return res_ != nullptr; }
1974 bool operator==(std::nullptr_t) const { return res_ == nullptr; }
1975 bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
1976 const Response &value() const { return *res_; }
1977 Response &value() { return *res_; }
1978 const Response &operator*() const { return *res_; }
1979 Response &operator*() { return *res_; }
1980 const Response *operator->() const { return res_.get(); }
1981 Response *operator->() { return res_.get(); }
1982
1983 // Error
1984 Error error() const { return err_; }
1985
1986 // Request Headers
1987 bool has_request_header(const std::string &key) const;
1988 std::string get_request_header_value(const std::string &key,
1989 const char *def = "",
1990 size_t id = 0) const;
1991 size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
1992 size_t id = 0) const;
1993 size_t get_request_header_value_count(const std::string &key) const;
1994
1995private:
1996 std::unique_ptr<Response> res_;
1997 Error err_ = Error::Unknown;
1998 Headers request_headers_;
1999
2000#ifdef CPPHTTPLIB_SSL_ENABLED
2001public:
2002 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
2003 int ssl_error)
2004 : res_(std::move(res)), err_(err),
2005 request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
2006 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
2007 int ssl_error, uint64_t ssl_backend_error)
2008 : res_(std::move(res)), err_(err),
2009 request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
2010 ssl_backend_error_(ssl_backend_error) {}
2011
2012 int ssl_error() const { return ssl_error_; }
2013 uint64_t ssl_backend_error() const { return ssl_backend_error_; }
2014
2015private:
2016 int ssl_error_ = 0;
2017 uint64_t ssl_backend_error_ = 0;
2018#endif
2019};
2020
2021struct ClientConnection {
2022 socket_t sock = INVALID_SOCKET;
2023
2024 bool is_open() const { return sock != INVALID_SOCKET; }
2025
2026 ClientConnection() = default;
2027
2028 ~ClientConnection();
2029
2030 ClientConnection(const ClientConnection &) = delete;
2031 ClientConnection &operator=(const ClientConnection &) = delete;
2032
2033 ClientConnection(ClientConnection &&other) noexcept
2034 : sock(other.sock)
2035#ifdef CPPHTTPLIB_SSL_ENABLED
2036 ,
2037 session(other.session)
2038#endif
2039 {
2040 other.sock = INVALID_SOCKET;
2041#ifdef CPPHTTPLIB_SSL_ENABLED
2042 other.session = nullptr;
2043#endif
2044 }
2045
2046 ClientConnection &operator=(ClientConnection &&other) noexcept {
2047 if (this != &other) {
2048 sock = other.sock;
2049 other.sock = INVALID_SOCKET;
2050#ifdef CPPHTTPLIB_SSL_ENABLED
2051 session = other.session;
2052 other.session = nullptr;
2053#endif
2054 }
2055 return *this;
2056 }
2057
2058#ifdef CPPHTTPLIB_SSL_ENABLED
2059 tls::session_t session = nullptr;
2060#endif
2061};
2062
2063namespace detail {
2064
2065struct ChunkedDecoder;
2066
2067struct BodyReader {
2068 Stream *stream = nullptr;
2069 bool has_content_length = false;
2070 size_t content_length = 0;
2071 size_t payload_max_length = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
2072 size_t bytes_read = 0;
2073 bool chunked = false;
2074 bool eof = false;
2075 std::unique_ptr<ChunkedDecoder> chunked_decoder;
2076 Error last_error = Error::Success;
2077
2078 ssize_t read(char *buf, size_t len);
2079 bool has_error() const { return last_error != Error::Success; }
2080};
2081
2082inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
2083 size_t len) {
2084 (void)stream;
2085 return br.read(buf, len);
2086}
2087
2088class decompressor;
2089
2090enum class NoProxyKind {
2091 Wildcard, // "*"
2092 HostnameSuffix, // "example.com" or ".example.com"
2093 IPv4Cidr, // "10.0.0.0/8" (or single IP, treated as /32)
2094 IPv6Cidr, // "fe80::/10" (or single IP, treated as /128)
2095};
2096
2097// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address.
2098// Lets one CIDR matcher cover both families.
2099using IPBytes = std::array<uint8_t, 16>;
2100
2101struct NoProxyEntry {
2102 NoProxyKind kind = NoProxyKind::Wildcard;
2103 std::string hostname_pattern; // lowercased, leading/trailing dot stripped
2104 IPBytes net{};
2105 int prefix_bits = 0;
2106};
2107
2108struct NormalizedTarget {
2109 std::string hostname; // lowercase; brackets and trailing dot removed
2110 bool is_ipv4 = false;
2111 bool is_ipv6 = false;
2112 IPBytes ip{};
2113};
2114
2115} // namespace detail
2116
2117class ClientImpl {
2118public:
2119 explicit ClientImpl(const std::string &host);
2120
2121 explicit ClientImpl(const std::string &host, int port);
2122
2123 explicit ClientImpl(const std::string &host, int port,
2124 const std::string &client_cert_path,
2125 const std::string &client_key_path);
2126
2127 virtual ~ClientImpl();
2128
2129 virtual bool is_valid() const;
2130
2131 struct StreamHandle {
2132 std::unique_ptr<Response> response;
2133 Error error = Error::Success;
2134
2135 StreamHandle() = default;
2136 StreamHandle(const StreamHandle &) = delete;
2137 StreamHandle &operator=(const StreamHandle &) = delete;
2138 StreamHandle(StreamHandle &&) = default;
2139 StreamHandle &operator=(StreamHandle &&) = default;
2140 ~StreamHandle() = default;
2141
2142 bool is_valid() const {
2143 return response != nullptr && error == Error::Success;
2144 }
2145
2146 ssize_t read(char *buf, size_t len);
2147 void parse_trailers_if_needed();
2148 Error get_read_error() const { return body_reader_.last_error; }
2149 bool has_read_error() const { return body_reader_.has_error(); }
2150
2151 bool trailers_parsed_ = false;
2152
2153 private:
2154 friend class ClientImpl;
2155
2156 ssize_t read_with_decompression(char *buf, size_t len);
2157
2158 std::unique_ptr<ClientConnection> connection_;
2159 std::unique_ptr<Stream> socket_stream_;
2160 Stream *stream_ = nullptr;
2161 detail::BodyReader body_reader_;
2162
2163 std::unique_ptr<detail::decompressor> decompressor_;
2164 std::string decompress_buffer_;
2165 size_t decompress_offset_ = 0;
2166 size_t decompressed_bytes_read_ = 0;
2167 };
2168
2169 // clang-format off
2170 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2171 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2172 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2173 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2174 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2175 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2176 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2177 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2178 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2179
2180 Result Head(const std::string &path);
2181 Result Head(const std::string &path, const Headers &headers);
2182
2183 Result Post(const std::string &path);
2184 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2185 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2186 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2187 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2188 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2189 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2190 Result Post(const std::string &path, const Params &params);
2191 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2192 Result Post(const std::string &path, const Headers &headers);
2193 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2194 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2195 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2196 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2197 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2198 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2199 Result Post(const std::string &path, const Headers &headers, const Params &params);
2200 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2201 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2202 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2203 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2204
2205 Result Put(const std::string &path);
2206 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2207 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2208 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2209 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2210 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2211 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2212 Result Put(const std::string &path, const Params &params);
2213 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2214 Result Put(const std::string &path, const Headers &headers);
2215 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2216 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2217 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2218 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2219 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2220 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2221 Result Put(const std::string &path, const Headers &headers, const Params &params);
2222 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2223 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2224 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2225 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2226
2227 Result Patch(const std::string &path);
2228 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2229 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2230 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2231 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2232 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2233 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2234 Result Patch(const std::string &path, const Params &params);
2235 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2236 Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
2237 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2238 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2239 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2240 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2241 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2242 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2243 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2244 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2245 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2246 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2247 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2248
2249 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2250 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2251 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2252 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2253 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2254 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2255 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2256 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2257
2258 Result Options(const std::string &path);
2259 Result Options(const std::string &path, const Headers &headers);
2260 // clang-format on
2261
2262 // Streaming API: Open a stream for reading response body incrementally
2263 // Socket ownership is transferred to StreamHandle for true streaming
2264 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2265 StreamHandle open_stream(const std::string &method, const std::string &path,
2266 const Params &params = {},
2267 const Headers &headers = {},
2268 const std::string &body = {},
2269 const std::string &content_type = {});
2270
2271 bool send(Request &req, Response &res, Error &error);
2272 Result send(const Request &req);
2273
2274 void stop();
2275
2276 std::string host() const;
2277 int port() const;
2278
2279 size_t is_socket_open() const;
2280 socket_t socket() const;
2281
2282 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2283
2284 void set_default_headers(Headers headers);
2285
2286 void
2287 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2288
2289 void set_address_family(int family);
2290 void set_tcp_nodelay(bool on);
2291 void set_ipv6_v6only(bool on);
2292 void set_socket_options(SocketOptions socket_options);
2293
2294 void set_connection_timeout(time_t sec, time_t usec = 0);
2295 template <class Rep, class Period>
2296 void
2297 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2298
2299 void set_read_timeout(time_t sec, time_t usec = 0);
2300 template <class Rep, class Period>
2301 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2302
2303 void set_write_timeout(time_t sec, time_t usec = 0);
2304 template <class Rep, class Period>
2305 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2306
2307 void set_max_timeout(time_t msec);
2308 template <class Rep, class Period>
2309 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2310
2311 void set_basic_auth(const std::string &username, const std::string &password);
2312 void set_bearer_token_auth(const std::string &token);
2313
2314 void set_keep_alive(bool on);
2315 void set_follow_location(bool on);
2316
2317 void set_path_encode(bool on);
2318
2319 void set_compress(bool on);
2320
2321 void set_decompress(bool on);
2322
2323 void set_payload_max_length(size_t length);
2324
2325 void set_interface(const std::string &intf);
2326
2327 void set_proxy(const std::string &host, int port);
2328 void set_proxy_basic_auth(const std::string &username,
2329 const std::string &password);
2330 void set_proxy_bearer_token_auth(const std::string &token);
2331 void set_no_proxy(const std::vector<std::string> &patterns);
2332
2333 void set_logger(Logger logger);
2334 void set_error_logger(ErrorLogger error_logger);
2335
2336protected:
2337 struct Socket {
2338 socket_t sock = INVALID_SOCKET;
2339
2340 // For Mbed TLS compatibility: start_time for request timeout tracking
2341 std::chrono::time_point<std::chrono::steady_clock> start_time_;
2342
2343 bool is_open() const { return sock != INVALID_SOCKET; }
2344
2345#ifdef CPPHTTPLIB_SSL_ENABLED
2346 tls::session_t ssl = nullptr;
2347#endif
2348 };
2349
2350 virtual bool create_and_connect_socket(Socket &socket, Error &error);
2351 virtual bool ensure_socket_connection(Socket &socket, Error &error);
2352 virtual bool setup_proxy_connection(
2353 Socket &socket,
2354 std::chrono::time_point<std::chrono::steady_clock> start_time,
2355 Response &res, bool &success, Error &error);
2356
2357 bool is_proxy_enabled_for_host(const std::string &host) const;
2358
2359 // All of:
2360 // shutdown_ssl
2361 // shutdown_socket
2362 // close_socket
2363 // disconnect
2364 // should ONLY be called when socket_mutex_ is locked, and only when
2365 // no other thread is using the socket.
2366 virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
2367 void shutdown_socket(Socket &socket) const;
2368 void close_socket(Socket &socket);
2369 void disconnect(bool gracefully);
2370
2371 bool process_request(Stream &strm, Request &req, Response &res,
2372 bool close_connection, Error &error);
2373
2374 bool write_content_with_provider(Stream &strm, const Request &req,
2375 Error &error) const;
2376
2377 void copy_settings(const ClientImpl &rhs);
2378
2379 void output_log(const Request &req, const Response &res) const;
2380 void output_error_log(const Error &err, const Request *req) const;
2381
2382 // Socket endpoint information
2383 const std::string host_;
2384 const int port_;
2385
2386 // Current open socket
2387 Socket socket_;
2388 mutable std::mutex socket_mutex_;
2389 std::recursive_mutex request_mutex_;
2390
2391 // These are all protected under socket_mutex
2392 size_t socket_requests_in_flight_ = 0;
2393 std::thread::id socket_requests_are_from_thread_ = std::thread::id();
2394 bool socket_should_be_closed_when_request_is_done_ = false;
2395
2396 // Hostname-IP map
2397 std::map<std::string, std::string> addr_map_;
2398
2399 // Default headers
2400 Headers default_headers_;
2401
2402 // Header writer
2403 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
2404 detail::write_headers;
2405
2406 // Settings
2407 std::string client_cert_path_;
2408 std::string client_key_path_;
2409
2410 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
2411 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
2412 time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
2413 time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
2414 time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
2415 time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
2416 time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;
2417
2418 std::string basic_auth_username_;
2419 std::string basic_auth_password_;
2420 std::string bearer_token_auth_token_;
2421
2422 bool keep_alive_ = false;
2423 bool follow_location_ = false;
2424
2425 bool path_encode_ = true;
2426
2427 int address_family_ = AF_UNSPEC;
2428 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
2429 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
2430 SocketOptions socket_options_ = nullptr;
2431
2432 bool compress_ = false;
2433 bool decompress_ = true;
2434
2435 size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
2436 bool has_payload_max_length_ = false;
2437
2438 std::string interface_;
2439
2440 std::string proxy_host_;
2441 int proxy_port_ = -1;
2442
2443 std::string proxy_basic_auth_username_;
2444 std::string proxy_basic_auth_password_;
2445 std::string proxy_bearer_token_auth_token_;
2446
2447 std::vector<detail::NoProxyEntry> no_proxy_entries_;
2448
2449 mutable detail::NormalizedTarget host_normalized_;
2450 mutable bool host_normalized_valid_ = false;
2451
2452 mutable std::mutex logger_mutex_;
2453 Logger logger_;
2454 ErrorLogger error_logger_;
2455
2456private:
2457 bool send_(Request &req, Response &res, Error &error);
2458 Result send_(Request &&req);
2459
2460 socket_t create_client_socket(Error &error) const;
2461 bool read_response_line(Stream &strm, const Request &req, Response &res,
2462 bool skip_100_continue = true) const;
2463 bool write_request(Stream &strm, Request &req, bool close_connection,
2464 Error &error, bool skip_body = false);
2465 bool write_request_body(Stream &strm, Request &req, Error &error);
2466 void prepare_default_headers(Request &r, bool for_stream,
2467 const std::string &ct);
2468 bool redirect(Request &req, Response &res, Error &error);
2469 bool create_redirect_client(const std::string &scheme,
2470 const std::string &host, int port, Request &req,
2471 Response &res, const std::string &path,
2472 const std::string &location, Error &error);
2473 template <typename ClientType> void setup_redirect_client(ClientType &client);
2474 bool handle_request(Stream &strm, Request &req, Response &res,
2475 bool close_connection, Error &error);
2476 std::unique_ptr<Response> send_with_content_provider_and_receiver(
2477 Request &req, const char *body, size_t content_length,
2478 ContentProvider content_provider,
2479 ContentProviderWithoutLength content_provider_without_length,
2480 const std::string &content_type, ContentReceiver content_receiver,
2481 Error &error);
2482 Result send_with_content_provider_and_receiver(
2483 const std::string &method, const std::string &path,
2484 const Headers &headers, const char *body, size_t content_length,
2485 ContentProvider content_provider,
2486 ContentProviderWithoutLength content_provider_without_length,
2487 const std::string &content_type, ContentReceiver content_receiver,
2488 UploadProgress progress);
2489 ContentProviderWithoutLength get_multipart_content_provider(
2490 const std::string &boundary, const UploadFormDataItems &items,
2491 const FormDataProviderItems &provider_items) const;
2492
2493 virtual bool
2494 process_socket(const Socket &socket,
2495 std::chrono::time_point<std::chrono::steady_clock> start_time,
2496 std::function<bool(Stream &strm)> callback);
2497 virtual bool is_ssl() const;
2498
2499 void transfer_socket_ownership_to_handle(StreamHandle &handle);
2500
2501#ifdef CPPHTTPLIB_SSL_ENABLED
2502public:
2503 void set_digest_auth(const std::string &username,
2504 const std::string &password);
2505 void set_proxy_digest_auth(const std::string &username,
2506 const std::string &password);
2507 void set_ca_cert_path(const std::string &ca_cert_file_path,
2508 const std::string &ca_cert_dir_path = std::string());
2509 void enable_server_certificate_verification(bool enabled);
2510 void enable_server_hostname_verification(bool enabled);
2511 void enable_system_ca(bool enabled);
2512
2513protected:
2514 std::string digest_auth_username_;
2515 std::string digest_auth_password_;
2516 std::string proxy_digest_auth_username_;
2517 std::string proxy_digest_auth_password_;
2518 std::string ca_cert_file_path_;
2519 std::string ca_cert_dir_path_;
2520 bool server_certificate_verification_ = true;
2521 bool server_hostname_verification_ = true;
2522 SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
2523 std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
2524 int last_ssl_error_ = 0;
2525 uint64_t last_backend_error_ = 0;
2526#endif
2527};
2528
2529class Client {
2530public:
2531 // Universal interface
2532 explicit Client(const std::string &scheme_host_port);
2533
2534 explicit Client(const std::string &scheme_host_port,
2535 const std::string &client_cert_path,
2536 const std::string &client_key_path);
2537
2538 // HTTP only interface
2539 explicit Client(const std::string &host, int port);
2540
2541 explicit Client(const std::string &host, int port,
2542 const std::string &client_cert_path,
2543 const std::string &client_key_path);
2544
2545 Client(Client &&) = default;
2546 Client &operator=(Client &&) = default;
2547
2548 ~Client();
2549
2550 bool is_valid() const;
2551
2552 // clang-format off
2553 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2554 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2555 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2556 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2557 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2558 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2559 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2560 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2561 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2562
2563 Result Head(const std::string &path);
2564 Result Head(const std::string &path, const Headers &headers);
2565
2566 Result Post(const std::string &path);
2567 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2568 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2569 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2570 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2571 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2572 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2573 Result Post(const std::string &path, const Params &params);
2574 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2575 Result Post(const std::string &path, const Headers &headers);
2576 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2577 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2578 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2579 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2580 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2581 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2582 Result Post(const std::string &path, const Headers &headers, const Params &params);
2583 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2584 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2585 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2586 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2587
2588 Result Put(const std::string &path);
2589 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2590 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2591 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2592 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2593 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2594 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2595 Result Put(const std::string &path, const Params &params);
2596 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2597 Result Put(const std::string &path, const Headers &headers);
2598 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2599 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2600 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2601 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2602 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2603 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2604 Result Put(const std::string &path, const Headers &headers, const Params &params);
2605 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2606 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2607 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2608 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2609
2610 Result Patch(const std::string &path);
2611 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2612 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2613 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2614 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2615 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2616 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2617 Result Patch(const std::string &path, const Params &params);
2618 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2619 Result Patch(const std::string &path, const Headers &headers);
2620 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2621 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2622 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2623 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2624 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2625 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2626 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2627 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2628 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2629 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2630 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2631
2632 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2633 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2634 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2635 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2636 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2637 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2638 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2639 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2640
2641 Result Options(const std::string &path);
2642 Result Options(const std::string &path, const Headers &headers);
2643 // clang-format on
2644
2645 // Streaming API: Open a stream for reading response body incrementally
2646 // Socket ownership is transferred to StreamHandle for true streaming
2647 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2648 ClientImpl::StreamHandle open_stream(const std::string &method,
2649 const std::string &path,
2650 const Params &params = {},
2651 const Headers &headers = {},
2652 const std::string &body = {},
2653 const std::string &content_type = {});
2654
2655 bool send(Request &req, Response &res, Error &error);
2656 Result send(const Request &req);
2657
2658 void stop();
2659
2660 std::string host() const;
2661 int port() const;
2662
2663 size_t is_socket_open() const;
2664 socket_t socket() const;
2665
2666 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2667
2668 void set_default_headers(Headers headers);
2669
2670 void
2671 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2672
2673 void set_address_family(int family);
2674 void set_tcp_nodelay(bool on);
2675 void set_socket_options(SocketOptions socket_options);
2676
2677 void set_connection_timeout(time_t sec, time_t usec = 0);
2678 template <class Rep, class Period>
2679 void
2680 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2681
2682 void set_read_timeout(time_t sec, time_t usec = 0);
2683 template <class Rep, class Period>
2684 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2685
2686 void set_write_timeout(time_t sec, time_t usec = 0);
2687 template <class Rep, class Period>
2688 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2689
2690 void set_max_timeout(time_t msec);
2691 template <class Rep, class Period>
2692 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2693
2694 void set_basic_auth(const std::string &username, const std::string &password);
2695 void set_bearer_token_auth(const std::string &token);
2696
2697 void set_keep_alive(bool on);
2698 void set_follow_location(bool on);
2699
2700 void set_path_encode(bool on);
2701
2702 void set_compress(bool on);
2703
2704 void set_decompress(bool on);
2705
2706 void set_payload_max_length(size_t length);
2707
2708 void set_interface(const std::string &intf);
2709
2710 void set_proxy(const std::string &host, int port);
2711 void set_proxy_basic_auth(const std::string &username,
2712 const std::string &password);
2713 void set_proxy_bearer_token_auth(const std::string &token);
2714 void set_no_proxy(const std::vector<std::string> &patterns);
2715 void set_logger(Logger logger);
2716 void set_error_logger(ErrorLogger error_logger);
2717
2718private:
2719 std::unique_ptr<ClientImpl> cli_;
2720
2721#ifdef CPPHTTPLIB_SSL_ENABLED
2722public:
2723 void set_digest_auth(const std::string &username,
2724 const std::string &password);
2725 void set_proxy_digest_auth(const std::string &username,
2726 const std::string &password);
2727 void enable_server_certificate_verification(bool enabled);
2728 void enable_server_hostname_verification(bool enabled);
2729 void enable_system_ca(bool enabled);
2730 void set_ca_cert_path(const std::string &ca_cert_file_path,
2731 const std::string &ca_cert_dir_path = std::string());
2732
2733 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2734 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2735
2736 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2737
2738 void set_session_verifier(
2739 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2740
2741 tls::ctx_t tls_context() const;
2742
2743#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2744 void enable_windows_certificate_verification(bool enabled);
2745#endif
2746
2747private:
2748 bool is_ssl_ = false;
2749#endif
2750};
2751
2752#ifdef CPPHTTPLIB_SSL_ENABLED
2753class SSLServer : public Server {
2754public:
2755 SSLServer(const char *cert_path, const char *private_key_path,
2756 const char *client_ca_cert_file_path = nullptr,
2757 const char *client_ca_cert_dir_path = nullptr,
2758 const char *private_key_password = nullptr);
2759
2760 struct PemMemory {
2761 const char *cert_pem;
2762 size_t cert_pem_len;
2763 const char *key_pem;
2764 size_t key_pem_len;
2765 const char *client_ca_pem;
2766 size_t client_ca_pem_len;
2767 const char *private_key_password;
2768 };
2769 explicit SSLServer(const PemMemory &pem);
2770
2771 // The callback receives the ctx_t handle which can be cast to the
2772 // appropriate backend type (SSL_CTX* for OpenSSL,
2773 // tls::impl::MbedTlsContext* for Mbed TLS)
2774 explicit SSLServer(const tls::ContextSetupCallback &setup_callback);
2775
2776 ~SSLServer() override;
2777
2778 bool is_valid() const override;
2779
2780 bool update_certs_pem(const char *cert_pem, const char *key_pem,
2781 const char *client_ca_pem = nullptr,
2782 const char *password = nullptr);
2783
2784 tls::ctx_t tls_context() const { return ctx_; }
2785
2786 int ssl_last_error() const { return last_ssl_error_; }
2787
2788private:
2789 bool process_and_close_socket(socket_t sock) override;
2790
2791 tls::ctx_t ctx_ = nullptr;
2792 std::mutex ctx_mutex_;
2793
2794 int last_ssl_error_ = 0;
2795};
2796
2797class SSLClient final : public ClientImpl {
2798public:
2799 explicit SSLClient(const std::string &host);
2800
2801 explicit SSLClient(const std::string &host, int port);
2802
2803 explicit SSLClient(const std::string &host, int port,
2804 const std::string &client_cert_path,
2805 const std::string &client_key_path,
2806 const std::string &private_key_password = std::string());
2807
2808 struct PemMemory {
2809 const char *cert_pem;
2810 size_t cert_pem_len;
2811 const char *key_pem;
2812 size_t key_pem_len;
2813 const char *private_key_password;
2814 };
2815 explicit SSLClient(const std::string &host, int port, const PemMemory &pem);
2816
2817 ~SSLClient() override;
2818
2819 bool is_valid() const override;
2820
2821 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2822 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2823
2824 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2825
2826 // Post-handshake session verifier (backend-independent)
2827 void set_session_verifier(
2828 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2829
2830 tls::ctx_t tls_context() const { return ctx_; }
2831
2832#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2833 void enable_windows_certificate_verification(bool enabled);
2834#endif
2835
2836private:
2837 bool create_and_connect_socket(Socket &socket, Error &error) override;
2838 bool ensure_socket_connection(Socket &socket, Error &error) override;
2839 void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
2840 void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
2841
2842 bool
2843 process_socket(const Socket &socket,
2844 std::chrono::time_point<std::chrono::steady_clock> start_time,
2845 std::function<bool(Stream &strm)> callback) override;
2846 bool is_ssl() const override;
2847
2848 bool setup_proxy_connection(
2849 Socket &socket,
2850 std::chrono::time_point<std::chrono::steady_clock> start_time,
2851 Response &res, bool &success, Error &error) override;
2852 bool connect_with_proxy(
2853 Socket &sock,
2854 std::chrono::time_point<std::chrono::steady_clock> start_time,
2855 Response &res, bool &success, Error &error);
2856 bool initialize_ssl(Socket &socket, Error &error);
2857
2858 void init_ctx();
2859 void reset_ctx_on_error();
2860
2861 bool load_certs();
2862
2863 tls::ctx_t ctx_ = nullptr;
2864 std::mutex ctx_mutex_;
2865 std::once_flag initialize_cert_;
2866
2867 // Tracks whether a custom CA store was applied via set_ca_cert_store(),
2868 // since the store handle itself is owned by ctx_ and leaves no other trace.
2869 // Used to keep custom CA configuration exclusive with system CA loading.
2870 bool ca_cert_store_set_ = false;
2871
2872 long verify_result_ = 0;
2873
2874 std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
2875
2876#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2877 bool enable_windows_cert_verification_ = true;
2878#endif
2879
2880 friend class ClientImpl;
2881};
2882#endif // CPPHTTPLIB_SSL_ENABLED
2883
2884namespace detail {
2885
2886template <typename T, typename U>
2887inline void duration_to_sec_and_usec(const T &duration, U callback) {
2888 auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
2889 auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
2890 duration - std::chrono::seconds(sec))
2891 .count();
2892 callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
2893}
2894
2895template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2896 return N - 1;
2897}
2898
2899inline bool is_numeric(const std::string &str) {
2900 return !str.empty() &&
2901 std::all_of(str.cbegin(), str.cend(),
2902 [](unsigned char c) { return std::isdigit(c); });
2903}
2904
2905inline size_t get_header_value_u64(const Headers &headers,
2906 const std::string &key, size_t def,
2907 size_t id, bool &is_invalid_value) {
2908 is_invalid_value = false;
2909 auto rng = headers.equal_range(key);
2910 auto it = rng.first;
2911 std::advance(it, static_cast<ssize_t>(id));
2912 if (it != rng.second) {
2913 if (is_numeric(it->second)) {
2914 return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
2915 } else {
2916 is_invalid_value = true;
2917 }
2918 }
2919 return def;
2920}
2921
2922inline size_t get_header_value_u64(const Headers &headers,
2923 const std::string &key, size_t def,
2924 size_t id) {
2925 auto dummy = false;
2926 return get_header_value_u64(headers, key, def, id, dummy);
2927}
2928
2929} // namespace detail
2930
2931template <class Rep, class Period>
2932inline Server &
2933Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2934 detail::duration_to_sec_and_usec(
2935 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2936 return *this;
2937}
2938
2939template <class Rep, class Period>
2940inline Server &
2941Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2942 detail::duration_to_sec_and_usec(
2943 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2944 return *this;
2945}
2946
2947template <class Rep, class Period>
2948inline Server &
2949Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
2950 detail::duration_to_sec_and_usec(
2951 duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
2952 return *this;
2953}
2954
2955template <class Rep, class Period>
2956inline void ClientImpl::set_connection_timeout(
2957 const std::chrono::duration<Rep, Period> &duration) {
2958 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
2959 set_connection_timeout(sec, usec);
2960 });
2961}
2962
2963template <class Rep, class Period>
2964inline void ClientImpl::set_read_timeout(
2965 const std::chrono::duration<Rep, Period> &duration) {
2966 detail::duration_to_sec_and_usec(
2967 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2968}
2969
2970template <class Rep, class Period>
2971inline void ClientImpl::set_write_timeout(
2972 const std::chrono::duration<Rep, Period> &duration) {
2973 detail::duration_to_sec_and_usec(
2974 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2975}
2976
2977template <class Rep, class Period>
2978inline void ClientImpl::set_max_timeout(
2979 const std::chrono::duration<Rep, Period> &duration) {
2980 auto msec =
2981 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
2982 set_max_timeout(msec);
2983}
2984
2985template <class Rep, class Period>
2986inline void Client::set_connection_timeout(
2987 const std::chrono::duration<Rep, Period> &duration) {
2988 cli_->set_connection_timeout(duration);
2989}
2990
2991template <class Rep, class Period>
2992inline void
2993Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2994 cli_->set_read_timeout(duration);
2995}
2996
2997template <class Rep, class Period>
2998inline void
2999Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
3000 cli_->set_write_timeout(duration);
3001}
3002
3003inline void Client::set_max_timeout(time_t msec) {
3004 cli_->set_max_timeout(msec);
3005}
3006
3007template <class Rep, class Period>
3008inline void
3009Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
3010 cli_->set_max_timeout(duration);
3011}
3012
3013/*
3014 * Forward declarations and types that will be part of the .h file if split into
3015 * .h + .cc.
3016 */
3017
3018std::string hosted_at(const std::string &hostname);
3019
3020void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
3021
3022// JavaScript-style URL encoding/decoding functions
3023std::string encode_uri_component(const std::string &value);
3024std::string encode_uri(const std::string &value);
3025std::string decode_uri_component(const std::string &value);
3026std::string decode_uri(const std::string &value);
3027
3028// RFC 3986 compliant URL component encoding/decoding functions
3029std::string encode_path_component(const std::string &component);
3030std::string decode_path_component(const std::string &component);
3031std::string encode_query_component(const std::string &component,
3032 bool space_as_plus = true);
3033std::string decode_query_component(const std::string &component,
3034 bool plus_as_space = true);
3035
3036std::string sanitize_filename(const std::string &filename);
3037
3038std::string append_query_params(const std::string &path, const Params &params);
3039
3040std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
3041
3042std::pair<std::string, std::string>
3043make_basic_authentication_header(const std::string &username,
3044 const std::string &password,
3045 bool is_proxy = false);
3046
3047namespace detail {
3048
3049#if defined(_WIN32)
3050inline std::wstring u8string_to_wstring(const char *s) {
3051 if (!s) { return std::wstring(); }
3052
3053 auto len = static_cast<int>(strlen(s));
3054 if (!len) { return std::wstring(); }
3055
3056 auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
3057 if (!wlen) { return std::wstring(); }
3058
3059 std::wstring ws;
3060 ws.resize(wlen);
3061 wlen = ::MultiByteToWideChar(
3062 CP_UTF8, 0, s, len,
3063 const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
3064 if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
3065 return ws;
3066}
3067#endif
3068
3069struct FileStat {
3070 FileStat(const std::string &path);
3071 bool is_file() const;
3072 bool is_dir() const;
3073 time_t mtime() const;
3074 size_t size() const;
3075
3076private:
3077#if defined(_WIN32)
3078 struct _stat st_;
3079#else
3080 struct stat st_;
3081#endif
3082 int ret_ = -1;
3083};
3084
3085std::string make_host_and_port_string(const std::string &host, int port,
3086 bool is_ssl);
3087
3088std::string trim_copy(const std::string &s);
3089
3090void divide(
3091 const char *data, std::size_t size, char d,
3092 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3093 fn);
3094
3095void divide(
3096 const std::string &str, char d,
3097 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3098 fn);
3099
3100void split(const char *b, const char *e, char d,
3101 std::function<void(const char *, const char *)> fn);
3102
3103void split(const char *b, const char *e, char d, size_t m,
3104 std::function<void(const char *, const char *)> fn);
3105
3106bool process_client_socket(
3107 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3108 time_t write_timeout_sec, time_t write_timeout_usec,
3109 time_t max_timeout_msec,
3110 std::chrono::time_point<std::chrono::steady_clock> start_time,
3111 std::function<bool(Stream &)> callback);
3112
3113socket_t create_client_socket(const std::string &host, const std::string &ip,
3114 int port, int address_family, bool tcp_nodelay,
3115 bool ipv6_v6only, SocketOptions socket_options,
3116 time_t connection_timeout_sec,
3117 time_t connection_timeout_usec,
3118 time_t read_timeout_sec, time_t read_timeout_usec,
3119 time_t write_timeout_sec,
3120 time_t write_timeout_usec,
3121 const std::string &intf, Error &error);
3122
3123const char *get_header_value(const Headers &headers, const std::string &key,
3124 const char *def, size_t id);
3125
3126std::string params_to_query_str(const Params &params);
3127
3128void parse_query_text(const char *data, std::size_t size, Params &params);
3129
3130void parse_query_text(const std::string &s, Params &params);
3131
3132bool parse_multipart_boundary(const std::string &content_type,
3133 std::string &boundary);
3134
3135bool parse_range_header(const std::string &s, Ranges &ranges);
3136
3137bool parse_accept_header(const std::string &s,
3138 std::vector<std::string> &content_types);
3139
3140ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
3141
3142ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
3143
3144enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
3145
3146EncodingType encoding_type(const Request &req, const Response &res);
3147
3148class BufferStream final : public Stream {
3149public:
3150 BufferStream() = default;
3151 ~BufferStream() override = default;
3152
3153 bool is_readable() const override;
3154 bool wait_readable() const override;
3155 bool wait_writable() const override;
3156 ssize_t read(char *ptr, size_t size) override;
3157 ssize_t write(const char *ptr, size_t size) override;
3158 void get_remote_ip_and_port(std::string &ip, int &port) const override;
3159 void get_local_ip_and_port(std::string &ip, int &port) const override;
3160 socket_t socket() const override;
3161 time_t duration() const override;
3162
3163 const std::string &get_buffer() const;
3164
3165private:
3166 std::string buffer;
3167 size_t position = 0;
3168};
3169
3170class compressor {
3171public:
3172 virtual ~compressor() = default;
3173
3174 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3175 virtual bool compress(const char *data, size_t data_length, bool last,
3176 Callback callback) = 0;
3177};
3178
3179class decompressor {
3180public:
3181 virtual ~decompressor() = default;
3182
3183 virtual bool is_valid() const = 0;
3184
3185 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3186 virtual bool decompress(const char *data, size_t data_length,
3187 Callback callback) = 0;
3188};
3189
3190class nocompressor final : public compressor {
3191public:
3192 ~nocompressor() override = default;
3193
3194 bool compress(const char *data, size_t data_length, bool /*last*/,
3195 Callback callback) override;
3196};
3197
3198#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3199class gzip_compressor final : public compressor {
3200public:
3201 gzip_compressor();
3202 ~gzip_compressor() override;
3203
3204 bool compress(const char *data, size_t data_length, bool last,
3205 Callback callback) override;
3206
3207private:
3208 bool is_valid_ = false;
3209 z_stream strm_;
3210};
3211
3212class gzip_decompressor final : public decompressor {
3213public:
3214 gzip_decompressor();
3215 ~gzip_decompressor() override;
3216
3217 bool is_valid() const override;
3218
3219 bool decompress(const char *data, size_t data_length,
3220 Callback callback) override;
3221
3222private:
3223 bool is_valid_ = false;
3224 z_stream strm_;
3225};
3226#endif
3227
3228#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3229class brotli_compressor final : public compressor {
3230public:
3231 brotli_compressor();
3232 ~brotli_compressor();
3233
3234 bool compress(const char *data, size_t data_length, bool last,
3235 Callback callback) override;
3236
3237private:
3238 BrotliEncoderState *state_ = nullptr;
3239};
3240
3241class brotli_decompressor final : public decompressor {
3242public:
3243 brotli_decompressor();
3244 ~brotli_decompressor();
3245
3246 bool is_valid() const override;
3247
3248 bool decompress(const char *data, size_t data_length,
3249 Callback callback) override;
3250
3251private:
3252 BrotliDecoderResult decoder_r;
3253 BrotliDecoderState *decoder_s = nullptr;
3254};
3255#endif
3256
3257#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3258class zstd_compressor : public compressor {
3259public:
3260 zstd_compressor();
3261 ~zstd_compressor();
3262
3263 bool compress(const char *data, size_t data_length, bool last,
3264 Callback callback) override;
3265
3266private:
3267 ZSTD_CCtx *ctx_ = nullptr;
3268};
3269
3270class zstd_decompressor : public decompressor {
3271public:
3272 zstd_decompressor();
3273 ~zstd_decompressor();
3274
3275 bool is_valid() const override;
3276
3277 bool decompress(const char *data, size_t data_length,
3278 Callback callback) override;
3279
3280private:
3281 ZSTD_DCtx *ctx_ = nullptr;
3282};
3283#endif
3284
3285// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
3286// to store data. The call can set memory on stack for performance.
3287class stream_line_reader {
3288public:
3289 stream_line_reader(Stream &strm, char *fixed_buffer,
3290 size_t fixed_buffer_size);
3291 const char *ptr() const;
3292 size_t size() const;
3293 bool end_with_crlf() const;
3294 bool getline();
3295
3296private:
3297 void append(char c);
3298
3299 Stream &strm_;
3300 char *fixed_buffer_;
3301 const size_t fixed_buffer_size_;
3302 size_t fixed_buffer_used_size_ = 0;
3303 std::string growable_buffer_;
3304};
3305
3306bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
3307 const Headers &src_headers);
3308
3309struct ChunkedDecoder {
3310 Stream &strm;
3311 size_t chunk_remaining = 0;
3312 bool finished = false;
3313 char line_buf[64];
3314 size_t last_chunk_total = 0;
3315 size_t last_chunk_offset = 0;
3316
3317 explicit ChunkedDecoder(Stream &s);
3318
3319 ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset,
3320 size_t &out_chunk_total);
3321
3322 bool parse_trailers_into(Headers &dest, const Headers &src_headers);
3323};
3324
3325class mmap {
3326public:
3327 mmap(const char *path);
3328 ~mmap();
3329
3330 bool open(const char *path);
3331 void close();
3332
3333 bool is_open() const;
3334 size_t size() const;
3335 const char *data() const;
3336
3337private:
3338#if defined(_WIN32)
3339 HANDLE hFile_ = NULL;
3340 HANDLE hMapping_ = NULL;
3341#else
3342 int fd_ = -1;
3343#endif
3344 size_t size_ = 0;
3345 void *addr_ = nullptr;
3346 bool is_open_empty_file = false;
3347};
3348
3349// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
3350namespace fields {
3351
3352bool is_token_char(char c);
3353bool is_token(const std::string &s);
3354bool is_field_name(const std::string &s);
3355bool is_vchar(char c);
3356bool is_obs_text(char c);
3357bool is_field_vchar(char c);
3358bool is_field_content(const std::string &s);
3359bool is_field_value(const std::string &s);
3360
3361} // namespace fields
3362} // namespace detail
3363
3364/*
3365 * TLS Abstraction Layer Declarations
3366 */
3367
3368#ifdef CPPHTTPLIB_SSL_ENABLED
3369// TLS abstraction layer - backend-specific type declarations
3370#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
3371namespace tls {
3372namespace impl {
3373
3374// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own
3375// cert/key). This struct is accessible via tls::impl for use in SSL context
3376// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
3377struct MbedTlsContext {
3378 mbedtls_ssl_config conf;
3379 mbedtls_entropy_context entropy;
3380 mbedtls_ctr_drbg_context ctr_drbg;
3381 mbedtls_x509_crt ca_chain;
3382 mbedtls_x509_crt own_cert;
3383 mbedtls_pk_context own_key;
3384 bool is_server = false;
3385 bool verify_client = false;
3386 bool has_verify_callback = false;
3387
3388 MbedTlsContext();
3389 ~MbedTlsContext();
3390
3391 MbedTlsContext(const MbedTlsContext &) = delete;
3392 MbedTlsContext &operator=(const MbedTlsContext &) = delete;
3393};
3394
3395} // namespace impl
3396} // namespace tls
3397#endif
3398
3399#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
3400namespace tls {
3401namespace impl {
3402
3403// wolfSSL context wrapper (holds WOLFSSL_CTX and related state).
3404// This struct is accessible via tls::impl for use in SSL context
3405// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*).
3406struct WolfSSLContext {
3407 WOLFSSL_CTX *ctx = nullptr;
3408 bool is_server = false;
3409 bool verify_client = false;
3410 bool has_verify_callback = false;
3411 std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
3412
3413 WolfSSLContext();
3414 ~WolfSSLContext();
3415
3416 WolfSSLContext(const WolfSSLContext &) = delete;
3417 WolfSSLContext &operator=(const WolfSSLContext &) = delete;
3418};
3419
3420// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx
3421struct WolfSSLCAStore {
3422 std::string pem_data;
3423};
3424
3425} // namespace impl
3426} // namespace tls
3427#endif
3428
3429#endif // CPPHTTPLIB_SSL_ENABLED
3430
3431namespace stream {
3432
3433class Result {
3434public:
3435 Result();
3436 explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192);
3437 Result(Result &&other) noexcept;
3438 Result &operator=(Result &&other) noexcept;
3439 Result(const Result &) = delete;
3440 Result &operator=(const Result &) = delete;
3441
3442 // Response info
3443 bool is_valid() const;
3444 explicit operator bool() const;
3445 int status() const;
3446 const Headers &headers() const;
3447 std::string get_header_value(const std::string &key,
3448 const char *def = "") const;
3449 bool has_header(const std::string &key) const;
3450 Error error() const;
3451 Error read_error() const;
3452 bool has_read_error() const;
3453
3454 // Stream reading
3455 bool next();
3456 const char *data() const;
3457 size_t size() const;
3458 std::string read_all();
3459
3460private:
3461 ClientImpl::StreamHandle handle_;
3462 std::string buffer_;
3463 size_t current_size_ = 0;
3464 size_t chunk_size_;
3465 bool finished_ = false;
3466};
3467
3468// GET
3469template <typename ClientType>
3470inline Result Get(ClientType &cli, const std::string &path,
3471 size_t chunk_size = 8192) {
3472 return Result{cli.open_stream("GET", path), chunk_size};
3473}
3474
3475template <typename ClientType>
3476inline Result Get(ClientType &cli, const std::string &path,
3477 const Headers &headers, size_t chunk_size = 8192) {
3478 return Result{cli.open_stream("GET", path, {}, headers), chunk_size};
3479}
3480
3481template <typename ClientType>
3482inline Result Get(ClientType &cli, const std::string &path,
3483 const Params &params, size_t chunk_size = 8192) {
3484 return Result{cli.open_stream("GET", path, params), chunk_size};
3485}
3486
3487template <typename ClientType>
3488inline Result Get(ClientType &cli, const std::string &path,
3489 const Params &params, const Headers &headers,
3490 size_t chunk_size = 8192) {
3491 return Result{cli.open_stream("GET", path, params, headers), chunk_size};
3492}
3493
3494// POST
3495template <typename ClientType>
3496inline Result Post(ClientType &cli, const std::string &path,
3497 const std::string &body, const std::string &content_type,
3498 size_t chunk_size = 8192) {
3499 return Result{cli.open_stream("POST", path, {}, {}, body, content_type),
3500 chunk_size};
3501}
3502
3503template <typename ClientType>
3504inline Result Post(ClientType &cli, const std::string &path,
3505 const Headers &headers, const std::string &body,
3506 const std::string &content_type, size_t chunk_size = 8192) {
3507 return Result{cli.open_stream("POST", path, {}, headers, body, content_type),
3508 chunk_size};
3509}
3510
3511template <typename ClientType>
3512inline Result Post(ClientType &cli, const std::string &path,
3513 const Params &params, const std::string &body,
3514 const std::string &content_type, size_t chunk_size = 8192) {
3515 return Result{cli.open_stream("POST", path, params, {}, body, content_type),
3516 chunk_size};
3517}
3518
3519template <typename ClientType>
3520inline Result Post(ClientType &cli, const std::string &path,
3521 const Params &params, const Headers &headers,
3522 const std::string &body, const std::string &content_type,
3523 size_t chunk_size = 8192) {
3524 return Result{
3525 cli.open_stream("POST", path, params, headers, body, content_type),
3526 chunk_size};
3527}
3528
3529// PUT
3530template <typename ClientType>
3531inline Result Put(ClientType &cli, const std::string &path,
3532 const std::string &body, const std::string &content_type,
3533 size_t chunk_size = 8192) {
3534 return Result{cli.open_stream("PUT", path, {}, {}, body, content_type),
3535 chunk_size};
3536}
3537
3538template <typename ClientType>
3539inline Result Put(ClientType &cli, const std::string &path,
3540 const Headers &headers, const std::string &body,
3541 const std::string &content_type, size_t chunk_size = 8192) {
3542 return Result{cli.open_stream("PUT", path, {}, headers, body, content_type),
3543 chunk_size};
3544}
3545
3546template <typename ClientType>
3547inline Result Put(ClientType &cli, const std::string &path,
3548 const Params &params, const std::string &body,
3549 const std::string &content_type, size_t chunk_size = 8192) {
3550 return Result{cli.open_stream("PUT", path, params, {}, body, content_type),
3551 chunk_size};
3552}
3553
3554template <typename ClientType>
3555inline Result Put(ClientType &cli, const std::string &path,
3556 const Params &params, const Headers &headers,
3557 const std::string &body, const std::string &content_type,
3558 size_t chunk_size = 8192) {
3559 return Result{
3560 cli.open_stream("PUT", path, params, headers, body, content_type),
3561 chunk_size};
3562}
3563
3564// PATCH
3565template <typename ClientType>
3566inline Result Patch(ClientType &cli, const std::string &path,
3567 const std::string &body, const std::string &content_type,
3568 size_t chunk_size = 8192) {
3569 return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type),
3570 chunk_size};
3571}
3572
3573template <typename ClientType>
3574inline Result Patch(ClientType &cli, const std::string &path,
3575 const Headers &headers, const std::string &body,
3576 const std::string &content_type, size_t chunk_size = 8192) {
3577 return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type),
3578 chunk_size};
3579}
3580
3581template <typename ClientType>
3582inline Result Patch(ClientType &cli, const std::string &path,
3583 const Params &params, const std::string &body,
3584 const std::string &content_type, size_t chunk_size = 8192) {
3585 return Result{cli.open_stream("PATCH", path, params, {}, body, content_type),
3586 chunk_size};
3587}
3588
3589template <typename ClientType>
3590inline Result Patch(ClientType &cli, const std::string &path,
3591 const Params &params, const Headers &headers,
3592 const std::string &body, const std::string &content_type,
3593 size_t chunk_size = 8192) {
3594 return Result{
3595 cli.open_stream("PATCH", path, params, headers, body, content_type),
3596 chunk_size};
3597}
3598
3599// DELETE
3600template <typename ClientType>
3601inline Result Delete(ClientType &cli, const std::string &path,
3602 size_t chunk_size = 8192) {
3603 return Result{cli.open_stream("DELETE", path), chunk_size};
3604}
3605
3606template <typename ClientType>
3607inline Result Delete(ClientType &cli, const std::string &path,
3608 const Headers &headers, size_t chunk_size = 8192) {
3609 return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size};
3610}
3611
3612template <typename ClientType>
3613inline Result Delete(ClientType &cli, const std::string &path,
3614 const std::string &body, const std::string &content_type,
3615 size_t chunk_size = 8192) {
3616 return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type),
3617 chunk_size};
3618}
3619
3620template <typename ClientType>
3621inline Result Delete(ClientType &cli, const std::string &path,
3622 const Headers &headers, const std::string &body,
3623 const std::string &content_type,
3624 size_t chunk_size = 8192) {
3625 return Result{
3626 cli.open_stream("DELETE", path, {}, headers, body, content_type),
3627 chunk_size};
3628}
3629
3630template <typename ClientType>
3631inline Result Delete(ClientType &cli, const std::string &path,
3632 const Params &params, size_t chunk_size = 8192) {
3633 return Result{cli.open_stream("DELETE", path, params), chunk_size};
3634}
3635
3636template <typename ClientType>
3637inline Result Delete(ClientType &cli, const std::string &path,
3638 const Params &params, const Headers &headers,
3639 size_t chunk_size = 8192) {
3640 return Result{cli.open_stream("DELETE", path, params, headers), chunk_size};
3641}
3642
3643template <typename ClientType>
3644inline Result Delete(ClientType &cli, const std::string &path,
3645 const Params &params, const std::string &body,
3646 const std::string &content_type,
3647 size_t chunk_size = 8192) {
3648 return Result{cli.open_stream("DELETE", path, params, {}, body, content_type),
3649 chunk_size};
3650}
3651
3652template <typename ClientType>
3653inline Result Delete(ClientType &cli, const std::string &path,
3654 const Params &params, const Headers &headers,
3655 const std::string &body, const std::string &content_type,
3656 size_t chunk_size = 8192) {
3657 return Result{
3658 cli.open_stream("DELETE", path, params, headers, body, content_type),
3659 chunk_size};
3660}
3661
3662// HEAD
3663template <typename ClientType>
3664inline Result Head(ClientType &cli, const std::string &path,
3665 size_t chunk_size = 8192) {
3666 return Result{cli.open_stream("HEAD", path), chunk_size};
3667}
3668
3669template <typename ClientType>
3670inline Result Head(ClientType &cli, const std::string &path,
3671 const Headers &headers, size_t chunk_size = 8192) {
3672 return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size};
3673}
3674
3675template <typename ClientType>
3676inline Result Head(ClientType &cli, const std::string &path,
3677 const Params &params, size_t chunk_size = 8192) {
3678 return Result{cli.open_stream("HEAD", path, params), chunk_size};
3679}
3680
3681template <typename ClientType>
3682inline Result Head(ClientType &cli, const std::string &path,
3683 const Params &params, const Headers &headers,
3684 size_t chunk_size = 8192) {
3685 return Result{cli.open_stream("HEAD", path, params, headers), chunk_size};
3686}
3687
3688// OPTIONS
3689template <typename ClientType>
3690inline Result Options(ClientType &cli, const std::string &path,
3691 size_t chunk_size = 8192) {
3692 return Result{cli.open_stream("OPTIONS", path), chunk_size};
3693}
3694
3695template <typename ClientType>
3696inline Result Options(ClientType &cli, const std::string &path,
3697 const Headers &headers, size_t chunk_size = 8192) {
3698 return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size};
3699}
3700
3701template <typename ClientType>
3702inline Result Options(ClientType &cli, const std::string &path,
3703 const Params &params, size_t chunk_size = 8192) {
3704 return Result{cli.open_stream("OPTIONS", path, params), chunk_size};
3705}
3706
3707template <typename ClientType>
3708inline Result Options(ClientType &cli, const std::string &path,
3709 const Params &params, const Headers &headers,
3710 size_t chunk_size = 8192) {
3711 return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size};
3712}
3713
3714} // namespace stream
3715
3716namespace sse {
3717
3718struct SSEMessage {
3719 std::string event; // Event type (default: "message")
3720 std::string data; // Event payload
3721 std::string id; // Event ID for Last-Event-ID header
3722
3723 SSEMessage();
3724 void clear();
3725};
3726
3727class SSEClient {
3728public:
3729 using MessageHandler = std::function<void(const SSEMessage &)>;
3730 using ErrorHandler = std::function<void(Error)>;
3731 using OpenHandler = std::function<void()>;
3732
3733 SSEClient(Client &client, const std::string &path);
3734 SSEClient(Client &client, const std::string &path, const Headers &headers);
3735 ~SSEClient();
3736
3737 SSEClient(const SSEClient &) = delete;
3738 SSEClient &operator=(const SSEClient &) = delete;
3739
3740 // Event handlers
3741 SSEClient &on_message(MessageHandler handler);
3742 SSEClient &on_event(const std::string &type, MessageHandler handler);
3743 SSEClient &on_open(OpenHandler handler);
3744 SSEClient &on_error(ErrorHandler handler);
3745 SSEClient &set_reconnect_interval(int ms);
3746 SSEClient &set_max_reconnect_attempts(int n);
3747
3748 // Update headers (thread-safe)
3749 SSEClient &set_headers(const Headers &headers);
3750
3751 // State accessors
3752 bool is_connected() const;
3753 const std::string &last_event_id() const;
3754
3755 // Blocking start - runs event loop with auto-reconnect
3756 void start();
3757
3758 // Non-blocking start - runs in background thread
3759 void start_async();
3760
3761 // Stop the client (thread-safe)
3762 void stop();
3763
3764private:
3765 bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
3766 void run_event_loop();
3767 void dispatch_event(const SSEMessage &msg);
3768 bool should_reconnect(int count) const;
3769 void wait_for_reconnect();
3770
3771 // Client and path
3772 Client &client_;
3773 std::string path_;
3774 Headers headers_;
3775 mutable std::mutex headers_mutex_;
3776
3777 // Callbacks
3778 MessageHandler on_message_;
3779 std::map<std::string, MessageHandler> event_handlers_;
3780 OpenHandler on_open_;
3781 ErrorHandler on_error_;
3782
3783 // Configuration
3784 int reconnect_interval_ms_ = 3000;
3785 int max_reconnect_attempts_ = 0; // 0 = unlimited
3786
3787 // State
3788 std::atomic<bool> running_{false};
3789 std::atomic<bool> connected_{false};
3790 std::string last_event_id_;
3791
3792 // Async support
3793 std::thread async_thread_;
3794};
3795
3796} // namespace sse
3797
3798namespace ws {
3799
3800enum class Opcode : uint8_t {
3801 Continuation = 0x0,
3802 Text = 0x1,
3803 Binary = 0x2,
3804 Close = 0x8,
3805 Ping = 0x9,
3806 Pong = 0xA,
3807};
3808
3809enum class CloseStatus : uint16_t {
3810 Normal = 1000,
3811 GoingAway = 1001,
3812 ProtocolError = 1002,
3813 UnsupportedData = 1003,
3814 NoStatus = 1005,
3815 Abnormal = 1006,
3816 InvalidPayload = 1007,
3817 PolicyViolation = 1008,
3818 MessageTooBig = 1009,
3819 MandatoryExtension = 1010,
3820 InternalError = 1011,
3821};
3822
3823enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 };
3824
3825class WebSocket {
3826public:
3827 WebSocket(const WebSocket &) = delete;
3828 WebSocket &operator=(const WebSocket &) = delete;
3829 ~WebSocket();
3830
3831 ReadResult read(std::string &msg);
3832 bool send(const std::string &data);
3833 bool send(const char *data, size_t len);
3834 void close(CloseStatus status = CloseStatus::Normal,
3835 const std::string &reason = "");
3836 const Request &request() const;
3837 bool is_open() const;
3838
3839private:
3840 friend class httplib::Server;
3841 friend class WebSocketClient;
3842
3843 WebSocket(
3844 Stream &strm, const Request &req, bool is_server,
3845 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3846 int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3847 : strm_(strm), req_(req), is_server_(is_server),
3848 ping_interval_sec_(ping_interval_sec),
3849 max_missed_pongs_(max_missed_pongs) {
3850 start_heartbeat();
3851 }
3852
3853 WebSocket(
3854 std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
3855 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3856 int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3857 : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
3858 is_server_(is_server), ping_interval_sec_(ping_interval_sec),
3859 max_missed_pongs_(max_missed_pongs) {
3860 start_heartbeat();
3861 }
3862
3863 void start_heartbeat();
3864 bool send_frame(Opcode op, const char *data, size_t len, bool fin = true);
3865
3866 Stream &strm_;
3867 std::unique_ptr<Stream> owned_strm_;
3868 Request req_;
3869 bool is_server_;
3870 time_t ping_interval_sec_;
3871 int max_missed_pongs_;
3872 int unacked_pings_ = 0;
3873 std::atomic<bool> closed_{false};
3874 std::mutex write_mutex_;
3875 std::thread ping_thread_;
3876 std::mutex ping_mutex_;
3877 std::condition_variable ping_cv_;
3878};
3879
3880class WebSocketClient {
3881public:
3882 explicit WebSocketClient(const std::string &scheme_host_port_path,
3883 const Headers &headers = {});
3884
3885 ~WebSocketClient();
3886 WebSocketClient(const WebSocketClient &) = delete;
3887 WebSocketClient &operator=(const WebSocketClient &) = delete;
3888
3889 bool is_valid() const;
3890
3891 bool connect();
3892 ReadResult read(std::string &msg);
3893 bool send(const std::string &data);
3894 bool send(const char *data, size_t len);
3895 void close(CloseStatus status = CloseStatus::Normal,
3896 const std::string &reason = "");
3897 bool is_open() const;
3898 const std::string &subprotocol() const;
3899 void set_read_timeout(time_t sec, time_t usec = 0);
3900 void set_write_timeout(time_t sec, time_t usec = 0);
3901 void set_websocket_ping_interval(time_t sec);
3902 void set_websocket_max_missed_pongs(int count);
3903 void set_tcp_nodelay(bool on);
3904 void set_address_family(int family);
3905 void set_ipv6_v6only(bool on);
3906 void set_socket_options(SocketOptions socket_options);
3907 void set_connection_timeout(time_t sec, time_t usec = 0);
3908 void set_interface(const std::string &intf);
3909 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
3910
3911#ifdef CPPHTTPLIB_SSL_ENABLED
3912 void set_ca_cert_path(const std::string &path);
3913 void set_ca_cert_store(tls::ca_store_t store);
3914 void load_ca_cert_store(const char *ca_cert, std::size_t size);
3915 void enable_server_certificate_verification(bool enabled);
3916 void enable_system_ca(bool enabled);
3917#endif
3918
3919private:
3920 void shutdown_and_close();
3921 bool create_stream(std::unique_ptr<Stream> &strm);
3922
3923 std::string host_;
3924 int port_;
3925 std::string path_;
3926 Headers headers_;
3927 std::string subprotocol_;
3928 bool is_valid_ = false;
3929 socket_t sock_ = INVALID_SOCKET;
3930 std::unique_ptr<WebSocket> ws_;
3931 time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND;
3932 time_t read_timeout_usec_ = 0;
3933 time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
3934 time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
3935 time_t websocket_ping_interval_sec_ =
3937 int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
3938 int address_family_ = AF_UNSPEC;
3939 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
3940 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
3941 SocketOptions socket_options_ = nullptr;
3942 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
3943 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
3944 std::string interface_;
3945
3946 // Hostname-IP map
3947 std::map<std::string, std::string> addr_map_;
3948
3949#ifdef CPPHTTPLIB_SSL_ENABLED
3950 bool is_ssl_ = false;
3951 tls::ctx_t tls_ctx_ = nullptr;
3952 tls::session_t tls_session_ = nullptr;
3953 std::string ca_cert_file_path_;
3954 bool custom_ca_loaded_ = false;
3955 bool certs_loaded_ = false;
3956 SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
3957 bool server_certificate_verification_ = true;
3958#endif
3959};
3960
3961namespace impl {
3962
3963bool is_valid_utf8(const std::string &s);
3964
3965bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload,
3966 bool &fin, bool expect_masked, size_t max_len);
3967
3968} // namespace impl
3969
3970} // namespace ws
3971
3972// ----------------------------------------------------------------------------
3973
3974/*
3975 * Implementation that will be part of the .cc file if split into .h + .cc.
3976 */
3977
3978namespace stream {
3979
3980// stream::Result implementations
3981inline Result::Result() : chunk_size_(8192) {}
3982
3983inline Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size)
3984 : handle_(std::move(handle)), chunk_size_(chunk_size) {}
3985
3986inline Result::Result(Result &&other) noexcept
3987 : handle_(std::move(other.handle_)), buffer_(std::move(other.buffer_)),
3988 current_size_(other.current_size_), chunk_size_(other.chunk_size_),
3989 finished_(other.finished_) {
3990 other.current_size_ = 0;
3991 other.finished_ = true;
3992}
3993
3994inline Result &Result::operator=(Result &&other) noexcept {
3995 if (this != &other) {
3996 handle_ = std::move(other.handle_);
3997 buffer_ = std::move(other.buffer_);
3998 current_size_ = other.current_size_;
3999 chunk_size_ = other.chunk_size_;
4000 finished_ = other.finished_;
4001 other.current_size_ = 0;
4002 other.finished_ = true;
4003 }
4004 return *this;
4005}
4006
4007inline bool Result::is_valid() const { return handle_.is_valid(); }
4008inline Result::operator bool() const { return is_valid(); }
4009
4010inline int Result::status() const {
4011 return handle_.response ? handle_.response->status : -1;
4012}
4013
4014inline const Headers &Result::headers() const {
4015 static const Headers empty_headers;
4016 return handle_.response ? handle_.response->headers : empty_headers;
4017}
4018
4019inline std::string Result::get_header_value(const std::string &key,
4020 const char *def) const {
4021 return handle_.response ? handle_.response->get_header_value(key, def) : def;
4022}
4023
4024inline bool Result::has_header(const std::string &key) const {
4025 return handle_.response ? handle_.response->has_header(key) : false;
4026}
4027
4028inline Error Result::error() const { return handle_.error; }
4029inline Error Result::read_error() const { return handle_.get_read_error(); }
4030inline bool Result::has_read_error() const { return handle_.has_read_error(); }
4031
4032inline bool Result::next() {
4033 if (!handle_.is_valid() || finished_) { return false; }
4034
4035 if (buffer_.size() < chunk_size_) { buffer_.resize(chunk_size_); }
4036
4037 ssize_t n = handle_.read(&buffer_[0], chunk_size_);
4038 if (n > 0) {
4039 current_size_ = static_cast<size_t>(n);
4040 return true;
4041 }
4042
4043 current_size_ = 0;
4044 finished_ = true;
4045 return false;
4046}
4047
4048inline const char *Result::data() const { return buffer_.data(); }
4049inline size_t Result::size() const { return current_size_; }
4050
4051inline std::string Result::read_all() {
4052 std::string result;
4053 while (next()) {
4054 result.append(data(), size());
4055 }
4056 return result;
4057}
4058
4059} // namespace stream
4060
4061namespace sse {
4062
4063// SSEMessage implementations
4064inline SSEMessage::SSEMessage() : event("message") {}
4065
4066inline void SSEMessage::clear() {
4067 event = "message";
4068 data.clear();
4069 id.clear();
4070}
4071
4072// SSEClient implementations
4073inline SSEClient::SSEClient(Client &client, const std::string &path)
4074 : client_(client), path_(path) {}
4075
4076inline SSEClient::SSEClient(Client &client, const std::string &path,
4077 const Headers &headers)
4078 : client_(client), path_(path), headers_(headers) {}
4079
4080inline SSEClient::~SSEClient() { stop(); }
4081
4082inline SSEClient &SSEClient::on_message(MessageHandler handler) {
4083 on_message_ = std::move(handler);
4084 return *this;
4085}
4086
4087inline SSEClient &SSEClient::on_event(const std::string &type,
4088 MessageHandler handler) {
4089 event_handlers_[type] = std::move(handler);
4090 return *this;
4091}
4092
4093inline SSEClient &SSEClient::on_open(OpenHandler handler) {
4094 on_open_ = std::move(handler);
4095 return *this;
4096}
4097
4098inline SSEClient &SSEClient::on_error(ErrorHandler handler) {
4099 on_error_ = std::move(handler);
4100 return *this;
4101}
4102
4103inline SSEClient &SSEClient::set_reconnect_interval(int ms) {
4104 reconnect_interval_ms_ = ms;
4105 return *this;
4106}
4107
4108inline SSEClient &SSEClient::set_max_reconnect_attempts(int n) {
4109 max_reconnect_attempts_ = n;
4110 return *this;
4111}
4112
4113inline SSEClient &SSEClient::set_headers(const Headers &headers) {
4114 std::lock_guard<std::mutex> lock(headers_mutex_);
4115 headers_ = headers;
4116 return *this;
4117}
4118
4119inline bool SSEClient::is_connected() const { return connected_.load(); }
4120
4121inline const std::string &SSEClient::last_event_id() const {
4122 return last_event_id_;
4123}
4124
4125inline void SSEClient::start() {
4126 running_.store(true);
4127 run_event_loop();
4128}
4129
4130inline void SSEClient::start_async() {
4131 running_.store(true);
4132 async_thread_ = std::thread([this]() { run_event_loop(); });
4133}
4134
4135inline void SSEClient::stop() {
4136 running_.store(false);
4137 client_.stop(); // Cancel any pending operations
4138 if (async_thread_.joinable()) { async_thread_.join(); }
4139}
4140
4141inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
4142 int &retry_ms) {
4143 // Blank line signals end of event
4144 if (line.empty() || line == "\r") { return true; }
4145
4146 // Lines starting with ':' are comments (ignored)
4147 if (!line.empty() && line[0] == ':') { return false; }
4148
4149 // Find the colon separator
4150 auto colon_pos = line.find(':');
4151 if (colon_pos == std::string::npos) {
4152 // Line with no colon is treated as field name with empty value
4153 return false;
4154 }
4155
4156 auto field = line.substr(0, colon_pos);
4157 std::string value;
4158
4159 // Value starts after colon, skip optional single space
4160 if (colon_pos + 1 < line.size()) {
4161 auto value_start = colon_pos + 1;
4162 if (line[value_start] == ' ') { value_start++; }
4163 value = line.substr(value_start);
4164 // Remove trailing \r if present
4165 if (!value.empty() && value.back() == '\r') { value.pop_back(); }
4166 }
4167
4168 // Handle known fields
4169 if (field == "event") {
4170 msg.event = value;
4171 } else if (field == "data") {
4172 // Multiple data lines are concatenated with newlines
4173 if (!msg.data.empty()) { msg.data += "\n"; }
4174 msg.data += value;
4175 } else if (field == "id") {
4176 // Empty id is valid (clears the last event ID)
4177 msg.id = value;
4178 } else if (field == "retry") {
4179 // Parse retry interval in milliseconds
4180 {
4181 int v = 0;
4182 auto res =
4183 detail::from_chars(value.data(), value.data() + value.size(), v);
4184 if (res.ec == std::errc{}) { retry_ms = v; }
4185 }
4186 }
4187 // Unknown fields are ignored per SSE spec
4188
4189 return false;
4190}
4191
4192inline void SSEClient::run_event_loop() {
4193 auto reconnect_count = 0;
4194
4195 while (running_.load()) {
4196 // Build headers, including Last-Event-ID if we have one
4197 Headers request_headers;
4198 {
4199 std::lock_guard<std::mutex> lock(headers_mutex_);
4200 request_headers = headers_;
4201 }
4202 if (!last_event_id_.empty()) {
4203 request_headers.emplace("Last-Event-ID", last_event_id_);
4204 }
4205
4206 // Open streaming connection
4207 auto result = stream::Get(client_, path_, request_headers);
4208
4209 // Connection error handling
4210 if (!result) {
4211 connected_.store(false);
4212 if (on_error_) { on_error_(result.error()); }
4213
4214 if (!should_reconnect(reconnect_count)) { break; }
4215 wait_for_reconnect();
4216 reconnect_count++;
4217 continue;
4218 }
4219
4220 if (result.status() != StatusCode::OK_200) {
4221 connected_.store(false);
4222 if (on_error_) { on_error_(Error::Connection); }
4223
4224 // For certain errors, don't reconnect.
4225 // Note: 401 is intentionally absent so that handlers can refresh
4226 // credentials via set_headers() and let the client reconnect.
4227 if (result.status() == StatusCode::NoContent_204 ||
4228 result.status() == StatusCode::NotFound_404 ||
4229 result.status() == StatusCode::Forbidden_403) {
4230 break;
4231 }
4232
4233 if (!should_reconnect(reconnect_count)) { break; }
4234 wait_for_reconnect();
4235 reconnect_count++;
4236 continue;
4237 }
4238
4239 // Connection successful
4240 connected_.store(true);
4241 reconnect_count = 0;
4242 if (on_open_) { on_open_(); }
4243
4244 // Event receiving loop
4245 std::string buffer;
4246 SSEMessage current_msg;
4247
4248 while (running_.load() && result.next()) {
4249 buffer.append(result.data(), result.size());
4250
4251 // Process complete lines in the buffer
4252 size_t line_start = 0;
4253 size_t newline_pos;
4254
4255 while ((newline_pos = buffer.find('\n', line_start)) !=
4256 std::string::npos) {
4257 auto line = buffer.substr(line_start, newline_pos - line_start);
4258 line_start = newline_pos + 1;
4259
4260 // Parse the line and check if event is complete
4261 auto event_complete =
4262 parse_sse_line(line, current_msg, reconnect_interval_ms_);
4263
4264 if (event_complete && !current_msg.data.empty()) {
4265 // Update last_event_id for reconnection
4266 if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
4267
4268 // Dispatch event to appropriate handler
4269 dispatch_event(current_msg);
4270
4271 current_msg.clear();
4272 }
4273 }
4274
4275 // Keep unprocessed data in buffer
4276 buffer.erase(0, line_start);
4277 }
4278
4279 // Connection ended
4280 connected_.store(false);
4281
4282 if (!running_.load()) { break; }
4283
4284 // Check for read errors
4285 if (result.has_read_error()) {
4286 if (on_error_) { on_error_(result.read_error()); }
4287 }
4288
4289 if (!should_reconnect(reconnect_count)) { break; }
4290 wait_for_reconnect();
4291 reconnect_count++;
4292 }
4293
4294 connected_.store(false);
4295}
4296
4297inline void SSEClient::dispatch_event(const SSEMessage &msg) {
4298 // Check for specific event type handler first
4299 auto it = event_handlers_.find(msg.event);
4300 if (it != event_handlers_.end()) {
4301 it->second(msg);
4302 return;
4303 }
4304
4305 // Fall back to generic message handler
4306 if (on_message_) { on_message_(msg); }
4307}
4308
4309inline bool SSEClient::should_reconnect(int count) const {
4310 if (!running_.load()) { return false; }
4311 if (max_reconnect_attempts_ == 0) { return true; } // unlimited
4312 return count < max_reconnect_attempts_;
4313}
4314
4315inline void SSEClient::wait_for_reconnect() {
4316 // Use small increments to check running_ flag frequently
4317 auto waited = 0;
4318 while (running_.load() && waited < reconnect_interval_ms_) {
4319 std::this_thread::sleep_for(std::chrono::milliseconds(100));
4320 waited += 100;
4321 }
4322}
4323
4324} // namespace sse
4325
4326#ifdef CPPHTTPLIB_SSL_ENABLED
4327/*
4328 * TLS abstraction layer - internal function declarations
4329 * These are implementation details and not part of the public API.
4330 */
4331namespace tls {
4332
4333// Client context
4334ctx_t create_client_context();
4335void free_context(ctx_t ctx);
4336bool set_min_version(ctx_t ctx, Version version);
4337bool load_ca_pem(ctx_t ctx, const char *pem, size_t len);
4338bool load_ca_file(ctx_t ctx, const char *file_path);
4339bool load_ca_dir(ctx_t ctx, const char *dir_path);
4340bool load_system_certs(ctx_t ctx);
4341bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
4342 const char *password);
4343bool set_client_cert_file(ctx_t ctx, const char *cert_path,
4344 const char *key_path, const char *password);
4345
4346// Server context
4347ctx_t create_server_context();
4348bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
4349 const char *password);
4350bool set_server_cert_file(ctx_t ctx, const char *cert_path,
4351 const char *key_path, const char *password);
4352bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir);
4353void set_verify_client(ctx_t ctx, bool require);
4354
4355// Session management
4356session_t create_session(ctx_t ctx, socket_t sock);
4357void free_session(session_t session);
4358bool set_sni(session_t session, const char *hostname);
4359bool set_hostname(session_t session, const char *hostname);
4360
4361// Handshake (non-blocking capable)
4362TlsError connect(session_t session);
4363TlsError accept(session_t session);
4364
4365// Handshake with timeout (blocking until timeout)
4366bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4367 time_t timeout_usec, TlsError *err);
4368bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4369 time_t timeout_usec, TlsError *err);
4370
4371// I/O (non-blocking capable)
4372ssize_t read(session_t session, void *buf, size_t len, TlsError &err);
4373ssize_t write(session_t session, const void *buf, size_t len, TlsError &err);
4374int pending(const_session_t session);
4375void shutdown(session_t session, bool graceful);
4376
4377// Connection state
4378bool is_peer_closed(session_t session, socket_t sock);
4379
4380// Certificate verification
4381cert_t get_peer_cert(const_session_t session);
4382void free_cert(cert_t cert);
4383bool verify_hostname(cert_t cert, const char *hostname);
4384uint64_t hostname_mismatch_code();
4385long get_verify_result(const_session_t session);
4386
4387// Certificate introspection
4388std::string get_cert_subject_cn(cert_t cert);
4389std::string get_cert_issuer_name(cert_t cert);
4390bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
4391bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
4392std::string get_cert_serial(cert_t cert);
4393bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
4394const char *get_sni(const_session_t session);
4395
4396// CA store management
4397ca_store_t create_ca_store(const char *pem, size_t len);
4398void free_ca_store(ca_store_t store);
4399bool set_ca_store(ctx_t ctx, ca_store_t store);
4400size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs);
4401std::vector<std::string> get_ca_names(ctx_t ctx);
4402
4403// Dynamic certificate update (for servers)
4404bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem,
4405 const char *password);
4406bool update_server_client_ca(ctx_t ctx, const char *ca_pem);
4407
4408// Certificate verification callback
4409bool set_verify_callback(ctx_t ctx, VerifyCallback callback);
4410long get_verify_error(const_session_t session);
4411std::string verify_error_string(long error_code);
4412
4413// TlsError information
4414uint64_t peek_error();
4415uint64_t get_error();
4416std::string error_string(uint64_t code);
4417
4418} // namespace tls
4419#endif // CPPHTTPLIB_SSL_ENABLED
4420
4421/*
4422 * Group 1: detail namespace - Non-SSL utilities
4423 */
4424
4425namespace detail {
4426
4427inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
4428 const void *optval, socklen_t optlen) {
4429 return setsockopt(sock, level, optname,
4430#ifdef _WIN32
4431 reinterpret_cast<const char *>(optval),
4432#else
4433 optval,
4434#endif
4435 optlen) == 0;
4436}
4437
4438inline bool set_socket_opt_time(socket_t sock, int level, int optname,
4439 time_t sec, time_t usec) {
4440#ifdef _WIN32
4441 auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
4442#else
4443 timeval timeout;
4444 timeout.tv_sec = static_cast<long>(sec);
4445 timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
4446#endif
4447 return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
4448}
4449
4450inline bool is_hex(char c, int &v) {
4451 if (isdigit(static_cast<unsigned char>(c))) {
4452 v = c - '0';
4453 return true;
4454 } else if ('A' <= c && c <= 'F') {
4455 v = c - 'A' + 10;
4456 return true;
4457 } else if ('a' <= c && c <= 'f') {
4458 v = c - 'a' + 10;
4459 return true;
4460 }
4461 return false;
4462}
4463
4464inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
4465 int &val) {
4466 if (i >= s.size()) { return false; }
4467
4468 val = 0;
4469 for (; cnt; i++, cnt--) {
4470 if (!s[i]) { return false; }
4471 auto v = 0;
4472 if (is_hex(s[i], v)) {
4473 val = val * 16 + v;
4474 } else {
4475 return false;
4476 }
4477 }
4478 return true;
4479}
4480
4481inline std::string from_i_to_hex(size_t n) {
4482 static const auto charset = "0123456789abcdef";
4483 std::string ret;
4484 do {
4485 ret = charset[n & 15] + ret;
4486 n >>= 4;
4487 } while (n > 0);
4488 return ret;
4489}
4490
4491inline std::string compute_etag(const FileStat &fs) {
4492 if (!fs.is_file()) { return std::string(); }
4493
4494 // If mtime cannot be determined (negative value indicates an error
4495 // or sentinel), do not generate an ETag. Returning a neutral / fixed
4496 // value like 0 could collide with a real file that legitimately has
4497 // mtime == 0 (epoch) and lead to misleading validators.
4498 auto mtime_raw = fs.mtime();
4499 if (mtime_raw < 0) { return std::string(); }
4500
4501 auto mtime = static_cast<size_t>(mtime_raw);
4502 auto size = fs.size();
4503
4504 return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
4505 from_i_to_hex(size) + "\"";
4506}
4507
4508// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
4509// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
4510// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
4511inline std::string file_mtime_to_http_date(time_t mtime) {
4512 if (mtime < 0) { return std::string(); }
4513
4514 struct tm tm_buf;
4515#ifdef _WIN32
4516 if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
4517#else
4518 if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
4519#endif
4520 char buf[64];
4521 if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
4522 return std::string();
4523 }
4524
4525 return std::string(buf);
4526}
4527
4528// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
4529inline time_t parse_http_date(const std::string &date_str) {
4530 struct tm tm_buf;
4531
4532 // Create a classic locale object once for all parsing attempts
4533 const std::locale classic_locale = std::locale::classic();
4534
4535 // Try to parse using std::get_time (C++11, cross-platform)
4536 auto try_parse = [&](const char *fmt) -> bool {
4537 std::istringstream ss(date_str);
4538 ss.imbue(classic_locale);
4539
4540 memset(&tm_buf, 0, sizeof(tm_buf));
4541 ss >> std::get_time(&tm_buf, fmt);
4542
4543 return !ss.fail();
4544 };
4545
4546 // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
4547 if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
4548 // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
4549 if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
4550 // asctime format: "Sun Nov 6 08:49:37 1994"
4551 if (!try_parse("%a %b %d %H:%M:%S %Y")) {
4552 return static_cast<time_t>(-1);
4553 }
4554 }
4555 }
4556
4557#ifdef _WIN32
4558 return _mkgmtime(&tm_buf);
4559#elif defined _AIX
4560 return mktime(&tm_buf);
4561#else
4562 return timegm(&tm_buf);
4563#endif
4564}
4565
4566inline bool is_weak_etag(const std::string &s) {
4567 // Check if the string is a weak ETag (starts with 'W/"')
4568 return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
4569}
4570
4571inline bool is_strong_etag(const std::string &s) {
4572 // Check if the string is a strong ETag (starts and ends with '"', at least 2
4573 // chars)
4574 return s.size() >= 2 && s[0] == '"' && s.back() == '"';
4575}
4576
4577inline size_t to_utf8(int code, char *buff) {
4578 if (code < 0x0080) {
4579 buff[0] = static_cast<char>(code & 0x7F);
4580 return 1;
4581 } else if (code < 0x0800) {
4582 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
4583 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
4584 return 2;
4585 } else if (code < 0xD800) {
4586 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4587 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4588 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4589 return 3;
4590 } else if (code < 0xE000) { // D800 - DFFF is invalid...
4591 return 0;
4592 } else if (code < 0x10000) {
4593 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4594 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4595 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4596 return 3;
4597 } else if (code < 0x110000) {
4598 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
4599 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
4600 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4601 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
4602 return 4;
4603 }
4604
4605 // NOTREACHED
4606 return 0;
4607}
4608
4609} // namespace detail
4610
4611namespace ws {
4612namespace impl {
4613
4614inline bool is_valid_utf8(const std::string &s) {
4615 size_t i = 0;
4616 auto n = s.size();
4617 while (i < n) {
4618 auto c = static_cast<unsigned char>(s[i]);
4619 size_t len;
4620 uint32_t cp;
4621 if (c < 0x80) {
4622 i++;
4623 continue;
4624 } else if ((c & 0xE0) == 0xC0) {
4625 len = 2;
4626 cp = c & 0x1F;
4627 } else if ((c & 0xF0) == 0xE0) {
4628 len = 3;
4629 cp = c & 0x0F;
4630 } else if ((c & 0xF8) == 0xF0) {
4631 len = 4;
4632 cp = c & 0x07;
4633 } else {
4634 return false;
4635 }
4636 if (i + len > n) { return false; }
4637 for (size_t j = 1; j < len; j++) {
4638 auto b = static_cast<unsigned char>(s[i + j]);
4639 if ((b & 0xC0) != 0x80) { return false; }
4640 cp = (cp << 6) | (b & 0x3F);
4641 }
4642 // Overlong encoding check
4643 if (len == 2 && cp < 0x80) { return false; }
4644 if (len == 3 && cp < 0x800) { return false; }
4645 if (len == 4 && cp < 0x10000) { return false; }
4646 // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid
4647 if (cp >= 0xD800 && cp <= 0xDFFF) { return false; }
4648 if (cp > 0x10FFFF) { return false; }
4649 i += len;
4650 }
4651 return true;
4652}
4653
4654} // namespace impl
4655} // namespace ws
4656
4657namespace detail {
4658
4659// NOTE: This code came up with the following stackoverflow post:
4660// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
4661inline std::string base64_encode(const std::string &in) {
4662 static const auto lookup =
4663 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4664
4665 std::string out;
4666 out.reserve(in.size());
4667
4668 auto val = 0;
4669 auto valb = -6;
4670
4671 for (auto c : in) {
4672 val = (val << 8) + static_cast<uint8_t>(c);
4673 valb += 8;
4674 while (valb >= 0) {
4675 out.push_back(lookup[(val >> valb) & 0x3F]);
4676 valb -= 6;
4677 }
4678 }
4679
4680 if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
4681
4682 while (out.size() % 4) {
4683 out.push_back('=');
4684 }
4685
4686 return out;
4687}
4688
4689inline std::string sha1(const std::string &input) {
4690 // RFC 3174 SHA-1 implementation
4691 auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t {
4692 return (x << n) | (x >> (32 - n));
4693 };
4694
4695 uint32_t h0 = 0x67452301;
4696 uint32_t h1 = 0xEFCDAB89;
4697 uint32_t h2 = 0x98BADCFE;
4698 uint32_t h3 = 0x10325476;
4699 uint32_t h4 = 0xC3D2E1F0;
4700
4701 // Pre-processing: adding padding bits
4702 std::string msg = input;
4703 uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
4704 msg.push_back(static_cast<char>(0x80u));
4705 while (msg.size() % 64 != 56) {
4706 msg.push_back(0);
4707 }
4708
4709 // Append original length in bits as 64-bit big-endian
4710 for (int i = 56; i >= 0; i -= 8) {
4711 msg.push_back(static_cast<char>((original_bit_len >> i) & 0xFF));
4712 }
4713
4714 // Process each 512-bit chunk
4715 for (size_t offset = 0; offset < msg.size(); offset += 64) {
4716 uint32_t w[80];
4717
4718 for (size_t i = 0; i < 16; i++) {
4719 w[i] =
4720 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4]))
4721 << 24) |
4722 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 1]))
4723 << 16) |
4724 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 2]))
4725 << 8) |
4726 (static_cast<uint32_t>(
4727 static_cast<uint8_t>(msg[offset + i * 4 + 3])));
4728 }
4729
4730 for (int i = 16; i < 80; i++) {
4731 w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
4732 }
4733
4734 uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
4735
4736 for (int i = 0; i < 80; i++) {
4737 uint32_t f, k;
4738 if (i < 20) {
4739 f = (b & c) | ((~b) & d);
4740 k = 0x5A827999;
4741 } else if (i < 40) {
4742 f = b ^ c ^ d;
4743 k = 0x6ED9EBA1;
4744 } else if (i < 60) {
4745 f = (b & c) | (b & d) | (c & d);
4746 k = 0x8F1BBCDC;
4747 } else {
4748 f = b ^ c ^ d;
4749 k = 0xCA62C1D6;
4750 }
4751
4752 uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
4753 e = d;
4754 d = c;
4755 c = left_rotate(b, 30);
4756 b = a;
4757 a = temp;
4758 }
4759
4760 h0 += a;
4761 h1 += b;
4762 h2 += c;
4763 h3 += d;
4764 h4 += e;
4765 }
4766
4767 // Produce the final hash as a 20-byte binary string
4768 std::string hash(20, '\0');
4769 for (size_t i = 0; i < 4; i++) {
4770 hash[i] = static_cast<char>((h0 >> (24 - i * 8)) & 0xFF);
4771 hash[4 + i] = static_cast<char>((h1 >> (24 - i * 8)) & 0xFF);
4772 hash[8 + i] = static_cast<char>((h2 >> (24 - i * 8)) & 0xFF);
4773 hash[12 + i] = static_cast<char>((h3 >> (24 - i * 8)) & 0xFF);
4774 hash[16 + i] = static_cast<char>((h4 >> (24 - i * 8)) & 0xFF);
4775 }
4776 return hash;
4777}
4778
4779inline std::string websocket_accept_key(const std::string &client_key) {
4780 const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4781 return base64_encode(sha1(client_key + magic));
4782}
4783
4784inline bool is_websocket_upgrade(const Request &req) {
4785 if (req.method != "GET") { return false; }
4786
4787 // Check Upgrade: websocket (case-insensitive)
4788 auto upgrade_it = req.headers.find("Upgrade");
4789 if (upgrade_it == req.headers.end()) { return false; }
4790 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
4791 if (upgrade_val != "websocket") { return false; }
4792
4793 // Check Connection header contains "Upgrade"
4794 auto connection_it = req.headers.find("Connection");
4795 if (connection_it == req.headers.end()) { return false; }
4796 auto connection_val = case_ignore::to_lower(connection_it->second);
4797 if (connection_val.find("upgrade") == std::string::npos) { return false; }
4798
4799 // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars)
4800 // RFC 6455 Section 4.2.1
4801 auto ws_key = req.get_header_value("Sec-WebSocket-Key");
4802 if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') {
4803 return false;
4804 }
4805 static const std::string b64chars =
4806 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4807 for (size_t i = 0; i < 22; i++) {
4808 if (b64chars.find(ws_key[i]) == std::string::npos) { return false; }
4809 }
4810
4811 // Check Sec-WebSocket-Version: 13
4812 auto version = req.get_header_value("Sec-WebSocket-Version");
4813 if (version != "13") { return false; }
4814
4815 return true;
4816}
4817
4818inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
4819 const char *data, size_t len, bool fin,
4820 bool mask) {
4821 // First byte: FIN + opcode
4822 uint8_t header[2];
4823 header[0] = static_cast<uint8_t>((fin ? 0x80 : 0x00) |
4824 (static_cast<uint8_t>(opcode) & 0x0F));
4825
4826 // Second byte: MASK + payload length
4827 if (len < 126) {
4828 header[1] = static_cast<uint8_t>(len);
4829 if (mask) { header[1] |= 0x80; }
4830 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4831 } else if (len <= 0xFFFF) {
4832 header[1] = 126;
4833 if (mask) { header[1] |= 0x80; }
4834 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4835 uint8_t ext[2];
4836 ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
4837 ext[1] = static_cast<uint8_t>(len & 0xFF);
4838 if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
4839 } else {
4840 header[1] = 127;
4841 if (mask) { header[1] |= 0x80; }
4842 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4843 uint8_t ext[8];
4844 for (int i = 7; i >= 0; i--) {
4845 ext[7 - i] =
4846 static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
4847 }
4848 if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
4849 }
4850
4851 if (mask) {
4852 // Generate random mask key
4853 thread_local std::mt19937 rng(std::random_device{}());
4854 uint8_t mask_key[4];
4855 auto r = rng();
4856 std::memcpy(mask_key, &r, 4);
4857 if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
4858
4859 // Write masked payload in chunks
4860 const size_t chunk_size = 4096;
4861 std::vector<char> buf((std::min)(len, chunk_size));
4862 for (size_t offset = 0; offset < len; offset += chunk_size) {
4863 size_t n = (std::min)(chunk_size, len - offset);
4864 for (size_t i = 0; i < n; i++) {
4865 buf[i] =
4866 data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
4867 }
4868 if (strm.write(buf.data(), n) < 0) { return false; }
4869 }
4870 } else {
4871 if (len > 0) {
4872 if (strm.write(data, len) < 0) { return false; }
4873 }
4874 }
4875
4876 return true;
4877}
4878
4879} // namespace detail
4880
4881namespace ws {
4882namespace impl {
4883
4884inline bool read_websocket_frame(Stream &strm, Opcode &opcode,
4885 std::string &payload, bool &fin,
4886 bool expect_masked, size_t max_len) {
4887 // Read first 2 bytes
4888 uint8_t header[2];
4889 if (strm.read(reinterpret_cast<char *>(header), 2) != 2) { return false; }
4890
4891 fin = (header[0] & 0x80) != 0;
4892
4893 // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated
4894 if (header[0] & 0x70) { return false; }
4895
4896 opcode = static_cast<Opcode>(header[0] & 0x0F);
4897 bool masked = (header[1] & 0x80) != 0;
4898 uint64_t payload_len = header[1] & 0x7F;
4899
4900 // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and
4901 // MUST have a payload length of 125 bytes or less
4902 bool is_control = (static_cast<uint8_t>(opcode) & 0x08) != 0;
4903 if (is_control) {
4904 if (!fin) { return false; }
4905 if (payload_len > 125) { return false; }
4906 }
4907
4908 if (masked != expect_masked) { return false; }
4909
4910 // Extended payload length
4911 if (payload_len == 126) {
4912 uint8_t ext[2];
4913 if (strm.read(reinterpret_cast<char *>(ext), 2) != 2) { return false; }
4914 payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1];
4915 } else if (payload_len == 127) {
4916 uint8_t ext[8];
4917 if (strm.read(reinterpret_cast<char *>(ext), 8) != 8) { return false; }
4918 // RFC 6455 Section 5.2: the most significant bit MUST be 0
4919 if (ext[0] & 0x80) { return false; }
4920 payload_len = 0;
4921 for (int i = 0; i < 8; i++) {
4922 payload_len = (payload_len << 8) | ext[i];
4923 }
4924 }
4925
4926 if (payload_len > max_len) { return false; }
4927
4928 // Read mask key if present
4929 uint8_t mask_key[4] = {0};
4930 if (masked) {
4931 if (strm.read(reinterpret_cast<char *>(mask_key), 4) != 4) { return false; }
4932 }
4933
4934 // Read payload
4935 payload.resize(static_cast<size_t>(payload_len));
4936 if (payload_len > 0) {
4937 size_t total_read = 0;
4938 while (total_read < payload_len) {
4939 auto n = strm.read(&payload[total_read],
4940 static_cast<size_t>(payload_len - total_read));
4941 if (n <= 0) { return false; }
4942 total_read += static_cast<size_t>(n);
4943 }
4944 }
4945
4946 // Unmask if needed
4947 if (masked) {
4948 for (size_t i = 0; i < payload.size(); i++) {
4949 payload[i] ^= static_cast<char>(mask_key[i % 4]);
4950 }
4951 }
4952
4953 return true;
4954}
4955
4956} // namespace impl
4957} // namespace ws
4958
4959namespace detail {
4960
4961inline bool is_valid_path(const std::string &path) {
4962 size_t level = 0;
4963 size_t i = 0;
4964
4965 // Skip slash
4966 while (i < path.size() && path[i] == '/') {
4967 i++;
4968 }
4969
4970 while (i < path.size()) {
4971 // Read component
4972 auto beg = i;
4973 while (i < path.size() && path[i] != '/') {
4974 if (path[i] == '\0') {
4975 return false;
4976 } else if (path[i] == '\\') {
4977 return false;
4978 }
4979 i++;
4980 }
4981
4982 auto len = i - beg;
4983 assert(len > 0);
4984
4985 if (!path.compare(beg, len, ".")) {
4986 ;
4987 } else if (!path.compare(beg, len, "..")) {
4988 if (level == 0) { return false; }
4989 level--;
4990 } else {
4991 level++;
4992 }
4993
4994 // Skip slash
4995 while (i < path.size() && path[i] == '/') {
4996 i++;
4997 }
4998 }
4999
5000 return true;
5001}
5002
5003inline bool canonicalize_path(const char *path, std::string &resolved) {
5004#if defined(_WIN32)
5005 char buf[_MAX_PATH];
5006 if (_fullpath(buf, path, _MAX_PATH) == nullptr) { return false; }
5007 resolved = buf;
5008#elif defined(PATH_MAX)
5009 char buf[PATH_MAX];
5010 if (realpath(path, buf) == nullptr) { return false; }
5011 resolved = buf;
5012#else
5013 auto buf = realpath(path, nullptr);
5014 auto guard = scope_exit([&]() { std::free(buf); });
5015 if (buf == nullptr) { return false; }
5016 resolved = buf;
5017#endif
5018 return true;
5019}
5020
5021inline bool is_path_within_base(const std::string &resolved_path,
5022 const std::string &resolved_base) {
5023#if defined(_WIN32)
5024 return _strnicmp(resolved_path.c_str(), resolved_base.c_str(),
5025 resolved_base.size()) == 0;
5026#else
5027 return strncmp(resolved_path.c_str(), resolved_base.c_str(),
5028 resolved_base.size()) == 0;
5029#endif
5030}
5031
5032inline FileStat::FileStat(const std::string &path) {
5033#if defined(_WIN32)
5034 auto wpath = u8string_to_wstring(path.c_str());
5035 ret_ = _wstat(wpath.c_str(), &st_);
5036#else
5037 ret_ = stat(path.c_str(), &st_);
5038#endif
5039}
5040inline bool FileStat::is_file() const {
5041 return ret_ >= 0 && S_ISREG(st_.st_mode);
5042}
5043inline bool FileStat::is_dir() const {
5044 return ret_ >= 0 && S_ISDIR(st_.st_mode);
5045}
5046
5047inline time_t FileStat::mtime() const {
5048 return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
5049 : static_cast<time_t>(-1);
5050}
5051
5052inline size_t FileStat::size() const {
5053 return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
5054}
5055
5056inline std::string encode_path(const std::string &s) {
5057 std::string result;
5058 result.reserve(s.size());
5059
5060 for (size_t i = 0; s[i]; i++) {
5061 switch (s[i]) {
5062 case ' ': result += "%20"; break;
5063 case '+': result += "%2B"; break;
5064 case '\r': result += "%0D"; break;
5065 case '\n': result += "%0A"; break;
5066 case '\'': result += "%27"; break;
5067 case ',': result += "%2C"; break;
5068 // case ':': result += "%3A"; break; // ok? probably...
5069 case ';': result += "%3B"; break;
5070 default:
5071 auto c = static_cast<uint8_t>(s[i]);
5072 if (c >= 0x80) {
5073 result += '%';
5074 char hex[4];
5075 auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
5076 assert(len == 2);
5077 result.append(hex, static_cast<size_t>(len));
5078 } else {
5079 result += s[i];
5080 }
5081 break;
5082 }
5083 }
5084
5085 return result;
5086}
5087
5088inline std::string file_extension(const std::string &path) {
5089 std::smatch m;
5090 thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
5091 if (std::regex_search(path, m, re)) { return m[1].str(); }
5092 return std::string();
5093}
5094
5095inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
5096
5097template <typename T>
5098inline bool parse_header(const char *beg, const char *end, T fn);
5099
5100template <typename T>
5101inline bool parse_header(const char *beg, const char *end, T fn) {
5102 // Skip trailing spaces and tabs.
5103 while (beg < end && is_space_or_tab(end[-1])) {
5104 end--;
5105 }
5106
5107 auto p = beg;
5108 while (p < end && *p != ':') {
5109 p++;
5110 }
5111
5112 auto name = std::string(beg, p);
5113 if (!detail::fields::is_field_name(name)) { return false; }
5114
5115 if (p == end) { return false; }
5116
5117 auto key_end = p;
5118
5119 if (*p++ != ':') { return false; }
5120
5121 while (p < end && is_space_or_tab(*p)) {
5122 p++;
5123 }
5124
5125 if (p <= end) {
5126 auto key_len = key_end - beg;
5127 if (!key_len) { return false; }
5128
5129 auto key = std::string(beg, key_end);
5130 auto val = std::string(p, end);
5131
5132 if (!detail::fields::is_field_value(val)) { return false; }
5133
5134 // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5135 // percent-decoded by the recipient. Applications that need to interpret a
5136 // value as a URI component should call httplib::decode_uri_component()
5137 // (or decode_path_component()) explicitly.
5138 fn(key, val);
5139
5140 return true;
5141 }
5142
5143 return false;
5144}
5145
5146inline bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
5147 const Headers &src_headers) {
5148 // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
5149 // transfer coding is complete when a chunk with a chunk-size of zero is
5150 // received, possibly followed by a trailer section, and finally terminated by
5151 // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
5152 //
5153 // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
5154 // doesn't care for the existence of the final CRLF. In other words, it seems
5155 // to be ok whether the final CRLF exists or not in the chunked data.
5156 // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
5157 //
5158 // According to the reference code in RFC 9112, cpp-httplib now allows
5159 // chunked transfer coding data without the final CRLF.
5160
5161 // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
5162 thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
5163 "transfer-encoding",
5164 "content-length",
5165 "host",
5166 "authorization",
5167 "www-authenticate",
5168 "proxy-authenticate",
5169 "proxy-authorization",
5170 "cookie",
5171 "set-cookie",
5172 "cache-control",
5173 "expect",
5174 "max-forwards",
5175 "pragma",
5176 "range",
5177 "te",
5178 "age",
5179 "expires",
5180 "date",
5181 "location",
5182 "retry-after",
5183 "vary",
5184 "warning",
5185 "content-encoding",
5186 "content-type",
5187 "content-range",
5188 "trailer"};
5189
5190 case_ignore::unordered_set<std::string> declared_trailers;
5191 auto trailer_header = get_header_value(src_headers, "Trailer", "", 0);
5192 if (trailer_header && std::strlen(trailer_header)) {
5193 auto len = std::strlen(trailer_header);
5194 split(trailer_header, trailer_header + len, ',',
5195 [&](const char *b, const char *e) {
5196 const char *kbeg = b;
5197 const char *kend = e;
5198 while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) {
5199 ++kbeg;
5200 }
5201 while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) {
5202 --kend;
5203 }
5204 std::string key(kbeg, static_cast<size_t>(kend - kbeg));
5205 if (!key.empty() &&
5206 prohibited_trailers.find(key) == prohibited_trailers.end()) {
5207 declared_trailers.insert(key);
5208 }
5209 });
5210 }
5211
5212 size_t trailer_header_count = 0;
5213 while (strcmp(line_reader.ptr(), "\r\n") != 0) {
5214 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
5215 if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
5216
5217 constexpr auto line_terminator_len = 2;
5218 auto line_beg = line_reader.ptr();
5219 auto line_end =
5220 line_reader.ptr() + line_reader.size() - line_terminator_len;
5221
5222 if (!parse_header(line_beg, line_end,
5223 [&](const std::string &key, const std::string &val) {
5224 if (declared_trailers.find(key) !=
5225 declared_trailers.end()) {
5226 dest.emplace(key, val);
5227 trailer_header_count++;
5228 }
5229 })) {
5230 return false;
5231 }
5232
5233 if (!line_reader.getline()) { return false; }
5234 }
5235
5236 return true;
5237}
5238
5239inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
5240 size_t right) {
5241 while (b + left < e && is_space_or_tab(b[left])) {
5242 left++;
5243 }
5244 while (right > 0 && is_space_or_tab(b[right - 1])) {
5245 right--;
5246 }
5247 return std::make_pair(left, right);
5248}
5249
5250inline std::string trim_copy(const std::string &s) {
5251 auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
5252 return s.substr(r.first, r.second - r.first);
5253}
5254
5255inline std::string trim_double_quotes_copy(const std::string &s) {
5256 if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
5257 return s.substr(1, s.size() - 2);
5258 }
5259 return s;
5260}
5261
5262inline void
5263divide(const char *data, std::size_t size, char d,
5264 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5265 fn) {
5266 const auto it = std::find(data, data + size, d);
5267 const auto found = static_cast<std::size_t>(it != data + size);
5268 const auto lhs_data = data;
5269 const auto lhs_size = static_cast<std::size_t>(it - data);
5270 const auto rhs_data = it + found;
5271 const auto rhs_size = size - lhs_size - found;
5272
5273 fn(lhs_data, lhs_size, rhs_data, rhs_size);
5274}
5275
5276inline void
5277divide(const std::string &str, char d,
5278 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5279 fn) {
5280 divide(str.data(), str.size(), d, std::move(fn));
5281}
5282
5283inline void split(const char *b, const char *e, char d,
5284 std::function<void(const char *, const char *)> fn) {
5285 return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
5286}
5287
5288inline void split(const char *b, const char *e, char d, size_t m,
5289 std::function<void(const char *, const char *)> fn) {
5290 size_t i = 0;
5291 size_t beg = 0;
5292 size_t count = 1;
5293
5294 while (e ? (b + i < e) : (b[i] != '\0')) {
5295 if (b[i] == d && count < m) {
5296 auto r = trim(b, e, beg, i);
5297 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5298 beg = i + 1;
5299 count++;
5300 }
5301 i++;
5302 }
5303
5304 if (i) {
5305 auto r = trim(b, e, beg, i);
5306 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5307 }
5308}
5309
5310inline bool split_find(const char *b, const char *e, char d, size_t m,
5311 std::function<bool(const char *, const char *)> fn) {
5312 size_t i = 0;
5313 size_t beg = 0;
5314 size_t count = 1;
5315
5316 while (e ? (b + i < e) : (b[i] != '\0')) {
5317 if (b[i] == d && count < m) {
5318 auto r = trim(b, e, beg, i);
5319 if (r.first < r.second) {
5320 auto found = fn(&b[r.first], &b[r.second]);
5321 if (found) { return true; }
5322 }
5323 beg = i + 1;
5324 count++;
5325 }
5326 i++;
5327 }
5328
5329 if (i) {
5330 auto r = trim(b, e, beg, i);
5331 if (r.first < r.second) {
5332 auto found = fn(&b[r.first], &b[r.second]);
5333 if (found) { return true; }
5334 }
5335 }
5336
5337 return false;
5338}
5339
5340inline bool split_find(const char *b, const char *e, char d,
5341 std::function<bool(const char *, const char *)> fn) {
5342 return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
5343 std::move(fn));
5344}
5345
5346inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
5347 size_t fixed_buffer_size)
5348 : strm_(strm), fixed_buffer_(fixed_buffer),
5349 fixed_buffer_size_(fixed_buffer_size) {}
5350
5351inline const char *stream_line_reader::ptr() const {
5352 if (growable_buffer_.empty()) {
5353 return fixed_buffer_;
5354 } else {
5355 return growable_buffer_.data();
5356 }
5357}
5358
5359inline size_t stream_line_reader::size() const {
5360 if (growable_buffer_.empty()) {
5361 return fixed_buffer_used_size_;
5362 } else {
5363 return growable_buffer_.size();
5364 }
5365}
5366
5367inline bool stream_line_reader::end_with_crlf() const {
5368 auto end = ptr() + size();
5369 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
5370}
5371
5372inline bool stream_line_reader::getline() {
5373 fixed_buffer_used_size_ = 0;
5374 growable_buffer_.clear();
5375
5376#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5377 char prev_byte = 0;
5378#endif
5379
5380 for (size_t i = 0;; i++) {
5381 if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
5382 // Treat exceptionally long lines as an error to
5383 // prevent infinite loops/memory exhaustion
5384 return false;
5385 }
5386 char byte;
5387 auto n = strm_.read(&byte, 1);
5388
5389 if (n < 0) {
5390 return false;
5391 } else if (n == 0) {
5392 if (i == 0) {
5393 return false;
5394 } else {
5395 break;
5396 }
5397 }
5398
5399 append(byte);
5400
5401#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5402 if (byte == '\n') { break; }
5403#else
5404 if (prev_byte == '\r' && byte == '\n') { break; }
5405 prev_byte = byte;
5406#endif
5407 }
5408
5409 return true;
5410}
5411
5412inline void stream_line_reader::append(char c) {
5413 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
5414 fixed_buffer_[fixed_buffer_used_size_++] = c;
5415 fixed_buffer_[fixed_buffer_used_size_] = '\0';
5416 } else {
5417 if (growable_buffer_.empty()) {
5418 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
5419 growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
5420 }
5421 growable_buffer_ += c;
5422 }
5423}
5424
5425inline mmap::mmap(const char *path) { open(path); }
5426
5427inline mmap::~mmap() { close(); }
5428
5429inline bool mmap::open(const char *path) {
5430 close();
5431
5432#if defined(_WIN32)
5433 auto wpath = u8string_to_wstring(path);
5434 if (wpath.empty()) { return false; }
5435
5436 hFile_ =
5437 ::CreateFile2(wpath.c_str(), GENERIC_READ,
5438 FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, NULL);
5439
5440 if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
5441
5442 LARGE_INTEGER size{};
5443 if (!::GetFileSizeEx(hFile_, &size)) { return false; }
5444 // If the following line doesn't compile due to QuadPart, update Windows SDK.
5445 // See:
5446 // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
5447 if (static_cast<ULONGLONG>(size.QuadPart) >
5448 (std::numeric_limits<decltype(size_)>::max)()) {
5449 // `size_t` might be 32-bits, on 32-bits Windows.
5450 return false;
5451 }
5452 size_ = static_cast<size_t>(size.QuadPart);
5453
5454 hMapping_ =
5455 ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
5456
5457 // Special treatment for an empty file...
5458 if (hMapping_ == NULL && size_ == 0) {
5459 close();
5460 is_open_empty_file = true;
5461 return true;
5462 }
5463
5464 if (hMapping_ == NULL) {
5465 close();
5466 return false;
5467 }
5468
5469 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
5470
5471 if (addr_ == nullptr) {
5472 close();
5473 return false;
5474 }
5475#else
5476 fd_ = ::open(path, O_RDONLY);
5477 if (fd_ == -1) { return false; }
5478
5479 struct stat sb;
5480 if (fstat(fd_, &sb) == -1) {
5481 close();
5482 return false;
5483 }
5484 size_ = static_cast<size_t>(sb.st_size);
5485
5486 addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
5487
5488 // Special treatment for an empty file...
5489 if (addr_ == MAP_FAILED && size_ == 0) {
5490 close();
5491 is_open_empty_file = true;
5492 return false;
5493 }
5494#endif
5495
5496 return true;
5497}
5498
5499inline bool mmap::is_open() const {
5500 return is_open_empty_file ? true : addr_ != nullptr;
5501}
5502
5503inline size_t mmap::size() const { return size_; }
5504
5505inline const char *mmap::data() const {
5506 return is_open_empty_file ? "" : static_cast<const char *>(addr_);
5507}
5508
5509inline void mmap::close() {
5510#if defined(_WIN32)
5511 if (addr_) {
5512 ::UnmapViewOfFile(addr_);
5513 addr_ = nullptr;
5514 }
5515
5516 if (hMapping_) {
5517 ::CloseHandle(hMapping_);
5518 hMapping_ = NULL;
5519 }
5520
5521 if (hFile_ != INVALID_HANDLE_VALUE) {
5522 ::CloseHandle(hFile_);
5523 hFile_ = INVALID_HANDLE_VALUE;
5524 }
5525
5526 is_open_empty_file = false;
5527#else
5528 if (addr_ != nullptr) {
5529 munmap(addr_, size_);
5530 addr_ = nullptr;
5531 }
5532
5533 if (fd_ != -1) {
5534 ::close(fd_);
5535 fd_ = -1;
5536 }
5537#endif
5538 size_ = 0;
5539}
5540inline int close_socket(socket_t sock) noexcept {
5541#ifdef _WIN32
5542 return closesocket(sock);
5543#else
5544 return close(sock);
5545#endif
5546}
5547
5548template <typename T> inline ssize_t handle_EINTR(T fn) {
5549 ssize_t res = 0;
5550 while (true) {
5551 res = fn();
5552 if (res < 0 && errno == EINTR) {
5553 std::this_thread::sleep_for(std::chrono::microseconds{1});
5554 continue;
5555 }
5556 break;
5557 }
5558 return res;
5559}
5560
5561inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
5562 return handle_EINTR([&]() {
5563 return recv(sock,
5564#ifdef _WIN32
5565 static_cast<char *>(ptr), static_cast<int>(size),
5566#else
5567 ptr, size,
5568#endif
5569 flags);
5570 });
5571}
5572
5573inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
5574 int flags) {
5575 return handle_EINTR([&]() {
5576 return send(sock,
5577#ifdef _WIN32
5578 static_cast<const char *>(ptr), static_cast<int>(size),
5579#else
5580 ptr, size,
5581#endif
5582 flags);
5583 });
5584}
5585
5586inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
5587#ifdef _WIN32
5588 return ::WSAPoll(fds, nfds, timeout);
5589#else
5590 return ::poll(fds, nfds, timeout);
5591#endif
5592}
5593
5594inline ssize_t select_impl(socket_t sock, short events, time_t sec,
5595 time_t usec) {
5596 struct pollfd pfd;
5597 pfd.fd = sock;
5598 pfd.events = events;
5599 pfd.revents = 0;
5600
5601 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5602
5603 return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
5604}
5605
5606inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
5607 return select_impl(sock, POLLIN, sec, usec);
5608}
5609
5610inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
5611 return select_impl(sock, POLLOUT, sec, usec);
5612}
5613
5614inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
5615 time_t usec) {
5616 struct pollfd pfd_read;
5617 pfd_read.fd = sock;
5618 pfd_read.events = POLLIN | POLLOUT;
5619 pfd_read.revents = 0;
5620
5621 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5622
5623 auto poll_res =
5624 handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
5625
5626 if (poll_res == 0) { return Error::ConnectionTimeout; }
5627
5628 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
5629 auto error = 0;
5630 socklen_t len = sizeof(error);
5631 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
5632 reinterpret_cast<char *>(&error), &len);
5633 auto successful = res >= 0 && !error;
5634 return successful ? Error::Success : Error::Connection;
5635 }
5636
5637 return Error::Connection;
5638}
5639
5640inline bool is_socket_alive(socket_t sock) {
5641 const auto val = detail::select_read(sock, 0, 0);
5642 if (val == 0) {
5643 return true;
5644 } else if (val < 0 && errno == EBADF) {
5645 return false;
5646 }
5647 char buf[1];
5648 return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
5649}
5650
5651class SocketStream final : public Stream {
5652public:
5653 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5654 time_t write_timeout_sec, time_t write_timeout_usec,
5655 time_t max_timeout_msec = 0,
5656 std::chrono::time_point<std::chrono::steady_clock> start_time =
5657 (std::chrono::steady_clock::time_point::min)());
5658 ~SocketStream() override;
5659
5660 bool is_readable() const override;
5661 bool wait_readable() const override;
5662 bool wait_writable() const override;
5663 bool is_peer_alive() const override;
5664 ssize_t read(char *ptr, size_t size) override;
5665 ssize_t write(const char *ptr, size_t size) override;
5666 void get_remote_ip_and_port(std::string &ip, int &port) const override;
5667 void get_local_ip_and_port(std::string &ip, int &port) const override;
5668 socket_t socket() const override;
5669 time_t duration() const override;
5670 void set_read_timeout(time_t sec, time_t usec = 0) override;
5671
5672private:
5673 socket_t sock_;
5674 time_t read_timeout_sec_;
5675 time_t read_timeout_usec_;
5676 time_t write_timeout_sec_;
5677 time_t write_timeout_usec_;
5678 time_t max_timeout_msec_;
5679 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5680
5681 std::vector<char> read_buff_;
5682 size_t read_buff_off_ = 0;
5683 size_t read_buff_content_size_ = 0;
5684
5685 static const size_t read_buff_size_ = 1024l * 4;
5686};
5687
5688inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
5689 time_t keep_alive_timeout_sec) {
5690 using namespace std::chrono;
5691
5692 const auto interval_usec =
5694
5695 // Avoid expensive `steady_clock::now()` call for the first time
5696 if (select_read(sock, 0, interval_usec) > 0) { return true; }
5697
5698 const auto start = steady_clock::now() - microseconds{interval_usec};
5699 const auto timeout = seconds{keep_alive_timeout_sec};
5700
5701 while (true) {
5702 if (svr_sock == INVALID_SOCKET) {
5703 break; // Server socket is closed
5704 }
5705
5706 auto val = select_read(sock, 0, interval_usec);
5707 if (val < 0) {
5708 break; // Ssocket error
5709 } else if (val == 0) {
5710 if (steady_clock::now() - start > timeout) {
5711 break; // Timeout
5712 }
5713 } else {
5714 return true; // Ready for read
5715 }
5716 }
5717
5718 return false;
5719}
5720
5721template <typename T>
5722inline bool
5723process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
5724 size_t keep_alive_max_count,
5725 time_t keep_alive_timeout_sec, T callback) {
5726 assert(keep_alive_max_count > 0);
5727 auto ret = false;
5728 auto count = keep_alive_max_count;
5729 while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
5730 auto close_connection = count == 1;
5731 auto connection_closed = false;
5732 ret = callback(close_connection, connection_closed);
5733 if (!ret || connection_closed) { break; }
5734 count--;
5735 }
5736 return ret;
5737}
5738
5739template <typename T>
5740inline bool
5741process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
5742 size_t keep_alive_max_count,
5743 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
5744 time_t read_timeout_usec, time_t write_timeout_sec,
5745 time_t write_timeout_usec, T callback) {
5746 return process_server_socket_core(
5747 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
5748 [&](bool close_connection, bool &connection_closed) {
5749 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5750 write_timeout_sec, write_timeout_usec);
5751 return callback(strm, close_connection, connection_closed);
5752 });
5753}
5754
5755inline bool process_client_socket(
5756 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5757 time_t write_timeout_sec, time_t write_timeout_usec,
5758 time_t max_timeout_msec,
5759 std::chrono::time_point<std::chrono::steady_clock> start_time,
5760 std::function<bool(Stream &)> callback) {
5761 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5762 write_timeout_sec, write_timeout_usec, max_timeout_msec,
5763 start_time);
5764 return callback(strm);
5765}
5766
5767inline int shutdown_socket(socket_t sock) noexcept {
5768#ifdef _WIN32
5769 return shutdown(sock, SD_BOTH);
5770#else
5771 return shutdown(sock, SHUT_RDWR);
5772#endif
5773}
5774
5775inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
5776 if (s.size() > 1 && s[0] == '\0') {
5777 auto ret = s;
5778 ret[0] = '@';
5779 return ret;
5780 }
5781 return s;
5782}
5783
5784inline std::string
5785unescape_abstract_namespace_unix_domain(const std::string &s) {
5786 if (s.size() > 1 && s[0] == '@') {
5787 auto ret = s;
5788 ret[0] = '\0';
5789 return ret;
5790 }
5791 return s;
5792}
5793
5794inline int getaddrinfo_with_timeout(const char *node, const char *service,
5795 const struct addrinfo *hints,
5796 struct addrinfo **res, time_t timeout_sec) {
5797#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
5798 if (timeout_sec <= 0) {
5799 // No timeout specified, use standard getaddrinfo
5800 return getaddrinfo(node, service, hints, res);
5801 }
5802
5803#ifdef _WIN32
5804 // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
5805 OVERLAPPED overlapped = {};
5806 HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
5807 if (!event) { return EAI_FAIL; }
5808
5809 overlapped.hEvent = event;
5810
5811 PADDRINFOEXW result_addrinfo = nullptr;
5812 HANDLE cancel_handle = nullptr;
5813
5814 ADDRINFOEXW hints_ex = {};
5815 if (hints) {
5816 hints_ex.ai_flags = hints->ai_flags;
5817 hints_ex.ai_family = hints->ai_family;
5818 hints_ex.ai_socktype = hints->ai_socktype;
5819 hints_ex.ai_protocol = hints->ai_protocol;
5820 }
5821
5822 auto wnode = u8string_to_wstring(node);
5823 auto wservice = u8string_to_wstring(service);
5824
5825 auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
5826 hints ? &hints_ex : nullptr, &result_addrinfo,
5827 nullptr, &overlapped, nullptr, &cancel_handle);
5828
5829 if (ret == WSA_IO_PENDING) {
5830 auto wait_result =
5831 ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
5832 if (wait_result == WAIT_TIMEOUT) {
5833 if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
5834 ::CloseHandle(event);
5835 return EAI_AGAIN;
5836 }
5837
5838 DWORD bytes_returned;
5839 if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
5840 &bytes_returned, FALSE)) {
5841 ::CloseHandle(event);
5842 return ::WSAGetLastError();
5843 }
5844 }
5845
5846 ::CloseHandle(event);
5847
5848 if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
5849 *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
5850 return 0;
5851 }
5852
5853 return ret;
5854#elif TARGET_OS_MAC && defined(__clang__)
5855 if (!node) { return EAI_NONAME; }
5856 // macOS implementation using CFHost API for asynchronous DNS resolution
5857 CFStringRef hostname_ref = CFStringCreateWithCString(
5858 kCFAllocatorDefault, node, kCFStringEncodingUTF8);
5859 if (!hostname_ref) { return EAI_MEMORY; }
5860
5861 CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
5862 CFRelease(hostname_ref);
5863 if (!host_ref) { return EAI_MEMORY; }
5864
5865 // Set up context for callback
5866 struct CFHostContext {
5867 bool completed = false;
5868 bool success = false;
5869 CFArrayRef addresses = nullptr;
5870 std::mutex mutex;
5871 std::condition_variable cv;
5872 } context;
5873
5874 CFHostClientContext client_context;
5875 memset(&client_context, 0, sizeof(client_context));
5876 client_context.info = &context;
5877
5878 // Set callback
5879 auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
5880 const CFStreamError *error, void *info) {
5881 auto ctx = static_cast<CFHostContext *>(info);
5882 std::lock_guard<std::mutex> lock(ctx->mutex);
5883
5884 if (error && error->error != 0) {
5885 ctx->success = false;
5886 } else {
5887 Boolean hasBeenResolved;
5888 ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
5889 if (ctx->addresses && hasBeenResolved) {
5890 CFRetain(ctx->addresses);
5891 ctx->success = true;
5892 } else {
5893 ctx->success = false;
5894 }
5895 }
5896 ctx->completed = true;
5897 ctx->cv.notify_one();
5898 };
5899
5900 if (!CFHostSetClient(host_ref, callback, &client_context)) {
5901 CFRelease(host_ref);
5902 return EAI_SYSTEM;
5903 }
5904
5905 // Schedule on run loop
5906 CFRunLoopRef run_loop = CFRunLoopGetCurrent();
5907 CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5908
5909 // Start resolution
5910 CFStreamError stream_error;
5911 if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
5912 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5913 CFRelease(host_ref);
5914 return EAI_FAIL;
5915 }
5916
5917 // Wait for completion with timeout
5918 auto timeout_time =
5919 std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
5920 bool timed_out = false;
5921
5922 {
5923 std::unique_lock<std::mutex> lock(context.mutex);
5924
5925 while (!context.completed) {
5926 auto now = std::chrono::steady_clock::now();
5927 if (now >= timeout_time) {
5928 timed_out = true;
5929 break;
5930 }
5931
5932 // Run the runloop for a short time
5933 lock.unlock();
5934 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
5935 lock.lock();
5936 }
5937 }
5938
5939 // Clean up
5940 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5941 CFHostSetClient(host_ref, nullptr, nullptr);
5942
5943 if (timed_out || !context.completed) {
5944 CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
5945 CFRelease(host_ref);
5946 return EAI_AGAIN;
5947 }
5948
5949 if (!context.success || !context.addresses) {
5950 CFRelease(host_ref);
5951 return EAI_NODATA;
5952 }
5953
5954 // Convert CFArray to addrinfo
5955 CFIndex count = CFArrayGetCount(context.addresses);
5956 if (count == 0) {
5957 CFRelease(context.addresses);
5958 CFRelease(host_ref);
5959 return EAI_NODATA;
5960 }
5961
5962 struct addrinfo *result_addrinfo = nullptr;
5963 struct addrinfo **current = &result_addrinfo;
5964
5965 for (CFIndex i = 0; i < count; i++) {
5966 CFDataRef addr_data =
5967 static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
5968 if (!addr_data) continue;
5969
5970 const struct sockaddr *sockaddr_ptr =
5971 reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
5972 socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
5973
5974 // Allocate addrinfo structure
5975 *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
5976 if (!*current) {
5977 freeaddrinfo(result_addrinfo);
5978 CFRelease(context.addresses);
5979 CFRelease(host_ref);
5980 return EAI_MEMORY;
5981 }
5982
5983 memset(*current, 0, sizeof(struct addrinfo));
5984
5985 // Set up addrinfo fields
5986 (*current)->ai_family = sockaddr_ptr->sa_family;
5987 (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
5988 (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
5989 (*current)->ai_addrlen = sockaddr_len;
5990
5991 // Copy sockaddr
5992 (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
5993 if (!(*current)->ai_addr) {
5994 freeaddrinfo(result_addrinfo);
5995 CFRelease(context.addresses);
5996 CFRelease(host_ref);
5997 return EAI_MEMORY;
5998 }
5999 memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
6000
6001 // Set port if service is specified
6002 if (service && *service) {
6003 int port = 0;
6004 if (parse_port(service, strlen(service), port)) {
6005 if (sockaddr_ptr->sa_family == AF_INET) {
6006 reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
6007 ->sin_port = htons(static_cast<uint16_t>(port));
6008 } else if (sockaddr_ptr->sa_family == AF_INET6) {
6009 reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
6010 ->sin6_port = htons(static_cast<uint16_t>(port));
6011 }
6012 }
6013 }
6014
6015 current = &((*current)->ai_next);
6016 }
6017
6018 CFRelease(context.addresses);
6019 CFRelease(host_ref);
6020
6021 *res = result_addrinfo;
6022 return 0;
6023#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \
6024 (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
6025 // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while
6026 // the resolver worker still references the stack-local gaicb. The cancel
6027 // path therefore waits (gai_suspend with no timeout) for the worker to
6028 // actually finish before letting the stack frame go. The trade-off is that
6029 // a wedged DNS server can hold this thread for the system resolver timeout
6030 // (~30s by default) past the caller's connection timeout.
6031 struct gaicb request {};
6032 struct gaicb *requests[1] = {&request};
6033 struct sigevent sevp {};
6034 struct timespec timeout {
6035 timeout_sec, 0
6036 };
6037
6038 request.ar_name = node;
6039 request.ar_service = service;
6040 request.ar_request = hints;
6041 sevp.sigev_notify = SIGEV_NONE;
6042
6043 int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
6044 if (rc != 0) { return rc; }
6045
6046 auto cleanup = scope_exit([&] {
6047 if (request.ar_result) { freeaddrinfo(request.ar_result); }
6048 });
6049
6050 int wait_result = gai_suspend(requests, 1, &timeout);
6051
6052 if (wait_result == 0 || wait_result == EAI_ALLDONE) {
6053 int gai_result = gai_error(&request);
6054 if (gai_result == 0) {
6055 *res = request.ar_result;
6056 request.ar_result = nullptr;
6057 return 0;
6058 }
6059 return gai_result;
6060 }
6061
6062 gai_cancel(&request);
6063 while (gai_error(&request) == EAI_INPROGRESS) {
6064 gai_suspend(requests, 1, nullptr);
6065 }
6066 return wait_result;
6067#else
6068 // Fallback implementation using thread-based timeout for other Unix systems.
6069
6070 struct GetAddrInfoState {
6071 ~GetAddrInfoState() {
6072 if (info) { freeaddrinfo(info); }
6073 }
6074
6075 std::mutex mutex;
6076 std::condition_variable result_cv;
6077 bool completed = false;
6078 int result = EAI_SYSTEM;
6079 std::string node;
6080 std::string service;
6081 struct addrinfo hints;
6082 struct addrinfo *info = nullptr;
6083 };
6084
6085 // Allocate on the heap, so the resolver thread can keep using the data.
6086 auto state = std::make_shared<GetAddrInfoState>();
6087 if (node) { state->node = node; }
6088 state->service = service;
6089 state->hints = *hints;
6090
6091 std::thread resolve_thread([state]() {
6092 auto thread_result =
6093 getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
6094 &state->info);
6095
6096 std::lock_guard<std::mutex> lock(state->mutex);
6097 state->result = thread_result;
6098 state->completed = true;
6099 state->result_cv.notify_one();
6100 });
6101
6102 // Wait for completion or timeout
6103 std::unique_lock<std::mutex> lock(state->mutex);
6104 auto finished =
6105 state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
6106 [&] { return state->completed; });
6107
6108 if (finished) {
6109 // Operation completed within timeout
6110 resolve_thread.join();
6111 *res = state->info;
6112 state->info = nullptr; // Pass ownership to caller
6113 return state->result;
6114 } else {
6115 // Timeout occurred
6116 resolve_thread.detach(); // Let the thread finish in background
6117 return EAI_AGAIN; // Return timeout error
6118 }
6119#endif
6120#else
6121 (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
6122 return getaddrinfo(node, service, hints, res);
6123#endif
6124}
6125
6126template <typename BindOrConnect>
6127socket_t create_socket(const std::string &host, const std::string &ip, int port,
6128 int address_family, int socket_flags, bool tcp_nodelay,
6129 bool ipv6_v6only, SocketOptions socket_options,
6130 BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
6131 // Get address info
6132 const char *node = nullptr;
6133 struct addrinfo hints;
6134 struct addrinfo *result;
6135
6136 memset(&hints, 0, sizeof(struct addrinfo));
6137 hints.ai_socktype = SOCK_STREAM;
6138 hints.ai_protocol = IPPROTO_IP;
6139
6140 if (!ip.empty()) {
6141 node = ip.c_str();
6142 // Ask getaddrinfo to convert IP in c-string to address
6143 hints.ai_family = AF_UNSPEC;
6144 hints.ai_flags = AI_NUMERICHOST;
6145 } else {
6146 if (!host.empty()) { node = host.c_str(); }
6147 hints.ai_family = address_family;
6148 hints.ai_flags = socket_flags;
6149 }
6150
6151#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
6152 if (hints.ai_family == AF_UNIX) {
6153 const auto addrlen = host.length();
6154 if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
6155
6156#ifdef SOCK_CLOEXEC
6157 auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
6158 hints.ai_protocol);
6159#else
6160 auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
6161#endif
6162
6163 if (sock != INVALID_SOCKET) {
6164 sockaddr_un addr{};
6165 addr.sun_family = AF_UNIX;
6166
6167 auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
6168 std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
6169
6170 hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
6171 hints.ai_addrlen = static_cast<socklen_t>(
6172 sizeof(addr) - sizeof(addr.sun_path) + addrlen);
6173
6174#ifndef SOCK_CLOEXEC
6175#ifndef _WIN32
6176 fcntl(sock, F_SETFD, FD_CLOEXEC);
6177#endif
6178#endif
6179
6180 if (socket_options) { socket_options(sock); }
6181
6182#ifdef _WIN32
6183 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
6184 // remove the option.
6185 set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
6186#endif
6187
6188 bool dummy;
6189 if (!bind_or_connect(sock, hints, dummy)) {
6190 close_socket(sock);
6191 sock = INVALID_SOCKET;
6192 }
6193 }
6194 return sock;
6195 }
6196#endif
6197
6198 auto service = std::to_string(port);
6199
6200 if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
6201 timeout_sec)) {
6202#if defined __linux__ && !defined __ANDROID__
6203 res_init();
6204#endif
6205 return INVALID_SOCKET;
6206 }
6207 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6208
6209 for (auto rp = result; rp; rp = rp->ai_next) {
6210 // Create a socket
6211#ifdef _WIN32
6212 auto sock =
6213 WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
6214 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
6215 /**
6216 * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
6217 * and above the socket creation fails on older Windows Systems.
6218 *
6219 * Let's try to create a socket the old way in this case.
6220 *
6221 * Reference:
6222 * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
6223 *
6224 * WSA_FLAG_NO_HANDLE_INHERIT:
6225 * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
6226 * SP1, and later
6227 *
6228 */
6229 if (sock == INVALID_SOCKET) {
6230 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6231 }
6232#else
6233
6234#ifdef SOCK_CLOEXEC
6235 auto sock =
6236 socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
6237#else
6238 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6239#endif
6240
6241#endif
6242 if (sock == INVALID_SOCKET) { continue; }
6243
6244#if !defined _WIN32 && !defined SOCK_CLOEXEC
6245 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
6246 close_socket(sock);
6247 continue;
6248 }
6249#endif
6250
6251 if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
6252
6253 if (rp->ai_family == AF_INET6) {
6254 set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
6255 }
6256
6257 if (socket_options) { socket_options(sock); }
6258
6259 // bind or connect
6260 auto quit = false;
6261 if (bind_or_connect(sock, *rp, quit)) { return sock; }
6262
6263 close_socket(sock);
6264
6265 if (quit) { break; }
6266 }
6267
6268 return INVALID_SOCKET;
6269}
6270
6271inline void set_nonblocking(socket_t sock, bool nonblocking) {
6272#ifdef _WIN32
6273 auto flags = nonblocking ? 1UL : 0UL;
6274 ioctlsocket(sock, FIONBIO, &flags);
6275#else
6276 auto flags = fcntl(sock, F_GETFL, 0);
6277 fcntl(sock, F_SETFL,
6278 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
6279#endif
6280}
6281
6282inline bool is_connection_error() {
6283#ifdef _WIN32
6284 return WSAGetLastError() != WSAEWOULDBLOCK;
6285#else
6286 return errno != EINPROGRESS;
6287#endif
6288}
6289
6290inline bool bind_ip_address(socket_t sock, const std::string &host) {
6291 struct addrinfo hints;
6292 struct addrinfo *result;
6293
6294 memset(&hints, 0, sizeof(struct addrinfo));
6295 hints.ai_family = AF_UNSPEC;
6296 hints.ai_socktype = SOCK_STREAM;
6297 hints.ai_protocol = 0;
6298
6299 if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
6300 return false;
6301 }
6302
6303 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6304
6305 auto ret = false;
6306 for (auto rp = result; rp; rp = rp->ai_next) {
6307 const auto &ai = *rp;
6308 if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
6309 ret = true;
6310 break;
6311 }
6312 }
6313
6314 return ret;
6315}
6316
6317#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
6318#define USE_IF2IP
6319#endif
6320
6321#ifdef USE_IF2IP
6322inline std::string if2ip(int address_family, const std::string &ifn) {
6323 struct ifaddrs *ifap;
6324 getifaddrs(&ifap);
6325 auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
6326
6327 std::string addr_candidate;
6328 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
6329 if (ifa->ifa_addr && ifn == ifa->ifa_name &&
6330 (AF_UNSPEC == address_family ||
6331 ifa->ifa_addr->sa_family == address_family)) {
6332 if (ifa->ifa_addr->sa_family == AF_INET) {
6333 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
6334 char buf[INET_ADDRSTRLEN];
6335 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
6336 return std::string(buf, INET_ADDRSTRLEN);
6337 }
6338 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
6339 auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
6340 if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
6341 char buf[INET6_ADDRSTRLEN] = {};
6342 if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
6343 // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
6344 auto s6_addr_head = sa->sin6_addr.s6_addr[0];
6345 if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
6346 addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
6347 } else {
6348 return std::string(buf, INET6_ADDRSTRLEN);
6349 }
6350 }
6351 }
6352 }
6353 }
6354 }
6355 return addr_candidate;
6356}
6357#endif
6358
6359inline socket_t create_client_socket(
6360 const std::string &host, const std::string &ip, int port,
6361 int address_family, bool tcp_nodelay, bool ipv6_v6only,
6362 SocketOptions socket_options, time_t connection_timeout_sec,
6363 time_t connection_timeout_usec, time_t read_timeout_sec,
6364 time_t read_timeout_usec, time_t write_timeout_sec,
6365 time_t write_timeout_usec, const std::string &intf, Error &error) {
6366 auto sock = create_socket(
6367 host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
6368 std::move(socket_options),
6369 [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
6370 if (!intf.empty()) {
6371#ifdef USE_IF2IP
6372 auto ip_from_if = if2ip(address_family, intf);
6373 if (ip_from_if.empty()) { ip_from_if = intf; }
6374 if (!bind_ip_address(sock2, ip_from_if)) {
6375 error = Error::BindIPAddress;
6376 return false;
6377 }
6378#endif
6379 }
6380
6381 set_nonblocking(sock2, true);
6382
6383 auto ret =
6384 ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
6385
6386 if (ret < 0) {
6387 if (is_connection_error()) {
6388 error = Error::Connection;
6389 return false;
6390 }
6391 error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
6392 connection_timeout_usec);
6393 if (error != Error::Success) {
6394 if (error == Error::ConnectionTimeout) { quit = true; }
6395 return false;
6396 }
6397 }
6398
6399 set_nonblocking(sock2, false);
6400 set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
6401 read_timeout_usec);
6402 set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
6403 write_timeout_usec);
6404
6405 error = Error::Success;
6406 return true;
6407 },
6408 connection_timeout_sec); // Pass DNS timeout
6409
6410 if (sock != INVALID_SOCKET) {
6411 error = Error::Success;
6412 } else {
6413 if (error == Error::Success) { error = Error::Connection; }
6414 }
6415
6416 return sock;
6417}
6418
6419inline bool get_ip_and_port(const struct sockaddr_storage &addr,
6420 socklen_t addr_len, std::string &ip, int &port) {
6421 if (addr.ss_family == AF_INET) {
6422 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
6423 } else if (addr.ss_family == AF_INET6) {
6424 port =
6425 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
6426 } else {
6427 return false;
6428 }
6429
6430 std::array<char, NI_MAXHOST> ipstr{};
6431 if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
6432 ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
6433 0, NI_NUMERICHOST)) {
6434 return false;
6435 }
6436
6437 ip = ipstr.data();
6438 return true;
6439}
6440
6441inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
6442 struct sockaddr_storage addr;
6443 socklen_t addr_len = sizeof(addr);
6444 if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
6445 &addr_len)) {
6446 get_ip_and_port(addr, addr_len, ip, port);
6447 }
6448}
6449
6450inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
6451 struct sockaddr_storage addr;
6452 socklen_t addr_len = sizeof(addr);
6453
6454 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
6455 &addr_len)) {
6456#ifndef _WIN32
6457 if (addr.ss_family == AF_UNIX) {
6458#if defined(__linux__)
6459 struct ucred ucred;
6460 socklen_t len = sizeof(ucred);
6461 if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
6462 port = ucred.pid;
6463 }
6464#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
6465 pid_t pid;
6466 socklen_t len = sizeof(pid);
6467 if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
6468 port = pid;
6469 }
6470#endif
6471 return;
6472 }
6473#endif
6474 get_ip_and_port(addr, addr_len, ip, port);
6475 }
6476}
6477
6478// Recursive form retained so operator""_t below can compute hashes for
6479// switch-case labels at compile time (C++11 constexpr forbids loops). Do not
6480// call from runtime paths with arbitrary-length inputs — use str2tag()
6481// instead, which is iterative and stack-safe.
6482inline constexpr unsigned int str2tag_core(const char *s, size_t l,
6483 unsigned int h) {
6484 return (l == 0)
6485 ? h
6486 : str2tag_core(
6487 s + 1, l - 1,
6488 // Unsets the 6 high bits of h, therefore no overflow happens
6489 (((std::numeric_limits<unsigned int>::max)() >> 6) &
6490 h * 33) ^
6491 static_cast<unsigned char>(*s));
6492}
6493
6494inline unsigned int str2tag(const std::string &s) {
6495 // Iterative form of str2tag_core: the recursive constexpr version is kept
6496 // for compile-time UDL evaluation of short string literals, but at runtime
6497 // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that
6498 // would blow the stack with one frame per character.
6499 unsigned int h = 0;
6500 for (auto c : s) {
6501 h = (((std::numeric_limits<unsigned int>::max)() >> 6) & h * 33) ^
6502 static_cast<unsigned char>(c);
6503 }
6504 return h;
6505}
6506
6507namespace udl {
6508
6509inline constexpr unsigned int operator""_t(const char *s, size_t l) {
6510 return str2tag_core(s, l, 0);
6511}
6512
6513} // namespace udl
6514
6515inline std::string
6516find_content_type(const std::string &path,
6517 const std::map<std::string, std::string> &user_data,
6518 const std::string &default_content_type) {
6519 auto ext = file_extension(path);
6520
6521 auto it = user_data.find(ext);
6522 if (it != user_data.end()) { return it->second; }
6523
6524 using udl::operator""_t;
6525
6526 switch (str2tag(ext)) {
6527 default: return default_content_type;
6528
6529 case "css"_t: return "text/css";
6530 case "csv"_t: return "text/csv";
6531 case "htm"_t:
6532 case "html"_t: return "text/html";
6533 case "js"_t:
6534 case "mjs"_t: return "text/javascript";
6535 case "txt"_t: return "text/plain";
6536 case "vtt"_t: return "text/vtt";
6537
6538 case "apng"_t: return "image/apng";
6539 case "avif"_t: return "image/avif";
6540 case "bmp"_t: return "image/bmp";
6541 case "gif"_t: return "image/gif";
6542 case "png"_t: return "image/png";
6543 case "svg"_t: return "image/svg+xml";
6544 case "webp"_t: return "image/webp";
6545 case "ico"_t: return "image/x-icon";
6546 case "tif"_t: return "image/tiff";
6547 case "tiff"_t: return "image/tiff";
6548 case "jpg"_t:
6549 case "jpeg"_t: return "image/jpeg";
6550
6551 case "mp4"_t: return "video/mp4";
6552 case "mpeg"_t: return "video/mpeg";
6553 case "webm"_t: return "video/webm";
6554
6555 case "mp3"_t: return "audio/mp3";
6556 case "mpga"_t: return "audio/mpeg";
6557 case "weba"_t: return "audio/webm";
6558 case "wav"_t: return "audio/wave";
6559
6560 case "otf"_t: return "font/otf";
6561 case "ttf"_t: return "font/ttf";
6562 case "woff"_t: return "font/woff";
6563 case "woff2"_t: return "font/woff2";
6564
6565 case "7z"_t: return "application/x-7z-compressed";
6566 case "atom"_t: return "application/atom+xml";
6567 case "pdf"_t: return "application/pdf";
6568 case "json"_t: return "application/json";
6569 case "rss"_t: return "application/rss+xml";
6570 case "tar"_t: return "application/x-tar";
6571 case "xht"_t:
6572 case "xhtml"_t: return "application/xhtml+xml";
6573 case "xslt"_t: return "application/xslt+xml";
6574 case "xml"_t: return "application/xml";
6575 case "gz"_t: return "application/gzip";
6576 case "zip"_t: return "application/zip";
6577 case "wasm"_t: return "application/wasm";
6578 }
6579}
6580
6581inline std::string
6582extract_media_type(const std::string &content_type,
6583 std::map<std::string, std::string> *params = nullptr) {
6584 // Extract type/subtype from Content-Type value (RFC 2045)
6585 // e.g. "application/json; charset=utf-8" -> "application/json"
6586 auto media_type = content_type;
6587 auto semicolon_pos = media_type.find(';');
6588 if (semicolon_pos != std::string::npos) {
6589 auto param_str = media_type.substr(semicolon_pos + 1);
6590 media_type = media_type.substr(0, semicolon_pos);
6591
6592 if (params) {
6593 // Parse parameters: key=value pairs separated by ';'
6594 split(param_str.data(), param_str.data() + param_str.size(), ';',
6595 [&](const char *b, const char *e) {
6596 std::string key;
6597 std::string val;
6598 split(b, e, '=', [&](const char *b2, const char *e2) {
6599 if (key.empty()) {
6600 key.assign(b2, e2);
6601 } else {
6602 val.assign(b2, e2);
6603 }
6604 });
6605 if (!key.empty()) {
6606 params->emplace(trim_copy(key), trim_double_quotes_copy(val));
6607 }
6608 });
6609 }
6610 }
6611
6612 // Trim whitespace from media type
6613 return trim_copy(media_type);
6614}
6615
6616inline bool can_compress_content_type(const std::string &content_type) {
6617 using udl::operator""_t;
6618
6619 auto mime_type = extract_media_type(content_type);
6620 auto tag = str2tag(mime_type);
6621
6622 switch (tag) {
6623 case "image/svg+xml"_t:
6624 case "application/javascript"_t:
6625 case "application/x-javascript"_t:
6626 case "application/json"_t:
6627 case "application/ld+json"_t:
6628 case "application/xml"_t:
6629 case "application/xhtml+xml"_t:
6630 case "application/rss+xml"_t:
6631 case "application/atom+xml"_t:
6632 case "application/xslt+xml"_t:
6633 case "application/protobuf"_t: return true;
6634
6635 case "text/event-stream"_t: return false;
6636
6637 default: return !mime_type.rfind("text/", 0);
6638 }
6639}
6640
6641inline bool parse_quality(const char *b, const char *e, std::string &token,
6642 double &quality) {
6643 quality = 1.0;
6644 token.clear();
6645
6646 // Split on first ';': left = token name, right = parameters
6647 const char *params_b = nullptr;
6648 std::size_t params_len = 0;
6649
6650 divide(
6651 b, static_cast<std::size_t>(e - b), ';',
6652 [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
6653 auto r = trim(lb, lb + llen, 0, llen);
6654 if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
6655 params_b = rb;
6656 params_len = rlen;
6657 });
6658
6659 if (token.empty()) { return false; }
6660 if (params_len == 0) { return true; }
6661
6662 // Scan parameters for q= (stops on first match)
6663 bool invalid = false;
6664 split_find(params_b, params_b + params_len, ';',
6665 (std::numeric_limits<size_t>::max)(),
6666 [&](const char *pb, const char *pe) -> bool {
6667 // Match exactly "q=" or "Q=" (not "query=" etc.)
6668 auto len = static_cast<size_t>(pe - pb);
6669 if (len < 2) { return false; }
6670 if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
6671 return false;
6672 }
6673
6674 // Trim the value portion
6675 auto r = trim(pb, pe, 2, len);
6676 if (r.first >= r.second) {
6677 invalid = true;
6678 return true;
6679 }
6680
6681 double v = 0.0;
6682 auto res = from_chars(pb + r.first, pb + r.second, v);
6683 if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
6684 invalid = true;
6685 return true;
6686 }
6687 quality = v;
6688 return true;
6689 });
6690
6691 return !invalid;
6692}
6693
6694inline EncodingType encoding_type(const Request &req, const Response &res) {
6695 if (!can_compress_content_type(res.get_header_value("Content-Type"))) {
6696 return EncodingType::None;
6697 }
6698
6699 const auto &s = req.get_header_value("Accept-Encoding");
6700 if (s.empty()) { return EncodingType::None; }
6701
6702 // Single-pass: iterate tokens and track the best supported encoding.
6703 // Server preference breaks ties (br > gzip > zstd).
6704 EncodingType best = EncodingType::None;
6705 double best_q = 0.0; // q=0 means "not acceptable"
6706
6707 // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
6708 auto priority = [](EncodingType t) -> int {
6709 switch (t) {
6710 case EncodingType::Brotli: return 0;
6711 case EncodingType::Gzip: return 1;
6712 case EncodingType::Zstd: return 2;
6713 default: return 3;
6714 }
6715 };
6716
6717 std::string name;
6718 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
6719 double quality = 1.0;
6720 if (!parse_quality(b, e, name, quality)) { return; }
6721 if (quality <= 0.0) { return; }
6722
6723 EncodingType type = EncodingType::None;
6724#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6725 if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
6726#endif
6727#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6728 if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
6729 type = EncodingType::Gzip;
6730 }
6731#endif
6732#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6733 if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
6734 type = EncodingType::Zstd;
6735 }
6736#endif
6737
6738 if (type == EncodingType::None) { return; }
6739
6740 // Higher q-value wins; for equal q, server preference breaks ties
6741 if (quality > best_q ||
6742 (quality == best_q && priority(type) < priority(best))) {
6743 best_q = quality;
6744 best = type;
6745 }
6746 });
6747
6748 return best;
6749}
6750
6751inline std::unique_ptr<compressor> make_compressor(EncodingType type) {
6752#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6753 if (type == EncodingType::Gzip) {
6754 return detail::make_unique<gzip_compressor>();
6755 }
6756#endif
6757#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6758 if (type == EncodingType::Brotli) {
6759 return detail::make_unique<brotli_compressor>();
6760 }
6761#endif
6762#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6763 if (type == EncodingType::Zstd) {
6764 return detail::make_unique<zstd_compressor>();
6765 }
6766#endif
6767 (void)type;
6768 return nullptr;
6769}
6770
6771inline const char *encoding_name(EncodingType type) {
6772 switch (type) {
6773 case EncodingType::Gzip: return "gzip";
6774 case EncodingType::Brotli: return "br";
6775 case EncodingType::Zstd: return "zstd";
6776 default: return "";
6777 }
6778}
6779
6780inline bool nocompressor::compress(const char *data, size_t data_length,
6781 bool /*last*/, Callback callback) {
6782 if (!data_length) { return true; }
6783 return callback(data, data_length);
6784}
6785
6786#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6787inline gzip_compressor::gzip_compressor() {
6788 std::memset(&strm_, 0, sizeof(strm_));
6789 strm_.zalloc = Z_NULL;
6790 strm_.zfree = Z_NULL;
6791 strm_.opaque = Z_NULL;
6792
6793 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
6794 Z_DEFAULT_STRATEGY) == Z_OK;
6795}
6796
6797inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
6798
6799inline bool gzip_compressor::compress(const char *data, size_t data_length,
6800 bool last, Callback callback) {
6801 assert(is_valid_);
6802
6803 do {
6804 constexpr size_t max_avail_in =
6805 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6806
6807 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6808 (std::min)(data_length, max_avail_in));
6809 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6810
6811 data_length -= strm_.avail_in;
6812 data += strm_.avail_in;
6813
6814 auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
6815 auto ret = Z_OK;
6816
6817 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6818 do {
6819 strm_.avail_out = static_cast<uInt>(buff.size());
6820 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6821
6822 ret = deflate(&strm_, flush);
6823 if (ret == Z_STREAM_ERROR) { return false; }
6824
6825 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6826 return false;
6827 }
6828 } while (strm_.avail_out == 0);
6829
6830 assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
6831 (flush == Z_NO_FLUSH && ret == Z_OK));
6832 assert(strm_.avail_in == 0);
6833 } while (data_length > 0);
6834
6835 return true;
6836}
6837
6838inline gzip_decompressor::gzip_decompressor() {
6839 std::memset(&strm_, 0, sizeof(strm_));
6840 strm_.zalloc = Z_NULL;
6841 strm_.zfree = Z_NULL;
6842 strm_.opaque = Z_NULL;
6843
6844 // 15 is the value of wbits, which should be at the maximum possible value
6845 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
6846 // that the stream type should be automatically detected either gzip or
6847 // deflate.
6848 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
6849}
6850
6851inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
6852
6853inline bool gzip_decompressor::is_valid() const { return is_valid_; }
6854
6855inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
6856 Callback callback) {
6857 assert(is_valid_);
6858
6859 auto ret = Z_OK;
6860
6861 do {
6862 constexpr size_t max_avail_in =
6863 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6864
6865 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6866 (std::min)(data_length, max_avail_in));
6867 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6868
6869 data_length -= strm_.avail_in;
6870 data += strm_.avail_in;
6871
6872 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6873 while (strm_.avail_in > 0 && ret == Z_OK) {
6874 strm_.avail_out = static_cast<uInt>(buff.size());
6875 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6876
6877 ret = inflate(&strm_, Z_NO_FLUSH);
6878
6879 assert(ret != Z_STREAM_ERROR);
6880 switch (ret) {
6881 case Z_NEED_DICT:
6882 case Z_DATA_ERROR:
6883 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
6884 }
6885
6886 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6887 return false;
6888 }
6889 }
6890
6891 if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
6892
6893 } while (data_length > 0);
6894
6895 return true;
6896}
6897#endif
6898
6899#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6900inline brotli_compressor::brotli_compressor() {
6901 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
6902}
6903
6904inline brotli_compressor::~brotli_compressor() {
6905 BrotliEncoderDestroyInstance(state_);
6906}
6907
6908inline bool brotli_compressor::compress(const char *data, size_t data_length,
6909 bool last, Callback callback) {
6910 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6911
6912 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
6913 auto available_in = data_length;
6914 auto next_in = reinterpret_cast<const uint8_t *>(data);
6915
6916 for (;;) {
6917 if (last) {
6918 if (BrotliEncoderIsFinished(state_)) { break; }
6919 } else {
6920 if (!available_in) { break; }
6921 }
6922
6923 auto available_out = buff.size();
6924 auto next_out = buff.data();
6925
6926 if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
6927 &available_out, &next_out, nullptr)) {
6928 return false;
6929 }
6930
6931 auto output_bytes = buff.size() - available_out;
6932 if (output_bytes) {
6933 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
6934 }
6935 }
6936
6937 return true;
6938}
6939
6940inline brotli_decompressor::brotli_decompressor() {
6941 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
6942 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
6943 : BROTLI_DECODER_RESULT_ERROR;
6944}
6945
6946inline brotli_decompressor::~brotli_decompressor() {
6947 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
6948}
6949
6950inline bool brotli_decompressor::is_valid() const { return decoder_s; }
6951
6952inline bool brotli_decompressor::decompress(const char *data,
6953 size_t data_length,
6954 Callback callback) {
6955 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6956 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
6957 return 0;
6958 }
6959
6960 auto next_in = reinterpret_cast<const uint8_t *>(data);
6961 size_t avail_in = data_length;
6962 size_t total_out;
6963
6964 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
6965
6966 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6967 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
6968 char *next_out = buff.data();
6969 size_t avail_out = buff.size();
6970
6971 decoder_r = BrotliDecoderDecompressStream(
6972 decoder_s, &avail_in, &next_in, &avail_out,
6973 reinterpret_cast<uint8_t **>(&next_out), &total_out);
6974
6975 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
6976
6977 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
6978 }
6979
6980 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6981 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
6982}
6983#endif
6984
6985#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6986inline zstd_compressor::zstd_compressor() {
6987 ctx_ = ZSTD_createCCtx();
6988 ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
6989}
6990
6991inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
6992
6993inline bool zstd_compressor::compress(const char *data, size_t data_length,
6994 bool last, Callback callback) {
6995 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6996
6997 ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
6998 ZSTD_inBuffer input = {data, data_length, 0};
6999
7000 bool finished;
7001 do {
7002 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
7003 size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
7004
7005 if (ZSTD_isError(remaining)) { return false; }
7006
7007 if (!callback(buff.data(), output.pos)) { return false; }
7008
7009 finished = last ? (remaining == 0) : (input.pos == input.size);
7010
7011 } while (!finished);
7012
7013 return true;
7014}
7015
7016inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
7017
7018inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
7019
7020inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
7021
7022inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
7023 Callback callback) {
7024 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
7025 ZSTD_inBuffer input = {data, data_length, 0};
7026
7027 while (input.pos < input.size) {
7028 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
7029 size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
7030
7031 if (ZSTD_isError(remaining)) { return false; }
7032
7033 if (!callback(buff.data(), output.pos)) { return false; }
7034 }
7035
7036 return true;
7037}
7038#endif
7039
7040inline std::unique_ptr<decompressor>
7041create_decompressor(const std::string &encoding) {
7042 std::unique_ptr<decompressor> decompressor;
7043
7044 if (encoding == "gzip" || encoding == "deflate") {
7045#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7046 decompressor = detail::make_unique<gzip_decompressor>();
7047#endif
7048 } else if (encoding.find("br") != std::string::npos) {
7049#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7050 decompressor = detail::make_unique<brotli_decompressor>();
7051#endif
7052 } else if (encoding == "zstd" || encoding.find("zstd") != std::string::npos) {
7053#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7054 decompressor = detail::make_unique<zstd_decompressor>();
7055#endif
7056 }
7057
7058 return decompressor;
7059}
7060
7061// Returns the best available compressor and its Content-Encoding name.
7062// Priority: Brotli > Gzip > Zstd (matches server-side preference).
7063inline std::pair<std::unique_ptr<compressor>, const char *>
7064create_compressor() {
7065#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7066 return {detail::make_unique<brotli_compressor>(), "br"};
7067#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
7068 return {detail::make_unique<gzip_compressor>(), "gzip"};
7069#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
7070 return {detail::make_unique<zstd_compressor>(), "zstd"};
7071#else
7072 return {nullptr, nullptr};
7073#endif
7074}
7075
7076inline bool is_prohibited_header_name(const std::string &name) {
7077 using udl::operator""_t;
7078
7079 switch (str2tag(name)) {
7080 case "REMOTE_ADDR"_t:
7081 case "REMOTE_PORT"_t:
7082 case "LOCAL_ADDR"_t:
7083 case "LOCAL_PORT"_t: return true;
7084 default: return false;
7085 }
7086}
7087
7088inline bool has_header(const Headers &headers, const std::string &key) {
7089 if (is_prohibited_header_name(key)) { return false; }
7090 return headers.find(key) != headers.end();
7091}
7092
7093inline const char *get_header_value(const Headers &headers,
7094 const std::string &key, const char *def,
7095 size_t id) {
7096 if (is_prohibited_header_name(key)) {
7097#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7098 std::string msg = "Prohibited header name '" + key + "' is specified.";
7099 throw std::invalid_argument(msg);
7100#else
7101 return "";
7102#endif
7103 }
7104
7105 auto rng = headers.equal_range(key);
7106 auto it = rng.first;
7107 std::advance(it, static_cast<ssize_t>(id));
7108 if (it != rng.second) { return it->second.c_str(); }
7109 return def;
7110}
7111
7112inline size_t get_header_value_count(const Headers &headers,
7113 const std::string &key) {
7114 auto r = headers.equal_range(key);
7115 return static_cast<size_t>(std::distance(r.first, r.second));
7116}
7117
7118template <typename Map>
7119inline typename Map::mapped_type
7120get_multimap_value(const Map &m, const std::string &key, size_t id) {
7121 auto rng = m.equal_range(key);
7122 auto it = rng.first;
7123 std::advance(it, static_cast<ssize_t>(id));
7124 if (it != rng.second) { return it->second; }
7125 return typename Map::mapped_type();
7126}
7127
7128inline void set_header(Headers &headers, const std::string &key,
7129 const std::string &val) {
7130 if (fields::is_field_name(key) && fields::is_field_value(val)) {
7131 headers.emplace(key, val);
7132 }
7133}
7134
7135inline bool read_headers(Stream &strm, Headers &headers) {
7136 const auto bufsiz = 2048;
7137 char buf[bufsiz];
7138 stream_line_reader line_reader(strm, buf, bufsiz);
7139
7140 size_t header_count = 0;
7141
7142 for (;;) {
7143 if (!line_reader.getline()) { return false; }
7144
7145 // Check if the line ends with CRLF.
7146 auto line_terminator_len = 2;
7147 if (line_reader.end_with_crlf()) {
7148 // Blank line indicates end of headers.
7149 if (line_reader.size() == 2) { break; }
7150 } else {
7151#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
7152 // Blank line indicates end of headers.
7153 if (line_reader.size() == 1) { break; }
7154 line_terminator_len = 1;
7155#else
7156 continue; // Skip invalid line.
7157#endif
7158 }
7159
7160 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7161
7162 // Check header count limit
7163 if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
7164
7165 // Exclude line terminator
7166 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
7167
7168 if (!parse_header(line_reader.ptr(), end,
7169 [&](const std::string &key, const std::string &val) {
7170 headers.emplace(key, val);
7171 })) {
7172 return false;
7173 }
7174
7175 header_count++;
7176 }
7177
7178 // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
7179 // headers that have different values to prevent request smuggling.
7180 auto cl_range = headers.equal_range("Content-Length");
7181 if (cl_range.first != cl_range.second) {
7182 const auto &first_val = cl_range.first->second;
7183 for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
7184 if (it->second != first_val) { return false; }
7185 }
7186 }
7187
7188 return true;
7189}
7190
7191inline bool read_websocket_upgrade_response(Stream &strm,
7192 const std::string &expected_accept,
7193 std::string &selected_subprotocol) {
7194 // Read status line
7195 const auto bufsiz = 2048;
7196 char buf[bufsiz];
7197 stream_line_reader line_reader(strm, buf, bufsiz);
7198 if (!line_reader.getline()) { return false; }
7199
7200 // Check for "HTTP/1.1 101"
7201 auto line = std::string(line_reader.ptr(), line_reader.size());
7202 if (line.find("HTTP/1.1 101") == std::string::npos) { return false; }
7203
7204 // Parse headers using existing read_headers
7205 Headers headers;
7206 if (!read_headers(strm, headers)) { return false; }
7207
7208 // Verify Upgrade: websocket (case-insensitive)
7209 auto upgrade_it = headers.find("Upgrade");
7210 if (upgrade_it == headers.end()) { return false; }
7211 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
7212 if (upgrade_val != "websocket") { return false; }
7213
7214 // Verify Connection header contains "Upgrade" (case-insensitive)
7215 auto connection_it = headers.find("Connection");
7216 if (connection_it == headers.end()) { return false; }
7217 auto connection_val = case_ignore::to_lower(connection_it->second);
7218 if (connection_val.find("upgrade") == std::string::npos) { return false; }
7219
7220 // Verify Sec-WebSocket-Accept header value
7221 auto it = headers.find("Sec-WebSocket-Accept");
7222 if (it == headers.end() || it->second != expected_accept) { return false; }
7223
7224 // Extract negotiated subprotocol
7225 auto proto_it = headers.find("Sec-WebSocket-Protocol");
7226 if (proto_it != headers.end()) { selected_subprotocol = proto_it->second; }
7227
7228 return true;
7229}
7230
7231enum class ReadContentResult {
7232 Success, // Successfully read the content
7233 PayloadTooLarge, // The content exceeds the specified payload limit
7234 Error // An error occurred while reading the content
7235};
7236
7237inline ReadContentResult read_content_with_length(
7238 Stream &strm, size_t len, DownloadProgress progress,
7239 ContentReceiverWithProgress out,
7240 size_t payload_max_length = (std::numeric_limits<size_t>::max)()) {
7241 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7242
7243 detail::BodyReader br;
7244 br.stream = &strm;
7245 br.has_content_length = true;
7246 br.content_length = len;
7247 br.payload_max_length = payload_max_length;
7248 br.chunked = false;
7249 br.bytes_read = 0;
7250 br.last_error = Error::Success;
7251
7252 size_t r = 0;
7253 while (r < len) {
7254 auto read_len = static_cast<size_t>(len - r);
7255 auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
7256 auto n = detail::read_body_content(&strm, br, buf, to_read);
7257 if (n <= 0) {
7258 // Check if it was a payload size error
7259 if (br.last_error == Error::ExceedMaxPayloadSize) {
7260 return ReadContentResult::PayloadTooLarge;
7261 }
7262 return ReadContentResult::Error;
7263 }
7264
7265 if (!out(buf, static_cast<size_t>(n), r, len)) {
7266 return ReadContentResult::Error;
7267 }
7268 r += static_cast<size_t>(n);
7269
7270 if (progress) {
7271 if (!progress(r, len)) { return ReadContentResult::Error; }
7272 }
7273 }
7274
7275 return ReadContentResult::Success;
7276}
7277
7278inline ReadContentResult
7279read_content_without_length(Stream &strm, size_t payload_max_length,
7280 ContentReceiverWithProgress out) {
7281 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7282 size_t r = 0;
7283 for (;;) {
7284 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
7285 if (n == 0) { return ReadContentResult::Success; }
7286 if (n < 0) { return ReadContentResult::Error; }
7287
7288 // Check if adding this data would exceed the payload limit
7289 if (r > payload_max_length ||
7290 payload_max_length - r < static_cast<size_t>(n)) {
7291 return ReadContentResult::PayloadTooLarge;
7292 }
7293
7294 if (!out(buf, static_cast<size_t>(n), r, 0)) {
7295 return ReadContentResult::Error;
7296 }
7297 r += static_cast<size_t>(n);
7298 }
7299
7300 return ReadContentResult::Success;
7301}
7302
7303template <typename T>
7304inline ReadContentResult read_content_chunked(Stream &strm, T &x,
7305 size_t payload_max_length,
7306 ContentReceiverWithProgress out) {
7307 detail::ChunkedDecoder dec(strm);
7308
7309 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7310 size_t total_len = 0;
7311
7312 for (;;) {
7313 size_t chunk_offset = 0;
7314 size_t chunk_total = 0;
7315 auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
7316 if (n < 0) { return ReadContentResult::Error; }
7317
7318 if (n == 0) {
7319 if (!dec.parse_trailers_into(x.trailers, x.headers)) {
7320 return ReadContentResult::Error;
7321 }
7322 return ReadContentResult::Success;
7323 }
7324
7325 if (total_len > payload_max_length ||
7326 payload_max_length - total_len < static_cast<size_t>(n)) {
7327 return ReadContentResult::PayloadTooLarge;
7328 }
7329
7330 if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
7331 return ReadContentResult::Error;
7332 }
7333
7334 total_len += static_cast<size_t>(n);
7335 }
7336}
7337
7338inline bool is_chunked_transfer_encoding(const Headers &headers) {
7339 return case_ignore::equal(
7340 get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
7341}
7342
7343template <typename T, typename U>
7344bool prepare_content_receiver(T &x, int &status,
7345 ContentReceiverWithProgress receiver,
7346 bool decompress, size_t payload_max_length,
7347 bool &exceed_payload_max_length, U callback) {
7348 if (decompress) {
7349 std::string encoding = x.get_header_value("Content-Encoding");
7350 std::unique_ptr<decompressor> decompressor;
7351
7352 if (!encoding.empty()) {
7353 decompressor = detail::create_decompressor(encoding);
7354 if (!decompressor) {
7355 // Unsupported encoding or no support compiled in
7356 status = StatusCode::UnsupportedMediaType_415;
7357 return false;
7358 }
7359 }
7360
7361 if (decompressor) {
7362 if (decompressor->is_valid()) {
7363 size_t decompressed_size = 0;
7364 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
7365 size_t off, size_t len) {
7366 return decompressor->decompress(
7367 buf, n, [&](const char *buf2, size_t n2) {
7368 // Guard against zip-bomb: check
7369 // decompressed size against limit.
7370 if (payload_max_length > 0 &&
7371 (decompressed_size >= payload_max_length ||
7372 n2 > payload_max_length - decompressed_size)) {
7373 exceed_payload_max_length = true;
7374 return false;
7375 }
7376 decompressed_size += n2;
7377 return receiver(buf2, n2, off, len);
7378 });
7379 };
7380 return callback(std::move(out));
7381 } else {
7382 status = StatusCode::InternalServerError_500;
7383 return false;
7384 }
7385 }
7386 }
7387
7388 ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
7389 size_t len) {
7390 return receiver(buf, n, off, len);
7391 };
7392 return callback(std::move(out));
7393}
7394
7395template <typename T>
7396bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
7397 DownloadProgress progress,
7398 ContentReceiverWithProgress receiver, bool decompress) {
7399 bool exceed_payload_max_length = false;
7400 return prepare_content_receiver(
7401 x, status, std::move(receiver), decompress, payload_max_length,
7402 exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
7403 auto ret = true;
7404 // Note: exceed_payload_max_length may also be set by the decompressor
7405 // wrapper in prepare_content_receiver when the decompressed payload
7406 // size exceeds the limit.
7407
7408 if (is_chunked_transfer_encoding(x.headers)) {
7409 auto result = read_content_chunked(strm, x, payload_max_length, out);
7410 if (result == ReadContentResult::Success) {
7411 ret = true;
7412 } else if (result == ReadContentResult::PayloadTooLarge) {
7413 exceed_payload_max_length = true;
7414 ret = false;
7415 } else {
7416 ret = false;
7417 }
7418 } else if (!has_header(x.headers, "Content-Length")) {
7419 auto result =
7420 read_content_without_length(strm, payload_max_length, out);
7421 if (result == ReadContentResult::Success) {
7422 ret = true;
7423 } else if (result == ReadContentResult::PayloadTooLarge) {
7424 exceed_payload_max_length = true;
7425 ret = false;
7426 } else {
7427 ret = false;
7428 }
7429 } else {
7430 auto is_invalid_value = false;
7431 auto len = get_header_value_u64(x.headers, "Content-Length",
7432 (std::numeric_limits<size_t>::max)(),
7433 0, is_invalid_value);
7434
7435 if (is_invalid_value) {
7436 ret = false;
7437 } else if (len > 0) {
7438 auto result = read_content_with_length(
7439 strm, len, std::move(progress), out, payload_max_length);
7440 ret = (result == ReadContentResult::Success);
7441 if (result == ReadContentResult::PayloadTooLarge) {
7442 exceed_payload_max_length = true;
7443 }
7444 }
7445 }
7446
7447 if (!ret) {
7448 status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
7449 : StatusCode::BadRequest_400;
7450 }
7451 return ret;
7452 });
7453}
7454
7455inline ssize_t write_request_line(Stream &strm, const std::string &method,
7456 const std::string &path) {
7457 std::string s = method;
7458 s += ' ';
7459 s += path;
7460 s += " HTTP/1.1\r\n";
7461 return strm.write(s.data(), s.size());
7462}
7463
7464inline ssize_t write_response_line(Stream &strm, int status) {
7465 std::string s = "HTTP/1.1 ";
7466 s += std::to_string(status);
7467 s += ' ';
7468 s += httplib::status_message(status);
7469 s += "\r\n";
7470 return strm.write(s.data(), s.size());
7471}
7472
7473inline ssize_t write_headers(Stream &strm, const Headers &headers) {
7474 ssize_t write_len = 0;
7475 for (const auto &x : headers) {
7476 std::string s;
7477 s = x.first;
7478 s += ": ";
7479 s += x.second;
7480 s += "\r\n";
7481
7482 auto len = strm.write(s.data(), s.size());
7483 if (len < 0) { return len; }
7484 write_len += len;
7485 }
7486 auto len = strm.write("\r\n");
7487 if (len < 0) { return len; }
7488 write_len += len;
7489 return write_len;
7490}
7491
7492inline bool write_data(Stream &strm, const char *d, size_t l) {
7493 size_t offset = 0;
7494 while (offset < l) {
7495 auto length = strm.write(d + offset, l - offset);
7496 if (length < 0) { return false; }
7497 offset += static_cast<size_t>(length);
7498 }
7499 return true;
7500}
7501
7502template <typename T>
7503inline bool write_content_with_progress(Stream &strm,
7504 const ContentProvider &content_provider,
7505 size_t offset, size_t length,
7506 T is_shutting_down,
7507 const UploadProgress &upload_progress,
7508 Error &error) {
7509 size_t end_offset = offset + length;
7510 size_t start_offset = offset;
7511 auto ok = true;
7512 DataSink data_sink;
7513
7514 data_sink.write = [&](const char *d, size_t l) -> bool {
7515 if (ok) {
7516 if (write_data(strm, d, l)) {
7517 offset += l;
7518
7519 if (upload_progress && length > 0) {
7520 size_t current_written = offset - start_offset;
7521 if (!upload_progress(current_written, length)) {
7522 ok = false;
7523 return false;
7524 }
7525 }
7526 } else {
7527 ok = false;
7528 }
7529 }
7530 return ok;
7531 };
7532
7533 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7534
7535 while (offset < end_offset && !is_shutting_down()) {
7536 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7537 error = Error::Write;
7538 return false;
7539 } else if (!content_provider(offset, end_offset - offset, data_sink)) {
7540 error = Error::Canceled;
7541 return false;
7542 } else if (!ok) {
7543 error = Error::Write;
7544 return false;
7545 }
7546 }
7547
7548 if (offset < end_offset) { // exited due to is_shutting_down(), not completion
7549 error = Error::Write;
7550 return false;
7551 }
7552
7553 error = Error::Success;
7554 return true;
7555}
7556
7557template <typename T>
7558inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7559 size_t offset, size_t length, T is_shutting_down,
7560 Error &error) {
7561 return write_content_with_progress<T>(strm, content_provider, offset, length,
7562 is_shutting_down, nullptr, error);
7563}
7564
7565template <typename T>
7566inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7567 size_t offset, size_t length,
7568 const T &is_shutting_down) {
7569 auto error = Error::Success;
7570 return write_content(strm, content_provider, offset, length, is_shutting_down,
7571 error);
7572}
7573
7574template <typename T>
7575inline bool
7576write_content_without_length(Stream &strm,
7577 const ContentProvider &content_provider,
7578 const T &is_shutting_down) {
7579 size_t offset = 0;
7580 auto data_available = true;
7581 auto ok = true;
7582 DataSink data_sink;
7583
7584 data_sink.write = [&](const char *d, size_t l) -> bool {
7585 if (ok) {
7586 offset += l;
7587 if (!write_data(strm, d, l)) { ok = false; }
7588 }
7589 return ok;
7590 };
7591
7592 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7593
7594 data_sink.done = [&](void) { data_available = false; };
7595
7596 while (data_available && !is_shutting_down()) {
7597 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7598 return false;
7599 } else if (!content_provider(offset, 0, data_sink)) {
7600 return false;
7601 } else if (!ok) {
7602 return false;
7603 }
7604 }
7605 return !data_available; // true only if done() was called, false if shutting
7606 // down
7607}
7608
7609template <typename T, typename U>
7610inline bool
7611write_content_chunked(Stream &strm, const ContentProvider &content_provider,
7612 const T &is_shutting_down, U &compressor, Error &error) {
7613 size_t offset = 0;
7614 auto data_available = true;
7615 auto ok = true;
7616 DataSink data_sink;
7617
7618 data_sink.write = [&](const char *d, size_t l) -> bool {
7619 if (ok) {
7620 data_available = l > 0;
7621 offset += l;
7622
7623 std::string payload;
7624 if (compressor.compress(d, l, false,
7625 [&](const char *data, size_t data_len) {
7626 payload.append(data, data_len);
7627 return true;
7628 })) {
7629 if (!payload.empty()) {
7630 // Emit chunked response header and footer for each chunk
7631 auto chunk =
7632 from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7633 if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
7634 }
7635 } else {
7636 ok = false;
7637 }
7638 }
7639 return ok;
7640 };
7641
7642 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7643
7644 auto done_with_trailer = [&](const Headers *trailer) {
7645 if (!ok) { return; }
7646
7647 data_available = false;
7648
7649 std::string payload;
7650 if (!compressor.compress(nullptr, 0, true,
7651 [&](const char *data, size_t data_len) {
7652 payload.append(data, data_len);
7653 return true;
7654 })) {
7655 ok = false;
7656 return;
7657 }
7658
7659 if (!payload.empty()) {
7660 // Emit chunked response header and footer for each chunk
7661 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7662 if (!write_data(strm, chunk.data(), chunk.size())) {
7663 ok = false;
7664 return;
7665 }
7666 }
7667
7668 constexpr const char done_marker[] = "0\r\n";
7669 if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
7670
7671 // Trailer
7672 if (trailer) {
7673 for (const auto &kv : *trailer) {
7674 std::string field_line = kv.first + ": " + kv.second + "\r\n";
7675 if (!write_data(strm, field_line.data(), field_line.size())) {
7676 ok = false;
7677 }
7678 }
7679 }
7680
7681 constexpr const char crlf[] = "\r\n";
7682 if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
7683 };
7684
7685 data_sink.done = [&](void) { done_with_trailer(nullptr); };
7686
7687 data_sink.done_with_trailer = [&](const Headers &trailer) {
7688 done_with_trailer(&trailer);
7689 };
7690
7691 while (data_available && !is_shutting_down()) {
7692 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7693 error = Error::Write;
7694 return false;
7695 } else if (!content_provider(offset, 0, data_sink)) {
7696 error = Error::Canceled;
7697 return false;
7698 } else if (!ok) {
7699 error = Error::Write;
7700 return false;
7701 }
7702 }
7703
7704 if (data_available) { // exited due to is_shutting_down(), not done()
7705 error = Error::Write;
7706 return false;
7707 }
7708
7709 error = Error::Success;
7710 return true;
7711}
7712
7713template <typename T, typename U>
7714inline bool write_content_chunked(Stream &strm,
7715 const ContentProvider &content_provider,
7716 const T &is_shutting_down, U &compressor) {
7717 auto error = Error::Success;
7718 return write_content_chunked(strm, content_provider, is_shutting_down,
7719 compressor, error);
7720}
7721
7722template <typename T>
7723inline bool redirect(T &cli, Request &req, Response &res,
7724 const std::string &path, const std::string &location,
7725 Error &error) {
7726 Request new_req = req;
7727 new_req.path = path;
7728 new_req.redirect_count_ -= 1;
7729
7730 if (res.status == StatusCode::SeeOther_303 &&
7731 (req.method != "GET" && req.method != "HEAD")) {
7732 new_req.method = "GET";
7733 new_req.body.clear();
7734 new_req.headers.clear();
7735 }
7736
7737 Response new_res;
7738
7739 auto ret = cli.send(new_req, new_res, error);
7740 if (ret) {
7741 req = std::move(new_req);
7742 res = std::move(new_res);
7743
7744 if (res.location.empty()) { res.location = location; }
7745 }
7746 return ret;
7747}
7748
7749inline std::string params_to_query_str(const Params &params) {
7750 std::string query;
7751
7752 for (auto it = params.begin(); it != params.end(); ++it) {
7753 if (it != params.begin()) { query += '&'; }
7754 query += encode_query_component(it->first);
7755 query += '=';
7756 query += encode_query_component(it->second);
7757 }
7758 return query;
7759}
7760
7761inline void parse_query_text(const char *data, std::size_t size,
7762 Params &params) {
7763 std::set<std::string> cache;
7764 split(data, data + size, '&', [&](const char *b, const char *e) {
7765 std::string kv(b, e);
7766 if (cache.find(kv) != cache.end()) { return; }
7767 cache.insert(std::move(kv));
7768
7769 std::string key;
7770 std::string val;
7771 divide(b, static_cast<std::size_t>(e - b), '=',
7772 [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
7773 std::size_t rhs_size) {
7774 key.assign(lhs_data, lhs_size);
7775 val.assign(rhs_data, rhs_size);
7776 });
7777
7778 if (!key.empty()) {
7779 params.emplace(decode_query_component(key), decode_query_component(val));
7780 }
7781 });
7782}
7783
7784inline void parse_query_text(const std::string &s, Params &params) {
7785 parse_query_text(s.data(), s.size(), params);
7786}
7787
7788// Normalize a query string by decoding and re-encoding each key/value pair
7789// while preserving the original parameter order. This avoids double-encoding
7790// and ensures consistent encoding without reordering (unlike Params which
7791// uses std::multimap and sorts keys).
7792inline std::string normalize_query_string(const std::string &query) {
7793 std::string result;
7794 split(query.data(), query.data() + query.size(), '&',
7795 [&](const char *b, const char *e) {
7796 std::string key;
7797 std::string val;
7798 divide(b, static_cast<std::size_t>(e - b), '=',
7799 [&](const char *lhs_data, std::size_t lhs_size,
7800 const char *rhs_data, std::size_t rhs_size) {
7801 key.assign(lhs_data, lhs_size);
7802 val.assign(rhs_data, rhs_size);
7803 });
7804
7805 if (!key.empty()) {
7806 auto dec_key = decode_query_component(key);
7807 auto dec_val = decode_query_component(val);
7808
7809 if (!result.empty()) { result += '&'; }
7810 result += encode_query_component(dec_key);
7811 if (!val.empty() || std::find(b, e, '=') != e) {
7812 result += '=';
7813 result += encode_query_component(dec_val);
7814 }
7815 }
7816 });
7817 return result;
7818}
7819
7820inline bool parse_multipart_boundary(const std::string &content_type,
7821 std::string &boundary) {
7822 std::map<std::string, std::string> params;
7823 extract_media_type(content_type, &params);
7824 auto it = params.find("boundary");
7825 if (it == params.end()) { return false; }
7826 boundary = it->second;
7827 return !boundary.empty();
7828}
7829
7830inline void parse_disposition_params(const std::string &s, Params &params) {
7831 std::set<std::string> cache;
7832 split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
7833 std::string kv(b, e);
7834 if (cache.find(kv) != cache.end()) { return; }
7835 cache.insert(kv);
7836
7837 std::string key;
7838 std::string val;
7839 split(b, e, '=', [&](const char *b2, const char *e2) {
7840 if (key.empty()) {
7841 key.assign(b2, e2);
7842 } else {
7843 val.assign(b2, e2);
7844 }
7845 });
7846
7847 if (!key.empty()) {
7848 params.emplace(trim_double_quotes_copy((key)),
7849 trim_double_quotes_copy((val)));
7850 }
7851 });
7852}
7853
7854#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7855inline bool parse_range_header(const std::string &s, Ranges &ranges) {
7856#else
7857inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
7858#endif
7859 auto is_valid = [](const std::string &str) {
7860 return std::all_of(str.cbegin(), str.cend(),
7861 [](unsigned char c) { return std::isdigit(c); });
7862 };
7863
7864 if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
7865 const auto pos = static_cast<size_t>(6);
7866 const auto len = static_cast<size_t>(s.size() - 6);
7867 auto all_valid_ranges = true;
7868 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
7869 if (!all_valid_ranges) { return; }
7870
7871 const auto it = std::find(b, e, '-');
7872 if (it == e) {
7873 all_valid_ranges = false;
7874 return;
7875 }
7876
7877 const auto lhs = std::string(b, it);
7878 const auto rhs = std::string(it + 1, e);
7879 if (!is_valid(lhs) || !is_valid(rhs)) {
7880 all_valid_ranges = false;
7881 return;
7882 }
7883
7884 ssize_t first = -1;
7885 if (!lhs.empty()) {
7886 ssize_t v;
7887 auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
7888 if (res.ec == std::errc{}) { first = v; }
7889 }
7890
7891 ssize_t last = -1;
7892 if (!rhs.empty()) {
7893 ssize_t v;
7894 auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
7895 if (res.ec == std::errc{}) { last = v; }
7896 }
7897
7898 if ((first == -1 && last == -1) ||
7899 (first != -1 && last != -1 && first > last)) {
7900 all_valid_ranges = false;
7901 return;
7902 }
7903
7904 ranges.emplace_back(first, last);
7905 });
7906 return all_valid_ranges && !ranges.empty();
7907 }
7908 return false;
7909#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7910}
7911#else
7912} catch (...) { return false; }
7913#endif
7914
7915inline bool parse_accept_header(const std::string &s,
7916 std::vector<std::string> &content_types) {
7917 content_types.clear();
7918
7919 // Empty string is considered valid (no preference)
7920 if (s.empty()) { return true; }
7921
7922 // Check for invalid patterns: leading/trailing commas or consecutive commas
7923 if (s.front() == ',' || s.back() == ',' ||
7924 s.find(",,") != std::string::npos) {
7925 return false;
7926 }
7927
7928 struct AcceptEntry {
7929 std::string media_type;
7930 double quality;
7931 int order;
7932 };
7933
7934 std::vector<AcceptEntry> entries;
7935 int order = 0;
7936 bool has_invalid_entry = false;
7937
7938 // Split by comma and parse each entry
7939 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
7940 std::string entry(b, e);
7941 entry = trim_copy(entry);
7942
7943 if (entry.empty()) {
7944 has_invalid_entry = true;
7945 return;
7946 }
7947
7948 AcceptEntry accept_entry;
7949 accept_entry.order = order++;
7950
7951 if (!parse_quality(entry.data(), entry.data() + entry.size(),
7952 accept_entry.media_type, accept_entry.quality)) {
7953 has_invalid_entry = true;
7954 return;
7955 }
7956
7957 // Remove additional parameters from media type
7958 accept_entry.media_type = extract_media_type(accept_entry.media_type);
7959
7960 // Basic validation of media type format
7961 if (accept_entry.media_type.empty()) {
7962 has_invalid_entry = true;
7963 return;
7964 }
7965
7966 // Check for basic media type format (should contain '/' or be '*')
7967 if (accept_entry.media_type != "*" &&
7968 accept_entry.media_type.find('/') == std::string::npos) {
7969 has_invalid_entry = true;
7970 return;
7971 }
7972
7973 entries.push_back(std::move(accept_entry));
7974 });
7975
7976 // Return false if any invalid entry was found
7977 if (has_invalid_entry) { return false; }
7978
7979 // Sort by quality (descending), then by original order (ascending)
7980 std::sort(entries.begin(), entries.end(),
7981 [](const AcceptEntry &a, const AcceptEntry &b) {
7982 if (a.quality != b.quality) {
7983 return a.quality > b.quality; // Higher quality first
7984 }
7985 return a.order < b.order; // Earlier order first for same quality
7986 });
7987
7988 // Extract sorted media types
7989 content_types.reserve(entries.size());
7990 for (auto &entry : entries) {
7991 content_types.push_back(std::move(entry.media_type));
7992 }
7993
7994 return true;
7995}
7996
7997class FormDataParser {
7998public:
7999 FormDataParser() = default;
8000
8001 void set_boundary(std::string &&boundary) {
8002 boundary_ = std::move(boundary);
8003 dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
8004 crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
8005 }
8006
8007 bool is_valid() const { return is_valid_; }
8008
8009 bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
8010 const ContentReceiver &content_callback) {
8011
8012 buf_append(buf, n);
8013
8014 while (buf_size() > 0) {
8015 switch (state_) {
8016 case 0: { // Initial boundary
8017 auto pos = buf_find(dash_boundary_crlf_);
8018 if (pos == buf_size()) { return true; }
8019 buf_erase(pos + dash_boundary_crlf_.size());
8020 state_ = 1;
8021 break;
8022 }
8023 case 1: { // New entry
8024 clear_file_info();
8025 state_ = 2;
8026 break;
8027 }
8028 case 2: { // Headers
8029 auto pos = buf_find(crlf_);
8030 if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
8031 while (pos < buf_size()) {
8032 // Empty line
8033 if (pos == 0) {
8034 if (!header_callback(file_)) {
8035 is_valid_ = false;
8036 return false;
8037 }
8038 buf_erase(crlf_.size());
8039 state_ = 3;
8040 break;
8041 }
8042
8043 const auto header = buf_head(pos);
8044
8045 if (!parse_header(header.data(), header.data() + header.size(),
8046 [&](const std::string &, const std::string &) {})) {
8047 is_valid_ = false;
8048 return false;
8049 }
8050
8051 // Parse and emplace space trimmed headers into a map
8052 if (!parse_header(
8053 header.data(), header.data() + header.size(),
8054 [&](const std::string &key, const std::string &val) {
8055 file_.headers.emplace(key, val);
8056 })) {
8057 is_valid_ = false;
8058 return false;
8059 }
8060
8061 constexpr const char header_content_type[] = "Content-Type:";
8062
8063 if (start_with_case_ignore(header, header_content_type)) {
8064 file_.content_type =
8065 trim_copy(header.substr(str_len(header_content_type)));
8066 } else {
8067 std::string disposition_params;
8068 if (parse_content_disposition(header, disposition_params)) {
8069 Params params;
8070 parse_disposition_params(disposition_params, params);
8071
8072 auto it = params.find("name");
8073 if (it != params.end()) {
8074 file_.name = it->second;
8075 } else {
8076 is_valid_ = false;
8077 return false;
8078 }
8079
8080 it = params.find("filename");
8081 if (it != params.end()) { file_.filename = it->second; }
8082
8083 it = params.find("filename*");
8084 if (it != params.end()) {
8085 // RFC 5987: only UTF-8 encoding is allowed
8086 const auto &val = it->second;
8087 constexpr const char utf8_prefix[] = "UTF-8''";
8088 constexpr size_t prefix_len = str_len(utf8_prefix);
8089 if (val.size() > prefix_len &&
8090 start_with_case_ignore(val, utf8_prefix)) {
8091 file_.filename = decode_path_component(
8092 val.substr(prefix_len)); // override...
8093 } else {
8094 is_valid_ = false;
8095 return false;
8096 }
8097 }
8098 }
8099 }
8100 buf_erase(pos + crlf_.size());
8101 pos = buf_find(crlf_);
8102 }
8103 if (state_ != 3) { return true; }
8104 break;
8105 }
8106 case 3: { // Body
8107 if (crlf_dash_boundary_.size() > buf_size()) { return true; }
8108 auto pos = buf_find(crlf_dash_boundary_);
8109 if (pos < buf_size()) {
8110 if (!content_callback(buf_data(), pos)) {
8111 is_valid_ = false;
8112 return false;
8113 }
8114 buf_erase(pos + crlf_dash_boundary_.size());
8115 state_ = 4;
8116 } else {
8117 auto len = buf_size() - crlf_dash_boundary_.size();
8118 if (len > 0) {
8119 if (!content_callback(buf_data(), len)) {
8120 is_valid_ = false;
8121 return false;
8122 }
8123 buf_erase(len);
8124 }
8125 return true;
8126 }
8127 break;
8128 }
8129 case 4: { // Boundary
8130 if (crlf_.size() > buf_size()) { return true; }
8131 if (buf_start_with(crlf_)) {
8132 buf_erase(crlf_.size());
8133 state_ = 1;
8134 } else {
8135 if (dash_.size() > buf_size()) { return true; }
8136 if (buf_start_with(dash_)) {
8137 buf_erase(dash_.size());
8138 is_valid_ = true;
8139 buf_erase(buf_size()); // Remove epilogue
8140 } else {
8141 return true;
8142 }
8143 }
8144 break;
8145 }
8146 }
8147 }
8148
8149 return true;
8150 }
8151
8152private:
8153 void clear_file_info() {
8154 file_.name.clear();
8155 file_.filename.clear();
8156 file_.content_type.clear();
8157 file_.headers.clear();
8158 }
8159
8160 bool start_with_case_ignore(const std::string &a, const char *b,
8161 size_t offset = 0) const {
8162 const auto b_len = strlen(b);
8163 if (a.size() < offset + b_len) { return false; }
8164 for (size_t i = 0; i < b_len; i++) {
8165 if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) {
8166 return false;
8167 }
8168 }
8169 return true;
8170 }
8171
8172 // Parses "Content-Disposition: form-data; <params>" without std::regex.
8173 // Returns true if header matches, with the params portion in `params_out`.
8174 bool parse_content_disposition(const std::string &header,
8175 std::string &params_out) const {
8176 constexpr const char prefix[] = "Content-Disposition:";
8177 constexpr size_t prefix_len = str_len(prefix);
8178
8179 if (!start_with_case_ignore(header, prefix)) { return false; }
8180
8181 // Skip whitespace after "Content-Disposition:"
8182 auto pos = prefix_len;
8183 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8184 pos++;
8185 }
8186
8187 // Match "form-data;" (case-insensitive)
8188 constexpr const char form_data[] = "form-data;";
8189 constexpr size_t form_data_len = str_len(form_data);
8190 if (!start_with_case_ignore(header, form_data, pos)) { return false; }
8191 pos += form_data_len;
8192
8193 // Skip whitespace after "form-data;"
8194 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8195 pos++;
8196 }
8197
8198 params_out = header.substr(pos);
8199 return true;
8200 }
8201
8202 const std::string dash_ = "--";
8203 const std::string crlf_ = "\r\n";
8204 std::string boundary_;
8205 std::string dash_boundary_crlf_;
8206 std::string crlf_dash_boundary_;
8207
8208 size_t state_ = 0;
8209 bool is_valid_ = false;
8210 FormData file_;
8211
8212 // Buffer
8213 bool start_with(const std::string &a, size_t spos, size_t epos,
8214 const std::string &b) const {
8215 if (epos - spos < b.size()) { return false; }
8216 for (size_t i = 0; i < b.size(); i++) {
8217 if (a[i + spos] != b[i]) { return false; }
8218 }
8219 return true;
8220 }
8221
8222 size_t buf_size() const { return buf_epos_ - buf_spos_; }
8223
8224 const char *buf_data() const { return &buf_[buf_spos_]; }
8225
8226 std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
8227
8228 bool buf_start_with(const std::string &s) const {
8229 return start_with(buf_, buf_spos_, buf_epos_, s);
8230 }
8231
8232 size_t buf_find(const std::string &s) const {
8233 auto c = s.front();
8234
8235 size_t off = buf_spos_;
8236 while (off < buf_epos_) {
8237 auto pos = off;
8238 while (true) {
8239 if (pos == buf_epos_) { return buf_size(); }
8240 if (buf_[pos] == c) { break; }
8241 pos++;
8242 }
8243
8244 auto remaining_size = buf_epos_ - pos;
8245 if (s.size() > remaining_size) { return buf_size(); }
8246
8247 if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
8248
8249 off = pos + 1;
8250 }
8251
8252 return buf_size();
8253 }
8254
8255 void buf_append(const char *data, size_t n) {
8256 auto remaining_size = buf_size();
8257 if (remaining_size > 0 && buf_spos_ > 0) {
8258 for (size_t i = 0; i < remaining_size; i++) {
8259 buf_[i] = buf_[buf_spos_ + i];
8260 }
8261 }
8262 buf_spos_ = 0;
8263 buf_epos_ = remaining_size;
8264
8265 if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
8266
8267 for (size_t i = 0; i < n; i++) {
8268 buf_[buf_epos_ + i] = data[i];
8269 }
8270 buf_epos_ += n;
8271 }
8272
8273 void buf_erase(size_t size) { buf_spos_ += size; }
8274
8275 std::string buf_;
8276 size_t buf_spos_ = 0;
8277 size_t buf_epos_ = 0;
8278};
8279
8280inline std::string random_string(size_t length) {
8281 constexpr const char data[] =
8282 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8283
8284 thread_local auto engine([]() {
8285 // std::random_device might actually be deterministic on some
8286 // platforms, but due to lack of support in the c++ standard library,
8287 // doing better requires either some ugly hacks or breaking portability.
8288 std::random_device seed_gen;
8289 // Request 128 bits of entropy for initialization
8290 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
8291 return std::mt19937(seed_sequence);
8292 }());
8293
8294 std::string result;
8295 for (size_t i = 0; i < length; i++) {
8296 result += data[engine() % (sizeof(data) - 1)];
8297 }
8298 return result;
8299}
8300
8301inline std::string make_multipart_data_boundary() {
8302 return "--cpp-httplib-multipart-data-" + detail::random_string(16);
8303}
8304
8305inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
8306 auto valid = true;
8307 for (size_t i = 0; i < boundary.size(); i++) {
8308 auto c = boundary[i];
8309 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '-' && c != '_') {
8310 valid = false;
8311 break;
8312 }
8313 }
8314 return valid;
8315}
8316
8317template <typename T>
8318inline std::string
8319serialize_multipart_formdata_item_begin(const T &item,
8320 const std::string &boundary) {
8321 std::string body = "--" + boundary + "\r\n";
8322 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
8323 if (!item.filename.empty()) {
8324 body += "; filename=\"" + item.filename + "\"";
8325 }
8326 body += "\r\n";
8327 if (!item.content_type.empty()) {
8328 body += "Content-Type: " + item.content_type + "\r\n";
8329 }
8330 body += "\r\n";
8331
8332 return body;
8333}
8334
8335inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
8336
8337inline std::string
8338serialize_multipart_formdata_finish(const std::string &boundary) {
8339 return "--" + boundary + "--\r\n";
8340}
8341
8342inline std::string
8343serialize_multipart_formdata_get_content_type(const std::string &boundary) {
8344 return "multipart/form-data; boundary=" + boundary;
8345}
8346
8347inline std::string
8348serialize_multipart_formdata(const UploadFormDataItems &items,
8349 const std::string &boundary, bool finish = true) {
8350 std::string body;
8351
8352 for (const auto &item : items) {
8353 body += serialize_multipart_formdata_item_begin(item, boundary);
8354 body += item.content + serialize_multipart_formdata_item_end();
8355 }
8356
8357 if (finish) { body += serialize_multipart_formdata_finish(boundary); }
8358
8359 return body;
8360}
8361
8362inline size_t get_multipart_content_length(const UploadFormDataItems &items,
8363 const std::string &boundary) {
8364 size_t total = 0;
8365 for (const auto &item : items) {
8366 total += serialize_multipart_formdata_item_begin(item, boundary).size();
8367 total += item.content.size();
8368 total += serialize_multipart_formdata_item_end().size();
8369 }
8370 total += serialize_multipart_formdata_finish(boundary).size();
8371 return total;
8372}
8373
8374struct MultipartSegment {
8375 const char *data;
8376 size_t size;
8377};
8378
8379// NOTE: items must outlive the returned ContentProvider
8380// (safe for synchronous use inside Post/Put/Patch)
8381inline ContentProvider
8382make_multipart_content_provider(const UploadFormDataItems &items,
8383 const std::string &boundary) {
8384 // Own the per-item header strings and the finish string
8385 std::vector<std::string> owned;
8386 owned.reserve(items.size() + 1);
8387 for (const auto &item : items)
8388 owned.push_back(serialize_multipart_formdata_item_begin(item, boundary));
8389 owned.push_back(serialize_multipart_formdata_finish(boundary));
8390
8391 // Flat segment list: [header, content, "\r\n"] * N + [finish]
8392 std::vector<MultipartSegment> segs;
8393 segs.reserve(items.size() * 3 + 1);
8394 static const char crlf[] = "\r\n";
8395 for (size_t i = 0; i < items.size(); i++) {
8396 segs.push_back({owned[i].data(), owned[i].size()});
8397 segs.push_back({items[i].content.data(), items[i].content.size()});
8398 segs.push_back({crlf, 2});
8399 }
8400 segs.push_back({owned.back().data(), owned.back().size()});
8401
8402 struct MultipartState {
8403 std::vector<std::string> owned;
8404 std::vector<MultipartSegment> segs;
8405 std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
8406 };
8407 auto state = std::make_shared<MultipartState>();
8408 state->owned = std::move(owned);
8409 // `segs` holds raw pointers into owned strings; std::string move preserves
8410 // the data pointer, so these pointers remain valid after the move above.
8411 state->segs = std::move(segs);
8412
8413 return [state](size_t offset, size_t length, DataSink &sink) -> bool {
8414 // Buffer multiple small segments into fewer, larger writes to avoid
8415 // excessive TCP packets when there are many form data items (#2410)
8416 auto &buf = state->buf;
8417 auto buf_size = buf.size();
8418 size_t buf_len = 0;
8419 size_t remaining = length;
8420
8421 // Find the first segment containing 'offset'
8422 size_t pos = 0;
8423 size_t seg_idx = 0;
8424 for (; seg_idx < state->segs.size(); seg_idx++) {
8425 const auto &seg = state->segs[seg_idx];
8426 if (seg.size > 0 && offset - pos < seg.size) { break; }
8427 pos += seg.size;
8428 }
8429
8430 size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0;
8431
8432 for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) {
8433 const auto &seg = state->segs[seg_idx];
8434 size_t available = seg.size - seg_offset;
8435 size_t to_copy = (std::min)(available, remaining);
8436 const char *src = seg.data + seg_offset;
8437 seg_offset = 0; // only the first segment has a non-zero offset
8438
8439 while (to_copy > 0) {
8440 size_t space = buf_size - buf_len;
8441 size_t chunk = (std::min)(to_copy, space);
8442 std::memcpy(buf.data() + buf_len, src, chunk);
8443 buf_len += chunk;
8444 src += chunk;
8445 to_copy -= chunk;
8446 remaining -= chunk;
8447
8448 if (buf_len == buf_size) {
8449 if (!sink.write(buf.data(), buf_len)) { return false; }
8450 buf_len = 0;
8451 }
8452 }
8453 }
8454
8455 if (buf_len > 0) { return sink.write(buf.data(), buf_len); }
8456 return true;
8457 };
8458}
8459
8460inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
8461 if (ranges.size() <= 1) return;
8462
8463 // Sort ranges by start position
8464 std::sort(ranges.begin(), ranges.end(),
8465 [](const Range &a, const Range &b) { return a.first < b.first; });
8466
8467 Ranges coalesced;
8468 coalesced.reserve(ranges.size());
8469
8470 for (auto &r : ranges) {
8471 auto first_pos = r.first;
8472 auto last_pos = r.second;
8473
8474 // Handle special cases like in range_error
8475 if (first_pos == -1 && last_pos == -1) {
8476 first_pos = 0;
8477 last_pos = static_cast<ssize_t>(content_length);
8478 }
8479
8480 if (first_pos == -1) {
8481 first_pos = static_cast<ssize_t>(content_length) - last_pos;
8482 last_pos = static_cast<ssize_t>(content_length) - 1;
8483 }
8484
8485 if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
8486 last_pos = static_cast<ssize_t>(content_length) - 1;
8487 }
8488
8489 // Skip invalid ranges
8490 if (!(0 <= first_pos && first_pos <= last_pos &&
8491 last_pos < static_cast<ssize_t>(content_length))) {
8492 continue;
8493 }
8494
8495 // Coalesce with previous range if overlapping or adjacent (but not
8496 // identical)
8497 if (!coalesced.empty()) {
8498 auto &prev = coalesced.back();
8499 // Check if current range overlaps or is adjacent to previous range
8500 // but don't coalesce identical ranges (allow duplicates)
8501 if (first_pos <= prev.second + 1 &&
8502 !(first_pos == prev.first && last_pos == prev.second)) {
8503 // Extend the previous range
8504 prev.second = (std::max)(prev.second, last_pos);
8505 continue;
8506 }
8507 }
8508
8509 // Add new range
8510 coalesced.emplace_back(first_pos, last_pos);
8511 }
8512
8513 ranges = std::move(coalesced);
8514}
8515
8516inline bool range_error(Request &req, Response &res) {
8517 if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
8518 if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
8519 req.ranges.clear();
8520 if (res.status == StatusCode::PartialContent_206) {
8521 res.status = StatusCode::OK_200;
8522 }
8523 return false;
8524 }
8525
8526 ssize_t content_len = static_cast<ssize_t>(
8527 res.content_length_ ? res.content_length_ : res.body.size());
8528
8529 std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
8530 size_t overwrapping_count = 0;
8531
8532 // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
8533 // 'HTTP Semantics' to avoid potential denial-of-service attacks.
8534 // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
8535
8536 // Too many ranges
8537 if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
8538
8539 for (auto &r : req.ranges) {
8540 auto &first_pos = r.first;
8541 auto &last_pos = r.second;
8542
8543 if (first_pos == -1 && last_pos == -1) {
8544 first_pos = 0;
8545 last_pos = content_len;
8546 }
8547
8548 if (first_pos == -1) {
8549 first_pos = content_len - last_pos;
8550 last_pos = content_len - 1;
8551 }
8552
8553 // NOTE: RFC-9110 '14.1.2. Byte Ranges':
8554 // A client can limit the number of bytes requested without knowing the
8555 // size of the selected representation. If the last-pos value is absent,
8556 // or if the value is greater than or equal to the current length of the
8557 // representation data, the byte range is interpreted as the remainder of
8558 // the representation (i.e., the server replaces the value of last-pos
8559 // with a value that is one less than the current length of the selected
8560 // representation).
8561 // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
8562 if (last_pos == -1 || last_pos >= content_len) {
8563 last_pos = content_len - 1;
8564 }
8565
8566 // Range must be within content length
8567 if (!(0 <= first_pos && first_pos <= last_pos &&
8568 last_pos <= content_len - 1)) {
8569 return true;
8570 }
8571
8572 // Request must not have more than two overlapping ranges
8573 for (const auto &processed_range : processed_ranges) {
8574 if (!(last_pos < processed_range.first ||
8575 first_pos > processed_range.second)) {
8576 overwrapping_count++;
8577 if (overwrapping_count > 2) { return true; }
8578 break; // Only count once per range
8579 }
8580 }
8581
8582 processed_ranges.emplace_back(first_pos, last_pos);
8583 }
8584
8585 // After validation, coalesce overlapping ranges as per RFC 9110
8586 coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
8587 }
8588
8589 return false;
8590}
8591
8592inline std::pair<size_t, size_t>
8593get_range_offset_and_length(Range r, size_t content_length) {
8594 assert(r.first != -1 && r.second != -1);
8595 assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
8596 assert(r.first <= r.second &&
8597 r.second < static_cast<ssize_t>(content_length));
8598 (void)(content_length);
8599 return std::make_pair(static_cast<size_t>(r.first),
8600 static_cast<size_t>(r.second - r.first) + 1);
8601}
8602
8603inline std::string make_content_range_header_field(
8604 const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
8605 auto st = offset_and_length.first;
8606 auto ed = st + offset_and_length.second - 1;
8607
8608 std::string field = "bytes ";
8609 field += std::to_string(st);
8610 field += '-';
8611 field += std::to_string(ed);
8612 field += '/';
8613 field += std::to_string(content_length);
8614 return field;
8615}
8616
8617template <typename SToken, typename CToken, typename Content>
8618bool process_multipart_ranges_data(const Request &req,
8619 const std::string &boundary,
8620 const std::string &content_type,
8621 size_t content_length, SToken stoken,
8622 CToken ctoken, Content content) {
8623 for (size_t i = 0; i < req.ranges.size(); i++) {
8624 ctoken("--");
8625 stoken(boundary);
8626 ctoken("\r\n");
8627 if (!content_type.empty()) {
8628 ctoken("Content-Type: ");
8629 stoken(content_type);
8630 ctoken("\r\n");
8631 }
8632
8633 auto offset_and_length =
8634 get_range_offset_and_length(req.ranges[i], content_length);
8635
8636 ctoken("Content-Range: ");
8637 stoken(make_content_range_header_field(offset_and_length, content_length));
8638 ctoken("\r\n");
8639 ctoken("\r\n");
8640
8641 if (!content(offset_and_length.first, offset_and_length.second)) {
8642 return false;
8643 }
8644 ctoken("\r\n");
8645 }
8646
8647 ctoken("--");
8648 stoken(boundary);
8649 ctoken("--");
8650
8651 return true;
8652}
8653
8654inline void make_multipart_ranges_data(const Request &req, Response &res,
8655 const std::string &boundary,
8656 const std::string &content_type,
8657 size_t content_length,
8658 std::string &data) {
8659 process_multipart_ranges_data(
8660 req, boundary, content_type, content_length,
8661 [&](const std::string &token) { data += token; },
8662 [&](const std::string &token) { data += token; },
8663 [&](size_t offset, size_t length) {
8664 assert(offset + length <= content_length);
8665 data += res.body.substr(offset, length);
8666 return true;
8667 });
8668}
8669
8670inline size_t get_multipart_ranges_data_length(const Request &req,
8671 const std::string &boundary,
8672 const std::string &content_type,
8673 size_t content_length) {
8674 size_t data_length = 0;
8675
8676 process_multipart_ranges_data(
8677 req, boundary, content_type, content_length,
8678 [&](const std::string &token) { data_length += token.size(); },
8679 [&](const std::string &token) { data_length += token.size(); },
8680 [&](size_t /*offset*/, size_t length) {
8681 data_length += length;
8682 return true;
8683 });
8684
8685 return data_length;
8686}
8687
8688template <typename T>
8689inline bool
8690write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
8691 const std::string &boundary,
8692 const std::string &content_type,
8693 size_t content_length, const T &is_shutting_down) {
8694 return process_multipart_ranges_data(
8695 req, boundary, content_type, content_length,
8696 [&](const std::string &token) { strm.write(token); },
8697 [&](const std::string &token) { strm.write(token); },
8698 [&](size_t offset, size_t length) {
8699 return write_content(strm, res.content_provider_, offset, length,
8700 is_shutting_down);
8701 });
8702}
8703
8704inline bool has_framed_body(const Request &req) {
8705 return is_chunked_transfer_encoding(req.headers) ||
8706 req.get_header_value_u64("Content-Length") > 0;
8707}
8708
8709inline bool is_connection_persistent(const Request &req) {
8710 auto conn = req.get_header_value("Connection");
8711 if (conn == "close") { return false; }
8712 if (req.version == "HTTP/1.0" && conn != "Keep-Alive") { return false; }
8713 return true;
8714}
8715
8716inline bool expect_content(const Request &req) {
8717 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
8718 req.method == "DELETE") {
8719 return true;
8720 }
8721 return has_framed_body(req);
8722}
8723
8724#ifdef _WIN32
8725class WSInit {
8726public:
8727 WSInit() {
8728 WSADATA wsaData;
8729 if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
8730 }
8731
8732 ~WSInit() {
8733 if (is_valid_) WSACleanup();
8734 }
8735
8736 bool is_valid_ = false;
8737};
8738
8739static WSInit wsinit_;
8740#endif
8741
8742inline bool parse_www_authenticate(const Response &res,
8743 std::map<std::string, std::string> &auth,
8744 bool is_proxy) {
8745 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
8746 if (res.has_header(auth_key)) {
8747 thread_local auto re =
8748 std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
8749 auto s = res.get_header_value(auth_key);
8750 auto pos = s.find(' ');
8751 if (pos != std::string::npos) {
8752 auto type = s.substr(0, pos);
8753 if (type == "Basic") {
8754 return false;
8755 } else if (type == "Digest") {
8756 s = s.substr(pos + 1);
8757 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
8758 for (auto i = beg; i != std::sregex_iterator(); ++i) {
8759 const auto &m = *i;
8760 auto key = s.substr(static_cast<size_t>(m.position(1)),
8761 static_cast<size_t>(m.length(1)));
8762 auto val = m.length(2) > 0
8763 ? s.substr(static_cast<size_t>(m.position(2)),
8764 static_cast<size_t>(m.length(2)))
8765 : s.substr(static_cast<size_t>(m.position(3)),
8766 static_cast<size_t>(m.length(3)));
8767 auth[std::move(key)] = std::move(val);
8768 }
8769 return true;
8770 }
8771 }
8772 }
8773 return false;
8774}
8775
8776class ContentProviderAdapter {
8777public:
8778 explicit ContentProviderAdapter(
8779 ContentProviderWithoutLength &&content_provider)
8780 : content_provider_(std::move(content_provider)) {}
8781
8782 bool operator()(size_t offset, size_t, DataSink &sink) {
8783 return content_provider_(offset, sink);
8784 }
8785
8786private:
8787 ContentProviderWithoutLength content_provider_;
8788};
8789
8790// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
8791namespace fields {
8792
8793inline bool is_token_char(char c) {
8794 return std::isalnum(static_cast<unsigned char>(c)) || c == '!' || c == '#' ||
8795 c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
8796 c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
8797 c == '|' || c == '~';
8798}
8799
8800inline bool is_token(const std::string &s) {
8801 if (s.empty()) { return false; }
8802 for (auto c : s) {
8803 if (!is_token_char(c)) { return false; }
8804 }
8805 return true;
8806}
8807
8808inline bool is_field_name(const std::string &s) { return is_token(s); }
8809
8810inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
8811
8812inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
8813
8814inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
8815
8816inline bool is_field_content(const std::string &s) {
8817 if (s.empty()) { return true; }
8818
8819 if (s.size() == 1) {
8820 return is_field_vchar(s[0]);
8821 } else if (s.size() == 2) {
8822 return is_field_vchar(s[0]) && is_field_vchar(s[1]);
8823 } else {
8824 size_t i = 0;
8825
8826 if (!is_field_vchar(s[i])) { return false; }
8827 i++;
8828
8829 while (i < s.size() - 1) {
8830 auto c = s[i++];
8831 if (c == ' ' || c == '\t' || is_field_vchar(c)) {
8832 } else {
8833 return false;
8834 }
8835 }
8836
8837 return is_field_vchar(s[i]);
8838 }
8839}
8840
8841inline bool is_field_value(const std::string &s) { return is_field_content(s); }
8842
8843} // namespace fields
8844
8845inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
8846 int port, const std::string &path,
8847 const Headers &headers,
8848 std::string &selected_subprotocol) {
8849 // Validate path and host
8850 if (!fields::is_field_value(path) || !fields::is_field_value(host)) {
8851 return false;
8852 }
8853
8854 // Validate user-provided headers
8855 for (const auto &h : headers) {
8856 if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) {
8857 return false;
8858 }
8859 }
8860
8861 // Generate random Sec-WebSocket-Key
8862 thread_local std::mt19937 rng(std::random_device{}());
8863 std::string key_bytes(16, '\0');
8864 for (size_t i = 0; i < 16; i += 4) {
8865 auto r = rng();
8866 std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i)));
8867 }
8868 auto client_key = base64_encode(key_bytes);
8869
8870 // Build upgrade request
8871 std::string req_str = "GET " + path + " HTTP/1.1\r\n";
8872 req_str += "Host: " + host + ":" + std::to_string(port) + "\r\n";
8873 req_str += "Upgrade: websocket\r\n";
8874 req_str += "Connection: Upgrade\r\n";
8875 req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
8876 req_str += "Sec-WebSocket-Version: 13\r\n";
8877 for (const auto &h : headers) {
8878 req_str += h.first + ": " + h.second + "\r\n";
8879 }
8880 req_str += "\r\n";
8881
8882 if (strm.write(req_str.data(), req_str.size()) < 0) { return false; }
8883
8884 // Verify 101 response and Sec-WebSocket-Accept header
8885 auto expected_accept = websocket_accept_key(client_key);
8886 return read_websocket_upgrade_response(strm, expected_accept,
8887 selected_subprotocol);
8888}
8889
8890} // namespace detail
8891
8892/*
8893 * Group 2: detail namespace - SSL common utilities
8894 */
8895
8896#ifdef CPPHTTPLIB_SSL_ENABLED
8897namespace detail {
8898
8899class SSLSocketStream final : public Stream {
8900public:
8901 SSLSocketStream(
8902 socket_t sock, tls::session_t session, time_t read_timeout_sec,
8903 time_t read_timeout_usec, time_t write_timeout_sec,
8904 time_t write_timeout_usec, time_t max_timeout_msec = 0,
8905 std::chrono::time_point<std::chrono::steady_clock> start_time =
8906 (std::chrono::steady_clock::time_point::min)());
8907 ~SSLSocketStream() override;
8908
8909 bool is_readable() const override;
8910 bool wait_readable() const override;
8911 bool wait_writable() const override;
8912 bool is_peer_alive() const override;
8913 ssize_t read(char *ptr, size_t size) override;
8914 ssize_t write(const char *ptr, size_t size) override;
8915 void get_remote_ip_and_port(std::string &ip, int &port) const override;
8916 void get_local_ip_and_port(std::string &ip, int &port) const override;
8917 socket_t socket() const override;
8918 time_t duration() const override;
8919 void set_read_timeout(time_t sec, time_t usec = 0) override;
8920
8921private:
8922 socket_t sock_;
8923 tls::session_t session_;
8924 time_t read_timeout_sec_;
8925 time_t read_timeout_usec_;
8926 time_t write_timeout_sec_;
8927 time_t write_timeout_usec_;
8928 time_t max_timeout_msec_;
8929 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
8930};
8931
8932#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8933inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
8934 auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
8935 EVP_MD_CTX_new(), EVP_MD_CTX_free);
8936
8937 unsigned int hash_length = 0;
8938 unsigned char hash[EVP_MAX_MD_SIZE];
8939
8940 EVP_DigestInit_ex(context.get(), algo, nullptr);
8941 EVP_DigestUpdate(context.get(), s.c_str(), s.size());
8942 EVP_DigestFinal_ex(context.get(), hash, &hash_length);
8943
8944 std::stringstream ss;
8945 for (auto i = 0u; i < hash_length; ++i) {
8946 ss << std::hex << std::setw(2) << std::setfill('0')
8947 << static_cast<unsigned int>(hash[i]);
8948 }
8949
8950 return ss.str();
8951}
8952
8953inline std::string MD5(const std::string &s) {
8954 return message_digest(s, EVP_md5());
8955}
8956
8957inline std::string SHA_256(const std::string &s) {
8958 return message_digest(s, EVP_sha256());
8959}
8960
8961inline std::string SHA_512(const std::string &s) {
8962 return message_digest(s, EVP_sha512());
8963}
8964#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
8965namespace {
8966template <size_t N>
8967inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
8968 std::stringstream ss;
8969 for (size_t i = 0; i < N; ++i) {
8970 ss << std::hex << std::setw(2) << std::setfill('0')
8971 << static_cast<unsigned int>(hash[i]);
8972 }
8973 return ss.str();
8974}
8975} // namespace
8976
8977inline std::string MD5(const std::string &s) {
8978 unsigned char hash[16];
8979#ifdef CPPHTTPLIB_MBEDTLS_V3
8980 mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8981 hash);
8982#else
8983 mbedtls_md5_ret(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8984 hash);
8985#endif
8986 return hash_to_hex(hash);
8987}
8988
8989inline std::string SHA_256(const std::string &s) {
8990 unsigned char hash[32];
8991#ifdef CPPHTTPLIB_MBEDTLS_V3
8992 mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8993 hash, 0);
8994#else
8995 mbedtls_sha256_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
8996 s.size(), hash, 0);
8997#endif
8998 return hash_to_hex(hash);
8999}
9000
9001inline std::string SHA_512(const std::string &s) {
9002 unsigned char hash[64];
9003#ifdef CPPHTTPLIB_MBEDTLS_V3
9004 mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
9005 hash, 0);
9006#else
9007 mbedtls_sha512_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
9008 s.size(), hash, 0);
9009#endif
9010 return hash_to_hex(hash);
9011}
9012#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
9013namespace {
9014template <size_t N>
9015inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
9016 std::stringstream ss;
9017 for (size_t i = 0; i < N; ++i) {
9018 ss << std::hex << std::setw(2) << std::setfill('0')
9019 << static_cast<unsigned int>(hash[i]);
9020 }
9021 return ss.str();
9022}
9023} // namespace
9024
9025inline std::string MD5(const std::string &s) {
9026 unsigned char hash[WC_MD5_DIGEST_SIZE];
9027 wc_Md5Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9028 static_cast<word32>(s.size()), hash);
9029 return hash_to_hex(hash);
9030}
9031
9032inline std::string SHA_256(const std::string &s) {
9033 unsigned char hash[WC_SHA256_DIGEST_SIZE];
9034 wc_Sha256Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9035 static_cast<word32>(s.size()), hash);
9036 return hash_to_hex(hash);
9037}
9038
9039inline std::string SHA_512(const std::string &s) {
9040 unsigned char hash[WC_SHA512_DIGEST_SIZE];
9041 wc_Sha512Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9042 static_cast<word32>(s.size()), hash);
9043 return hash_to_hex(hash);
9044}
9045#endif
9046
9047inline bool is_ip_address(const std::string &host) {
9048 struct in_addr addr4;
9049 struct in6_addr addr6;
9050 return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
9051 inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
9052}
9053
9054template <typename T>
9055inline bool process_server_socket_ssl(
9056 const std::atomic<socket_t> &svr_sock, tls::session_t session,
9057 socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
9058 time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
9059 time_t write_timeout_usec, T callback) {
9060 return process_server_socket_core(
9061 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
9062 [&](bool close_connection, bool &connection_closed) {
9063 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9064 write_timeout_sec, write_timeout_usec);
9065 return callback(strm, close_connection, connection_closed);
9066 });
9067}
9068
9069template <typename T>
9070inline bool process_client_socket_ssl(
9071 tls::session_t session, socket_t sock, time_t read_timeout_sec,
9072 time_t read_timeout_usec, time_t write_timeout_sec,
9073 time_t write_timeout_usec, time_t max_timeout_msec,
9074 std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
9075 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9076 write_timeout_sec, write_timeout_usec, max_timeout_msec,
9077 start_time);
9078 return callback(strm);
9079}
9080
9081inline std::pair<std::string, std::string> make_digest_authentication_header(
9082 const Request &req, const std::map<std::string, std::string> &auth,
9083 size_t cnonce_count, const std::string &cnonce, const std::string &username,
9084 const std::string &password, bool is_proxy = false) {
9085 std::string nc;
9086 {
9087 std::stringstream ss;
9088 ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
9089 nc = ss.str();
9090 }
9091
9092 std::string qop;
9093 if (auth.find("qop") != auth.end()) {
9094 qop = auth.at("qop");
9095 if (qop.find("auth-int") != std::string::npos) {
9096 qop = "auth-int";
9097 } else if (qop.find("auth") != std::string::npos) {
9098 qop = "auth";
9099 } else {
9100 qop.clear();
9101 }
9102 }
9103
9104 std::string algo = "MD5";
9105 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
9106
9107 std::string response;
9108 {
9109 auto H = algo == "SHA-256" ? detail::SHA_256
9110 : algo == "SHA-512" ? detail::SHA_512
9111 : detail::MD5;
9112
9113 auto A1 = username + ":" + auth.at("realm") + ":" + password;
9114
9115 auto A2 = req.method + ":" + req.path;
9116 if (qop == "auth-int") { A2 += ":" + H(req.body); }
9117
9118 if (qop.empty()) {
9119 response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
9120 } else {
9121 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
9122 ":" + qop + ":" + H(A2));
9123 }
9124 }
9125
9126 auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
9127
9128 auto field = "Digest username=\"" + username + "\", realm=\"" +
9129 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
9130 "\", uri=\"" + req.path + "\", algorithm=" + algo +
9131 (qop.empty() ? ", response=\""
9132 : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
9133 cnonce + "\", response=\"") +
9134 response + "\"" +
9135 (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
9136
9137 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9138 return std::make_pair(key, field);
9139}
9140
9141inline bool match_hostname(const std::string &pattern,
9142 const std::string &hostname) {
9143 // Exact match (case-insensitive)
9144 if (detail::case_ignore::equal(hostname, pattern)) { return true; }
9145
9146 // Split both pattern and hostname into components by '.'
9147 std::vector<std::string> pattern_components;
9148 if (!pattern.empty()) {
9149 split(pattern.data(), pattern.data() + pattern.size(), '.',
9150 [&](const char *b, const char *e) {
9151 pattern_components.emplace_back(b, e);
9152 });
9153 }
9154
9155 std::vector<std::string> host_components;
9156 if (!hostname.empty()) {
9157 split(hostname.data(), hostname.data() + hostname.size(), '.',
9158 [&](const char *b, const char *e) {
9159 host_components.emplace_back(b, e);
9160 });
9161 }
9162
9163 // Component count must match
9164 if (host_components.size() != pattern_components.size()) { return false; }
9165
9166 // Compare each component with wildcard support
9167 // Supports: "*" (full wildcard), "prefix*" (partial wildcard)
9168 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
9169 auto itr = pattern_components.begin();
9170 for (const auto &h : host_components) {
9171 auto &p = *itr;
9172 if (!detail::case_ignore::equal(p, h) && p != "*") {
9173 bool partial_match = false;
9174 if (!p.empty() && p[p.size() - 1] == '*') {
9175 const auto prefix_length = p.size() - 1;
9176 if (prefix_length == 0) {
9177 partial_match = true;
9178 } else if (h.size() >= prefix_length) {
9179 partial_match =
9180 std::equal(p.begin(),
9181 p.begin() + static_cast<std::string::difference_type>(
9182 prefix_length),
9183 h.begin(), [](const char ca, const char cb) {
9184 return detail::case_ignore::to_lower(ca) ==
9185 detail::case_ignore::to_lower(cb);
9186 });
9187 }
9188 }
9189 if (!partial_match) { return false; }
9190 }
9191 ++itr;
9192 }
9193
9194 return true;
9195}
9196
9197#ifdef _WIN32
9198// Verify certificate using Windows CertGetCertificateChain API.
9199// This provides real-time certificate validation with Windows Update
9200// integration, independent of the TLS backend (OpenSSL or MbedTLS).
9201inline bool
9202verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
9203 const std::string &hostname,
9204 bool verify_hostname, uint64_t &out_error) {
9205 if (der_cert.empty()) { return false; }
9206
9207 out_error = 0;
9208
9209 // Create Windows certificate context from DER data
9210 auto cert_context = CertCreateCertificateContext(
9211 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
9212 static_cast<DWORD>(der_cert.size()));
9213
9214 if (!cert_context) {
9215 out_error = GetLastError();
9216 return false;
9217 }
9218
9219 auto cert_guard =
9220 scope_exit([&] { CertFreeCertificateContext(cert_context); });
9221
9222 // Setup chain parameters
9223 CERT_CHAIN_PARA chain_para = {};
9224 chain_para.cbSize = sizeof(chain_para);
9225
9226 // Build certificate chain with revocation checking
9227 PCCERT_CHAIN_CONTEXT chain_context = nullptr;
9228 auto chain_result = CertGetCertificateChain(
9229 nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
9230 CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
9231 CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
9232 nullptr, &chain_context);
9233
9234 if (!chain_result || !chain_context) {
9235 out_error = GetLastError();
9236 return false;
9237 }
9238
9239 auto chain_guard =
9240 scope_exit([&] { CertFreeCertificateChain(chain_context); });
9241
9242 // Check if chain has errors
9243 if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
9244 out_error = chain_context->TrustStatus.dwErrorStatus;
9245 return false;
9246 }
9247
9248 // Verify SSL policy
9249 SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
9250 extra_policy_para.cbSize = sizeof(extra_policy_para);
9251#ifdef AUTHTYPE_SERVER
9252 extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
9253#endif
9254
9255 std::wstring whost;
9256 if (verify_hostname) {
9257 whost = u8string_to_wstring(hostname.c_str());
9258 extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
9259 }
9260
9261 CERT_CHAIN_POLICY_PARA policy_para = {};
9262 policy_para.cbSize = sizeof(policy_para);
9263#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
9264 policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
9265#else
9266 policy_para.dwFlags = 0;
9267#endif
9268 policy_para.pvExtraPolicyPara = &extra_policy_para;
9269
9270 CERT_CHAIN_POLICY_STATUS policy_status = {};
9271 policy_status.cbSize = sizeof(policy_status);
9272
9273 if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
9274 &policy_para, &policy_status)) {
9275 out_error = GetLastError();
9276 return false;
9277 }
9278
9279 if (policy_status.dwError != 0) {
9280 out_error = policy_status.dwError;
9281 return false;
9282 }
9283
9284 return true;
9285}
9286#endif // _WIN32
9287
9288// Loads CA file/dir configuration and applies the system CA policy to a
9289// client TLS context. PEM data and native stores are applied to the context
9290// directly at set time; has_custom_store reflects them for the Auto policy
9291// decision.
9292inline bool load_client_ca_config(tls::ctx_t ctx,
9293 const std::string &ca_cert_file_path,
9294 const std::string &ca_cert_dir_path,
9295 bool has_custom_store, SystemCAMode mode,
9296 uint64_t &backend_error) {
9297 auto ret = true;
9298
9299 if (!ca_cert_file_path.empty()) {
9300 if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) {
9301 backend_error = tls::get_error();
9302 ret = false;
9303 }
9304 } else if (!ca_cert_dir_path.empty()) {
9305 if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) {
9306 backend_error = tls::get_error();
9307 ret = false;
9308 }
9309 }
9310
9311 auto has_custom_ca = !ca_cert_file_path.empty() ||
9312 !ca_cert_dir_path.empty() || has_custom_store;
9313 if (mode == SystemCAMode::Enabled ||
9314 (mode == SystemCAMode::Auto && !has_custom_ca)) {
9315 if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); }
9316 }
9317
9318 return ret;
9319}
9320
9321inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
9322 tls::session_t &session, socket_t sock,
9323 bool server_certificate_verification,
9324 time_t timeout_sec, time_t timeout_usec) {
9325 using namespace tls;
9326
9327 if (!ctx) { return false; }
9328
9329 bool is_ip = is_ip_address(host);
9330
9331#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
9332 // Chain verification happens during the handshake even for IP hosts; the
9333 // certificate identity is verified post-handshake via verify_hostname()
9334 set_verify_client(ctx, server_certificate_verification);
9335#endif
9336
9337 session = create_session(ctx, sock);
9338 if (!session) { return false; }
9339
9340 // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
9341 // set_hostname also sets SNI, so it must be skipped for IP hosts as well;
9342 // their identity is checked post-handshake below instead.
9343 if (!is_ip) {
9344 if (server_certificate_verification) {
9345 set_hostname(session, host.c_str());
9346 } else {
9347 set_sni(session, host.c_str());
9348 }
9349 }
9350
9351 if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
9352 return false;
9353 }
9354
9355 if (server_certificate_verification) {
9356 if (get_verify_result(session) != 0) { return false; }
9357
9358 // Identity check against the peer certificate, post-handshake for all
9359 // backends (same as SSLClient). For IP hosts this is the only identity
9360 // verification since no hostname is bound during the handshake.
9361 auto server_cert = get_peer_cert(session);
9362 if (!server_cert) { return false; }
9363 auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
9364 if (!verify_hostname(server_cert, host.c_str())) { return false; }
9365 }
9366
9367 return true;
9368}
9369
9370} // namespace detail
9371#endif // CPPHTTPLIB_SSL_ENABLED
9372
9373/*
9374 * Group 3: httplib namespace - Non-SSL public API implementations
9375 */
9376
9377inline void default_socket_options(socket_t sock) {
9378 set_socket_opt(sock, SOL_SOCKET,
9379#ifdef SO_REUSEPORT
9380 SO_REUSEPORT,
9381#else
9382 SO_REUSEADDR,
9383#endif
9384 1);
9385}
9386
9387inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
9388 return detail::set_socket_opt_impl(sock, level, optname, &optval,
9389 sizeof(optval));
9390}
9391
9392inline std::string get_bearer_token_auth(const Request &req) {
9393 if (req.has_header("Authorization")) {
9394 constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
9395 return req.get_header_value("Authorization")
9396 .substr(bearer_header_prefix_len);
9397 }
9398 return "";
9399}
9400
9401inline const char *status_message(int status) {
9402 switch (status) {
9403 case StatusCode::Continue_100: return "Continue";
9404 case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
9405 case StatusCode::Processing_102: return "Processing";
9406 case StatusCode::EarlyHints_103: return "Early Hints";
9407 case StatusCode::OK_200: return "OK";
9408 case StatusCode::Created_201: return "Created";
9409 case StatusCode::Accepted_202: return "Accepted";
9410 case StatusCode::NonAuthoritativeInformation_203:
9411 return "Non-Authoritative Information";
9412 case StatusCode::NoContent_204: return "No Content";
9413 case StatusCode::ResetContent_205: return "Reset Content";
9414 case StatusCode::PartialContent_206: return "Partial Content";
9415 case StatusCode::MultiStatus_207: return "Multi-Status";
9416 case StatusCode::AlreadyReported_208: return "Already Reported";
9417 case StatusCode::IMUsed_226: return "IM Used";
9418 case StatusCode::MultipleChoices_300: return "Multiple Choices";
9419 case StatusCode::MovedPermanently_301: return "Moved Permanently";
9420 case StatusCode::Found_302: return "Found";
9421 case StatusCode::SeeOther_303: return "See Other";
9422 case StatusCode::NotModified_304: return "Not Modified";
9423 case StatusCode::UseProxy_305: return "Use Proxy";
9424 case StatusCode::unused_306: return "unused";
9425 case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
9426 case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
9427 case StatusCode::BadRequest_400: return "Bad Request";
9428 case StatusCode::Unauthorized_401: return "Unauthorized";
9429 case StatusCode::PaymentRequired_402: return "Payment Required";
9430 case StatusCode::Forbidden_403: return "Forbidden";
9431 case StatusCode::NotFound_404: return "Not Found";
9432 case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
9433 case StatusCode::NotAcceptable_406: return "Not Acceptable";
9434 case StatusCode::ProxyAuthenticationRequired_407:
9435 return "Proxy Authentication Required";
9436 case StatusCode::RequestTimeout_408: return "Request Timeout";
9437 case StatusCode::Conflict_409: return "Conflict";
9438 case StatusCode::Gone_410: return "Gone";
9439 case StatusCode::LengthRequired_411: return "Length Required";
9440 case StatusCode::PreconditionFailed_412: return "Precondition Failed";
9441 case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
9442 case StatusCode::UriTooLong_414: return "URI Too Long";
9443 case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
9444 case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
9445 case StatusCode::ExpectationFailed_417: return "Expectation Failed";
9446 case StatusCode::ImATeapot_418: return "I'm a teapot";
9447 case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
9448 case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
9449 case StatusCode::Locked_423: return "Locked";
9450 case StatusCode::FailedDependency_424: return "Failed Dependency";
9451 case StatusCode::TooEarly_425: return "Too Early";
9452 case StatusCode::UpgradeRequired_426: return "Upgrade Required";
9453 case StatusCode::PreconditionRequired_428: return "Precondition Required";
9454 case StatusCode::TooManyRequests_429: return "Too Many Requests";
9455 case StatusCode::RequestHeaderFieldsTooLarge_431:
9456 return "Request Header Fields Too Large";
9457 case StatusCode::UnavailableForLegalReasons_451:
9458 return "Unavailable For Legal Reasons";
9459 case StatusCode::NotImplemented_501: return "Not Implemented";
9460 case StatusCode::BadGateway_502: return "Bad Gateway";
9461 case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
9462 case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
9463 case StatusCode::HttpVersionNotSupported_505:
9464 return "HTTP Version Not Supported";
9465 case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
9466 case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
9467 case StatusCode::LoopDetected_508: return "Loop Detected";
9468 case StatusCode::NotExtended_510: return "Not Extended";
9469 case StatusCode::NetworkAuthenticationRequired_511:
9470 return "Network Authentication Required";
9471
9472 default:
9473 case StatusCode::InternalServerError_500: return "Internal Server Error";
9474 }
9475}
9476
9477inline std::string to_string(const Error error) {
9478 switch (error) {
9479 case Error::Success: return "Success (no error)";
9480 case Error::Unknown: return "Unknown";
9481 case Error::Connection: return "Could not establish connection";
9482 case Error::BindIPAddress: return "Failed to bind IP address";
9483 case Error::Read: return "Failed to read connection";
9484 case Error::Write: return "Failed to write connection";
9485 case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
9486 case Error::Canceled: return "Connection handling canceled";
9487 case Error::SSLConnection: return "SSL connection failed";
9488 case Error::SSLLoadingCerts: return "SSL certificate loading failed";
9489 case Error::SSLServerVerification: return "SSL server verification failed";
9490 case Error::SSLServerHostnameVerification:
9491 return "SSL server hostname verification failed";
9492 case Error::UnsupportedMultipartBoundaryChars:
9493 return "Unsupported HTTP multipart boundary characters";
9494 case Error::Compression: return "Compression failed";
9495 case Error::ConnectionTimeout: return "Connection timed out";
9496 case Error::ProxyConnection: return "Proxy connection failed";
9497 case Error::ConnectionClosed: return "Connection closed by server";
9498 case Error::Timeout: return "Read timeout";
9499 case Error::ResourceExhaustion: return "Resource exhaustion";
9500 case Error::TooManyFormDataFiles: return "Too many form data files";
9501 case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
9502 case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
9503 case Error::ExceedMaxSocketDescriptorCount:
9504 return "Exceeded maximum socket descriptor count";
9505 case Error::InvalidRequestLine: return "Invalid request line";
9506 case Error::InvalidHTTPMethod: return "Invalid HTTP method";
9507 case Error::InvalidHTTPVersion: return "Invalid HTTP version";
9508 case Error::InvalidHeaders: return "Invalid headers";
9509 case Error::MultipartParsing: return "Multipart parsing failed";
9510 case Error::OpenFile: return "Failed to open file";
9511 case Error::Listen: return "Failed to listen on socket";
9512 case Error::GetSockName: return "Failed to get socket name";
9513 case Error::UnsupportedAddressFamily: return "Unsupported address family";
9514 case Error::HTTPParsing: return "HTTP parsing failed";
9515 case Error::InvalidRangeHeader: return "Invalid Range header";
9516 default: break;
9517 }
9518
9519 return "Invalid";
9520}
9521
9522inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
9523 os << to_string(obj);
9524 os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
9525 return os;
9526}
9527
9528inline std::string hosted_at(const std::string &hostname) {
9529 std::vector<std::string> addrs;
9530 hosted_at(hostname, addrs);
9531 if (addrs.empty()) { return std::string(); }
9532 return addrs[0];
9533}
9534
9535inline void hosted_at(const std::string &hostname,
9536 std::vector<std::string> &addrs) {
9537 struct addrinfo hints;
9538 struct addrinfo *result;
9539
9540 memset(&hints, 0, sizeof(struct addrinfo));
9541 hints.ai_family = AF_UNSPEC;
9542 hints.ai_socktype = SOCK_STREAM;
9543 hints.ai_protocol = 0;
9544
9545 if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
9546 &result, 0)) {
9547#if defined __linux__ && !defined __ANDROID__
9548 res_init();
9549#endif
9550 return;
9551 }
9552 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
9553
9554 for (auto rp = result; rp; rp = rp->ai_next) {
9555 const auto &addr =
9556 *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
9557 std::string ip;
9558 auto dummy = -1;
9559 if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
9560 dummy)) {
9561 addrs.emplace_back(std::move(ip));
9562 }
9563 }
9564}
9565
9566inline std::string encode_uri_component(const std::string &value) {
9567 std::ostringstream escaped;
9568 escaped.fill('0');
9569 escaped << std::hex;
9570
9571 for (auto c : value) {
9572 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9573 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9574 c == ')') {
9575 escaped << c;
9576 } else {
9577 escaped << std::uppercase;
9578 escaped << '%' << std::setw(2)
9579 << static_cast<int>(static_cast<unsigned char>(c));
9580 escaped << std::nouppercase;
9581 }
9582 }
9583
9584 return escaped.str();
9585}
9586
9587inline std::string encode_uri(const std::string &value) {
9588 std::ostringstream escaped;
9589 escaped.fill('0');
9590 escaped << std::hex;
9591
9592 for (auto c : value) {
9593 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9594 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9595 c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
9596 c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
9597 escaped << c;
9598 } else {
9599 escaped << std::uppercase;
9600 escaped << '%' << std::setw(2)
9601 << static_cast<int>(static_cast<unsigned char>(c));
9602 escaped << std::nouppercase;
9603 }
9604 }
9605
9606 return escaped.str();
9607}
9608
9609inline std::string decode_uri_component(const std::string &value) {
9610 std::string result;
9611
9612 for (size_t i = 0; i < value.size(); i++) {
9613 if (value[i] == '%' && i + 2 < value.size()) {
9614 auto val = 0;
9615 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9616 result += static_cast<char>(val);
9617 i += 2;
9618 } else {
9619 result += value[i];
9620 }
9621 } else {
9622 result += value[i];
9623 }
9624 }
9625
9626 return result;
9627}
9628
9629inline std::string decode_uri(const std::string &value) {
9630 std::string result;
9631
9632 for (size_t i = 0; i < value.size(); i++) {
9633 if (value[i] == '%' && i + 2 < value.size()) {
9634 auto val = 0;
9635 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9636 result += static_cast<char>(val);
9637 i += 2;
9638 } else {
9639 result += value[i];
9640 }
9641 } else {
9642 result += value[i];
9643 }
9644 }
9645
9646 return result;
9647}
9648
9649inline std::string encode_path_component(const std::string &component) {
9650 std::string result;
9651 result.reserve(component.size() * 3);
9652
9653 for (size_t i = 0; i < component.size(); i++) {
9654 auto c = static_cast<unsigned char>(component[i]);
9655
9656 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
9657 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9658 result += static_cast<char>(c);
9659 }
9660 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
9661 // "," / ";" / "="
9662 else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
9663 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
9664 c == '=') {
9665 result += static_cast<char>(c);
9666 }
9667 // Colon is allowed in path segments except first segment
9668 else if (c == ':') {
9669 result += static_cast<char>(c);
9670 }
9671 // @ is allowed in path
9672 else if (c == '@') {
9673 result += static_cast<char>(c);
9674 } else {
9675 result += '%';
9676 char hex[3];
9677 snprintf(hex, sizeof(hex), "%02X", c);
9678 result.append(hex, 2);
9679 }
9680 }
9681 return result;
9682}
9683
9684inline std::string decode_path_component(const std::string &component) {
9685 std::string result;
9686 result.reserve(component.size());
9687
9688 for (size_t i = 0; i < component.size(); i++) {
9689 if (component[i] == '%' && i + 1 < component.size()) {
9690 if (component[i + 1] == 'u') {
9691 // Unicode %uXXXX encoding
9692 auto val = 0;
9693 if (detail::from_hex_to_i(component, i + 2, 4, val)) {
9694 // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits),
9695 // so to_utf8 writes at most 3 bytes. buff[4] is safe.
9696 char buff[4];
9697 size_t len = detail::to_utf8(val, buff);
9698 if (len > 0) { result.append(buff, len); }
9699 i += 5; // 'u0000'
9700 } else {
9701 result += component[i];
9702 }
9703 } else {
9704 // Standard %XX encoding
9705 auto val = 0;
9706 if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9707 // 2 digits hex codes
9708 result += static_cast<char>(val);
9709 i += 2; // 'XX'
9710 } else {
9711 result += component[i];
9712 }
9713 }
9714 } else {
9715 result += component[i];
9716 }
9717 }
9718 return result;
9719}
9720
9721inline std::string encode_query_component(const std::string &component,
9722 bool space_as_plus) {
9723 std::string result;
9724 result.reserve(component.size() * 3);
9725
9726 for (size_t i = 0; i < component.size(); i++) {
9727 auto c = static_cast<unsigned char>(component[i]);
9728
9729 // Unreserved characters per RFC 3986
9730 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9731 result += static_cast<char>(c);
9732 }
9733 // Space handling
9734 else if (c == ' ') {
9735 if (space_as_plus) {
9736 result += '+';
9737 } else {
9738 result += "%20";
9739 }
9740 }
9741 // Plus sign handling
9742 else if (c == '+') {
9743 if (space_as_plus) {
9744 result += "%2B";
9745 } else {
9746 result += static_cast<char>(c);
9747 }
9748 }
9749 // Query-safe sub-delimiters (excluding & and = which are query delimiters)
9750 else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
9751 c == '*' || c == ',' || c == ';') {
9752 result += static_cast<char>(c);
9753 }
9754 // Colon and @ are allowed in query
9755 else if (c == ':' || c == '@') {
9756 result += static_cast<char>(c);
9757 }
9758 // Forward slash is allowed in query values
9759 else if (c == '/') {
9760 result += static_cast<char>(c);
9761 }
9762 // Question mark is allowed in query values (after first ?)
9763 else if (c == '?') {
9764 result += static_cast<char>(c);
9765 } else {
9766 result += '%';
9767 char hex[3];
9768 snprintf(hex, sizeof(hex), "%02X", c);
9769 result.append(hex, 2);
9770 }
9771 }
9772 return result;
9773}
9774
9775inline std::string decode_query_component(const std::string &component,
9776 bool plus_as_space) {
9777 std::string result;
9778 result.reserve(component.size());
9779
9780 for (size_t i = 0; i < component.size(); i++) {
9781 if (component[i] == '%' && i + 2 < component.size()) {
9782 auto val = 0;
9783 if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9784 result += static_cast<char>(val);
9785 i += 2;
9786 } else {
9787 result += component[i];
9788 }
9789 } else if (component[i] == '+' && plus_as_space) {
9790 result += ' '; // + becomes space in form-urlencoded
9791 } else {
9792 result += component[i];
9793 }
9794 }
9795 return result;
9796}
9797
9798inline std::string sanitize_filename(const std::string &filename) {
9799 // Extract basename: find the last path separator (/ or \‍)
9800 auto pos = filename.find_last_of("/\\");
9801 auto result =
9802 (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
9803
9804 // Strip null bytes
9805 result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());
9806
9807 // Trim whitespace
9808 {
9809 auto start = result.find_first_not_of(" \t");
9810 auto end = result.find_last_not_of(" \t");
9811 result = (start == std::string::npos)
9812 ? ""
9813 : result.substr(start, end - start + 1);
9814 }
9815
9816 // Reject . and ..
9817 if (result == "." || result == "..") { return ""; }
9818
9819 return result;
9820}
9821
9822inline std::string append_query_params(const std::string &path,
9823 const Params &params) {
9824 std::string path_with_query = path;
9825 thread_local const std::regex re("[^?]+\\?.*");
9826 auto delm = std::regex_match(path, re) ? '&' : '?';
9827 path_with_query += delm + detail::params_to_query_str(params);
9828 return path_with_query;
9829}
9830
9831// Header utilities
9832inline std::pair<std::string, std::string>
9833make_range_header(const Ranges &ranges) {
9834 std::string field = "bytes=";
9835 auto i = 0;
9836 for (const auto &r : ranges) {
9837 if (i != 0) { field += ", "; }
9838 if (r.first != -1) { field += std::to_string(r.first); }
9839 field += '-';
9840 if (r.second != -1) { field += std::to_string(r.second); }
9841 i++;
9842 }
9843 return std::make_pair("Range", std::move(field));
9844}
9845
9846inline std::pair<std::string, std::string>
9847make_basic_authentication_header(const std::string &username,
9848 const std::string &password, bool is_proxy) {
9849 auto field = "Basic " + detail::base64_encode(username + ":" + password);
9850 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9851 return std::make_pair(key, std::move(field));
9852}
9853
9854inline std::pair<std::string, std::string>
9855make_bearer_token_authentication_header(const std::string &token,
9856 bool is_proxy = false) {
9857 auto field = "Bearer " + token;
9858 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9859 return std::make_pair(key, std::move(field));
9860}
9861
9862// Request implementation
9863inline size_t Request::get_header_value_u64(const std::string &key, size_t def,
9864 size_t id) const {
9865 return detail::get_header_value_u64(headers, key, def, id);
9866}
9867
9868inline bool Request::has_header(const std::string &key) const {
9869 return detail::has_header(headers, key);
9870}
9871
9872inline std::string Request::get_header_value(const std::string &key,
9873 const char *def, size_t id) const {
9874 return detail::get_header_value(headers, key, def, id);
9875}
9876
9877inline size_t Request::get_header_value_count(const std::string &key) const {
9878 return detail::get_header_value_count(headers, key);
9879}
9880
9881inline void Request::set_header(const std::string &key,
9882 const std::string &val) {
9883 detail::set_header(headers, key, val);
9884}
9885
9886inline bool Request::has_trailer(const std::string &key) const {
9887 return trailers.find(key) != trailers.end();
9888}
9889
9890inline std::string Request::get_trailer_value(const std::string &key,
9891 size_t id) const {
9892 return detail::get_multimap_value(trailers, key, id);
9893}
9894
9895inline size_t Request::get_trailer_value_count(const std::string &key) const {
9896 auto r = trailers.equal_range(key);
9897 return static_cast<size_t>(std::distance(r.first, r.second));
9898}
9899
9900inline bool Request::has_param(const std::string &key) const {
9901 return params.find(key) != params.end();
9902}
9903
9904inline std::string Request::get_param_value(const std::string &key,
9905 size_t id) const {
9906 return detail::get_multimap_value(params, key, id);
9907}
9908
9909inline std::vector<std::string>
9910Request::get_param_values(const std::string &key) const {
9911 auto rng = params.equal_range(key);
9912 std::vector<std::string> values;
9913 values.reserve(static_cast<size_t>(std::distance(rng.first, rng.second)));
9914 for (auto it = rng.first; it != rng.second; ++it) {
9915 values.push_back(it->second);
9916 }
9917 return values;
9918}
9919
9920inline size_t Request::get_param_value_count(const std::string &key) const {
9921 auto r = params.equal_range(key);
9922 return static_cast<size_t>(std::distance(r.first, r.second));
9923}
9924
9925inline bool Request::is_multipart_form_data() const {
9926 const auto &content_type = get_header_value("Content-Type");
9927 return detail::extract_media_type(content_type) == "multipart/form-data";
9928}
9929
9930// Multipart FormData implementation
9931inline std::string MultipartFormData::get_field(const std::string &key,
9932 size_t id) const {
9933 auto rng = fields.equal_range(key);
9934 auto it = rng.first;
9935 std::advance(it, static_cast<ssize_t>(id));
9936 if (it != rng.second) { return it->second.content; }
9937 return std::string();
9938}
9939
9940inline std::vector<std::string>
9941MultipartFormData::get_fields(const std::string &key) const {
9942 std::vector<std::string> values;
9943 auto rng = fields.equal_range(key);
9944 for (auto it = rng.first; it != rng.second; it++) {
9945 values.push_back(it->second.content);
9946 }
9947 return values;
9948}
9949
9950inline bool MultipartFormData::has_field(const std::string &key) const {
9951 return fields.find(key) != fields.end();
9952}
9953
9954inline size_t MultipartFormData::get_field_count(const std::string &key) const {
9955 auto r = fields.equal_range(key);
9956 return static_cast<size_t>(std::distance(r.first, r.second));
9957}
9958
9959inline FormData MultipartFormData::get_file(const std::string &key,
9960 size_t id) const {
9961 return detail::get_multimap_value(files, key, id);
9962}
9963
9964inline std::vector<FormData>
9965MultipartFormData::get_files(const std::string &key) const {
9966 std::vector<FormData> values;
9967 auto rng = files.equal_range(key);
9968 for (auto it = rng.first; it != rng.second; it++) {
9969 values.push_back(it->second);
9970 }
9971 return values;
9972}
9973
9974inline bool MultipartFormData::has_file(const std::string &key) const {
9975 return files.find(key) != files.end();
9976}
9977
9978inline size_t MultipartFormData::get_file_count(const std::string &key) const {
9979 auto r = files.equal_range(key);
9980 return static_cast<size_t>(std::distance(r.first, r.second));
9981}
9982
9983// Response implementation
9984inline size_t Response::get_header_value_u64(const std::string &key, size_t def,
9985 size_t id) const {
9986 return detail::get_header_value_u64(headers, key, def, id);
9987}
9988
9989inline bool Response::has_header(const std::string &key) const {
9990 return headers.find(key) != headers.end();
9991}
9992
9993inline std::string Response::get_header_value(const std::string &key,
9994 const char *def,
9995 size_t id) const {
9996 return detail::get_header_value(headers, key, def, id);
9997}
9998
9999inline size_t Response::get_header_value_count(const std::string &key) const {
10000 return detail::get_header_value_count(headers, key);
10001}
10002
10003inline void Response::set_header(const std::string &key,
10004 const std::string &val) {
10005 detail::set_header(headers, key, val);
10006}
10007inline bool Response::has_trailer(const std::string &key) const {
10008 return trailers.find(key) != trailers.end();
10009}
10010
10011inline std::string Response::get_trailer_value(const std::string &key,
10012 size_t id) const {
10013 return detail::get_multimap_value(trailers, key, id);
10014}
10015
10016inline size_t Response::get_trailer_value_count(const std::string &key) const {
10017 auto r = trailers.equal_range(key);
10018 return static_cast<size_t>(std::distance(r.first, r.second));
10019}
10020
10021inline void Response::set_redirect(const std::string &url, int stat) {
10022 if (detail::fields::is_field_value(url)) {
10023 set_header("Location", url);
10024 if (300 <= stat && stat < 400) {
10025 this->status = stat;
10026 } else {
10027 this->status = StatusCode::Found_302;
10028 }
10029 }
10030}
10031
10032inline void Response::set_content(const char *s, size_t n,
10033 const std::string &content_type) {
10034 body.assign(s, n);
10035
10036 auto rng = headers.equal_range("Content-Type");
10037 headers.erase(rng.first, rng.second);
10038 set_header("Content-Type", content_type);
10039}
10040
10041inline void Response::set_content(const std::string &s,
10042 const std::string &content_type) {
10043 set_content(s.data(), s.size(), content_type);
10044}
10045
10046inline void Response::set_content(std::string &&s,
10047 const std::string &content_type) {
10048 body = std::move(s);
10049
10050 auto rng = headers.equal_range("Content-Type");
10051 headers.erase(rng.first, rng.second);
10052 set_header("Content-Type", content_type);
10053}
10054
10055inline void Response::set_content_provider(
10056 size_t in_length, const std::string &content_type, ContentProvider provider,
10057 ContentProviderResourceReleaser resource_releaser) {
10058 set_header("Content-Type", content_type);
10059 content_length_ = in_length;
10060 if (in_length > 0) { content_provider_ = std::move(provider); }
10061 content_provider_resource_releaser_ = std::move(resource_releaser);
10062 is_chunked_content_provider_ = false;
10063}
10064
10065inline void Response::set_content_provider(
10066 const std::string &content_type, ContentProviderWithoutLength provider,
10067 ContentProviderResourceReleaser resource_releaser) {
10068 set_header("Content-Type", content_type);
10069 content_length_ = 0;
10070 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
10071 content_provider_resource_releaser_ = std::move(resource_releaser);
10072 is_chunked_content_provider_ = false;
10073}
10074
10075inline void Response::set_chunked_content_provider(
10076 const std::string &content_type, ContentProviderWithoutLength provider,
10077 ContentProviderResourceReleaser resource_releaser) {
10078 set_header("Content-Type", content_type);
10079 content_length_ = 0;
10080 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
10081 content_provider_resource_releaser_ = std::move(resource_releaser);
10082 is_chunked_content_provider_ = true;
10083}
10084
10085inline void Response::set_file_content(const std::string &path,
10086 const std::string &content_type) {
10087 file_content_path_ = path;
10088 file_content_content_type_ = content_type;
10089}
10090
10091inline void Response::set_file_content(const std::string &path) {
10092 file_content_path_ = path;
10093}
10094
10095// Result implementation
10096inline size_t Result::get_request_header_value_u64(const std::string &key,
10097 size_t def,
10098 size_t id) const {
10099 return detail::get_header_value_u64(request_headers_, key, def, id);
10100}
10101
10102inline bool Result::has_request_header(const std::string &key) const {
10103 return request_headers_.find(key) != request_headers_.end();
10104}
10105
10106inline std::string Result::get_request_header_value(const std::string &key,
10107 const char *def,
10108 size_t id) const {
10109 return detail::get_header_value(request_headers_, key, def, id);
10110}
10111
10112inline size_t
10113Result::get_request_header_value_count(const std::string &key) const {
10114 auto r = request_headers_.equal_range(key);
10115 return static_cast<size_t>(std::distance(r.first, r.second));
10116}
10117
10118// Stream implementation
10119inline ssize_t Stream::write(const char *ptr) {
10120 return write(ptr, strlen(ptr));
10121}
10122
10123inline ssize_t Stream::write(const std::string &s) {
10124 return write(s.data(), s.size());
10125}
10126
10127// BodyReader implementation
10128inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
10129 if (!stream) {
10130 last_error = Error::Connection;
10131 return -1;
10132 }
10133 if (eof) { return 0; }
10134
10135 if (!chunked) {
10136 // Content-Length based reading
10137 if (has_content_length && bytes_read >= content_length) {
10138 eof = true;
10139 return 0;
10140 }
10141
10142 auto to_read = len;
10143 if (has_content_length) {
10144 auto remaining = content_length - bytes_read;
10145 to_read = (std::min)(len, remaining);
10146 }
10147 auto n = stream->read(buf, to_read);
10148
10149 if (n < 0) {
10150 last_error = stream->get_error();
10151 if (last_error == Error::Success) { last_error = Error::Read; }
10152 eof = true;
10153 return n;
10154 }
10155 if (n == 0) {
10156 // Unexpected EOF before content_length
10157 last_error = stream->get_error();
10158 if (last_error == Error::Success) { last_error = Error::Read; }
10159 eof = true;
10160 return 0;
10161 }
10162
10163 bytes_read += static_cast<size_t>(n);
10164 if (has_content_length && bytes_read >= content_length) { eof = true; }
10165 if (payload_max_length > 0 && bytes_read > payload_max_length) {
10166 last_error = Error::ExceedMaxPayloadSize;
10167 eof = true;
10168 return -1;
10169 }
10170 return n;
10171 }
10172
10173 // Chunked transfer encoding: delegate to shared decoder instance.
10174 if (!chunked_decoder) { chunked_decoder.reset(new ChunkedDecoder(*stream)); }
10175
10176 size_t chunk_offset = 0;
10177 size_t chunk_total = 0;
10178 auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
10179 if (n < 0) {
10180 last_error = stream->get_error();
10181 if (last_error == Error::Success) { last_error = Error::Read; }
10182 eof = true;
10183 return n;
10184 }
10185
10186 if (n == 0) {
10187 // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
10188 eof = true;
10189 return 0;
10190 }
10191
10192 bytes_read += static_cast<size_t>(n);
10193 if (payload_max_length > 0 && bytes_read > payload_max_length) {
10194 last_error = Error::ExceedMaxPayloadSize;
10195 eof = true;
10196 return -1;
10197 }
10198 return n;
10199}
10200
10201// ThreadPool implementation
10202inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr)
10203 : base_thread_count_(n), max_queued_requests_(mqr), idle_thread_count_(0),
10204 shutdown_(false) {
10205#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10206 if (max_n != 0 && max_n < n) {
10207 std::string msg = "max_threads must be >= base_threads";
10208 throw std::invalid_argument(msg);
10209 }
10210#endif
10211 max_thread_count_ = max_n == 0 ? n : max_n;
10212 threads_.reserve(base_thread_count_);
10213#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10214 try {
10215#endif
10216 for (size_t i = 0; i < base_thread_count_; i++) {
10217 threads_.emplace_back(std::thread([this]() { worker(false); }));
10218 }
10219#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10220 } catch (...) {
10221 // If thread creation fails partway (e.g., pthread_create returns EAGAIN),
10222 // signal the workers we already spawned to exit and join them so the
10223 // vector destructor does not see joinable threads (which would call
10224 // std::terminate). Then rethrow so the caller learns of the failure.
10225 {
10226 std::unique_lock<std::mutex> lock(mutex_);
10227 shutdown_ = true;
10228 }
10229 cond_.notify_all();
10230 for (auto &t : threads_) {
10231 if (t.joinable()) { t.join(); }
10232 }
10233 throw;
10234 }
10235#endif
10236}
10237
10238inline bool ThreadPool::enqueue(std::function<void()> fn) {
10239 {
10240 std::unique_lock<std::mutex> lock(mutex_);
10241 if (shutdown_) { return false; }
10242 if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
10243 return false;
10244 }
10245 jobs_.push_back(std::move(fn));
10246
10247 // Spawn a dynamic thread if no idle threads and under max
10248 if (idle_thread_count_ == 0 &&
10249 threads_.size() + dynamic_threads_.size() < max_thread_count_) {
10250 cleanup_finished_threads();
10251 dynamic_threads_.emplace_back(std::thread([this]() { worker(true); }));
10252 }
10253 }
10254
10255 cond_.notify_one();
10256 return true;
10257}
10258
10259inline void ThreadPool::shutdown() {
10260 {
10261 std::unique_lock<std::mutex> lock(mutex_);
10262 shutdown_ = true;
10263 }
10264
10265 cond_.notify_all();
10266
10267 for (auto &t : threads_) {
10268 if (t.joinable()) { t.join(); }
10269 }
10270
10271 // Move dynamic_threads_ to a local list under the lock to avoid racing
10272 // with worker threads that call move_to_finished() concurrently.
10273 std::list<std::thread> remaining_dynamic;
10274 {
10275 std::unique_lock<std::mutex> lock(mutex_);
10276 remaining_dynamic = std::move(dynamic_threads_);
10277 }
10278 for (auto &t : remaining_dynamic) {
10279 if (t.joinable()) { t.join(); }
10280 }
10281
10282 std::unique_lock<std::mutex> lock(mutex_);
10283 cleanup_finished_threads();
10284}
10285
10286inline void ThreadPool::move_to_finished(std::thread::id id) {
10287 // Must be called with mutex_ held
10288 for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) {
10289 if (it->get_id() == id) {
10290 finished_threads_.push_back(std::move(*it));
10291 dynamic_threads_.erase(it);
10292 return;
10293 }
10294 }
10295}
10296
10297inline void ThreadPool::cleanup_finished_threads() {
10298 // Must be called with mutex_ held
10299 for (auto &t : finished_threads_) {
10300 if (t.joinable()) { t.join(); }
10301 }
10302 finished_threads_.clear();
10303}
10304
10305inline void ThreadPool::worker(bool is_dynamic) {
10306 for (;;) {
10307 std::function<void()> fn;
10308 {
10309 std::unique_lock<std::mutex> lock(mutex_);
10310 idle_thread_count_++;
10311
10312 if (is_dynamic) {
10313 auto has_work = cond_.wait_for(
10314 lock, std::chrono::seconds(CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT),
10315 [&] { return !jobs_.empty() || shutdown_; });
10316 if (!has_work) {
10317 // Timed out with no work - exit this dynamic thread
10318 idle_thread_count_--;
10319 move_to_finished(std::this_thread::get_id());
10320 break;
10321 }
10322 } else {
10323 cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; });
10324 }
10325
10326 idle_thread_count_--;
10327
10328 if (shutdown_ && jobs_.empty()) { break; }
10329
10330 fn = std::move(jobs_.front());
10331 jobs_.pop_front();
10332 }
10333
10334 assert(true == static_cast<bool>(fn));
10335 fn();
10336 }
10337
10338#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
10339 !defined(LIBRESSL_VERSION_NUMBER)
10340 OPENSSL_thread_stop();
10341#endif
10342}
10343
10344/*
10345 * Group 1 (continued): detail namespace - Stream implementations
10346 */
10347
10348namespace detail {
10349
10350inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
10351 time_t timeout_sec, time_t timeout_usec,
10352 time_t &actual_timeout_sec,
10353 time_t &actual_timeout_usec) {
10354 auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
10355
10356 auto actual_timeout_msec =
10357 (std::min)(max_timeout_msec - duration_msec, timeout_msec);
10358
10359 if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
10360
10361 actual_timeout_sec = actual_timeout_msec / 1000;
10362 actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
10363}
10364
10365// Socket stream implementation
10366inline SocketStream::SocketStream(
10367 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
10368 time_t write_timeout_sec, time_t write_timeout_usec,
10369 time_t max_timeout_msec,
10370 std::chrono::time_point<std::chrono::steady_clock> start_time)
10371 : sock_(sock), read_timeout_sec_(read_timeout_sec),
10372 read_timeout_usec_(read_timeout_usec),
10373 write_timeout_sec_(write_timeout_sec),
10374 write_timeout_usec_(write_timeout_usec),
10375 max_timeout_msec_(max_timeout_msec), start_time_(start_time),
10376 read_buff_(read_buff_size_, 0) {}
10377
10378inline SocketStream::~SocketStream() = default;
10379
10380inline bool SocketStream::is_readable() const {
10381 return read_buff_off_ < read_buff_content_size_;
10382}
10383
10384inline bool SocketStream::wait_readable() const {
10385 if (max_timeout_msec_ <= 0) {
10386 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10387 }
10388
10389 time_t read_timeout_sec;
10390 time_t read_timeout_usec;
10391 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10392 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10393
10394 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10395}
10396
10397inline bool SocketStream::wait_writable() const {
10398 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
10399}
10400
10401inline bool SocketStream::is_peer_alive() const {
10402 return detail::is_socket_alive(sock_);
10403}
10404
10405inline ssize_t SocketStream::read(char *ptr, size_t size) {
10406#ifdef _WIN32
10407 size =
10408 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10409#else
10410 size = (std::min)(size,
10411 static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
10412#endif
10413
10414 if (read_buff_off_ < read_buff_content_size_) {
10415 auto remaining_size = read_buff_content_size_ - read_buff_off_;
10416 if (size <= remaining_size) {
10417 memcpy(ptr, read_buff_.data() + read_buff_off_, size);
10418 read_buff_off_ += size;
10419 return static_cast<ssize_t>(size);
10420 } else {
10421 memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
10422 read_buff_off_ += remaining_size;
10423 return static_cast<ssize_t>(remaining_size);
10424 }
10425 }
10426
10427 if (!wait_readable()) {
10428 error_ = Error::Timeout;
10429 return -1;
10430 }
10431
10432 read_buff_off_ = 0;
10433 read_buff_content_size_ = 0;
10434
10435 if (size < read_buff_size_) {
10436 auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
10438 if (n <= 0) {
10439 if (n == 0) {
10440 error_ = Error::ConnectionClosed;
10441 } else {
10442 error_ = Error::Read;
10443 }
10444 return n;
10445 } else if (n <= static_cast<ssize_t>(size)) {
10446 memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
10447 return n;
10448 } else {
10449 memcpy(ptr, read_buff_.data(), size);
10450 read_buff_off_ = size;
10451 read_buff_content_size_ = static_cast<size_t>(n);
10452 return static_cast<ssize_t>(size);
10453 }
10454 } else {
10455 auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
10456 if (n <= 0) {
10457 if (n == 0) {
10458 error_ = Error::ConnectionClosed;
10459 } else {
10460 error_ = Error::Read;
10461 }
10462 }
10463 return n;
10464 }
10465}
10466
10467inline ssize_t SocketStream::write(const char *ptr, size_t size) {
10468 if (!wait_writable()) { return -1; }
10469
10470#if defined(_WIN32) && !defined(_WIN64)
10471 size =
10472 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10473#endif
10474
10475 return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
10476}
10477
10478inline void SocketStream::get_remote_ip_and_port(std::string &ip,
10479 int &port) const {
10480 return detail::get_remote_ip_and_port(sock_, ip, port);
10481}
10482
10483inline void SocketStream::get_local_ip_and_port(std::string &ip,
10484 int &port) const {
10485 return detail::get_local_ip_and_port(sock_, ip, port);
10486}
10487
10488inline socket_t SocketStream::socket() const { return sock_; }
10489
10490inline time_t SocketStream::duration() const {
10491 return std::chrono::duration_cast<std::chrono::milliseconds>(
10492 std::chrono::steady_clock::now() - start_time_)
10493 .count();
10494}
10495
10496inline void SocketStream::set_read_timeout(time_t sec, time_t usec) {
10497 read_timeout_sec_ = sec;
10498 read_timeout_usec_ = usec;
10499}
10500
10501// Buffer stream implementation
10502inline bool BufferStream::is_readable() const { return true; }
10503
10504inline bool BufferStream::wait_readable() const { return true; }
10505
10506inline bool BufferStream::wait_writable() const { return true; }
10507
10508inline ssize_t BufferStream::read(char *ptr, size_t size) {
10509#if defined(_MSC_VER) && _MSC_VER < 1910
10510 auto len_read = buffer._Copy_s(ptr, size, size, position);
10511#else
10512 auto len_read = buffer.copy(ptr, size, position);
10513#endif
10514 position += static_cast<size_t>(len_read);
10515 return static_cast<ssize_t>(len_read);
10516}
10517
10518inline ssize_t BufferStream::write(const char *ptr, size_t size) {
10519 buffer.append(ptr, size);
10520 return static_cast<ssize_t>(size);
10521}
10522
10523inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
10524 int & /*port*/) const {}
10525
10526inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
10527 int & /*port*/) const {}
10528
10529inline socket_t BufferStream::socket() const { return 0; }
10530
10531inline time_t BufferStream::duration() const { return 0; }
10532
10533inline const std::string &BufferStream::get_buffer() const { return buffer; }
10534
10535inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
10536 : MatcherBase(pattern) {
10537 constexpr const char marker[] = "/:";
10538
10539 // One past the last ending position of a path param substring
10540 std::size_t last_param_end = 0;
10541
10542#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10543 // Needed to ensure that parameter names are unique during matcher
10544 // construction
10545 // If exceptions are disabled, only last duplicate path
10546 // parameter will be set
10547 std::unordered_set<std::string> param_name_set;
10548#endif
10549
10550 while (true) {
10551 const auto marker_pos = pattern.find(
10552 marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
10553 if (marker_pos == std::string::npos) { break; }
10554
10555 static_fragments_.push_back(
10556 pattern.substr(last_param_end, marker_pos - last_param_end + 1));
10557
10558 const auto param_name_start = marker_pos + str_len(marker);
10559
10560 auto sep_pos = pattern.find(separator, param_name_start);
10561 if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
10562
10563 auto param_name =
10564 pattern.substr(param_name_start, sep_pos - param_name_start);
10565
10566#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10567 if (param_name_set.find(param_name) != param_name_set.cend()) {
10568 std::string msg = "Encountered path parameter '" + param_name +
10569 "' multiple times in route pattern '" + pattern + "'.";
10570 throw std::invalid_argument(msg);
10571 }
10572#endif
10573
10574 param_names_.push_back(std::move(param_name));
10575
10576 last_param_end = sep_pos + 1;
10577 }
10578
10579 if (last_param_end < pattern.length()) {
10580 static_fragments_.push_back(pattern.substr(last_param_end));
10581 }
10582}
10583
10584inline bool PathParamsMatcher::match(Request &request) const {
10585 request.matches = std::smatch();
10586 request.path_params.clear();
10587 request.path_params.reserve(param_names_.size());
10588
10589 // One past the position at which the path matched the pattern last time
10590 std::size_t starting_pos = 0;
10591 for (size_t i = 0; i < static_fragments_.size(); ++i) {
10592 const auto &fragment = static_fragments_[i];
10593
10594 if (starting_pos + fragment.length() > request.path.length()) {
10595 return false;
10596 }
10597
10598 // Avoid unnecessary allocation by using strncmp instead of substr +
10599 // comparison
10600 if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
10601 fragment.length()) != 0) {
10602 return false;
10603 }
10604
10605 starting_pos += fragment.length();
10606
10607 // Should only happen when we have a static fragment after a param
10608 // Example: '/users/:id/subscriptions'
10609 // The 'subscriptions' fragment here does not have a corresponding param
10610 if (i >= param_names_.size()) { continue; }
10611
10612 auto sep_pos = request.path.find(separator, starting_pos);
10613 if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
10614
10615 const auto &param_name = param_names_[i];
10616
10617 request.path_params.emplace(
10618 param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
10619
10620 // Mark everything up to '/' as matched
10621 starting_pos = sep_pos + 1;
10622 }
10623 // Returns false if the path is longer than the pattern
10624 return starting_pos >= request.path.length();
10625}
10626
10627inline bool RegexMatcher::match(Request &request) const {
10628 request.path_params.clear();
10629 return std::regex_match(request.path, request.matches, regex_);
10630}
10631
10632// Enclose IPv6 address in brackets if needed
10633inline std::string prepare_host_string(const std::string &host) {
10634 // Enclose IPv6 address in brackets (but not if already enclosed)
10635 if (host.find(':') == std::string::npos ||
10636 (!host.empty() && host[0] == '[')) {
10637 // IPv4, hostname, or already bracketed IPv6
10638 return host;
10639 } else {
10640 // IPv6 address without brackets
10641 return "[" + host + "]";
10642 }
10643}
10644
10645inline std::string make_host_and_port_string(const std::string &host, int port,
10646 bool is_ssl) {
10647 auto result = prepare_host_string(host);
10648
10649 // Append port if not default
10650 if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
10651 ; // do nothing
10652 } else {
10653 result += ":" + std::to_string(port);
10654 }
10655
10656 return result;
10657}
10658
10659// Create "host:port" string always including port number (for CONNECT method)
10660inline std::string
10661make_host_and_port_string_always_port(const std::string &host, int port) {
10662 return prepare_host_string(host) + ":" + std::to_string(port);
10663}
10664
10665bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out);
10666NormalizedTarget normalize_target(const std::string &host);
10667bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits);
10668bool host_matches_no_proxy(const NormalizedTarget &target,
10669 const std::vector<NoProxyEntry> &entries);
10670
10671inline bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) {
10672 if (prefix_bits < 0 || prefix_bits > 128) { return false; }
10673 if (prefix_bits == 0) { return true; }
10674 int full_bytes = prefix_bits / 8;
10675 int rem_bits = prefix_bits % 8;
10676 if (full_bytes > 0 && std::memcmp(ip.data(), net.data(),
10677 static_cast<size_t>(full_bytes)) != 0) {
10678 return false;
10679 }
10680 if (rem_bits == 0) { return true; }
10681 auto i = static_cast<size_t>(full_bytes);
10682 auto mask = static_cast<uint8_t>(0xFFu << (8 - rem_bits));
10683 return (ip[i] & mask) == (net[i] & mask);
10684}
10685
10686inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
10687 if (token.empty()) { return false; }
10688
10689 if (token == "*") {
10690 out.kind = NoProxyKind::Wildcard;
10691 return true;
10692 }
10693
10694 auto slash = token.find('/');
10695 std::string addr_part =
10696 (slash == std::string::npos) ? token : token.substr(0, slash);
10697 std::string prefix_part =
10698 (slash == std::string::npos) ? std::string() : token.substr(slash + 1);
10699
10700 // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed;
10701 // don't silently treat it as a /32 (or /128).
10702 if (slash != std::string::npos && prefix_part.empty()) { return false; }
10703
10704 // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the
10705 // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt
10706 // when brackets are present.
10707 bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' &&
10708 addr_part.back() == ']';
10709 if (bracketed) { addr_part = addr_part.substr(1, addr_part.size() - 2); }
10710
10711 if (!bracketed) {
10712 struct in_addr v4;
10713 if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
10714 int prefix = 32;
10715 if (!prefix_part.empty()) {
10716 auto r = from_chars(prefix_part.data(),
10717 prefix_part.data() + prefix_part.size(), prefix);
10718 if (r.ec != std::errc{} ||
10719 r.ptr != prefix_part.data() + prefix_part.size()) {
10720 return false;
10721 }
10722 if (prefix < 0 || prefix > 32) { return false; }
10723 }
10724 out.kind = NoProxyKind::IPv4Cidr;
10725 std::memcpy(out.net.data(), &v4, sizeof(v4));
10726 out.prefix_bits = prefix;
10727 return true;
10728 }
10729 }
10730
10731 struct in6_addr v6;
10732 if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
10733 int prefix = 128;
10734 if (!prefix_part.empty()) {
10735 auto r = from_chars(prefix_part.data(),
10736 prefix_part.data() + prefix_part.size(), prefix);
10737 if (r.ec != std::errc{} ||
10738 r.ptr != prefix_part.data() + prefix_part.size()) {
10739 return false;
10740 }
10741 if (prefix < 0 || prefix > 128) { return false; }
10742 }
10743 out.kind = NoProxyKind::IPv6Cidr;
10744 std::memcpy(out.net.data(), &v6, sizeof(v6));
10745 out.prefix_bits = prefix;
10746 return true;
10747 }
10748
10749 // Bracketed entries can only be IPv6. If the IPv6 parse above failed,
10750 // the entry is malformed — don't fall through to the hostname branch.
10751 if (bracketed) { return false; }
10752
10753 // A '/' on a non-IP token means a CIDR prefix without an address. Reject.
10754 if (slash != std::string::npos) { return false; }
10755 // Port-specific entries (host:port) are not supported.
10756 if (token.find(':') != std::string::npos) { return false; }
10757
10758 std::string hostname = case_ignore::to_lower(token);
10759 while (!hostname.empty() && hostname.front() == '.') {
10760 hostname.erase(hostname.begin());
10761 }
10762 while (!hostname.empty() && hostname.back() == '.') {
10763 hostname.pop_back();
10764 }
10765 if (hostname.empty()) { return false; }
10766
10767 out.kind = NoProxyKind::HostnameSuffix;
10768 out.hostname_pattern = std::move(hostname);
10769 return true;
10770}
10771
10772inline NormalizedTarget normalize_target(const std::string &host) {
10773 NormalizedTarget t;
10774 std::string h = host;
10775
10776 if (h.size() >= 2 && h.front() == '[' && h.back() == ']') {
10777 h = h.substr(1, h.size() - 2);
10778 }
10779
10780 // Strip a single trailing dot so "example.com." canonicalizes to
10781 // "example.com".
10782 if (!h.empty() && h.back() == '.') { h.pop_back(); }
10783
10784 t.hostname = case_ignore::to_lower(h);
10785
10786 if (!t.hostname.empty()) {
10787 struct in_addr v4;
10788 struct in6_addr v6;
10789 if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) {
10790 t.is_ipv4 = true;
10791 std::memcpy(t.ip.data(), &v4, sizeof(v4));
10792 } else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) {
10793 t.is_ipv6 = true;
10794 std::memcpy(t.ip.data(), &v6, sizeof(v6));
10795 }
10796 }
10797 return t;
10798}
10799
10800inline bool host_matches_no_proxy(const NormalizedTarget &target,
10801 const std::vector<NoProxyEntry> &entries) {
10802 if (target.hostname.empty()) { return false; }
10803 for (const auto &e : entries) {
10804 switch (e.kind) {
10805 case NoProxyKind::Wildcard: return true;
10806 case NoProxyKind::IPv4Cidr:
10807 if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10808 return true;
10809 }
10810 break;
10811 case NoProxyKind::IPv6Cidr:
10812 if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10813 return true;
10814 }
10815 break;
10816 case NoProxyKind::HostnameSuffix:
10817 if (target.is_ipv4 || target.is_ipv6) { break; }
10818 if (target.hostname == e.hostname_pattern) { return true; }
10819 // Dot-boundary suffix match: prevents "evilexample.com" from matching
10820 // an entry of "example.com".
10821 if (target.hostname.size() > e.hostname_pattern.size() + 1) {
10822 auto offset = target.hostname.size() - e.hostname_pattern.size();
10823 if (target.hostname[offset - 1] == '.' &&
10824 target.hostname.compare(offset, e.hostname_pattern.size(),
10825 e.hostname_pattern) == 0) {
10826 return true;
10827 }
10828 }
10829 break;
10830 }
10831 }
10832 return false;
10833}
10834
10835template <typename T>
10836inline bool check_and_write_headers(Stream &strm, Headers &headers,
10837 T header_writer, Error &error) {
10838 for (const auto &h : headers) {
10839 if (!detail::fields::is_field_name(h.first) ||
10840 !detail::fields::is_field_value(h.second)) {
10841 error = Error::InvalidHeaders;
10842 return false;
10843 }
10844 }
10845 if (header_writer(strm, headers) <= 0) {
10846 error = Error::Write;
10847 return false;
10848 }
10849 return true;
10850}
10851
10852} // namespace detail
10853
10854/*
10855 * Group 2 (continued): detail namespace - SSLSocketStream implementation
10856 */
10857
10858#ifdef CPPHTTPLIB_SSL_ENABLED
10859namespace detail {
10860
10861// SSL socket stream implementation
10862inline SSLSocketStream::SSLSocketStream(
10863 socket_t sock, tls::session_t session, time_t read_timeout_sec,
10864 time_t read_timeout_usec, time_t write_timeout_sec,
10865 time_t write_timeout_usec, time_t max_timeout_msec,
10866 std::chrono::time_point<std::chrono::steady_clock> start_time)
10867 : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
10868 read_timeout_usec_(read_timeout_usec),
10869 write_timeout_sec_(write_timeout_sec),
10870 write_timeout_usec_(write_timeout_usec),
10871 max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
10872#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10873 // Clear AUTO_RETRY for proper non-blocking I/O timeout handling
10874 // Note: create_session() also clears this, but SSLClient currently
10875 // uses ssl_new() which does not. Until full TLS API migration is complete,
10876 // we need to ensure AUTO_RETRY is cleared here regardless of how the
10877 // SSL session was created.
10878 SSL_clear_mode(static_cast<SSL *>(session), SSL_MODE_AUTO_RETRY);
10879#endif
10880}
10881
10882inline SSLSocketStream::~SSLSocketStream() = default;
10883
10884inline bool SSLSocketStream::is_readable() const {
10885 return tls::pending(session_) > 0;
10886}
10887
10888inline bool SSLSocketStream::wait_readable() const {
10889 if (max_timeout_msec_ <= 0) {
10890 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10891 }
10892
10893 time_t read_timeout_sec;
10894 time_t read_timeout_usec;
10895 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10896 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10897
10898 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10899}
10900
10901inline bool SSLSocketStream::wait_writable() const {
10902 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
10903 !tls::is_peer_closed(session_, sock_);
10904}
10905
10906inline bool SSLSocketStream::is_peer_alive() const {
10907 return !tls::is_peer_closed(session_, sock_);
10908}
10909
10910inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
10911 if (tls::pending(session_) > 0) {
10912 tls::TlsError err;
10913 auto ret = tls::read(session_, ptr, size, err);
10914 if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10915 error_ = Error::ConnectionClosed;
10916 }
10917 return ret;
10918 } else if (wait_readable()) {
10919 tls::TlsError err;
10920 auto ret = tls::read(session_, ptr, size, err);
10921 if (ret < 0) {
10922 auto n = 1000;
10923#ifdef _WIN32
10924 while (--n >= 0 && (err.code == tls::ErrorCode::WantRead ||
10925 (err.code == tls::ErrorCode::SyscallError &&
10926 WSAGetLastError() == WSAETIMEDOUT))) {
10927#else
10928 while (--n >= 0 && err.code == tls::ErrorCode::WantRead) {
10929#endif
10930 if (tls::pending(session_) > 0) {
10931 return tls::read(session_, ptr, size, err);
10932 } else if (wait_readable()) {
10933 std::this_thread::sleep_for(std::chrono::microseconds{10});
10934 ret = tls::read(session_, ptr, size, err);
10935 if (ret >= 0) { return ret; }
10936 } else {
10937 break;
10938 }
10939 }
10940 assert(ret < 0);
10941 } else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10942 error_ = Error::ConnectionClosed;
10943 }
10944 return ret;
10945 } else {
10946 error_ = Error::Timeout;
10947 return -1;
10948 }
10949}
10950
10951inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
10952 if (wait_writable()) {
10953 auto handle_size =
10954 std::min<size_t>(size, (std::numeric_limits<int>::max)());
10955
10956 tls::TlsError err;
10957 auto ret = tls::write(session_, ptr, handle_size, err);
10958 if (ret < 0) {
10959 auto n = 1000;
10960#ifdef _WIN32
10961 while (--n >= 0 && (err.code == tls::ErrorCode::WantWrite ||
10962 (err.code == tls::ErrorCode::SyscallError &&
10963 WSAGetLastError() == WSAETIMEDOUT))) {
10964#else
10965 while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) {
10966#endif
10967 if (wait_writable()) {
10968 std::this_thread::sleep_for(std::chrono::microseconds{10});
10969 ret = tls::write(session_, ptr, handle_size, err);
10970 if (ret >= 0) { return ret; }
10971 } else {
10972 break;
10973 }
10974 }
10975 assert(ret < 0);
10976 }
10977 return ret;
10978 }
10979 return -1;
10980}
10981
10982inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
10983 int &port) const {
10984 detail::get_remote_ip_and_port(sock_, ip, port);
10985}
10986
10987inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
10988 int &port) const {
10989 detail::get_local_ip_and_port(sock_, ip, port);
10990}
10991
10992inline socket_t SSLSocketStream::socket() const { return sock_; }
10993
10994inline time_t SSLSocketStream::duration() const {
10995 return std::chrono::duration_cast<std::chrono::milliseconds>(
10996 std::chrono::steady_clock::now() - start_time_)
10997 .count();
10998}
10999
11000inline void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) {
11001 read_timeout_sec_ = sec;
11002 read_timeout_usec_ = usec;
11003}
11004
11005} // namespace detail
11006#endif // CPPHTTPLIB_SSL_ENABLED
11007
11008/*
11009 * Group 4: Server implementation
11010 */
11011
11012// HTTP server implementation
11013inline Server::Server()
11014 : new_task_queue([] {
11015 return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT,
11017 }) {
11018#ifndef _WIN32
11019 signal(SIGPIPE, SIG_IGN);
11020#endif
11021}
11022
11023inline Server::~Server() = default;
11024
11025inline std::unique_ptr<detail::MatcherBase>
11026Server::make_matcher(const std::string &pattern) {
11027 if (pattern.find("/:") != std::string::npos) {
11028 return detail::make_unique<detail::PathParamsMatcher>(pattern);
11029 } else {
11030 return detail::make_unique<detail::RegexMatcher>(pattern);
11031 }
11032}
11033
11034inline Server &Server::Get(const std::string &pattern, Handler handler) {
11035 return add_handler(get_handlers_, pattern, std::move(handler));
11036}
11037
11038inline Server &Server::Post(const std::string &pattern, Handler handler) {
11039 return add_handler(post_handlers_, pattern, std::move(handler));
11040}
11041
11042inline Server &Server::Post(const std::string &pattern,
11043 HandlerWithContentReader handler) {
11044 return add_handler(post_handlers_for_content_reader_, pattern,
11045 std::move(handler));
11046}
11047
11048inline Server &Server::Put(const std::string &pattern, Handler handler) {
11049 return add_handler(put_handlers_, pattern, std::move(handler));
11050}
11051
11052inline Server &Server::Put(const std::string &pattern,
11053 HandlerWithContentReader handler) {
11054 return add_handler(put_handlers_for_content_reader_, pattern,
11055 std::move(handler));
11056}
11057
11058inline Server &Server::Patch(const std::string &pattern, Handler handler) {
11059 return add_handler(patch_handlers_, pattern, std::move(handler));
11060}
11061
11062inline Server &Server::Patch(const std::string &pattern,
11063 HandlerWithContentReader handler) {
11064 return add_handler(patch_handlers_for_content_reader_, pattern,
11065 std::move(handler));
11066}
11067
11068inline Server &Server::Delete(const std::string &pattern, Handler handler) {
11069 return add_handler(delete_handlers_, pattern, std::move(handler));
11070}
11071
11072inline Server &Server::Delete(const std::string &pattern,
11073 HandlerWithContentReader handler) {
11074 return add_handler(delete_handlers_for_content_reader_, pattern,
11075 std::move(handler));
11076}
11077
11078inline Server &Server::Options(const std::string &pattern, Handler handler) {
11079 return add_handler(options_handlers_, pattern, std::move(handler));
11080}
11081
11082inline Server &Server::WebSocket(const std::string &pattern,
11083 WebSocketHandler handler) {
11084 websocket_handlers_.push_back(
11085 {make_matcher(pattern), std::move(handler), nullptr});
11086 return *this;
11087}
11088
11089inline Server &Server::WebSocket(const std::string &pattern,
11090 WebSocketHandler handler,
11091 SubProtocolSelector sub_protocol_selector) {
11092 websocket_handlers_.push_back({make_matcher(pattern), std::move(handler),
11093 std::move(sub_protocol_selector)});
11094 return *this;
11095}
11096
11097inline bool Server::set_base_dir(const std::string &dir,
11098 const std::string &mount_point) {
11099 return set_mount_point(mount_point, dir);
11100}
11101
11102inline bool Server::set_mount_point(const std::string &mount_point,
11103 const std::string &dir, Headers headers) {
11104 detail::FileStat stat(dir);
11105 if (stat.is_dir()) {
11106 std::string mnt = !mount_point.empty() ? mount_point : "/";
11107 if (!mnt.empty() && mnt[0] == '/') {
11108 std::string resolved_base;
11109 if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
11110#if defined(_WIN32)
11111 if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
11112 resolved_base += '\\';
11113 }
11114#else
11115 if (resolved_base.back() != '/') { resolved_base += '/'; }
11116#endif
11117 }
11118 base_dirs_.push_back(
11119 {std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
11120 return true;
11121 }
11122 }
11123 return false;
11124}
11125
11126inline bool Server::remove_mount_point(const std::string &mount_point) {
11127 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
11128 if (it->mount_point == mount_point) {
11129 base_dirs_.erase(it);
11130 return true;
11131 }
11132 }
11133 return false;
11134}
11135
11136inline Server &
11137Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
11138 const std::string &mime) {
11139 file_extension_and_mimetype_map_[ext] = mime;
11140 return *this;
11141}
11142
11143inline Server &Server::set_default_file_mimetype(const std::string &mime) {
11144 default_file_mimetype_ = mime;
11145 return *this;
11146}
11147
11148inline Server &Server::set_file_request_handler(Handler handler) {
11149 file_request_handler_ = std::move(handler);
11150 return *this;
11151}
11152
11153inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
11154 std::true_type) {
11155 error_handler_ = std::move(handler);
11156 return *this;
11157}
11158
11159inline Server &Server::set_error_handler_core(Handler handler,
11160 std::false_type) {
11161 error_handler_ = [handler](const Request &req, Response &res) {
11162 handler(req, res);
11163 return HandlerResponse::Handled;
11164 };
11165 return *this;
11166}
11167
11168inline Server &Server::set_exception_handler(ExceptionHandler handler) {
11169 exception_handler_ = std::move(handler);
11170 return *this;
11171}
11172
11173inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
11174 pre_routing_handler_ = std::move(handler);
11175 return *this;
11176}
11177
11178inline Server &Server::set_post_routing_handler(Handler handler) {
11179 post_routing_handler_ = std::move(handler);
11180 return *this;
11181}
11182
11183inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
11184 pre_request_handler_ = std::move(handler);
11185 return *this;
11186}
11187
11188inline Server &Server::set_logger(Logger logger) {
11189 logger_ = std::move(logger);
11190 return *this;
11191}
11192
11193inline Server &Server::set_error_logger(ErrorLogger error_logger) {
11194 error_logger_ = std::move(error_logger);
11195 return *this;
11196}
11197
11198inline Server &Server::set_pre_compression_logger(Logger logger) {
11199 pre_compression_logger_ = std::move(logger);
11200 return *this;
11201}
11202
11203inline Server &
11204Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
11205 expect_100_continue_handler_ = std::move(handler);
11206 return *this;
11207}
11208
11209inline Server &Server::set_start_handler(StartHandler handler) {
11210 start_handler_ = std::move(handler);
11211 return *this;
11212}
11213
11214inline Server &Server::set_address_family(int family) {
11215 address_family_ = family;
11216 return *this;
11217}
11218
11219inline Server &Server::set_tcp_nodelay(bool on) {
11220 tcp_nodelay_ = on;
11221 return *this;
11222}
11223
11224inline Server &Server::set_ipv6_v6only(bool on) {
11225 ipv6_v6only_ = on;
11226 return *this;
11227}
11228
11229inline Server &Server::set_socket_options(SocketOptions socket_options) {
11230 socket_options_ = std::move(socket_options);
11231 return *this;
11232}
11233
11234inline Server &Server::set_default_headers(Headers headers) {
11235 default_headers_ = std::move(headers);
11236 return *this;
11237}
11238
11239inline Server &Server::set_header_writer(
11240 std::function<ssize_t(Stream &, Headers &)> const &writer) {
11241 header_writer_ = writer;
11242 return *this;
11243}
11244
11245inline Server &
11246Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
11247 trusted_proxies_ = proxies;
11248 return *this;
11249}
11250
11251inline Server &Server::set_keep_alive_max_count(size_t count) {
11252 keep_alive_max_count_ = count;
11253 return *this;
11254}
11255
11256inline Server &Server::set_keep_alive_timeout(time_t sec) {
11257 keep_alive_timeout_sec_ = sec;
11258 return *this;
11259}
11260
11261template <class Rep, class Period>
11262inline Server &Server::set_keep_alive_timeout(
11263 const std::chrono::duration<Rep, Period> &duration) {
11264 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11265 set_keep_alive_timeout(sec);
11266 });
11267 return *this;
11268}
11269
11270inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
11271 read_timeout_sec_ = sec;
11272 read_timeout_usec_ = usec;
11273 return *this;
11274}
11275
11276inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
11277 write_timeout_sec_ = sec;
11278 write_timeout_usec_ = usec;
11279 return *this;
11280}
11281
11282inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
11283 idle_interval_sec_ = sec;
11284 idle_interval_usec_ = usec;
11285 return *this;
11286}
11287
11288inline Server &Server::set_payload_max_length(size_t length) {
11289 payload_max_length_ = length;
11290 return *this;
11291}
11292
11293inline Server &Server::set_websocket_max_missed_pongs(int count) {
11294 websocket_max_missed_pongs_ = count;
11295 return *this;
11296}
11297
11298inline Server &Server::set_websocket_ping_interval(time_t sec) {
11299 websocket_ping_interval_sec_ = sec;
11300 return *this;
11301}
11302
11303template <class Rep, class Period>
11304inline Server &Server::set_websocket_ping_interval(
11305 const std::chrono::duration<Rep, Period> &duration) {
11306 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11307 set_websocket_ping_interval(sec);
11308 });
11309 return *this;
11310}
11311
11312inline bool Server::bind_to_port(const std::string &host, int port,
11313 int socket_flags) {
11314 auto ret = bind_internal(host, port, socket_flags);
11315 if (ret == -1) { is_decommissioned = true; }
11316 return ret >= 0;
11317}
11318inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
11319 auto ret = bind_internal(host, 0, socket_flags);
11320 if (ret == -1) { is_decommissioned = true; }
11321 return ret;
11322}
11323
11324inline bool Server::listen_after_bind() { return listen_internal(); }
11325
11326inline bool Server::listen(const std::string &host, int port,
11327 int socket_flags) {
11328 return bind_to_port(host, port, socket_flags) && listen_internal();
11329}
11330
11331inline bool Server::is_running() const { return is_running_; }
11332
11333inline void Server::wait_until_ready() const {
11334 while (!is_running_ && !is_decommissioned) {
11335 std::this_thread::sleep_for(std::chrono::milliseconds{1});
11336 }
11337}
11338
11339inline void Server::stop() noexcept {
11340 if (is_running_) {
11341 assert(svr_sock_ != INVALID_SOCKET);
11342 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
11343 detail::shutdown_socket(sock);
11344 detail::close_socket(sock);
11345 }
11346 is_decommissioned = false;
11347}
11348
11349inline void Server::decommission() { is_decommissioned = true; }
11350
11351inline bool Server::parse_request_line(const char *s, Request &req) const {
11352 auto len = strlen(s);
11353 if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
11354 len -= 2;
11355
11356 {
11357 size_t count = 0;
11358
11359 detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
11360 switch (count) {
11361 case 0: req.method = std::string(b, e); break;
11362 case 1: req.target = std::string(b, e); break;
11363 case 2: req.version = std::string(b, e); break;
11364 default: break;
11365 }
11366 count++;
11367 });
11368
11369 if (count != 3) { return false; }
11370 }
11371
11372 thread_local const std::set<std::string> methods{
11373 "GET", "HEAD", "POST", "PUT", "DELETE",
11374 "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
11375
11376 if (methods.find(req.method) == methods.end()) {
11377 output_error_log(Error::InvalidHTTPMethod, &req);
11378 return false;
11379 }
11380
11381 if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
11382 output_error_log(Error::InvalidHTTPVersion, &req);
11383 return false;
11384 }
11385
11386 {
11387 // Skip URL fragment
11388 for (size_t i = 0; i < req.target.size(); i++) {
11389 if (req.target[i] == '#') {
11390 req.target.erase(i);
11391 break;
11392 }
11393 }
11394
11395 detail::divide(req.target, '?',
11396 [&](const char *lhs_data, std::size_t lhs_size,
11397 const char *rhs_data, std::size_t rhs_size) {
11398 req.path =
11399 decode_path_component(std::string(lhs_data, lhs_size));
11400 detail::parse_query_text(rhs_data, rhs_size, req.params);
11401 });
11402 }
11403
11404 return true;
11405}
11406
11407inline bool Server::write_response(Stream &strm, bool close_connection,
11408 Request &req, Response &res) {
11409 // NOTE: `req.ranges` should be empty, otherwise it will be applied
11410 // incorrectly to the error content.
11411 req.ranges.clear();
11412 return write_response_core(strm, close_connection, req, res, false);
11413}
11414
11415inline bool Server::write_response_with_content(Stream &strm,
11416 bool close_connection,
11417 const Request &req,
11418 Response &res) {
11419 return write_response_core(strm, close_connection, req, res, true);
11420}
11421
11422inline bool Server::write_response_core(Stream &strm, bool close_connection,
11423 const Request &req, Response &res,
11424 bool need_apply_ranges) {
11425 assert(res.status != -1);
11426
11427 if (400 <= res.status && error_handler_ &&
11428 error_handler_(req, res) == HandlerResponse::Handled) {
11429 need_apply_ranges = true;
11430 }
11431
11432 std::string content_type;
11433 std::string boundary;
11434 if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
11435
11436 // Prepare additional headers
11437 if (close_connection || req.get_header_value("Connection") == "close" ||
11438 400 <= res.status) { // Don't leave connections open after errors
11439 res.set_header("Connection", "close");
11440 } else {
11441 std::string s = "timeout=";
11442 s += std::to_string(keep_alive_timeout_sec_);
11443 s += ", max=";
11444 s += std::to_string(keep_alive_max_count_);
11445 res.set_header("Keep-Alive", s);
11446 }
11447
11448 if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
11449 !res.has_header("Content-Type")) {
11450 res.set_header("Content-Type", "text/plain");
11451 }
11452
11453 if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
11454 !res.has_header("Content-Length")) {
11455 res.set_header("Content-Length", "0");
11456 }
11457
11458 if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
11459 res.set_header("Accept-Ranges", "bytes");
11460 }
11461
11462 if (post_routing_handler_) { post_routing_handler_(req, res); }
11463
11464 // Response line and headers
11465 detail::BufferStream bstrm;
11466 if (!detail::write_response_line(bstrm, res.status)) { return false; }
11467 if (header_writer_(bstrm, res.headers) <= 0) { return false; }
11468
11469 // Combine small body with headers to reduce write syscalls
11470 if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
11471 bstrm.write(res.body.data(), res.body.size());
11472 }
11473
11474 // Log before writing to avoid race condition with client-side code that
11475 // accesses logger-captured data immediately after receiving the response.
11476 output_log(req, res);
11477
11478 // Flush buffer
11479 auto &data = bstrm.get_buffer();
11480 if (!detail::write_data(strm, data.data(), data.size())) { return false; }
11481
11482 // Streaming body
11483 auto ret = true;
11484 if (req.method != "HEAD" && res.content_provider_) {
11485 if (write_content_with_provider(strm, req, res, boundary, content_type)) {
11486 res.content_provider_success_ = true;
11487 } else {
11488 ret = false;
11489 }
11490 }
11491
11492 return ret;
11493}
11494
11495inline bool
11496Server::write_content_with_provider(Stream &strm, const Request &req,
11497 Response &res, const std::string &boundary,
11498 const std::string &content_type) {
11499 auto is_shutting_down = [this]() {
11500 return this->svr_sock_ == INVALID_SOCKET;
11501 };
11502
11503 if (res.content_length_ > 0) {
11504 if (req.ranges.empty()) {
11505 return detail::write_content(strm, res.content_provider_, 0,
11506 res.content_length_, is_shutting_down);
11507 } else if (req.ranges.size() == 1) {
11508 auto offset_and_length = detail::get_range_offset_and_length(
11509 req.ranges[0], res.content_length_);
11510
11511 return detail::write_content(strm, res.content_provider_,
11512 offset_and_length.first,
11513 offset_and_length.second, is_shutting_down);
11514 } else {
11515 return detail::write_multipart_ranges_data(
11516 strm, req, res, boundary, content_type, res.content_length_,
11517 is_shutting_down);
11518 }
11519 } else {
11520 if (res.is_chunked_content_provider_) {
11521 auto type = detail::encoding_type(req, res);
11522
11523 auto compressor = detail::make_compressor(type);
11524 if (!compressor) {
11525 compressor = detail::make_unique<detail::nocompressor>();
11526 }
11527
11528 return detail::write_content_chunked(strm, res.content_provider_,
11529 is_shutting_down, *compressor);
11530 } else {
11531 return detail::write_content_without_length(strm, res.content_provider_,
11532 is_shutting_down);
11533 }
11534 }
11535}
11536
11537inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
11538 FormFields::iterator cur_field;
11539 FormFiles::iterator cur_file;
11540 auto is_text_field = false;
11541 size_t count = 0;
11542 if (read_content_core(
11543 strm, req, res,
11544 // Regular
11545 [&](const char *buf, size_t n) {
11546 // Prevent arithmetic overflow when checking sizes.
11547 // Avoid computing (req.body.size() + n) directly because
11548 // adding two unsigned `size_t` values can wrap around and
11549 // produce a small result instead of indicating overflow.
11550 // Instead, check using subtraction: ensure `n` does not
11551 // exceed the remaining capacity `max_size() - size()`.
11552 if (req.body.size() >= req.body.max_size() ||
11553 n > req.body.max_size() - req.body.size()) {
11554 return false;
11555 }
11556
11557 // Limit decompressed body size to payload_max_length_ to protect
11558 // against "zip bomb" attacks where a small compressed payload
11559 // decompresses to a massive size.
11560 if (payload_max_length_ > 0 &&
11561 (req.body.size() >= payload_max_length_ ||
11562 n > payload_max_length_ - req.body.size())) {
11563 return false;
11564 }
11565
11566 req.body.append(buf, n);
11567 return true;
11568 },
11569 // Multipart FormData
11570 [&](const FormData &file) {
11572 output_error_log(Error::TooManyFormDataFiles, &req);
11573 return false;
11574 }
11575
11576 if (file.filename.empty()) {
11577 cur_field = req.form.fields.emplace(
11578 file.name, FormField{file.name, file.content, file.headers});
11579 is_text_field = true;
11580 } else {
11581 cur_file = req.form.files.emplace(file.name, file);
11582 is_text_field = false;
11583 }
11584 return true;
11585 },
11586 [&](const char *buf, size_t n) {
11587 if (is_text_field) {
11588 auto &content = cur_field->second.content;
11589 if (content.size() + n > content.max_size()) { return false; }
11590 content.append(buf, n);
11591 } else {
11592 auto &content = cur_file->second.content;
11593 if (content.size() + n > content.max_size()) { return false; }
11594 content.append(buf, n);
11595 }
11596 return true;
11597 })) {
11598 const auto &content_type = req.get_header_value("Content-Type");
11599 if (detail::extract_media_type(content_type) ==
11600 "application/x-www-form-urlencoded") {
11601 if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
11602 res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
11603 output_error_log(Error::ExceedMaxPayloadSize, &req);
11604 return false;
11605 }
11606 detail::parse_query_text(req.body, req.params);
11607 }
11608 return true;
11609 }
11610 return false;
11611}
11612
11613inline bool Server::read_content_with_content_receiver(
11614 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11615 FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
11616 return read_content_core(strm, req, res, std::move(receiver),
11617 std::move(multipart_header),
11618 std::move(multipart_receiver));
11619}
11620
11621inline bool Server::read_content_core(
11622 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11623 FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
11624 detail::FormDataParser multipart_form_data_parser;
11625 ContentReceiverWithProgress out;
11626
11627 if (req.is_multipart_form_data()) {
11628 const auto &content_type = req.get_header_value("Content-Type");
11629 std::string boundary;
11630 if (!detail::parse_multipart_boundary(content_type, boundary)) {
11631 res.status = StatusCode::BadRequest_400;
11632 output_error_log(Error::MultipartParsing, &req);
11633 return false;
11634 }
11635
11636 multipart_form_data_parser.set_boundary(std::move(boundary));
11637 out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
11638 return multipart_form_data_parser.parse(buf, n, multipart_header,
11639 multipart_receiver);
11640 };
11641 } else {
11642 out = [receiver](const char *buf, size_t n, size_t /*off*/,
11643 size_t /*len*/) { return receiver(buf, n); };
11644 }
11645
11646 // RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body.
11647 // For non-SSL builds we still scan non-persistent connections for stray
11648 // body bytes so the payload limit is enforced (413). On keep-alive,
11649 // pending bytes may be the next request (issue #2450), so skip.
11650#if !defined(CPPHTTPLIB_SSL_ENABLED)
11651 if (!req.has_header("Content-Length") &&
11652 !detail::is_chunked_transfer_encoding(req.headers)) {
11653 if (!detail::is_connection_persistent(req) && payload_max_length_ > 0 &&
11654 payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
11655 auto has_data = strm.is_readable();
11656 if (!has_data) {
11657 auto s = strm.socket();
11658 if (s != INVALID_SOCKET) {
11659 has_data = detail::select_read(s, 0, 0) > 0;
11660 }
11661 }
11662 if (has_data) {
11663 auto result =
11664 detail::read_content_without_length(strm, payload_max_length_, out);
11665 if (result == detail::ReadContentResult::PayloadTooLarge) {
11666 res.status = StatusCode::PayloadTooLarge_413;
11667 return false;
11668 } else if (result != detail::ReadContentResult::Success) {
11669 return false;
11670 }
11671 return true;
11672 }
11673 }
11674 return true;
11675 }
11676#else
11677 if (!req.has_header("Content-Length") &&
11678 !detail::is_chunked_transfer_encoding(req.headers)) {
11679 return true;
11680 }
11681#endif
11682
11683 if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
11684 out, true)) {
11685 return false;
11686 }
11687
11688 req.body_consumed_ = true;
11689
11690 if (req.is_multipart_form_data()) {
11691 if (!multipart_form_data_parser.is_valid()) {
11692 res.status = StatusCode::BadRequest_400;
11693 output_error_log(Error::MultipartParsing, &req);
11694 return false;
11695 }
11696 }
11697
11698 return true;
11699}
11700
11701inline bool Server::handle_file_request(Request &req, Response &res) {
11702 for (const auto &entry : base_dirs_) {
11703 // Prefix match
11704 if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
11705 std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
11706 if (detail::is_valid_path(sub_path)) {
11707 auto path = entry.base_dir + sub_path;
11708 if (path.back() == '/') { path += "index.html"; }
11709
11710 // Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
11711 // but symlinks/junctions can still escape the base directory.
11712 if (!entry.resolved_base_dir.empty()) {
11713 std::string resolved_path;
11714 if (detail::canonicalize_path(path.c_str(), resolved_path) &&
11715 !detail::is_path_within_base(resolved_path,
11716 entry.resolved_base_dir)) {
11717 res.status = StatusCode::Forbidden_403;
11718 return true;
11719 }
11720 }
11721
11722 detail::FileStat stat(path);
11723
11724 if (stat.is_dir()) {
11725 res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
11726 return true;
11727 }
11728
11729 if (stat.is_file()) {
11730 for (const auto &kv : entry.headers) {
11731 res.set_header(kv.first, kv.second);
11732 }
11733
11734 auto etag = detail::compute_etag(stat);
11735 if (!etag.empty()) { res.set_header("ETag", etag); }
11736
11737 auto mtime = stat.mtime();
11738
11739 auto last_modified = detail::file_mtime_to_http_date(mtime);
11740 if (!last_modified.empty()) {
11741 res.set_header("Last-Modified", last_modified);
11742 }
11743
11744 if (check_if_not_modified(req, res, etag, mtime)) { return true; }
11745
11746 check_if_range(req, etag, mtime);
11747
11748 auto mm = std::make_shared<detail::mmap>(path.c_str());
11749 if (!mm->is_open()) {
11750 output_error_log(Error::OpenFile, &req);
11751 return false;
11752 }
11753
11754 res.set_content_provider(
11755 mm->size(),
11756 detail::find_content_type(path, file_extension_and_mimetype_map_,
11757 default_file_mimetype_),
11758 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
11759 sink.write(mm->data() + offset, length);
11760 return true;
11761 });
11762
11763 if (req.method != "HEAD" && file_request_handler_) {
11764 file_request_handler_(req, res);
11765 }
11766
11767 return true;
11768 } else {
11769 output_error_log(Error::OpenFile, &req);
11770 }
11771 }
11772 }
11773 }
11774 return false;
11775}
11776
11777inline bool Server::check_if_not_modified(const Request &req, Response &res,
11778 const std::string &etag,
11779 time_t mtime) const {
11780 // Handle conditional GET:
11781 // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
11782 // 2. If-Modified-Since is checked only when If-None-Match is absent
11783 if (req.has_header("If-None-Match")) {
11784 if (!etag.empty()) {
11785 auto val = req.get_header_value("If-None-Match");
11786
11787 // NOTE: We use exact string matching here. This works correctly
11788 // because our server always generates weak ETags (W/"..."), and
11789 // clients typically send back the same ETag they received.
11790 // RFC 9110 Section 8.8.3.2 allows weak comparison for
11791 // If-None-Match, where W/"x" and "x" would match, but this
11792 // simplified implementation requires exact matches.
11793 auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
11794 [&](const char *b, const char *e) {
11795 auto seg_len = static_cast<size_t>(e - b);
11796 return (seg_len == 1 && *b == '*') ||
11797 (seg_len == etag.size() &&
11798 std::equal(b, e, etag.begin()));
11799 });
11800
11801 if (ret) {
11802 res.status = StatusCode::NotModified_304;
11803 return true;
11804 }
11805 }
11806 } else if (req.has_header("If-Modified-Since")) {
11807 auto val = req.get_header_value("If-Modified-Since");
11808 auto t = detail::parse_http_date(val);
11809
11810 if (t != static_cast<time_t>(-1) && mtime <= t) {
11811 res.status = StatusCode::NotModified_304;
11812 return true;
11813 }
11814 }
11815 return false;
11816}
11817
11818inline bool Server::check_if_range(Request &req, const std::string &etag,
11819 time_t mtime) const {
11820 // Handle If-Range for partial content requests (RFC 9110
11821 // Section 13.1.5). If-Range is only evaluated when Range header is
11822 // present. If the validator matches, serve partial content; otherwise
11823 // serve full content.
11824 if (!req.ranges.empty() && req.has_header("If-Range")) {
11825 auto val = req.get_header_value("If-Range");
11826
11827 auto is_valid_range = [&]() {
11828 if (detail::is_strong_etag(val)) {
11829 // RFC 9110 Section 13.1.5: If-Range requires strong ETag
11830 // comparison.
11831 return (!etag.empty() && val == etag);
11832 } else if (detail::is_weak_etag(val)) {
11833 // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
11834 return false;
11835 } else {
11836 // HTTP-date comparison
11837 auto t = detail::parse_http_date(val);
11838 return (t != static_cast<time_t>(-1) && mtime <= t);
11839 }
11840 };
11841
11842 if (!is_valid_range()) {
11843 // Validator doesn't match: ignore Range and serve full content
11844 req.ranges.clear();
11845 return false;
11846 }
11847 }
11848
11849 return true;
11850}
11851
11852inline socket_t
11853Server::create_server_socket(const std::string &host, int port,
11854 int socket_flags,
11855 SocketOptions socket_options) const {
11856 return detail::create_socket(
11857 host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
11858 ipv6_v6only_, std::move(socket_options),
11859 [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
11860 if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
11861 output_error_log(Error::BindIPAddress, nullptr);
11862 return false;
11863 }
11864 if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) {
11865 output_error_log(Error::Listen, nullptr);
11866 return false;
11867 }
11868 return true;
11869 });
11870}
11871
11872inline int Server::bind_internal(const std::string &host, int port,
11873 int socket_flags) {
11874 if (is_decommissioned) { return -1; }
11875
11876 if (!is_valid()) { return -1; }
11877
11878 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
11879 if (svr_sock_ == INVALID_SOCKET) { return -1; }
11880
11881 if (port == 0) {
11882 struct sockaddr_storage addr;
11883 socklen_t addr_len = sizeof(addr);
11884 if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
11885 &addr_len) == -1) {
11886 output_error_log(Error::GetSockName, nullptr);
11887 return -1;
11888 }
11889 if (addr.ss_family == AF_INET) {
11890 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
11891 } else if (addr.ss_family == AF_INET6) {
11892 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
11893 } else {
11894 output_error_log(Error::UnsupportedAddressFamily, nullptr);
11895 return -1;
11896 }
11897 } else {
11898 return port;
11899 }
11900}
11901
11902inline bool Server::listen_internal() {
11903 if (is_decommissioned) { return false; }
11904
11905 auto ret = true;
11906 is_running_ = true;
11907 auto se = detail::scope_exit([&]() { is_running_ = false; });
11908
11909 if (start_handler_) { start_handler_(); }
11910
11911 {
11912 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
11913
11914 while (svr_sock_ != INVALID_SOCKET) {
11915#ifndef _WIN32
11916 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
11917#endif
11918 auto val = detail::select_read(svr_sock_, idle_interval_sec_,
11919 idle_interval_usec_);
11920 if (val == 0) { // Timeout
11921 task_queue->on_idle();
11922 continue;
11923 }
11924#ifndef _WIN32
11925 }
11926#endif
11927
11928#if defined _WIN32
11929 // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
11930 // OVERLAPPED
11931 socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
11932#elif defined SOCK_CLOEXEC
11933 socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
11934#else
11935 socket_t sock = accept(svr_sock_, nullptr, nullptr);
11936#endif
11937
11938 if (sock == INVALID_SOCKET) {
11939 if (errno == EMFILE) {
11940 // The per-process limit of open file descriptors has been reached.
11941 // Try to accept new connections after a short sleep.
11942 std::this_thread::sleep_for(std::chrono::microseconds{1});
11943 continue;
11944 } else if (errno == EINTR || errno == EAGAIN) {
11945 continue;
11946 }
11947 if (svr_sock_ != INVALID_SOCKET) {
11948 detail::close_socket(svr_sock_);
11949 ret = false;
11950 output_error_log(Error::Connection, nullptr);
11951 } else {
11952 ; // The server socket was closed by user.
11953 }
11954 break;
11955 }
11956
11957 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
11958 read_timeout_sec_, read_timeout_usec_);
11959 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
11960 write_timeout_sec_, write_timeout_usec_);
11961
11962 if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
11963
11964 if (!task_queue->enqueue(
11965 [this, sock]() { process_and_close_socket(sock); })) {
11966 output_error_log(Error::ResourceExhaustion, nullptr);
11967 detail::shutdown_socket(sock);
11968 detail::close_socket(sock);
11969 }
11970 }
11971
11972 task_queue->shutdown();
11973 }
11974
11975 is_decommissioned = !ret;
11976 return ret;
11977}
11978
11979inline bool Server::routing(Request &req, Response &res, Stream &strm) {
11980 if (pre_routing_handler_ &&
11981 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
11982 return true;
11983 }
11984
11985 // File handler
11986 if ((req.method == "GET" || req.method == "HEAD") &&
11987 handle_file_request(req, res)) {
11988 return true;
11989 }
11990
11991 if (detail::expect_content(req)) {
11992 // Content reader handler
11993 {
11994 // Track whether the ContentReader was aborted due to the decompressed
11995 // payload exceeding `payload_max_length_`.
11996 // The user handler runs after the lambda returns, so we must restore the
11997 // 413 status if the handler overwrites it.
11998 bool content_reader_payload_too_large = false;
11999
12000 ContentReader reader(
12001 [&](ContentReceiver receiver) {
12002 auto result = read_content_with_content_receiver(
12003 strm, req, res, std::move(receiver), nullptr, nullptr);
12004 if (!result) {
12005 output_error_log(Error::Read, &req);
12006 if (res.status == StatusCode::PayloadTooLarge_413) {
12007 content_reader_payload_too_large = true;
12008 }
12009 }
12010 return result;
12011 },
12012 [&](FormDataHeader header, ContentReceiver receiver) {
12013 auto result = read_content_with_content_receiver(
12014 strm, req, res, nullptr, std::move(header),
12015 std::move(receiver));
12016 if (!result) {
12017 output_error_log(Error::Read, &req);
12018 if (res.status == StatusCode::PayloadTooLarge_413) {
12019 content_reader_payload_too_large = true;
12020 }
12021 }
12022 return result;
12023 });
12024
12025 bool dispatched = false;
12026 if (req.method == "POST") {
12027 dispatched = dispatch_request_for_content_reader(
12028 req, res, std::move(reader), post_handlers_for_content_reader_);
12029 } else if (req.method == "PUT") {
12030 dispatched = dispatch_request_for_content_reader(
12031 req, res, std::move(reader), put_handlers_for_content_reader_);
12032 } else if (req.method == "PATCH") {
12033 dispatched = dispatch_request_for_content_reader(
12034 req, res, std::move(reader), patch_handlers_for_content_reader_);
12035 } else if (req.method == "DELETE") {
12036 dispatched = dispatch_request_for_content_reader(
12037 req, res, std::move(reader), delete_handlers_for_content_reader_);
12038 }
12039
12040 if (dispatched) {
12041 if (content_reader_payload_too_large) {
12042 // Enforce the limit: override any status the handler may have set
12043 // and return false so the error path sends a plain 413 response.
12044 res.status = StatusCode::PayloadTooLarge_413;
12045 res.body.clear();
12046 res.content_length_ = 0;
12047 res.content_provider_ = nullptr;
12048 return false;
12049 }
12050 return true;
12051 }
12052 }
12053
12054 // NOTE: `req.body` is not read here. For a regular handler the body is
12055 // read inside dispatch_request(), after the route has matched and the
12056 // pre-request handler has approved the request, so that a rejected
12057 // request (e.g. failed authentication) never forces us to buffer a
12058 // potentially large body.
12059 }
12060
12061 // Regular handler
12062 if (req.method == "GET" || req.method == "HEAD") {
12063 return dispatch_request(req, res, get_handlers_, strm);
12064 } else if (req.method == "POST") {
12065 return dispatch_request(req, res, post_handlers_, strm);
12066 } else if (req.method == "PUT") {
12067 return dispatch_request(req, res, put_handlers_, strm);
12068 } else if (req.method == "DELETE") {
12069 return dispatch_request(req, res, delete_handlers_, strm);
12070 } else if (req.method == "OPTIONS") {
12071 return dispatch_request(req, res, options_handlers_, strm);
12072 } else if (req.method == "PATCH") {
12073 return dispatch_request(req, res, patch_handlers_, strm);
12074 }
12075
12076 res.status = StatusCode::BadRequest_400;
12077 return false;
12078}
12079
12080inline bool Server::dispatch_request(Request &req, Response &res,
12081 const Handlers &handlers, Stream &strm) {
12082 for (const auto &x : handlers) {
12083 const auto &matcher = x.first;
12084 const auto &handler = x.second;
12085
12086 if (matcher->match(req)) {
12087 req.matched_route = matcher->pattern();
12088
12089 // Run the pre-request handler before reading the body so a rejected
12090 // request (e.g. failed authentication) never forces us to buffer a
12091 // potentially large body. `req.matched_route` is available here.
12092 if (pre_request_handler_ &&
12093 pre_request_handler_(req, res) == HandlerResponse::Handled) {
12094 return true;
12095 }
12096
12097 // The route matched and the request was approved; read the body now.
12098 if (detail::expect_content(req) && !read_content(strm, req, res)) {
12099 output_error_log(Error::Read, &req);
12100 return false;
12101 }
12102
12103 handler(req, res);
12104 return true;
12105 }
12106 }
12107 return false;
12108}
12109
12110inline void Server::apply_ranges(const Request &req, Response &res,
12111 std::string &content_type,
12112 std::string &boundary) const {
12113 if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
12114 auto it = res.headers.find("Content-Type");
12115 if (it != res.headers.end()) {
12116 content_type = it->second;
12117 res.headers.erase(it);
12118 }
12119
12120 boundary = detail::make_multipart_data_boundary();
12121
12122 res.set_header("Content-Type",
12123 "multipart/byteranges; boundary=" + boundary);
12124 }
12125
12126 auto type = detail::encoding_type(req, res);
12127
12128 if (res.body.empty()) {
12129 if (res.content_length_ > 0) {
12130 size_t length = 0;
12131 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12132 length = res.content_length_;
12133 } else if (req.ranges.size() == 1) {
12134 auto offset_and_length = detail::get_range_offset_and_length(
12135 req.ranges[0], res.content_length_);
12136
12137 length = offset_and_length.second;
12138
12139 auto content_range = detail::make_content_range_header_field(
12140 offset_and_length, res.content_length_);
12141 res.set_header("Content-Range", content_range);
12142 } else {
12143 length = detail::get_multipart_ranges_data_length(
12144 req, boundary, content_type, res.content_length_);
12145 }
12146 res.set_header("Content-Length", std::to_string(length));
12147 } else {
12148 if (res.content_provider_) {
12149 if (res.is_chunked_content_provider_) {
12150 res.set_header("Transfer-Encoding", "chunked");
12151 if (type != detail::EncodingType::None) {
12152 res.set_header("Content-Encoding", detail::encoding_name(type));
12153 res.set_header("Vary", "Accept-Encoding");
12154 }
12155 }
12156 }
12157 }
12158 } else {
12159 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12160 ;
12161 } else if (req.ranges.size() == 1) {
12162 auto offset_and_length =
12163 detail::get_range_offset_and_length(req.ranges[0], res.body.size());
12164 auto offset = offset_and_length.first;
12165 auto length = offset_and_length.second;
12166
12167 auto content_range = detail::make_content_range_header_field(
12168 offset_and_length, res.body.size());
12169 res.set_header("Content-Range", content_range);
12170
12171 assert(offset + length <= res.body.size());
12172 res.body = res.body.substr(offset, length);
12173 } else {
12174 std::string data;
12175 detail::make_multipart_ranges_data(req, res, boundary, content_type,
12176 res.body.size(), data);
12177 res.body.swap(data);
12178 }
12179
12180 if (type != detail::EncodingType::None) {
12181 output_pre_compression_log(req, res);
12182
12183 if (auto compressor = detail::make_compressor(type)) {
12184 std::string compressed;
12185 if (compressor->compress(res.body.data(), res.body.size(), true,
12186 [&](const char *data, size_t data_len) {
12187 compressed.append(data, data_len);
12188 return true;
12189 })) {
12190 res.body.swap(compressed);
12191 res.set_header("Content-Encoding", detail::encoding_name(type));
12192 res.set_header("Vary", "Accept-Encoding");
12193 }
12194 }
12195 }
12196
12197 auto length = std::to_string(res.body.size());
12198 res.set_header("Content-Length", length);
12199 }
12200}
12201
12202inline bool Server::dispatch_request_for_content_reader(
12203 Request &req, Response &res, ContentReader content_reader,
12204 const HandlersForContentReader &handlers) const {
12205 for (const auto &x : handlers) {
12206 const auto &matcher = x.first;
12207 const auto &handler = x.second;
12208
12209 if (matcher->match(req)) {
12210 req.matched_route = matcher->pattern();
12211 if (!pre_request_handler_ ||
12212 pre_request_handler_(req, res) != HandlerResponse::Handled) {
12213 handler(req, res, content_reader);
12214 }
12215 return true;
12216 }
12217 }
12218 return false;
12219}
12220
12221inline std::string
12222get_client_ip(const std::string &x_forwarded_for,
12223 const std::vector<std::string> &trusted_proxies) {
12224 // X-Forwarded-For is a comma-separated list per RFC 7239
12225 std::vector<std::string> ip_list;
12226 detail::split(x_forwarded_for.data(),
12227 x_forwarded_for.data() + x_forwarded_for.size(), ',',
12228 [&](const char *b, const char *e) {
12229 auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
12230 ip_list.emplace_back(std::string(b + r.first, b + r.second));
12231 });
12232
12233 // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields
12234 // no segments. Signal "no client IP derived" with an empty string so the
12235 // caller can fall back to the connection-level remote address.
12236 if (ip_list.empty()) { return std::string(); }
12237
12238 for (size_t i = 0; i < ip_list.size(); ++i) {
12239 auto ip = ip_list[i];
12240
12241 auto is_trusted_proxy =
12242 std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
12243 [&](const std::string &proxy) { return ip == proxy; });
12244
12245 if (is_trusted_proxy) {
12246 if (i == 0) {
12247 // If the trusted proxy is the first IP, there's no preceding client IP
12248 return ip;
12249 } else {
12250 // Return the IP immediately before the trusted proxy
12251 return ip_list[i - 1];
12252 }
12253 }
12254 }
12255
12256 // If no trusted proxy is found, return the first IP in the list
12257 return ip_list.front();
12258}
12259
12260inline bool
12261Server::process_request(Stream &strm, const std::string &remote_addr,
12262 int remote_port, const std::string &local_addr,
12263 int local_port, bool close_connection,
12264 bool &connection_closed,
12265 const std::function<void(Request &)> &setup_request,
12266 bool *websocket_upgraded) {
12267 std::array<char, 2048> buf{};
12268
12269 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12270
12271 // Connection has been closed on client
12272 if (!line_reader.getline()) { return false; }
12273
12274 Request req;
12275 req.start_time_ = std::chrono::steady_clock::now();
12276 req.remote_addr = remote_addr;
12277 req.remote_port = remote_port;
12278 req.local_addr = local_addr;
12279 req.local_port = local_port;
12280
12281 Response res;
12282 res.version = "HTTP/1.1";
12283 res.headers = default_headers_;
12284
12285 // Request line and headers
12286 if (!parse_request_line(line_reader.ptr(), req)) {
12287 res.status = StatusCode::BadRequest_400;
12288 output_error_log(Error::InvalidRequestLine, &req);
12289 return write_response(strm, close_connection, req, res);
12290 }
12291
12292 // Request headers
12293 if (!detail::read_headers(strm, req.headers)) {
12294 res.status = StatusCode::BadRequest_400;
12295 output_error_log(Error::InvalidHeaders, &req);
12296 return write_response(strm, close_connection, req, res);
12297 }
12298
12299 // RFC 9112 §6.3: Reject requests with both a non-zero Content-Length and
12300 // any Transfer-Encoding to prevent request smuggling. Content-Length: 0 is
12301 // tolerated for compatibility with existing clients.
12302 if (req.get_header_value_u64("Content-Length") > 0 &&
12303 req.has_header("Transfer-Encoding")) {
12304 connection_closed = true;
12305 res.status = StatusCode::BadRequest_400;
12306 return write_response(strm, close_connection, req, res);
12307 }
12308
12309 // Check if the request URI doesn't exceed the limit
12310 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
12311 connection_closed = true;
12312 res.status = StatusCode::UriTooLong_414;
12313 output_error_log(Error::ExceedUriMaxLength, &req);
12314 return write_response(strm, close_connection, req, res);
12315 }
12316
12317 if (req.get_header_value("Connection") == "close") {
12318 connection_closed = true;
12319 }
12320
12321 if (req.version == "HTTP/1.0" &&
12322 req.get_header_value("Connection") != "Keep-Alive") {
12323 connection_closed = true;
12324 }
12325
12326 if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
12327 auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
12328 auto derived = get_client_ip(x_forwarded_for, trusted_proxies_);
12329 req.remote_addr = derived.empty() ? remote_addr : derived;
12330 } else {
12331 req.remote_addr = remote_addr;
12332 }
12333 req.remote_port = remote_port;
12334
12335 req.local_addr = local_addr;
12336 req.local_port = local_port;
12337
12338 if (req.has_header("Accept")) {
12339 const auto &accept_header = req.get_header_value("Accept");
12340 if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
12341 connection_closed = true;
12342 res.status = StatusCode::BadRequest_400;
12343 output_error_log(Error::HTTPParsing, &req);
12344 return write_response(strm, close_connection, req, res);
12345 }
12346 }
12347
12348 if (req.has_header("Range")) {
12349 const auto &range_header_value = req.get_header_value("Range");
12350 if (!detail::parse_range_header(range_header_value, req.ranges)) {
12351 connection_closed = true;
12352 res.status = StatusCode::RangeNotSatisfiable_416;
12353 output_error_log(Error::InvalidRangeHeader, &req);
12354 return write_response(strm, close_connection, req, res);
12355 }
12356 }
12357
12358 if (setup_request) { setup_request(req); }
12359
12360 if (req.get_header_value("Expect") == "100-continue") {
12361 int status = StatusCode::Continue_100;
12362 if (expect_100_continue_handler_) {
12363 status = expect_100_continue_handler_(req, res);
12364 }
12365 switch (status) {
12366 case StatusCode::Continue_100:
12367 case StatusCode::ExpectationFailed_417:
12368 detail::write_response_line(strm, status);
12369 strm.write("\r\n");
12370 break;
12371 default:
12372 connection_closed = true;
12373 return write_response(strm, true, req, res);
12374 }
12375 }
12376
12377 // Setup `is_connection_closed` method
12378 auto sock = strm.socket();
12379 req.is_connection_closed = [sock]() {
12380 return !detail::is_socket_alive(sock);
12381 };
12382
12383 // WebSocket upgrade
12384 // Check pre_routing_handler_ before upgrading so that authentication
12385 // and other middleware can reject the request with an HTTP response
12386 // (e.g., 401) before the protocol switches.
12387 if (detail::is_websocket_upgrade(req)) {
12388 if (pre_routing_handler_ &&
12389 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
12390 if (res.status == -1) { res.status = StatusCode::OK_200; }
12391 return write_response(strm, close_connection, req, res);
12392 }
12393 // Find matching WebSocket handler
12394 for (const auto &entry : websocket_handlers_) {
12395 if (entry.matcher->match(req)) {
12396 // Compute accept key
12397 auto client_key = req.get_header_value("Sec-WebSocket-Key");
12398 auto accept_key = detail::websocket_accept_key(client_key);
12399
12400 // Negotiate subprotocol
12401 std::string selected_subprotocol;
12402 if (entry.sub_protocol_selector) {
12403 auto protocol_header = req.get_header_value("Sec-WebSocket-Protocol");
12404 if (!protocol_header.empty()) {
12405 std::vector<std::string> protocols;
12406 std::istringstream iss(protocol_header);
12407 std::string token;
12408 while (std::getline(iss, token, ',')) {
12409 // Trim whitespace
12410 auto start = token.find_first_not_of(' ');
12411 auto end = token.find_last_not_of(' ');
12412 if (start != std::string::npos) {
12413 protocols.push_back(token.substr(start, end - start + 1));
12414 }
12415 }
12416 selected_subprotocol = entry.sub_protocol_selector(protocols);
12417 }
12418 }
12419
12420 // Send 101 Switching Protocols
12421 std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
12422 "Upgrade: websocket\r\n"
12423 "Connection: Upgrade\r\n"
12424 "Sec-WebSocket-Accept: " +
12425 accept_key + "\r\n";
12426 if (!selected_subprotocol.empty()) {
12427 if (!detail::fields::is_field_value(selected_subprotocol)) {
12428 return false;
12429 }
12430 handshake_response +=
12431 "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
12432 }
12433 handshake_response += "\r\n";
12434 if (strm.write(handshake_response.data(), handshake_response.size()) <
12435 0) {
12436 return false;
12437 }
12438
12439 connection_closed = true;
12440 if (websocket_upgraded) { *websocket_upgraded = true; }
12441
12442 {
12443 // Use WebSocket-specific read timeout instead of HTTP timeout
12444 strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND, 0);
12445 ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
12446 websocket_max_missed_pongs_);
12447 entry.handler(req, ws);
12448 }
12449 return true;
12450 }
12451 }
12452 // No matching handler - fall through to 404
12453 }
12454
12455 // Routing
12456 auto routed = false;
12457#ifdef CPPHTTPLIB_NO_EXCEPTIONS
12458 routed = routing(req, res, strm);
12459#else
12460 try {
12461 routed = routing(req, res, strm);
12462 } catch (std::exception &) {
12463 if (exception_handler_) {
12464 auto ep = std::current_exception();
12465 exception_handler_(req, res, ep);
12466 routed = true;
12467 } else {
12468 res.status = StatusCode::InternalServerError_500;
12469 }
12470 } catch (...) {
12471 if (exception_handler_) {
12472 auto ep = std::current_exception();
12473 exception_handler_(req, res, ep);
12474 routed = true;
12475 } else {
12476 res.status = StatusCode::InternalServerError_500;
12477 }
12478 }
12479#endif
12480 auto ret = false;
12481 if (routed) {
12482 if (res.status == -1) {
12483 res.status = req.ranges.empty() ? StatusCode::OK_200
12484 : StatusCode::PartialContent_206;
12485 }
12486
12487 // Serve file content by using a content provider
12488 auto file_open_error = false;
12489 if (!res.file_content_path_.empty()) {
12490 const auto &path = res.file_content_path_;
12491 auto mm = std::make_shared<detail::mmap>(path.c_str());
12492 if (!mm->is_open()) {
12493 res.body.clear();
12494 res.content_length_ = 0;
12495 res.content_provider_ = nullptr;
12496 res.status = StatusCode::NotFound_404;
12497 output_error_log(Error::OpenFile, &req);
12498 file_open_error = true;
12499 } else {
12500 auto content_type = res.file_content_content_type_;
12501 if (content_type.empty()) {
12502 content_type = detail::find_content_type(
12503 path, file_extension_and_mimetype_map_, default_file_mimetype_);
12504 }
12505
12506 res.set_content_provider(
12507 mm->size(), content_type,
12508 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
12509 sink.write(mm->data() + offset, length);
12510 return true;
12511 });
12512 }
12513 }
12514
12515 if (file_open_error) {
12516 ret = write_response(strm, close_connection, req, res);
12517 } else if (detail::range_error(req, res)) {
12518 res.body.clear();
12519 res.content_length_ = 0;
12520 res.content_provider_ = nullptr;
12521 res.status = StatusCode::RangeNotSatisfiable_416;
12522 ret = write_response(strm, close_connection, req, res);
12523 } else {
12524 ret = write_response_with_content(strm, close_connection, req, res);
12525 }
12526 } else {
12527 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
12528 ret = write_response(strm, close_connection, req, res);
12529 }
12530
12531 // Drain any unconsumed framed body to prevent request smuggling on
12532 // keep-alive. Without framing there is no body to drain — reading would
12533 // consume the next request (issue #2450).
12534 if (!req.body_consumed_ && detail::has_framed_body(req)) {
12535 int dummy_status;
12536 if (!detail::read_content(
12537 strm, req, payload_max_length_, dummy_status, nullptr,
12538 [](const char *, size_t, size_t, size_t) { return true; }, false)) {
12539 connection_closed = true;
12540 }
12541 }
12542
12543 return ret;
12544}
12545
12546inline bool Server::is_valid() const { return true; }
12547
12548inline bool Server::process_and_close_socket(socket_t sock) {
12549 std::string remote_addr;
12550 int remote_port = 0;
12551 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
12552
12553 std::string local_addr;
12554 int local_port = 0;
12555 detail::get_local_ip_and_port(sock, local_addr, local_port);
12556
12557 bool websocket_upgraded = false;
12558 auto ret = detail::process_server_socket(
12559 svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
12560 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
12561 write_timeout_usec_,
12562 [&](Stream &strm, bool close_connection, bool &connection_closed) {
12563 return process_request(strm, remote_addr, remote_port, local_addr,
12564 local_port, close_connection, connection_closed,
12565 nullptr, &websocket_upgraded);
12566 });
12567
12568 detail::shutdown_socket(sock);
12569 detail::close_socket(sock);
12570 return ret;
12571}
12572
12573inline void Server::output_log(const Request &req, const Response &res) const {
12574 if (logger_) {
12575 std::lock_guard<std::mutex> guard(logger_mutex_);
12576 logger_(req, res);
12577 }
12578}
12579
12580inline void Server::output_pre_compression_log(const Request &req,
12581 const Response &res) const {
12582 if (pre_compression_logger_) {
12583 std::lock_guard<std::mutex> guard(logger_mutex_);
12584 pre_compression_logger_(req, res);
12585 }
12586}
12587
12588inline void Server::output_error_log(const Error &err,
12589 const Request *req) const {
12590 if (error_logger_) {
12591 std::lock_guard<std::mutex> guard(logger_mutex_);
12592 error_logger_(err, req);
12593 }
12594}
12595
12596/*
12597 * Group 5: ClientImpl and Client (Universal) implementation
12598 */
12599// HTTP client implementation
12600inline ClientImpl::ClientImpl(const std::string &host)
12601 : ClientImpl(host, 80, std::string(), std::string()) {}
12602
12603inline ClientImpl::ClientImpl(const std::string &host, int port)
12604 : ClientImpl(host, port, std::string(), std::string()) {}
12605
12606inline ClientImpl::ClientImpl(const std::string &host, int port,
12607 const std::string &client_cert_path,
12608 const std::string &client_key_path)
12609 : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
12610 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
12611
12612inline ClientImpl::~ClientImpl() {
12613 // Wait until all the requests in flight are handled.
12614 size_t retry_count = 10;
12615 while (retry_count-- > 0) {
12616 {
12617 std::lock_guard<std::mutex> guard(socket_mutex_);
12618 if (socket_requests_in_flight_ == 0) { break; }
12619 }
12620 std::this_thread::sleep_for(std::chrono::milliseconds{1});
12621 }
12622
12623 std::lock_guard<std::mutex> guard(socket_mutex_);
12624 shutdown_socket(socket_);
12625 close_socket(socket_);
12626}
12627
12628inline bool ClientImpl::is_valid() const { return true; }
12629
12630inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
12631 client_cert_path_ = rhs.client_cert_path_;
12632 client_key_path_ = rhs.client_key_path_;
12633 connection_timeout_sec_ = rhs.connection_timeout_sec_;
12634 read_timeout_sec_ = rhs.read_timeout_sec_;
12635 read_timeout_usec_ = rhs.read_timeout_usec_;
12636 write_timeout_sec_ = rhs.write_timeout_sec_;
12637 write_timeout_usec_ = rhs.write_timeout_usec_;
12638 max_timeout_msec_ = rhs.max_timeout_msec_;
12639 basic_auth_username_ = rhs.basic_auth_username_;
12640 basic_auth_password_ = rhs.basic_auth_password_;
12641 bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
12642 keep_alive_ = rhs.keep_alive_;
12643 follow_location_ = rhs.follow_location_;
12644 path_encode_ = rhs.path_encode_;
12645 address_family_ = rhs.address_family_;
12646 tcp_nodelay_ = rhs.tcp_nodelay_;
12647 ipv6_v6only_ = rhs.ipv6_v6only_;
12648 socket_options_ = rhs.socket_options_;
12649 compress_ = rhs.compress_;
12650 decompress_ = rhs.decompress_;
12651 payload_max_length_ = rhs.payload_max_length_;
12652 has_payload_max_length_ = rhs.has_payload_max_length_;
12653 interface_ = rhs.interface_;
12654 proxy_host_ = rhs.proxy_host_;
12655 proxy_port_ = rhs.proxy_port_;
12656 proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
12657 proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
12658 proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
12659 no_proxy_entries_ = rhs.no_proxy_entries_;
12660 logger_ = rhs.logger_;
12661 error_logger_ = rhs.error_logger_;
12662
12663#ifdef CPPHTTPLIB_SSL_ENABLED
12664 digest_auth_username_ = rhs.digest_auth_username_;
12665 digest_auth_password_ = rhs.digest_auth_password_;
12666 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
12667 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
12668 ca_cert_file_path_ = rhs.ca_cert_file_path_;
12669 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
12670 server_certificate_verification_ = rhs.server_certificate_verification_;
12671 server_hostname_verification_ = rhs.server_hostname_verification_;
12672 system_ca_mode_ = rhs.system_ca_mode_;
12673#endif
12674}
12675
12676inline bool
12677ClientImpl::is_proxy_enabled_for_host(const std::string &host) const {
12678 if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
12679 if (no_proxy_entries_.empty()) { return true; }
12680 // host_ is const so its normalized form is invariant; cache it. The
12681 // cross-host path (setup_redirect_client passing next_host) re-normalizes.
12682 if (host == host_) {
12683 if (!host_normalized_valid_) {
12684 host_normalized_ = detail::normalize_target(host_);
12685 host_normalized_valid_ = true;
12686 }
12687 return !detail::host_matches_no_proxy(host_normalized_, no_proxy_entries_);
12688 }
12689 auto target = detail::normalize_target(host);
12690 return !detail::host_matches_no_proxy(target, no_proxy_entries_);
12691}
12692
12693inline socket_t ClientImpl::create_client_socket(Error &error) const {
12694 if (is_proxy_enabled_for_host(host_)) {
12695 return detail::create_client_socket(
12696 proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
12697 ipv6_v6only_, socket_options_, connection_timeout_sec_,
12698 connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
12699 write_timeout_sec_, write_timeout_usec_, interface_, error);
12700 }
12701
12702 // Check is custom IP specified for host_
12703 std::string ip;
12704 auto it = addr_map_.find(host_);
12705 if (it != addr_map_.end()) { ip = it->second; }
12706
12707 return detail::create_client_socket(
12708 host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
12709 socket_options_, connection_timeout_sec_, connection_timeout_usec_,
12710 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
12711 write_timeout_usec_, interface_, error);
12712}
12713
12714inline bool ClientImpl::create_and_connect_socket(Socket &socket,
12715 Error &error) {
12716 auto sock = create_client_socket(error);
12717 if (sock == INVALID_SOCKET) { return false; }
12718 socket.sock = sock;
12719 return true;
12720}
12721
12722inline bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
12723 return create_and_connect_socket(socket, error);
12724}
12725
12726inline bool ClientImpl::setup_proxy_connection(
12727 Socket & /*socket*/,
12728 std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
12729 Response & /*res*/, bool & /*success*/, Error & /*error*/) {
12730 return true;
12731}
12732
12733inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
12734 bool /*shutdown_gracefully*/) {
12735 // If there are any requests in flight from threads other than us, then it's
12736 // a thread-unsafe race because individual ssl* objects are not thread-safe.
12737 assert(socket_requests_in_flight_ == 0 ||
12738 socket_requests_are_from_thread_ == std::this_thread::get_id());
12739}
12740
12741inline void ClientImpl::shutdown_socket(Socket &socket) const {
12742 if (socket.sock == INVALID_SOCKET) { return; }
12743 detail::shutdown_socket(socket.sock);
12744}
12745
12746inline void ClientImpl::close_socket(Socket &socket) {
12747 // If there are requests in flight in another thread, usually closing
12748 // the socket will be fine and they will simply receive an error when
12749 // using the closed socket, but it is still a bug since rarely the OS
12750 // may reassign the socket id to be used for a new socket, and then
12751 // suddenly they will be operating on a live socket that is different
12752 // than the one they intended!
12753 assert(socket_requests_in_flight_ == 0 ||
12754 socket_requests_are_from_thread_ == std::this_thread::get_id());
12755
12756 // It is also a bug if this happens while SSL is still active
12757#ifdef CPPHTTPLIB_SSL_ENABLED
12758 assert(socket.ssl == nullptr);
12759#endif
12760
12761 if (socket.sock == INVALID_SOCKET) { return; }
12762 detail::close_socket(socket.sock);
12763 socket.sock = INVALID_SOCKET;
12764}
12765
12766inline void ClientImpl::disconnect(bool gracefully) {
12767 shutdown_ssl(socket_, gracefully);
12768 shutdown_socket(socket_);
12769 close_socket(socket_);
12770}
12771
12772inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
12773 Response &res,
12774 bool skip_100_continue) const {
12775 std::array<char, 2048> buf{};
12776
12777 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12778
12779 if (!line_reader.getline()) { return false; }
12780
12781#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
12782 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
12783#else
12784 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
12785#endif
12786
12787 std::cmatch m;
12788 if (!std::regex_match(line_reader.ptr(), m, re)) {
12789 return req.method == "CONNECT";
12790 }
12791 res.version = std::string(m[1]);
12792 res.status = std::stoi(std::string(m[2]));
12793 res.reason = std::string(m[3]);
12794
12795 // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
12796 while (skip_100_continue && res.status == StatusCode::Continue_100) {
12797 if (!line_reader.getline()) { return false; } // CRLF
12798 if (!line_reader.getline()) { return false; } // next response line
12799
12800 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
12801 res.version = std::string(m[1]);
12802 res.status = std::stoi(std::string(m[2]));
12803 res.reason = std::string(m[3]);
12804 }
12805
12806 return true;
12807}
12808
12809inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
12810 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
12811 auto ret = send_(req, res, error);
12812 if (error == Error::SSLPeerCouldBeClosed_) {
12813 assert(!ret);
12814 ret = send_(req, res, error);
12815 // If still failing with SSLPeerCouldBeClosed_, convert to Read error
12816 if (error == Error::SSLPeerCouldBeClosed_) { error = Error::Read; }
12817 }
12818 return ret;
12819}
12820
12821inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
12822 {
12823 std::lock_guard<std::mutex> guard(socket_mutex_);
12824
12825 // Set this to false immediately - if it ever gets set to true by the end
12826 // of the request, we know another thread instructed us to close the
12827 // socket.
12828 socket_should_be_closed_when_request_is_done_ = false;
12829
12830 auto is_alive = false;
12831 if (socket_.is_open()) {
12832 is_alive = detail::is_socket_alive(socket_.sock);
12833
12834#ifdef CPPHTTPLIB_SSL_ENABLED
12835 if (is_alive && is_ssl()) {
12836 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12837 is_alive = false;
12838 }
12839 }
12840#endif
12841
12842 if (!is_alive) {
12843 // Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
12844 disconnect(/*gracefully=*/false);
12845 }
12846 }
12847
12848 if (!is_alive) {
12849 if (!ensure_socket_connection(socket_, error)) {
12850 output_error_log(error, &req);
12851 return false;
12852 }
12853
12854 {
12855 auto success = true;
12856 if (!setup_proxy_connection(socket_, req.start_time_, res, success,
12857 error)) {
12858 if (!success) { output_error_log(error, &req); }
12859 return success;
12860 }
12861 }
12862 }
12863
12864 // Mark the current socket as being in use so that it cannot be closed by
12865 // anyone else while this request is ongoing, even though we will be
12866 // releasing the mutex.
12867 if (socket_requests_in_flight_ > 1) {
12868 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
12869 }
12870 socket_requests_in_flight_ += 1;
12871 socket_requests_are_from_thread_ = std::this_thread::get_id();
12872 }
12873
12874 for (const auto &header : default_headers_) {
12875 if (req.headers.find(header.first) == req.headers.end()) {
12876 req.headers.insert(header);
12877 }
12878 }
12879
12880 auto ret = false;
12881 auto close_connection = !keep_alive_;
12882
12883 auto se = detail::scope_exit([&]() {
12884 // Briefly lock mutex in order to mark that a request is no longer ongoing
12885 std::lock_guard<std::mutex> guard(socket_mutex_);
12886 socket_requests_in_flight_ -= 1;
12887 if (socket_requests_in_flight_ <= 0) {
12888 assert(socket_requests_in_flight_ == 0);
12889 socket_requests_are_from_thread_ = std::thread::id();
12890 }
12891
12892 if (socket_should_be_closed_when_request_is_done_ || close_connection ||
12893 !ret) {
12894 disconnect(/*gracefully=*/true);
12895 }
12896 });
12897
12898 ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
12899 return handle_request(strm, req, res, close_connection, error);
12900 });
12901
12902 if (!ret) {
12903 if (error == Error::Success) {
12904 error = Error::Unknown;
12905 output_error_log(error, &req);
12906 }
12907 }
12908
12909 return ret;
12910}
12911
12912inline Result ClientImpl::send(const Request &req) {
12913 auto req2 = req;
12914 return send_(std::move(req2));
12915}
12916
12917inline Result ClientImpl::send_(Request &&req) {
12918 auto res = detail::make_unique<Response>();
12919 auto error = Error::Success;
12920 auto ret = send(req, *res, error);
12921#ifdef CPPHTTPLIB_SSL_ENABLED
12922 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
12923 last_ssl_error_, last_backend_error_};
12924#else
12925 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
12926#endif
12927}
12928
12929inline void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
12930 const std::string &ct) {
12931 (void)for_stream;
12932 for (const auto &header : default_headers_) {
12933 if (!r.has_header(header.first)) { r.headers.insert(header); }
12934 }
12935
12936 if (!r.has_header("Host")) {
12937 if (address_family_ == AF_UNIX) {
12938 r.headers.emplace("Host", "localhost");
12939 } else {
12940 r.headers.emplace(
12941 "Host", detail::make_host_and_port_string(host_, port_, is_ssl()));
12942 }
12943 }
12944
12945 if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
12946
12947 if (!r.content_receiver) {
12948 if (!r.has_header("Accept-Encoding")) {
12949 std::string accept_encoding;
12950#ifdef CPPHTTPLIB_BROTLI_SUPPORT
12951 accept_encoding = "br";
12952#endif
12953#ifdef CPPHTTPLIB_ZLIB_SUPPORT
12954 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12955 accept_encoding += "gzip, deflate";
12956#endif
12957#ifdef CPPHTTPLIB_ZSTD_SUPPORT
12958 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12959 accept_encoding += "zstd";
12960#endif
12961 r.set_header("Accept-Encoding", accept_encoding);
12962 }
12963
12964#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
12965 if (!r.has_header("User-Agent")) {
12966 auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
12967 r.set_header("User-Agent", agent);
12968 }
12969#endif
12970 }
12971
12972 if (!r.body.empty()) {
12973 if (!ct.empty() && !r.has_header("Content-Type")) {
12974 r.headers.emplace("Content-Type", ct);
12975 }
12976 if (!r.has_header("Content-Length")) {
12977 r.headers.emplace("Content-Length", std::to_string(r.body.size()));
12978 }
12979 }
12980}
12981
12982inline ClientImpl::StreamHandle
12983ClientImpl::open_stream(const std::string &method, const std::string &path,
12984 const Params &params, const Headers &headers,
12985 const std::string &body,
12986 const std::string &content_type) {
12987 StreamHandle handle;
12988 handle.response = detail::make_unique<Response>();
12989 handle.error = Error::Success;
12990
12991 auto query_path = params.empty() ? path : append_query_params(path, params);
12992 handle.connection_ = detail::make_unique<ClientConnection>();
12993
12994 {
12995 std::lock_guard<std::mutex> guard(socket_mutex_);
12996
12997 auto is_alive = false;
12998 if (socket_.is_open()) {
12999 is_alive = detail::is_socket_alive(socket_.sock);
13000#ifdef CPPHTTPLIB_SSL_ENABLED
13001 if (is_alive && is_ssl()) {
13002 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
13003 is_alive = false;
13004 }
13005 }
13006#endif
13007 if (!is_alive) { disconnect(/*gracefully=*/false); }
13008 }
13009
13010 if (!is_alive) {
13011 if (!ensure_socket_connection(socket_, handle.error)) {
13012 handle.response.reset();
13013 return handle;
13014 }
13015
13016 {
13017 auto success = true;
13018 auto start_time = std::chrono::steady_clock::now();
13019 if (!setup_proxy_connection(socket_, start_time, *handle.response,
13020 success, handle.error)) {
13021 if (!success) { handle.response.reset(); }
13022 return handle;
13023 }
13024 }
13025 }
13026
13027 transfer_socket_ownership_to_handle(handle);
13028 }
13029
13030#ifdef CPPHTTPLIB_SSL_ENABLED
13031 if (is_ssl() && handle.connection_->session) {
13032 handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
13033 handle.connection_->sock, handle.connection_->session,
13034 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
13035 write_timeout_usec_);
13036 } else {
13037 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
13038 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
13039 write_timeout_sec_, write_timeout_usec_);
13040 }
13041#else
13042 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
13043 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
13044 write_timeout_sec_, write_timeout_usec_);
13045#endif
13046 handle.stream_ = handle.socket_stream_.get();
13047
13048 Request req;
13049 req.method = method;
13050 req.path = query_path;
13051 req.headers = headers;
13052 req.body = body;
13053
13054 prepare_default_headers(req, true, content_type);
13055
13056 auto &strm = *handle.stream_;
13057 if (detail::write_request_line(strm, req.method, req.path) < 0) {
13058 handle.error = Error::Write;
13059 handle.response.reset();
13060 return handle;
13061 }
13062
13063 if (!detail::check_and_write_headers(strm, req.headers, header_writer_,
13064 handle.error)) {
13065 handle.response.reset();
13066 return handle;
13067 }
13068
13069 if (!body.empty()) {
13070 if (strm.write(body.data(), body.size()) < 0) {
13071 handle.error = Error::Write;
13072 handle.response.reset();
13073 return handle;
13074 }
13075 }
13076
13077 if (!read_response_line(strm, req, *handle.response) ||
13078 !detail::read_headers(strm, handle.response->headers)) {
13079 handle.error = Error::Read;
13080 handle.response.reset();
13081 return handle;
13082 }
13083
13084 handle.body_reader_.stream = handle.stream_;
13085 handle.body_reader_.payload_max_length = payload_max_length_;
13086
13087 if (handle.response->has_header("Content-Length")) {
13088 bool is_invalid = false;
13089 auto content_length = detail::get_header_value_u64(
13090 handle.response->headers, "Content-Length", 0, 0, is_invalid);
13091 if (is_invalid) {
13092 handle.error = Error::Read;
13093 handle.response.reset();
13094 return handle;
13095 }
13096 handle.body_reader_.has_content_length = true;
13097 handle.body_reader_.content_length = content_length;
13098 }
13099
13100 auto transfer_encoding =
13101 handle.response->get_header_value("Transfer-Encoding");
13102 handle.body_reader_.chunked = (transfer_encoding == "chunked");
13103
13104 auto content_encoding = handle.response->get_header_value("Content-Encoding");
13105 if (!content_encoding.empty()) {
13106 handle.decompressor_ = detail::create_decompressor(content_encoding);
13107 }
13108
13109 return handle;
13110}
13111
13112inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
13113 if (!is_valid() || !response) { return -1; }
13114
13115 if (decompressor_) { return read_with_decompression(buf, len); }
13116 auto n = detail::read_body_content(stream_, body_reader_, buf, len);
13117
13118 if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
13119 trailers_parsed_ = true;
13120 if (body_reader_.chunked_decoder) {
13121 if (!body_reader_.chunked_decoder->parse_trailers_into(
13122 response->trailers, response->headers)) {
13123 return n;
13124 }
13125 } else {
13126 detail::ChunkedDecoder dec(*stream_);
13127 if (!dec.parse_trailers_into(response->trailers, response->headers)) {
13128 return n;
13129 }
13130 }
13131 }
13132
13133 return n;
13134}
13135
13136inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
13137 size_t len) {
13138 if (decompress_offset_ < decompress_buffer_.size()) {
13139 auto available = decompress_buffer_.size() - decompress_offset_;
13140 auto to_copy = (std::min)(len, available);
13141 std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
13142 decompress_offset_ += to_copy;
13143 decompressed_bytes_read_ += to_copy;
13144 return static_cast<ssize_t>(to_copy);
13145 }
13146
13147 decompress_buffer_.clear();
13148 decompress_offset_ = 0;
13149
13150 constexpr size_t kDecompressionBufferSize = 8192;
13151 char compressed_buf[kDecompressionBufferSize];
13152
13153 while (true) {
13154 auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
13155 sizeof(compressed_buf));
13156
13157 if (n <= 0) { return n; }
13158
13159 bool decompress_ok = decompressor_->decompress(
13160 compressed_buf, static_cast<size_t>(n),
13161 [this](const char *data, size_t data_len) {
13162 decompress_buffer_.append(data, data_len);
13163 auto limit = body_reader_.payload_max_length;
13164 if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) {
13165 return false;
13166 }
13167 return true;
13168 });
13169
13170 if (!decompress_ok) {
13171 body_reader_.last_error = Error::Read;
13172 return -1;
13173 }
13174
13175 if (!decompress_buffer_.empty()) { break; }
13176 }
13177
13178 auto to_copy = (std::min)(len, decompress_buffer_.size());
13179 std::memcpy(buf, decompress_buffer_.data(), to_copy);
13180 decompress_offset_ = to_copy;
13181 decompressed_bytes_read_ += to_copy;
13182 return static_cast<ssize_t>(to_copy);
13183}
13184
13185inline void ClientImpl::StreamHandle::parse_trailers_if_needed() {
13186 if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
13187 return;
13188 }
13189
13190 trailers_parsed_ = true;
13191
13192 const auto bufsiz = 128;
13193 char line_buf[bufsiz];
13194 detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
13195
13196 if (!line_reader.getline()) { return; }
13197
13198 if (!detail::parse_trailers(line_reader, response->trailers,
13199 response->headers)) {
13200 return;
13201 }
13202}
13203
13204namespace detail {
13205
13206inline ChunkedDecoder::ChunkedDecoder(Stream &s) : strm(s) {}
13207
13208inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
13209 size_t &out_chunk_offset,
13210 size_t &out_chunk_total) {
13211 if (finished) { return 0; }
13212
13213 if (chunk_remaining == 0) {
13214 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13215 if (!lr.getline()) { return -1; }
13216
13217 // RFC 9112 §7.1: chunk-size = 1*HEXDIG
13218 const char *p = lr.ptr();
13219 int v = 0;
13220 if (!is_hex(*p, v)) { return -1; }
13221
13222 size_t chunk_len = 0;
13223 constexpr size_t chunk_len_max = (std::numeric_limits<size_t>::max)();
13224 for (; is_hex(*p, v); ++p) {
13225 if (chunk_len > (chunk_len_max >> 4)) { return -1; }
13226 chunk_len = (chunk_len << 4) | static_cast<size_t>(v);
13227 }
13228
13229 while (is_space_or_tab(*p)) {
13230 ++p;
13231 }
13232 if (*p != '\0' && *p != ';' && *p != '\r' && *p != '\n') { return -1; }
13233
13234 if (chunk_len == 0) {
13235 chunk_remaining = 0;
13236 finished = true;
13237 out_chunk_offset = 0;
13238 out_chunk_total = 0;
13239 return 0;
13240 }
13241
13242 chunk_remaining = chunk_len;
13243 last_chunk_total = chunk_remaining;
13244 last_chunk_offset = 0;
13245 }
13246
13247 auto to_read = (std::min)(chunk_remaining, len);
13248 auto n = strm.read(buf, to_read);
13249 if (n <= 0) { return -1; }
13250
13251 auto offset_before = last_chunk_offset;
13252 last_chunk_offset += static_cast<size_t>(n);
13253 chunk_remaining -= static_cast<size_t>(n);
13254
13255 out_chunk_offset = offset_before;
13256 out_chunk_total = last_chunk_total;
13257
13258 if (chunk_remaining == 0) {
13259 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13260 if (!lr.getline()) { return -1; }
13261 if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
13262 }
13263
13264 return n;
13265}
13266
13267inline bool ChunkedDecoder::parse_trailers_into(Headers &dest,
13268 const Headers &src_headers) {
13269 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13270 if (!lr.getline()) { return false; }
13271 return parse_trailers(lr, dest, src_headers);
13272}
13273
13274} // namespace detail
13275
13276inline void
13277ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
13278 handle.connection_->sock = socket_.sock;
13279#ifdef CPPHTTPLIB_SSL_ENABLED
13280 handle.connection_->session = socket_.ssl;
13281 socket_.ssl = nullptr;
13282#endif
13283 socket_.sock = INVALID_SOCKET;
13284}
13285
13286inline bool ClientImpl::handle_request(Stream &strm, Request &req,
13287 Response &res, bool close_connection,
13288 Error &error) {
13289 if (req.path.empty()) {
13290 error = Error::Connection;
13291 output_error_log(error, &req);
13292 return false;
13293 }
13294
13295 auto req_save = req;
13296
13297 bool ret;
13298
13299 if (!is_ssl() && is_proxy_enabled_for_host(host_)) {
13300 auto req2 = req;
13301 req2.path = "http://" +
13302 detail::make_host_and_port_string(host_, port_, false) +
13303 req.path;
13304 ret = process_request(strm, req2, res, close_connection, error);
13305 req = std::move(req2);
13306 req.path = req_save.path;
13307 } else {
13308 ret = process_request(strm, req, res, close_connection, error);
13309 }
13310
13311 if (!ret) { return false; }
13312
13313 if (res.get_header_value("Connection") == "close" ||
13314 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
13315 // NOTE: this requires a not-entirely-obvious chain of calls to be correct
13316 // for this to be safe.
13317
13318 // This is safe to call because handle_request is only called by send_
13319 // which locks the request mutex during the process. It would be a bug
13320 // to call it from a different thread since it's a thread-safety issue
13321 // to do these things to the socket if another thread is using the socket.
13322 std::lock_guard<std::mutex> guard(socket_mutex_);
13323 disconnect(/*gracefully=*/true);
13324 }
13325
13326 if (300 < res.status && res.status < 400 && follow_location_) {
13327 req = std::move(req_save);
13328 ret = redirect(req, res, error);
13329 }
13330
13331#ifdef CPPHTTPLIB_SSL_ENABLED
13332 if ((res.status == StatusCode::Unauthorized_401 ||
13333 res.status == StatusCode::ProxyAuthenticationRequired_407) &&
13334 req.authorization_count_ < 5) {
13335 auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
13336
13337 // Only retry when the 407 actually came from a proxy hop: plain HTTP
13338 // through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the
13339 // origin (#2457); direct/bypassed origins have no proxy hop at all.
13340 if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) {
13341 return ret;
13342 }
13343
13344 const auto &username =
13345 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
13346 const auto &password =
13347 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
13348
13349 if (!username.empty() && !password.empty()) {
13350 std::map<std::string, std::string> auth;
13351 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
13352 Request new_req = req;
13353 new_req.authorization_count_ += 1;
13354 new_req.headers.erase(is_proxy ? "Proxy-Authorization"
13355 : "Authorization");
13356 new_req.headers.insert(detail::make_digest_authentication_header(
13357 req, auth, new_req.authorization_count_, detail::random_string(10),
13358 username, password, is_proxy));
13359
13360 Response new_res;
13361
13362 ret = send(new_req, new_res, error);
13363 if (ret) { res = std::move(new_res); }
13364 }
13365 }
13366 }
13367#endif
13368
13369 return ret;
13370}
13371
13372inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
13373 if (req.redirect_count_ == 0) {
13374 error = Error::ExceedRedirectCount;
13375 output_error_log(error, &req);
13376 return false;
13377 }
13378
13379 auto location = res.get_header_value("location");
13380 if (location.empty()) { return false; }
13381
13382 detail::UrlComponents uc;
13383 if (!detail::parse_url(location, uc)) { return false; }
13384
13385 // Only follow http/https redirects
13386 if (!uc.scheme.empty() && uc.scheme != "http" && uc.scheme != "https") {
13387 return false;
13388 }
13389
13390 auto scheme = is_ssl() ? "https" : "http";
13391
13392 auto next_scheme = std::move(uc.scheme);
13393 auto next_host = std::move(uc.host);
13394 auto port_str = std::move(uc.port);
13395 auto next_path = std::move(uc.path);
13396 auto next_query = std::move(uc.query);
13397
13398 auto next_port = port_;
13399 if (!port_str.empty()) {
13400 if (!detail::parse_port(port_str, next_port)) { return false; }
13401 } else if (!next_scheme.empty()) {
13402 next_port = next_scheme == "https" ? 443 : 80;
13403 }
13404
13405 if (next_scheme.empty()) { next_scheme = scheme; }
13406 if (next_host.empty()) { next_host = host_; }
13407 if (next_path.empty()) { next_path = "/"; }
13408
13409 auto path = decode_path_component(next_path) + next_query;
13410
13411 // Same host redirect - use current client
13412 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
13413 return detail::redirect(*this, req, res, path, location, error);
13414 }
13415
13416 // Cross-host/scheme redirect - create new client with robust setup
13417 return create_redirect_client(next_scheme, next_host, next_port, req, res,
13418 path, location, error);
13419}
13420
13421// New method for robust redirect client creation
13422inline bool ClientImpl::create_redirect_client(
13423 const std::string &scheme, const std::string &host, int port, Request &req,
13424 Response &res, const std::string &path, const std::string &location,
13425 Error &error) {
13426 // Determine if we need SSL
13427 auto need_ssl = (scheme == "https");
13428
13429 // Clean up request headers that are host/client specific
13430 // Remove headers that should not be carried over to new host
13431 auto headers_to_remove =
13432 std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
13433
13434 for (const auto &header_name : headers_to_remove) {
13435 auto it = req.headers.find(header_name);
13436 while (it != req.headers.end()) {
13437 it = req.headers.erase(it);
13438 it = req.headers.find(header_name);
13439 }
13440 }
13441
13442 // Create appropriate client type and handle redirect
13443 if (need_ssl) {
13444#ifdef CPPHTTPLIB_SSL_ENABLED
13445 // Create SSL client for HTTPS redirect
13446 SSLClient redirect_client(host, port);
13447
13448 // Setup basic client configuration first
13449 setup_redirect_client(redirect_client);
13450
13451 redirect_client.enable_server_certificate_verification(
13452 server_certificate_verification_);
13453 redirect_client.enable_server_hostname_verification(
13454 server_hostname_verification_);
13455 redirect_client.system_ca_mode_ = system_ca_mode_;
13456
13457 // Transfer CA certificate to redirect client
13458 if (!ca_cert_pem_.empty()) {
13459 redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
13460 ca_cert_pem_.size());
13461 }
13462 if (!ca_cert_file_path_.empty()) {
13463 redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
13464 }
13465
13466 // Client certificates are set through constructor for SSLClient
13467 // NOTE: SSLClient constructor already takes client_cert_path and
13468 // client_key_path so we need to create it properly if client certs are
13469 // needed
13470
13471 // Execute the redirect
13472 return detail::redirect(redirect_client, req, res, path, location, error);
13473#else
13474 // SSL not supported - set appropriate error
13475 error = Error::SSLConnection;
13476 output_error_log(error, &req);
13477 return false;
13478#endif
13479 } else {
13480 // HTTP redirect
13481 ClientImpl redirect_client(host, port);
13482
13483 // Setup client with robust configuration
13484 setup_redirect_client(redirect_client);
13485
13486 // Execute the redirect
13487 return detail::redirect(redirect_client, req, res, path, location, error);
13488 }
13489}
13490
13491// New method for robust client setup (based on basic_manual_redirect.cpp
13492// logic)
13493template <typename ClientType>
13494inline void ClientImpl::setup_redirect_client(ClientType &client) {
13495 // Copy basic settings first
13496 client.set_connection_timeout(connection_timeout_sec_);
13497 client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
13498 client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
13499 client.set_keep_alive(keep_alive_);
13500 client.set_follow_location(
13501 true); // Enable redirects to handle multi-step redirects
13502 client.set_path_encode(path_encode_);
13503 client.set_compress(compress_);
13504 client.set_decompress(decompress_);
13505
13506 // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
13507 // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
13508 // 15.4, credentials must not be forwarded when redirecting to a different
13509 // host. This function is only called for cross-host redirects; same-host
13510 // redirects are handled directly in ClientImpl::redirect().
13511
13512 // Copy the proxy configuration unconditionally; the per-target bypass is
13513 // re-evaluated at send time, so a later hop to a non-bypassed host can
13514 // still use the proxy.
13515 client.no_proxy_entries_ = no_proxy_entries_;
13516 if (!proxy_host_.empty() && proxy_port_ != -1) {
13517 client.set_proxy(proxy_host_, proxy_port_);
13518
13519 if (!proxy_basic_auth_username_.empty()) {
13520 client.set_proxy_basic_auth(proxy_basic_auth_username_,
13521 proxy_basic_auth_password_);
13522 }
13523 if (!proxy_bearer_token_auth_token_.empty()) {
13524 client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
13525 }
13526#ifdef CPPHTTPLIB_SSL_ENABLED
13527 if (!proxy_digest_auth_username_.empty()) {
13528 client.set_proxy_digest_auth(proxy_digest_auth_username_,
13529 proxy_digest_auth_password_);
13530 }
13531#endif
13532 }
13533
13534 // Copy network and socket settings
13535 client.set_address_family(address_family_);
13536 client.set_tcp_nodelay(tcp_nodelay_);
13537 client.set_ipv6_v6only(ipv6_v6only_);
13538 if (socket_options_) { client.set_socket_options(socket_options_); }
13539 if (!interface_.empty()) { client.set_interface(interface_); }
13540
13541 // Copy logging and headers
13542 if (logger_) { client.set_logger(logger_); }
13543 if (error_logger_) { client.set_error_logger(error_logger_); }
13544
13545 // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
13546 // Each new client should generate its own headers based on its target host
13547}
13548
13549inline bool ClientImpl::write_content_with_provider(Stream &strm,
13550 const Request &req,
13551 Error &error) const {
13552 auto is_shutting_down = []() { return false; };
13553
13554 if (req.is_chunked_content_provider_) {
13555 auto compressor = compress_ ? detail::create_compressor().first
13556 : std::unique_ptr<detail::compressor>();
13557 if (!compressor) {
13558 compressor = detail::make_unique<detail::nocompressor>();
13559 }
13560
13561 return detail::write_content_chunked(strm, req.content_provider_,
13562 is_shutting_down, *compressor, error);
13563 } else {
13564 return detail::write_content_with_progress(
13565 strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
13566 req.upload_progress, error);
13567 }
13568}
13569
13570inline bool ClientImpl::write_request(Stream &strm, Request &req,
13571 bool close_connection, Error &error,
13572 bool skip_body) {
13573 // Prepare additional headers
13574 if (close_connection) {
13575 if (!req.has_header("Connection")) {
13576 req.set_header("Connection", "close");
13577 }
13578 }
13579
13580 std::string ct_for_defaults;
13581 if (!req.has_header("Content-Type") && !req.body.empty()) {
13582 ct_for_defaults = "text/plain";
13583 }
13584 prepare_default_headers(req, false, ct_for_defaults);
13585
13586 if (req.body.empty()) {
13587 if (req.content_provider_) {
13588 if (!req.is_chunked_content_provider_) {
13589 if (!req.has_header("Content-Length")) {
13590 auto length = std::to_string(req.content_length_);
13591 req.set_header("Content-Length", length);
13592 }
13593 }
13594 } else {
13595 if (req.method == "POST" || req.method == "PUT" ||
13596 req.method == "PATCH") {
13597 req.set_header("Content-Length", "0");
13598 }
13599 }
13600 }
13601
13602 if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
13603 if (!req.has_header("Authorization")) {
13604 req.headers.insert(make_basic_authentication_header(
13605 basic_auth_username_, basic_auth_password_, false));
13606 }
13607 }
13608
13609 if (!bearer_token_auth_token_.empty()) {
13610 if (!req.has_header("Authorization")) {
13611 req.headers.insert(make_bearer_token_authentication_header(
13612 bearer_token_auth_token_, false));
13613 }
13614 }
13615
13616 // Proxy-Authorization is only sent when the proxy is actually used for
13617 // this target — otherwise NO_PROXY-matched requests would leak proxy
13618 // credentials directly to the destination server.
13619 if (is_proxy_enabled_for_host(host_)) {
13620 if (!proxy_basic_auth_username_.empty() &&
13621 !proxy_basic_auth_password_.empty() &&
13622 !req.has_header("Proxy-Authorization")) {
13623 req.headers.insert(make_basic_authentication_header(
13624 proxy_basic_auth_username_, proxy_basic_auth_password_, true));
13625 }
13626 if (!proxy_bearer_token_auth_token_.empty() &&
13627 !req.has_header("Proxy-Authorization")) {
13628 req.headers.insert(make_bearer_token_authentication_header(
13629 proxy_bearer_token_auth_token_, true));
13630 }
13631 }
13632
13633 // Request line and headers
13634 {
13635 detail::BufferStream bstrm;
13636
13637 // Extract path and query from req.path
13638 std::string path_part, query_part;
13639 auto query_pos = req.path.find('?');
13640 if (query_pos != std::string::npos) {
13641 path_part = req.path.substr(0, query_pos);
13642 query_part = req.path.substr(query_pos + 1);
13643 } else {
13644 path_part = req.path;
13645 query_part = "";
13646 }
13647
13648 // Encode path part. If the original `req.path` already contained a
13649 // query component, preserve its raw query string (including parameter
13650 // order) instead of reparsing and reassembling it which may reorder
13651 // parameters due to container ordering (e.g. `Params` uses
13652 // `std::multimap`). When there is no query in `req.path`, fall back to
13653 // building a query from `req.params` so existing callers that pass
13654 // `Params` continue to work.
13655 auto path_with_query =
13656 path_encode_ ? detail::encode_path(path_part) : path_part;
13657
13658 if (!query_part.empty()) {
13659 // Normalize the query string (decode then re-encode) while preserving
13660 // the original parameter order.
13661 auto normalized = detail::normalize_query_string(query_part);
13662 if (!normalized.empty()) { path_with_query += '?' + normalized; }
13663
13664 // Still populate req.params for handlers/users who read them.
13665 detail::parse_query_text(query_part, req.params);
13666 } else {
13667 // No query in path; parse any query_part (empty) and append params
13668 // from `req.params` when present (preserves prior behavior for
13669 // callers who provide Params separately).
13670 detail::parse_query_text(query_part, req.params);
13671 if (!req.params.empty()) {
13672 path_with_query = append_query_params(path_with_query, req.params);
13673 }
13674 }
13675
13676 // Write request line and headers
13677 detail::write_request_line(bstrm, req.method, path_with_query);
13678 if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_,
13679 error)) {
13680 output_error_log(error, &req);
13681 return false;
13682 }
13683
13684 // Flush buffer
13685 auto &data = bstrm.get_buffer();
13686 if (!detail::write_data(strm, data.data(), data.size())) {
13687 error = Error::Write;
13688 output_error_log(error, &req);
13689 return false;
13690 }
13691 }
13692
13693 // After sending request line and headers, wait briefly for an early server
13694 // response (e.g. 4xx) and avoid sending a potentially large request body
13695 // unnecessarily. This workaround is only enabled on Windows because Unix
13696 // platforms surface write errors (EPIPE) earlier; on Windows kernel send
13697 // buffering can accept large writes even when the peer already responded.
13698 // Check the stream first (which covers SSL via `is_readable()`), then
13699 // fall back to select on the socket. Only perform the wait for very large
13700 // request bodies to avoid interfering with normal small requests and
13701 // reduce side-effects. Poll briefly (up to 50ms as default) for an early
13702 // response. Skip this check when using Expect: 100-continue, as the protocol
13703 // handles early responses properly.
13704#if defined(_WIN32)
13705 if (!skip_body &&
13707 req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
13708 auto start = std::chrono::high_resolution_clock::now();
13709
13710 for (;;) {
13711 // Prefer socket-level readiness to avoid SSL_pending() false-positives
13712 // from SSL internals. If the underlying socket is readable, assume an
13713 // early response may be present.
13714 auto sock = strm.socket();
13715 if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
13716 return false;
13717 }
13718
13719 // Fallback to stream-level check for non-socket streams or when the
13720 // socket isn't reporting readable. Avoid using `is_readable()` for
13721 // SSL, since `SSL_pending()` may report buffered records that do not
13722 // indicate a complete application-level response yet.
13723 if (!is_ssl() && strm.is_readable()) { return false; }
13724
13725 auto now = std::chrono::high_resolution_clock::now();
13726 auto elapsed =
13727 std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
13728 .count();
13730 break;
13731 }
13732
13733 std::this_thread::sleep_for(std::chrono::milliseconds(1));
13734 }
13735 }
13736#endif
13737
13738 // Body
13739 if (skip_body) { return true; }
13740
13741 return write_request_body(strm, req, error);
13742}
13743
13744inline bool ClientImpl::write_request_body(Stream &strm, Request &req,
13745 Error &error) {
13746 if (req.body.empty()) {
13747 return write_content_with_provider(strm, req, error);
13748 }
13749
13750 if (req.upload_progress) {
13751 auto body_size = req.body.size();
13752 size_t written = 0;
13753 auto data = req.body.data();
13754
13755 while (written < body_size) {
13756 size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
13757 if (!detail::write_data(strm, data + written, to_write)) {
13758 error = Error::Write;
13759 output_error_log(error, &req);
13760 return false;
13761 }
13762 written += to_write;
13763
13764 if (!req.upload_progress(written, body_size)) {
13765 error = Error::Canceled;
13766 output_error_log(error, &req);
13767 return false;
13768 }
13769 }
13770 } else {
13771 if (!detail::write_data(strm, req.body.data(), req.body.size())) {
13772 error = Error::Write;
13773 output_error_log(error, &req);
13774 return false;
13775 }
13776 }
13777
13778 return true;
13779}
13780
13781inline std::unique_ptr<Response>
13782ClientImpl::send_with_content_provider_and_receiver(
13783 Request &req, const char *body, size_t content_length,
13784 ContentProvider content_provider,
13785 ContentProviderWithoutLength content_provider_without_length,
13786 const std::string &content_type, ContentReceiver content_receiver,
13787 Error &error) {
13788 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
13789
13790 auto enc = compress_
13791 ? detail::create_compressor()
13792 : std::pair<std::unique_ptr<detail::compressor>, const char *>(
13793 nullptr, nullptr);
13794
13795 if (enc.second) { req.set_header("Content-Encoding", enc.second); }
13796
13797 if (enc.first && !content_provider_without_length) {
13798 auto &compressor = enc.first;
13799
13800 if (content_provider) {
13801 auto ok = true;
13802 size_t offset = 0;
13803 DataSink data_sink;
13804
13805 data_sink.write = [&](const char *data, size_t data_len) -> bool {
13806 if (ok) {
13807 auto last = offset + data_len == content_length;
13808
13809 auto ret = compressor->compress(
13810 data, data_len, last,
13811 [&](const char *compressed_data, size_t compressed_data_len) {
13812 req.body.append(compressed_data, compressed_data_len);
13813 return true;
13814 });
13815
13816 if (ret) {
13817 offset += data_len;
13818 } else {
13819 ok = false;
13820 }
13821 }
13822 return ok;
13823 };
13824
13825 while (ok && offset < content_length) {
13826 if (!content_provider(offset, content_length - offset, data_sink)) {
13827 error = Error::Canceled;
13828 output_error_log(error, &req);
13829 return nullptr;
13830 }
13831 }
13832 } else {
13833 if (!compressor->compress(body, content_length, true,
13834 [&](const char *data, size_t data_len) {
13835 req.body.append(data, data_len);
13836 return true;
13837 })) {
13838 error = Error::Compression;
13839 output_error_log(error, &req);
13840 return nullptr;
13841 }
13842 }
13843 } else {
13844 if (content_provider) {
13845 req.content_length_ = content_length;
13846 req.content_provider_ = std::move(content_provider);
13847 req.is_chunked_content_provider_ = false;
13848 } else if (content_provider_without_length) {
13849 req.content_length_ = 0;
13850 req.content_provider_ = detail::ContentProviderAdapter(
13851 std::move(content_provider_without_length));
13852 req.is_chunked_content_provider_ = true;
13853 req.set_header("Transfer-Encoding", "chunked");
13854 } else {
13855 req.body.assign(body, content_length);
13856 }
13857 }
13858
13859 if (content_receiver) {
13860 req.content_receiver =
13861 [content_receiver](const char *data, size_t data_length,
13862 size_t /*offset*/, size_t /*total_length*/) {
13863 return content_receiver(data, data_length);
13864 };
13865 }
13866
13867 auto res = detail::make_unique<Response>();
13868 return send(req, *res, error) ? std::move(res) : nullptr;
13869}
13870
13871inline Result ClientImpl::send_with_content_provider_and_receiver(
13872 const std::string &method, const std::string &path, const Headers &headers,
13873 const char *body, size_t content_length, ContentProvider content_provider,
13874 ContentProviderWithoutLength content_provider_without_length,
13875 const std::string &content_type, ContentReceiver content_receiver,
13876 UploadProgress progress) {
13877 Request req;
13878 req.method = method;
13879 req.headers = headers;
13880 req.path = path;
13881 req.upload_progress = std::move(progress);
13882 if (max_timeout_msec_ > 0) {
13883 req.start_time_ = std::chrono::steady_clock::now();
13884 }
13885
13886 auto error = Error::Success;
13887
13888 auto res = send_with_content_provider_and_receiver(
13889 req, body, content_length, std::move(content_provider),
13890 std::move(content_provider_without_length), content_type,
13891 std::move(content_receiver), error);
13892
13893#ifdef CPPHTTPLIB_SSL_ENABLED
13894 return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
13895 last_backend_error_};
13896#else
13897 return Result{std::move(res), error, std::move(req.headers)};
13898#endif
13899}
13900
13901inline void ClientImpl::output_log(const Request &req,
13902 const Response &res) const {
13903 if (logger_) {
13904 std::lock_guard<std::mutex> guard(logger_mutex_);
13905 logger_(req, res);
13906 }
13907}
13908
13909inline void ClientImpl::output_error_log(const Error &err,
13910 const Request *req) const {
13911 if (error_logger_) {
13912 std::lock_guard<std::mutex> guard(logger_mutex_);
13913 error_logger_(err, req);
13914 }
13915}
13916
13917inline bool ClientImpl::process_request(Stream &strm, Request &req,
13918 Response &res, bool close_connection,
13919 Error &error) {
13920 // Auto-add Expect: 100-continue for large bodies
13921 if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
13922 auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
13923 if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
13924 req.set_header("Expect", "100-continue");
13925 }
13926 }
13927
13928 // Check for Expect: 100-continue
13929 auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
13930
13931 // Send request (skip body if using Expect: 100-continue)
13932 auto write_request_success =
13933 write_request(strm, req, close_connection, error, expect_100_continue);
13934
13935#ifdef CPPHTTPLIB_SSL_ENABLED
13936 if (is_ssl() && !expect_100_continue) {
13937 auto is_proxy_enabled = is_proxy_enabled_for_host(host_);
13938 if (!is_proxy_enabled) {
13939 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
13940 error = Error::SSLPeerCouldBeClosed_;
13941 output_error_log(error, &req);
13942 return false;
13943 }
13944 }
13945 }
13946#endif
13947
13948 // Handle Expect: 100-continue.
13949 //
13950 // Wait for an interim/early response by attempting to read the status line
13951 // under a short timeout, instead of trusting raw socket readability. Over
13952 // TLS, post-handshake records (e.g. session tickets) make the socket
13953 // readable without any HTTP response being available; relying on
13954 // `select_read` there caused the body to be withheld forever and the
13955 // request to fail with `Read` (#2458). If no status line arrives within the
13956 // timeout, send the body anyway (matching curl's behavior).
13957 auto status_line_read = false;
13958 if (expect_100_continue && write_request_success) {
13960 time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
13961 time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
13962 strm.set_read_timeout(sec, usec);
13963 status_line_read = read_response_line(strm, req, res, false);
13964 strm.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
13965 }
13966
13967 if (!status_line_read) {
13968 // No interim response within the timeout: send the body and handle the
13969 // response as usual.
13970 if (!write_request_body(strm, req, error)) { return false; }
13971 expect_100_continue = false; // Switch to normal response handling
13972 }
13973 }
13974
13975 // Receive response and headers
13976 // When using Expect: 100-continue, don't auto-skip `100 Continue` response
13977 if ((!status_line_read &&
13978 !read_response_line(strm, req, res, !expect_100_continue)) ||
13979 !detail::read_headers(strm, res.headers)) {
13980 if (write_request_success) { error = Error::Read; }
13981 output_error_log(error, &req);
13982 return false;
13983 }
13984
13985 if (!write_request_success) { return false; }
13986
13987 // Handle Expect: 100-continue response
13988 if (expect_100_continue) {
13989 if (res.status == StatusCode::Continue_100) {
13990 // Server accepted, send the body
13991 if (!write_request_body(strm, req, error)) { return false; }
13992
13993 // Read the actual response
13994 res.headers.clear();
13995 res.body.clear();
13996 if (!read_response_line(strm, req, res) ||
13997 !detail::read_headers(strm, res.headers)) {
13998 error = Error::Read;
13999 output_error_log(error, &req);
14000 return false;
14001 }
14002 }
14003 // If not 100 Continue, server returned an error; proceed with that response
14004 }
14005
14006 // Body
14007 if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
14008 req.method != "CONNECT") {
14009 auto redirect = 300 < res.status && res.status < 400 &&
14010 res.status != StatusCode::NotModified_304 &&
14011 follow_location_;
14012
14013 if (req.response_handler && !redirect) {
14014 if (!req.response_handler(res)) {
14015 error = Error::Canceled;
14016 output_error_log(error, &req);
14017 return false;
14018 }
14019 }
14020
14021 auto out =
14022 req.content_receiver
14023 ? static_cast<ContentReceiverWithProgress>(
14024 [&](const char *buf, size_t n, size_t off, size_t len) {
14025 if (redirect) { return true; }
14026 auto ret = req.content_receiver(buf, n, off, len);
14027 if (!ret) {
14028 error = Error::Canceled;
14029 output_error_log(error, &req);
14030 }
14031 return ret;
14032 })
14033 : static_cast<ContentReceiverWithProgress>(
14034 [&](const char *buf, size_t n, size_t /*off*/,
14035 size_t /*len*/) {
14036 assert(res.body.size() + n <= res.body.max_size());
14037 if (payload_max_length_ > 0 &&
14038 (res.body.size() >= payload_max_length_ ||
14039 n > payload_max_length_ - res.body.size())) {
14040 return false;
14041 }
14042 res.body.append(buf, n);
14043 return true;
14044 });
14045
14046 auto progress = [&](size_t current, size_t total) {
14047 if (!req.download_progress || redirect) { return true; }
14048 auto ret = req.download_progress(current, total);
14049 if (!ret) {
14050 error = Error::Canceled;
14051 output_error_log(error, &req);
14052 }
14053 return ret;
14054 };
14055
14056 if (res.has_header("Content-Length")) {
14057 if (!req.content_receiver) {
14058 auto len = res.get_header_value_u64("Content-Length");
14059 if (len > res.body.max_size()) {
14060 error = Error::Read;
14061 output_error_log(error, &req);
14062 return false;
14063 }
14064 // Cap the reservation by payload_max_length_ to avoid OOM when a
14065 // hostile or malformed server sends an enormous Content-Length.
14066 // The actual body read below is bounded by payload_max_length_,
14067 // so reserving more than that is never useful.
14068 auto reserve_len = static_cast<size_t>(len);
14069 if (payload_max_length_ > 0 && reserve_len > payload_max_length_) {
14070 reserve_len = payload_max_length_;
14071 }
14072 res.body.reserve(reserve_len);
14073 }
14074 }
14075
14076 if (res.status != StatusCode::NotModified_304) {
14077 int dummy_status;
14078 auto max_length = (!has_payload_max_length_ && req.content_receiver)
14079 ? (std::numeric_limits<size_t>::max)()
14080 : payload_max_length_;
14081 if (!detail::read_content(strm, res, max_length, dummy_status,
14082 std::move(progress), std::move(out),
14083 decompress_)) {
14084 if (error != Error::Canceled) { error = Error::Read; }
14085 output_error_log(error, &req);
14086 return false;
14087 }
14088 }
14089 }
14090
14091 // Log
14092 output_log(req, res);
14093
14094 return true;
14095}
14096
14097inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
14098 const std::string &boundary, const UploadFormDataItems &items,
14099 const FormDataProviderItems &provider_items) const {
14100 size_t cur_item = 0;
14101 size_t cur_start = 0;
14102 // cur_item and cur_start are copied to within the std::function and
14103 // maintain state between successive calls
14104 return [&, cur_item, cur_start](size_t offset,
14105 DataSink &sink) mutable -> bool {
14106 if (!offset && !items.empty()) {
14107 sink.os << detail::serialize_multipart_formdata(items, boundary, false);
14108 return true;
14109 } else if (cur_item < provider_items.size()) {
14110 if (!cur_start) {
14111 const auto &begin = detail::serialize_multipart_formdata_item_begin(
14112 provider_items[cur_item], boundary);
14113 offset += begin.size();
14114 cur_start = offset;
14115 sink.os << begin;
14116 }
14117
14118 DataSink cur_sink;
14119 auto has_data = true;
14120 cur_sink.write = sink.write;
14121 cur_sink.done = [&]() { has_data = false; };
14122
14123 if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
14124 return false;
14125 }
14126
14127 if (!has_data) {
14128 sink.os << detail::serialize_multipart_formdata_item_end();
14129 cur_item++;
14130 cur_start = 0;
14131 }
14132 return true;
14133 } else {
14134 sink.os << detail::serialize_multipart_formdata_finish(boundary);
14135 sink.done();
14136 return true;
14137 }
14138 };
14139}
14140
14141inline bool ClientImpl::process_socket(
14142 const Socket &socket,
14143 std::chrono::time_point<std::chrono::steady_clock> start_time,
14144 std::function<bool(Stream &strm)> callback) {
14145 return detail::process_client_socket(
14146 socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
14147 write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
14148}
14149
14150inline bool ClientImpl::is_ssl() const { return false; }
14151
14152inline Result ClientImpl::Get(const std::string &path,
14153 DownloadProgress progress) {
14154 return Get(path, Headers(), std::move(progress));
14155}
14156
14157inline Result ClientImpl::Get(const std::string &path, const Params &params,
14158 const Headers &headers,
14159 DownloadProgress progress) {
14160 if (params.empty()) { return Get(path, headers); }
14161
14162 std::string path_with_query = append_query_params(path, params);
14163 return Get(path_with_query, headers, std::move(progress));
14164}
14165
14166inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14167 DownloadProgress progress) {
14168 Request req;
14169 req.method = "GET";
14170 req.path = path;
14171 req.headers = headers;
14172 req.download_progress = std::move(progress);
14173 if (max_timeout_msec_ > 0) {
14174 req.start_time_ = std::chrono::steady_clock::now();
14175 }
14176
14177 return send_(std::move(req));
14178}
14179
14180inline Result ClientImpl::Get(const std::string &path,
14181 ContentReceiver content_receiver,
14182 DownloadProgress progress) {
14183 return Get(path, Headers(), nullptr, std::move(content_receiver),
14184 std::move(progress));
14185}
14186
14187inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14188 ContentReceiver content_receiver,
14189 DownloadProgress progress) {
14190 return Get(path, headers, nullptr, std::move(content_receiver),
14191 std::move(progress));
14192}
14193
14194inline Result ClientImpl::Get(const std::string &path,
14195 ResponseHandler response_handler,
14196 ContentReceiver content_receiver,
14197 DownloadProgress progress) {
14198 return Get(path, Headers(), std::move(response_handler),
14199 std::move(content_receiver), std::move(progress));
14200}
14201
14202inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14203 ResponseHandler response_handler,
14204 ContentReceiver content_receiver,
14205 DownloadProgress progress) {
14206 Request req;
14207 req.method = "GET";
14208 req.path = path;
14209 req.headers = headers;
14210 req.response_handler = std::move(response_handler);
14211 req.content_receiver =
14212 [content_receiver](const char *data, size_t data_length,
14213 size_t /*offset*/, size_t /*total_length*/) {
14214 return content_receiver(data, data_length);
14215 };
14216 req.download_progress = std::move(progress);
14217 if (max_timeout_msec_ > 0) {
14218 req.start_time_ = std::chrono::steady_clock::now();
14219 }
14220
14221 return send_(std::move(req));
14222}
14223
14224inline Result ClientImpl::Get(const std::string &path, const Params &params,
14225 const Headers &headers,
14226 ContentReceiver content_receiver,
14227 DownloadProgress progress) {
14228 return Get(path, params, headers, nullptr, std::move(content_receiver),
14229 std::move(progress));
14230}
14231
14232inline Result ClientImpl::Get(const std::string &path, const Params &params,
14233 const Headers &headers,
14234 ResponseHandler response_handler,
14235 ContentReceiver content_receiver,
14236 DownloadProgress progress) {
14237 if (params.empty()) {
14238 return Get(path, headers, std::move(response_handler),
14239 std::move(content_receiver), std::move(progress));
14240 }
14241
14242 std::string path_with_query = append_query_params(path, params);
14243 return Get(path_with_query, headers, std::move(response_handler),
14244 std::move(content_receiver), std::move(progress));
14245}
14246
14247inline Result ClientImpl::Head(const std::string &path) {
14248 return Head(path, Headers());
14249}
14250
14251inline Result ClientImpl::Head(const std::string &path,
14252 const Headers &headers) {
14253 Request req;
14254 req.method = "HEAD";
14255 req.headers = headers;
14256 req.path = path;
14257 if (max_timeout_msec_ > 0) {
14258 req.start_time_ = std::chrono::steady_clock::now();
14259 }
14260
14261 return send_(std::move(req));
14262}
14263
14264inline Result ClientImpl::Post(const std::string &path) {
14265 return Post(path, std::string(), std::string());
14266}
14267
14268inline Result ClientImpl::Post(const std::string &path,
14269 const Headers &headers) {
14270 return Post(path, headers, nullptr, 0, std::string());
14271}
14272
14273inline Result ClientImpl::Post(const std::string &path, const char *body,
14274 size_t content_length,
14275 const std::string &content_type,
14276 UploadProgress progress) {
14277 return Post(path, Headers(), body, content_length, content_type, progress);
14278}
14279
14280inline Result ClientImpl::Post(const std::string &path, const std::string &body,
14281 const std::string &content_type,
14282 UploadProgress progress) {
14283 return Post(path, Headers(), body, content_type, progress);
14284}
14285
14286inline Result ClientImpl::Post(const std::string &path, const Params &params) {
14287 return Post(path, Headers(), params);
14288}
14289
14290inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14291 ContentProvider content_provider,
14292 const std::string &content_type,
14293 UploadProgress progress) {
14294 return Post(path, Headers(), content_length, std::move(content_provider),
14295 content_type, progress);
14296}
14297
14298inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14299 ContentProvider content_provider,
14300 const std::string &content_type,
14301 ContentReceiver content_receiver,
14302 UploadProgress progress) {
14303 return Post(path, Headers(), content_length, std::move(content_provider),
14304 content_type, std::move(content_receiver), progress);
14305}
14306
14307inline Result ClientImpl::Post(const std::string &path,
14308 ContentProviderWithoutLength content_provider,
14309 const std::string &content_type,
14310 UploadProgress progress) {
14311 return Post(path, Headers(), std::move(content_provider), content_type,
14312 progress);
14313}
14314
14315inline Result ClientImpl::Post(const std::string &path,
14316 ContentProviderWithoutLength content_provider,
14317 const std::string &content_type,
14318 ContentReceiver content_receiver,
14319 UploadProgress progress) {
14320 return Post(path, Headers(), std::move(content_provider), content_type,
14321 std::move(content_receiver), progress);
14322}
14323
14324inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14325 const Params &params) {
14326 auto query = detail::params_to_query_str(params);
14327 return Post(path, headers, query, "application/x-www-form-urlencoded");
14328}
14329
14330inline Result ClientImpl::Post(const std::string &path,
14331 const UploadFormDataItems &items,
14332 UploadProgress progress) {
14333 return Post(path, Headers(), items, progress);
14334}
14335
14336inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14337 const UploadFormDataItems &items,
14338 UploadProgress progress) {
14339 const auto &boundary = detail::make_multipart_data_boundary();
14340 const auto &content_type =
14341 detail::serialize_multipart_formdata_get_content_type(boundary);
14342 auto content_length = detail::get_multipart_content_length(items, boundary);
14343 return Post(path, headers, content_length,
14344 detail::make_multipart_content_provider(items, boundary),
14345 content_type, progress);
14346}
14347
14348inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14349 const UploadFormDataItems &items,
14350 const std::string &boundary,
14351 UploadProgress progress) {
14352 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14353 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14354 }
14355
14356 const auto &content_type =
14357 detail::serialize_multipart_formdata_get_content_type(boundary);
14358 auto content_length = detail::get_multipart_content_length(items, boundary);
14359 return Post(path, headers, content_length,
14360 detail::make_multipart_content_provider(items, boundary),
14361 content_type, progress);
14362}
14363
14364inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14365 const char *body, size_t content_length,
14366 const std::string &content_type,
14367 UploadProgress progress) {
14368 return send_with_content_provider_and_receiver(
14369 "POST", path, headers, body, content_length, nullptr, nullptr,
14370 content_type, nullptr, progress);
14371}
14372
14373inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14374 const std::string &body,
14375 const std::string &content_type,
14376 UploadProgress progress) {
14377 return send_with_content_provider_and_receiver(
14378 "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
14379 content_type, nullptr, progress);
14380}
14381
14382inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14383 size_t content_length,
14384 ContentProvider content_provider,
14385 const std::string &content_type,
14386 UploadProgress progress) {
14387 return send_with_content_provider_and_receiver(
14388 "POST", path, headers, nullptr, content_length,
14389 std::move(content_provider), nullptr, content_type, nullptr, progress);
14390}
14391
14392inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14393 size_t content_length,
14394 ContentProvider content_provider,
14395 const std::string &content_type,
14396 ContentReceiver content_receiver,
14397 DownloadProgress progress) {
14398 return send_with_content_provider_and_receiver(
14399 "POST", path, headers, nullptr, content_length,
14400 std::move(content_provider), nullptr, content_type,
14401 std::move(content_receiver), std::move(progress));
14402}
14403
14404inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14405 ContentProviderWithoutLength content_provider,
14406 const std::string &content_type,
14407 UploadProgress progress) {
14408 return send_with_content_provider_and_receiver(
14409 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14410 content_type, nullptr, progress);
14411}
14412
14413inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14414 ContentProviderWithoutLength content_provider,
14415 const std::string &content_type,
14416 ContentReceiver content_receiver,
14417 DownloadProgress progress) {
14418 return send_with_content_provider_and_receiver(
14419 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14420 content_type, std::move(content_receiver), std::move(progress));
14421}
14422
14423inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14424 const UploadFormDataItems &items,
14425 const FormDataProviderItems &provider_items,
14426 UploadProgress progress) {
14427 const auto &boundary = detail::make_multipart_data_boundary();
14428 const auto &content_type =
14429 detail::serialize_multipart_formdata_get_content_type(boundary);
14430 return send_with_content_provider_and_receiver(
14431 "POST", path, headers, nullptr, 0, nullptr,
14432 get_multipart_content_provider(boundary, items, provider_items),
14433 content_type, nullptr, progress);
14434}
14435
14436inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14437 const std::string &body,
14438 const std::string &content_type,
14439 ContentReceiver content_receiver,
14440 DownloadProgress progress) {
14441 Request req;
14442 req.method = "POST";
14443 req.path = path;
14444 req.headers = headers;
14445 req.body = body;
14446 req.content_receiver =
14447 [content_receiver](const char *data, size_t data_length,
14448 size_t /*offset*/, size_t /*total_length*/) {
14449 return content_receiver(data, data_length);
14450 };
14451 req.download_progress = std::move(progress);
14452
14453 if (max_timeout_msec_ > 0) {
14454 req.start_time_ = std::chrono::steady_clock::now();
14455 }
14456
14457 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14458
14459 return send_(std::move(req));
14460}
14461
14462inline Result ClientImpl::Put(const std::string &path) {
14463 return Put(path, std::string(), std::string());
14464}
14465
14466inline Result ClientImpl::Put(const std::string &path, const Headers &headers) {
14467 return Put(path, headers, nullptr, 0, std::string());
14468}
14469
14470inline Result ClientImpl::Put(const std::string &path, const char *body,
14471 size_t content_length,
14472 const std::string &content_type,
14473 UploadProgress progress) {
14474 return Put(path, Headers(), body, content_length, content_type, progress);
14475}
14476
14477inline Result ClientImpl::Put(const std::string &path, const std::string &body,
14478 const std::string &content_type,
14479 UploadProgress progress) {
14480 return Put(path, Headers(), body, content_type, progress);
14481}
14482
14483inline Result ClientImpl::Put(const std::string &path, const Params &params) {
14484 return Put(path, Headers(), params);
14485}
14486
14487inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14488 ContentProvider content_provider,
14489 const std::string &content_type,
14490 UploadProgress progress) {
14491 return Put(path, Headers(), content_length, std::move(content_provider),
14492 content_type, progress);
14493}
14494
14495inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14496 ContentProvider content_provider,
14497 const std::string &content_type,
14498 ContentReceiver content_receiver,
14499 UploadProgress progress) {
14500 return Put(path, Headers(), content_length, std::move(content_provider),
14501 content_type, std::move(content_receiver), progress);
14502}
14503
14504inline Result ClientImpl::Put(const std::string &path,
14505 ContentProviderWithoutLength content_provider,
14506 const std::string &content_type,
14507 UploadProgress progress) {
14508 return Put(path, Headers(), std::move(content_provider), content_type,
14509 progress);
14510}
14511
14512inline Result ClientImpl::Put(const std::string &path,
14513 ContentProviderWithoutLength content_provider,
14514 const std::string &content_type,
14515 ContentReceiver content_receiver,
14516 UploadProgress progress) {
14517 return Put(path, Headers(), std::move(content_provider), content_type,
14518 std::move(content_receiver), progress);
14519}
14520
14521inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14522 const Params &params) {
14523 auto query = detail::params_to_query_str(params);
14524 return Put(path, headers, query, "application/x-www-form-urlencoded");
14525}
14526
14527inline Result ClientImpl::Put(const std::string &path,
14528 const UploadFormDataItems &items,
14529 UploadProgress progress) {
14530 return Put(path, Headers(), items, progress);
14531}
14532
14533inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14534 const UploadFormDataItems &items,
14535 UploadProgress progress) {
14536 const auto &boundary = detail::make_multipart_data_boundary();
14537 const auto &content_type =
14538 detail::serialize_multipart_formdata_get_content_type(boundary);
14539 auto content_length = detail::get_multipart_content_length(items, boundary);
14540 return Put(path, headers, content_length,
14541 detail::make_multipart_content_provider(items, boundary),
14542 content_type, progress);
14543}
14544
14545inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14546 const UploadFormDataItems &items,
14547 const std::string &boundary,
14548 UploadProgress progress) {
14549 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14550 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14551 }
14552
14553 const auto &content_type =
14554 detail::serialize_multipart_formdata_get_content_type(boundary);
14555 auto content_length = detail::get_multipart_content_length(items, boundary);
14556 return Put(path, headers, content_length,
14557 detail::make_multipart_content_provider(items, boundary),
14558 content_type, progress);
14559}
14560
14561inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14562 const char *body, size_t content_length,
14563 const std::string &content_type,
14564 UploadProgress progress) {
14565 return send_with_content_provider_and_receiver(
14566 "PUT", path, headers, body, content_length, nullptr, nullptr,
14567 content_type, nullptr, progress);
14568}
14569
14570inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14571 const std::string &body,
14572 const std::string &content_type,
14573 UploadProgress progress) {
14574 return send_with_content_provider_and_receiver(
14575 "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
14576 content_type, nullptr, progress);
14577}
14578
14579inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14580 size_t content_length,
14581 ContentProvider content_provider,
14582 const std::string &content_type,
14583 UploadProgress progress) {
14584 return send_with_content_provider_and_receiver(
14585 "PUT", path, headers, nullptr, content_length,
14586 std::move(content_provider), nullptr, content_type, nullptr, progress);
14587}
14588
14589inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14590 size_t content_length,
14591 ContentProvider content_provider,
14592 const std::string &content_type,
14593 ContentReceiver content_receiver,
14594 UploadProgress progress) {
14595 return send_with_content_provider_and_receiver(
14596 "PUT", path, headers, nullptr, content_length,
14597 std::move(content_provider), nullptr, content_type,
14598 std::move(content_receiver), progress);
14599}
14600
14601inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14602 ContentProviderWithoutLength content_provider,
14603 const std::string &content_type,
14604 UploadProgress progress) {
14605 return send_with_content_provider_and_receiver(
14606 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14607 content_type, nullptr, progress);
14608}
14609
14610inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14611 ContentProviderWithoutLength content_provider,
14612 const std::string &content_type,
14613 ContentReceiver content_receiver,
14614 UploadProgress progress) {
14615 return send_with_content_provider_and_receiver(
14616 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14617 content_type, std::move(content_receiver), progress);
14618}
14619
14620inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14621 const UploadFormDataItems &items,
14622 const FormDataProviderItems &provider_items,
14623 UploadProgress progress) {
14624 const auto &boundary = detail::make_multipart_data_boundary();
14625 const auto &content_type =
14626 detail::serialize_multipart_formdata_get_content_type(boundary);
14627 return send_with_content_provider_and_receiver(
14628 "PUT", path, headers, nullptr, 0, nullptr,
14629 get_multipart_content_provider(boundary, items, provider_items),
14630 content_type, nullptr, progress);
14631}
14632
14633inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14634 const std::string &body,
14635 const std::string &content_type,
14636 ContentReceiver content_receiver,
14637 DownloadProgress progress) {
14638 Request req;
14639 req.method = "PUT";
14640 req.path = path;
14641 req.headers = headers;
14642 req.body = body;
14643 req.content_receiver =
14644 [content_receiver](const char *data, size_t data_length,
14645 size_t /*offset*/, size_t /*total_length*/) {
14646 return content_receiver(data, data_length);
14647 };
14648 req.download_progress = std::move(progress);
14649
14650 if (max_timeout_msec_ > 0) {
14651 req.start_time_ = std::chrono::steady_clock::now();
14652 }
14653
14654 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14655
14656 return send_(std::move(req));
14657}
14658
14659inline Result ClientImpl::Patch(const std::string &path) {
14660 return Patch(path, std::string(), std::string());
14661}
14662
14663inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14664 UploadProgress progress) {
14665 return Patch(path, headers, nullptr, 0, std::string(), progress);
14666}
14667
14668inline Result ClientImpl::Patch(const std::string &path, const char *body,
14669 size_t content_length,
14670 const std::string &content_type,
14671 UploadProgress progress) {
14672 return Patch(path, Headers(), body, content_length, content_type, progress);
14673}
14674
14675inline Result ClientImpl::Patch(const std::string &path,
14676 const std::string &body,
14677 const std::string &content_type,
14678 UploadProgress progress) {
14679 return Patch(path, Headers(), body, content_type, progress);
14680}
14681
14682inline Result ClientImpl::Patch(const std::string &path, const Params &params) {
14683 return Patch(path, Headers(), params);
14684}
14685
14686inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14687 ContentProvider content_provider,
14688 const std::string &content_type,
14689 UploadProgress progress) {
14690 return Patch(path, Headers(), content_length, std::move(content_provider),
14691 content_type, progress);
14692}
14693
14694inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14695 ContentProvider content_provider,
14696 const std::string &content_type,
14697 ContentReceiver content_receiver,
14698 UploadProgress progress) {
14699 return Patch(path, Headers(), content_length, std::move(content_provider),
14700 content_type, std::move(content_receiver), progress);
14701}
14702
14703inline Result ClientImpl::Patch(const std::string &path,
14704 ContentProviderWithoutLength content_provider,
14705 const std::string &content_type,
14706 UploadProgress progress) {
14707 return Patch(path, Headers(), std::move(content_provider), content_type,
14708 progress);
14709}
14710
14711inline Result ClientImpl::Patch(const std::string &path,
14712 ContentProviderWithoutLength content_provider,
14713 const std::string &content_type,
14714 ContentReceiver content_receiver,
14715 UploadProgress progress) {
14716 return Patch(path, Headers(), std::move(content_provider), content_type,
14717 std::move(content_receiver), progress);
14718}
14719
14720inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14721 const Params &params) {
14722 auto query = detail::params_to_query_str(params);
14723 return Patch(path, headers, query, "application/x-www-form-urlencoded");
14724}
14725
14726inline Result ClientImpl::Patch(const std::string &path,
14727 const UploadFormDataItems &items,
14728 UploadProgress progress) {
14729 return Patch(path, Headers(), items, progress);
14730}
14731
14732inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14733 const UploadFormDataItems &items,
14734 UploadProgress progress) {
14735 const auto &boundary = detail::make_multipart_data_boundary();
14736 const auto &content_type =
14737 detail::serialize_multipart_formdata_get_content_type(boundary);
14738 auto content_length = detail::get_multipart_content_length(items, boundary);
14739 return Patch(path, headers, content_length,
14740 detail::make_multipart_content_provider(items, boundary),
14741 content_type, progress);
14742}
14743
14744inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14745 const UploadFormDataItems &items,
14746 const std::string &boundary,
14747 UploadProgress progress) {
14748 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14749 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14750 }
14751
14752 const auto &content_type =
14753 detail::serialize_multipart_formdata_get_content_type(boundary);
14754 auto content_length = detail::get_multipart_content_length(items, boundary);
14755 return Patch(path, headers, content_length,
14756 detail::make_multipart_content_provider(items, boundary),
14757 content_type, progress);
14758}
14759
14760inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14761 const char *body, size_t content_length,
14762 const std::string &content_type,
14763 UploadProgress progress) {
14764 return send_with_content_provider_and_receiver(
14765 "PATCH", path, headers, body, content_length, nullptr, nullptr,
14766 content_type, nullptr, progress);
14767}
14768
14769inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14770 const std::string &body,
14771 const std::string &content_type,
14772 UploadProgress progress) {
14773 return send_with_content_provider_and_receiver(
14774 "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
14775 content_type, nullptr, progress);
14776}
14777
14778inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14779 size_t content_length,
14780 ContentProvider content_provider,
14781 const std::string &content_type,
14782 UploadProgress progress) {
14783 return send_with_content_provider_and_receiver(
14784 "PATCH", path, headers, nullptr, content_length,
14785 std::move(content_provider), nullptr, content_type, nullptr, progress);
14786}
14787
14788inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14789 size_t content_length,
14790 ContentProvider content_provider,
14791 const std::string &content_type,
14792 ContentReceiver content_receiver,
14793 UploadProgress progress) {
14794 return send_with_content_provider_and_receiver(
14795 "PATCH", path, headers, nullptr, content_length,
14796 std::move(content_provider), nullptr, content_type,
14797 std::move(content_receiver), progress);
14798}
14799
14800inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14801 ContentProviderWithoutLength content_provider,
14802 const std::string &content_type,
14803 UploadProgress progress) {
14804 return send_with_content_provider_and_receiver(
14805 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14806 content_type, nullptr, progress);
14807}
14808
14809inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14810 ContentProviderWithoutLength content_provider,
14811 const std::string &content_type,
14812 ContentReceiver content_receiver,
14813 UploadProgress progress) {
14814 return send_with_content_provider_and_receiver(
14815 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14816 content_type, std::move(content_receiver), progress);
14817}
14818
14819inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14820 const UploadFormDataItems &items,
14821 const FormDataProviderItems &provider_items,
14822 UploadProgress progress) {
14823 const auto &boundary = detail::make_multipart_data_boundary();
14824 const auto &content_type =
14825 detail::serialize_multipart_formdata_get_content_type(boundary);
14826 return send_with_content_provider_and_receiver(
14827 "PATCH", path, headers, nullptr, 0, nullptr,
14828 get_multipart_content_provider(boundary, items, provider_items),
14829 content_type, nullptr, progress);
14830}
14831
14832inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14833 const std::string &body,
14834 const std::string &content_type,
14835 ContentReceiver content_receiver,
14836 DownloadProgress progress) {
14837 Request req;
14838 req.method = "PATCH";
14839 req.path = path;
14840 req.headers = headers;
14841 req.body = body;
14842 req.content_receiver =
14843 [content_receiver](const char *data, size_t data_length,
14844 size_t /*offset*/, size_t /*total_length*/) {
14845 return content_receiver(data, data_length);
14846 };
14847 req.download_progress = std::move(progress);
14848
14849 if (max_timeout_msec_ > 0) {
14850 req.start_time_ = std::chrono::steady_clock::now();
14851 }
14852
14853 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14854
14855 return send_(std::move(req));
14856}
14857
14858inline Result ClientImpl::Delete(const std::string &path,
14859 DownloadProgress progress) {
14860 return Delete(path, Headers(), std::string(), std::string(), progress);
14861}
14862
14863inline Result ClientImpl::Delete(const std::string &path,
14864 const Headers &headers,
14865 DownloadProgress progress) {
14866 return Delete(path, headers, std::string(), std::string(), progress);
14867}
14868
14869inline Result ClientImpl::Delete(const std::string &path, const char *body,
14870 size_t content_length,
14871 const std::string &content_type,
14872 DownloadProgress progress) {
14873 return Delete(path, Headers(), body, content_length, content_type, progress);
14874}
14875
14876inline Result ClientImpl::Delete(const std::string &path,
14877 const std::string &body,
14878 const std::string &content_type,
14879 DownloadProgress progress) {
14880 return Delete(path, Headers(), body.data(), body.size(), content_type,
14881 progress);
14882}
14883
14884inline Result ClientImpl::Delete(const std::string &path,
14885 const Headers &headers,
14886 const std::string &body,
14887 const std::string &content_type,
14888 DownloadProgress progress) {
14889 return Delete(path, headers, body.data(), body.size(), content_type,
14890 progress);
14891}
14892
14893inline Result ClientImpl::Delete(const std::string &path, const Params &params,
14894 DownloadProgress progress) {
14895 return Delete(path, Headers(), params, progress);
14896}
14897
14898inline Result ClientImpl::Delete(const std::string &path,
14899 const Headers &headers, const Params &params,
14900 DownloadProgress progress) {
14901 auto query = detail::params_to_query_str(params);
14902 return Delete(path, headers, query, "application/x-www-form-urlencoded",
14903 progress);
14904}
14905
14906inline Result ClientImpl::Delete(const std::string &path,
14907 const Headers &headers, const char *body,
14908 size_t content_length,
14909 const std::string &content_type,
14910 DownloadProgress progress) {
14911 Request req;
14912 req.method = "DELETE";
14913 req.headers = headers;
14914 req.path = path;
14915 req.download_progress = std::move(progress);
14916 if (max_timeout_msec_ > 0) {
14917 req.start_time_ = std::chrono::steady_clock::now();
14918 }
14919
14920 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14921 req.body.assign(body, content_length);
14922
14923 return send_(std::move(req));
14924}
14925
14926inline Result ClientImpl::Options(const std::string &path) {
14927 return Options(path, Headers());
14928}
14929
14930inline Result ClientImpl::Options(const std::string &path,
14931 const Headers &headers) {
14932 Request req;
14933 req.method = "OPTIONS";
14934 req.headers = headers;
14935 req.path = path;
14936 if (max_timeout_msec_ > 0) {
14937 req.start_time_ = std::chrono::steady_clock::now();
14938 }
14939
14940 return send_(std::move(req));
14941}
14942
14943inline void ClientImpl::stop() {
14944 std::lock_guard<std::mutex> guard(socket_mutex_);
14945
14946 // If there is anything ongoing right now, the ONLY thread-safe thing we can
14947 // do is to shutdown_socket, so that threads using this socket suddenly
14948 // discover they can't read/write any more and error out. Everything else
14949 // (closing the socket, shutting ssl down) is unsafe because these actions
14950 // are not thread-safe.
14951 if (socket_requests_in_flight_ > 0) {
14952 shutdown_socket(socket_);
14953
14954 // Aside from that, we set a flag for the socket to be closed when we're
14955 // done.
14956 socket_should_be_closed_when_request_is_done_ = true;
14957 return;
14958 }
14959
14960 disconnect(/*gracefully=*/true);
14961}
14962
14963inline std::string ClientImpl::host() const { return host_; }
14964
14965inline int ClientImpl::port() const { return port_; }
14966
14967inline size_t ClientImpl::is_socket_open() const {
14968 std::lock_guard<std::mutex> guard(socket_mutex_);
14969 return socket_.is_open();
14970}
14971
14972inline socket_t ClientImpl::socket() const { return socket_.sock; }
14973
14974inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
14975 connection_timeout_sec_ = sec;
14976 connection_timeout_usec_ = usec;
14977}
14978
14979inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
14980 read_timeout_sec_ = sec;
14981 read_timeout_usec_ = usec;
14982}
14983
14984inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
14985 write_timeout_sec_ = sec;
14986 write_timeout_usec_ = usec;
14987}
14988
14989inline void ClientImpl::set_max_timeout(time_t msec) {
14990 max_timeout_msec_ = msec;
14991}
14992
14993inline void ClientImpl::set_basic_auth(const std::string &username,
14994 const std::string &password) {
14995 basic_auth_username_ = username;
14996 basic_auth_password_ = password;
14997}
14998
14999inline void ClientImpl::set_bearer_token_auth(const std::string &token) {
15000 bearer_token_auth_token_ = token;
15001}
15002
15003inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
15004
15005inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
15006
15007inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
15008
15009inline void
15010ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15011 addr_map_ = std::move(addr_map);
15012}
15013
15014inline void ClientImpl::set_default_headers(Headers headers) {
15015 default_headers_ = std::move(headers);
15016}
15017
15018inline void ClientImpl::set_header_writer(
15019 std::function<ssize_t(Stream &, Headers &)> const &writer) {
15020 header_writer_ = writer;
15021}
15022
15023inline void ClientImpl::set_address_family(int family) {
15024 address_family_ = family;
15025}
15026
15027inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
15028
15029inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
15030
15031inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
15032 socket_options_ = std::move(socket_options);
15033}
15034
15035inline void ClientImpl::set_compress(bool on) { compress_ = on; }
15036
15037inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
15038
15039inline void ClientImpl::set_payload_max_length(size_t length) {
15040 payload_max_length_ = length;
15041 has_payload_max_length_ = true;
15042}
15043
15044inline void ClientImpl::set_interface(const std::string &intf) {
15045 interface_ = intf;
15046}
15047
15048inline void ClientImpl::set_proxy(const std::string &host, int port) {
15049 proxy_host_ = host;
15050 proxy_port_ = port;
15051 std::lock_guard<std::mutex> guard(socket_mutex_);
15052 disconnect(/*gracefully=*/true);
15053}
15054
15055inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
15056 const std::string &password) {
15057 proxy_basic_auth_username_ = username;
15058 proxy_basic_auth_password_ = password;
15059}
15060
15061inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
15062 proxy_bearer_token_auth_token_ = token;
15063}
15064
15065inline void ClientImpl::set_no_proxy(const std::vector<std::string> &patterns) {
15066 std::vector<detail::NoProxyEntry> parsed;
15067 parsed.reserve(patterns.size());
15068 for (const auto &p : patterns) {
15069 auto trimmed = detail::trim_copy(p);
15070 if (trimmed.empty()) { continue; }
15071 detail::NoProxyEntry entry;
15072 if (detail::parse_no_proxy_entry(trimmed, entry)) {
15073 parsed.push_back(std::move(entry));
15074 }
15075 }
15076 no_proxy_entries_ = std::move(parsed);
15077 std::lock_guard<std::mutex> guard(socket_mutex_);
15078 disconnect(/*gracefully=*/true);
15079}
15080
15081#ifdef CPPHTTPLIB_SSL_ENABLED
15082inline void ClientImpl::set_digest_auth(const std::string &username,
15083 const std::string &password) {
15084 digest_auth_username_ = username;
15085 digest_auth_password_ = password;
15086}
15087
15088inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
15089 const std::string &ca_cert_dir_path) {
15090 ca_cert_file_path_ = ca_cert_file_path;
15091 ca_cert_dir_path_ = ca_cert_dir_path;
15092}
15093
15094inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
15095 const std::string &password) {
15096 proxy_digest_auth_username_ = username;
15097 proxy_digest_auth_password_ = password;
15098}
15099
15100inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
15101 server_certificate_verification_ = enabled;
15102}
15103
15104inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
15105 server_hostname_verification_ = enabled;
15106}
15107
15108inline void ClientImpl::enable_system_ca(bool enabled) {
15109 system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
15110}
15111#endif
15112
15113inline void ClientImpl::set_logger(Logger logger) {
15114 logger_ = std::move(logger);
15115}
15116
15117inline void ClientImpl::set_error_logger(ErrorLogger error_logger) {
15118 error_logger_ = std::move(error_logger);
15119}
15120
15121/*
15122 * SSL/TLS Common Implementation
15123 */
15124
15125inline ClientConnection::~ClientConnection() {
15126#ifdef CPPHTTPLIB_SSL_ENABLED
15127 if (session) {
15128 tls::shutdown(session, true);
15129 tls::free_session(session);
15130 session = nullptr;
15131 }
15132#endif
15133
15134 if (sock != INVALID_SOCKET) {
15135 detail::close_socket(sock);
15136 sock = INVALID_SOCKET;
15137 }
15138}
15139
15140// Universal client implementation
15141inline Client::Client(const std::string &scheme_host_port)
15142 : Client(scheme_host_port, std::string(), std::string()) {}
15143
15144inline Client::Client(const std::string &scheme_host_port,
15145 const std::string &client_cert_path,
15146 const std::string &client_key_path) {
15147 detail::UrlComponents uc;
15148 if (detail::parse_url(scheme_host_port, uc) && !uc.host.empty()) {
15149 auto &scheme = uc.scheme;
15150
15151#ifdef CPPHTTPLIB_SSL_ENABLED
15152 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
15153#else
15154 if (!scheme.empty() && scheme != "http") {
15155#endif
15156#ifndef CPPHTTPLIB_NO_EXCEPTIONS
15157 std::string msg = "'" + scheme + "' scheme is not supported.";
15158 throw std::invalid_argument(msg);
15159#endif
15160 return;
15161 }
15162
15163 auto is_ssl = scheme == "https";
15164
15165 auto host = std::move(uc.host);
15166
15167 auto port = is_ssl ? 443 : 80;
15168 if (!uc.port.empty() && !detail::parse_port(uc.port, port)) { return; }
15169
15170 if (is_ssl) {
15171#ifdef CPPHTTPLIB_SSL_ENABLED
15172 cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
15173 client_key_path);
15174 is_ssl_ = is_ssl;
15175#endif
15176 } else {
15177 cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
15178 client_key_path);
15179 }
15180 } else {
15181 // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
15182 // if port param below changes.
15183 cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
15184 client_cert_path, client_key_path);
15185 }
15186}
15187
15188inline Client::Client(const std::string &host, int port)
15189 : Client(host, port, std::string(), std::string()) {}
15190
15191inline Client::Client(const std::string &host, int port,
15192 const std::string &client_cert_path,
15193 const std::string &client_key_path)
15194 : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
15195 client_key_path)) {}
15196
15197inline Client::~Client() = default;
15198
15199inline bool Client::is_valid() const {
15200 return cli_ != nullptr && cli_->is_valid();
15201}
15202
15203inline Result Client::Get(const std::string &path, DownloadProgress progress) {
15204 return cli_->Get(path, std::move(progress));
15205}
15206inline Result Client::Get(const std::string &path, const Headers &headers,
15207 DownloadProgress progress) {
15208 return cli_->Get(path, headers, std::move(progress));
15209}
15210inline Result Client::Get(const std::string &path,
15211 ContentReceiver content_receiver,
15212 DownloadProgress progress) {
15213 return cli_->Get(path, std::move(content_receiver), std::move(progress));
15214}
15215inline Result Client::Get(const std::string &path, const Headers &headers,
15216 ContentReceiver content_receiver,
15217 DownloadProgress progress) {
15218 return cli_->Get(path, headers, std::move(content_receiver),
15219 std::move(progress));
15220}
15221inline Result Client::Get(const std::string &path,
15222 ResponseHandler response_handler,
15223 ContentReceiver content_receiver,
15224 DownloadProgress progress) {
15225 return cli_->Get(path, std::move(response_handler),
15226 std::move(content_receiver), std::move(progress));
15227}
15228inline Result Client::Get(const std::string &path, const Headers &headers,
15229 ResponseHandler response_handler,
15230 ContentReceiver content_receiver,
15231 DownloadProgress progress) {
15232 return cli_->Get(path, headers, std::move(response_handler),
15233 std::move(content_receiver), std::move(progress));
15234}
15235inline Result Client::Get(const std::string &path, const Params &params,
15236 const Headers &headers, DownloadProgress progress) {
15237 return cli_->Get(path, params, headers, std::move(progress));
15238}
15239inline Result Client::Get(const std::string &path, const Params &params,
15240 const Headers &headers,
15241 ContentReceiver content_receiver,
15242 DownloadProgress progress) {
15243 return cli_->Get(path, params, headers, std::move(content_receiver),
15244 std::move(progress));
15245}
15246inline Result Client::Get(const std::string &path, const Params &params,
15247 const Headers &headers,
15248 ResponseHandler response_handler,
15249 ContentReceiver content_receiver,
15250 DownloadProgress progress) {
15251 return cli_->Get(path, params, headers, std::move(response_handler),
15252 std::move(content_receiver), std::move(progress));
15253}
15254
15255inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
15256inline Result Client::Head(const std::string &path, const Headers &headers) {
15257 return cli_->Head(path, headers);
15258}
15259
15260inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
15261inline Result Client::Post(const std::string &path, const Headers &headers) {
15262 return cli_->Post(path, headers);
15263}
15264inline Result Client::Post(const std::string &path, const char *body,
15265 size_t content_length,
15266 const std::string &content_type,
15267 UploadProgress progress) {
15268 return cli_->Post(path, body, content_length, content_type, progress);
15269}
15270inline Result Client::Post(const std::string &path, const Headers &headers,
15271 const char *body, size_t content_length,
15272 const std::string &content_type,
15273 UploadProgress progress) {
15274 return cli_->Post(path, headers, body, content_length, content_type,
15275 progress);
15276}
15277inline Result Client::Post(const std::string &path, const std::string &body,
15278 const std::string &content_type,
15279 UploadProgress progress) {
15280 return cli_->Post(path, body, content_type, progress);
15281}
15282inline Result Client::Post(const std::string &path, const Headers &headers,
15283 const std::string &body,
15284 const std::string &content_type,
15285 UploadProgress progress) {
15286 return cli_->Post(path, headers, body, content_type, progress);
15287}
15288inline Result Client::Post(const std::string &path, size_t content_length,
15289 ContentProvider content_provider,
15290 const std::string &content_type,
15291 UploadProgress progress) {
15292 return cli_->Post(path, content_length, std::move(content_provider),
15293 content_type, progress);
15294}
15295inline Result Client::Post(const std::string &path, size_t content_length,
15296 ContentProvider content_provider,
15297 const std::string &content_type,
15298 ContentReceiver content_receiver,
15299 UploadProgress progress) {
15300 return cli_->Post(path, content_length, std::move(content_provider),
15301 content_type, std::move(content_receiver), progress);
15302}
15303inline Result Client::Post(const std::string &path,
15304 ContentProviderWithoutLength content_provider,
15305 const std::string &content_type,
15306 UploadProgress progress) {
15307 return cli_->Post(path, std::move(content_provider), content_type, progress);
15308}
15309inline Result Client::Post(const std::string &path,
15310 ContentProviderWithoutLength content_provider,
15311 const std::string &content_type,
15312 ContentReceiver content_receiver,
15313 UploadProgress progress) {
15314 return cli_->Post(path, std::move(content_provider), content_type,
15315 std::move(content_receiver), progress);
15316}
15317inline Result Client::Post(const std::string &path, const Headers &headers,
15318 size_t content_length,
15319 ContentProvider content_provider,
15320 const std::string &content_type,
15321 UploadProgress progress) {
15322 return cli_->Post(path, headers, content_length, std::move(content_provider),
15323 content_type, progress);
15324}
15325inline Result Client::Post(const std::string &path, const Headers &headers,
15326 size_t content_length,
15327 ContentProvider content_provider,
15328 const std::string &content_type,
15329 ContentReceiver content_receiver,
15330 DownloadProgress progress) {
15331 return cli_->Post(path, headers, content_length, std::move(content_provider),
15332 content_type, std::move(content_receiver), progress);
15333}
15334inline Result Client::Post(const std::string &path, const Headers &headers,
15335 ContentProviderWithoutLength content_provider,
15336 const std::string &content_type,
15337 UploadProgress progress) {
15338 return cli_->Post(path, headers, std::move(content_provider), content_type,
15339 progress);
15340}
15341inline Result Client::Post(const std::string &path, const Headers &headers,
15342 ContentProviderWithoutLength content_provider,
15343 const std::string &content_type,
15344 ContentReceiver content_receiver,
15345 DownloadProgress progress) {
15346 return cli_->Post(path, headers, std::move(content_provider), content_type,
15347 std::move(content_receiver), progress);
15348}
15349inline Result Client::Post(const std::string &path, const Params &params) {
15350 return cli_->Post(path, params);
15351}
15352inline Result Client::Post(const std::string &path, const Headers &headers,
15353 const Params &params) {
15354 return cli_->Post(path, headers, params);
15355}
15356inline Result Client::Post(const std::string &path,
15357 const UploadFormDataItems &items,
15358 UploadProgress progress) {
15359 return cli_->Post(path, items, progress);
15360}
15361inline Result Client::Post(const std::string &path, const Headers &headers,
15362 const UploadFormDataItems &items,
15363 UploadProgress progress) {
15364 return cli_->Post(path, headers, items, progress);
15365}
15366inline Result Client::Post(const std::string &path, const Headers &headers,
15367 const UploadFormDataItems &items,
15368 const std::string &boundary,
15369 UploadProgress progress) {
15370 return cli_->Post(path, headers, items, boundary, progress);
15371}
15372inline Result Client::Post(const std::string &path, const Headers &headers,
15373 const UploadFormDataItems &items,
15374 const FormDataProviderItems &provider_items,
15375 UploadProgress progress) {
15376 return cli_->Post(path, headers, items, provider_items, progress);
15377}
15378inline Result Client::Post(const std::string &path, const Headers &headers,
15379 const std::string &body,
15380 const std::string &content_type,
15381 ContentReceiver content_receiver,
15382 DownloadProgress progress) {
15383 return cli_->Post(path, headers, body, content_type,
15384 std::move(content_receiver), progress);
15385}
15386
15387inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
15388inline Result Client::Put(const std::string &path, const Headers &headers) {
15389 return cli_->Put(path, headers);
15390}
15391inline Result Client::Put(const std::string &path, const char *body,
15392 size_t content_length,
15393 const std::string &content_type,
15394 UploadProgress progress) {
15395 return cli_->Put(path, body, content_length, content_type, progress);
15396}
15397inline Result Client::Put(const std::string &path, const Headers &headers,
15398 const char *body, size_t content_length,
15399 const std::string &content_type,
15400 UploadProgress progress) {
15401 return cli_->Put(path, headers, body, content_length, content_type, progress);
15402}
15403inline Result Client::Put(const std::string &path, const std::string &body,
15404 const std::string &content_type,
15405 UploadProgress progress) {
15406 return cli_->Put(path, body, content_type, progress);
15407}
15408inline Result Client::Put(const std::string &path, const Headers &headers,
15409 const std::string &body,
15410 const std::string &content_type,
15411 UploadProgress progress) {
15412 return cli_->Put(path, headers, body, content_type, progress);
15413}
15414inline Result Client::Put(const std::string &path, size_t content_length,
15415 ContentProvider content_provider,
15416 const std::string &content_type,
15417 UploadProgress progress) {
15418 return cli_->Put(path, content_length, std::move(content_provider),
15419 content_type, progress);
15420}
15421inline Result Client::Put(const std::string &path, size_t content_length,
15422 ContentProvider content_provider,
15423 const std::string &content_type,
15424 ContentReceiver content_receiver,
15425 UploadProgress progress) {
15426 return cli_->Put(path, content_length, std::move(content_provider),
15427 content_type, std::move(content_receiver), progress);
15428}
15429inline Result Client::Put(const std::string &path,
15430 ContentProviderWithoutLength content_provider,
15431 const std::string &content_type,
15432 UploadProgress progress) {
15433 return cli_->Put(path, std::move(content_provider), content_type, progress);
15434}
15435inline Result Client::Put(const std::string &path,
15436 ContentProviderWithoutLength content_provider,
15437 const std::string &content_type,
15438 ContentReceiver content_receiver,
15439 UploadProgress progress) {
15440 return cli_->Put(path, std::move(content_provider), content_type,
15441 std::move(content_receiver), progress);
15442}
15443inline Result Client::Put(const std::string &path, const Headers &headers,
15444 size_t content_length,
15445 ContentProvider content_provider,
15446 const std::string &content_type,
15447 UploadProgress progress) {
15448 return cli_->Put(path, headers, content_length, std::move(content_provider),
15449 content_type, progress);
15450}
15451inline Result Client::Put(const std::string &path, const Headers &headers,
15452 size_t content_length,
15453 ContentProvider content_provider,
15454 const std::string &content_type,
15455 ContentReceiver content_receiver,
15456 UploadProgress progress) {
15457 return cli_->Put(path, headers, content_length, std::move(content_provider),
15458 content_type, std::move(content_receiver), progress);
15459}
15460inline Result Client::Put(const std::string &path, const Headers &headers,
15461 ContentProviderWithoutLength content_provider,
15462 const std::string &content_type,
15463 UploadProgress progress) {
15464 return cli_->Put(path, headers, std::move(content_provider), content_type,
15465 progress);
15466}
15467inline Result Client::Put(const std::string &path, const Headers &headers,
15468 ContentProviderWithoutLength content_provider,
15469 const std::string &content_type,
15470 ContentReceiver content_receiver,
15471 UploadProgress progress) {
15472 return cli_->Put(path, headers, std::move(content_provider), content_type,
15473 std::move(content_receiver), progress);
15474}
15475inline Result Client::Put(const std::string &path, const Params &params) {
15476 return cli_->Put(path, params);
15477}
15478inline Result Client::Put(const std::string &path, const Headers &headers,
15479 const Params &params) {
15480 return cli_->Put(path, headers, params);
15481}
15482inline Result Client::Put(const std::string &path,
15483 const UploadFormDataItems &items,
15484 UploadProgress progress) {
15485 return cli_->Put(path, items, progress);
15486}
15487inline Result Client::Put(const std::string &path, const Headers &headers,
15488 const UploadFormDataItems &items,
15489 UploadProgress progress) {
15490 return cli_->Put(path, headers, items, progress);
15491}
15492inline Result Client::Put(const std::string &path, const Headers &headers,
15493 const UploadFormDataItems &items,
15494 const std::string &boundary,
15495 UploadProgress progress) {
15496 return cli_->Put(path, headers, items, boundary, progress);
15497}
15498inline Result Client::Put(const std::string &path, const Headers &headers,
15499 const UploadFormDataItems &items,
15500 const FormDataProviderItems &provider_items,
15501 UploadProgress progress) {
15502 return cli_->Put(path, headers, items, provider_items, progress);
15503}
15504inline Result Client::Put(const std::string &path, const Headers &headers,
15505 const std::string &body,
15506 const std::string &content_type,
15507 ContentReceiver content_receiver,
15508 DownloadProgress progress) {
15509 return cli_->Put(path, headers, body, content_type, content_receiver,
15510 progress);
15511}
15512
15513inline Result Client::Patch(const std::string &path) {
15514 return cli_->Patch(path);
15515}
15516inline Result Client::Patch(const std::string &path, const Headers &headers) {
15517 return cli_->Patch(path, headers);
15518}
15519inline Result Client::Patch(const std::string &path, const char *body,
15520 size_t content_length,
15521 const std::string &content_type,
15522 UploadProgress progress) {
15523 return cli_->Patch(path, body, content_length, content_type, progress);
15524}
15525inline Result Client::Patch(const std::string &path, const Headers &headers,
15526 const char *body, size_t content_length,
15527 const std::string &content_type,
15528 UploadProgress progress) {
15529 return cli_->Patch(path, headers, body, content_length, content_type,
15530 progress);
15531}
15532inline Result Client::Patch(const std::string &path, const std::string &body,
15533 const std::string &content_type,
15534 UploadProgress progress) {
15535 return cli_->Patch(path, body, content_type, progress);
15536}
15537inline Result Client::Patch(const std::string &path, const Headers &headers,
15538 const std::string &body,
15539 const std::string &content_type,
15540 UploadProgress progress) {
15541 return cli_->Patch(path, headers, body, content_type, progress);
15542}
15543inline Result Client::Patch(const std::string &path, size_t content_length,
15544 ContentProvider content_provider,
15545 const std::string &content_type,
15546 UploadProgress progress) {
15547 return cli_->Patch(path, content_length, std::move(content_provider),
15548 content_type, progress);
15549}
15550inline Result Client::Patch(const std::string &path, size_t content_length,
15551 ContentProvider content_provider,
15552 const std::string &content_type,
15553 ContentReceiver content_receiver,
15554 UploadProgress progress) {
15555 return cli_->Patch(path, content_length, std::move(content_provider),
15556 content_type, std::move(content_receiver), progress);
15557}
15558inline Result Client::Patch(const std::string &path,
15559 ContentProviderWithoutLength content_provider,
15560 const std::string &content_type,
15561 UploadProgress progress) {
15562 return cli_->Patch(path, std::move(content_provider), content_type, progress);
15563}
15564inline Result Client::Patch(const std::string &path,
15565 ContentProviderWithoutLength content_provider,
15566 const std::string &content_type,
15567 ContentReceiver content_receiver,
15568 UploadProgress progress) {
15569 return cli_->Patch(path, std::move(content_provider), content_type,
15570 std::move(content_receiver), progress);
15571}
15572inline Result Client::Patch(const std::string &path, const Headers &headers,
15573 size_t content_length,
15574 ContentProvider content_provider,
15575 const std::string &content_type,
15576 UploadProgress progress) {
15577 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15578 content_type, progress);
15579}
15580inline Result Client::Patch(const std::string &path, const Headers &headers,
15581 size_t content_length,
15582 ContentProvider content_provider,
15583 const std::string &content_type,
15584 ContentReceiver content_receiver,
15585 UploadProgress progress) {
15586 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15587 content_type, std::move(content_receiver), progress);
15588}
15589inline Result Client::Patch(const std::string &path, const Headers &headers,
15590 ContentProviderWithoutLength content_provider,
15591 const std::string &content_type,
15592 UploadProgress progress) {
15593 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15594 progress);
15595}
15596inline Result Client::Patch(const std::string &path, const Headers &headers,
15597 ContentProviderWithoutLength content_provider,
15598 const std::string &content_type,
15599 ContentReceiver content_receiver,
15600 UploadProgress progress) {
15601 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15602 std::move(content_receiver), progress);
15603}
15604inline Result Client::Patch(const std::string &path, const Params &params) {
15605 return cli_->Patch(path, params);
15606}
15607inline Result Client::Patch(const std::string &path, const Headers &headers,
15608 const Params &params) {
15609 return cli_->Patch(path, headers, params);
15610}
15611inline Result Client::Patch(const std::string &path,
15612 const UploadFormDataItems &items,
15613 UploadProgress progress) {
15614 return cli_->Patch(path, items, progress);
15615}
15616inline Result Client::Patch(const std::string &path, const Headers &headers,
15617 const UploadFormDataItems &items,
15618 UploadProgress progress) {
15619 return cli_->Patch(path, headers, items, progress);
15620}
15621inline Result Client::Patch(const std::string &path, const Headers &headers,
15622 const UploadFormDataItems &items,
15623 const std::string &boundary,
15624 UploadProgress progress) {
15625 return cli_->Patch(path, headers, items, boundary, progress);
15626}
15627inline Result Client::Patch(const std::string &path, const Headers &headers,
15628 const UploadFormDataItems &items,
15629 const FormDataProviderItems &provider_items,
15630 UploadProgress progress) {
15631 return cli_->Patch(path, headers, items, provider_items, progress);
15632}
15633inline Result Client::Patch(const std::string &path, const Headers &headers,
15634 const std::string &body,
15635 const std::string &content_type,
15636 ContentReceiver content_receiver,
15637 DownloadProgress progress) {
15638 return cli_->Patch(path, headers, body, content_type, content_receiver,
15639 progress);
15640}
15641
15642inline Result Client::Delete(const std::string &path,
15643 DownloadProgress progress) {
15644 return cli_->Delete(path, progress);
15645}
15646inline Result Client::Delete(const std::string &path, const Headers &headers,
15647 DownloadProgress progress) {
15648 return cli_->Delete(path, headers, progress);
15649}
15650inline Result Client::Delete(const std::string &path, const char *body,
15651 size_t content_length,
15652 const std::string &content_type,
15653 DownloadProgress progress) {
15654 return cli_->Delete(path, body, content_length, content_type, progress);
15655}
15656inline Result Client::Delete(const std::string &path, const Headers &headers,
15657 const char *body, size_t content_length,
15658 const std::string &content_type,
15659 DownloadProgress progress) {
15660 return cli_->Delete(path, headers, body, content_length, content_type,
15661 progress);
15662}
15663inline Result Client::Delete(const std::string &path, const std::string &body,
15664 const std::string &content_type,
15665 DownloadProgress progress) {
15666 return cli_->Delete(path, body, content_type, progress);
15667}
15668inline Result Client::Delete(const std::string &path, const Headers &headers,
15669 const std::string &body,
15670 const std::string &content_type,
15671 DownloadProgress progress) {
15672 return cli_->Delete(path, headers, body, content_type, progress);
15673}
15674inline Result Client::Delete(const std::string &path, const Params &params,
15675 DownloadProgress progress) {
15676 return cli_->Delete(path, params, progress);
15677}
15678inline Result Client::Delete(const std::string &path, const Headers &headers,
15679 const Params &params, DownloadProgress progress) {
15680 return cli_->Delete(path, headers, params, progress);
15681}
15682
15683inline Result Client::Options(const std::string &path) {
15684 return cli_->Options(path);
15685}
15686inline Result Client::Options(const std::string &path, const Headers &headers) {
15687 return cli_->Options(path, headers);
15688}
15689
15690inline ClientImpl::StreamHandle
15691Client::open_stream(const std::string &method, const std::string &path,
15692 const Params &params, const Headers &headers,
15693 const std::string &body, const std::string &content_type) {
15694 return cli_->open_stream(method, path, params, headers, body, content_type);
15695}
15696
15697inline bool Client::send(Request &req, Response &res, Error &error) {
15698 return cli_->send(req, res, error);
15699}
15700
15701inline Result Client::send(const Request &req) { return cli_->send(req); }
15702
15703inline void Client::stop() { cli_->stop(); }
15704
15705inline std::string Client::host() const { return cli_->host(); }
15706
15707inline int Client::port() const { return cli_->port(); }
15708
15709inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
15710
15711inline socket_t Client::socket() const { return cli_->socket(); }
15712
15713inline void
15714Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15715 cli_->set_hostname_addr_map(std::move(addr_map));
15716}
15717
15718inline void Client::set_default_headers(Headers headers) {
15719 cli_->set_default_headers(std::move(headers));
15720}
15721
15722inline void Client::set_header_writer(
15723 std::function<ssize_t(Stream &, Headers &)> const &writer) {
15724 cli_->set_header_writer(writer);
15725}
15726
15727inline void Client::set_address_family(int family) {
15728 cli_->set_address_family(family);
15729}
15730
15731inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
15732
15733inline void Client::set_socket_options(SocketOptions socket_options) {
15734 cli_->set_socket_options(std::move(socket_options));
15735}
15736
15737inline void Client::set_connection_timeout(time_t sec, time_t usec) {
15738 cli_->set_connection_timeout(sec, usec);
15739}
15740
15741inline void Client::set_read_timeout(time_t sec, time_t usec) {
15742 cli_->set_read_timeout(sec, usec);
15743}
15744
15745inline void Client::set_write_timeout(time_t sec, time_t usec) {
15746 cli_->set_write_timeout(sec, usec);
15747}
15748
15749inline void Client::set_basic_auth(const std::string &username,
15750 const std::string &password) {
15751 cli_->set_basic_auth(username, password);
15752}
15753inline void Client::set_bearer_token_auth(const std::string &token) {
15754 cli_->set_bearer_token_auth(token);
15755}
15756
15757inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
15758inline void Client::set_follow_location(bool on) {
15759 cli_->set_follow_location(on);
15760}
15761
15762inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
15763
15764inline void Client::set_compress(bool on) { cli_->set_compress(on); }
15765
15766inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
15767
15768inline void Client::set_payload_max_length(size_t length) {
15769 cli_->set_payload_max_length(length);
15770}
15771
15772inline void Client::set_interface(const std::string &intf) {
15773 cli_->set_interface(intf);
15774}
15775
15776inline void Client::set_proxy(const std::string &host, int port) {
15777 cli_->set_proxy(host, port);
15778}
15779inline void Client::set_proxy_basic_auth(const std::string &username,
15780 const std::string &password) {
15781 cli_->set_proxy_basic_auth(username, password);
15782}
15783inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
15784 cli_->set_proxy_bearer_token_auth(token);
15785}
15786inline void Client::set_no_proxy(const std::vector<std::string> &patterns) {
15787 cli_->set_no_proxy(patterns);
15788}
15789
15790inline void Client::set_logger(Logger logger) {
15791 cli_->set_logger(std::move(logger));
15792}
15793
15794inline void Client::set_error_logger(ErrorLogger error_logger) {
15795 cli_->set_error_logger(std::move(error_logger));
15796}
15797
15798/*
15799 * Group 6: SSL Server and Client implementation
15800 */
15801
15802#ifdef CPPHTTPLIB_SSL_ENABLED
15803
15804// SSL HTTP server implementation
15805inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
15806 const char *client_ca_cert_file_path,
15807 const char *client_ca_cert_dir_path,
15808 const char *private_key_password) {
15809 using namespace tls;
15810
15811 ctx_ = create_server_context();
15812 if (!ctx_) { return; }
15813
15814 // Load server certificate and private key
15815 if (!set_server_cert_file(ctx_, cert_path, private_key_path,
15816 private_key_password)) {
15817 last_ssl_error_ = static_cast<int>(get_error());
15818 free_context(ctx_);
15819 ctx_ = nullptr;
15820 return;
15821 }
15822
15823 // Load client CA certificates for client authentication
15824 if (client_ca_cert_file_path || client_ca_cert_dir_path) {
15825 if (!set_client_ca_file(ctx_, client_ca_cert_file_path,
15826 client_ca_cert_dir_path)) {
15827 last_ssl_error_ = static_cast<int>(get_error());
15828 free_context(ctx_);
15829 ctx_ = nullptr;
15830 return;
15831 }
15832 // Enable client certificate verification
15833 set_verify_client(ctx_, true);
15834 }
15835}
15836
15837inline SSLServer::SSLServer(const PemMemory &pem) {
15838 using namespace tls;
15839 ctx_ = create_server_context();
15840 if (ctx_) {
15841 if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
15842 pem.private_key_password)) {
15843 last_ssl_error_ = static_cast<int>(get_error());
15844 free_context(ctx_);
15845 ctx_ = nullptr;
15846 } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) {
15847 if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) {
15848 last_ssl_error_ = static_cast<int>(get_error());
15849 free_context(ctx_);
15850 ctx_ = nullptr;
15851 } else {
15852 set_verify_client(ctx_, true);
15853 }
15854 }
15855 }
15856}
15857
15858inline SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) {
15859 using namespace tls;
15860 ctx_ = create_server_context();
15861 if (ctx_) {
15862 if (!setup_callback(ctx_)) {
15863 free_context(ctx_);
15864 ctx_ = nullptr;
15865 }
15866 }
15867}
15868
15869inline SSLServer::~SSLServer() {
15870 if (ctx_) { tls::free_context(ctx_); }
15871}
15872
15873inline bool SSLServer::is_valid() const { return ctx_ != nullptr; }
15874
15875inline bool SSLServer::process_and_close_socket(socket_t sock) {
15876 using namespace tls;
15877
15878 // Create TLS session with mutex protection
15879 session_t session = nullptr;
15880 {
15881 std::lock_guard<std::mutex> guard(ctx_mutex_);
15882 session = create_session(static_cast<ctx_t>(ctx_), sock);
15883 }
15884
15885 if (!session) {
15886 last_ssl_error_ = static_cast<int>(get_error());
15887 detail::shutdown_socket(sock);
15888 detail::close_socket(sock);
15889 return false;
15890 }
15891
15892 // Use scope_exit to ensure cleanup on all paths (including exceptions)
15893 bool handshake_done = false;
15894 bool ret = false;
15895 bool websocket_upgraded = false;
15896 auto cleanup = detail::scope_exit([&] {
15897 if (handshake_done) { shutdown(session, !websocket_upgraded && ret); }
15898 free_session(session);
15899 detail::shutdown_socket(sock);
15900 detail::close_socket(sock);
15901 });
15902
15903 // Perform TLS accept handshake with timeout
15904 TlsError tls_err;
15905 if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_,
15906 &tls_err)) {
15907#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
15908 // Map TlsError to legacy ssl_error for backward compatibility
15909 if (tls_err.code == ErrorCode::WantRead) {
15910 last_ssl_error_ = SSL_ERROR_WANT_READ;
15911 } else if (tls_err.code == ErrorCode::WantWrite) {
15912 last_ssl_error_ = SSL_ERROR_WANT_WRITE;
15913 } else {
15914 last_ssl_error_ = SSL_ERROR_SSL;
15915 }
15916#else
15917 last_ssl_error_ = static_cast<int>(get_error());
15918#endif
15919 return false;
15920 }
15921
15922 handshake_done = true;
15923
15924 std::string remote_addr;
15925 int remote_port = 0;
15926 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
15927
15928 std::string local_addr;
15929 int local_port = 0;
15930 detail::get_local_ip_and_port(sock, local_addr, local_port);
15931
15932 ret = detail::process_server_socket_ssl(
15933 svr_sock_, session, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
15934 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
15935 write_timeout_usec_,
15936 [&](Stream &strm, bool close_connection, bool &connection_closed) {
15937 return process_request(
15938 strm, remote_addr, remote_port, local_addr, local_port,
15939 close_connection, connection_closed,
15940 [&](Request &req) { req.ssl = session; }, &websocket_upgraded);
15941 });
15942
15943 return ret;
15944}
15945
15946inline bool SSLServer::update_certs_pem(const char *cert_pem,
15947 const char *key_pem,
15948 const char *client_ca_pem,
15949 const char *password) {
15950 if (!ctx_) { return false; }
15951 std::lock_guard<std::mutex> guard(ctx_mutex_);
15952 if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) {
15953 return false;
15954 }
15955 if (client_ca_pem) {
15956 return tls::update_server_client_ca(ctx_, client_ca_pem);
15957 }
15958 return true;
15959}
15960
15961// SSL HTTP client implementation
15962inline SSLClient::~SSLClient() {
15963 if (ctx_) { tls::free_context(ctx_); }
15964 // Make sure to shut down SSL since shutdown_ssl will resolve to the
15965 // base function rather than the derived function once we get to the
15966 // base class destructor, and won't free the SSL (causing a leak).
15967 shutdown_ssl_impl(socket_, true);
15968}
15969
15970inline bool SSLClient::is_valid() const { return ctx_ != nullptr; }
15971
15972inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
15973 shutdown_ssl_impl(socket, shutdown_gracefully);
15974}
15975
15976inline void SSLClient::shutdown_ssl_impl(Socket &socket,
15977 bool shutdown_gracefully) {
15978 if (socket.sock == INVALID_SOCKET) {
15979 assert(socket.ssl == nullptr);
15980 return;
15981 }
15982 if (socket.ssl) {
15983 tls::shutdown(socket.ssl, shutdown_gracefully);
15984 {
15985 std::lock_guard<std::mutex> guard(ctx_mutex_);
15986 tls::free_session(socket.ssl);
15987 }
15988 socket.ssl = nullptr;
15989 }
15990 assert(socket.ssl == nullptr);
15991}
15992
15993inline bool SSLClient::process_socket(
15994 const Socket &socket,
15995 std::chrono::time_point<std::chrono::steady_clock> start_time,
15996 std::function<bool(Stream &strm)> callback) {
15997 assert(socket.ssl);
15998 return detail::process_client_socket_ssl(
15999 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
16000 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
16001 std::move(callback));
16002}
16003
16004inline bool SSLClient::is_ssl() const { return true; }
16005
16006inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
16007 if (!is_valid()) {
16008 error = Error::SSLConnection;
16009 return false;
16010 }
16011 return ClientImpl::create_and_connect_socket(socket, error);
16012}
16013
16014inline bool SSLClient::setup_proxy_connection(
16015 Socket &socket,
16016 std::chrono::time_point<std::chrono::steady_clock> start_time,
16017 Response &res, bool &success, Error &error) {
16018 if (!is_proxy_enabled_for_host(host_)) { return true; }
16019
16020 if (!connect_with_proxy(socket, start_time, res, success, error)) {
16021 return false;
16022 }
16023
16024 if (!initialize_ssl(socket, error)) {
16025 success = false;
16026 return false;
16027 }
16028
16029 return true;
16030}
16031
16032// Assumes that socket_mutex_ is locked and that there are no requests in
16033// flight
16034inline bool SSLClient::connect_with_proxy(
16035 Socket &socket,
16036 std::chrono::time_point<std::chrono::steady_clock> start_time,
16037 Response &res, bool &success, Error &error) {
16038 success = true;
16039 Response proxy_res;
16040 if (!detail::process_client_socket(
16041 socket.sock, read_timeout_sec_, read_timeout_usec_,
16042 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
16043 start_time, [&](Stream &strm) {
16044 Request req2;
16045 req2.method = "CONNECT";
16046 req2.path =
16047 detail::make_host_and_port_string_always_port(host_, port_);
16048 if (max_timeout_msec_ > 0) {
16049 req2.start_time_ = std::chrono::steady_clock::now();
16050 }
16051 return process_request(strm, req2, proxy_res, false, error);
16052 })) {
16053 // Thread-safe to close everything because we are assuming there are no
16054 // requests in flight
16055 shutdown_ssl(socket, true);
16056 shutdown_socket(socket);
16057 close_socket(socket);
16058 success = false;
16059 return false;
16060 }
16061
16062 if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
16063 if (!proxy_digest_auth_username_.empty() &&
16064 !proxy_digest_auth_password_.empty()) {
16065 std::map<std::string, std::string> auth;
16066 if (detail::parse_www_authenticate(proxy_res, auth, true)) {
16067 // Close the current socket and create a new one for the authenticated
16068 // request
16069 shutdown_ssl(socket, true);
16070 shutdown_socket(socket);
16071 close_socket(socket);
16072
16073 // Create a new socket for the authenticated CONNECT request
16074 if (!ensure_socket_connection(socket, error)) {
16075 success = false;
16076 output_error_log(error, nullptr);
16077 return false;
16078 }
16079
16080 proxy_res = Response();
16081 if (!detail::process_client_socket(
16082 socket.sock, read_timeout_sec_, read_timeout_usec_,
16083 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
16084 start_time, [&](Stream &strm) {
16085 Request req3;
16086 req3.method = "CONNECT";
16087 req3.path = detail::make_host_and_port_string_always_port(
16088 host_, port_);
16089 req3.headers.insert(detail::make_digest_authentication_header(
16090 req3, auth, 1, detail::random_string(10),
16091 proxy_digest_auth_username_, proxy_digest_auth_password_,
16092 true));
16093 if (max_timeout_msec_ > 0) {
16094 req3.start_time_ = std::chrono::steady_clock::now();
16095 }
16096 return process_request(strm, req3, proxy_res, false, error);
16097 })) {
16098 // Thread-safe to close everything because we are assuming there are
16099 // no requests in flight
16100 shutdown_ssl(socket, true);
16101 shutdown_socket(socket);
16102 close_socket(socket);
16103 success = false;
16104 return false;
16105 }
16106 }
16107 }
16108 }
16109
16110 // If status code is not 200, proxy request is failed.
16111 // Set error to ProxyConnection and return proxy response
16112 // as the response of the request
16113 if (proxy_res.status != StatusCode::OK_200) {
16114 error = Error::ProxyConnection;
16115 output_error_log(error, nullptr);
16116 res = std::move(proxy_res);
16117 // Thread-safe to close everything because we are assuming there are
16118 // no requests in flight
16119 shutdown_ssl(socket, true);
16120 shutdown_socket(socket);
16121 close_socket(socket);
16122 return false;
16123 }
16124
16125 return true;
16126}
16127
16128inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
16129 if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
16130
16131 if (is_proxy_enabled_for_host(host_)) { return true; }
16132
16133 if (!initialize_ssl(socket, error)) {
16134 shutdown_socket(socket);
16135 close_socket(socket);
16136 return false;
16137 }
16138
16139 return true;
16140}
16141
16142// SSL HTTP client implementation
16143inline SSLClient::SSLClient(const std::string &host)
16144 : SSLClient(host, 443, std::string(), std::string()) {}
16145
16146inline SSLClient::SSLClient(const std::string &host, int port)
16147 : SSLClient(host, port, std::string(), std::string()) {}
16148
16149inline void SSLClient::init_ctx() {
16150 ctx_ = tls::create_client_context();
16151 if (ctx_) { tls::set_min_version(ctx_, tls::Version::TLS1_2); }
16152}
16153
16154inline void SSLClient::reset_ctx_on_error() {
16155 last_backend_error_ = tls::get_error();
16156 tls::free_context(ctx_);
16157 ctx_ = nullptr;
16158}
16159
16160inline SSLClient::SSLClient(const std::string &host, int port,
16161 const std::string &client_cert_path,
16162 const std::string &client_key_path,
16163 const std::string &private_key_password)
16164 : ClientImpl(host, port, client_cert_path, client_key_path) {
16165 init_ctx();
16166 if (!ctx_) { return; }
16167
16168 if (!client_cert_path.empty() && !client_key_path.empty()) {
16169 const char *password =
16170 private_key_password.empty() ? nullptr : private_key_password.c_str();
16171 if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(),
16172 client_key_path.c_str(), password)) {
16173 reset_ctx_on_error();
16174 }
16175 }
16176}
16177
16178inline SSLClient::SSLClient(const std::string &host, int port,
16179 const PemMemory &pem)
16180 : ClientImpl(host, port) {
16181 init_ctx();
16182 if (!ctx_) { return; }
16183
16184 if (pem.cert_pem && pem.key_pem) {
16185 if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
16186 pem.private_key_password)) {
16187 reset_ctx_on_error();
16188 }
16189 }
16190}
16191
16192inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16193 if (ca_cert_store && ctx_) {
16194 // set_ca_store takes ownership of ca_cert_store
16195 tls::set_ca_store(ctx_, ca_cert_store);
16196 ca_cert_store_set_ = true;
16197 } else if (ca_cert_store) {
16198 tls::free_ca_store(ca_cert_store);
16199 }
16200}
16201
16202inline void
16203SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16204 if (!ctx_) { return; }
16205 tls::set_verify_callback(ctx_, verifier);
16206}
16207
16208inline void SSLClient::set_session_verifier(
16209 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16210 session_verifier_ = std::move(verifier);
16211}
16212
16213#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16214inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
16215 enable_windows_cert_verification_ = enabled;
16216}
16217#endif
16218
16219inline void SSLClient::load_ca_cert_store(const char *ca_cert,
16220 std::size_t size) {
16221 if (ctx_ && ca_cert && size > 0) {
16222 ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer
16223 tls::load_ca_pem(ctx_, ca_cert, size);
16224 }
16225}
16226
16227inline bool SSLClient::load_certs() {
16228 auto ret = true;
16229
16230 std::call_once(initialize_cert_, [&]() {
16231 std::lock_guard<std::mutex> guard(ctx_mutex_);
16232
16233 ret = detail::load_client_ca_config(
16234 ctx_, ca_cert_file_path_, ca_cert_dir_path_,
16235 !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
16236 last_backend_error_);
16237 });
16238
16239 return ret;
16240}
16241
16242inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
16243 using namespace tls;
16244
16245 // Load CA certificates if server verification is enabled
16246 if (server_certificate_verification_) {
16247 if (!load_certs()) {
16248 error = Error::SSLLoadingCerts;
16249 output_error_log(error, nullptr);
16250 return false;
16251 }
16252 }
16253
16254 bool is_ip = detail::is_ip_address(host_);
16255
16256#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
16257 // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
16258 // SSL_VERIFY_NONE by default and performs all verification post-handshake).
16259 // Chain verification happens during the handshake even for IP hosts; the
16260 // certificate identity is verified post-handshake via verify_hostname().
16261 set_verify_client(ctx_, server_certificate_verification_);
16262#endif
16263
16264 // Create TLS session
16265 session_t session = nullptr;
16266 {
16267 std::lock_guard<std::mutex> guard(ctx_mutex_);
16268 session = create_session(ctx_, socket.sock);
16269 }
16270
16271 if (!session) {
16272 error = Error::SSLConnection;
16273 last_backend_error_ = get_error();
16274 return false;
16275 }
16276
16277 // Use scope_exit to ensure session is freed on error paths
16278 bool success = false;
16279 auto session_guard = detail::scope_exit([&] {
16280 if (!success) { free_session(session); }
16281 });
16282
16283 // Set SNI extension (skip for IP addresses per RFC 6066).
16284 // On MbedTLS, set_sni also enables hostname verification internally.
16285 // On OpenSSL, set_sni only sets SNI; verification is done post-handshake.
16286 if (!is_ip) {
16287 if (!set_sni(session, host_.c_str())) {
16288 error = Error::SSLConnection;
16289 last_backend_error_ = get_error();
16290 return false;
16291 }
16292 }
16293
16294 // Perform non-blocking TLS handshake with timeout
16295 TlsError tls_err;
16296 if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_,
16297 connection_timeout_usec_, &tls_err)) {
16298 last_ssl_error_ = static_cast<int>(tls_err.code);
16299 last_backend_error_ = tls_err.backend_code;
16300 if (tls_err.code == ErrorCode::CertVerifyFailed) {
16301 error = Error::SSLServerVerification;
16302 } else if (tls_err.code == ErrorCode::HostnameMismatch) {
16303 error = Error::SSLServerHostnameVerification;
16304 } else {
16305 error = Error::SSLConnection;
16306 }
16307 output_error_log(error, nullptr);
16308 return false;
16309 }
16310
16311 // Post-handshake session verifier callback
16312 auto verification_status = SSLVerifierResponse::NoDecisionMade;
16313 if (session_verifier_) { verification_status = session_verifier_(session); }
16314
16315 if (verification_status == SSLVerifierResponse::CertificateRejected) {
16316 last_backend_error_ = get_error();
16317 error = Error::SSLServerVerification;
16318 output_error_log(error, nullptr);
16319 return false;
16320 }
16321
16322 // Default server certificate verification
16323 if (verification_status == SSLVerifierResponse::NoDecisionMade &&
16324 server_certificate_verification_) {
16325 verify_result_ = tls::get_verify_result(session);
16326 if (verify_result_ != 0) {
16327 last_backend_error_ = static_cast<uint64_t>(verify_result_);
16328 error = Error::SSLServerVerification;
16329 output_error_log(error, nullptr);
16330 return false;
16331 }
16332
16333 auto server_cert = get_peer_cert(session);
16334 if (!server_cert) {
16335 last_backend_error_ = get_error();
16336 error = Error::SSLServerVerification;
16337 output_error_log(error, nullptr);
16338 return false;
16339 }
16340 auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
16341
16342 // Hostname verification (post-handshake for all cases).
16343 // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE).
16344 // On MbedTLS, set_sni already enabled hostname verification during
16345 // handshake for non-IP hosts, but this check is still needed for IP
16346 // addresses where SNI is not set.
16347 if (server_hostname_verification_) {
16348 if (!verify_hostname(server_cert, host_.c_str())) {
16349 last_backend_error_ = hostname_mismatch_code();
16350 error = Error::SSLServerHostnameVerification;
16351 output_error_log(error, nullptr);
16352 return false;
16353 }
16354 }
16355
16356#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16357 // Additional Windows Schannel verification.
16358 // This provides real-time certificate validation with Windows Update
16359 // integration, working with both OpenSSL and MbedTLS backends.
16360 // Skip when a custom CA cert is specified, as the Windows certificate
16361 // store would not know about user-provided CA certificates. Also skip
16362 // when system CA trust is explicitly disabled.
16363 if (enable_windows_cert_verification_ &&
16364 system_ca_mode_ != SystemCAMode::Disabled &&
16365 ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() &&
16366 ca_cert_pem_.empty() && !ca_cert_store_set_) {
16367 std::vector<unsigned char> der;
16368 if (get_cert_der(server_cert, der)) {
16369 uint64_t wincrypt_error = 0;
16370 if (!detail::verify_cert_with_windows_schannel(
16371 der, host_, server_hostname_verification_, wincrypt_error)) {
16372 last_backend_error_ = wincrypt_error;
16373 error = Error::SSLServerVerification;
16374 output_error_log(error, nullptr);
16375 return false;
16376 }
16377 }
16378 }
16379#endif
16380 }
16381
16382 success = true;
16383 socket.ssl = session;
16384 return true;
16385}
16386
16387inline void Client::set_digest_auth(const std::string &username,
16388 const std::string &password) {
16389 cli_->set_digest_auth(username, password);
16390}
16391
16392inline void Client::set_proxy_digest_auth(const std::string &username,
16393 const std::string &password) {
16394 cli_->set_proxy_digest_auth(username, password);
16395}
16396
16397inline void Client::enable_server_certificate_verification(bool enabled) {
16398 cli_->enable_server_certificate_verification(enabled);
16399}
16400
16401inline void Client::enable_server_hostname_verification(bool enabled) {
16402 cli_->enable_server_hostname_verification(enabled);
16403}
16404
16405inline void Client::enable_system_ca(bool enabled) {
16406 cli_->enable_system_ca(enabled);
16407}
16408
16409#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16410inline void Client::enable_windows_certificate_verification(bool enabled) {
16411 if (is_ssl_) {
16412 static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
16413 enabled);
16414 }
16415}
16416#endif
16417
16418inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
16419 const std::string &ca_cert_dir_path) {
16420 cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
16421}
16422
16423inline void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16424 if (is_ssl_) {
16425 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
16426 } else if (ca_cert_store) {
16427 tls::free_ca_store(ca_cert_store);
16428 }
16429}
16430
16431inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
16432 if (is_ssl_) {
16433 // Use the PEM-based path so the CA data is retained for redirect transfer
16434 static_cast<SSLClient &>(*cli_).load_ca_cert_store(ca_cert, size);
16435 }
16436}
16437
16438inline void
16439Client::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16440 if (is_ssl_) {
16441 static_cast<SSLClient &>(*cli_).set_server_certificate_verifier(
16442 std::move(verifier));
16443 }
16444}
16445
16446inline void Client::set_session_verifier(
16447 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16448 if (is_ssl_) {
16449 static_cast<SSLClient &>(*cli_).set_session_verifier(std::move(verifier));
16450 }
16451}
16452
16453inline tls::ctx_t Client::tls_context() const {
16454 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).tls_context(); }
16455 return nullptr;
16456}
16457
16458#endif // CPPHTTPLIB_SSL_ENABLED
16459
16460/*
16461 * Group 7: TLS abstraction layer - Common API
16462 */
16463
16464#ifdef CPPHTTPLIB_SSL_ENABLED
16465
16466namespace tls {
16467
16468// Helper for PeerCert construction
16469inline PeerCert get_peer_cert_from_session(const_session_t session) {
16470 return PeerCert(get_peer_cert(session));
16471}
16472
16473namespace impl {
16474
16475inline VerifyCallback &get_verify_callback() {
16476 static thread_local VerifyCallback callback;
16477 return callback;
16478}
16479
16480inline VerifyCallback &get_mbedtls_verify_callback() {
16481 static thread_local VerifyCallback callback;
16482 return callback;
16483}
16484
16485// Check if a string is an IPv4 address
16486inline bool is_ipv4_address(const std::string &str) {
16487 int dots = 0;
16488 for (char c : str) {
16489 if (c == '.') {
16490 dots++;
16491 } else if (!isdigit(static_cast<unsigned char>(c))) {
16492 return false;
16493 }
16494 }
16495 return dots == 3;
16496}
16497
16498// Parse IPv4 address string to bytes
16499inline bool parse_ipv4(const std::string &str, unsigned char *out) {
16500 const char *p = str.c_str();
16501 for (int i = 0; i < 4; i++) {
16502 if (i > 0) {
16503 if (*p != '.') { return false; }
16504 p++;
16505 }
16506 int val = 0;
16507 int digits = 0;
16508 while (*p >= '0' && *p <= '9') {
16509 val = val * 10 + (*p - '0');
16510 if (val > 255) { return false; }
16511 p++;
16512 digits++;
16513 }
16514 if (digits == 0) { return false; }
16515 // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity
16516 if (digits > 1 && *(p - digits) == '0') { return false; }
16517 out[i] = static_cast<unsigned char>(val);
16518 }
16519 return *p == '\0';
16520}
16521
16522// Parse an IP literal (IPv4 or IPv6) into raw network-order bytes.
16523// `out` must have room for at least 16 bytes. Returns the address length
16524// (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP
16525// literal. Used to match a host against iPAddress SANs the same way the
16526// OpenSSL backend does via X509_check_ip.
16527inline size_t parse_ip_address(const std::string &str, unsigned char *out) {
16528 if (is_ipv4_address(str)) { return parse_ipv4(str, out) ? 4 : 0; }
16529 struct in6_addr addr6 = {};
16530 if (inet_pton(AF_INET6, str.c_str(), &addr6) == 1) {
16531 memcpy(out, &addr6, 16);
16532 return 16;
16533 }
16534 return 0;
16535}
16536
16537#ifdef _WIN32
16538// Enumerate Windows system certificates and call callback with DER data
16539template <typename Callback>
16540inline bool enumerate_windows_system_certs(Callback cb) {
16541 bool loaded = false;
16542 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16543 for (auto store_name : store_names) {
16544 HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
16545 if (hStore) {
16546 PCCERT_CONTEXT pContext = nullptr;
16547 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16548 nullptr) {
16549 if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) {
16550 loaded = true;
16551 }
16552 }
16553 CertCloseStore(hStore, 0);
16554 }
16555 }
16556 return loaded;
16557}
16558#endif
16559
16560#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16561// Enumerate macOS Keychain certificates and call callback with DER data
16562template <typename Callback>
16563inline bool enumerate_macos_keychain_certs(Callback cb) {
16564 bool loaded = false;
16565 const SecTrustSettingsDomain domains[] = {
16566 kSecTrustSettingsDomainSystem,
16567 kSecTrustSettingsDomainAdmin,
16568 kSecTrustSettingsDomainUser,
16569 };
16570 for (auto domain : domains) {
16571 CFArrayRef certs = nullptr;
16572 OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
16573 if (status != errSecSuccess || !certs) {
16574 if (certs) CFRelease(certs);
16575 continue;
16576 }
16577 CFIndex count = CFArrayGetCount(certs);
16578 for (CFIndex i = 0; i < count; i++) {
16579 SecCertificateRef cert =
16580 (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
16581 CFDataRef data = SecCertificateCopyData(cert);
16582 if (data) {
16583 if (cb(CFDataGetBytePtr(data),
16584 static_cast<size_t>(CFDataGetLength(data)))) {
16585 loaded = true;
16586 }
16587 CFRelease(data);
16588 }
16589 }
16590 CFRelease(certs);
16591 }
16592 return loaded;
16593}
16594#endif
16595
16596#if !defined(_WIN32) && !(defined(__APPLE__) && \
16597 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN))
16598// Common CA certificate file paths on Linux/Unix
16599inline const char **system_ca_paths() {
16600 static const char *paths[] = {
16601 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
16602 "/etc/pki/tls/certs/ca-bundle.crt", // RHEL/CentOS
16603 "/etc/ssl/ca-bundle.pem", // OpenSUSE
16604 "/etc/pki/tls/cacert.pem", // OpenELEC
16605 "/etc/ssl/cert.pem", // Alpine, FreeBSD
16606 nullptr};
16607 return paths;
16608}
16609
16610// Common CA certificate directory paths on Linux/Unix
16611inline const char **system_ca_dirs() {
16612 static const char *dirs[] = {"/etc/ssl/certs", // Debian/Ubuntu
16613 "/etc/pki/tls/certs", // RHEL/CentOS
16614 "/usr/share/ca-certificates", // Other
16615 nullptr};
16616 return dirs;
16617}
16618#endif
16619
16620} // namespace impl
16621
16622inline bool set_client_ca_file(ctx_t ctx, const char *ca_file,
16623 const char *ca_dir) {
16624 if (!ctx) { return false; }
16625
16626 bool success = true;
16627 if (ca_file && *ca_file) {
16628 if (!load_ca_file(ctx, ca_file)) { success = false; }
16629 }
16630 if (ca_dir && *ca_dir) {
16631 if (!load_ca_dir(ctx, ca_dir)) { success = false; }
16632 }
16633
16634#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16635 // Set CA list for client certificate request (CertificateRequest message)
16636 if (ca_file && *ca_file) {
16637 auto list = SSL_load_client_CA_file(ca_file);
16638 if (list) { SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), list); }
16639 }
16640#endif
16641
16642 return success;
16643}
16644
16645inline bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
16646 const char *password) {
16647 return set_client_cert_pem(ctx, cert, key, password);
16648}
16649
16650inline bool set_server_cert_file(ctx_t ctx, const char *cert_path,
16651 const char *key_path, const char *password) {
16652 return set_client_cert_file(ctx, cert_path, key_path, password);
16653}
16654
16655// PeerCert implementation
16656inline PeerCert::PeerCert() = default;
16657
16658inline PeerCert::PeerCert(cert_t cert) : cert_(cert) {}
16659
16660inline PeerCert::PeerCert(PeerCert &&other) noexcept : cert_(other.cert_) {
16661 other.cert_ = nullptr;
16662}
16663
16664inline PeerCert &PeerCert::operator=(PeerCert &&other) noexcept {
16665 if (this != &other) {
16666 if (cert_) { free_cert(cert_); }
16667 cert_ = other.cert_;
16668 other.cert_ = nullptr;
16669 }
16670 return *this;
16671}
16672
16673inline PeerCert::~PeerCert() {
16674 if (cert_) { free_cert(cert_); }
16675}
16676
16677inline PeerCert::operator bool() const { return cert_ != nullptr; }
16678
16679inline std::string PeerCert::subject_cn() const {
16680 return cert_ ? get_cert_subject_cn(cert_) : std::string();
16681}
16682
16683inline std::string PeerCert::issuer_name() const {
16684 return cert_ ? get_cert_issuer_name(cert_) : std::string();
16685}
16686
16687inline bool PeerCert::check_hostname(const char *hostname) const {
16688 return cert_ ? verify_hostname(cert_, hostname) : false;
16689}
16690
16691inline std::vector<SanEntry> PeerCert::sans() const {
16692 std::vector<SanEntry> result;
16693 if (cert_) { get_cert_sans(cert_, result); }
16694 return result;
16695}
16696
16697inline bool PeerCert::validity(time_t &not_before, time_t &not_after) const {
16698 return cert_ ? get_cert_validity(cert_, not_before, not_after) : false;
16699}
16700
16701inline std::string PeerCert::serial() const {
16702 return cert_ ? get_cert_serial(cert_) : std::string();
16703}
16704
16705// VerifyContext method implementations
16706inline std::string VerifyContext::subject_cn() const {
16707 return cert ? get_cert_subject_cn(cert) : std::string();
16708}
16709
16710inline std::string VerifyContext::issuer_name() const {
16711 return cert ? get_cert_issuer_name(cert) : std::string();
16712}
16713
16714inline bool VerifyContext::check_hostname(const char *hostname) const {
16715 return cert ? verify_hostname(cert, hostname) : false;
16716}
16717
16718inline std::vector<SanEntry> VerifyContext::sans() const {
16719 std::vector<SanEntry> result;
16720 if (cert) { get_cert_sans(cert, result); }
16721 return result;
16722}
16723
16724inline bool VerifyContext::validity(time_t &not_before,
16725 time_t &not_after) const {
16726 return cert ? get_cert_validity(cert, not_before, not_after) : false;
16727}
16728
16729inline std::string VerifyContext::serial() const {
16730 return cert ? get_cert_serial(cert) : std::string();
16731}
16732
16733// TlsError static method implementation
16734inline std::string TlsError::verify_error_to_string(long error_code) {
16735 return verify_error_string(error_code);
16736}
16737
16738} // namespace tls
16739
16740// Request::peer_cert() implementation
16741inline tls::PeerCert Request::peer_cert() const {
16742 return tls::get_peer_cert_from_session(ssl);
16743}
16744
16745// Request::sni() implementation
16746inline std::string Request::sni() const {
16747 if (!ssl) { return std::string(); }
16748 const char *s = tls::get_sni(ssl);
16749 return s ? std::string(s) : std::string();
16750}
16751
16752#endif // CPPHTTPLIB_SSL_ENABLED
16753
16754/*
16755 * Group 8: TLS abstraction layer - OpenSSL backend
16756 */
16757
16758/*
16759 * OpenSSL Backend Implementation
16760 */
16761
16762#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16763namespace tls {
16764
16765namespace impl {
16766
16767// Helper to map OpenSSL SSL_get_error to ErrorCode
16768inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
16769 switch (ssl_error) {
16770 case SSL_ERROR_NONE: return ErrorCode::Success;
16771 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
16772 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
16773 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
16774 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
16775 case SSL_ERROR_SSL:
16776 default: return ErrorCode::Fatal;
16777 }
16778}
16779
16780// Helper: Create client CA list from PEM string
16781// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
16782// Caller takes ownership of returned list
16783inline STACK_OF(X509_NAME) *
16784 create_client_ca_list_from_pem(const char *ca_pem) {
16785 if (!ca_pem) { return nullptr; }
16786
16787 auto ca_list = sk_X509_NAME_new_null();
16788 if (!ca_list) { return nullptr; }
16789
16790 BIO *bio = BIO_new_mem_buf(ca_pem, -1);
16791 if (!bio) {
16792 sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
16793 return nullptr;
16794 }
16795
16796 X509 *cert = nullptr;
16797 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16798 nullptr) {
16799 const X509_NAME *name = X509_get_subject_name(cert);
16800 if (name) {
16801 sk_X509_NAME_push(ca_list, X509_NAME_dup(const_cast<X509_NAME *>(name)));
16802 }
16803 X509_free(cert);
16804 }
16805 BIO_free(bio);
16806
16807 return ca_list;
16808}
16809
16810// OpenSSL verify callback wrapper
16811inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
16812 auto &callback = get_verify_callback();
16813 if (!callback) { return preverify_ok; }
16814
16815 // Get SSL object from X509_STORE_CTX
16816 auto ssl = static_cast<SSL *>(
16817 X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
16818 if (!ssl) { return preverify_ok; }
16819
16820 // Get current certificate and depth
16821 auto cert = X509_STORE_CTX_get_current_cert(ctx);
16822 int depth = X509_STORE_CTX_get_error_depth(ctx);
16823 int error = X509_STORE_CTX_get_error(ctx);
16824
16825 // Build context
16826 VerifyContext verify_ctx;
16827 verify_ctx.session = static_cast<session_t>(ssl);
16828 verify_ctx.cert = static_cast<cert_t>(cert);
16829 verify_ctx.depth = depth;
16830 verify_ctx.preverify_ok = (preverify_ok != 0);
16831 verify_ctx.error_code = error;
16832 verify_ctx.error_string =
16833 (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
16834
16835 return callback(verify_ctx) ? 1 : 0;
16836}
16837
16838// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
16839// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot
16840// that must be released with release_store_objects
16841#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) && \
16842 OPENSSL_VERSION_NUMBER >= 0x30300000L
16843#define CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
16844#endif
16845
16846inline STACK_OF(X509_OBJECT) * get_store_objects(X509_STORE *store) {
16847#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
16848 return X509_STORE_get1_objects(store);
16849#else
16850 return X509_STORE_get0_objects(store);
16851#endif
16852}
16853
16854inline void release_store_objects(STACK_OF(X509_OBJECT) * objs) {
16855#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
16856 sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
16857#else
16858 (void)objs; // get0 variant returns an internal pointer; nothing to free
16859#endif
16860}
16861
16862} // namespace impl
16863
16864inline ctx_t create_client_context() {
16865 SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
16866 if (ctx) {
16867 // Disable auto-retry to properly handle non-blocking I/O
16868 SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
16869 // Set minimum TLS version
16870 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
16871 }
16872 return static_cast<ctx_t>(ctx);
16873}
16874
16875inline void free_context(ctx_t ctx) {
16876 if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
16877}
16878
16879inline bool set_min_version(ctx_t ctx, Version version) {
16880 if (!ctx) return false;
16881 return SSL_CTX_set_min_proto_version(static_cast<SSL_CTX *>(ctx),
16882 static_cast<int>(version)) == 1;
16883}
16884
16885inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
16886 if (!ctx || !pem || len == 0) return false;
16887
16888 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16889 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16890 if (!store) return false;
16891
16892 auto bio = BIO_new_mem_buf(pem, static_cast<int>(len));
16893 if (!bio) return false;
16894
16895 bool ok = true;
16896 X509 *cert = nullptr;
16897 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16898 nullptr) {
16899 if (X509_STORE_add_cert(store, cert) != 1) {
16900 // Ignore duplicate errors
16901 auto err = ERR_peek_last_error();
16902 if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
16903 ok = false;
16904 }
16905 }
16906 X509_free(cert);
16907 if (!ok) break;
16908 }
16909 BIO_free(bio);
16910
16911 // Clear any "no more certificates" errors
16912 ERR_clear_error();
16913 return ok;
16914}
16915
16916inline bool load_ca_file(ctx_t ctx, const char *file_path) {
16917 if (!ctx || !file_path) return false;
16918 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), file_path,
16919 nullptr) == 1;
16920}
16921
16922inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
16923 if (!ctx || !dir_path) return false;
16924 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), nullptr,
16925 dir_path) == 1;
16926}
16927
16928inline bool load_system_certs(ctx_t ctx) {
16929 if (!ctx) return false;
16930 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16931
16932#ifdef _WIN32
16933 // Windows: Load from system certificate store (ROOT and CA)
16934 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16935 if (!store) return false;
16936
16937 bool loaded_any = false;
16938 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16939 for (auto store_name : store_names) {
16940 auto hStore = CertOpenSystemStoreW(NULL, store_name);
16941 if (!hStore) continue;
16942
16943 PCCERT_CONTEXT pContext = nullptr;
16944 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16945 nullptr) {
16946 const unsigned char *data = pContext->pbCertEncoded;
16947 auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
16948 if (x509) {
16949 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16950 X509_free(x509);
16951 }
16952 }
16953 CertCloseStore(hStore, 0);
16954 }
16955 return loaded_any;
16956
16957#elif defined(__APPLE__)
16958#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16959 // macOS: Load from Keychain
16960 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16961 if (!store) return false;
16962
16963 bool loaded_any = false;
16964 const SecTrustSettingsDomain domains[] = {
16965 kSecTrustSettingsDomainSystem,
16966 kSecTrustSettingsDomainAdmin,
16967 kSecTrustSettingsDomainUser,
16968 };
16969 for (auto domain : domains) {
16970 CFArrayRef certs = nullptr;
16971 if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
16972 !certs) {
16973 if (certs) CFRelease(certs);
16974 continue;
16975 }
16976 auto count = CFArrayGetCount(certs);
16977 for (CFIndex i = 0; i < count; i++) {
16978 auto cert = reinterpret_cast<SecCertificateRef>(
16979 const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
16980 CFDataRef der = SecCertificateCopyData(cert);
16981 if (der) {
16982 const unsigned char *data = CFDataGetBytePtr(der);
16983 auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
16984 if (x509) {
16985 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16986 X509_free(x509);
16987 }
16988 CFRelease(der);
16989 }
16990 }
16991 CFRelease(certs);
16992 }
16993 return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16994#else
16995 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16996#endif
16997
16998#else
16999 // Other Unix: use default verify paths
17000 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
17001#endif
17002}
17003
17004inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
17005 const char *password) {
17006 if (!ctx || !cert || !key) return false;
17007
17008 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17009
17010 // Load certificate
17011 auto cert_bio = BIO_new_mem_buf(cert, -1);
17012 if (!cert_bio) return false;
17013
17014 auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17015 BIO_free(cert_bio);
17016 if (!x509) return false;
17017
17018 auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1;
17019 X509_free(x509);
17020 if (!cert_ok) return false;
17021
17022 // Load private key
17023 auto key_bio = BIO_new_mem_buf(key, -1);
17024 if (!key_bio) return false;
17025
17026 auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17027 password ? const_cast<char *>(password)
17028 : nullptr);
17029 BIO_free(key_bio);
17030 if (!pkey) return false;
17031
17032 auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1;
17033 EVP_PKEY_free(pkey);
17034
17035 return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1;
17036}
17037
17038inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
17039 const char *key_path, const char *password) {
17040 if (!ctx || !cert_path || !key_path) return false;
17041
17042 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17043
17044 if (password && password[0] != '\0') {
17045 SSL_CTX_set_default_passwd_cb_userdata(
17046 ssl_ctx, reinterpret_cast<void *>(const_cast<char *>(password)));
17047 }
17048
17049 return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 &&
17050 SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1;
17051}
17052
17053inline ctx_t create_server_context() {
17054 SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
17055 if (ctx) {
17056 SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION |
17057 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
17058 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
17059 }
17060 return static_cast<ctx_t>(ctx);
17061}
17062
17063inline void set_verify_client(ctx_t ctx, bool require) {
17064 if (!ctx) return;
17065 SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
17066 require
17067 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
17068 : SSL_VERIFY_NONE,
17069 nullptr);
17070}
17071
17072inline session_t create_session(ctx_t ctx, socket_t sock) {
17073 if (!ctx || sock == INVALID_SOCKET) return nullptr;
17074
17075 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17076 SSL *ssl = SSL_new(ssl_ctx);
17077 if (!ssl) return nullptr;
17078
17079 // Disable auto-retry for proper non-blocking I/O handling
17080 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
17081
17082 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
17083 if (!bio) {
17084 SSL_free(ssl);
17085 return nullptr;
17086 }
17087
17088 SSL_set_bio(ssl, bio, bio);
17089 return static_cast<session_t>(ssl);
17090}
17091
17092inline void free_session(session_t session) {
17093 if (session) { SSL_free(static_cast<SSL *>(session)); }
17094}
17095
17096inline bool set_sni(session_t session, const char *hostname) {
17097 if (!session || !hostname) return false;
17098
17099 auto ssl = static_cast<SSL *>(session);
17100
17101 // Set SNI (Server Name Indication) only - does not enable verification
17102#if defined(OPENSSL_IS_BORINGSSL)
17103 return SSL_set_tlsext_host_name(ssl, hostname) == 1;
17104#else
17105 // Direct call instead of macro to suppress -Wold-style-cast warning
17106 return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
17107 static_cast<void *>(const_cast<char *>(hostname))) == 1;
17108#endif
17109}
17110
17111inline bool set_hostname(session_t session, const char *hostname) {
17112 if (!session || !hostname) return false;
17113
17114 auto ssl = static_cast<SSL *>(session);
17115
17116 // Enable hostname verification
17117 auto param = SSL_get0_param(ssl);
17118 if (!param) return false;
17119
17120 if (detail::is_ip_address(hostname)) {
17121 // RFC 6066: SNI must not be set for IP addresses; verify against the
17122 // certificate's IP SANs instead of its DNS names
17123 if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; }
17124 } else {
17125 // Set SNI (Server Name Indication)
17126 if (!set_sni(session, hostname)) { return false; }
17127
17128 X509_VERIFY_PARAM_set_hostflags(param,
17129 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
17130 if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
17131 }
17132
17133 SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
17134 return true;
17135}
17136
17137inline TlsError connect(session_t session) {
17138 if (!session) { return TlsError(); }
17139
17140 auto ssl = static_cast<SSL *>(session);
17141 auto ret = SSL_connect(ssl);
17142
17143 TlsError err;
17144 if (ret == 1) {
17145 err.code = ErrorCode::Success;
17146 } else {
17147 auto ssl_err = SSL_get_error(ssl, ret);
17148 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17149 err.backend_code = ERR_get_error();
17150 }
17151 return err;
17152}
17153
17154inline TlsError accept(session_t session) {
17155 if (!session) { return TlsError(); }
17156
17157 auto ssl = static_cast<SSL *>(session);
17158 auto ret = SSL_accept(ssl);
17159
17160 TlsError err;
17161 if (ret == 1) {
17162 err.code = ErrorCode::Success;
17163 } else {
17164 auto ssl_err = SSL_get_error(ssl, ret);
17165 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17166 err.backend_code = ERR_get_error();
17167 }
17168 return err;
17169}
17170
17171inline bool connect_nonblocking(session_t session, socket_t sock,
17172 time_t timeout_sec, time_t timeout_usec,
17173 TlsError *err) {
17174 if (!session) {
17175 if (err) { err->code = ErrorCode::Fatal; }
17176 return false;
17177 }
17178
17179 auto ssl = static_cast<SSL *>(session);
17180 auto bio = SSL_get_rbio(ssl);
17181
17182 // Set non-blocking mode for handshake
17183 detail::set_nonblocking(sock, true);
17184 if (bio) { BIO_set_nbio(bio, 1); }
17185
17186 auto cleanup = detail::scope_exit([&]() {
17187 // Restore blocking mode after handshake
17188 if (bio) { BIO_set_nbio(bio, 0); }
17189 detail::set_nonblocking(sock, false);
17190 });
17191
17192 auto res = 0;
17193 while ((res = SSL_connect(ssl)) != 1) {
17194 auto ssl_err = SSL_get_error(ssl, res);
17195 switch (ssl_err) {
17196 case SSL_ERROR_WANT_READ:
17197 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17198 continue;
17199 }
17200 break;
17201 case SSL_ERROR_WANT_WRITE:
17202 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17203 continue;
17204 }
17205 break;
17206 default: break;
17207 }
17208 if (err) {
17209 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17210 err->backend_code = ERR_get_error();
17211 }
17212 return false;
17213 }
17214 if (err) { err->code = ErrorCode::Success; }
17215 return true;
17216}
17217
17218inline bool accept_nonblocking(session_t session, socket_t sock,
17219 time_t timeout_sec, time_t timeout_usec,
17220 TlsError *err) {
17221 if (!session) {
17222 if (err) { err->code = ErrorCode::Fatal; }
17223 return false;
17224 }
17225
17226 auto ssl = static_cast<SSL *>(session);
17227 auto bio = SSL_get_rbio(ssl);
17228
17229 // Set non-blocking mode for handshake
17230 detail::set_nonblocking(sock, true);
17231 if (bio) { BIO_set_nbio(bio, 1); }
17232
17233 auto cleanup = detail::scope_exit([&]() {
17234 // Restore blocking mode after handshake
17235 if (bio) { BIO_set_nbio(bio, 0); }
17236 detail::set_nonblocking(sock, false);
17237 });
17238
17239 auto res = 0;
17240 while ((res = SSL_accept(ssl)) != 1) {
17241 auto ssl_err = SSL_get_error(ssl, res);
17242 switch (ssl_err) {
17243 case SSL_ERROR_WANT_READ:
17244 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17245 continue;
17246 }
17247 break;
17248 case SSL_ERROR_WANT_WRITE:
17249 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17250 continue;
17251 }
17252 break;
17253 default: break;
17254 }
17255 if (err) {
17256 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17257 err->backend_code = ERR_get_error();
17258 }
17259 return false;
17260 }
17261 if (err) { err->code = ErrorCode::Success; }
17262 return true;
17263}
17264
17265inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
17266 if (!session || !buf) {
17267 err.code = ErrorCode::Fatal;
17268 return -1;
17269 }
17270
17271 auto ssl = static_cast<SSL *>(session);
17272 constexpr auto max_len =
17273 static_cast<size_t>((std::numeric_limits<int>::max)());
17274 if (len > max_len) { len = max_len; }
17275 auto ret = SSL_read(ssl, buf, static_cast<int>(len));
17276
17277 if (ret > 0) {
17278 err.code = ErrorCode::Success;
17279 return ret;
17280 }
17281
17282 auto ssl_err = SSL_get_error(ssl, ret);
17283 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17284 if (err.code == ErrorCode::PeerClosed) {
17285 return 0;
17286 } // Gracefully handle the peer closed state.
17287 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17288 return -1;
17289}
17290
17291inline ssize_t write(session_t session, const void *buf, size_t len,
17292 TlsError &err) {
17293 if (!session || !buf) {
17294 err.code = ErrorCode::Fatal;
17295 return -1;
17296 }
17297
17298 auto ssl = static_cast<SSL *>(session);
17299 auto ret = SSL_write(ssl, buf, static_cast<int>(len));
17300
17301 if (ret > 0) {
17302 err.code = ErrorCode::Success;
17303 return ret;
17304 }
17305
17306 auto ssl_err = SSL_get_error(ssl, ret);
17307 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17308 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17309 return -1;
17310}
17311
17312inline int pending(const_session_t session) {
17313 if (!session) return 0;
17314 return SSL_pending(static_cast<SSL *>(const_cast<void *>(session)));
17315}
17316
17317inline void shutdown(session_t session, bool graceful) {
17318 if (!session) return;
17319
17320 auto ssl = static_cast<SSL *>(session);
17321 if (graceful) {
17322 // First call sends close_notify
17323 if (SSL_shutdown(ssl) == 0) {
17324 // Second call waits for peer's close_notify
17325 SSL_shutdown(ssl);
17326 }
17327 }
17328}
17329
17330inline bool is_peer_closed(session_t session, socket_t sock) {
17331 if (!session) return true;
17332
17333 // Temporarily set socket to non-blocking to avoid blocking on SSL_peek
17334 detail::set_nonblocking(sock, true);
17335 auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
17336
17337 auto ssl = static_cast<SSL *>(session);
17338 char buf;
17339 auto ret = SSL_peek(ssl, &buf, 1);
17340 if (ret > 0) return false;
17341
17342 auto err = SSL_get_error(ssl, ret);
17343 return err == SSL_ERROR_ZERO_RETURN;
17344}
17345
17346inline cert_t get_peer_cert(const_session_t session) {
17347 if (!session) return nullptr;
17348 return static_cast<cert_t>(SSL_get1_peer_certificate(
17349 static_cast<SSL *>(const_cast<void *>(session))));
17350}
17351
17352inline void free_cert(cert_t cert) {
17353 if (cert) { X509_free(static_cast<X509 *>(cert)); }
17354}
17355
17356inline bool verify_hostname(cert_t cert, const char *hostname) {
17357 if (!cert || !hostname) return false;
17358
17359 auto x509 = static_cast<X509 *>(cert);
17360
17361 // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names
17362 if (detail::is_ip_address(hostname)) {
17363 return X509_check_ip_asc(x509, hostname, 0) == 1;
17364 }
17365 return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
17366}
17367
17368inline uint64_t hostname_mismatch_code() {
17369 return static_cast<uint64_t>(X509_V_ERR_HOSTNAME_MISMATCH);
17370}
17371
17372inline long get_verify_result(const_session_t session) {
17373 if (!session) return X509_V_ERR_UNSPECIFIED;
17374 return SSL_get_verify_result(static_cast<SSL *>(const_cast<void *>(session)));
17375}
17376
17377inline std::string get_cert_subject_cn(cert_t cert) {
17378 if (!cert) return "";
17379 auto x509 = static_cast<X509 *>(cert);
17380 auto subject_name = X509_get_subject_name(x509);
17381 if (!subject_name) return "";
17382
17383 // X509_NAME_get_text_by_NID is deprecated since OpenSSL 4.0
17384 auto idx = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
17385 if (idx < 0) return "";
17386
17387 auto entry = X509_NAME_get_entry(subject_name, idx);
17388 if (!entry) return "";
17389
17390 auto data = X509_NAME_ENTRY_get_data(entry);
17391 if (!data) return "";
17392
17393 return std::string(
17394 reinterpret_cast<const char *>(ASN1_STRING_get0_data(data)),
17395 static_cast<size_t>(ASN1_STRING_length(data)));
17396}
17397
17398inline std::string get_cert_issuer_name(cert_t cert) {
17399 if (!cert) return "";
17400 auto x509 = static_cast<X509 *>(cert);
17401 auto issuer_name = X509_get_issuer_name(x509);
17402 if (!issuer_name) return "";
17403
17404 char buf[256];
17405 X509_NAME_oneline(issuer_name, buf, sizeof(buf));
17406 return std::string(buf);
17407}
17408
17409inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
17410 sans.clear();
17411 if (!cert) return false;
17412 auto x509 = static_cast<X509 *>(cert);
17413
17414 auto names = static_cast<GENERAL_NAMES *>(
17415 X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
17416 if (!names) return true; // No SANs is valid
17417
17418 auto count = sk_GENERAL_NAME_num(names);
17419 for (decltype(count) i = 0; i < count; i++) {
17420 auto gen = sk_GENERAL_NAME_value(names, i);
17421 if (!gen) continue;
17422
17423 SanEntry entry;
17424 switch (gen->type) {
17425 case GEN_DNS:
17426 entry.type = SanType::DNS;
17427 if (gen->d.dNSName) {
17428 entry.value = std::string(
17429 reinterpret_cast<const char *>(
17430 ASN1_STRING_get0_data(gen->d.dNSName)),
17431 static_cast<size_t>(ASN1_STRING_length(gen->d.dNSName)));
17432 }
17433 break;
17434 case GEN_IPADD:
17435 entry.type = SanType::IP;
17436 if (gen->d.iPAddress) {
17437 auto data = ASN1_STRING_get0_data(gen->d.iPAddress);
17438 auto len = ASN1_STRING_length(gen->d.iPAddress);
17439 if (len == 4) {
17440 // IPv4
17441 char buf[INET_ADDRSTRLEN];
17442 inet_ntop(AF_INET, data, buf, sizeof(buf));
17443 entry.value = buf;
17444 } else if (len == 16) {
17445 // IPv6
17446 char buf[INET6_ADDRSTRLEN];
17447 inet_ntop(AF_INET6, data, buf, sizeof(buf));
17448 entry.value = buf;
17449 }
17450 }
17451 break;
17452 case GEN_EMAIL:
17453 entry.type = SanType::EMAIL;
17454 if (gen->d.rfc822Name) {
17455 entry.value = std::string(
17456 reinterpret_cast<const char *>(
17457 ASN1_STRING_get0_data(gen->d.rfc822Name)),
17458 static_cast<size_t>(ASN1_STRING_length(gen->d.rfc822Name)));
17459 }
17460 break;
17461 case GEN_URI:
17462 entry.type = SanType::URI;
17463 if (gen->d.uniformResourceIdentifier) {
17464 entry.value = std::string(
17465 reinterpret_cast<const char *>(
17466 ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
17467 static_cast<size_t>(
17468 ASN1_STRING_length(gen->d.uniformResourceIdentifier)));
17469 }
17470 break;
17471 default: entry.type = SanType::OTHER; break;
17472 }
17473
17474 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
17475 }
17476
17477 GENERAL_NAMES_free(names);
17478 return true;
17479}
17480
17481inline bool get_cert_validity(cert_t cert, time_t &not_before,
17482 time_t &not_after) {
17483 if (!cert) return false;
17484 auto x509 = static_cast<X509 *>(cert);
17485
17486 auto nb = X509_get0_notBefore(x509);
17487 auto na = X509_get0_notAfter(x509);
17488 if (!nb || !na) return false;
17489
17490 ASN1_TIME *epoch = ASN1_TIME_new();
17491 if (!epoch) return false;
17492 auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); });
17493
17494 if (!ASN1_TIME_set(epoch, 0)) return false;
17495
17496 int pday, psec;
17497
17498 if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) return false;
17499 not_before = 86400 * (time_t)pday + psec;
17500
17501 if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) return false;
17502 not_after = 86400 * (time_t)pday + psec;
17503
17504 return true;
17505}
17506
17507inline std::string get_cert_serial(cert_t cert) {
17508 if (!cert) return "";
17509 auto x509 = static_cast<X509 *>(cert);
17510
17511 auto serial = X509_get_serialNumber(x509);
17512 if (!serial) return "";
17513
17514 auto bn = ASN1_INTEGER_to_BN(serial, nullptr);
17515 if (!bn) return "";
17516
17517 auto hex = BN_bn2hex(bn);
17518 BN_free(bn);
17519 if (!hex) return "";
17520
17521 std::string result(hex);
17522 OPENSSL_free(hex);
17523 return result;
17524}
17525
17526inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
17527 if (!cert) return false;
17528 auto x509 = static_cast<X509 *>(cert);
17529 auto len = i2d_X509(x509, nullptr);
17530 if (len < 0) return false;
17531 der.resize(static_cast<size_t>(len));
17532 auto p = der.data();
17533 i2d_X509(x509, &p);
17534 return true;
17535}
17536
17537inline const char *get_sni(const_session_t session) {
17538 if (!session) return nullptr;
17539 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17540 return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
17541}
17542
17543inline uint64_t peek_error() { return ERR_peek_last_error(); }
17544
17545inline uint64_t get_error() { return ERR_get_error(); }
17546
17547inline std::string error_string(uint64_t code) {
17548 char buf[256];
17549 ERR_error_string_n(static_cast<unsigned long>(code), buf, sizeof(buf));
17550 return std::string(buf);
17551}
17552
17553inline ca_store_t create_ca_store(const char *pem, size_t len) {
17554 auto mem = BIO_new_mem_buf(pem, static_cast<int>(len));
17555 if (!mem) { return nullptr; }
17556 auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); });
17557
17558 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
17559 if (!inf) { return nullptr; }
17560
17561 auto store = X509_STORE_new();
17562 if (store) {
17563 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
17564 auto itmp = sk_X509_INFO_value(inf, i);
17565 if (!itmp) { continue; }
17566 if (itmp->x509) { X509_STORE_add_cert(store, itmp->x509); }
17567 if (itmp->crl) { X509_STORE_add_crl(store, itmp->crl); }
17568 }
17569 }
17570
17571 sk_X509_INFO_pop_free(inf, X509_INFO_free);
17572 return static_cast<ca_store_t>(store);
17573}
17574
17575inline void free_ca_store(ca_store_t store) {
17576 if (store) { X509_STORE_free(static_cast<X509_STORE *>(store)); }
17577}
17578
17579inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
17580 if (!ctx || !store) { return false; }
17581 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17582 auto x509_store = static_cast<X509_STORE *>(store);
17583
17584 // Check if same store is already set
17585 if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { return true; }
17586
17587 // SSL_CTX_set_cert_store takes ownership and frees the old store
17588 SSL_CTX_set_cert_store(ssl_ctx, x509_store);
17589 return true;
17590}
17591
17592inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
17593 certs.clear();
17594 if (!ctx) { return 0; }
17595 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17596
17597 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17598 if (!store) { return 0; }
17599
17600 auto objs = impl::get_store_objects(store);
17601 if (!objs) { return 0; }
17602 auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
17603
17604 auto count = sk_X509_OBJECT_num(objs);
17605 for (decltype(count) i = 0; i < count; i++) {
17606 auto obj = sk_X509_OBJECT_value(objs, i);
17607 if (!obj) { continue; }
17608 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17609 auto x509 = X509_OBJECT_get0_X509(obj);
17610 if (x509) {
17611 // Increment reference count so caller can free it
17612 X509_up_ref(x509);
17613 certs.push_back(static_cast<cert_t>(x509));
17614 }
17615 }
17616 }
17617 return certs.size();
17618}
17619
17620inline std::vector<std::string> get_ca_names(ctx_t ctx) {
17621 std::vector<std::string> names;
17622 if (!ctx) { return names; }
17623 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17624
17625 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17626 if (!store) { return names; }
17627
17628 auto objs = impl::get_store_objects(store);
17629 if (!objs) { return names; }
17630 auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
17631
17632 auto count = sk_X509_OBJECT_num(objs);
17633 for (decltype(count) i = 0; i < count; i++) {
17634 auto obj = sk_X509_OBJECT_value(objs, i);
17635 if (!obj) { continue; }
17636 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17637 auto x509 = X509_OBJECT_get0_X509(obj);
17638 if (x509) {
17639 auto subject = X509_get_subject_name(x509);
17640 if (subject) {
17641 char buf[512];
17642 X509_NAME_oneline(subject, buf, sizeof(buf));
17643 names.push_back(buf);
17644 }
17645 }
17646 }
17647 }
17648 return names;
17649}
17650
17651inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
17652 const char *key_pem, const char *password) {
17653 if (!ctx || !cert_pem || !key_pem) { return false; }
17654 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17655
17656 // Load certificate from PEM
17657 auto cert_bio = BIO_new_mem_buf(cert_pem, -1);
17658 if (!cert_bio) { return false; }
17659 auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17660 BIO_free(cert_bio);
17661 if (!cert) { return false; }
17662
17663 // Load private key from PEM
17664 auto key_bio = BIO_new_mem_buf(key_pem, -1);
17665 if (!key_bio) {
17666 X509_free(cert);
17667 return false;
17668 }
17669 auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17670 password ? const_cast<char *>(password)
17671 : nullptr);
17672 BIO_free(key_bio);
17673 if (!key) {
17674 X509_free(cert);
17675 return false;
17676 }
17677
17678 // Update certificate and key
17679 auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 &&
17680 SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1;
17681
17682 X509_free(cert);
17683 EVP_PKEY_free(key);
17684 return ret;
17685}
17686
17687inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
17688 if (!ctx || !ca_pem) { return false; }
17689 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17690
17691 // Create new X509_STORE from PEM
17692 auto store = create_ca_store(ca_pem, strlen(ca_pem));
17693 if (!store) { return false; }
17694
17695 // SSL_CTX_set_cert_store takes ownership
17696 SSL_CTX_set_cert_store(ssl_ctx, static_cast<X509_STORE *>(store));
17697
17698 // Set client CA list for client certificate request
17699 auto ca_list = impl::create_client_ca_list_from_pem(ca_pem);
17700 if (ca_list) {
17701 // SSL_CTX_set_client_CA_list takes ownership of ca_list
17702 SSL_CTX_set_client_CA_list(ssl_ctx, ca_list);
17703 }
17704
17705 return true;
17706}
17707
17708inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
17709 if (!ctx) { return false; }
17710 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17711
17712 impl::get_verify_callback() = std::move(callback);
17713
17714 if (impl::get_verify_callback()) {
17715 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
17716 } else {
17717 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
17718 }
17719 return true;
17720}
17721
17722inline long get_verify_error(const_session_t session) {
17723 if (!session) { return -1; }
17724 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17725 return SSL_get_verify_result(ssl);
17726}
17727
17728inline std::string verify_error_string(long error_code) {
17729 if (error_code == X509_V_OK) { return ""; }
17730 const char *str = X509_verify_cert_error_string(static_cast<int>(error_code));
17731 return str ? str : "unknown error";
17732}
17733
17734} // namespace tls
17735
17736#endif // CPPHTTPLIB_OPENSSL_SUPPORT
17737
17738/*
17739 * Group 9: TLS abstraction layer - Mbed TLS backend
17740 */
17741
17742/*
17743 * Mbed TLS Backend Implementation
17744 */
17745
17746#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
17747namespace tls {
17748
17749namespace impl {
17750
17751// Mbed TLS session wrapper
17752struct MbedTlsSession {
17753 mbedtls_ssl_context ssl;
17754 socket_t sock = INVALID_SOCKET;
17755 std::string hostname; // For client: set via set_sni
17756 std::string sni_hostname; // For server: received from client via SNI callback
17757
17758 MbedTlsSession() { mbedtls_ssl_init(&ssl); }
17759
17760 ~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
17761
17762 MbedTlsSession(const MbedTlsSession &) = delete;
17763 MbedTlsSession &operator=(const MbedTlsSession &) = delete;
17764};
17765
17766// Thread-local error code accessor for Mbed TLS (since it doesn't have an error
17767// queue)
17768inline int &mbedtls_last_error() {
17769 static thread_local int err = 0;
17770 return err;
17771}
17772
17773// Helper to map Mbed TLS error to ErrorCode
17774inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
17775 if (ret == 0) { return ErrorCode::Success; }
17776 if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
17777 if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
17778 if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
17779 return ErrorCode::PeerClosed;
17780 }
17781 if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED ||
17782 ret == MBEDTLS_ERR_NET_RECV_FAILED) {
17783 out_errno = errno;
17784 return ErrorCode::SyscallError;
17785 }
17786 if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
17787 return ErrorCode::CertVerifyFailed;
17788 }
17789 return ErrorCode::Fatal;
17790}
17791
17792// BIO-like send callback for Mbed TLS
17793inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
17794 size_t len) {
17795 auto sock = *static_cast<socket_t *>(ctx);
17796#ifdef _WIN32
17797 auto ret =
17798 send(sock, reinterpret_cast<const char *>(buf), static_cast<int>(len), 0);
17799 if (ret == SOCKET_ERROR) {
17800 int err = WSAGetLastError();
17801 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
17802 return MBEDTLS_ERR_NET_SEND_FAILED;
17803 }
17804#else
17805 auto ret = send(sock, buf, len, 0);
17806 if (ret < 0) {
17807 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17808 return MBEDTLS_ERR_SSL_WANT_WRITE;
17809 }
17810 return MBEDTLS_ERR_NET_SEND_FAILED;
17811 }
17812#endif
17813 return static_cast<int>(ret);
17814}
17815
17816// BIO-like recv callback for Mbed TLS
17817inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
17818 auto sock = *static_cast<socket_t *>(ctx);
17819#ifdef _WIN32
17820 auto ret =
17821 recv(sock, reinterpret_cast<char *>(buf), static_cast<int>(len), 0);
17822 if (ret == SOCKET_ERROR) {
17823 int err = WSAGetLastError();
17824 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_READ; }
17825 return MBEDTLS_ERR_NET_RECV_FAILED;
17826 }
17827#else
17828 auto ret = recv(sock, buf, len, 0);
17829 if (ret < 0) {
17830 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17831 return MBEDTLS_ERR_SSL_WANT_READ;
17832 }
17833 return MBEDTLS_ERR_NET_RECV_FAILED;
17834 }
17835#endif
17836 if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
17837 return static_cast<int>(ret);
17838}
17839
17840// MbedTlsContext constructor/destructor implementations
17841inline MbedTlsContext::MbedTlsContext() {
17842 mbedtls_ssl_config_init(&conf);
17843 mbedtls_entropy_init(&entropy);
17844 mbedtls_ctr_drbg_init(&ctr_drbg);
17845 mbedtls_x509_crt_init(&ca_chain);
17846 mbedtls_x509_crt_init(&own_cert);
17847 mbedtls_pk_init(&own_key);
17848}
17849
17850inline MbedTlsContext::~MbedTlsContext() {
17851 mbedtls_pk_free(&own_key);
17852 mbedtls_x509_crt_free(&own_cert);
17853 mbedtls_x509_crt_free(&ca_chain);
17854 mbedtls_ctr_drbg_free(&ctr_drbg);
17855 mbedtls_entropy_free(&entropy);
17856 mbedtls_ssl_config_free(&conf);
17857}
17858
17859// Thread-local storage for SNI captured during handshake
17860// This is needed because the SNI callback doesn't have a way to pass
17861// session-specific data before the session is fully set up
17862inline std::string &mbedpending_sni() {
17863 static thread_local std::string sni;
17864 return sni;
17865}
17866
17867// SNI callback for Mbed TLS server to capture client's SNI hostname
17868inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
17869 const unsigned char *name, size_t name_len) {
17870 (void)p_ctx;
17871 (void)ssl;
17872
17873 // Store SNI name in thread-local storage
17874 // It will be retrieved and stored in the session after handshake
17875 if (name && name_len > 0) {
17876 mbedpending_sni().assign(reinterpret_cast<const char *>(name), name_len);
17877 } else {
17878 mbedpending_sni().clear();
17879 }
17880 return 0; // Accept any SNI
17881}
17882
17883inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17884 int cert_depth, uint32_t *flags);
17885
17886// MbedTLS verify callback wrapper
17887inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17888 int cert_depth, uint32_t *flags) {
17889 auto &callback = get_verify_callback();
17890 if (!callback) { return 0; } // Continue with default verification
17891
17892 // data points to the MbedTlsSession
17893 auto *session = static_cast<MbedTlsSession *>(data);
17894
17895 // Build context
17896 VerifyContext verify_ctx;
17897 verify_ctx.session = static_cast<session_t>(session);
17898 verify_ctx.cert = static_cast<cert_t>(crt);
17899 verify_ctx.depth = cert_depth;
17900 verify_ctx.preverify_ok = (*flags == 0);
17901 verify_ctx.error_code = static_cast<long>(*flags);
17902
17903 // Convert Mbed TLS flags to error string
17904 static thread_local char error_buf[256];
17905 if (*flags != 0) {
17906 mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags);
17907 verify_ctx.error_string = error_buf;
17908 } else {
17909 verify_ctx.error_string = nullptr;
17910 }
17911
17912 bool accepted = callback(verify_ctx);
17913
17914 if (accepted) {
17915 *flags = 0; // Clear all error flags
17916 return 0;
17917 }
17918 return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
17919}
17920
17921} // namespace impl
17922
17923inline ctx_t create_client_context() {
17924 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17925 if (!ctx) { return nullptr; }
17926
17927 ctx->is_server = false;
17928
17929 // Seed the random number generator
17930 const char *pers = "httplib_client";
17931 int ret = mbedtls_ctr_drbg_seed(
17932 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17933 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17934 if (ret != 0) {
17935 impl::mbedtls_last_error() = ret;
17936 delete ctx;
17937 return nullptr;
17938 }
17939
17940 // Set up SSL config for client
17941 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
17942 MBEDTLS_SSL_TRANSPORT_STREAM,
17943 MBEDTLS_SSL_PRESET_DEFAULT);
17944 if (ret != 0) {
17945 impl::mbedtls_last_error() = ret;
17946 delete ctx;
17947 return nullptr;
17948 }
17949
17950 // Set random number generator
17951 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17952
17953 // Default: verify peer certificate
17954 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
17955
17956 // Set minimum TLS version to 1.2
17957#ifdef CPPHTTPLIB_MBEDTLS_V3
17958 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
17959#else
17960 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
17961 MBEDTLS_SSL_MINOR_VERSION_3);
17962#endif
17963
17964 return static_cast<ctx_t>(ctx);
17965}
17966
17967inline ctx_t create_server_context() {
17968 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17969 if (!ctx) { return nullptr; }
17970
17971 ctx->is_server = true;
17972
17973 // Seed the random number generator
17974 const char *pers = "httplib_server";
17975 int ret = mbedtls_ctr_drbg_seed(
17976 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17977 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17978 if (ret != 0) {
17979 impl::mbedtls_last_error() = ret;
17980 delete ctx;
17981 return nullptr;
17982 }
17983
17984 // Set up SSL config for server
17985 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
17986 MBEDTLS_SSL_TRANSPORT_STREAM,
17987 MBEDTLS_SSL_PRESET_DEFAULT);
17988 if (ret != 0) {
17989 impl::mbedtls_last_error() = ret;
17990 delete ctx;
17991 return nullptr;
17992 }
17993
17994 // Set random number generator
17995 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17996
17997 // Default: don't verify client
17998 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
17999
18000 // Set minimum TLS version to 1.2
18001#ifdef CPPHTTPLIB_MBEDTLS_V3
18002 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
18003#else
18004 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
18005 MBEDTLS_SSL_MINOR_VERSION_3);
18006#endif
18007
18008 // Set SNI callback to capture client's SNI hostname
18009 mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr);
18010
18011 return static_cast<ctx_t>(ctx);
18012}
18013
18014inline void free_context(ctx_t ctx) {
18015 if (ctx) { delete static_cast<impl::MbedTlsContext *>(ctx); }
18016}
18017
18018inline bool set_min_version(ctx_t ctx, Version version) {
18019 if (!ctx) { return false; }
18020 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18021
18022#ifdef CPPHTTPLIB_MBEDTLS_V3
18023 // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum
18024 mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2;
18025 if (version >= Version::TLS1_3) {
18026#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
18027 min_ver = MBEDTLS_SSL_VERSION_TLS1_3;
18028#endif
18029 }
18030 mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver);
18031#else
18032 // Mbed TLS 2.x uses major/minor version numbers
18033 int major = MBEDTLS_SSL_MAJOR_VERSION_3;
18034 int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2
18035 if (version >= Version::TLS1_3) {
18036#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
18037 minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3
18038#else
18039 minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2
18040#endif
18041 }
18042 mbedtls_ssl_conf_min_version(&mctx->conf, major, minor);
18043#endif
18044 return true;
18045}
18046
18047inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
18048 if (!ctx || !pem) { return false; }
18049 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18050
18051 // mbedtls_x509_crt_parse expects null-terminated string for PEM
18052 // Add null terminator if not present
18053 std::string pem_str(pem, len);
18054 int ret = mbedtls_x509_crt_parse(
18055 &mctx->ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
18056 pem_str.size() + 1);
18057 if (ret != 0) {
18058 impl::mbedtls_last_error() = ret;
18059 return false;
18060 }
18061
18062 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18063 return true;
18064}
18065
18066inline bool load_ca_file(ctx_t ctx, const char *file_path) {
18067 if (!ctx || !file_path) { return false; }
18068 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18069
18070 int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path);
18071 if (ret != 0) {
18072 impl::mbedtls_last_error() = ret;
18073 return false;
18074 }
18075
18076 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18077 return true;
18078}
18079
18080inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
18081 if (!ctx || !dir_path) { return false; }
18082 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18083
18084 int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path);
18085 if (ret < 0) { // Returns number of certs on success, negative on error
18086 impl::mbedtls_last_error() = ret;
18087 return false;
18088 }
18089
18090 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18091 return true;
18092}
18093
18094inline bool load_system_certs(ctx_t ctx) {
18095 if (!ctx) { return false; }
18096 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18097 bool loaded = false;
18098
18099#ifdef _WIN32
18100 loaded = impl::enumerate_windows_system_certs(
18101 [&](const unsigned char *data, size_t len) {
18102 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18103 });
18104#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
18105 loaded = impl::enumerate_macos_keychain_certs(
18106 [&](const unsigned char *data, size_t len) {
18107 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18108 });
18109#else
18110 for (auto path = impl::system_ca_paths(); *path; ++path) {
18111 if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) {
18112 loaded = true;
18113 break;
18114 }
18115 }
18116
18117 if (!loaded) {
18118 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
18119 if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) {
18120 loaded = true;
18121 break;
18122 }
18123 }
18124 }
18125#endif
18126
18127 if (loaded) {
18128 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18129 }
18130 return loaded;
18131}
18132
18133inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
18134 const char *password) {
18135 if (!ctx || !cert || !key) { return false; }
18136 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18137
18138 // Parse certificate
18139 std::string cert_str(cert);
18140 int ret = mbedtls_x509_crt_parse(
18141 &mctx->own_cert,
18142 reinterpret_cast<const unsigned char *>(cert_str.c_str()),
18143 cert_str.size() + 1);
18144 if (ret != 0) {
18145 impl::mbedtls_last_error() = ret;
18146 return false;
18147 }
18148
18149 // Parse private key
18150 std::string key_str(key);
18151 const unsigned char *pwd =
18152 password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
18153 size_t pwd_len = password ? strlen(password) : 0;
18154
18155#ifdef CPPHTTPLIB_MBEDTLS_V3
18156 ret = mbedtls_pk_parse_key(
18157 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18158 key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
18159 &mctx->ctr_drbg);
18160#else
18161 ret = mbedtls_pk_parse_key(
18162 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18163 key_str.size() + 1, pwd, pwd_len);
18164#endif
18165 if (ret != 0) {
18166 impl::mbedtls_last_error() = ret;
18167 return false;
18168 }
18169
18170 // Verify that the certificate and private key match
18171#ifdef CPPHTTPLIB_MBEDTLS_V3
18172 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18173 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18174#else
18175 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18176#endif
18177 if (ret != 0) {
18178 impl::mbedtls_last_error() = ret;
18179 return false;
18180 }
18181
18182 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18183 if (ret != 0) {
18184 impl::mbedtls_last_error() = ret;
18185 return false;
18186 }
18187
18188 return true;
18189}
18190
18191inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
18192 const char *key_path, const char *password) {
18193 if (!ctx || !cert_path || !key_path) { return false; }
18194 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18195
18196 // Parse certificate file
18197 int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path);
18198 if (ret != 0) {
18199 impl::mbedtls_last_error() = ret;
18200 return false;
18201 }
18202
18203 // Parse private key file
18204#ifdef CPPHTTPLIB_MBEDTLS_V3
18205 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
18206 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18207#else
18208 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password);
18209#endif
18210 if (ret != 0) {
18211 impl::mbedtls_last_error() = ret;
18212 return false;
18213 }
18214
18215 // Verify that the certificate and private key match
18216#ifdef CPPHTTPLIB_MBEDTLS_V3
18217 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18218 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18219#else
18220 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18221#endif
18222 if (ret != 0) {
18223 impl::mbedtls_last_error() = ret;
18224 return false;
18225 }
18226
18227 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18228 if (ret != 0) {
18229 impl::mbedtls_last_error() = ret;
18230 return false;
18231 }
18232
18233 return true;
18234}
18235
18236inline void set_verify_client(ctx_t ctx, bool require) {
18237 if (!ctx) { return; }
18238 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18239 mctx->verify_client = require;
18240 if (require) {
18241 mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
18242 } else {
18243 // If a verify callback is set, use OPTIONAL mode to ensure the callback
18244 // is called (matching OpenSSL behavior). Otherwise use NONE.
18245 mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback
18246 ? MBEDTLS_SSL_VERIFY_OPTIONAL
18247 : MBEDTLS_SSL_VERIFY_NONE);
18248 }
18249}
18250
18251inline session_t create_session(ctx_t ctx, socket_t sock) {
18252 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
18253 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18254
18255 auto session = new (std::nothrow) impl::MbedTlsSession();
18256 if (!session) { return nullptr; }
18257
18258 session->sock = sock;
18259
18260 int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf);
18261 if (ret != 0) {
18262 impl::mbedtls_last_error() = ret;
18263 delete session;
18264 return nullptr;
18265 }
18266
18267 // Explicitly opt out of in-handshake hostname verification by default;
18268 // since Mbed TLS 3.6.4 a client handshake with certificate verification
18269 // fails outright when no hostname was set. set_sni() installs the real
18270 // hostname for DNS hosts; for IP hosts (where SNI must not be set) the
18271 // caller verifies the certificate identity post-handshake via
18272 // verify_hostname().
18273 mbedtls_ssl_set_hostname(&session->ssl, nullptr);
18274
18275 // Set BIO callbacks
18276 mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
18277 impl::mbedtls_net_recv_cb, nullptr);
18278
18279 // Set per-session verify callback with session pointer if callback is
18280 // registered
18281 if (mctx->has_verify_callback) {
18282 mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
18283 session);
18284 }
18285
18286 return static_cast<session_t>(session);
18287}
18288
18289inline void free_session(session_t session) {
18290 if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
18291}
18292
18293inline bool set_sni(session_t session, const char *hostname) {
18294 if (!session || !hostname) { return false; }
18295 auto msession = static_cast<impl::MbedTlsSession *>(session);
18296
18297 int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
18298 if (ret != 0) {
18299 impl::mbedtls_last_error() = ret;
18300 return false;
18301 }
18302
18303 msession->hostname = hostname;
18304 return true;
18305}
18306
18307inline bool set_hostname(session_t session, const char *hostname) {
18308 // In Mbed TLS, set_hostname also sets up hostname verification
18309 return set_sni(session, hostname);
18310}
18311
18312inline TlsError connect(session_t session) {
18313 TlsError err;
18314 if (!session) {
18315 err.code = ErrorCode::Fatal;
18316 return err;
18317 }
18318
18319 auto msession = static_cast<impl::MbedTlsSession *>(session);
18320 int ret = mbedtls_ssl_handshake(&msession->ssl);
18321
18322 if (ret == 0) {
18323 err.code = ErrorCode::Success;
18324 } else {
18325 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18326 err.backend_code = static_cast<uint64_t>(-ret);
18327 impl::mbedtls_last_error() = ret;
18328 }
18329
18330 return err;
18331}
18332
18333inline TlsError accept(session_t session) {
18334 // Same as connect for Mbed TLS - handshake works for both client and server
18335 auto result = connect(session);
18336
18337 // After successful handshake, capture SNI from thread-local storage
18338 if (result.code == ErrorCode::Success && session) {
18339 auto msession = static_cast<impl::MbedTlsSession *>(session);
18340 msession->sni_hostname = std::move(impl::mbedpending_sni());
18341 impl::mbedpending_sni().clear();
18342 }
18343
18344 return result;
18345}
18346
18347inline bool connect_nonblocking(session_t session, socket_t sock,
18348 time_t timeout_sec, time_t timeout_usec,
18349 TlsError *err) {
18350 if (!session) {
18351 if (err) { err->code = ErrorCode::Fatal; }
18352 return false;
18353 }
18354
18355 auto msession = static_cast<impl::MbedTlsSession *>(session);
18356
18357 // Set socket to non-blocking mode
18358 detail::set_nonblocking(sock, true);
18359 auto cleanup =
18360 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18361
18362 int ret;
18363 while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
18364 if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
18365 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
18366 continue;
18367 }
18368 } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
18369 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
18370 continue;
18371 }
18372 }
18373
18374 // TlsError or timeout
18375 if (err) {
18376 err->code = impl::map_mbedtls_error(ret, err->sys_errno);
18377 err->backend_code = static_cast<uint64_t>(-ret);
18378 }
18379 impl::mbedtls_last_error() = ret;
18380 return false;
18381 }
18382
18383 if (err) { err->code = ErrorCode::Success; }
18384 return true;
18385}
18386
18387inline bool accept_nonblocking(session_t session, socket_t sock,
18388 time_t timeout_sec, time_t timeout_usec,
18389 TlsError *err) {
18390 // Same implementation as connect for Mbed TLS
18391 bool result =
18392 connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
18393
18394 // After successful handshake, capture SNI from thread-local storage
18395 if (result && session) {
18396 auto msession = static_cast<impl::MbedTlsSession *>(session);
18397 msession->sni_hostname = std::move(impl::mbedpending_sni());
18398 impl::mbedpending_sni().clear();
18399 }
18400
18401 return result;
18402}
18403
18404inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
18405 if (!session || !buf) {
18406 err.code = ErrorCode::Fatal;
18407 return -1;
18408 }
18409
18410 auto msession = static_cast<impl::MbedTlsSession *>(session);
18411 int ret =
18412 mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len);
18413
18414 if (ret > 0) {
18415 err.code = ErrorCode::Success;
18416 return static_cast<ssize_t>(ret);
18417 }
18418
18419 if (ret == 0) {
18420 err.code = ErrorCode::PeerClosed;
18421 return 0;
18422 }
18423
18424 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18425 err.backend_code = static_cast<uint64_t>(-ret);
18426 impl::mbedtls_last_error() = ret;
18427 // mbedTLS signals a clean close_notify via a negative error code rather
18428 // than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do.
18429 if (err.code == ErrorCode::PeerClosed) { return 0; }
18430 return -1;
18431}
18432
18433inline ssize_t write(session_t session, const void *buf, size_t len,
18434 TlsError &err) {
18435 if (!session || !buf) {
18436 err.code = ErrorCode::Fatal;
18437 return -1;
18438 }
18439
18440 auto msession = static_cast<impl::MbedTlsSession *>(session);
18441 int ret = mbedtls_ssl_write(&msession->ssl,
18442 static_cast<const unsigned char *>(buf), len);
18443
18444 if (ret > 0) {
18445 err.code = ErrorCode::Success;
18446 return static_cast<ssize_t>(ret);
18447 }
18448
18449 if (ret == 0) {
18450 err.code = ErrorCode::PeerClosed;
18451 return 0;
18452 }
18453
18454 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18455 err.backend_code = static_cast<uint64_t>(-ret);
18456 impl::mbedtls_last_error() = ret;
18457 return -1;
18458}
18459
18460inline int pending(const_session_t session) {
18461 if (!session) { return 0; }
18462 auto msession =
18463 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18464 return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl));
18465}
18466
18467inline void shutdown(session_t session, bool graceful) {
18468 if (!session) { return; }
18469 auto msession = static_cast<impl::MbedTlsSession *>(session);
18470
18471 if (graceful) {
18472 // Try to send close_notify, but don't block forever
18473 int ret;
18474 int attempts = 0;
18475 while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 &&
18476 attempts < 3) {
18477 if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
18478 ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
18479 break;
18480 }
18481 attempts++;
18482 }
18483 }
18484}
18485
18486inline bool is_peer_closed(session_t session, socket_t sock) {
18487 if (!session || sock == INVALID_SOCKET) { return true; }
18488 auto msession = static_cast<impl::MbedTlsSession *>(session);
18489
18490 // Check if there's already decrypted data available in the TLS buffer
18491 // If so, the connection is definitely alive
18492 if (mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { return false; }
18493
18494 // Set socket to non-blocking to avoid blocking on read
18495 detail::set_nonblocking(sock, true);
18496 auto cleanup =
18497 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18498
18499 // Try a 1-byte read to check connection status
18500 // Note: This will consume the byte if data is available, but for the
18501 // purpose of checking if peer is closed, this should be acceptable
18502 // since we're only called when we expect the connection might be closing
18503 unsigned char buf;
18504 int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
18505
18506 // If we got data or WANT_READ (would block), connection is alive
18507 if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
18508
18509 // If we get a peer close notify or a connection reset, the peer is closed
18510 return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
18511 ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0;
18512}
18513
18514inline cert_t get_peer_cert(const_session_t session) {
18515 if (!session) { return nullptr; }
18516 auto msession =
18517 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18518
18519 // Mbed TLS returns a pointer to the internal peer cert chain.
18520 // WARNING: This pointer is only valid while the session is active.
18521 // Do not use the certificate after calling free_session().
18522 const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl);
18523 return const_cast<mbedtls_x509_crt *>(cert);
18524}
18525
18526inline void free_cert(cert_t cert) {
18527 // Mbed TLS: peer certificate is owned by the SSL context.
18528 // No-op here, but callers should still call this for cross-backend
18529 // portability.
18530 (void)cert;
18531}
18532
18533inline bool verify_hostname(cert_t cert, const char *hostname) {
18534 if (!cert || !hostname) { return false; }
18535 auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
18536 std::string host_str(hostname);
18537
18538 // Check if hostname is an IP address (IPv4 or IPv6)
18539 unsigned char ip_bytes[16];
18540 auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
18541 auto is_ip = ip_len > 0;
18542
18543 // Check Subject Alternative Names (SAN)
18544 // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
18545 // - DNS names: raw string bytes
18546 // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
18547 const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
18548 while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
18549 const unsigned char *p = san->buf.p;
18550 size_t len = san->buf.len;
18551
18552 if (is_ip) {
18553 // For an IP host, only a matching iPAddress SAN of the same family
18554 // (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it.
18555 if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { return true; }
18556 } else {
18557 // Check if this SAN is a DNS name (printable ASCII string)
18558 bool is_dns = len > 0;
18559 for (size_t i = 0; i < len && is_dns; i++) {
18560 if (p[i] < 32 || p[i] > 126) { is_dns = false; }
18561 }
18562 if (is_dns) {
18563 std::string san_name(reinterpret_cast<const char *>(p), len);
18564 if (detail::match_hostname(san_name, host_str)) { return true; }
18565 }
18566 }
18567 san = san->next;
18568 }
18569
18570 // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
18571 // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
18572 // the OpenSSL backend's X509_check_ip behaves the same way).
18573 if (!is_ip) {
18574 char cn[256];
18575 int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18576 if (ret > 0) {
18577 std::string cn_str(cn);
18578
18579 // Look for "CN=" in the DN string
18580 size_t cn_pos = cn_str.find("CN=");
18581 if (cn_pos != std::string::npos) {
18582 size_t start = cn_pos + 3;
18583 size_t end = cn_str.find(',', start);
18584 std::string cn_value =
18585 cn_str.substr(start, end == std::string::npos ? end : end - start);
18586
18587 if (detail::match_hostname(cn_value, host_str)) { return true; }
18588 }
18589 }
18590 }
18591
18592 return false;
18593}
18594
18595inline uint64_t hostname_mismatch_code() {
18596 return static_cast<uint64_t>(MBEDTLS_X509_BADCERT_CN_MISMATCH);
18597}
18598
18599inline long get_verify_result(const_session_t session) {
18600 if (!session) { return -1; }
18601 auto msession =
18602 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18603 uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl);
18604 // Return 0 (X509_V_OK equivalent) if verification passed
18605 return flags == 0 ? 0 : static_cast<long>(flags);
18606}
18607
18608inline std::string get_cert_subject_cn(cert_t cert) {
18609 if (!cert) return "";
18610 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18611
18612 // Find the CN in the subject
18613 const mbedtls_x509_name *name = &x509->subject;
18614 while (name != nullptr) {
18615 if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
18616 return std::string(reinterpret_cast<const char *>(name->val.p),
18617 name->val.len);
18618 }
18619 name = name->next;
18620 }
18621 return "";
18622}
18623
18624inline std::string get_cert_issuer_name(cert_t cert) {
18625 if (!cert) return "";
18626 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18627
18628 // Build a human-readable issuer name string
18629 char buf[512];
18630 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer);
18631 if (ret < 0) return "";
18632 return std::string(buf);
18633}
18634
18635inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
18636 sans.clear();
18637 if (!cert) return false;
18638 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18639
18640 // Parse the Subject Alternative Name extension
18641 const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
18642 while (cur != nullptr) {
18643 if (cur->buf.len > 0) {
18644 // Mbed TLS stores SAN as ASN.1 sequences
18645 // The tag byte indicates the type
18646 const unsigned char *p = cur->buf.p;
18647 size_t len = cur->buf.len;
18648
18649 // First byte is the tag
18650 unsigned char tag = *p;
18651 p++;
18652 len--;
18653
18654 // Parse length (simple single-byte length assumed)
18655 if (len > 0 && *p < 0x80) {
18656 size_t value_len = *p;
18657 p++;
18658 len--;
18659
18660 if (value_len <= len) {
18661 SanEntry entry;
18662 // ASN.1 context tags for GeneralName
18663 switch (tag & 0x1F) {
18664 case 2: // dNSName
18665 entry.type = SanType::DNS;
18666 entry.value =
18667 std::string(reinterpret_cast<const char *>(p), value_len);
18668 break;
18669 case 7: // iPAddress
18670 entry.type = SanType::IP;
18671 if (value_len == 4) {
18672 // IPv4
18673 char buf[16];
18674 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
18675 entry.value = buf;
18676 } else if (value_len == 16) {
18677 // IPv6
18678 char buf[64];
18679 snprintf(buf, sizeof(buf),
18680 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
18681 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
18682 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
18683 p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
18684 entry.value = buf;
18685 }
18686 break;
18687 case 1: // rfc822Name (email)
18688 entry.type = SanType::EMAIL;
18689 entry.value =
18690 std::string(reinterpret_cast<const char *>(p), value_len);
18691 break;
18692 case 6: // uniformResourceIdentifier
18693 entry.type = SanType::URI;
18694 entry.value =
18695 std::string(reinterpret_cast<const char *>(p), value_len);
18696 break;
18697 default: entry.type = SanType::OTHER; break;
18698 }
18699
18700 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
18701 }
18702 }
18703 }
18704 cur = cur->next;
18705 }
18706 return true;
18707}
18708
18709inline bool get_cert_validity(cert_t cert, time_t &not_before,
18710 time_t &not_after) {
18711 if (!cert) return false;
18712 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18713
18714 // Convert mbedtls_x509_time to time_t
18715 auto to_time_t = [](const mbedtls_x509_time &t) -> time_t {
18716 struct tm tm_time = {};
18717 tm_time.tm_year = t.year - 1900;
18718 tm_time.tm_mon = t.mon - 1;
18719 tm_time.tm_mday = t.day;
18720 tm_time.tm_hour = t.hour;
18721 tm_time.tm_min = t.min;
18722 tm_time.tm_sec = t.sec;
18723#ifdef _WIN32
18724 return _mkgmtime(&tm_time);
18725#else
18726 return timegm(&tm_time);
18727#endif
18728 };
18729
18730 not_before = to_time_t(x509->valid_from);
18731 not_after = to_time_t(x509->valid_to);
18732 return true;
18733}
18734
18735inline std::string get_cert_serial(cert_t cert) {
18736 if (!cert) return "";
18737 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18738
18739 // Convert serial number to hex string
18740 std::string result;
18741 result.reserve(x509->serial.len * 2);
18742 for (size_t i = 0; i < x509->serial.len; i++) {
18743 char hex[3];
18744 snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]);
18745 result += hex;
18746 }
18747 return result;
18748}
18749
18750inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
18751 if (!cert) return false;
18752 auto crt = static_cast<mbedtls_x509_crt *>(cert);
18753 if (!crt->raw.p || crt->raw.len == 0) return false;
18754 der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
18755 return true;
18756}
18757
18758inline const char *get_sni(const_session_t session) {
18759 if (!session) return nullptr;
18760 auto msession = static_cast<const impl::MbedTlsSession *>(session);
18761
18762 // For server: return SNI received from client during handshake
18763 if (!msession->sni_hostname.empty()) {
18764 return msession->sni_hostname.c_str();
18765 }
18766
18767 // For client: return the hostname set via set_sni
18768 if (!msession->hostname.empty()) { return msession->hostname.c_str(); }
18769
18770 return nullptr;
18771}
18772
18773inline uint64_t peek_error() {
18774 // Mbed TLS doesn't have an error queue, return the last error
18775 return static_cast<uint64_t>(-impl::mbedtls_last_error());
18776}
18777
18778inline uint64_t get_error() {
18779 // Mbed TLS doesn't have an error queue, return and clear the last error
18780 uint64_t err = static_cast<uint64_t>(-impl::mbedtls_last_error());
18781 impl::mbedtls_last_error() = 0;
18782 return err;
18783}
18784
18785inline std::string error_string(uint64_t code) {
18786 char buf[256];
18787 mbedtls_strerror(-static_cast<int>(code), buf, sizeof(buf));
18788 return std::string(buf);
18789}
18790
18791inline ca_store_t create_ca_store(const char *pem, size_t len) {
18792 auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
18793 if (!ca_chain) { return nullptr; }
18794
18795 mbedtls_x509_crt_init(ca_chain);
18796
18797 // mbedtls_x509_crt_parse expects null-terminated PEM
18798 int ret = mbedtls_x509_crt_parse(ca_chain,
18799 reinterpret_cast<const unsigned char *>(pem),
18800 len + 1); // +1 for null terminator
18801 if (ret != 0) {
18802 // Try without +1 in case PEM is already null-terminated
18803 ret = mbedtls_x509_crt_parse(
18804 ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
18805 if (ret != 0) {
18806 mbedtls_x509_crt_free(ca_chain);
18807 delete ca_chain;
18808 return nullptr;
18809 }
18810 }
18811
18812 return static_cast<ca_store_t>(ca_chain);
18813}
18814
18815inline void free_ca_store(ca_store_t store) {
18816 if (store) {
18817 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18818 mbedtls_x509_crt_free(ca_chain);
18819 delete ca_chain;
18820 }
18821}
18822
18823inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
18824 if (!ctx || !store) { return false; }
18825 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18826 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18827
18828 // Free existing CA chain
18829 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18830 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18831
18832 // Copy the CA chain (deep copy)
18833 // Parse from the raw data of the source cert
18834 mbedtls_x509_crt *src = ca_chain;
18835 while (src != nullptr) {
18836 int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
18837 src->raw.len);
18838 if (ret != 0) {
18839 free_ca_store(store);
18840 return false;
18841 }
18842 src = src->next;
18843 }
18844
18845 // This function takes ownership of the store; the chain was deep-copied
18846 // above, so release the source
18847 free_ca_store(store);
18848
18849 // Update the SSL config to use the new CA chain
18850 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18851 return true;
18852}
18853
18854inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
18855 certs.clear();
18856 if (!ctx) { return 0; }
18857 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18858
18859 // Iterate through the CA chain
18860 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18861 while (cert != nullptr && cert->raw.len > 0) {
18862 // Create a copy of the certificate for the caller
18863 auto *copy = new mbedtls_x509_crt;
18864 mbedtls_x509_crt_init(copy);
18865 int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
18866 if (ret == 0) {
18867 certs.push_back(static_cast<cert_t>(copy));
18868 } else {
18869 mbedtls_x509_crt_free(copy);
18870 delete copy;
18871 }
18872 cert = cert->next;
18873 }
18874 return certs.size();
18875}
18876
18877inline std::vector<std::string> get_ca_names(ctx_t ctx) {
18878 std::vector<std::string> names;
18879 if (!ctx) { return names; }
18880 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18881
18882 // Iterate through the CA chain
18883 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18884 while (cert != nullptr && cert->raw.len > 0) {
18885 char buf[512];
18886 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject);
18887 if (ret > 0) { names.push_back(buf); }
18888 cert = cert->next;
18889 }
18890 return names;
18891}
18892
18893inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
18894 const char *key_pem, const char *password) {
18895 if (!ctx || !cert_pem || !key_pem) { return false; }
18896 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18897
18898 // Free existing certificate and key
18899 mbedtls_x509_crt_free(&mbed_ctx->own_cert);
18900 mbedtls_pk_free(&mbed_ctx->own_key);
18901 mbedtls_x509_crt_init(&mbed_ctx->own_cert);
18902 mbedtls_pk_init(&mbed_ctx->own_key);
18903
18904 // Parse certificate PEM
18905 int ret = mbedtls_x509_crt_parse(
18906 &mbed_ctx->own_cert, reinterpret_cast<const unsigned char *>(cert_pem),
18907 strlen(cert_pem) + 1);
18908 if (ret != 0) {
18909 impl::mbedtls_last_error() = ret;
18910 return false;
18911 }
18912
18913 // Parse private key PEM
18914#ifdef CPPHTTPLIB_MBEDTLS_V3
18915 ret = mbedtls_pk_parse_key(
18916 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18917 strlen(key_pem) + 1,
18918 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18919 password ? strlen(password) : 0, mbedtls_ctr_drbg_random,
18920 &mbed_ctx->ctr_drbg);
18921#else
18922 ret = mbedtls_pk_parse_key(
18923 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18924 strlen(key_pem) + 1,
18925 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18926 password ? strlen(password) : 0);
18927#endif
18928 if (ret != 0) {
18929 impl::mbedtls_last_error() = ret;
18930 return false;
18931 }
18932
18933 // Configure SSL to use the new certificate and key
18934 ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert,
18935 &mbed_ctx->own_key);
18936 if (ret != 0) {
18937 impl::mbedtls_last_error() = ret;
18938 return false;
18939 }
18940
18941 return true;
18942}
18943
18944inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
18945 if (!ctx || !ca_pem) { return false; }
18946 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18947
18948 // Free existing CA chain
18949 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18950 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18951
18952 // Parse CA PEM
18953 int ret = mbedtls_x509_crt_parse(
18954 &mbed_ctx->ca_chain, reinterpret_cast<const unsigned char *>(ca_pem),
18955 strlen(ca_pem) + 1);
18956 if (ret != 0) {
18957 impl::mbedtls_last_error() = ret;
18958 return false;
18959 }
18960
18961 // Update SSL config to use new CA chain
18962 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18963 return true;
18964}
18965
18966inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
18967 if (!ctx) { return false; }
18968 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18969
18970 impl::get_verify_callback() = std::move(callback);
18971 mbed_ctx->has_verify_callback =
18972 static_cast<bool>(impl::get_verify_callback());
18973
18974 if (mbed_ctx->has_verify_callback) {
18975 // Set OPTIONAL mode to ensure callback is called even when verification
18976 // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
18977 mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
18978 mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
18979 nullptr);
18980 } else {
18981 mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
18982 }
18983 return true;
18984}
18985
18986inline long get_verify_error(const_session_t session) {
18987 if (!session) { return -1; }
18988 auto *msession =
18989 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18990 return static_cast<long>(mbedtls_ssl_get_verify_result(&msession->ssl));
18991}
18992
18993inline std::string verify_error_string(long error_code) {
18994 if (error_code == 0) { return ""; }
18995 char buf[256];
18996 mbedtls_x509_crt_verify_info(buf, sizeof(buf), "",
18997 static_cast<uint32_t>(error_code));
18998 // Remove trailing newline if present
18999 std::string result(buf);
19000 while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) {
19001 result.pop_back();
19002 }
19003 return result;
19004}
19005
19006} // namespace tls
19007
19008#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
19009
19010/*
19011 * Group 10: TLS abstraction layer - wolfSSL backend
19012 */
19013
19014/*
19015 * wolfSSL Backend Implementation
19016 */
19017
19018#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
19019namespace tls {
19020
19021namespace impl {
19022
19023// wolfSSL session wrapper
19024struct WolfSSLSession {
19025 WOLFSSL *ssl = nullptr;
19026 socket_t sock = INVALID_SOCKET;
19027 std::string hostname; // For client: set via set_sni
19028 std::string sni_hostname; // For server: received from client via SNI callback
19029
19030 WolfSSLSession() = default;
19031
19032 ~WolfSSLSession() {
19033 if (ssl) { wolfSSL_free(ssl); }
19034 }
19035
19036 WolfSSLSession(const WolfSSLSession &) = delete;
19037 WolfSSLSession &operator=(const WolfSSLSession &) = delete;
19038};
19039
19040// Thread-local error code accessor for wolfSSL
19041inline uint64_t &wolfssl_last_error() {
19042 static thread_local uint64_t err = 0;
19043 return err;
19044}
19045
19046// Helper to map wolfSSL error to ErrorCode.
19047// ssl_error is the value from wolfSSL_get_error().
19048// raw_ret is the raw return value from the wolfSSL call (for low-level error).
19049inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
19050 int &out_errno) {
19051 switch (ssl_error) {
19052 case SSL_ERROR_NONE: return ErrorCode::Success;
19053 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
19054 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
19055 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
19056 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
19057 default:
19058 if (ssl) {
19059 // wolfSSL stores the low-level error code as a negative value.
19060 // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure.
19061 int low_err = ssl_error; // wolfSSL_get_error returns the low-level code
19062 if (low_err == DOMAIN_NAME_MISMATCH) {
19063 return ErrorCode::HostnameMismatch;
19064 }
19065 // Check verify result to distinguish cert verification from generic SSL
19066 // errors.
19067 long vr = wolfSSL_get_verify_result(ssl);
19068 if (vr != 0) { return ErrorCode::CertVerifyFailed; }
19069 }
19070 return ErrorCode::Fatal;
19071 }
19072}
19073
19074// WolfSSLContext constructor/destructor implementations
19075inline WolfSSLContext::WolfSSLContext() { wolfSSL_Init(); }
19076
19077inline WolfSSLContext::~WolfSSLContext() {
19078 if (ctx) { wolfSSL_CTX_free(ctx); }
19079}
19080
19081// Thread-local storage for SNI captured during handshake
19082inline std::string &wolfssl_pending_sni() {
19083 static thread_local std::string sni;
19084 return sni;
19085}
19086
19087// SNI callback for wolfSSL server to capture client's SNI hostname
19088inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
19089 (void)ret;
19090 (void)exArg;
19091
19092 void *name_data = nullptr;
19093 unsigned short name_len =
19094 wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data);
19095
19096 if (name_data && name_len > 0) {
19097 wolfssl_pending_sni().assign(static_cast<const char *>(name_data),
19098 name_len);
19099 } else {
19100 wolfssl_pending_sni().clear();
19101 }
19102 return 0; // Continue regardless
19103}
19104
19105// wolfSSL verify callback wrapper
19106inline int wolfssl_verify_callback(int preverify_ok,
19107 WOLFSSL_X509_STORE_CTX *x509_ctx) {
19108 auto &callback = get_verify_callback();
19109 if (!callback) { return preverify_ok; }
19110
19111 WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
19112 int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
19113 int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx);
19114
19115 // Get the WOLFSSL object from the X509_STORE_CTX
19116 WOLFSSL *ssl = static_cast<WOLFSSL *>(wolfSSL_X509_STORE_CTX_get_ex_data(
19117 x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()));
19118
19119 VerifyContext verify_ctx;
19120 verify_ctx.session = static_cast<session_t>(ssl);
19121 verify_ctx.cert = static_cast<cert_t>(cert);
19122 verify_ctx.depth = depth;
19123 verify_ctx.preverify_ok = (preverify_ok != 0);
19124 verify_ctx.error_code = static_cast<long>(err);
19125
19126 if (err != 0) {
19127 verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err);
19128 } else {
19129 verify_ctx.error_string = nullptr;
19130 }
19131
19132 bool accepted = callback(verify_ctx);
19133 return accepted ? 1 : 0;
19134}
19135
19136inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
19137 wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
19138 wolfSSL_CTX_set_default_passwd_cb(
19139 ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int {
19140 auto *pwd = static_cast<const char *>(userdata);
19141 if (!pwd) return 0;
19142 auto len = static_cast<int>(strlen(pwd));
19143 if (len > size) len = size;
19144 memcpy(buf, pwd, static_cast<size_t>(len));
19145 return len;
19146 });
19147}
19148
19149} // namespace impl
19150
19151inline ctx_t create_client_context() {
19152 auto ctx = new (std::nothrow) impl::WolfSSLContext();
19153 if (!ctx) { return nullptr; }
19154
19155 ctx->is_server = false;
19156
19157 WOLFSSL_METHOD *method = wolfTLSv1_2_client_method();
19158 if (!method) {
19159 delete ctx;
19160 return nullptr;
19161 }
19162
19163 ctx->ctx = wolfSSL_CTX_new(method);
19164 if (!ctx->ctx) {
19165 delete ctx;
19166 return nullptr;
19167 }
19168
19169 // Default: verify peer certificate
19170 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
19171
19172 return static_cast<ctx_t>(ctx);
19173}
19174
19175inline ctx_t create_server_context() {
19176 auto ctx = new (std::nothrow) impl::WolfSSLContext();
19177 if (!ctx) { return nullptr; }
19178
19179 ctx->is_server = true;
19180
19181 WOLFSSL_METHOD *method = wolfTLSv1_2_server_method();
19182 if (!method) {
19183 delete ctx;
19184 return nullptr;
19185 }
19186
19187 ctx->ctx = wolfSSL_CTX_new(method);
19188 if (!ctx->ctx) {
19189 delete ctx;
19190 return nullptr;
19191 }
19192
19193 // Default: don't verify client
19194 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
19195
19196 // Enable SNI on server
19197 wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
19198 WOLFSSL_SNI_CONTINUE_ON_MISMATCH);
19199 wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback);
19200
19201 return static_cast<ctx_t>(ctx);
19202}
19203
19204inline void free_context(ctx_t ctx) {
19205 if (ctx) { delete static_cast<impl::WolfSSLContext *>(ctx); }
19206}
19207
19208inline bool set_min_version(ctx_t ctx, Version version) {
19209 if (!ctx) { return false; }
19210 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19211
19212 int min_ver = WOLFSSL_TLSV1_2;
19213 if (version >= Version::TLS1_3) { min_ver = WOLFSSL_TLSV1_3; }
19214
19215 return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS;
19216}
19217
19218inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
19219 if (!ctx || !pem) { return false; }
19220 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19221
19222 int ret = wolfSSL_CTX_load_verify_buffer(
19223 wctx->ctx, reinterpret_cast<const unsigned char *>(pem),
19224 static_cast<long>(len), SSL_FILETYPE_PEM);
19225 if (ret != SSL_SUCCESS) {
19226 impl::wolfssl_last_error() =
19227 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19228 return false;
19229 }
19230 wctx->ca_pem_data_.append(pem, len);
19231 return true;
19232}
19233
19234inline bool load_ca_file(ctx_t ctx, const char *file_path) {
19235 if (!ctx || !file_path) { return false; }
19236 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19237
19238 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr);
19239 if (ret != SSL_SUCCESS) {
19240 impl::wolfssl_last_error() =
19241 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19242 return false;
19243 }
19244 return true;
19245}
19246
19247inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
19248 if (!ctx || !dir_path) { return false; }
19249 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19250
19251 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path);
19252 // wolfSSL may fail if the directory doesn't contain properly hashed certs.
19253 // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans
19254 // immediately. Return true even on failure since the CA file may have
19255 // already been loaded, matching OpenSSL's lenient behavior.
19256 (void)ret;
19257 return true;
19258}
19259
19260inline bool load_system_certs(ctx_t ctx) {
19261 if (!ctx) { return false; }
19262 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19263 bool loaded = false;
19264
19265#ifdef _WIN32
19266 loaded = impl::enumerate_windows_system_certs(
19267 [&](const unsigned char *data, size_t len) {
19268 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19269 static_cast<long>(len),
19270 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19271 });
19272#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
19273 loaded = impl::enumerate_macos_keychain_certs(
19274 [&](const unsigned char *data, size_t len) {
19275 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19276 static_cast<long>(len),
19277 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19278 });
19279#else
19280 for (auto path = impl::system_ca_paths(); *path; ++path) {
19281 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) ==
19282 SSL_SUCCESS) {
19283 loaded = true;
19284 break;
19285 }
19286 }
19287
19288 if (!loaded) {
19289 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
19290 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) ==
19291 SSL_SUCCESS) {
19292 loaded = true;
19293 break;
19294 }
19295 }
19296 }
19297#endif
19298
19299 return loaded;
19300}
19301
19302inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
19303 const char *password) {
19304 if (!ctx || !cert || !key) { return false; }
19305 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19306
19307 // Load certificate
19308 int ret = wolfSSL_CTX_use_certificate_buffer(
19309 wctx->ctx, reinterpret_cast<const unsigned char *>(cert),
19310 static_cast<long>(strlen(cert)), SSL_FILETYPE_PEM);
19311 if (ret != SSL_SUCCESS) {
19312 impl::wolfssl_last_error() =
19313 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19314 return false;
19315 }
19316
19317 // Set password callback if password is provided
19318 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19319
19320 // Load private key
19321 ret = wolfSSL_CTX_use_PrivateKey_buffer(
19322 wctx->ctx, reinterpret_cast<const unsigned char *>(key),
19323 static_cast<long>(strlen(key)), SSL_FILETYPE_PEM);
19324 if (ret != SSL_SUCCESS) {
19325 impl::wolfssl_last_error() =
19326 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19327 return false;
19328 }
19329
19330 // Verify that the certificate and private key match
19331 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19332}
19333
19334inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
19335 const char *key_path, const char *password) {
19336 if (!ctx || !cert_path || !key_path) { return false; }
19337 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19338
19339 // Load certificate file
19340 int ret =
19341 wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM);
19342 if (ret != SSL_SUCCESS) {
19343 impl::wolfssl_last_error() =
19344 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19345 return false;
19346 }
19347
19348 // Set password callback if password is provided
19349 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19350
19351 // Load private key file
19352 ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM);
19353 if (ret != SSL_SUCCESS) {
19354 impl::wolfssl_last_error() =
19355 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19356 return false;
19357 }
19358
19359 // Verify that the certificate and private key match
19360 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19361}
19362
19363inline void set_verify_client(ctx_t ctx, bool require) {
19364 if (!ctx) { return; }
19365 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19366 wctx->verify_client = require;
19367 if (require) {
19368 wolfSSL_CTX_set_verify(
19369 wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
19370 wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr);
19371 } else {
19372 if (wctx->has_verify_callback) {
19373 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
19374 impl::wolfssl_verify_callback);
19375 } else {
19376 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
19377 }
19378 }
19379}
19380
19381inline session_t create_session(ctx_t ctx, socket_t sock) {
19382 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
19383 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19384
19385 auto session = new (std::nothrow) impl::WolfSSLSession();
19386 if (!session) { return nullptr; }
19387
19388 session->sock = sock;
19389 session->ssl = wolfSSL_new(wctx->ctx);
19390 if (!session->ssl) {
19391 impl::wolfssl_last_error() =
19392 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19393 delete session;
19394 return nullptr;
19395 }
19396
19397 wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
19398
19399 return static_cast<session_t>(session);
19400}
19401
19402inline void free_session(session_t session) {
19403 if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
19404}
19405
19406inline bool set_sni(session_t session, const char *hostname) {
19407 if (!session || !hostname) { return false; }
19408 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19409
19410 int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname,
19411 static_cast<word16>(strlen(hostname)));
19412 if (ret != WOLFSSL_SUCCESS) {
19413 impl::wolfssl_last_error() =
19414 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19415 return false;
19416 }
19417
19418 // Also set hostname for verification
19419 wolfSSL_check_domain_name(wsession->ssl, hostname);
19420
19421 wsession->hostname = hostname;
19422 return true;
19423}
19424
19425inline bool set_hostname(session_t session, const char *hostname) {
19426 // In wolfSSL, set_hostname also sets up hostname verification
19427 return set_sni(session, hostname);
19428}
19429
19430inline TlsError connect(session_t session) {
19431 TlsError err;
19432 if (!session) {
19433 err.code = ErrorCode::Fatal;
19434 return err;
19435 }
19436
19437 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19438 int ret = wolfSSL_connect(wsession->ssl);
19439
19440 if (ret == SSL_SUCCESS) {
19441 err.code = ErrorCode::Success;
19442 } else {
19443 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19444 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19445 err.backend_code = static_cast<uint64_t>(ssl_error);
19446 impl::wolfssl_last_error() = err.backend_code;
19447 }
19448
19449 return err;
19450}
19451
19452inline TlsError accept(session_t session) {
19453 TlsError err;
19454 if (!session) {
19455 err.code = ErrorCode::Fatal;
19456 return err;
19457 }
19458
19459 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19460 int ret = wolfSSL_accept(wsession->ssl);
19461
19462 if (ret == SSL_SUCCESS) {
19463 err.code = ErrorCode::Success;
19464 // Capture SNI from thread-local storage after successful handshake
19465 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19466 impl::wolfssl_pending_sni().clear();
19467 } else {
19468 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19469 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19470 err.backend_code = static_cast<uint64_t>(ssl_error);
19471 impl::wolfssl_last_error() = err.backend_code;
19472 }
19473
19474 return err;
19475}
19476
19477inline bool connect_nonblocking(session_t session, socket_t sock,
19478 time_t timeout_sec, time_t timeout_usec,
19479 TlsError *err) {
19480 if (!session) {
19481 if (err) { err->code = ErrorCode::Fatal; }
19482 return false;
19483 }
19484
19485 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19486
19487 // Set socket to non-blocking mode
19488 detail::set_nonblocking(sock, true);
19489 auto cleanup =
19490 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19491
19492 int ret;
19493 while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) {
19494 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19495 if (ssl_error == SSL_ERROR_WANT_READ) {
19496 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19497 continue;
19498 }
19499 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19500 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19501 continue;
19502 }
19503 }
19504
19505 // Error or timeout
19506 if (err) {
19507 err->code =
19508 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19509 err->backend_code = static_cast<uint64_t>(ssl_error);
19510 }
19511 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19512 return false;
19513 }
19514
19515 if (err) { err->code = ErrorCode::Success; }
19516 return true;
19517}
19518
19519inline bool accept_nonblocking(session_t session, socket_t sock,
19520 time_t timeout_sec, time_t timeout_usec,
19521 TlsError *err) {
19522 if (!session) {
19523 if (err) { err->code = ErrorCode::Fatal; }
19524 return false;
19525 }
19526
19527 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19528
19529 // Set socket to non-blocking mode
19530 detail::set_nonblocking(sock, true);
19531 auto cleanup =
19532 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19533
19534 int ret;
19535 while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) {
19536 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19537 if (ssl_error == SSL_ERROR_WANT_READ) {
19538 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19539 continue;
19540 }
19541 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19542 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19543 continue;
19544 }
19545 }
19546
19547 // Error or timeout
19548 if (err) {
19549 err->code =
19550 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19551 err->backend_code = static_cast<uint64_t>(ssl_error);
19552 }
19553 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19554 return false;
19555 }
19556
19557 if (err) { err->code = ErrorCode::Success; }
19558
19559 // Capture SNI from thread-local storage after successful handshake
19560 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19561 impl::wolfssl_pending_sni().clear();
19562
19563 return true;
19564}
19565
19566inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
19567 if (!session || !buf) {
19568 err.code = ErrorCode::Fatal;
19569 return -1;
19570 }
19571
19572 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19573 int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
19574
19575 if (ret > 0) {
19576 err.code = ErrorCode::Success;
19577 return static_cast<ssize_t>(ret);
19578 }
19579
19580 if (ret == 0) {
19581 err.code = ErrorCode::PeerClosed;
19582 return 0;
19583 }
19584
19585 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19586 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19587 err.backend_code = static_cast<uint64_t>(ssl_error);
19588 impl::wolfssl_last_error() = err.backend_code;
19589 return -1;
19590}
19591
19592inline ssize_t write(session_t session, const void *buf, size_t len,
19593 TlsError &err) {
19594 if (!session || !buf) {
19595 err.code = ErrorCode::Fatal;
19596 return -1;
19597 }
19598
19599 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19600 int ret = wolfSSL_write(wsession->ssl, buf, static_cast<int>(len));
19601
19602 if (ret > 0) {
19603 err.code = ErrorCode::Success;
19604 return static_cast<ssize_t>(ret);
19605 }
19606
19607 // wolfSSL_write returns 0 when the peer has sent a close_notify.
19608 // Treat this as an error (return -1) so callers don't spin in a
19609 // write loop adding zero to the offset.
19610 if (ret == 0) {
19611 err.code = ErrorCode::PeerClosed;
19612 return -1;
19613 }
19614
19615 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19616 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19617 err.backend_code = static_cast<uint64_t>(ssl_error);
19618 impl::wolfssl_last_error() = err.backend_code;
19619 return -1;
19620}
19621
19622inline int pending(const_session_t session) {
19623 if (!session) { return 0; }
19624 auto wsession =
19625 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19626 return wolfSSL_pending(wsession->ssl);
19627}
19628
19629inline void shutdown(session_t session, bool graceful) {
19630 if (!session) { return; }
19631 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19632
19633 if (graceful) {
19634 int ret;
19635 int attempts = 0;
19636 while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS &&
19637 attempts < 3) {
19638 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19639 if (ssl_error != SSL_ERROR_WANT_READ &&
19640 ssl_error != SSL_ERROR_WANT_WRITE) {
19641 break;
19642 }
19643 attempts++;
19644 }
19645 } else {
19646 wolfSSL_shutdown(wsession->ssl);
19647 }
19648}
19649
19650inline bool is_peer_closed(session_t session, socket_t sock) {
19651 if (!session || sock == INVALID_SOCKET) { return true; }
19652 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19653
19654 // Check if there's already decrypted data available
19655 if (wolfSSL_pending(wsession->ssl) > 0) { return false; }
19656
19657 // Set socket to non-blocking to avoid blocking on read
19658 detail::set_nonblocking(sock, true);
19659 auto cleanup =
19660 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19661
19662 // Peek 1 byte to check connection status without consuming data
19663 unsigned char buf;
19664 int ret = wolfSSL_peek(wsession->ssl, &buf, 1);
19665
19666 // If we got data or WANT_READ (would block), connection is alive
19667 if (ret > 0) { return false; }
19668
19669 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19670 if (ssl_error == SSL_ERROR_WANT_READ) { return false; }
19671
19672 return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL ||
19673 ret == 0;
19674}
19675
19676inline cert_t get_peer_cert(const_session_t session) {
19677 if (!session) { return nullptr; }
19678 auto wsession =
19679 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19680
19681 WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl);
19682 return static_cast<cert_t>(cert);
19683}
19684
19685inline void free_cert(cert_t cert) {
19686 if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
19687}
19688
19689inline bool verify_hostname(cert_t cert, const char *hostname) {
19690 if (!cert || !hostname) { return false; }
19691 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19692 std::string host_str(hostname);
19693
19694 // Check if hostname is an IP address (IPv4 or IPv6)
19695 unsigned char ip_bytes[16];
19696 auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
19697 auto is_ip = ip_len > 0;
19698
19699 // Check Subject Alternative Names
19700 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19701 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19702
19703 if (san_names) {
19704 int san_count = wolfSSL_sk_num(san_names);
19705 for (int i = 0; i < san_count; i++) {
19706 auto *names =
19707 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19708 if (!names) continue;
19709
19710 if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
19711 // DNS name
19712 unsigned char *dns_name = nullptr;
19713 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
19714 if (dns_name && dns_len > 0) {
19715 std::string san_name(reinterpret_cast<char *>(dns_name),
19716 static_cast<size_t>(dns_len));
19717 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19718 if (detail::match_hostname(san_name, host_str)) {
19719 wolfSSL_sk_free(san_names);
19720 return true;
19721 }
19722 }
19723 } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
19724 // IP address: only an iPAddress SAN of the same family (4 bytes for
19725 // IPv4, 16 bytes for IPv6) may authenticate the host.
19726 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
19727 auto san_ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19728 if (ip_data && san_ip_len == static_cast<int>(ip_len) &&
19729 memcmp(ip_data, ip_bytes, ip_len) == 0) {
19730 wolfSSL_sk_free(san_names);
19731 return true;
19732 }
19733 }
19734 }
19735 wolfSSL_sk_free(san_names);
19736 }
19737
19738 // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
19739 // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
19740 // the OpenSSL backend's X509_check_ip behaves the same way).
19741 auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509);
19742 if (subject) {
19743 char cn[256] = {};
19744 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19745 sizeof(cn));
19746 if (cn_len > 0) {
19747 std::string cn_str(cn, static_cast<size_t>(cn_len));
19748 if (detail::match_hostname(cn_str, host_str)) { return true; }
19749 }
19750 }
19751
19752 return false;
19753}
19754
19755inline uint64_t hostname_mismatch_code() {
19756 return static_cast<uint64_t>(DOMAIN_NAME_MISMATCH);
19757}
19758
19759inline long get_verify_result(const_session_t session) {
19760 if (!session) { return -1; }
19761 auto wsession =
19762 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19763 long result = wolfSSL_get_verify_result(wsession->ssl);
19764 return result;
19765}
19766
19767inline std::string get_cert_subject_cn(cert_t cert) {
19768 if (!cert) return "";
19769 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19770
19771 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19772 if (!subject) return "";
19773
19774 char cn[256] = {};
19775 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19776 sizeof(cn));
19777 if (cn_len <= 0) return "";
19778 return std::string(cn, static_cast<size_t>(cn_len));
19779}
19780
19781inline std::string get_cert_issuer_name(cert_t cert) {
19782 if (!cert) return "";
19783 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19784
19785 WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509);
19786 if (!issuer) return "";
19787
19788 char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0);
19789 if (!name_str) return "";
19790
19791 std::string result(name_str);
19792 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19793 return result;
19794}
19795
19796inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
19797 sans.clear();
19798 if (!cert) return false;
19799 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19800
19801 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19802 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19803 if (!san_names) return true; // No SANs is not an error
19804
19805 int count = wolfSSL_sk_num(san_names);
19806 for (int i = 0; i < count; i++) {
19807 auto *name =
19808 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19809 if (!name) continue;
19810
19811 SanEntry entry;
19812 switch (name->type) {
19813 case WOLFSSL_GEN_DNS: {
19814 entry.type = SanType::DNS;
19815 unsigned char *dns_name = nullptr;
19816 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName);
19817 if (dns_name && dns_len > 0) {
19818 entry.value = std::string(reinterpret_cast<char *>(dns_name),
19819 static_cast<size_t>(dns_len));
19820 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19821 }
19822 break;
19823 }
19824 case WOLFSSL_GEN_IPADD: {
19825 entry.type = SanType::IP;
19826 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress);
19827 int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress);
19828 if (ip_data && ip_len == 4) {
19829 char buf[16];
19830 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1],
19831 ip_data[2], ip_data[3]);
19832 entry.value = buf;
19833 } else if (ip_data && ip_len == 16) {
19834 char buf[64];
19835 snprintf(buf, sizeof(buf),
19836 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
19837 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
19838 ip_data[0], ip_data[1], ip_data[2], ip_data[3], ip_data[4],
19839 ip_data[5], ip_data[6], ip_data[7], ip_data[8], ip_data[9],
19840 ip_data[10], ip_data[11], ip_data[12], ip_data[13],
19841 ip_data[14], ip_data[15]);
19842 entry.value = buf;
19843 }
19844 break;
19845 }
19846 case WOLFSSL_GEN_EMAIL:
19847 entry.type = SanType::EMAIL;
19848 {
19849 unsigned char *email = nullptr;
19850 int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name);
19851 if (email && email_len > 0) {
19852 entry.value = std::string(reinterpret_cast<char *>(email),
19853 static_cast<size_t>(email_len));
19854 XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL);
19855 }
19856 }
19857 break;
19858 case WOLFSSL_GEN_URI:
19859 entry.type = SanType::URI;
19860 {
19861 unsigned char *uri = nullptr;
19862 int uri_len = wolfSSL_ASN1_STRING_to_UTF8(
19863 &uri, name->d.uniformResourceIdentifier);
19864 if (uri && uri_len > 0) {
19865 entry.value = std::string(reinterpret_cast<char *>(uri),
19866 static_cast<size_t>(uri_len));
19867 XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL);
19868 }
19869 }
19870 break;
19871 default: entry.type = SanType::OTHER; break;
19872 }
19873
19874 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
19875 }
19876 wolfSSL_sk_free(san_names);
19877 return true;
19878}
19879
19880inline bool get_cert_validity(cert_t cert, time_t &not_before,
19881 time_t &not_after) {
19882 if (!cert) return false;
19883 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19884
19885 const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509);
19886 const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509);
19887
19888 if (!nb || !na) return false;
19889
19890 // wolfSSL_ASN1_TIME_to_tm is available
19891 struct tm tm_nb = {}, tm_na = {};
19892 if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) return false;
19893 if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) return false;
19894
19895#ifdef _WIN32
19896 not_before = _mkgmtime(&tm_nb);
19897 not_after = _mkgmtime(&tm_na);
19898#else
19899 not_before = timegm(&tm_nb);
19900 not_after = timegm(&tm_na);
19901#endif
19902 return true;
19903}
19904
19905inline std::string get_cert_serial(cert_t cert) {
19906 if (!cert) return "";
19907 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19908
19909 WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509);
19910 if (!serial_asn1) return "";
19911
19912 // Get the serial number data
19913 int len = serial_asn1->length;
19914 unsigned char *data = serial_asn1->data;
19915 if (!data || len <= 0) return "";
19916
19917 std::string result;
19918 result.reserve(static_cast<size_t>(len) * 2);
19919 for (int i = 0; i < len; i++) {
19920 char hex[3];
19921 snprintf(hex, sizeof(hex), "%02X", data[i]);
19922 result += hex;
19923 }
19924 return result;
19925}
19926
19927inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
19928 if (!cert) return false;
19929 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19930
19931 int der_len = 0;
19932 const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len);
19933 if (!der_data || der_len <= 0) return false;
19934
19935 der.assign(der_data, der_data + der_len);
19936 return true;
19937}
19938
19939inline const char *get_sni(const_session_t session) {
19940 if (!session) return nullptr;
19941 auto wsession = static_cast<const impl::WolfSSLSession *>(session);
19942
19943 // For server: return SNI received from client during handshake
19944 if (!wsession->sni_hostname.empty()) {
19945 return wsession->sni_hostname.c_str();
19946 }
19947
19948 // For client: return the hostname set via set_sni
19949 if (!wsession->hostname.empty()) { return wsession->hostname.c_str(); }
19950
19951 return nullptr;
19952}
19953
19954inline uint64_t peek_error() {
19955 return static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19956}
19957
19958inline uint64_t get_error() {
19959 uint64_t err = impl::wolfssl_last_error();
19960 impl::wolfssl_last_error() = 0;
19961 return err;
19962}
19963
19964inline std::string error_string(uint64_t code) {
19965 char buf[256];
19966 wolfSSL_ERR_error_string(static_cast<unsigned long>(code), buf);
19967 return std::string(buf);
19968}
19969
19970inline ca_store_t create_ca_store(const char *pem, size_t len) {
19971 if (!pem || len == 0) { return nullptr; }
19972 // Validate by attempting to load into a temporary ctx
19973 WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
19974 if (!tmp_ctx) { return nullptr; }
19975 int ret = wolfSSL_CTX_load_verify_buffer(
19976 tmp_ctx, reinterpret_cast<const unsigned char *>(pem),
19977 static_cast<long>(len), SSL_FILETYPE_PEM);
19978 wolfSSL_CTX_free(tmp_ctx);
19979 if (ret != SSL_SUCCESS) { return nullptr; }
19980 return static_cast<ca_store_t>(
19981 new impl::WolfSSLCAStore{std::string(pem, len)});
19982}
19983
19984inline void free_ca_store(ca_store_t store) {
19985 delete static_cast<impl::WolfSSLCAStore *>(store);
19986}
19987
19988inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
19989 if (!ctx || !store) { return false; }
19990 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19991 auto *ca = static_cast<impl::WolfSSLCAStore *>(store);
19992 int ret = wolfSSL_CTX_load_verify_buffer(
19993 wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
19994 static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
19995 if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
19996 // This function takes ownership of the store; the PEM data was copied into
19997 // the context, so release the source
19998 free_ca_store(store);
19999 return ret == SSL_SUCCESS;
20000}
20001
20002inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
20003 certs.clear();
20004 if (!ctx) { return 0; }
20005 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20006 if (wctx->ca_pem_data_.empty()) { return 0; }
20007
20008 const std::string &pem = wctx->ca_pem_data_;
20009 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
20010 const std::string end_marker = "-----END CERTIFICATE-----";
20011 size_t pos = 0;
20012 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
20013 size_t end_pos = pem.find(end_marker, pos);
20014 if (end_pos == std::string::npos) { break; }
20015 end_pos += end_marker.size();
20016 std::string cert_pem = pem.substr(pos, end_pos - pos);
20017 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
20018 reinterpret_cast<const unsigned char *>(cert_pem.data()),
20019 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
20020 if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
20021 pos = end_pos;
20022 }
20023 return certs.size();
20024}
20025
20026inline std::vector<std::string> get_ca_names(ctx_t ctx) {
20027 std::vector<std::string> names;
20028 if (!ctx) { return names; }
20029 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20030 if (wctx->ca_pem_data_.empty()) { return names; }
20031
20032 const std::string &pem = wctx->ca_pem_data_;
20033 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
20034 const std::string end_marker = "-----END CERTIFICATE-----";
20035 size_t pos = 0;
20036 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
20037 size_t end_pos = pem.find(end_marker, pos);
20038 if (end_pos == std::string::npos) { break; }
20039 end_pos += end_marker.size();
20040 std::string cert_pem = pem.substr(pos, end_pos - pos);
20041 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
20042 reinterpret_cast<const unsigned char *>(cert_pem.data()),
20043 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
20044 if (x509) {
20045 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
20046 if (subject) {
20047 char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0);
20048 if (name_str) {
20049 names.push_back(name_str);
20050 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
20051 }
20052 }
20053 wolfSSL_X509_free(x509);
20054 }
20055 pos = end_pos;
20056 }
20057 return names;
20058}
20059
20060inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
20061 const char *key_pem, const char *password) {
20062 if (!ctx || !cert_pem || !key_pem) { return false; }
20063 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20064
20065 // Load new certificate
20066 int ret = wolfSSL_CTX_use_certificate_buffer(
20067 wctx->ctx, reinterpret_cast<const unsigned char *>(cert_pem),
20068 static_cast<long>(strlen(cert_pem)), SSL_FILETYPE_PEM);
20069 if (ret != SSL_SUCCESS) {
20070 impl::wolfssl_last_error() =
20071 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20072 return false;
20073 }
20074
20075 // Set password if provided
20076 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
20077
20078 // Load new private key
20079 ret = wolfSSL_CTX_use_PrivateKey_buffer(
20080 wctx->ctx, reinterpret_cast<const unsigned char *>(key_pem),
20081 static_cast<long>(strlen(key_pem)), SSL_FILETYPE_PEM);
20082 if (ret != SSL_SUCCESS) {
20083 impl::wolfssl_last_error() =
20084 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20085 return false;
20086 }
20087
20088 return true;
20089}
20090
20091inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
20092 if (!ctx || !ca_pem) { return false; }
20093 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20094
20095 int ret = wolfSSL_CTX_load_verify_buffer(
20096 wctx->ctx, reinterpret_cast<const unsigned char *>(ca_pem),
20097 static_cast<long>(strlen(ca_pem)), SSL_FILETYPE_PEM);
20098 if (ret != SSL_SUCCESS) {
20099 impl::wolfssl_last_error() =
20100 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20101 return false;
20102 }
20103 return true;
20104}
20105
20106inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
20107 if (!ctx) { return false; }
20108 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20109
20110 impl::get_verify_callback() = std::move(callback);
20111 wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
20112
20113 if (wctx->has_verify_callback) {
20114 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
20115 impl::wolfssl_verify_callback);
20116 } else {
20117 wolfSSL_CTX_set_verify(
20118 wctx->ctx,
20119 wctx->verify_client
20120 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
20121 : SSL_VERIFY_NONE,
20122 nullptr);
20123 }
20124 return true;
20125}
20126
20127inline long get_verify_error(const_session_t session) {
20128 if (!session) { return -1; }
20129 auto *wsession =
20130 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
20131 return wolfSSL_get_verify_result(wsession->ssl);
20132}
20133
20134inline std::string verify_error_string(long error_code) {
20135 if (error_code == 0) { return ""; }
20136 const char *str =
20137 wolfSSL_X509_verify_cert_error_string(static_cast<int>(error_code));
20138 return str ? std::string(str) : std::string();
20139}
20140
20141} // namespace tls
20142
20143#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
20144
20145// WebSocket implementation
20146namespace ws {
20147
20148inline bool WebSocket::send_frame(Opcode op, const char *data, size_t len,
20149 bool fin) {
20150 std::lock_guard<std::mutex> lock(write_mutex_);
20151 if (closed_) { return false; }
20152 return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_);
20153}
20154
20155inline ReadResult WebSocket::read(std::string &msg) {
20156 while (!closed_) {
20157 Opcode opcode;
20158 std::string payload;
20159 bool fin;
20160
20161 if (!impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_,
20163 closed_ = true;
20164 return Fail;
20165 }
20166
20167 switch (opcode) {
20168 case Opcode::Ping: {
20169 std::lock_guard<std::mutex> lock(write_mutex_);
20170 detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(),
20171 payload.size(), true, !is_server_);
20172 continue;
20173 }
20174 case Opcode::Pong: {
20175 std::lock_guard<std::mutex> lock(ping_mutex_);
20176 unacked_pings_ = 0;
20177 continue;
20178 }
20179 case Opcode::Close: {
20180 if (!closed_.exchange(true)) {
20181 // Echo close frame back
20182 std::lock_guard<std::mutex> lock(write_mutex_);
20183 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20184 payload.size(), true, !is_server_);
20185 }
20186 return Fail;
20187 }
20188 case Opcode::Text:
20189 case Opcode::Binary: {
20190 auto result = opcode == Opcode::Text ? Text : Binary;
20191 msg = std::move(payload);
20192
20193 // Handle fragmentation
20194 if (!fin) {
20195 while (true) {
20196 Opcode cont_opcode;
20197 std::string cont_payload;
20198 bool cont_fin;
20199 if (!impl::read_websocket_frame(
20200 strm_, cont_opcode, cont_payload, cont_fin, is_server_,
20202 closed_ = true;
20203 return Fail;
20204 }
20205 if (cont_opcode == Opcode::Ping) {
20206 std::lock_guard<std::mutex> lock(write_mutex_);
20207 detail::write_websocket_frame(
20208 strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(),
20209 true, !is_server_);
20210 continue;
20211 }
20212 if (cont_opcode == Opcode::Pong) {
20213 std::lock_guard<std::mutex> lock(ping_mutex_);
20214 unacked_pings_ = 0;
20215 continue;
20216 }
20217 if (cont_opcode == Opcode::Close) {
20218 if (!closed_.exchange(true)) {
20219 std::lock_guard<std::mutex> lock(write_mutex_);
20220 detail::write_websocket_frame(
20221 strm_, Opcode::Close, cont_payload.data(),
20222 cont_payload.size(), true, !is_server_);
20223 }
20224 return Fail;
20225 }
20226 // RFC 6455: continuation frames must use opcode 0x0
20227 if (cont_opcode != Opcode::Continuation) {
20228 closed_ = true;
20229 return Fail;
20230 }
20231 msg += cont_payload;
20232 if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) {
20233 closed_ = true;
20234 return Fail;
20235 }
20236 if (cont_fin) { break; }
20237 }
20238 }
20239 // RFC 6455 Section 5.6: text frames must contain valid UTF-8
20240 if (result == Text && !impl::is_valid_utf8(msg)) {
20241 close(CloseStatus::InvalidPayload, "invalid UTF-8");
20242 return Fail;
20243 }
20244 return result;
20245 }
20246 default: closed_ = true; return Fail;
20247 }
20248 }
20249 return Fail;
20250}
20251
20252inline bool WebSocket::send(const std::string &data) {
20253 return send_frame(Opcode::Text, data.data(), data.size());
20254}
20255
20256inline bool WebSocket::send(const char *data, size_t len) {
20257 return send_frame(Opcode::Binary, data, len);
20258}
20259
20260inline void WebSocket::close(CloseStatus status, const std::string &reason) {
20261 if (closed_.exchange(true)) { return; }
20262 ping_cv_.notify_all();
20263 std::string payload;
20264 auto code = static_cast<uint16_t>(status);
20265 payload.push_back(static_cast<char>((code >> 8) & 0xFF));
20266 payload.push_back(static_cast<char>(code & 0xFF));
20267 // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes
20268 // Close frame has 2-byte status code, so reason is limited to 123 bytes
20269 payload += reason.substr(0, 123);
20270 {
20271 std::lock_guard<std::mutex> lock(write_mutex_);
20272 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20273 payload.size(), true, !is_server_);
20274 }
20275
20276 // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's
20277 // Close response before closing the TCP connection. Use a short timeout to
20278 // avoid hanging if the peer doesn't respond.
20279 strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0);
20280 Opcode op;
20281 std::string resp;
20282 bool fin;
20283 while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125)) {
20284 if (op == Opcode::Close) { break; }
20285 }
20286}
20287
20288inline WebSocket::~WebSocket() {
20289 {
20290 std::lock_guard<std::mutex> lock(ping_mutex_);
20291 closed_ = true;
20292 }
20293 ping_cv_.notify_all();
20294 if (ping_thread_.joinable()) { ping_thread_.join(); }
20295}
20296
20297inline void WebSocket::start_heartbeat() {
20298 if (ping_interval_sec_ == 0) { return; }
20299 ping_thread_ = std::thread([this]() {
20300 std::unique_lock<std::mutex> lock(ping_mutex_);
20301 while (!closed_) {
20302 ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
20303 if (closed_) { break; }
20304 // If the peer has failed to respond to the previous pings, give up.
20305 // RFC 6455 does not define a pong-timeout mechanism; this is an
20306 // opt-in liveness check controlled by max_missed_pongs_.
20307 if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) {
20308 lock.unlock();
20309 close(CloseStatus::GoingAway, "pong timeout");
20310 return;
20311 }
20312 lock.unlock();
20313 if (!send_frame(Opcode::Ping, nullptr, 0)) {
20314 lock.lock();
20315 closed_ = true;
20316 break;
20317 }
20318 lock.lock();
20319 unacked_pings_++;
20320 }
20321 });
20322}
20323
20324inline const Request &WebSocket::request() const { return req_; }
20325
20326inline bool WebSocket::is_open() const { return !closed_; }
20327
20328// WebSocketClient implementation
20329inline WebSocketClient::WebSocketClient(
20330 const std::string &scheme_host_port_path, const Headers &headers)
20331 : headers_(headers) {
20332 detail::UrlComponents uc;
20333 if (detail::parse_url(scheme_host_port_path, uc) && !uc.scheme.empty() &&
20334 !uc.host.empty() && !uc.path.empty()) {
20335 auto &scheme = uc.scheme;
20336
20337#ifdef CPPHTTPLIB_SSL_ENABLED
20338 if (scheme != "ws" && scheme != "wss") {
20339#else
20340 if (scheme != "ws") {
20341#endif
20342#ifndef CPPHTTPLIB_NO_EXCEPTIONS
20343 std::string msg = "'" + scheme + "' scheme is not supported.";
20344 throw std::invalid_argument(msg);
20345#endif
20346 return;
20347 }
20348
20349 auto is_ssl = scheme == "wss";
20350
20351 host_ = std::move(uc.host);
20352
20353 port_ = is_ssl ? 443 : 80;
20354 if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
20355
20356 path_ = std::move(uc.path);
20357 if (!uc.query.empty()) { path_ += uc.query; }
20358
20359#ifdef CPPHTTPLIB_SSL_ENABLED
20360 is_ssl_ = is_ssl;
20361 if (is_ssl_) {
20362 // The context lives as long as the client so that CA configuration
20363 // survives reconnects; sessions are created per connection.
20364 tls_ctx_ = tls::create_client_context();
20365 if (!tls_ctx_) { return; }
20366 }
20367#else
20368 if (is_ssl) { return; }
20369#endif
20370
20371 is_valid_ = true;
20372 }
20373}
20374
20375inline WebSocketClient::~WebSocketClient() {
20376 shutdown_and_close();
20377#ifdef CPPHTTPLIB_SSL_ENABLED
20378 if (tls_ctx_) {
20379 tls::free_context(tls_ctx_);
20380 tls_ctx_ = nullptr;
20381 }
20382#endif
20383}
20384
20385inline bool WebSocketClient::is_valid() const { return is_valid_; }
20386
20387inline void WebSocketClient::shutdown_and_close() {
20388#ifdef CPPHTTPLIB_SSL_ENABLED
20389 if (is_ssl_) {
20390 if (tls_session_) {
20391 tls::shutdown(tls_session_, true);
20392 tls::free_session(tls_session_);
20393 tls_session_ = nullptr;
20394 }
20395 }
20396#endif
20397 if (ws_ && ws_->is_open()) { ws_->close(); }
20398 ws_.reset();
20399 if (sock_ != INVALID_SOCKET) {
20400 detail::shutdown_socket(sock_);
20401 detail::close_socket(sock_);
20402 sock_ = INVALID_SOCKET;
20403 }
20404}
20405
20406inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
20407#ifdef CPPHTTPLIB_SSL_ENABLED
20408 if (is_ssl_) {
20409 if (server_certificate_verification_ && !certs_loaded_) {
20410 uint64_t backend_error = 0;
20411 detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(),
20412 custom_ca_loaded_, system_ca_mode_,
20413 backend_error);
20414 certs_loaded_ = true;
20415 }
20416
20417 if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
20418 server_certificate_verification_,
20419 read_timeout_sec_,
20420 read_timeout_usec_)) {
20421 return false;
20422 }
20423
20424 strm = std::unique_ptr<Stream>(new detail::SSLSocketStream(
20425 sock_, tls_session_, read_timeout_sec_, read_timeout_usec_,
20426 write_timeout_sec_, write_timeout_usec_));
20427 return true;
20428 }
20429#endif
20430 strm = std::unique_ptr<Stream>(
20431 new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_,
20432 write_timeout_sec_, write_timeout_usec_));
20433 return true;
20434}
20435
20436inline bool WebSocketClient::connect() {
20437 if (!is_valid_) { return false; }
20438 shutdown_and_close();
20439
20440 // Check is custom IP specified for host_
20441 std::string ip;
20442 auto it = addr_map_.find(host_);
20443 if (it != addr_map_.end()) { ip = it->second; }
20444
20445 Error error;
20446 sock_ = detail::create_client_socket(
20447 host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
20448 socket_options_, connection_timeout_sec_, connection_timeout_usec_,
20449 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
20450 write_timeout_usec_, interface_, error);
20451
20452 if (sock_ == INVALID_SOCKET) { return false; }
20453
20454 std::unique_ptr<Stream> strm;
20455 if (!create_stream(strm)) {
20456 shutdown_and_close();
20457 return false;
20458 }
20459
20460 std::string selected_subprotocol;
20461 if (!detail::perform_websocket_handshake(*strm, host_, port_, path_, headers_,
20462 selected_subprotocol)) {
20463 shutdown_and_close();
20464 return false;
20465 }
20466 subprotocol_ = std::move(selected_subprotocol);
20467
20468 Request req;
20469 req.method = "GET";
20470 req.path = path_;
20471 ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false,
20472 websocket_ping_interval_sec_,
20473 websocket_max_missed_pongs_));
20474 return true;
20475}
20476
20477inline ReadResult WebSocketClient::read(std::string &msg) {
20478 if (!ws_) { return Fail; }
20479 return ws_->read(msg);
20480}
20481
20482inline bool WebSocketClient::send(const std::string &data) {
20483 if (!ws_) { return false; }
20484 return ws_->send(data);
20485}
20486
20487inline bool WebSocketClient::send(const char *data, size_t len) {
20488 if (!ws_) { return false; }
20489 return ws_->send(data, len);
20490}
20491
20492inline void WebSocketClient::close(CloseStatus status,
20493 const std::string &reason) {
20494 if (ws_) { ws_->close(status, reason); }
20495}
20496
20497inline bool WebSocketClient::is_open() const { return ws_ && ws_->is_open(); }
20498
20499inline const std::string &WebSocketClient::subprotocol() const {
20500 return subprotocol_;
20501}
20502
20503inline void WebSocketClient::set_read_timeout(time_t sec, time_t usec) {
20504 read_timeout_sec_ = sec;
20505 read_timeout_usec_ = usec;
20506}
20507
20508inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
20509 write_timeout_sec_ = sec;
20510 write_timeout_usec_ = usec;
20511}
20512
20513inline void WebSocketClient::set_websocket_ping_interval(time_t sec) {
20514 websocket_ping_interval_sec_ = sec;
20515}
20516
20517inline void WebSocketClient::set_websocket_max_missed_pongs(int count) {
20518 websocket_max_missed_pongs_ = count;
20519}
20520
20521inline void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
20522
20523inline void WebSocketClient::set_address_family(int family) {
20524 address_family_ = family;
20525}
20526
20527inline void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
20528
20529inline void WebSocketClient::set_socket_options(SocketOptions socket_options) {
20530 socket_options_ = std::move(socket_options);
20531}
20532
20533inline void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) {
20534 connection_timeout_sec_ = sec;
20535 connection_timeout_usec_ = usec;
20536}
20537
20538inline void WebSocketClient::set_interface(const std::string &intf) {
20539 interface_ = intf;
20540}
20541
20542inline void WebSocketClient::set_hostname_addr_map(
20543 std::map<std::string, std::string> addr_map) {
20544 addr_map_ = std::move(addr_map);
20545}
20546
20547#ifdef CPPHTTPLIB_SSL_ENABLED
20548
20549inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
20550 ca_cert_file_path_ = path;
20551}
20552
20553inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
20554 if (store && tls_ctx_) {
20555 // set_ca_store takes ownership of store
20556 tls::set_ca_store(tls_ctx_, store);
20557 custom_ca_loaded_ = true;
20558 } else if (store) {
20559 tls::free_ca_store(store);
20560 }
20561}
20562
20563inline void WebSocketClient::load_ca_cert_store(const char *ca_cert,
20564 std::size_t size) {
20565 if (tls_ctx_ && ca_cert && size > 0) {
20566 tls::load_ca_pem(tls_ctx_, ca_cert, size);
20567 custom_ca_loaded_ = true;
20568 }
20569}
20570
20571inline void
20572WebSocketClient::enable_server_certificate_verification(bool enabled) {
20573 server_certificate_verification_ = enabled;
20574}
20575
20576inline void WebSocketClient::enable_system_ca(bool enabled) {
20577 system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
20578}
20579
20580#endif // CPPHTTPLIB_SSL_ENABLED
20581
20582} // namespace ws
20583
20584// ----------------------------------------------------------------------------
20585
20586} // namespace httplib
20587
20588#endif // CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION
Definition httplib.h:11
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
Definition httplib.h:126
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH
Definition httplib.h:130
#define CPPHTTPLIB_HEADER_MAX_COUNT
Definition httplib.h:118
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
Definition httplib.h:46
#define INVALID_SOCKET
Definition httplib.h:300
#define CPPHTTPLIB_SEND_FLAGS
Definition httplib.h:181
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
Definition httplib.h:30
#define CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND
Definition httplib.h:197
#define CPPHTTPLIB_SEND_BUFSIZ
Definition httplib.h:154
#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
Definition httplib.h:193
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
Definition httplib.h:50
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
Definition httplib.h:54
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
Definition httplib.h:74
#define CPPHTTPLIB_LISTEN_BACKLOG
Definition httplib.h:185
#define CPPHTTPLIB_IDLE_INTERVAL_SECOND
Definition httplib.h:98
#define CPPHTTPLIB_EXPECT_100_THRESHOLD
Definition httplib.h:82
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT
Definition httplib.h:34
#define CPPHTTPLIB_RANGE_MAX_COUNT
Definition httplib.h:138
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
Definition httplib.h:134
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD
Definition httplib.h:90
#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT
Definition httplib.h:173
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND
Definition httplib.h:105
#define CPPHTTPLIB_IPV6_V6ONLY
Definition httplib.h:146
#define CPPHTTPLIB_RECV_FLAGS
Definition httplib.h:177
#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT
Definition httplib.h:169
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
Definition httplib.h:62
int socket_t
Definition httplib.h:298
#define CPPHTTPLIB_HEADER_MAX_LENGTH
Definition httplib.h:114
#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND
Definition httplib.h:86
#define CPPHTTPLIB_COMPRESSION_BUFSIZ
Definition httplib.h:158
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
Definition httplib.h:26
#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND
Definition httplib.h:205
#define CPPHTTPLIB_THREAD_POOL_COUNT
Definition httplib.h:162
#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
Definition httplib.h:78
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
Definition httplib.h:58
#define CPPHTTPLIB_RECV_BUFSIZ
Definition httplib.h:150
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
Definition httplib.h:110
#define CPPHTTPLIB_REDIRECT_MAX_COUNT
Definition httplib.h:122
#define CPPHTTPLIB_MAX_LINE_LENGTH
Definition httplib.h:189
#define CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND
Definition httplib.h:201
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
Definition httplib.h:66
#define CPPHTTPLIB_TCP_NODELAY
Definition httplib.h:142
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
Definition httplib.h:70
#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
Definition httplib.h:38
#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS
Definition httplib.h:209
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
Definition httplib.h:42
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
Definition httplib.h:94