GameApi 28
Library used by GameApi Builder tool
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.46.1"
12#define CPPHTTPLIB_VERSION_NUM "0x002e01"
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,
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
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
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 (simple wrapper for strtod)
690inline from_chars_result<double> from_chars(const char *first, const char *last,
691 double &value) {
692 std::string s(first, last);
693 char *endptr = nullptr;
694 errno = 0;
695 value = std::strtod(s.c_str(), &endptr);
696 if (endptr == s.c_str()) { return {first, std::errc::invalid_argument}; }
697 if (errno == ERANGE) {
698 return {first + (endptr - s.c_str()), std::errc::result_out_of_range};
699 }
700 return {first + (endptr - s.c_str()), std::errc{}};
701}
702
703inline bool parse_port(const char *s, size_t len, int &port) {
704 int val = 0;
705 auto r = from_chars(s, s + len, val);
706 if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
707 port = val;
708 return true;
709}
710
711inline bool parse_port(const std::string &s, int &port) {
712 return parse_port(s.data(), s.size(), port);
713}
714
716 std::string scheme;
717 std::string host;
718 std::string port;
719 std::string path;
720 std::string query;
721};
722
723inline bool parse_url(const std::string &url, UrlComponents &uc) {
724 uc = {};
725 size_t pos = 0;
726
727 auto sep = url.find("://");
728 if (sep != std::string::npos) {
729 uc.scheme = url.substr(0, sep);
730
731 // Scheme must be [a-z]+ only
732 if (uc.scheme.empty()) { return false; }
733 for (auto c : uc.scheme) {
734 if (c < 'a' || c > 'z') { return false; }
735 }
736
737 pos = sep + 3;
738 } else if (url.compare(0, 2, "//") == 0) {
739 pos = 2;
740 }
741
742 auto has_authority_prefix = pos > 0;
743 auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' &&
744 url[0] != '?' && url[0] != '#');
745 if (has_authority) {
746 if (pos < url.size() && url[pos] == '[') {
747 auto close = url.find(']', pos);
748 if (close == std::string::npos) { return false; }
749 uc.host = url.substr(pos + 1, close - pos - 1);
750
751 // IPv6 host must be [a-fA-F0-9:]+ only
752 if (uc.host.empty()) { return false; }
753 for (auto c : uc.host) {
754 if (!((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
755 (c >= '0' && c <= '9') || c == ':')) {
756 return false;
757 }
758 }
759
760 pos = close + 1;
761 } else {
762 auto end = url.find_first_of(":/?#", pos);
763 if (end == std::string::npos) { end = url.size(); }
764 uc.host = url.substr(pos, end - pos);
765 pos = end;
766 }
767
768 if (pos < url.size() && url[pos] == ':') {
769 ++pos;
770 auto end = url.find_first_of("/?#", pos);
771 if (end == std::string::npos) { end = url.size(); }
772 uc.port = url.substr(pos, end - pos);
773 pos = end;
774 }
775
776 // Without :// or //, the entire input must be consumed as host[:port].
777 // If there is leftover (path, query, etc.), this is not a valid
778 // host[:port] string — clear and reparse as a plain path.
779 if (!has_authority_prefix && pos < url.size()) {
780 uc.host.clear();
781 uc.port.clear();
782 pos = 0;
783 }
784 }
785
786 if (pos < url.size() && url[pos] != '?' && url[pos] != '#') {
787 auto end = url.find_first_of("?#", pos);
788 if (end == std::string::npos) { end = url.size(); }
789 uc.path = url.substr(pos, end - pos);
790 pos = end;
791 }
792
793 if (pos < url.size() && url[pos] == '?') {
794 auto end = url.find('#', pos);
795 if (end == std::string::npos) { end = url.size(); }
796 uc.query = url.substr(pos, end - pos);
797 }
798
799 return true;
800}
801
802} // namespace detail
803
805 // no decision has been made, use the built-in certificate verifier
807 // connection certificate is verified and accepted
809 // connection certificate was processed but is rejected
811};
812
814 // Information responses
819
820 // Successful responses
821 OK_200 = 200,
831
832 // Redirection messages
842
843 // Client error responses
854 Gone_410 = 410,
873
874 // Server error responses
886};
887
888using Headers =
889 std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
891
892using Params = std::multimap<std::string, std::string>;
893using Match = std::smatch;
894
895using DownloadProgress = std::function<bool(size_t current, size_t total)>;
896using UploadProgress = std::function<bool(size_t current, size_t total)>;
897
898/*
899 * detail: type-erased storage used by UserData.
900 * ABI-stable regardless of C++ standard — always uses this custom
901 * implementation instead of std::any.
902 */
903namespace detail {
904
905using any_type_id = const void *;
906
907template <typename T> any_type_id any_typeid() noexcept {
908 static const char id = 0;
909 return &id;
910}
911
913 virtual ~any_storage() = default;
914 virtual std::unique_ptr<any_storage> clone() const = 0;
915 virtual any_type_id type_id() const noexcept = 0;
916};
917
918template <typename T> struct any_value final : any_storage {
920 template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
921 std::unique_ptr<any_storage> clone() const override {
922 return std::unique_ptr<any_storage>(new any_value<T>(value));
923 }
924 any_type_id type_id() const noexcept override { return any_typeid<T>(); }
925};
926
927} // namespace detail
928
929class UserData {
930public:
931 UserData() = default;
932 UserData(UserData &&) noexcept = default;
933 UserData &operator=(UserData &&) noexcept = default;
934
935 UserData(const UserData &o) {
936 for (const auto &e : o.entries_) {
937 if (e.second) { entries_[e.first] = e.second->clone(); }
938 }
939 }
940
942 if (this != &o) {
943 entries_.clear();
944 for (const auto &e : o.entries_) {
945 if (e.second) { entries_[e.first] = e.second->clone(); }
946 }
947 }
948 return *this;
949 }
950
951 template <typename T> void set(const std::string &key, T &&value) {
952 using D = typename std::decay<T>::type;
953 entries_[key].reset(new detail::any_value<D>(std::forward<T>(value)));
954 }
955
956 template <typename T> T *get(const std::string &key) noexcept {
957 auto it = entries_.find(key);
958 if (it == entries_.end() || !it->second) { return nullptr; }
959 if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
960 return &static_cast<detail::any_value<T> *>(it->second.get())->value;
961 }
962
963 template <typename T> const T *get(const std::string &key) const noexcept {
964 auto it = entries_.find(key);
965 if (it == entries_.end() || !it->second) { return nullptr; }
966 if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
967 return &static_cast<const detail::any_value<T> *>(it->second.get())->value;
968 }
969
970 bool has(const std::string &key) const noexcept {
971 return entries_.find(key) != entries_.end();
972 }
973
974 void erase(const std::string &key) { entries_.erase(key); }
975
976 void clear() noexcept { entries_.clear(); }
977
978private:
979 std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
980 entries_;
981};
982
983struct Response;
984using ResponseHandler = std::function<bool(const Response &response)>;
985
986struct FormData {
987 std::string name;
988 std::string content;
989 std::string filename;
990 std::string content_type;
992};
993
994struct FormField {
995 std::string name;
996 std::string content;
998};
999using FormFields = std::multimap<std::string, FormField>;
1000
1001using FormFiles = std::multimap<std::string, FormData>;
1002
1004 FormFields fields; // Text fields from multipart
1005 FormFiles files; // Files from multipart
1006
1007 // Text field access
1008 std::string get_field(const std::string &key, size_t id = 0) const;
1009 std::vector<std::string> get_fields(const std::string &key) const;
1010 bool has_field(const std::string &key) const;
1011 size_t get_field_count(const std::string &key) const;
1012
1013 // File access
1014 FormData get_file(const std::string &key, size_t id = 0) const;
1015 std::vector<FormData> get_files(const std::string &key) const;
1016 bool has_file(const std::string &key) const;
1017 size_t get_file_count(const std::string &key) const;
1018};
1019
1021 std::string name;
1022 std::string content;
1023 std::string filename;
1024 std::string content_type;
1025};
1026using UploadFormDataItems = std::vector<UploadFormData>;
1027
1029public:
1030 DataSink() : os(&sb_), sb_(*this) {}
1031
1032 DataSink(const DataSink &) = delete;
1033 DataSink &operator=(const DataSink &) = delete;
1034 DataSink(DataSink &&) = delete;
1036
1037 std::function<bool(const char *data, size_t data_len)> write;
1038 std::function<bool()> is_writable;
1039 std::function<void()> done;
1040 std::function<void(const Headers &trailer)> done_with_trailer;
1041 std::ostream os;
1042
1043private:
1044 class data_sink_streambuf final : public std::streambuf {
1045 public:
1046 explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
1047
1048 protected:
1049 std::streamsize xsputn(const char *s, std::streamsize n) override {
1050 if (sink_.write(s, static_cast<size_t>(n))) { return n; }
1051 return 0;
1052 }
1053
1054 private:
1055 DataSink &sink_;
1056 };
1057
1058 data_sink_streambuf sb_;
1059};
1060
1062 std::function<bool(size_t offset, size_t length, DataSink &sink)>;
1063
1065 std::function<bool(size_t offset, DataSink &sink)>;
1066
1067using ContentProviderResourceReleaser = std::function<void(bool success)>;
1068
1075using FormDataProviderItems = std::vector<FormDataProvider>;
1076
1077inline FormDataProvider
1078make_file_provider(const std::string &name, const std::string &filepath,
1079 const std::string &filename = std::string(),
1080 const std::string &content_type = std::string()) {
1081 FormDataProvider fdp;
1082 fdp.name = name;
1083 fdp.filename = filename.empty() ? filepath : filename;
1084 fdp.content_type = content_type;
1085 fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool {
1086 std::ifstream f(filepath, std::ios::binary);
1087 if (!f) { return false; }
1088 if (offset > 0) {
1089 f.seekg(static_cast<std::streamoff>(offset));
1090 if (!f.good()) {
1091 sink.done();
1092 return true;
1093 }
1094 }
1095 char buf[8192];
1096 f.read(buf, sizeof(buf));
1097 auto n = static_cast<size_t>(f.gcount());
1098 if (n > 0) { return sink.write(buf, n); }
1099 sink.done(); // EOF
1100 return true;
1101 };
1102 return fdp;
1103}
1104
1105inline std::pair<size_t, ContentProvider>
1106make_file_body(const std::string &filepath) {
1107 size_t size = 0;
1108 {
1109 std::ifstream f(filepath, std::ios::binary | std::ios::ate);
1110 if (!f) { return {0, ContentProvider{}}; }
1111 size = static_cast<size_t>(f.tellg());
1112 }
1113
1114 ContentProvider provider = [filepath](size_t offset, size_t length,
1115 DataSink &sink) -> bool {
1116 std::ifstream f(filepath, std::ios::binary);
1117 if (!f) { return false; }
1118 f.seekg(static_cast<std::streamoff>(offset));
1119 if (!f.good()) { return false; }
1120 char buf[8192];
1121 while (length > 0) {
1122 auto to_read = (std::min)(sizeof(buf), length);
1123 f.read(buf, static_cast<std::streamsize>(to_read));
1124 auto n = static_cast<size_t>(f.gcount());
1125 if (n == 0) { break; }
1126 if (!sink.write(buf, n)) { return false; }
1127 length -= n;
1128 }
1129 return true;
1130 };
1131 return {size, std::move(provider)};
1132}
1133
1134using ContentReceiverWithProgress = std::function<bool(
1135 const char *data, size_t data_length, size_t offset, size_t total_length)>;
1136
1138 std::function<bool(const char *data, size_t data_length)>;
1139
1140using FormDataHeader = std::function<bool(const FormData &file)>;
1141
1143public:
1144 using Reader = std::function<bool(ContentReceiver receiver)>;
1146 std::function<bool(FormDataHeader header, ContentReceiver receiver)>;
1147
1148 ContentReader(Reader reader, FormDataReader multipart_reader)
1149 : reader_(std::move(reader)),
1150 formdata_reader_(std::move(multipart_reader)) {}
1151
1153 return formdata_reader_(std::move(header), std::move(receiver));
1154 }
1155
1156 bool operator()(ContentReceiver receiver) const {
1157 return reader_(std::move(receiver));
1158 }
1159
1162};
1163
1164using Range = std::pair<ssize_t, ssize_t>;
1165using Ranges = std::vector<Range>;
1166
1167#ifdef CPPHTTPLIB_SSL_ENABLED
1168// TLS abstraction layer - public type definitions and API
1169namespace tls {
1170
1171// Opaque handles (defined as void* for abstraction)
1172using ctx_t = void *;
1173using session_t = void *;
1174using const_session_t = const void *; // For read-only session access
1175using cert_t = void *;
1176using ca_store_t = void *;
1177
1178// TLS versions
1179enum class Version {
1180 TLS1_2 = 0x0303,
1181 TLS1_3 = 0x0304,
1182};
1183
1184// Subject Alternative Names (SAN) entry types
1185enum class SanType { DNS, IP, EMAIL, URI, OTHER };
1186
1187// SAN entry structure
1188struct SanEntry {
1189 SanType type;
1190 std::string value;
1191};
1192
1193// Verification context for certificate verification callback
1194struct VerifyContext {
1195 session_t session; // TLS session handle
1196 cert_t cert; // Current certificate being verified
1197 int depth; // Certificate chain depth (0 = leaf)
1198 bool preverify_ok; // OpenSSL/Mbed TLS pre-verification result
1199 long error_code; // Backend-specific error code (0 = no error)
1200 const char *error_string; // Human-readable error description
1201
1202 // Certificate introspection methods
1203 std::string subject_cn() const;
1204 std::string issuer_name() const;
1205 bool check_hostname(const char *hostname) const;
1206 std::vector<SanEntry> sans() const;
1207 bool validity(time_t &not_before, time_t &not_after) const;
1208 std::string serial() const;
1209};
1210
1211using VerifyCallback = std::function<bool(const VerifyContext &ctx)>;
1212
1213// TlsError codes for TLS operations (backend-independent)
1214enum class ErrorCode : int {
1215 Success = 0,
1216 WantRead, // Non-blocking: need to wait for read
1217 WantWrite, // Non-blocking: need to wait for write
1218 PeerClosed, // Peer closed the connection
1219 Fatal, // Unrecoverable error
1220 SyscallError, // System call error (check sys_errno)
1221 CertVerifyFailed, // Certificate verification failed
1222 HostnameMismatch, // Hostname verification failed
1223};
1224
1225// TLS error information
1226struct TlsError {
1227 ErrorCode code = ErrorCode::Fatal;
1228 uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value
1229 int sys_errno = 0; // errno when SyscallError
1230
1231 // Convert verification error code to human-readable string
1232 static std::string verify_error_to_string(long error_code);
1233};
1234
1235// RAII wrapper for peer certificate
1236class PeerCert {
1237public:
1238 PeerCert();
1239 PeerCert(PeerCert &&other) noexcept;
1240 PeerCert &operator=(PeerCert &&other) noexcept;
1241 ~PeerCert();
1242
1243 PeerCert(const PeerCert &) = delete;
1244 PeerCert &operator=(const PeerCert &) = delete;
1245
1246 explicit operator bool() const;
1247 std::string subject_cn() const;
1248 std::string issuer_name() const;
1249 bool check_hostname(const char *hostname) const;
1250 std::vector<SanEntry> sans() const;
1251 bool validity(time_t &not_before, time_t &not_after) const;
1252 std::string serial() const;
1253
1254private:
1255 explicit PeerCert(cert_t cert);
1256 cert_t cert_ = nullptr;
1257 friend PeerCert get_peer_cert_from_session(const_session_t session);
1258};
1259
1260// Callback for TLS context setup (used by SSLServer constructor)
1261using ContextSetupCallback = std::function<bool(ctx_t ctx)>;
1262
1263} // namespace tls
1264#endif
1265
1266struct Request {
1267 std::string method;
1268 std::string path;
1269 std::string matched_route;
1273 std::string body;
1274
1275 std::string remote_addr;
1276 int remote_port = -1;
1277 std::string local_addr;
1278 int local_port = -1;
1279
1280 // for server
1281 std::string version;
1282 std::string target;
1286 std::unordered_map<std::string, std::string> path_params;
1287 std::function<bool()> is_connection_closed = []() { return true; };
1288
1289 // for client
1290 std::vector<std::string> accept_content_types;
1295
1296 bool has_header(const std::string &key) const;
1297 std::string get_header_value(const std::string &key, const char *def = "",
1298 size_t id = 0) const;
1299 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1300 size_t id = 0) const;
1301 size_t get_header_value_count(const std::string &key) const;
1302 void set_header(const std::string &key, const std::string &val);
1303
1304 bool has_trailer(const std::string &key) const;
1305 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1306 size_t get_trailer_value_count(const std::string &key) const;
1307
1308 bool has_param(const std::string &key) const;
1309 std::string get_param_value(const std::string &key, size_t id = 0) const;
1310 std::vector<std::string> get_param_values(const std::string &key) const;
1311 size_t get_param_value_count(const std::string &key) const;
1312
1313 bool is_multipart_form_data() const;
1314
1315 // private members...
1316 bool body_consumed_ = false;
1322 std::chrono::time_point<std::chrono::steady_clock> start_time_ =
1323 (std::chrono::steady_clock::time_point::min)();
1324
1325#ifdef CPPHTTPLIB_SSL_ENABLED
1326 tls::const_session_t ssl = nullptr;
1327 tls::PeerCert peer_cert() const;
1328 std::string sni() const;
1329#endif
1330};
1331
1332struct Response {
1333 std::string version;
1334 int status = -1;
1335 std::string reason;
1338 std::string body;
1339 std::string location; // Redirect location
1340
1341 // User-defined context — set by pre-routing/pre-request handlers and read
1342 // by route handlers to pass arbitrary data (e.g. decoded auth tokens).
1344
1345 bool has_header(const std::string &key) const;
1346 std::string get_header_value(const std::string &key, const char *def = "",
1347 size_t id = 0) const;
1348 size_t get_header_value_u64(const std::string &key, size_t def = 0,
1349 size_t id = 0) const;
1350 size_t get_header_value_count(const std::string &key) const;
1351 void set_header(const std::string &key, const std::string &val);
1352
1353 bool has_trailer(const std::string &key) const;
1354 std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1355 size_t get_trailer_value_count(const std::string &key) const;
1356
1357 void set_redirect(const std::string &url, int status = StatusCode::Found_302);
1358 void set_content(const char *s, size_t n, const std::string &content_type);
1359 void set_content(const std::string &s, const std::string &content_type);
1360 void set_content(std::string &&s, const std::string &content_type);
1361
1363 size_t length, const std::string &content_type, ContentProvider provider,
1364 ContentProviderResourceReleaser resource_releaser = nullptr);
1365
1367 const std::string &content_type, ContentProviderWithoutLength provider,
1368 ContentProviderResourceReleaser resource_releaser = nullptr);
1369
1371 const std::string &content_type, ContentProviderWithoutLength provider,
1372 ContentProviderResourceReleaser resource_releaser = nullptr);
1373
1374 void set_file_content(const std::string &path,
1375 const std::string &content_type);
1376 void set_file_content(const std::string &path);
1377
1378 Response() = default;
1379 Response(const Response &) = default;
1380 Response &operator=(const Response &) = default;
1381 Response(Response &&) = default;
1388
1389 // private members...
1397};
1398
1438
1439std::string to_string(Error error);
1440
1441std::ostream &operator<<(std::ostream &os, const Error &obj);
1442
1443class Stream {
1444public:
1445 virtual ~Stream() = default;
1446
1447 virtual bool is_readable() const = 0;
1448 virtual bool wait_readable() const = 0;
1449 virtual bool wait_writable() const = 0;
1450 virtual bool is_peer_alive() const { return wait_writable(); }
1451
1452 virtual ssize_t read(char *ptr, size_t size) = 0;
1453 virtual ssize_t write(const char *ptr, size_t size) = 0;
1454 virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
1455 virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
1456 virtual socket_t socket() const = 0;
1457
1458 virtual time_t duration() const = 0;
1459
1460 virtual void set_read_timeout(time_t sec, time_t usec = 0) {
1461 (void)sec;
1462 (void)usec;
1463 }
1464
1465 ssize_t write(const char *ptr);
1466 ssize_t write(const std::string &s);
1467
1468 Error get_error() const { return error_; }
1469
1470protected:
1472};
1473
1475public:
1476 TaskQueue() = default;
1477 virtual ~TaskQueue() = default;
1478
1479 virtual bool enqueue(std::function<void()> fn) = 0;
1480 virtual void shutdown() = 0;
1481
1482 virtual void on_idle() {}
1483};
1484
1485class ThreadPool final : public TaskQueue {
1486public:
1487 explicit ThreadPool(size_t n, size_t max_n = 0, size_t mqr = 0);
1488 ThreadPool(const ThreadPool &) = delete;
1489 ~ThreadPool() override = default;
1490
1491 bool enqueue(std::function<void()> fn) override;
1492 void shutdown() override;
1493
1494private:
1495 void worker(bool is_dynamic);
1496 void move_to_finished(std::thread::id id);
1497 void cleanup_finished_threads();
1498
1499 size_t base_thread_count_;
1500 size_t max_thread_count_;
1501 size_t max_queued_requests_;
1502 size_t idle_thread_count_;
1503
1504 bool shutdown_;
1505
1506 std::list<std::function<void()>> jobs_;
1507 std::vector<std::thread> threads_; // base threads
1508 std::list<std::thread> dynamic_threads_; // dynamic threads
1509 std::vector<std::thread>
1510 finished_threads_; // exited dynamic threads awaiting join
1511
1512 std::condition_variable cond_;
1513 std::mutex mutex_;
1514};
1515
1516using Logger = std::function<void(const Request &, const Response &)>;
1517
1518// Forward declaration for Error type
1519enum class Error;
1520using ErrorLogger = std::function<void(const Error &, const Request *)>;
1521
1522using SocketOptions = std::function<void(socket_t sock)>;
1523
1525
1526bool set_socket_opt(socket_t sock, int level, int optname, int optval);
1527
1528const char *status_message(int status);
1529
1530std::string to_string(Error error);
1531
1532std::ostream &operator<<(std::ostream &os, const Error &obj);
1533
1534std::string get_bearer_token_auth(const Request &req);
1535
1536namespace detail {
1537
1539public:
1540 MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {}
1541 virtual ~MatcherBase() = default;
1542
1543 const std::string &pattern() const { return pattern_; }
1544
1545 // Match request path and populate its matches and
1546 virtual bool match(Request &request) const = 0;
1547
1548private:
1549 std::string pattern_;
1550};
1551
1570class PathParamsMatcher final : public MatcherBase {
1571public:
1572 PathParamsMatcher(const std::string &pattern);
1573
1574 bool match(Request &request) const override;
1575
1576private:
1577 // Treat segment separators as the end of path parameter capture
1578 // Does not need to handle query parameters as they are parsed before path
1579 // matching
1580 static constexpr char separator = '/';
1581
1582 // Contains static path fragments to match against, excluding the '/' after
1583 // path params
1584 // Fragments are separated by path params
1585 std::vector<std::string> static_fragments_;
1586 // Stores the names of the path parameters to be used as keys in the
1587 // Request::path_params map
1588 std::vector<std::string> param_names_;
1589};
1590
1599class RegexMatcher final : public MatcherBase {
1600public:
1601 RegexMatcher(const std::string &pattern)
1602 : MatcherBase(pattern), regex_(pattern) {}
1603
1604 bool match(Request &request) const override;
1605
1606private:
1607 std::regex regex_;
1608};
1609
1610int close_socket(socket_t sock) noexcept;
1611
1612ssize_t write_headers(Stream &strm, const Headers &headers);
1613
1614bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
1615 time_t usec);
1616
1618 const std::string &boundary);
1619
1622 const std::string &boundary);
1623
1624} // namespace detail
1625
1626class Server {
1627public:
1628 using Handler = std::function<void(const Request &, Response &)>;
1629
1631 std::function<void(const Request &, Response &, std::exception_ptr ep)>;
1632
1638 std::function<HandlerResponse(const Request &, Response &)>;
1639
1640 using HandlerWithContentReader = std::function<void(
1641 const Request &, Response &, const ContentReader &content_reader)>;
1642
1644 std::function<int(const Request &, Response &)>;
1645
1646 using StartHandler = std::function<void()>;
1647
1649 std::function<void(const Request &, ws::WebSocket &)>;
1651 std::function<std::string(const std::vector<std::string> &protocols)>;
1652
1653 Server();
1654
1655 virtual ~Server();
1656
1657 virtual bool is_valid() const;
1658
1659 Server &Get(const std::string &pattern, Handler handler);
1660 Server &Post(const std::string &pattern, Handler handler);
1661 Server &Post(const std::string &pattern, HandlerWithContentReader handler);
1662 Server &Put(const std::string &pattern, Handler handler);
1663 Server &Put(const std::string &pattern, HandlerWithContentReader handler);
1664 Server &Patch(const std::string &pattern, Handler handler);
1665 Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
1666 Server &Delete(const std::string &pattern, Handler handler);
1667 Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
1668 Server &Options(const std::string &pattern, Handler handler);
1669
1670 Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
1671 Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
1672 SubProtocolSelector sub_protocol_selector);
1673
1674 bool set_base_dir(const std::string &dir,
1675 const std::string &mount_point = std::string());
1676 bool set_mount_point(const std::string &mount_point, const std::string &dir,
1677 Headers headers = Headers());
1678 bool remove_mount_point(const std::string &mount_point);
1679 Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
1680 const std::string &mime);
1681 Server &set_default_file_mimetype(const std::string &mime);
1683
1684 template <class ErrorHandlerFunc>
1685 Server &set_error_handler(ErrorHandlerFunc &&handler) {
1686 return set_error_handler_core(
1687 std::forward<ErrorHandlerFunc>(handler),
1688 std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
1689 }
1690
1692
1694 Server &set_post_routing_handler(Handler handler);
1695
1697
1699
1701
1702 Server &set_logger(Logger logger);
1704 Server &set_error_logger(ErrorLogger error_logger);
1705
1706 Server &set_address_family(int family);
1707 Server &set_tcp_nodelay(bool on);
1708 Server &set_ipv6_v6only(bool on);
1709 Server &set_socket_options(SocketOptions socket_options);
1710
1712 Server &
1713 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1714
1715 Server &set_trusted_proxies(const std::vector<std::string> &proxies);
1716
1717 Server &set_keep_alive_max_count(size_t count);
1718 Server &set_keep_alive_timeout(time_t sec);
1719 template <class Rep, class Period>
1720 Server &
1721 set_keep_alive_timeout(const std::chrono::duration<Rep, Period> &duration);
1722
1723 Server &set_read_timeout(time_t sec, time_t usec = 0);
1724 template <class Rep, class Period>
1725 Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1726
1727 Server &set_write_timeout(time_t sec, time_t usec = 0);
1728 template <class Rep, class Period>
1729 Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1730
1731 Server &set_idle_interval(time_t sec, time_t usec = 0);
1732 template <class Rep, class Period>
1733 Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
1734
1735 Server &set_payload_max_length(size_t length);
1736
1738 template <class Rep, class Period>
1740 const std::chrono::duration<Rep, Period> &duration);
1741
1743
1744 bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
1745 int bind_to_any_port(const std::string &host, int socket_flags = 0);
1746 bool listen_after_bind();
1747
1748 bool listen(const std::string &host, int port, int socket_flags = 0);
1749
1750 bool is_running() const;
1751 void wait_until_ready() const;
1752 void stop() noexcept;
1753 void decommission();
1754
1756
1757protected:
1758 bool process_request(Stream &strm, const std::string &remote_addr,
1759 int remote_port, const std::string &local_addr,
1760 int local_port, bool close_connection,
1761 bool &connection_closed,
1762 const std::function<void(Request &)> &setup_request,
1763 bool *websocket_upgraded = nullptr);
1764
1766
1767 std::vector<std::string> trusted_proxies_;
1768
1781
1782private:
1783 using Handlers =
1784 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
1785 using HandlersForContentReader =
1786 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
1788
1789 static std::unique_ptr<detail::MatcherBase>
1790 make_matcher(const std::string &pattern);
1791
1792 template <typename H>
1793 Server &add_handler(
1794 std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, H>> &handlers,
1795 const std::string &pattern, H handler) {
1796 handlers.emplace_back(make_matcher(pattern), std::move(handler));
1797 return *this;
1798 }
1799
1800 Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
1801 Server &set_error_handler_core(Handler handler, std::false_type);
1802
1803 socket_t create_server_socket(const std::string &host, int port,
1804 int socket_flags,
1805 SocketOptions socket_options) const;
1806 int bind_internal(const std::string &host, int port, int socket_flags);
1807 bool listen_internal();
1808
1809 bool routing(Request &req, Response &res, Stream &strm);
1810 bool handle_file_request(Request &req, Response &res);
1811 bool check_if_not_modified(const Request &req, Response &res,
1812 const std::string &etag, time_t mtime) const;
1813 bool check_if_range(Request &req, const std::string &etag,
1814 time_t mtime) const;
1815 bool dispatch_request(Request &req, Response &res,
1816 const Handlers &handlers) const;
1817 bool dispatch_request_for_content_reader(
1818 Request &req, Response &res, ContentReader content_reader,
1819 const HandlersForContentReader &handlers) const;
1820
1821 bool parse_request_line(const char *s, Request &req) const;
1822 void apply_ranges(const Request &req, Response &res,
1823 std::string &content_type, std::string &boundary) const;
1824 bool write_response(Stream &strm, bool close_connection, Request &req,
1825 Response &res);
1826 bool write_response_with_content(Stream &strm, bool close_connection,
1827 const Request &req, Response &res);
1828 bool write_response_core(Stream &strm, bool close_connection,
1829 const Request &req, Response &res,
1830 bool need_apply_ranges);
1831 bool write_content_with_provider(Stream &strm, const Request &req,
1832 Response &res, const std::string &boundary,
1833 const std::string &content_type);
1834 bool read_content(Stream &strm, Request &req, Response &res);
1835 bool read_content_with_content_receiver(Stream &strm, Request &req,
1836 Response &res,
1837 ContentReceiver receiver,
1838 FormDataHeader multipart_header,
1839 ContentReceiver multipart_receiver);
1840 bool read_content_core(Stream &strm, Request &req, Response &res,
1841 ContentReceiver receiver,
1842 FormDataHeader multipart_header,
1843 ContentReceiver multipart_receiver) const;
1844
1845 virtual bool process_and_close_socket(socket_t sock);
1846
1847 void output_log(const Request &req, const Response &res) const;
1848 void output_pre_compression_log(const Request &req,
1849 const Response &res) const;
1850 void output_error_log(const Error &err, const Request *req) const;
1851
1852 std::atomic<bool> is_running_{false};
1853 std::atomic<bool> is_decommissioned{false};
1854
1855 struct MountPointEntry {
1856 std::string mount_point;
1857 std::string base_dir;
1858 std::string resolved_base_dir;
1859 Headers headers;
1860 };
1861 std::vector<MountPointEntry> base_dirs_;
1862 std::map<std::string, std::string> file_extension_and_mimetype_map_;
1863 std::string default_file_mimetype_ = "application/octet-stream";
1864 Handler file_request_handler_;
1865
1866 Handlers get_handlers_;
1867 Handlers post_handlers_;
1868 HandlersForContentReader post_handlers_for_content_reader_;
1869 Handlers put_handlers_;
1870 HandlersForContentReader put_handlers_for_content_reader_;
1871 Handlers patch_handlers_;
1872 HandlersForContentReader patch_handlers_for_content_reader_;
1873 Handlers delete_handlers_;
1874 HandlersForContentReader delete_handlers_for_content_reader_;
1875 Handlers options_handlers_;
1876
1877 struct WebSocketHandlerEntry {
1878 std::unique_ptr<detail::MatcherBase> matcher;
1879 WebSocketHandler handler;
1880 SubProtocolSelector sub_protocol_selector;
1881 };
1882 using WebSocketHandlers = std::vector<WebSocketHandlerEntry>;
1883 WebSocketHandlers websocket_handlers_;
1884
1885 HandlerWithResponse error_handler_;
1886 ExceptionHandler exception_handler_;
1887 HandlerWithResponse pre_routing_handler_;
1888 Handler post_routing_handler_;
1889 HandlerWithResponse pre_request_handler_;
1890 Expect100ContinueHandler expect_100_continue_handler_;
1891 StartHandler start_handler_;
1892
1893 mutable std::mutex logger_mutex_;
1894 Logger logger_;
1895 Logger pre_compression_logger_;
1896 ErrorLogger error_logger_;
1897
1898 int address_family_ = AF_UNSPEC;
1899 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
1900 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
1901 SocketOptions socket_options_ = default_socket_options;
1902
1903 Headers default_headers_;
1904 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
1906};
1907
1908class Result {
1909public:
1910 Result() = default;
1911 Result(std::unique_ptr<Response> &&res, Error err,
1912 Headers &&request_headers = Headers{})
1913 : res_(std::move(res)), err_(err),
1914 request_headers_(std::move(request_headers)) {}
1915 // Response
1916 operator bool() const { return res_ != nullptr; }
1917 bool operator==(std::nullptr_t) const { return res_ == nullptr; }
1918 bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
1919 const Response &value() const { return *res_; }
1920 Response &value() { return *res_; }
1921 const Response &operator*() const { return *res_; }
1922 Response &operator*() { return *res_; }
1923 const Response *operator->() const { return res_.get(); }
1924 Response *operator->() { return res_.get(); }
1925
1926 // Error
1927 Error error() const { return err_; }
1928
1929 // Request Headers
1930 bool has_request_header(const std::string &key) const;
1931 std::string get_request_header_value(const std::string &key,
1932 const char *def = "",
1933 size_t id = 0) const;
1934 size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
1935 size_t id = 0) const;
1936 size_t get_request_header_value_count(const std::string &key) const;
1937
1938private:
1939 std::unique_ptr<Response> res_;
1940 Error err_ = Error::Unknown;
1941 Headers request_headers_;
1942
1943#ifdef CPPHTTPLIB_SSL_ENABLED
1944public:
1945 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1946 int ssl_error)
1947 : res_(std::move(res)), err_(err),
1948 request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
1949 Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
1950 int ssl_error, uint64_t ssl_backend_error)
1951 : res_(std::move(res)), err_(err),
1952 request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
1953 ssl_backend_error_(ssl_backend_error) {}
1954
1955 int ssl_error() const { return ssl_error_; }
1956 uint64_t ssl_backend_error() const { return ssl_backend_error_; }
1957
1958private:
1959 int ssl_error_ = 0;
1960 uint64_t ssl_backend_error_ = 0;
1961#endif
1962};
1963
1966
1967 bool is_open() const { return sock != INVALID_SOCKET; }
1968
1969 ClientConnection() = default;
1970
1972
1975
1977 : sock(other.sock)
1978#ifdef CPPHTTPLIB_SSL_ENABLED
1979 ,
1980 session(other.session)
1981#endif
1982 {
1983 other.sock = INVALID_SOCKET;
1984#ifdef CPPHTTPLIB_SSL_ENABLED
1985 other.session = nullptr;
1986#endif
1987 }
1988
1990 if (this != &other) {
1991 sock = other.sock;
1992 other.sock = INVALID_SOCKET;
1993#ifdef CPPHTTPLIB_SSL_ENABLED
1994 session = other.session;
1995 other.session = nullptr;
1996#endif
1997 }
1998 return *this;
1999 }
2000
2001#ifdef CPPHTTPLIB_SSL_ENABLED
2002 tls::session_t session = nullptr;
2003#endif
2004};
2005
2006namespace detail {
2007
2008struct ChunkedDecoder;
2009
2011 Stream *stream = nullptr;
2013 size_t content_length = 0;
2015 size_t bytes_read = 0;
2016 bool chunked = false;
2017 bool eof = false;
2018 std::unique_ptr<ChunkedDecoder> chunked_decoder;
2020
2021 ssize_t read(char *buf, size_t len);
2022 bool has_error() const { return last_error != Error::Success; }
2023};
2024
2025inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
2026 size_t len) {
2027 (void)stream;
2028 return br.read(buf, len);
2029}
2030
2031class decompressor;
2032
2033enum class NoProxyKind {
2034 Wildcard, // "*"
2035 HostnameSuffix, // "example.com" or ".example.com"
2036 IPv4Cidr, // "10.0.0.0/8" (or single IP, treated as /32)
2037 IPv6Cidr, // "fe80::/10" (or single IP, treated as /128)
2038};
2039
2040// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address.
2041// Lets one CIDR matcher cover both families.
2042using IPBytes = std::array<uint8_t, 16>;
2043
2046 std::string hostname_pattern; // lowercased, leading/trailing dot stripped
2049};
2050
2052 std::string hostname; // lowercase; brackets and trailing dot removed
2053 bool is_ipv4 = false;
2054 bool is_ipv6 = false;
2056};
2057
2058} // namespace detail
2059
2061public:
2062 explicit ClientImpl(const std::string &host);
2063
2064 explicit ClientImpl(const std::string &host, int port);
2065
2066 explicit ClientImpl(const std::string &host, int port,
2067 const std::string &client_cert_path,
2068 const std::string &client_key_path);
2069
2070 virtual ~ClientImpl();
2071
2072 virtual bool is_valid() const;
2073
2075 std::unique_ptr<Response> response;
2077
2078 StreamHandle() = default;
2079 StreamHandle(const StreamHandle &) = delete;
2083 ~StreamHandle() = default;
2084
2085 bool is_valid() const {
2086 return response != nullptr && error == Error::Success;
2087 }
2088
2089 ssize_t read(char *buf, size_t len);
2091 Error get_read_error() const { return body_reader_.last_error; }
2092 bool has_read_error() const { return body_reader_.has_error(); }
2093
2094 bool trailers_parsed_ = false;
2095
2096 private:
2097 friend class ClientImpl;
2098
2099 ssize_t read_with_decompression(char *buf, size_t len);
2100
2101 std::unique_ptr<ClientConnection> connection_;
2102 std::unique_ptr<Stream> socket_stream_;
2103 Stream *stream_ = nullptr;
2104 detail::BodyReader body_reader_;
2105
2106 std::unique_ptr<detail::decompressor> decompressor_;
2107 std::string decompress_buffer_;
2108 size_t decompress_offset_ = 0;
2109 size_t decompressed_bytes_read_ = 0;
2110 };
2111
2112 // clang-format off
2113 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2114 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2115 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2116 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2117 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2118 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2119 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2120 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2121 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2122
2123 Result Head(const std::string &path);
2124 Result Head(const std::string &path, const Headers &headers);
2125
2126 Result Post(const std::string &path);
2127 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2128 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2129 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2130 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2131 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2132 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2133 Result Post(const std::string &path, const Params &params);
2134 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2135 Result Post(const std::string &path, const Headers &headers);
2136 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2137 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2138 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2139 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);
2140 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2141 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2142 Result Post(const std::string &path, const Headers &headers, const Params &params);
2143 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2144 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2145 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2146 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2147
2148 Result Put(const std::string &path);
2149 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2150 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2151 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2152 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2153 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2154 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2155 Result Put(const std::string &path, const Params &params);
2156 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2157 Result Put(const std::string &path, const Headers &headers);
2158 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2159 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2160 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2161 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);
2162 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2163 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2164 Result Put(const std::string &path, const Headers &headers, const Params &params);
2165 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2166 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2167 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2168 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2169
2170 Result Patch(const std::string &path);
2171 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2172 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2173 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2174 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2175 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2176 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2177 Result Patch(const std::string &path, const Params &params);
2178 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2179 Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
2180 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2181 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2182 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2183 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);
2184 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2185 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2186 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2187 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2188 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2189 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2190 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2191
2192 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2193 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2194 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2195 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2196 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2197 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2198 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2199 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2200
2201 Result Options(const std::string &path);
2202 Result Options(const std::string &path, const Headers &headers);
2203 // clang-format on
2204
2205 // Streaming API: Open a stream for reading response body incrementally
2206 // Socket ownership is transferred to StreamHandle for true streaming
2207 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2208 StreamHandle open_stream(const std::string &method, const std::string &path,
2209 const Params &params = {},
2210 const Headers &headers = {},
2211 const std::string &body = {},
2212 const std::string &content_type = {});
2213
2214 bool send(Request &req, Response &res, Error &error);
2215 Result send(const Request &req);
2216
2217 void stop();
2218
2219 std::string host() const;
2220 int port() const;
2221
2222 size_t is_socket_open() const;
2223 socket_t socket() const;
2224
2225 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2226
2227 void set_default_headers(Headers headers);
2228
2229 void
2230 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2231
2232 void set_address_family(int family);
2233 void set_tcp_nodelay(bool on);
2234 void set_ipv6_v6only(bool on);
2235 void set_socket_options(SocketOptions socket_options);
2236
2237 void set_connection_timeout(time_t sec, time_t usec = 0);
2238 template <class Rep, class Period>
2239 void
2240 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2241
2242 void set_read_timeout(time_t sec, time_t usec = 0);
2243 template <class Rep, class Period>
2244 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2245
2246 void set_write_timeout(time_t sec, time_t usec = 0);
2247 template <class Rep, class Period>
2248 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2249
2250 void set_max_timeout(time_t msec);
2251 template <class Rep, class Period>
2252 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2253
2254 void set_basic_auth(const std::string &username, const std::string &password);
2255 void set_bearer_token_auth(const std::string &token);
2256
2257 void set_keep_alive(bool on);
2258 void set_follow_location(bool on);
2259
2260 void set_path_encode(bool on);
2261
2262 void set_compress(bool on);
2263
2264 void set_decompress(bool on);
2265
2266 void set_payload_max_length(size_t length);
2267
2268 void set_interface(const std::string &intf);
2269
2270 void set_proxy(const std::string &host, int port);
2271 void set_proxy_basic_auth(const std::string &username,
2272 const std::string &password);
2273 void set_proxy_bearer_token_auth(const std::string &token);
2274 void set_no_proxy(const std::vector<std::string> &patterns);
2275
2276 void set_logger(Logger logger);
2277 void set_error_logger(ErrorLogger error_logger);
2278
2279protected:
2280 struct Socket {
2282
2283 // For Mbed TLS compatibility: start_time for request timeout tracking
2284 std::chrono::time_point<std::chrono::steady_clock> start_time_;
2285
2286 bool is_open() const { return sock != INVALID_SOCKET; }
2287
2288#ifdef CPPHTTPLIB_SSL_ENABLED
2289 tls::session_t ssl = nullptr;
2290#endif
2291 };
2292
2293 virtual bool create_and_connect_socket(Socket &socket, Error &error);
2294 virtual bool ensure_socket_connection(Socket &socket, Error &error);
2295 virtual bool setup_proxy_connection(
2296 Socket &socket,
2297 std::chrono::time_point<std::chrono::steady_clock> start_time,
2298 Response &res, bool &success, Error &error);
2299
2300 bool is_proxy_enabled_for_host(const std::string &host) const;
2301
2302 // All of:
2303 // shutdown_ssl
2304 // shutdown_socket
2305 // close_socket
2306 // disconnect
2307 // should ONLY be called when socket_mutex_ is locked, and only when
2308 // no other thread is using the socket.
2309 virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
2310 void shutdown_socket(Socket &socket) const;
2311 void close_socket(Socket &socket);
2312 void disconnect(bool gracefully);
2313
2314 bool process_request(Stream &strm, Request &req, Response &res,
2315 bool close_connection, Error &error);
2316
2317 bool write_content_with_provider(Stream &strm, const Request &req,
2318 Error &error) const;
2319
2320 void copy_settings(const ClientImpl &rhs);
2321
2322 void output_log(const Request &req, const Response &res) const;
2323 void output_error_log(const Error &err, const Request *req) const;
2324
2325 // Socket endpoint information
2326 const std::string host_;
2327 const int port_;
2328
2329 // Current open socket
2331 mutable std::mutex socket_mutex_;
2332 std::recursive_mutex request_mutex_;
2333
2334 // These are all protected under socket_mutex
2336 std::thread::id socket_requests_are_from_thread_ = std::thread::id();
2338
2339 // Hostname-IP map
2340 std::map<std::string, std::string> addr_map_;
2341
2342 // Default headers
2344
2345 // Header writer
2346 std::function<ssize_t(Stream &, Headers &)> header_writer_ =
2348
2349 // Settings
2351 std::string client_key_path_;
2352
2360
2364
2365 bool keep_alive_ = false;
2366 bool follow_location_ = false;
2367
2368 bool path_encode_ = true;
2369
2370 int address_family_ = AF_UNSPEC;
2374
2375 bool compress_ = false;
2376 bool decompress_ = true;
2377
2380
2381 std::string interface_;
2382
2383 std::string proxy_host_;
2384 int proxy_port_ = -1;
2385
2389
2390 std::vector<detail::NoProxyEntry> no_proxy_entries_;
2391
2393 mutable bool host_normalized_valid_ = false;
2394
2395 mutable std::mutex logger_mutex_;
2398
2399private:
2400 bool send_(Request &req, Response &res, Error &error);
2401 Result send_(Request &&req);
2402
2403 socket_t create_client_socket(Error &error) const;
2404 bool read_response_line(Stream &strm, const Request &req, Response &res,
2405 bool skip_100_continue = true) const;
2406 bool write_request(Stream &strm, Request &req, bool close_connection,
2407 Error &error, bool skip_body = false);
2408 bool write_request_body(Stream &strm, Request &req, Error &error);
2409 void prepare_default_headers(Request &r, bool for_stream,
2410 const std::string &ct);
2411 bool redirect(Request &req, Response &res, Error &error);
2412 bool create_redirect_client(const std::string &scheme,
2413 const std::string &host, int port, Request &req,
2414 Response &res, const std::string &path,
2415 const std::string &location, Error &error);
2416 template <typename ClientType> void setup_redirect_client(ClientType &client);
2417 bool handle_request(Stream &strm, Request &req, Response &res,
2418 bool close_connection, Error &error);
2419 std::unique_ptr<Response> send_with_content_provider_and_receiver(
2420 Request &req, const char *body, size_t content_length,
2421 ContentProvider content_provider,
2422 ContentProviderWithoutLength content_provider_without_length,
2423 const std::string &content_type, ContentReceiver content_receiver,
2424 Error &error);
2425 Result send_with_content_provider_and_receiver(
2426 const std::string &method, const std::string &path,
2427 const Headers &headers, const char *body, size_t content_length,
2428 ContentProvider content_provider,
2429 ContentProviderWithoutLength content_provider_without_length,
2430 const std::string &content_type, ContentReceiver content_receiver,
2431 UploadProgress progress);
2432 ContentProviderWithoutLength get_multipart_content_provider(
2433 const std::string &boundary, const UploadFormDataItems &items,
2434 const FormDataProviderItems &provider_items) const;
2435
2436 virtual bool
2437 process_socket(const Socket &socket,
2438 std::chrono::time_point<std::chrono::steady_clock> start_time,
2439 std::function<bool(Stream &strm)> callback);
2440 virtual bool is_ssl() const;
2441
2442 void transfer_socket_ownership_to_handle(StreamHandle &handle);
2443
2444#ifdef CPPHTTPLIB_SSL_ENABLED
2445public:
2446 void set_digest_auth(const std::string &username,
2447 const std::string &password);
2448 void set_proxy_digest_auth(const std::string &username,
2449 const std::string &password);
2450 void set_ca_cert_path(const std::string &ca_cert_file_path,
2451 const std::string &ca_cert_dir_path = std::string());
2452 void enable_server_certificate_verification(bool enabled);
2453 void enable_server_hostname_verification(bool enabled);
2454
2455protected:
2456 std::string digest_auth_username_;
2457 std::string digest_auth_password_;
2458 std::string proxy_digest_auth_username_;
2459 std::string proxy_digest_auth_password_;
2460 std::string ca_cert_file_path_;
2461 std::string ca_cert_dir_path_;
2462 bool server_certificate_verification_ = true;
2463 bool server_hostname_verification_ = true;
2464 std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
2465 int last_ssl_error_ = 0;
2466 uint64_t last_backend_error_ = 0;
2467#endif
2468};
2469
2470class Client {
2471public:
2472 // Universal interface
2473 explicit Client(const std::string &scheme_host_port);
2474
2475 explicit Client(const std::string &scheme_host_port,
2476 const std::string &client_cert_path,
2477 const std::string &client_key_path);
2478
2479 // HTTP only interface
2480 explicit Client(const std::string &host, int port);
2481
2482 explicit Client(const std::string &host, int port,
2483 const std::string &client_cert_path,
2484 const std::string &client_key_path);
2485
2486 Client(Client &&) = default;
2487 Client &operator=(Client &&) = default;
2488
2490
2491 bool is_valid() const;
2492
2493 // clang-format off
2494 Result Get(const std::string &path, DownloadProgress progress = nullptr);
2495 Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2496 Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2497 Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2498 Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2499 Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2500 Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2501 Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2502 Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2503
2504 Result Head(const std::string &path);
2505 Result Head(const std::string &path, const Headers &headers);
2506
2507 Result Post(const std::string &path);
2508 Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2509 Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2510 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2511 Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2512 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2513 Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2514 Result Post(const std::string &path, const Params &params);
2515 Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2516 Result Post(const std::string &path, const Headers &headers);
2517 Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2518 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2519 Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2520 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);
2521 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2522 Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2523 Result Post(const std::string &path, const Headers &headers, const Params &params);
2524 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2525 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2526 Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2527 Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2528
2529 Result Put(const std::string &path);
2530 Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2531 Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2532 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2533 Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2534 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2535 Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2536 Result Put(const std::string &path, const Params &params);
2537 Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2538 Result Put(const std::string &path, const Headers &headers);
2539 Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2540 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2541 Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2542 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);
2543 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2544 Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2545 Result Put(const std::string &path, const Headers &headers, const Params &params);
2546 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2547 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2548 Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2549 Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2550
2551 Result Patch(const std::string &path);
2552 Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2553 Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2554 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2555 Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2556 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2557 Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2558 Result Patch(const std::string &path, const Params &params);
2559 Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2560 Result Patch(const std::string &path, const Headers &headers);
2561 Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2562 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2563 Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2564 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);
2565 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2566 Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2567 Result Patch(const std::string &path, const Headers &headers, const Params &params);
2568 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2569 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2570 Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2571 Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2572
2573 Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2574 Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2575 Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2576 Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2577 Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2578 Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2579 Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2580 Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2581
2582 Result Options(const std::string &path);
2583 Result Options(const std::string &path, const Headers &headers);
2584 // clang-format on
2585
2586 // Streaming API: Open a stream for reading response body incrementally
2587 // Socket ownership is transferred to StreamHandle for true streaming
2588 // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2589 ClientImpl::StreamHandle open_stream(const std::string &method,
2590 const std::string &path,
2591 const Params &params = {},
2592 const Headers &headers = {},
2593 const std::string &body = {},
2594 const std::string &content_type = {});
2595
2596 bool send(Request &req, Response &res, Error &error);
2597 Result send(const Request &req);
2598
2599 void stop();
2600
2601 std::string host() const;
2602 int port() const;
2603
2604 size_t is_socket_open() const;
2605 socket_t socket() const;
2606
2607 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2608
2609 void set_default_headers(Headers headers);
2610
2611 void
2612 set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2613
2614 void set_address_family(int family);
2615 void set_tcp_nodelay(bool on);
2616 void set_socket_options(SocketOptions socket_options);
2617
2618 void set_connection_timeout(time_t sec, time_t usec = 0);
2619 template <class Rep, class Period>
2620 void
2621 set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2622
2623 void set_read_timeout(time_t sec, time_t usec = 0);
2624 template <class Rep, class Period>
2625 void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2626
2627 void set_write_timeout(time_t sec, time_t usec = 0);
2628 template <class Rep, class Period>
2629 void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2630
2631 void set_max_timeout(time_t msec);
2632 template <class Rep, class Period>
2633 void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2634
2635 void set_basic_auth(const std::string &username, const std::string &password);
2636 void set_bearer_token_auth(const std::string &token);
2637
2638 void set_keep_alive(bool on);
2639 void set_follow_location(bool on);
2640
2641 void set_path_encode(bool on);
2642
2643 void set_compress(bool on);
2644
2645 void set_decompress(bool on);
2646
2647 void set_payload_max_length(size_t length);
2648
2649 void set_interface(const std::string &intf);
2650
2651 void set_proxy(const std::string &host, int port);
2652 void set_proxy_basic_auth(const std::string &username,
2653 const std::string &password);
2654 void set_proxy_bearer_token_auth(const std::string &token);
2655 void set_no_proxy(const std::vector<std::string> &patterns);
2656 void set_logger(Logger logger);
2657 void set_error_logger(ErrorLogger error_logger);
2658
2659private:
2660 std::unique_ptr<ClientImpl> cli_;
2661
2662#ifdef CPPHTTPLIB_SSL_ENABLED
2663public:
2664 void set_digest_auth(const std::string &username,
2665 const std::string &password);
2666 void set_proxy_digest_auth(const std::string &username,
2667 const std::string &password);
2668 void enable_server_certificate_verification(bool enabled);
2669 void enable_server_hostname_verification(bool enabled);
2670 void set_ca_cert_path(const std::string &ca_cert_file_path,
2671 const std::string &ca_cert_dir_path = std::string());
2672
2673 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2674 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2675
2676 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2677
2678 void set_session_verifier(
2679 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2680
2681 tls::ctx_t tls_context() const;
2682
2683#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2684 void enable_windows_certificate_verification(bool enabled);
2685#endif
2686
2687private:
2688 bool is_ssl_ = false;
2689#endif
2690};
2691
2692#ifdef CPPHTTPLIB_SSL_ENABLED
2693class SSLServer : public Server {
2694public:
2695 SSLServer(const char *cert_path, const char *private_key_path,
2696 const char *client_ca_cert_file_path = nullptr,
2697 const char *client_ca_cert_dir_path = nullptr,
2698 const char *private_key_password = nullptr);
2699
2700 struct PemMemory {
2701 const char *cert_pem;
2702 size_t cert_pem_len;
2703 const char *key_pem;
2704 size_t key_pem_len;
2705 const char *client_ca_pem;
2706 size_t client_ca_pem_len;
2707 const char *private_key_password;
2708 };
2709 explicit SSLServer(const PemMemory &pem);
2710
2711 // The callback receives the ctx_t handle which can be cast to the
2712 // appropriate backend type (SSL_CTX* for OpenSSL,
2713 // tls::impl::MbedTlsContext* for Mbed TLS)
2714 explicit SSLServer(const tls::ContextSetupCallback &setup_callback);
2715
2716 ~SSLServer() override;
2717
2718 bool is_valid() const override;
2719
2720 bool update_certs_pem(const char *cert_pem, const char *key_pem,
2721 const char *client_ca_pem = nullptr,
2722 const char *password = nullptr);
2723
2724 tls::ctx_t tls_context() const { return ctx_; }
2725
2726 int ssl_last_error() const { return last_ssl_error_; }
2727
2728private:
2729 bool process_and_close_socket(socket_t sock) override;
2730
2731 tls::ctx_t ctx_ = nullptr;
2732 std::mutex ctx_mutex_;
2733
2734 int last_ssl_error_ = 0;
2735};
2736
2737class SSLClient final : public ClientImpl {
2738public:
2739 explicit SSLClient(const std::string &host);
2740
2741 explicit SSLClient(const std::string &host, int port);
2742
2743 explicit SSLClient(const std::string &host, int port,
2744 const std::string &client_cert_path,
2745 const std::string &client_key_path,
2746 const std::string &private_key_password = std::string());
2747
2748 struct PemMemory {
2749 const char *cert_pem;
2750 size_t cert_pem_len;
2751 const char *key_pem;
2752 size_t key_pem_len;
2753 const char *private_key_password;
2754 };
2755 explicit SSLClient(const std::string &host, int port, const PemMemory &pem);
2756
2757 ~SSLClient() override;
2758
2759 bool is_valid() const override;
2760
2761 void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2762 void load_ca_cert_store(const char *ca_cert, std::size_t size);
2763
2764 void set_server_certificate_verifier(tls::VerifyCallback verifier);
2765
2766 // Post-handshake session verifier (backend-independent)
2767 void set_session_verifier(
2768 std::function<SSLVerifierResponse(tls::session_t)> verifier);
2769
2770 tls::ctx_t tls_context() const { return ctx_; }
2771
2772#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2773 void enable_windows_certificate_verification(bool enabled);
2774#endif
2775
2776private:
2777 bool create_and_connect_socket(Socket &socket, Error &error) override;
2778 bool ensure_socket_connection(Socket &socket, Error &error) override;
2779 void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
2780 void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
2781
2782 bool
2783 process_socket(const Socket &socket,
2784 std::chrono::time_point<std::chrono::steady_clock> start_time,
2785 std::function<bool(Stream &strm)> callback) override;
2786 bool is_ssl() const override;
2787
2788 bool setup_proxy_connection(
2789 Socket &socket,
2790 std::chrono::time_point<std::chrono::steady_clock> start_time,
2791 Response &res, bool &success, Error &error) override;
2792 bool connect_with_proxy(
2793 Socket &sock,
2794 std::chrono::time_point<std::chrono::steady_clock> start_time,
2795 Response &res, bool &success, Error &error);
2796 bool initialize_ssl(Socket &socket, Error &error);
2797
2798 void init_ctx();
2799 void reset_ctx_on_error();
2800
2801 bool load_certs();
2802
2803 tls::ctx_t ctx_ = nullptr;
2804 std::mutex ctx_mutex_;
2805 std::once_flag initialize_cert_;
2806
2807 long verify_result_ = 0;
2808
2809 std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
2810
2811#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2812 bool enable_windows_cert_verification_ = true;
2813#endif
2814
2815 friend class ClientImpl;
2816
2817#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2818private:
2819 bool verify_host(X509 *server_cert) const;
2820 bool verify_host_with_subject_alt_name(X509 *server_cert) const;
2821 bool verify_host_with_common_name(X509 *server_cert) const;
2822#endif
2823};
2824#endif // CPPHTTPLIB_SSL_ENABLED
2825
2826namespace detail {
2827
2828template <typename T, typename U>
2829inline void duration_to_sec_and_usec(const T &duration, U callback) {
2830 auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
2831 auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
2832 duration - std::chrono::seconds(sec))
2833 .count();
2834 callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
2835}
2836
2837template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2838 return N - 1;
2839}
2840
2841inline bool is_numeric(const std::string &str) {
2842 return !str.empty() &&
2843 std::all_of(str.cbegin(), str.cend(),
2844 [](unsigned char c) { return std::isdigit(c); });
2845}
2846
2847inline size_t get_header_value_u64(const Headers &headers,
2848 const std::string &key, size_t def,
2849 size_t id, bool &is_invalid_value) {
2850 is_invalid_value = false;
2851 auto rng = headers.equal_range(key);
2852 auto it = rng.first;
2853 std::advance(it, static_cast<ssize_t>(id));
2854 if (it != rng.second) {
2855 if (is_numeric(it->second)) {
2856 return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
2857 } else {
2858 is_invalid_value = true;
2859 }
2860 }
2861 return def;
2862}
2863
2864inline size_t get_header_value_u64(const Headers &headers,
2865 const std::string &key, size_t def,
2866 size_t id) {
2867 auto dummy = false;
2868 return get_header_value_u64(headers, key, def, id, dummy);
2869}
2870
2871} // namespace detail
2872
2873template <class Rep, class Period>
2874inline Server &
2875Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2877 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2878 return *this;
2879}
2880
2881template <class Rep, class Period>
2882inline Server &
2883Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2885 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2886 return *this;
2887}
2888
2889template <class Rep, class Period>
2890inline Server &
2891Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
2893 duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
2894 return *this;
2895}
2896
2897template <class Rep, class Period>
2899 const std::chrono::duration<Rep, Period> &duration) {
2900 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
2901 set_connection_timeout(sec, usec);
2902 });
2903}
2904
2905template <class Rep, class Period>
2907 const std::chrono::duration<Rep, Period> &duration) {
2909 duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2910}
2911
2912template <class Rep, class Period>
2914 const std::chrono::duration<Rep, Period> &duration) {
2916 duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2917}
2918
2919template <class Rep, class Period>
2921 const std::chrono::duration<Rep, Period> &duration) {
2922 auto msec =
2923 std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
2924 set_max_timeout(msec);
2925}
2926
2927template <class Rep, class Period>
2929 const std::chrono::duration<Rep, Period> &duration) {
2930 cli_->set_connection_timeout(duration);
2931}
2932
2933template <class Rep, class Period>
2934inline void
2935Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2936 cli_->set_read_timeout(duration);
2937}
2938
2939template <class Rep, class Period>
2940inline void
2941Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2942 cli_->set_write_timeout(duration);
2943}
2944
2945inline void Client::set_max_timeout(time_t msec) {
2946 cli_->set_max_timeout(msec);
2947}
2948
2949template <class Rep, class Period>
2950inline void
2951Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
2952 cli_->set_max_timeout(duration);
2953}
2954
2955/*
2956 * Forward declarations and types that will be part of the .h file if split into
2957 * .h + .cc.
2958 */
2959
2960std::string hosted_at(const std::string &hostname);
2961
2962void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
2963
2964// JavaScript-style URL encoding/decoding functions
2965std::string encode_uri_component(const std::string &value);
2966std::string encode_uri(const std::string &value);
2967std::string decode_uri_component(const std::string &value);
2968std::string decode_uri(const std::string &value);
2969
2970// RFC 3986 compliant URL component encoding/decoding functions
2971std::string encode_path_component(const std::string &component);
2972std::string decode_path_component(const std::string &component);
2973std::string encode_query_component(const std::string &component,
2974 bool space_as_plus = true);
2975std::string decode_query_component(const std::string &component,
2976 bool plus_as_space = true);
2977
2978std::string sanitize_filename(const std::string &filename);
2979
2980std::string append_query_params(const std::string &path, const Params &params);
2981
2982std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
2983
2984std::pair<std::string, std::string>
2985make_basic_authentication_header(const std::string &username,
2986 const std::string &password,
2987 bool is_proxy = false);
2988
2989namespace detail {
2990
2991#if defined(_WIN32)
2992inline std::wstring u8string_to_wstring(const char *s) {
2993 if (!s) { return std::wstring(); }
2994
2995 auto len = static_cast<int>(strlen(s));
2996 if (!len) { return std::wstring(); }
2997
2998 auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
2999 if (!wlen) { return std::wstring(); }
3000
3001 std::wstring ws;
3002 ws.resize(wlen);
3003 wlen = ::MultiByteToWideChar(
3004 CP_UTF8, 0, s, len,
3005 const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
3006 if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
3007 return ws;
3008}
3009#endif
3010
3011struct FileStat {
3012 FileStat(const std::string &path);
3013 bool is_file() const;
3014 bool is_dir() const;
3015 time_t mtime() const;
3016 size_t size() const;
3017
3018private:
3019#if defined(_WIN32)
3020 struct _stat st_;
3021#else
3022 struct stat st_;
3023#endif
3024 int ret_ = -1;
3025};
3026
3027std::string make_host_and_port_string(const std::string &host, int port,
3028 bool is_ssl);
3029
3030std::string trim_copy(const std::string &s);
3031
3032void divide(
3033 const char *data, std::size_t size, char d,
3034 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3035 fn);
3036
3037void divide(
3038 const std::string &str, char d,
3039 std::function<void(const char *, std::size_t, const char *, std::size_t)>
3040 fn);
3041
3042void split(const char *b, const char *e, char d,
3043 std::function<void(const char *, const char *)> fn);
3044
3045void split(const char *b, const char *e, char d, size_t m,
3046 std::function<void(const char *, const char *)> fn);
3047
3049 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3050 time_t write_timeout_sec, time_t write_timeout_usec,
3051 time_t max_timeout_msec,
3052 std::chrono::time_point<std::chrono::steady_clock> start_time,
3053 std::function<bool(Stream &)> callback);
3054
3055socket_t create_client_socket(const std::string &host, const std::string &ip,
3056 int port, int address_family, bool tcp_nodelay,
3057 bool ipv6_v6only, SocketOptions socket_options,
3058 time_t connection_timeout_sec,
3059 time_t connection_timeout_usec,
3060 time_t read_timeout_sec, time_t read_timeout_usec,
3061 time_t write_timeout_sec,
3062 time_t write_timeout_usec,
3063 const std::string &intf, Error &error);
3064
3065const char *get_header_value(const Headers &headers, const std::string &key,
3066 const char *def, size_t id);
3067
3068std::string params_to_query_str(const Params &params);
3069
3070void parse_query_text(const char *data, std::size_t size, Params &params);
3071
3072void parse_query_text(const std::string &s, Params &params);
3073
3074bool parse_multipart_boundary(const std::string &content_type,
3075 std::string &boundary);
3076
3077bool parse_range_header(const std::string &s, Ranges &ranges);
3078
3079bool parse_accept_header(const std::string &s,
3080 std::vector<std::string> &content_types);
3081
3082ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
3083
3084ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
3085
3086enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
3087
3088EncodingType encoding_type(const Request &req, const Response &res);
3089
3090class BufferStream final : public Stream {
3091public:
3092 BufferStream() = default;
3093 ~BufferStream() override = default;
3094
3095 bool is_readable() const override;
3096 bool wait_readable() const override;
3097 bool wait_writable() const override;
3098 ssize_t read(char *ptr, size_t size) override;
3099 ssize_t write(const char *ptr, size_t size) override;
3100 void get_remote_ip_and_port(std::string &ip, int &port) const override;
3101 void get_local_ip_and_port(std::string &ip, int &port) const override;
3102 socket_t socket() const override;
3103 time_t duration() const override;
3104
3105 const std::string &get_buffer() const;
3106
3107private:
3108 std::string buffer;
3109 size_t position = 0;
3110};
3111
3113public:
3114 virtual ~compressor() = default;
3115
3116 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3117 virtual bool compress(const char *data, size_t data_length, bool last,
3118 Callback callback) = 0;
3119};
3120
3122public:
3123 virtual ~decompressor() = default;
3124
3125 virtual bool is_valid() const = 0;
3126
3127 typedef std::function<bool(const char *data, size_t data_len)> Callback;
3128 virtual bool decompress(const char *data, size_t data_length,
3129 Callback callback) = 0;
3130};
3131
3132class nocompressor final : public compressor {
3133public:
3134 ~nocompressor() override = default;
3135
3136 bool compress(const char *data, size_t data_length, bool /*last*/,
3137 Callback callback) override;
3138};
3139
3140#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3141class gzip_compressor final : public compressor {
3142public:
3143 gzip_compressor();
3144 ~gzip_compressor() override;
3145
3146 bool compress(const char *data, size_t data_length, bool last,
3147 Callback callback) override;
3148
3149private:
3150 bool is_valid_ = false;
3151 z_stream strm_;
3152};
3153
3154class gzip_decompressor final : public decompressor {
3155public:
3156 gzip_decompressor();
3157 ~gzip_decompressor() override;
3158
3159 bool is_valid() const override;
3160
3161 bool decompress(const char *data, size_t data_length,
3162 Callback callback) override;
3163
3164private:
3165 bool is_valid_ = false;
3166 z_stream strm_;
3167};
3168#endif
3169
3170#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3171class brotli_compressor final : public compressor {
3172public:
3173 brotli_compressor();
3174 ~brotli_compressor();
3175
3176 bool compress(const char *data, size_t data_length, bool last,
3177 Callback callback) override;
3178
3179private:
3180 BrotliEncoderState *state_ = nullptr;
3181};
3182
3183class brotli_decompressor final : public decompressor {
3184public:
3185 brotli_decompressor();
3186 ~brotli_decompressor();
3187
3188 bool is_valid() const override;
3189
3190 bool decompress(const char *data, size_t data_length,
3191 Callback callback) override;
3192
3193private:
3194 BrotliDecoderResult decoder_r;
3195 BrotliDecoderState *decoder_s = nullptr;
3196};
3197#endif
3198
3199#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3200class zstd_compressor : public compressor {
3201public:
3202 zstd_compressor();
3203 ~zstd_compressor();
3204
3205 bool compress(const char *data, size_t data_length, bool last,
3206 Callback callback) override;
3207
3208private:
3209 ZSTD_CCtx *ctx_ = nullptr;
3210};
3211
3212class zstd_decompressor : public decompressor {
3213public:
3214 zstd_decompressor();
3215 ~zstd_decompressor();
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 ZSTD_DCtx *ctx_ = nullptr;
3224};
3225#endif
3226
3227// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
3228// to store data. The call can set memory on stack for performance.
3230public:
3231 stream_line_reader(Stream &strm, char *fixed_buffer,
3232 size_t fixed_buffer_size);
3233 const char *ptr() const;
3234 size_t size() const;
3235 bool end_with_crlf() const;
3236 bool getline();
3237
3238private:
3239 void append(char c);
3240
3241 Stream &strm_;
3242 char *fixed_buffer_;
3243 const size_t fixed_buffer_size_;
3244 size_t fixed_buffer_used_size_ = 0;
3245 std::string growable_buffer_;
3246};
3247
3248bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
3249 const Headers &src_headers);
3250
3254 bool finished = false;
3255 char line_buf[64];
3258
3259 explicit ChunkedDecoder(Stream &s);
3260
3261 ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset,
3262 size_t &out_chunk_total);
3263
3264 bool parse_trailers_into(Headers &dest, const Headers &src_headers);
3265};
3266
3267class mmap {
3268public:
3269 mmap(const char *path);
3270 ~mmap();
3271
3272 bool open(const char *path);
3273 void close();
3274
3275 bool is_open() const;
3276 size_t size() const;
3277 const char *data() const;
3278
3279private:
3280#if defined(_WIN32)
3281 HANDLE hFile_ = NULL;
3282 HANDLE hMapping_ = NULL;
3283#else
3284 int fd_ = -1;
3285#endif
3286 size_t size_ = 0;
3287 void *addr_ = nullptr;
3288 bool is_open_empty_file = false;
3289};
3290
3291// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
3292namespace fields {
3293
3294bool is_token_char(char c);
3295bool is_token(const std::string &s);
3296bool is_field_name(const std::string &s);
3297bool is_vchar(char c);
3298bool is_obs_text(char c);
3299bool is_field_vchar(char c);
3300bool is_field_content(const std::string &s);
3301bool is_field_value(const std::string &s);
3302
3303} // namespace fields
3304} // namespace detail
3305
3306/*
3307 * TLS Abstraction Layer Declarations
3308 */
3309
3310#ifdef CPPHTTPLIB_SSL_ENABLED
3311// TLS abstraction layer - backend-specific type declarations
3312#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
3313namespace tls {
3314namespace impl {
3315
3316// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own
3317// cert/key). This struct is accessible via tls::impl for use in SSL context
3318// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
3319struct MbedTlsContext {
3320 mbedtls_ssl_config conf;
3321 mbedtls_entropy_context entropy;
3322 mbedtls_ctr_drbg_context ctr_drbg;
3323 mbedtls_x509_crt ca_chain;
3324 mbedtls_x509_crt own_cert;
3325 mbedtls_pk_context own_key;
3326 bool is_server = false;
3327 bool verify_client = false;
3328 bool has_verify_callback = false;
3329
3330 MbedTlsContext();
3331 ~MbedTlsContext();
3332
3333 MbedTlsContext(const MbedTlsContext &) = delete;
3334 MbedTlsContext &operator=(const MbedTlsContext &) = delete;
3335};
3336
3337} // namespace impl
3338} // namespace tls
3339#endif
3340
3341#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
3342namespace tls {
3343namespace impl {
3344
3345// wolfSSL context wrapper (holds WOLFSSL_CTX and related state).
3346// This struct is accessible via tls::impl for use in SSL context
3347// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*).
3348struct WolfSSLContext {
3349 WOLFSSL_CTX *ctx = nullptr;
3350 bool is_server = false;
3351 bool verify_client = false;
3352 bool has_verify_callback = false;
3353 std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
3354
3355 WolfSSLContext();
3356 ~WolfSSLContext();
3357
3358 WolfSSLContext(const WolfSSLContext &) = delete;
3359 WolfSSLContext &operator=(const WolfSSLContext &) = delete;
3360};
3361
3362// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx
3363struct WolfSSLCAStore {
3364 std::string pem_data;
3365};
3366
3367} // namespace impl
3368} // namespace tls
3369#endif
3370
3371#endif // CPPHTTPLIB_SSL_ENABLED
3372
3373namespace stream {
3374
3375class Result {
3376public:
3377 Result();
3378 explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192);
3379 Result(Result &&other) noexcept;
3380 Result &operator=(Result &&other) noexcept;
3381 Result(const Result &) = delete;
3382 Result &operator=(const Result &) = delete;
3383
3384 // Response info
3385 bool is_valid() const;
3386 explicit operator bool() const;
3387 int status() const;
3388 const Headers &headers() const;
3389 std::string get_header_value(const std::string &key,
3390 const char *def = "") const;
3391 bool has_header(const std::string &key) const;
3392 Error error() const;
3393 Error read_error() const;
3394 bool has_read_error() const;
3395
3396 // Stream reading
3397 bool next();
3398 const char *data() const;
3399 size_t size() const;
3400 std::string read_all();
3401
3402private:
3404 std::string buffer_;
3405 size_t current_size_ = 0;
3406 size_t chunk_size_;
3407 bool finished_ = false;
3408};
3409
3410// GET
3411template <typename ClientType>
3412inline Result Get(ClientType &cli, const std::string &path,
3413 size_t chunk_size = 8192) {
3414 return Result{cli.open_stream("GET", path), chunk_size};
3415}
3416
3417template <typename ClientType>
3418inline Result Get(ClientType &cli, const std::string &path,
3419 const Headers &headers, size_t chunk_size = 8192) {
3420 return Result{cli.open_stream("GET", path, {}, headers), chunk_size};
3421}
3422
3423template <typename ClientType>
3424inline Result Get(ClientType &cli, const std::string &path,
3425 const Params &params, size_t chunk_size = 8192) {
3426 return Result{cli.open_stream("GET", path, params), chunk_size};
3427}
3428
3429template <typename ClientType>
3430inline Result Get(ClientType &cli, const std::string &path,
3431 const Params &params, const Headers &headers,
3432 size_t chunk_size = 8192) {
3433 return Result{cli.open_stream("GET", path, params, headers), chunk_size};
3434}
3435
3436// POST
3437template <typename ClientType>
3438inline Result Post(ClientType &cli, const std::string &path,
3439 const std::string &body, const std::string &content_type,
3440 size_t chunk_size = 8192) {
3441 return Result{cli.open_stream("POST", path, {}, {}, body, content_type),
3442 chunk_size};
3443}
3444
3445template <typename ClientType>
3446inline Result Post(ClientType &cli, const std::string &path,
3447 const Headers &headers, const std::string &body,
3448 const std::string &content_type, size_t chunk_size = 8192) {
3449 return Result{cli.open_stream("POST", path, {}, headers, body, content_type),
3450 chunk_size};
3451}
3452
3453template <typename ClientType>
3454inline Result Post(ClientType &cli, const std::string &path,
3455 const Params &params, const std::string &body,
3456 const std::string &content_type, size_t chunk_size = 8192) {
3457 return Result{cli.open_stream("POST", path, params, {}, body, content_type),
3458 chunk_size};
3459}
3460
3461template <typename ClientType>
3462inline Result Post(ClientType &cli, const std::string &path,
3463 const Params &params, const Headers &headers,
3464 const std::string &body, const std::string &content_type,
3465 size_t chunk_size = 8192) {
3466 return Result{
3467 cli.open_stream("POST", path, params, headers, body, content_type),
3468 chunk_size};
3469}
3470
3471// PUT
3472template <typename ClientType>
3473inline Result Put(ClientType &cli, const std::string &path,
3474 const std::string &body, const std::string &content_type,
3475 size_t chunk_size = 8192) {
3476 return Result{cli.open_stream("PUT", path, {}, {}, body, content_type),
3477 chunk_size};
3478}
3479
3480template <typename ClientType>
3481inline Result Put(ClientType &cli, const std::string &path,
3482 const Headers &headers, const std::string &body,
3483 const std::string &content_type, size_t chunk_size = 8192) {
3484 return Result{cli.open_stream("PUT", path, {}, headers, body, content_type),
3485 chunk_size};
3486}
3487
3488template <typename ClientType>
3489inline Result Put(ClientType &cli, const std::string &path,
3490 const Params &params, const std::string &body,
3491 const std::string &content_type, size_t chunk_size = 8192) {
3492 return Result{cli.open_stream("PUT", path, params, {}, body, content_type),
3493 chunk_size};
3494}
3495
3496template <typename ClientType>
3497inline Result Put(ClientType &cli, const std::string &path,
3498 const Params &params, const Headers &headers,
3499 const std::string &body, const std::string &content_type,
3500 size_t chunk_size = 8192) {
3501 return Result{
3502 cli.open_stream("PUT", path, params, headers, body, content_type),
3503 chunk_size};
3504}
3505
3506// PATCH
3507template <typename ClientType>
3508inline Result Patch(ClientType &cli, const std::string &path,
3509 const std::string &body, const std::string &content_type,
3510 size_t chunk_size = 8192) {
3511 return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type),
3512 chunk_size};
3513}
3514
3515template <typename ClientType>
3516inline Result Patch(ClientType &cli, const std::string &path,
3517 const Headers &headers, const std::string &body,
3518 const std::string &content_type, size_t chunk_size = 8192) {
3519 return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type),
3520 chunk_size};
3521}
3522
3523template <typename ClientType>
3524inline Result Patch(ClientType &cli, const std::string &path,
3525 const Params &params, const std::string &body,
3526 const std::string &content_type, size_t chunk_size = 8192) {
3527 return Result{cli.open_stream("PATCH", path, params, {}, body, content_type),
3528 chunk_size};
3529}
3530
3531template <typename ClientType>
3532inline Result Patch(ClientType &cli, const std::string &path,
3533 const Params &params, const Headers &headers,
3534 const std::string &body, const std::string &content_type,
3535 size_t chunk_size = 8192) {
3536 return Result{
3537 cli.open_stream("PATCH", path, params, headers, body, content_type),
3538 chunk_size};
3539}
3540
3541// DELETE
3542template <typename ClientType>
3543inline Result Delete(ClientType &cli, const std::string &path,
3544 size_t chunk_size = 8192) {
3545 return Result{cli.open_stream("DELETE", path), chunk_size};
3546}
3547
3548template <typename ClientType>
3549inline Result Delete(ClientType &cli, const std::string &path,
3550 const Headers &headers, size_t chunk_size = 8192) {
3551 return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size};
3552}
3553
3554template <typename ClientType>
3555inline Result Delete(ClientType &cli, const std::string &path,
3556 const std::string &body, const std::string &content_type,
3557 size_t chunk_size = 8192) {
3558 return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type),
3559 chunk_size};
3560}
3561
3562template <typename ClientType>
3563inline Result Delete(ClientType &cli, const std::string &path,
3564 const Headers &headers, const std::string &body,
3565 const std::string &content_type,
3566 size_t chunk_size = 8192) {
3567 return Result{
3568 cli.open_stream("DELETE", path, {}, headers, body, content_type),
3569 chunk_size};
3570}
3571
3572template <typename ClientType>
3573inline Result Delete(ClientType &cli, const std::string &path,
3574 const Params &params, size_t chunk_size = 8192) {
3575 return Result{cli.open_stream("DELETE", path, params), chunk_size};
3576}
3577
3578template <typename ClientType>
3579inline Result Delete(ClientType &cli, const std::string &path,
3580 const Params &params, const Headers &headers,
3581 size_t chunk_size = 8192) {
3582 return Result{cli.open_stream("DELETE", path, params, headers), chunk_size};
3583}
3584
3585template <typename ClientType>
3586inline Result Delete(ClientType &cli, const std::string &path,
3587 const Params &params, const std::string &body,
3588 const std::string &content_type,
3589 size_t chunk_size = 8192) {
3590 return Result{cli.open_stream("DELETE", path, params, {}, body, content_type),
3591 chunk_size};
3592}
3593
3594template <typename ClientType>
3595inline Result Delete(ClientType &cli, const std::string &path,
3596 const Params &params, const Headers &headers,
3597 const std::string &body, const std::string &content_type,
3598 size_t chunk_size = 8192) {
3599 return Result{
3600 cli.open_stream("DELETE", path, params, headers, body, content_type),
3601 chunk_size};
3602}
3603
3604// HEAD
3605template <typename ClientType>
3606inline Result Head(ClientType &cli, const std::string &path,
3607 size_t chunk_size = 8192) {
3608 return Result{cli.open_stream("HEAD", path), chunk_size};
3609}
3610
3611template <typename ClientType>
3612inline Result Head(ClientType &cli, const std::string &path,
3613 const Headers &headers, size_t chunk_size = 8192) {
3614 return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size};
3615}
3616
3617template <typename ClientType>
3618inline Result Head(ClientType &cli, const std::string &path,
3619 const Params &params, size_t chunk_size = 8192) {
3620 return Result{cli.open_stream("HEAD", path, params), chunk_size};
3621}
3622
3623template <typename ClientType>
3624inline Result Head(ClientType &cli, const std::string &path,
3625 const Params &params, const Headers &headers,
3626 size_t chunk_size = 8192) {
3627 return Result{cli.open_stream("HEAD", path, params, headers), chunk_size};
3628}
3629
3630// OPTIONS
3631template <typename ClientType>
3632inline Result Options(ClientType &cli, const std::string &path,
3633 size_t chunk_size = 8192) {
3634 return Result{cli.open_stream("OPTIONS", path), chunk_size};
3635}
3636
3637template <typename ClientType>
3638inline Result Options(ClientType &cli, const std::string &path,
3639 const Headers &headers, size_t chunk_size = 8192) {
3640 return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size};
3641}
3642
3643template <typename ClientType>
3644inline Result Options(ClientType &cli, const std::string &path,
3645 const Params &params, size_t chunk_size = 8192) {
3646 return Result{cli.open_stream("OPTIONS", path, params), chunk_size};
3647}
3648
3649template <typename ClientType>
3650inline Result Options(ClientType &cli, const std::string &path,
3651 const Params &params, const Headers &headers,
3652 size_t chunk_size = 8192) {
3653 return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size};
3654}
3655
3656} // namespace stream
3657
3658namespace sse {
3659
3661 std::string event; // Event type (default: "message")
3662 std::string data; // Event payload
3663 std::string id; // Event ID for Last-Event-ID header
3664
3665 SSEMessage();
3666 void clear();
3667};
3668
3670public:
3671 using MessageHandler = std::function<void(const SSEMessage &)>;
3672 using ErrorHandler = std::function<void(Error)>;
3673 using OpenHandler = std::function<void()>;
3674
3675 SSEClient(Client &client, const std::string &path);
3676 SSEClient(Client &client, const std::string &path, const Headers &headers);
3677 ~SSEClient();
3678
3679 SSEClient(const SSEClient &) = delete;
3680 SSEClient &operator=(const SSEClient &) = delete;
3681
3682 // Event handlers
3684 SSEClient &on_event(const std::string &type, MessageHandler handler);
3685 SSEClient &on_open(OpenHandler handler);
3689
3690 // Update headers (thread-safe)
3691 SSEClient &set_headers(const Headers &headers);
3692
3693 // State accessors
3694 bool is_connected() const;
3695 const std::string &last_event_id() const;
3696
3697 // Blocking start - runs event loop with auto-reconnect
3698 void start();
3699
3700 // Non-blocking start - runs in background thread
3701 void start_async();
3702
3703 // Stop the client (thread-safe)
3704 void stop();
3705
3706private:
3707 bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
3708 void run_event_loop();
3709 void dispatch_event(const SSEMessage &msg);
3710 bool should_reconnect(int count) const;
3711 void wait_for_reconnect();
3712
3713 // Client and path
3714 Client &client_;
3715 std::string path_;
3716 Headers headers_;
3717 mutable std::mutex headers_mutex_;
3718
3719 // Callbacks
3720 MessageHandler on_message_;
3721 std::map<std::string, MessageHandler> event_handlers_;
3722 OpenHandler on_open_;
3723 ErrorHandler on_error_;
3724
3725 // Configuration
3726 int reconnect_interval_ms_ = 3000;
3727 int max_reconnect_attempts_ = 0; // 0 = unlimited
3728
3729 // State
3730 std::atomic<bool> running_{false};
3731 std::atomic<bool> connected_{false};
3732 std::string last_event_id_;
3733
3734 // Async support
3735 std::thread async_thread_;
3736};
3737
3738} // namespace sse
3739
3740namespace ws {
3741
3742enum class Opcode : uint8_t {
3744 Text = 0x1,
3745 Binary = 0x2,
3746 Close = 0x8,
3747 Ping = 0x9,
3748 Pong = 0xA,
3749};
3750
3764
3765enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 };
3766
3768public:
3769 WebSocket(const WebSocket &) = delete;
3770 WebSocket &operator=(const WebSocket &) = delete;
3771 ~WebSocket();
3772
3773 ReadResult read(std::string &msg);
3774 bool send(const std::string &data);
3775 bool send(const char *data, size_t len);
3777 const std::string &reason = "");
3778 const Request &request() const;
3779 bool is_open() const;
3780
3781private:
3782 friend class httplib::Server;
3783 friend class WebSocketClient;
3784
3785 WebSocket(
3786 Stream &strm, const Request &req, bool is_server,
3787 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3788 int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3789 : strm_(strm), req_(req), is_server_(is_server),
3790 ping_interval_sec_(ping_interval_sec),
3791 max_missed_pongs_(max_missed_pongs) {
3792 start_heartbeat();
3793 }
3794
3795 WebSocket(
3796 std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
3797 time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3798 int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3799 : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
3800 is_server_(is_server), ping_interval_sec_(ping_interval_sec),
3801 max_missed_pongs_(max_missed_pongs) {
3802 start_heartbeat();
3803 }
3804
3805 void start_heartbeat();
3806 bool send_frame(Opcode op, const char *data, size_t len, bool fin = true);
3807
3808 Stream &strm_;
3809 std::unique_ptr<Stream> owned_strm_;
3810 Request req_;
3811 bool is_server_;
3812 time_t ping_interval_sec_;
3813 int max_missed_pongs_;
3814 int unacked_pings_ = 0;
3815 std::atomic<bool> closed_{false};
3816 std::mutex write_mutex_;
3817 std::thread ping_thread_;
3818 std::mutex ping_mutex_;
3819 std::condition_variable ping_cv_;
3820};
3821
3823public:
3824 explicit WebSocketClient(const std::string &scheme_host_port_path,
3825 const Headers &headers = {});
3826
3830
3831 bool is_valid() const;
3832
3833 bool connect();
3834 ReadResult read(std::string &msg);
3835 bool send(const std::string &data);
3836 bool send(const char *data, size_t len);
3838 const std::string &reason = "");
3839 bool is_open() const;
3840 const std::string &subprotocol() const;
3841 void set_read_timeout(time_t sec, time_t usec = 0);
3842 void set_write_timeout(time_t sec, time_t usec = 0);
3843 void set_websocket_ping_interval(time_t sec);
3844 void set_websocket_max_missed_pongs(int count);
3845 void set_tcp_nodelay(bool on);
3846 void set_address_family(int family);
3847 void set_ipv6_v6only(bool on);
3848 void set_socket_options(SocketOptions socket_options);
3849 void set_connection_timeout(time_t sec, time_t usec = 0);
3850 void set_interface(const std::string &intf);
3851 void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
3852
3853#ifdef CPPHTTPLIB_SSL_ENABLED
3854 void set_ca_cert_path(const std::string &path);
3855 void set_ca_cert_store(tls::ca_store_t store);
3856 void enable_server_certificate_verification(bool enabled);
3857#endif
3858
3859private:
3860 void shutdown_and_close();
3861 bool create_stream(std::unique_ptr<Stream> &strm);
3862
3863 std::string host_;
3864 int port_;
3865 std::string path_;
3866 Headers headers_;
3867 std::string subprotocol_;
3868 bool is_valid_ = false;
3869 socket_t sock_ = INVALID_SOCKET;
3870 std::unique_ptr<WebSocket> ws_;
3871 time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND;
3872 time_t read_timeout_usec_ = 0;
3873 time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
3874 time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
3875 time_t websocket_ping_interval_sec_ =
3877 int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
3878 int address_family_ = AF_UNSPEC;
3879 bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
3880 bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
3881 SocketOptions socket_options_ = nullptr;
3882 time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
3883 time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
3884 std::string interface_;
3885
3886 // Hostname-IP map
3887 std::map<std::string, std::string> addr_map_;
3888
3889#ifdef CPPHTTPLIB_SSL_ENABLED
3890 bool is_ssl_ = false;
3891 tls::ctx_t tls_ctx_ = nullptr;
3892 tls::session_t tls_session_ = nullptr;
3893 std::string ca_cert_file_path_;
3894 tls::ca_store_t ca_cert_store_ = nullptr;
3895 bool server_certificate_verification_ = true;
3896#endif
3897};
3898
3899namespace impl {
3900
3901bool is_valid_utf8(const std::string &s);
3902
3903bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload,
3904 bool &fin, bool expect_masked, size_t max_len);
3905
3906} // namespace impl
3907
3908} // namespace ws
3909
3910// ----------------------------------------------------------------------------
3911
3912/*
3913 * Implementation that will be part of the .cc file if split into .h + .cc.
3914 */
3915
3916namespace stream {
3917
3918// stream::Result implementations
3919inline Result::Result() : chunk_size_(8192) {}
3920
3921inline Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size)
3922 : handle_(std::move(handle)), chunk_size_(chunk_size) {}
3923
3924inline Result::Result(Result &&other) noexcept
3925 : handle_(std::move(other.handle_)), buffer_(std::move(other.buffer_)),
3926 current_size_(other.current_size_), chunk_size_(other.chunk_size_),
3927 finished_(other.finished_) {
3928 other.current_size_ = 0;
3929 other.finished_ = true;
3930}
3931
3932inline Result &Result::operator=(Result &&other) noexcept {
3933 if (this != &other) {
3934 handle_ = std::move(other.handle_);
3935 buffer_ = std::move(other.buffer_);
3936 current_size_ = other.current_size_;
3937 chunk_size_ = other.chunk_size_;
3938 finished_ = other.finished_;
3939 other.current_size_ = 0;
3940 other.finished_ = true;
3941 }
3942 return *this;
3943}
3944
3945inline bool Result::is_valid() const { return handle_.is_valid(); }
3946inline Result::operator bool() const { return is_valid(); }
3947
3948inline int Result::status() const {
3949 return handle_.response ? handle_.response->status : -1;
3950}
3951
3952inline const Headers &Result::headers() const {
3953 static const Headers empty_headers;
3954 return handle_.response ? handle_.response->headers : empty_headers;
3955}
3956
3957inline std::string Result::get_header_value(const std::string &key,
3958 const char *def) const {
3959 return handle_.response ? handle_.response->get_header_value(key, def) : def;
3960}
3961
3962inline bool Result::has_header(const std::string &key) const {
3963 return handle_.response ? handle_.response->has_header(key) : false;
3964}
3965
3966inline Error Result::error() const { return handle_.error; }
3967inline Error Result::read_error() const { return handle_.get_read_error(); }
3968inline bool Result::has_read_error() const { return handle_.has_read_error(); }
3969
3970inline bool Result::next() {
3971 if (!handle_.is_valid() || finished_) { return false; }
3972
3973 if (buffer_.size() < chunk_size_) { buffer_.resize(chunk_size_); }
3974
3975 ssize_t n = handle_.read(&buffer_[0], chunk_size_);
3976 if (n > 0) {
3977 current_size_ = static_cast<size_t>(n);
3978 return true;
3979 }
3980
3981 current_size_ = 0;
3982 finished_ = true;
3983 return false;
3984}
3985
3986inline const char *Result::data() const { return buffer_.data(); }
3987inline size_t Result::size() const { return current_size_; }
3988
3989inline std::string Result::read_all() {
3990 std::string result;
3991 while (next()) {
3992 result.append(data(), size());
3993 }
3994 return result;
3995}
3996
3997} // namespace stream
3998
3999namespace sse {
4000
4001// SSEMessage implementations
4002inline SSEMessage::SSEMessage() : event("message") {}
4003
4004inline void SSEMessage::clear() {
4005 event = "message";
4006 data.clear();
4007 id.clear();
4008}
4009
4010// SSEClient implementations
4011inline SSEClient::SSEClient(Client &client, const std::string &path)
4012 : client_(client), path_(path) {}
4013
4014inline SSEClient::SSEClient(Client &client, const std::string &path,
4015 const Headers &headers)
4016 : client_(client), path_(path), headers_(headers) {}
4017
4019
4021 on_message_ = std::move(handler);
4022 return *this;
4023}
4024
4025inline SSEClient &SSEClient::on_event(const std::string &type,
4026 MessageHandler handler) {
4027 event_handlers_[type] = std::move(handler);
4028 return *this;
4029}
4030
4032 on_open_ = std::move(handler);
4033 return *this;
4034}
4035
4037 on_error_ = std::move(handler);
4038 return *this;
4039}
4040
4042 reconnect_interval_ms_ = ms;
4043 return *this;
4044}
4045
4047 max_reconnect_attempts_ = n;
4048 return *this;
4049}
4050
4052 std::lock_guard<std::mutex> lock(headers_mutex_);
4053 headers_ = headers;
4054 return *this;
4055}
4056
4057inline bool SSEClient::is_connected() const { return connected_.load(); }
4058
4059inline const std::string &SSEClient::last_event_id() const {
4060 return last_event_id_;
4061}
4062
4063inline void SSEClient::start() {
4064 running_.store(true);
4065 run_event_loop();
4066}
4067
4069 running_.store(true);
4070 async_thread_ = std::thread([this]() { run_event_loop(); });
4071}
4072
4073inline void SSEClient::stop() {
4074 running_.store(false);
4075 client_.stop(); // Cancel any pending operations
4076 if (async_thread_.joinable()) { async_thread_.join(); }
4077}
4078
4079inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
4080 int &retry_ms) {
4081 // Blank line signals end of event
4082 if (line.empty() || line == "\r") { return true; }
4083
4084 // Lines starting with ':' are comments (ignored)
4085 if (!line.empty() && line[0] == ':') { return false; }
4086
4087 // Find the colon separator
4088 auto colon_pos = line.find(':');
4089 if (colon_pos == std::string::npos) {
4090 // Line with no colon is treated as field name with empty value
4091 return false;
4092 }
4093
4094 auto field = line.substr(0, colon_pos);
4095 std::string value;
4096
4097 // Value starts after colon, skip optional single space
4098 if (colon_pos + 1 < line.size()) {
4099 auto value_start = colon_pos + 1;
4100 if (line[value_start] == ' ') { value_start++; }
4101 value = line.substr(value_start);
4102 // Remove trailing \r if present
4103 if (!value.empty() && value.back() == '\r') { value.pop_back(); }
4104 }
4105
4106 // Handle known fields
4107 if (field == "event") {
4108 msg.event = value;
4109 } else if (field == "data") {
4110 // Multiple data lines are concatenated with newlines
4111 if (!msg.data.empty()) { msg.data += "\n"; }
4112 msg.data += value;
4113 } else if (field == "id") {
4114 // Empty id is valid (clears the last event ID)
4115 msg.id = value;
4116 } else if (field == "retry") {
4117 // Parse retry interval in milliseconds
4118 {
4119 int v = 0;
4120 auto res =
4121 detail::from_chars(value.data(), value.data() + value.size(), v);
4122 if (res.ec == std::errc{}) { retry_ms = v; }
4123 }
4124 }
4125 // Unknown fields are ignored per SSE spec
4126
4127 return false;
4128}
4129
4130inline void SSEClient::run_event_loop() {
4131 auto reconnect_count = 0;
4132
4133 while (running_.load()) {
4134 // Build headers, including Last-Event-ID if we have one
4135 Headers request_headers;
4136 {
4137 std::lock_guard<std::mutex> lock(headers_mutex_);
4138 request_headers = headers_;
4139 }
4140 if (!last_event_id_.empty()) {
4141 request_headers.emplace("Last-Event-ID", last_event_id_);
4142 }
4143
4144 // Open streaming connection
4145 auto result = stream::Get(client_, path_, request_headers);
4146
4147 // Connection error handling
4148 if (!result) {
4149 connected_.store(false);
4150 if (on_error_) { on_error_(result.error()); }
4151
4152 if (!should_reconnect(reconnect_count)) { break; }
4153 wait_for_reconnect();
4154 reconnect_count++;
4155 continue;
4156 }
4157
4158 if (result.status() != StatusCode::OK_200) {
4159 connected_.store(false);
4160 if (on_error_) { on_error_(Error::Connection); }
4161
4162 // For certain errors, don't reconnect.
4163 // Note: 401 is intentionally absent so that handlers can refresh
4164 // credentials via set_headers() and let the client reconnect.
4165 if (result.status() == StatusCode::NoContent_204 ||
4166 result.status() == StatusCode::NotFound_404 ||
4167 result.status() == StatusCode::Forbidden_403) {
4168 break;
4169 }
4170
4171 if (!should_reconnect(reconnect_count)) { break; }
4172 wait_for_reconnect();
4173 reconnect_count++;
4174 continue;
4175 }
4176
4177 // Connection successful
4178 connected_.store(true);
4179 reconnect_count = 0;
4180 if (on_open_) { on_open_(); }
4181
4182 // Event receiving loop
4183 std::string buffer;
4184 SSEMessage current_msg;
4185
4186 while (running_.load() && result.next()) {
4187 buffer.append(result.data(), result.size());
4188
4189 // Process complete lines in the buffer
4190 size_t line_start = 0;
4191 size_t newline_pos;
4192
4193 while ((newline_pos = buffer.find('\n', line_start)) !=
4194 std::string::npos) {
4195 auto line = buffer.substr(line_start, newline_pos - line_start);
4196 line_start = newline_pos + 1;
4197
4198 // Parse the line and check if event is complete
4199 auto event_complete =
4200 parse_sse_line(line, current_msg, reconnect_interval_ms_);
4201
4202 if (event_complete && !current_msg.data.empty()) {
4203 // Update last_event_id for reconnection
4204 if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
4205
4206 // Dispatch event to appropriate handler
4207 dispatch_event(current_msg);
4208
4209 current_msg.clear();
4210 }
4211 }
4212
4213 // Keep unprocessed data in buffer
4214 buffer.erase(0, line_start);
4215 }
4216
4217 // Connection ended
4218 connected_.store(false);
4219
4220 if (!running_.load()) { break; }
4221
4222 // Check for read errors
4223 if (result.has_read_error()) {
4224 if (on_error_) { on_error_(result.read_error()); }
4225 }
4226
4227 if (!should_reconnect(reconnect_count)) { break; }
4228 wait_for_reconnect();
4229 reconnect_count++;
4230 }
4231
4232 connected_.store(false);
4233}
4234
4235inline void SSEClient::dispatch_event(const SSEMessage &msg) {
4236 // Check for specific event type handler first
4237 auto it = event_handlers_.find(msg.event);
4238 if (it != event_handlers_.end()) {
4239 it->second(msg);
4240 return;
4241 }
4242
4243 // Fall back to generic message handler
4244 if (on_message_) { on_message_(msg); }
4245}
4246
4247inline bool SSEClient::should_reconnect(int count) const {
4248 if (!running_.load()) { return false; }
4249 if (max_reconnect_attempts_ == 0) { return true; } // unlimited
4250 return count < max_reconnect_attempts_;
4251}
4252
4253inline void SSEClient::wait_for_reconnect() {
4254 // Use small increments to check running_ flag frequently
4255 auto waited = 0;
4256 while (running_.load() && waited < reconnect_interval_ms_) {
4257 std::this_thread::sleep_for(std::chrono::milliseconds(100));
4258 waited += 100;
4259 }
4260}
4261
4262} // namespace sse
4263
4264#ifdef CPPHTTPLIB_SSL_ENABLED
4265/*
4266 * TLS abstraction layer - internal function declarations
4267 * These are implementation details and not part of the public API.
4268 */
4269namespace tls {
4270
4271// Client context
4272ctx_t create_client_context();
4273void free_context(ctx_t ctx);
4274bool set_min_version(ctx_t ctx, Version version);
4275bool load_ca_pem(ctx_t ctx, const char *pem, size_t len);
4276bool load_ca_file(ctx_t ctx, const char *file_path);
4277bool load_ca_dir(ctx_t ctx, const char *dir_path);
4278bool load_system_certs(ctx_t ctx);
4279bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
4280 const char *password);
4281bool set_client_cert_file(ctx_t ctx, const char *cert_path,
4282 const char *key_path, const char *password);
4283
4284// Server context
4285ctx_t create_server_context();
4286bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
4287 const char *password);
4288bool set_server_cert_file(ctx_t ctx, const char *cert_path,
4289 const char *key_path, const char *password);
4290bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir);
4291void set_verify_client(ctx_t ctx, bool require);
4292
4293// Session management
4294session_t create_session(ctx_t ctx, socket_t sock);
4295void free_session(session_t session);
4296bool set_sni(session_t session, const char *hostname);
4297bool set_hostname(session_t session, const char *hostname);
4298
4299// Handshake (non-blocking capable)
4300TlsError connect(session_t session);
4301TlsError accept(session_t session);
4302
4303// Handshake with timeout (blocking until timeout)
4304bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4305 time_t timeout_usec, TlsError *err);
4306bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4307 time_t timeout_usec, TlsError *err);
4308
4309// I/O (non-blocking capable)
4310ssize_t read(session_t session, void *buf, size_t len, TlsError &err);
4311ssize_t write(session_t session, const void *buf, size_t len, TlsError &err);
4312int pending(const_session_t session);
4313void shutdown(session_t session, bool graceful);
4314
4315// Connection state
4316bool is_peer_closed(session_t session, socket_t sock);
4317
4318// Certificate verification
4319cert_t get_peer_cert(const_session_t session);
4320void free_cert(cert_t cert);
4321bool verify_hostname(cert_t cert, const char *hostname);
4322uint64_t hostname_mismatch_code();
4323long get_verify_result(const_session_t session);
4324
4325// Certificate introspection
4326std::string get_cert_subject_cn(cert_t cert);
4327std::string get_cert_issuer_name(cert_t cert);
4328bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
4329bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
4330std::string get_cert_serial(cert_t cert);
4331bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
4332const char *get_sni(const_session_t session);
4333
4334// CA store management
4335ca_store_t create_ca_store(const char *pem, size_t len);
4336void free_ca_store(ca_store_t store);
4337bool set_ca_store(ctx_t ctx, ca_store_t store);
4338size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs);
4339std::vector<std::string> get_ca_names(ctx_t ctx);
4340
4341// Dynamic certificate update (for servers)
4342bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem,
4343 const char *password);
4344bool update_server_client_ca(ctx_t ctx, const char *ca_pem);
4345
4346// Certificate verification callback
4347bool set_verify_callback(ctx_t ctx, VerifyCallback callback);
4348long get_verify_error(const_session_t session);
4349std::string verify_error_string(long error_code);
4350
4351// TlsError information
4352uint64_t peek_error();
4353uint64_t get_error();
4354std::string error_string(uint64_t code);
4355
4356} // namespace tls
4357#endif // CPPHTTPLIB_SSL_ENABLED
4358
4359/*
4360 * Group 1: detail namespace - Non-SSL utilities
4361 */
4362
4363namespace detail {
4364
4365inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
4366 const void *optval, socklen_t optlen) {
4367 return setsockopt(sock, level, optname,
4368#ifdef _WIN32
4369 reinterpret_cast<const char *>(optval),
4370#else
4371 optval,
4372#endif
4373 optlen) == 0;
4374}
4375
4376inline bool set_socket_opt_time(socket_t sock, int level, int optname,
4377 time_t sec, time_t usec) {
4378#ifdef _WIN32
4379 auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
4380#else
4381 timeval timeout;
4382 timeout.tv_sec = static_cast<long>(sec);
4383 timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
4384#endif
4385 return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
4386}
4387
4388inline bool is_hex(char c, int &v) {
4389 if (isdigit(c)) {
4390 v = c - '0';
4391 return true;
4392 } else if ('A' <= c && c <= 'F') {
4393 v = c - 'A' + 10;
4394 return true;
4395 } else if ('a' <= c && c <= 'f') {
4396 v = c - 'a' + 10;
4397 return true;
4398 }
4399 return false;
4400}
4401
4402inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
4403 int &val) {
4404 if (i >= s.size()) { return false; }
4405
4406 val = 0;
4407 for (; cnt; i++, cnt--) {
4408 if (!s[i]) { return false; }
4409 auto v = 0;
4410 if (is_hex(s[i], v)) {
4411 val = val * 16 + v;
4412 } else {
4413 return false;
4414 }
4415 }
4416 return true;
4417}
4418
4419inline std::string from_i_to_hex(size_t n) {
4420 static const auto charset = "0123456789abcdef";
4421 std::string ret;
4422 do {
4423 ret = charset[n & 15] + ret;
4424 n >>= 4;
4425 } while (n > 0);
4426 return ret;
4427}
4428
4429inline std::string compute_etag(const FileStat &fs) {
4430 if (!fs.is_file()) { return std::string(); }
4431
4432 // If mtime cannot be determined (negative value indicates an error
4433 // or sentinel), do not generate an ETag. Returning a neutral / fixed
4434 // value like 0 could collide with a real file that legitimately has
4435 // mtime == 0 (epoch) and lead to misleading validators.
4436 auto mtime_raw = fs.mtime();
4437 if (mtime_raw < 0) { return std::string(); }
4438
4439 auto mtime = static_cast<size_t>(mtime_raw);
4440 auto size = fs.size();
4441
4442 return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
4443 from_i_to_hex(size) + "\"";
4444}
4445
4446// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
4447// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
4448// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
4449inline std::string file_mtime_to_http_date(time_t mtime) {
4450 if (mtime < 0) { return std::string(); }
4451
4452 struct tm tm_buf;
4453#ifdef _WIN32
4454 if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
4455#else
4456 if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
4457#endif
4458 char buf[64];
4459 if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
4460 return std::string();
4461 }
4462
4463 return std::string(buf);
4464}
4465
4466// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
4467inline time_t parse_http_date(const std::string &date_str) {
4468 struct tm tm_buf;
4469
4470 // Create a classic locale object once for all parsing attempts
4471 const std::locale classic_locale = std::locale::classic();
4472
4473 // Try to parse using std::get_time (C++11, cross-platform)
4474 auto try_parse = [&](const char *fmt) -> bool {
4475 std::istringstream ss(date_str);
4476 ss.imbue(classic_locale);
4477
4478 memset(&tm_buf, 0, sizeof(tm_buf));
4479 ss >> std::get_time(&tm_buf, fmt);
4480
4481 return !ss.fail();
4482 };
4483
4484 // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
4485 if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
4486 // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
4487 if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
4488 // asctime format: "Sun Nov 6 08:49:37 1994"
4489 if (!try_parse("%a %b %d %H:%M:%S %Y")) {
4490 return static_cast<time_t>(-1);
4491 }
4492 }
4493 }
4494
4495#ifdef _WIN32
4496 return _mkgmtime(&tm_buf);
4497#elif defined _AIX
4498 return mktime(&tm_buf);
4499#else
4500 return timegm(&tm_buf);
4501#endif
4502}
4503
4504inline bool is_weak_etag(const std::string &s) {
4505 // Check if the string is a weak ETag (starts with 'W/"')
4506 return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
4507}
4508
4509inline bool is_strong_etag(const std::string &s) {
4510 // Check if the string is a strong ETag (starts and ends with '"', at least 2
4511 // chars)
4512 return s.size() >= 2 && s[0] == '"' && s.back() == '"';
4513}
4514
4515inline size_t to_utf8(int code, char *buff) {
4516 if (code < 0x0080) {
4517 buff[0] = static_cast<char>(code & 0x7F);
4518 return 1;
4519 } else if (code < 0x0800) {
4520 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
4521 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
4522 return 2;
4523 } else if (code < 0xD800) {
4524 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4525 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4526 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4527 return 3;
4528 } else if (code < 0xE000) { // D800 - DFFF is invalid...
4529 return 0;
4530 } else if (code < 0x10000) {
4531 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4532 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4533 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4534 return 3;
4535 } else if (code < 0x110000) {
4536 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
4537 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
4538 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4539 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
4540 return 4;
4541 }
4542
4543 // NOTREACHED
4544 return 0;
4545}
4546
4547} // namespace detail
4548
4549namespace ws {
4550namespace impl {
4551
4552inline bool is_valid_utf8(const std::string &s) {
4553 size_t i = 0;
4554 auto n = s.size();
4555 while (i < n) {
4556 auto c = static_cast<unsigned char>(s[i]);
4557 size_t len;
4558 uint32_t cp;
4559 if (c < 0x80) {
4560 i++;
4561 continue;
4562 } else if ((c & 0xE0) == 0xC0) {
4563 len = 2;
4564 cp = c & 0x1F;
4565 } else if ((c & 0xF0) == 0xE0) {
4566 len = 3;
4567 cp = c & 0x0F;
4568 } else if ((c & 0xF8) == 0xF0) {
4569 len = 4;
4570 cp = c & 0x07;
4571 } else {
4572 return false;
4573 }
4574 if (i + len > n) { return false; }
4575 for (size_t j = 1; j < len; j++) {
4576 auto b = static_cast<unsigned char>(s[i + j]);
4577 if ((b & 0xC0) != 0x80) { return false; }
4578 cp = (cp << 6) | (b & 0x3F);
4579 }
4580 // Overlong encoding check
4581 if (len == 2 && cp < 0x80) { return false; }
4582 if (len == 3 && cp < 0x800) { return false; }
4583 if (len == 4 && cp < 0x10000) { return false; }
4584 // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid
4585 if (cp >= 0xD800 && cp <= 0xDFFF) { return false; }
4586 if (cp > 0x10FFFF) { return false; }
4587 i += len;
4588 }
4589 return true;
4590}
4591
4592} // namespace impl
4593} // namespace ws
4594
4595namespace detail {
4596
4597// NOTE: This code came up with the following stackoverflow post:
4598// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
4599inline std::string base64_encode(const std::string &in) {
4600 static const auto lookup =
4601 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4602
4603 std::string out;
4604 out.reserve(in.size());
4605
4606 auto val = 0;
4607 auto valb = -6;
4608
4609 for (auto c : in) {
4610 val = (val << 8) + static_cast<uint8_t>(c);
4611 valb += 8;
4612 while (valb >= 0) {
4613 out.push_back(lookup[(val >> valb) & 0x3F]);
4614 valb -= 6;
4615 }
4616 }
4617
4618 if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
4619
4620 while (out.size() % 4) {
4621 out.push_back('=');
4622 }
4623
4624 return out;
4625}
4626
4627inline std::string sha1(const std::string &input) {
4628 // RFC 3174 SHA-1 implementation
4629 auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t {
4630 return (x << n) | (x >> (32 - n));
4631 };
4632
4633 uint32_t h0 = 0x67452301;
4634 uint32_t h1 = 0xEFCDAB89;
4635 uint32_t h2 = 0x98BADCFE;
4636 uint32_t h3 = 0x10325476;
4637 uint32_t h4 = 0xC3D2E1F0;
4638
4639 // Pre-processing: adding padding bits
4640 std::string msg = input;
4641 uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
4642 msg.push_back(static_cast<char>(0x80u));
4643 while (msg.size() % 64 != 56) {
4644 msg.push_back(0);
4645 }
4646
4647 // Append original length in bits as 64-bit big-endian
4648 for (int i = 56; i >= 0; i -= 8) {
4649 msg.push_back(static_cast<char>((original_bit_len >> i) & 0xFF));
4650 }
4651
4652 // Process each 512-bit chunk
4653 for (size_t offset = 0; offset < msg.size(); offset += 64) {
4654 uint32_t w[80];
4655
4656 for (size_t i = 0; i < 16; i++) {
4657 w[i] =
4658 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4]))
4659 << 24) |
4660 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 1]))
4661 << 16) |
4662 (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 2]))
4663 << 8) |
4664 (static_cast<uint32_t>(
4665 static_cast<uint8_t>(msg[offset + i * 4 + 3])));
4666 }
4667
4668 for (int i = 16; i < 80; i++) {
4669 w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
4670 }
4671
4672 uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
4673
4674 for (int i = 0; i < 80; i++) {
4675 uint32_t f, k;
4676 if (i < 20) {
4677 f = (b & c) | ((~b) & d);
4678 k = 0x5A827999;
4679 } else if (i < 40) {
4680 f = b ^ c ^ d;
4681 k = 0x6ED9EBA1;
4682 } else if (i < 60) {
4683 f = (b & c) | (b & d) | (c & d);
4684 k = 0x8F1BBCDC;
4685 } else {
4686 f = b ^ c ^ d;
4687 k = 0xCA62C1D6;
4688 }
4689
4690 uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
4691 e = d;
4692 d = c;
4693 c = left_rotate(b, 30);
4694 b = a;
4695 a = temp;
4696 }
4697
4698 h0 += a;
4699 h1 += b;
4700 h2 += c;
4701 h3 += d;
4702 h4 += e;
4703 }
4704
4705 // Produce the final hash as a 20-byte binary string
4706 std::string hash(20, '\0');
4707 for (size_t i = 0; i < 4; i++) {
4708 hash[i] = static_cast<char>((h0 >> (24 - i * 8)) & 0xFF);
4709 hash[4 + i] = static_cast<char>((h1 >> (24 - i * 8)) & 0xFF);
4710 hash[8 + i] = static_cast<char>((h2 >> (24 - i * 8)) & 0xFF);
4711 hash[12 + i] = static_cast<char>((h3 >> (24 - i * 8)) & 0xFF);
4712 hash[16 + i] = static_cast<char>((h4 >> (24 - i * 8)) & 0xFF);
4713 }
4714 return hash;
4715}
4716
4717inline std::string websocket_accept_key(const std::string &client_key) {
4718 const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4719 return base64_encode(sha1(client_key + magic));
4720}
4721
4722inline bool is_websocket_upgrade(const Request &req) {
4723 if (req.method != "GET") { return false; }
4724
4725 // Check Upgrade: websocket (case-insensitive)
4726 auto upgrade_it = req.headers.find("Upgrade");
4727 if (upgrade_it == req.headers.end()) { return false; }
4728 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
4729 if (upgrade_val != "websocket") { return false; }
4730
4731 // Check Connection header contains "Upgrade"
4732 auto connection_it = req.headers.find("Connection");
4733 if (connection_it == req.headers.end()) { return false; }
4734 auto connection_val = case_ignore::to_lower(connection_it->second);
4735 if (connection_val.find("upgrade") == std::string::npos) { return false; }
4736
4737 // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars)
4738 // RFC 6455 Section 4.2.1
4739 auto ws_key = req.get_header_value("Sec-WebSocket-Key");
4740 if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') {
4741 return false;
4742 }
4743 static const std::string b64chars =
4744 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4745 for (size_t i = 0; i < 22; i++) {
4746 if (b64chars.find(ws_key[i]) == std::string::npos) { return false; }
4747 }
4748
4749 // Check Sec-WebSocket-Version: 13
4750 auto version = req.get_header_value("Sec-WebSocket-Version");
4751 if (version != "13") { return false; }
4752
4753 return true;
4754}
4755
4756inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
4757 const char *data, size_t len, bool fin,
4758 bool mask) {
4759 // First byte: FIN + opcode
4760 uint8_t header[2];
4761 header[0] = static_cast<uint8_t>((fin ? 0x80 : 0x00) |
4762 (static_cast<uint8_t>(opcode) & 0x0F));
4763
4764 // Second byte: MASK + payload length
4765 if (len < 126) {
4766 header[1] = static_cast<uint8_t>(len);
4767 if (mask) { header[1] |= 0x80; }
4768 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4769 } else if (len <= 0xFFFF) {
4770 header[1] = 126;
4771 if (mask) { header[1] |= 0x80; }
4772 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4773 uint8_t ext[2];
4774 ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
4775 ext[1] = static_cast<uint8_t>(len & 0xFF);
4776 if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
4777 } else {
4778 header[1] = 127;
4779 if (mask) { header[1] |= 0x80; }
4780 if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4781 uint8_t ext[8];
4782 for (int i = 7; i >= 0; i--) {
4783 ext[7 - i] =
4784 static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
4785 }
4786 if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
4787 }
4788
4789 if (mask) {
4790 // Generate random mask key
4791 thread_local std::mt19937 rng(std::random_device{}());
4792 uint8_t mask_key[4];
4793 auto r = rng();
4794 std::memcpy(mask_key, &r, 4);
4795 if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
4796
4797 // Write masked payload in chunks
4798 const size_t chunk_size = 4096;
4799 std::vector<char> buf((std::min)(len, chunk_size));
4800 for (size_t offset = 0; offset < len; offset += chunk_size) {
4801 size_t n = (std::min)(chunk_size, len - offset);
4802 for (size_t i = 0; i < n; i++) {
4803 buf[i] =
4804 data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
4805 }
4806 if (strm.write(buf.data(), n) < 0) { return false; }
4807 }
4808 } else {
4809 if (len > 0) {
4810 if (strm.write(data, len) < 0) { return false; }
4811 }
4812 }
4813
4814 return true;
4815}
4816
4817} // namespace detail
4818
4819namespace ws {
4820namespace impl {
4821
4822inline bool read_websocket_frame(Stream &strm, Opcode &opcode,
4823 std::string &payload, bool &fin,
4824 bool expect_masked, size_t max_len) {
4825 // Read first 2 bytes
4826 uint8_t header[2];
4827 if (strm.read(reinterpret_cast<char *>(header), 2) != 2) { return false; }
4828
4829 fin = (header[0] & 0x80) != 0;
4830
4831 // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated
4832 if (header[0] & 0x70) { return false; }
4833
4834 opcode = static_cast<Opcode>(header[0] & 0x0F);
4835 bool masked = (header[1] & 0x80) != 0;
4836 uint64_t payload_len = header[1] & 0x7F;
4837
4838 // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and
4839 // MUST have a payload length of 125 bytes or less
4840 bool is_control = (static_cast<uint8_t>(opcode) & 0x08) != 0;
4841 if (is_control) {
4842 if (!fin) { return false; }
4843 if (payload_len > 125) { return false; }
4844 }
4845
4846 if (masked != expect_masked) { return false; }
4847
4848 // Extended payload length
4849 if (payload_len == 126) {
4850 uint8_t ext[2];
4851 if (strm.read(reinterpret_cast<char *>(ext), 2) != 2) { return false; }
4852 payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1];
4853 } else if (payload_len == 127) {
4854 uint8_t ext[8];
4855 if (strm.read(reinterpret_cast<char *>(ext), 8) != 8) { return false; }
4856 // RFC 6455 Section 5.2: the most significant bit MUST be 0
4857 if (ext[0] & 0x80) { return false; }
4858 payload_len = 0;
4859 for (int i = 0; i < 8; i++) {
4860 payload_len = (payload_len << 8) | ext[i];
4861 }
4862 }
4863
4864 if (payload_len > max_len) { return false; }
4865
4866 // Read mask key if present
4867 uint8_t mask_key[4] = {0};
4868 if (masked) {
4869 if (strm.read(reinterpret_cast<char *>(mask_key), 4) != 4) { return false; }
4870 }
4871
4872 // Read payload
4873 payload.resize(static_cast<size_t>(payload_len));
4874 if (payload_len > 0) {
4875 size_t total_read = 0;
4876 while (total_read < payload_len) {
4877 auto n = strm.read(&payload[total_read],
4878 static_cast<size_t>(payload_len - total_read));
4879 if (n <= 0) { return false; }
4880 total_read += static_cast<size_t>(n);
4881 }
4882 }
4883
4884 // Unmask if needed
4885 if (masked) {
4886 for (size_t i = 0; i < payload.size(); i++) {
4887 payload[i] ^= static_cast<char>(mask_key[i % 4]);
4888 }
4889 }
4890
4891 return true;
4892}
4893
4894} // namespace impl
4895} // namespace ws
4896
4897namespace detail {
4898
4899inline bool is_valid_path(const std::string &path) {
4900 size_t level = 0;
4901 size_t i = 0;
4902
4903 // Skip slash
4904 while (i < path.size() && path[i] == '/') {
4905 i++;
4906 }
4907
4908 while (i < path.size()) {
4909 // Read component
4910 auto beg = i;
4911 while (i < path.size() && path[i] != '/') {
4912 if (path[i] == '\0') {
4913 return false;
4914 } else if (path[i] == '\\') {
4915 return false;
4916 }
4917 i++;
4918 }
4919
4920 auto len = i - beg;
4921 assert(len > 0);
4922
4923 if (!path.compare(beg, len, ".")) {
4924 ;
4925 } else if (!path.compare(beg, len, "..")) {
4926 if (level == 0) { return false; }
4927 level--;
4928 } else {
4929 level++;
4930 }
4931
4932 // Skip slash
4933 while (i < path.size() && path[i] == '/') {
4934 i++;
4935 }
4936 }
4937
4938 return true;
4939}
4940
4941inline bool canonicalize_path(const char *path, std::string &resolved) {
4942#if defined(_WIN32)
4943 char buf[_MAX_PATH];
4944 if (_fullpath(buf, path, _MAX_PATH) == nullptr) { return false; }
4945 resolved = buf;
4946#elif defined(PATH_MAX)
4947 char buf[PATH_MAX];
4948 if (realpath(path, buf) == nullptr) { return false; }
4949 resolved = buf;
4950#else
4951 auto buf = realpath(path, nullptr);
4952 auto guard = scope_exit([&]() { std::free(buf); });
4953 if (buf == nullptr) { return false; }
4954 resolved = buf;
4955#endif
4956 return true;
4957}
4958
4959inline bool is_path_within_base(const std::string &resolved_path,
4960 const std::string &resolved_base) {
4961#if defined(_WIN32)
4962 return _strnicmp(resolved_path.c_str(), resolved_base.c_str(),
4963 resolved_base.size()) == 0;
4964#else
4965 return strncmp(resolved_path.c_str(), resolved_base.c_str(),
4966 resolved_base.size()) == 0;
4967#endif
4968}
4969
4970inline FileStat::FileStat(const std::string &path) {
4971#if defined(_WIN32)
4972 auto wpath = u8string_to_wstring(path.c_str());
4973 ret_ = _wstat(wpath.c_str(), &st_);
4974#else
4975 ret_ = stat(path.c_str(), &st_);
4976#endif
4977}
4978inline bool FileStat::is_file() const {
4979 return ret_ >= 0 && S_ISREG(st_.st_mode);
4980}
4981inline bool FileStat::is_dir() const {
4982 return ret_ >= 0 && S_ISDIR(st_.st_mode);
4983}
4984
4985inline time_t FileStat::mtime() const {
4986 return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
4987 : static_cast<time_t>(-1);
4988}
4989
4990inline size_t FileStat::size() const {
4991 return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
4992}
4993
4994inline std::string encode_path(const std::string &s) {
4995 std::string result;
4996 result.reserve(s.size());
4997
4998 for (size_t i = 0; s[i]; i++) {
4999 switch (s[i]) {
5000 case ' ': result += "%20"; break;
5001 case '+': result += "%2B"; break;
5002 case '\r': result += "%0D"; break;
5003 case '\n': result += "%0A"; break;
5004 case '\'': result += "%27"; break;
5005 case ',': result += "%2C"; break;
5006 // case ':': result += "%3A"; break; // ok? probably...
5007 case ';': result += "%3B"; break;
5008 default:
5009 auto c = static_cast<uint8_t>(s[i]);
5010 if (c >= 0x80) {
5011 result += '%';
5012 char hex[4];
5013 auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
5014 assert(len == 2);
5015 result.append(hex, static_cast<size_t>(len));
5016 } else {
5017 result += s[i];
5018 }
5019 break;
5020 }
5021 }
5022
5023 return result;
5024}
5025
5026inline std::string file_extension(const std::string &path) {
5027 std::smatch m;
5028 thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
5029 if (std::regex_search(path, m, re)) { return m[1].str(); }
5030 return std::string();
5031}
5032
5033inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
5034
5035template <typename T>
5036inline bool parse_header(const char *beg, const char *end, T fn);
5037
5038template <typename T>
5039inline bool parse_header(const char *beg, const char *end, T fn) {
5040 // Skip trailing spaces and tabs.
5041 while (beg < end && is_space_or_tab(end[-1])) {
5042 end--;
5043 }
5044
5045 auto p = beg;
5046 while (p < end && *p != ':') {
5047 p++;
5048 }
5049
5050 auto name = std::string(beg, p);
5051 if (!detail::fields::is_field_name(name)) { return false; }
5052
5053 if (p == end) { return false; }
5054
5055 auto key_end = p;
5056
5057 if (*p++ != ':') { return false; }
5058
5059 while (p < end && is_space_or_tab(*p)) {
5060 p++;
5061 }
5062
5063 if (p <= end) {
5064 auto key_len = key_end - beg;
5065 if (!key_len) { return false; }
5066
5067 auto key = std::string(beg, key_end);
5068 auto val = std::string(p, end);
5069
5070 if (!detail::fields::is_field_value(val)) { return false; }
5071
5072 // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5073 // percent-decoded by the recipient. Applications that need to interpret a
5074 // value as a URI component should call httplib::decode_uri_component()
5075 // (or decode_path_component()) explicitly.
5076 fn(key, val);
5077
5078 return true;
5079 }
5080
5081 return false;
5082}
5083
5084inline bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
5085 const Headers &src_headers) {
5086 // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
5087 // transfer coding is complete when a chunk with a chunk-size of zero is
5088 // received, possibly followed by a trailer section, and finally terminated by
5089 // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
5090 //
5091 // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
5092 // doesn't care for the existence of the final CRLF. In other words, it seems
5093 // to be ok whether the final CRLF exists or not in the chunked data.
5094 // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
5095 //
5096 // According to the reference code in RFC 9112, cpp-httplib now allows
5097 // chunked transfer coding data without the final CRLF.
5098
5099 // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
5100 thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
5101 "transfer-encoding",
5102 "content-length",
5103 "host",
5104 "authorization",
5105 "www-authenticate",
5106 "proxy-authenticate",
5107 "proxy-authorization",
5108 "cookie",
5109 "set-cookie",
5110 "cache-control",
5111 "expect",
5112 "max-forwards",
5113 "pragma",
5114 "range",
5115 "te",
5116 "age",
5117 "expires",
5118 "date",
5119 "location",
5120 "retry-after",
5121 "vary",
5122 "warning",
5123 "content-encoding",
5124 "content-type",
5125 "content-range",
5126 "trailer"};
5127
5129 auto trailer_header = get_header_value(src_headers, "Trailer", "", 0);
5130 if (trailer_header && std::strlen(trailer_header)) {
5131 auto len = std::strlen(trailer_header);
5132 split(trailer_header, trailer_header + len, ',',
5133 [&](const char *b, const char *e) {
5134 const char *kbeg = b;
5135 const char *kend = e;
5136 while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) {
5137 ++kbeg;
5138 }
5139 while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) {
5140 --kend;
5141 }
5142 std::string key(kbeg, static_cast<size_t>(kend - kbeg));
5143 if (!key.empty() &&
5144 prohibited_trailers.find(key) == prohibited_trailers.end()) {
5145 declared_trailers.insert(key);
5146 }
5147 });
5148 }
5149
5150 size_t trailer_header_count = 0;
5151 while (strcmp(line_reader.ptr(), "\r\n") != 0) {
5152 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
5153 if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
5154
5155 constexpr auto line_terminator_len = 2;
5156 auto line_beg = line_reader.ptr();
5157 auto line_end =
5158 line_reader.ptr() + line_reader.size() - line_terminator_len;
5159
5160 if (!parse_header(line_beg, line_end,
5161 [&](const std::string &key, const std::string &val) {
5162 if (declared_trailers.find(key) !=
5163 declared_trailers.end()) {
5164 dest.emplace(key, val);
5165 trailer_header_count++;
5166 }
5167 })) {
5168 return false;
5169 }
5170
5171 if (!line_reader.getline()) { return false; }
5172 }
5173
5174 return true;
5175}
5176
5177inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
5178 size_t right) {
5179 while (b + left < e && is_space_or_tab(b[left])) {
5180 left++;
5181 }
5182 while (right > 0 && is_space_or_tab(b[right - 1])) {
5183 right--;
5184 }
5185 return std::make_pair(left, right);
5186}
5187
5188inline std::string trim_copy(const std::string &s) {
5189 auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
5190 return s.substr(r.first, r.second - r.first);
5191}
5192
5193inline std::string trim_double_quotes_copy(const std::string &s) {
5194 if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
5195 return s.substr(1, s.size() - 2);
5196 }
5197 return s;
5198}
5199
5200inline void
5201divide(const char *data, std::size_t size, char d,
5202 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5203 fn) {
5204 const auto it = std::find(data, data + size, d);
5205 const auto found = static_cast<std::size_t>(it != data + size);
5206 const auto lhs_data = data;
5207 const auto lhs_size = static_cast<std::size_t>(it - data);
5208 const auto rhs_data = it + found;
5209 const auto rhs_size = size - lhs_size - found;
5210
5211 fn(lhs_data, lhs_size, rhs_data, rhs_size);
5212}
5213
5214inline void
5215divide(const std::string &str, char d,
5216 std::function<void(const char *, std::size_t, const char *, std::size_t)>
5217 fn) {
5218 divide(str.data(), str.size(), d, std::move(fn));
5219}
5220
5221inline void split(const char *b, const char *e, char d,
5222 std::function<void(const char *, const char *)> fn) {
5223 return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
5224}
5225
5226inline void split(const char *b, const char *e, char d, size_t m,
5227 std::function<void(const char *, const char *)> fn) {
5228 size_t i = 0;
5229 size_t beg = 0;
5230 size_t count = 1;
5231
5232 while (e ? (b + i < e) : (b[i] != '\0')) {
5233 if (b[i] == d && count < m) {
5234 auto r = trim(b, e, beg, i);
5235 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5236 beg = i + 1;
5237 count++;
5238 }
5239 i++;
5240 }
5241
5242 if (i) {
5243 auto r = trim(b, e, beg, i);
5244 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5245 }
5246}
5247
5248inline bool split_find(const char *b, const char *e, char d, size_t m,
5249 std::function<bool(const char *, const char *)> fn) {
5250 size_t i = 0;
5251 size_t beg = 0;
5252 size_t count = 1;
5253
5254 while (e ? (b + i < e) : (b[i] != '\0')) {
5255 if (b[i] == d && count < m) {
5256 auto r = trim(b, e, beg, i);
5257 if (r.first < r.second) {
5258 auto found = fn(&b[r.first], &b[r.second]);
5259 if (found) { return true; }
5260 }
5261 beg = i + 1;
5262 count++;
5263 }
5264 i++;
5265 }
5266
5267 if (i) {
5268 auto r = trim(b, e, beg, i);
5269 if (r.first < r.second) {
5270 auto found = fn(&b[r.first], &b[r.second]);
5271 if (found) { return true; }
5272 }
5273 }
5274
5275 return false;
5276}
5277
5278inline bool split_find(const char *b, const char *e, char d,
5279 std::function<bool(const char *, const char *)> fn) {
5280 return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
5281 std::move(fn));
5282}
5283
5284inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
5285 size_t fixed_buffer_size)
5286 : strm_(strm), fixed_buffer_(fixed_buffer),
5287 fixed_buffer_size_(fixed_buffer_size) {}
5288
5289inline const char *stream_line_reader::ptr() const {
5290 if (growable_buffer_.empty()) {
5291 return fixed_buffer_;
5292 } else {
5293 return growable_buffer_.data();
5294 }
5295}
5296
5297inline size_t stream_line_reader::size() const {
5298 if (growable_buffer_.empty()) {
5299 return fixed_buffer_used_size_;
5300 } else {
5301 return growable_buffer_.size();
5302 }
5303}
5304
5306 auto end = ptr() + size();
5307 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
5308}
5309
5311 fixed_buffer_used_size_ = 0;
5312 growable_buffer_.clear();
5313
5314#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5315 char prev_byte = 0;
5316#endif
5317
5318 for (size_t i = 0;; i++) {
5320 // Treat exceptionally long lines as an error to
5321 // prevent infinite loops/memory exhaustion
5322 return false;
5323 }
5324 char byte;
5325 auto n = strm_.read(&byte, 1);
5326
5327 if (n < 0) {
5328 return false;
5329 } else if (n == 0) {
5330 if (i == 0) {
5331 return false;
5332 } else {
5333 break;
5334 }
5335 }
5336
5337 append(byte);
5338
5339#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5340 if (byte == '\n') { break; }
5341#else
5342 if (prev_byte == '\r' && byte == '\n') { break; }
5343 prev_byte = byte;
5344#endif
5345 }
5346
5347 return true;
5348}
5349
5350inline void stream_line_reader::append(char c) {
5351 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
5352 fixed_buffer_[fixed_buffer_used_size_++] = c;
5353 fixed_buffer_[fixed_buffer_used_size_] = '\0';
5354 } else {
5355 if (growable_buffer_.empty()) {
5356 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
5357 growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
5358 }
5359 growable_buffer_ += c;
5360 }
5361}
5362
5363inline mmap::mmap(const char *path) { open(path); }
5364
5365inline mmap::~mmap() { close(); }
5366
5367inline bool mmap::open(const char *path) {
5368 close();
5369
5370#if defined(_WIN32)
5371 auto wpath = u8string_to_wstring(path);
5372 if (wpath.empty()) { return false; }
5373
5374 hFile_ =
5375 ::CreateFile2(wpath.c_str(), GENERIC_READ,
5376 FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, NULL);
5377
5378 if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
5379
5380 LARGE_INTEGER size{};
5381 if (!::GetFileSizeEx(hFile_, &size)) { return false; }
5382 // If the following line doesn't compile due to QuadPart, update Windows SDK.
5383 // See:
5384 // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
5385 if (static_cast<ULONGLONG>(size.QuadPart) >
5386 (std::numeric_limits<decltype(size_)>::max)()) {
5387 // `size_t` might be 32-bits, on 32-bits Windows.
5388 return false;
5389 }
5390 size_ = static_cast<size_t>(size.QuadPart);
5391
5392 hMapping_ =
5393 ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
5394
5395 // Special treatment for an empty file...
5396 if (hMapping_ == NULL && size_ == 0) {
5397 close();
5398 is_open_empty_file = true;
5399 return true;
5400 }
5401
5402 if (hMapping_ == NULL) {
5403 close();
5404 return false;
5405 }
5406
5407 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
5408
5409 if (addr_ == nullptr) {
5410 close();
5411 return false;
5412 }
5413#else
5414 fd_ = ::open(path, O_RDONLY);
5415 if (fd_ == -1) { return false; }
5416
5417 struct stat sb;
5418 if (fstat(fd_, &sb) == -1) {
5419 close();
5420 return false;
5421 }
5422 size_ = static_cast<size_t>(sb.st_size);
5423
5424 addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
5425
5426 // Special treatment for an empty file...
5427 if (addr_ == MAP_FAILED && size_ == 0) {
5428 close();
5429 is_open_empty_file = true;
5430 return false;
5431 }
5432#endif
5433
5434 return true;
5435}
5436
5437inline bool mmap::is_open() const {
5438 return is_open_empty_file ? true : addr_ != nullptr;
5439}
5440
5441inline size_t mmap::size() const { return size_; }
5442
5443inline const char *mmap::data() const {
5444 return is_open_empty_file ? "" : static_cast<const char *>(addr_);
5445}
5446
5447inline void mmap::close() {
5448#if defined(_WIN32)
5449 if (addr_) {
5450 ::UnmapViewOfFile(addr_);
5451 addr_ = nullptr;
5452 }
5453
5454 if (hMapping_) {
5455 ::CloseHandle(hMapping_);
5456 hMapping_ = NULL;
5457 }
5458
5459 if (hFile_ != INVALID_HANDLE_VALUE) {
5460 ::CloseHandle(hFile_);
5461 hFile_ = INVALID_HANDLE_VALUE;
5462 }
5463
5464 is_open_empty_file = false;
5465#else
5466 if (addr_ != nullptr) {
5467 munmap(addr_, size_);
5468 addr_ = nullptr;
5469 }
5470
5471 if (fd_ != -1) {
5472 ::close(fd_);
5473 fd_ = -1;
5474 }
5475#endif
5476 size_ = 0;
5477}
5478inline int close_socket(socket_t sock) noexcept {
5479#ifdef _WIN32
5480 return closesocket(sock);
5481#else
5482 return close(sock);
5483#endif
5484}
5485
5486template <typename T> inline ssize_t handle_EINTR(T fn) {
5487 ssize_t res = 0;
5488 while (true) {
5489 res = fn();
5490 if (res < 0 && errno == EINTR) {
5491 std::this_thread::sleep_for(std::chrono::microseconds{1});
5492 continue;
5493 }
5494 break;
5495 }
5496 return res;
5497}
5498
5499inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
5500 return handle_EINTR([&]() {
5501 return recv(sock,
5502#ifdef _WIN32
5503 static_cast<char *>(ptr), static_cast<int>(size),
5504#else
5505 ptr, size,
5506#endif
5507 flags);
5508 });
5509}
5510
5511inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
5512 int flags) {
5513 return handle_EINTR([&]() {
5514 return send(sock,
5515#ifdef _WIN32
5516 static_cast<const char *>(ptr), static_cast<int>(size),
5517#else
5518 ptr, size,
5519#endif
5520 flags);
5521 });
5522}
5523
5524inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
5525#ifdef _WIN32
5526 return ::WSAPoll(fds, nfds, timeout);
5527#else
5528 return ::poll(fds, nfds, timeout);
5529#endif
5530}
5531
5532inline ssize_t select_impl(socket_t sock, short events, time_t sec,
5533 time_t usec) {
5534 struct pollfd pfd;
5535 pfd.fd = sock;
5536 pfd.events = events;
5537 pfd.revents = 0;
5538
5539 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5540
5541 return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
5542}
5543
5544inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
5545 return select_impl(sock, POLLIN, sec, usec);
5546}
5547
5548inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
5549 return select_impl(sock, POLLOUT, sec, usec);
5550}
5551
5553 time_t usec) {
5554 struct pollfd pfd_read;
5555 pfd_read.fd = sock;
5556 pfd_read.events = POLLIN | POLLOUT;
5557 pfd_read.revents = 0;
5558
5559 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5560
5561 auto poll_res =
5562 handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
5563
5564 if (poll_res == 0) { return Error::ConnectionTimeout; }
5565
5566 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
5567 auto error = 0;
5568 socklen_t len = sizeof(error);
5569 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
5570 reinterpret_cast<char *>(&error), &len);
5571 auto successful = res >= 0 && !error;
5572 return successful ? Error::Success : Error::Connection;
5573 }
5574
5575 return Error::Connection;
5576}
5577
5578inline bool is_socket_alive(socket_t sock) {
5579 const auto val = detail::select_read(sock, 0, 0);
5580 if (val == 0) {
5581 return true;
5582 } else if (val < 0 && errno == EBADF) {
5583 return false;
5584 }
5585 char buf[1];
5586 return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
5587}
5588
5589class SocketStream final : public Stream {
5590public:
5591 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5592 time_t write_timeout_sec, time_t write_timeout_usec,
5593 time_t max_timeout_msec = 0,
5594 std::chrono::time_point<std::chrono::steady_clock> start_time =
5595 (std::chrono::steady_clock::time_point::min)());
5596 ~SocketStream() override;
5597
5598 bool is_readable() const override;
5599 bool wait_readable() const override;
5600 bool wait_writable() const override;
5601 bool is_peer_alive() const override;
5602 ssize_t read(char *ptr, size_t size) override;
5603 ssize_t write(const char *ptr, size_t size) override;
5604 void get_remote_ip_and_port(std::string &ip, int &port) const override;
5605 void get_local_ip_and_port(std::string &ip, int &port) const override;
5606 socket_t socket() const override;
5607 time_t duration() const override;
5608 void set_read_timeout(time_t sec, time_t usec = 0) override;
5609
5610private:
5611 socket_t sock_;
5612 time_t read_timeout_sec_;
5613 time_t read_timeout_usec_;
5614 time_t write_timeout_sec_;
5615 time_t write_timeout_usec_;
5616 time_t max_timeout_msec_;
5617 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5618
5619 std::vector<char> read_buff_;
5620 size_t read_buff_off_ = 0;
5621 size_t read_buff_content_size_ = 0;
5622
5623 static const size_t read_buff_size_ = 1024l * 4;
5624};
5625
5626inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
5627 time_t keep_alive_timeout_sec) {
5628 using namespace std::chrono;
5629
5630 const auto interval_usec =
5632
5633 // Avoid expensive `steady_clock::now()` call for the first time
5634 if (select_read(sock, 0, interval_usec) > 0) { return true; }
5635
5636 const auto start = steady_clock::now() - microseconds{interval_usec};
5637 const auto timeout = seconds{keep_alive_timeout_sec};
5638
5639 while (true) {
5640 if (svr_sock == INVALID_SOCKET) {
5641 break; // Server socket is closed
5642 }
5643
5644 auto val = select_read(sock, 0, interval_usec);
5645 if (val < 0) {
5646 break; // Ssocket error
5647 } else if (val == 0) {
5648 if (steady_clock::now() - start > timeout) {
5649 break; // Timeout
5650 }
5651 } else {
5652 return true; // Ready for read
5653 }
5654 }
5655
5656 return false;
5657}
5658
5659template <typename T>
5660inline bool
5661process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
5662 size_t keep_alive_max_count,
5663 time_t keep_alive_timeout_sec, T callback) {
5664 assert(keep_alive_max_count > 0);
5665 auto ret = false;
5666 auto count = keep_alive_max_count;
5667 while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
5668 auto close_connection = count == 1;
5669 auto connection_closed = false;
5670 ret = callback(close_connection, connection_closed);
5671 if (!ret || connection_closed) { break; }
5672 count--;
5673 }
5674 return ret;
5675}
5676
5677template <typename T>
5678inline bool
5679process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
5680 size_t keep_alive_max_count,
5681 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
5682 time_t read_timeout_usec, time_t write_timeout_sec,
5683 time_t write_timeout_usec, T callback) {
5685 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
5686 [&](bool close_connection, bool &connection_closed) {
5687 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5688 write_timeout_sec, write_timeout_usec);
5689 return callback(strm, close_connection, connection_closed);
5690 });
5691}
5692
5694 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5695 time_t write_timeout_sec, time_t write_timeout_usec,
5696 time_t max_timeout_msec,
5697 std::chrono::time_point<std::chrono::steady_clock> start_time,
5698 std::function<bool(Stream &)> callback) {
5699 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5700 write_timeout_sec, write_timeout_usec, max_timeout_msec,
5701 start_time);
5702 return callback(strm);
5703}
5704
5705inline int shutdown_socket(socket_t sock) noexcept {
5706#ifdef _WIN32
5707 return shutdown(sock, SD_BOTH);
5708#else
5709 return shutdown(sock, SHUT_RDWR);
5710#endif
5711}
5712
5713inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
5714 if (s.size() > 1 && s[0] == '\0') {
5715 auto ret = s;
5716 ret[0] = '@';
5717 return ret;
5718 }
5719 return s;
5720}
5721
5722inline std::string
5724 if (s.size() > 1 && s[0] == '@') {
5725 auto ret = s;
5726 ret[0] = '\0';
5727 return ret;
5728 }
5729 return s;
5730}
5731
5732inline int getaddrinfo_with_timeout(const char *node, const char *service,
5733 const struct addrinfo *hints,
5734 struct addrinfo **res, time_t timeout_sec) {
5735#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
5736 if (timeout_sec <= 0) {
5737 // No timeout specified, use standard getaddrinfo
5738 return getaddrinfo(node, service, hints, res);
5739 }
5740
5741#ifdef _WIN32
5742 // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
5743 OVERLAPPED overlapped = {};
5744 HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
5745 if (!event) { return EAI_FAIL; }
5746
5747 overlapped.hEvent = event;
5748
5749 PADDRINFOEXW result_addrinfo = nullptr;
5750 HANDLE cancel_handle = nullptr;
5751
5752 ADDRINFOEXW hints_ex = {};
5753 if (hints) {
5754 hints_ex.ai_flags = hints->ai_flags;
5755 hints_ex.ai_family = hints->ai_family;
5756 hints_ex.ai_socktype = hints->ai_socktype;
5757 hints_ex.ai_protocol = hints->ai_protocol;
5758 }
5759
5760 auto wnode = u8string_to_wstring(node);
5761 auto wservice = u8string_to_wstring(service);
5762
5763 auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
5764 hints ? &hints_ex : nullptr, &result_addrinfo,
5765 nullptr, &overlapped, nullptr, &cancel_handle);
5766
5767 if (ret == WSA_IO_PENDING) {
5768 auto wait_result =
5769 ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
5770 if (wait_result == WAIT_TIMEOUT) {
5771 if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
5772 ::CloseHandle(event);
5773 return EAI_AGAIN;
5774 }
5775
5776 DWORD bytes_returned;
5777 if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
5778 &bytes_returned, FALSE)) {
5779 ::CloseHandle(event);
5780 return ::WSAGetLastError();
5781 }
5782 }
5783
5784 ::CloseHandle(event);
5785
5786 if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
5787 *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
5788 return 0;
5789 }
5790
5791 return ret;
5792#elif TARGET_OS_MAC && defined(__clang__)
5793 if (!node) { return EAI_NONAME; }
5794 // macOS implementation using CFHost API for asynchronous DNS resolution
5795 CFStringRef hostname_ref = CFStringCreateWithCString(
5796 kCFAllocatorDefault, node, kCFStringEncodingUTF8);
5797 if (!hostname_ref) { return EAI_MEMORY; }
5798
5799 CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
5800 CFRelease(hostname_ref);
5801 if (!host_ref) { return EAI_MEMORY; }
5802
5803 // Set up context for callback
5804 struct CFHostContext {
5805 bool completed = false;
5806 bool success = false;
5807 CFArrayRef addresses = nullptr;
5808 std::mutex mutex;
5809 std::condition_variable cv;
5810 } context;
5811
5812 CFHostClientContext client_context;
5813 memset(&client_context, 0, sizeof(client_context));
5814 client_context.info = &context;
5815
5816 // Set callback
5817 auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
5818 const CFStreamError *error, void *info) {
5819 auto ctx = static_cast<CFHostContext *>(info);
5820 std::lock_guard<std::mutex> lock(ctx->mutex);
5821
5822 if (error && error->error != 0) {
5823 ctx->success = false;
5824 } else {
5825 Boolean hasBeenResolved;
5826 ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
5827 if (ctx->addresses && hasBeenResolved) {
5828 CFRetain(ctx->addresses);
5829 ctx->success = true;
5830 } else {
5831 ctx->success = false;
5832 }
5833 }
5834 ctx->completed = true;
5835 ctx->cv.notify_one();
5836 };
5837
5838 if (!CFHostSetClient(host_ref, callback, &client_context)) {
5839 CFRelease(host_ref);
5840 return EAI_SYSTEM;
5841 }
5842
5843 // Schedule on run loop
5844 CFRunLoopRef run_loop = CFRunLoopGetCurrent();
5845 CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5846
5847 // Start resolution
5848 CFStreamError stream_error;
5849 if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
5850 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5851 CFRelease(host_ref);
5852 return EAI_FAIL;
5853 }
5854
5855 // Wait for completion with timeout
5856 auto timeout_time =
5857 std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
5858 bool timed_out = false;
5859
5860 {
5861 std::unique_lock<std::mutex> lock(context.mutex);
5862
5863 while (!context.completed) {
5864 auto now = std::chrono::steady_clock::now();
5865 if (now >= timeout_time) {
5866 timed_out = true;
5867 break;
5868 }
5869
5870 // Run the runloop for a short time
5871 lock.unlock();
5872 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
5873 lock.lock();
5874 }
5875 }
5876
5877 // Clean up
5878 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5879 CFHostSetClient(host_ref, nullptr, nullptr);
5880
5881 if (timed_out || !context.completed) {
5882 CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
5883 CFRelease(host_ref);
5884 return EAI_AGAIN;
5885 }
5886
5887 if (!context.success || !context.addresses) {
5888 CFRelease(host_ref);
5889 return EAI_NODATA;
5890 }
5891
5892 // Convert CFArray to addrinfo
5893 CFIndex count = CFArrayGetCount(context.addresses);
5894 if (count == 0) {
5895 CFRelease(context.addresses);
5896 CFRelease(host_ref);
5897 return EAI_NODATA;
5898 }
5899
5900 struct addrinfo *result_addrinfo = nullptr;
5901 struct addrinfo **current = &result_addrinfo;
5902
5903 for (CFIndex i = 0; i < count; i++) {
5904 CFDataRef addr_data =
5905 static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
5906 if (!addr_data) continue;
5907
5908 const struct sockaddr *sockaddr_ptr =
5909 reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
5910 socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
5911
5912 // Allocate addrinfo structure
5913 *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
5914 if (!*current) {
5915 freeaddrinfo(result_addrinfo);
5916 CFRelease(context.addresses);
5917 CFRelease(host_ref);
5918 return EAI_MEMORY;
5919 }
5920
5921 memset(*current, 0, sizeof(struct addrinfo));
5922
5923 // Set up addrinfo fields
5924 (*current)->ai_family = sockaddr_ptr->sa_family;
5925 (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
5926 (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
5927 (*current)->ai_addrlen = sockaddr_len;
5928
5929 // Copy sockaddr
5930 (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
5931 if (!(*current)->ai_addr) {
5932 freeaddrinfo(result_addrinfo);
5933 CFRelease(context.addresses);
5934 CFRelease(host_ref);
5935 return EAI_MEMORY;
5936 }
5937 memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
5938
5939 // Set port if service is specified
5940 if (service && *service) {
5941 int port = 0;
5942 if (parse_port(service, strlen(service), port)) {
5943 if (sockaddr_ptr->sa_family == AF_INET) {
5944 reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
5945 ->sin_port = htons(static_cast<uint16_t>(port));
5946 } else if (sockaddr_ptr->sa_family == AF_INET6) {
5947 reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
5948 ->sin6_port = htons(static_cast<uint16_t>(port));
5949 }
5950 }
5951 }
5952
5953 current = &((*current)->ai_next);
5954 }
5955
5956 CFRelease(context.addresses);
5957 CFRelease(host_ref);
5958
5959 *res = result_addrinfo;
5960 return 0;
5961#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \
5962 (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
5963 // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while
5964 // the resolver worker still references the stack-local gaicb. The cancel
5965 // path therefore waits (gai_suspend with no timeout) for the worker to
5966 // actually finish before letting the stack frame go. The trade-off is that
5967 // a wedged DNS server can hold this thread for the system resolver timeout
5968 // (~30s by default) past the caller's connection timeout.
5969 struct gaicb request {};
5970 struct gaicb *requests[1] = {&request};
5971 struct sigevent sevp {};
5972 struct timespec timeout {
5973 timeout_sec, 0
5974 };
5975
5976 request.ar_name = node;
5977 request.ar_service = service;
5978 request.ar_request = hints;
5979 sevp.sigev_notify = SIGEV_NONE;
5980
5981 int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
5982 if (rc != 0) { return rc; }
5983
5984 auto cleanup = scope_exit([&] {
5985 if (request.ar_result) { freeaddrinfo(request.ar_result); }
5986 });
5987
5988 int wait_result = gai_suspend(requests, 1, &timeout);
5989
5990 if (wait_result == 0 || wait_result == EAI_ALLDONE) {
5991 int gai_result = gai_error(&request);
5992 if (gai_result == 0) {
5993 *res = request.ar_result;
5994 request.ar_result = nullptr;
5995 return 0;
5996 }
5997 return gai_result;
5998 }
5999
6000 gai_cancel(&request);
6001 while (gai_error(&request) == EAI_INPROGRESS) {
6002 gai_suspend(requests, 1, nullptr);
6003 }
6004 return wait_result;
6005#else
6006 // Fallback implementation using thread-based timeout for other Unix systems.
6007
6008 struct GetAddrInfoState {
6009 ~GetAddrInfoState() {
6010 if (info) { freeaddrinfo(info); }
6011 }
6012
6013 std::mutex mutex;
6014 std::condition_variable result_cv;
6015 bool completed = false;
6016 int result = EAI_SYSTEM;
6017 std::string node;
6018 std::string service;
6019 struct addrinfo hints;
6020 struct addrinfo *info = nullptr;
6021 };
6022
6023 // Allocate on the heap, so the resolver thread can keep using the data.
6024 auto state = std::make_shared<GetAddrInfoState>();
6025 if (node) { state->node = node; }
6026 state->service = service;
6027 state->hints = *hints;
6028
6029 std::thread resolve_thread([state]() {
6030 auto thread_result =
6031 getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
6032 &state->info);
6033
6034 std::lock_guard<std::mutex> lock(state->mutex);
6035 state->result = thread_result;
6036 state->completed = true;
6037 state->result_cv.notify_one();
6038 });
6039
6040 // Wait for completion or timeout
6041 std::unique_lock<std::mutex> lock(state->mutex);
6042 auto finished =
6043 state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
6044 [&] { return state->completed; });
6045
6046 if (finished) {
6047 // Operation completed within timeout
6048 resolve_thread.join();
6049 *res = state->info;
6050 state->info = nullptr; // Pass ownership to caller
6051 return state->result;
6052 } else {
6053 // Timeout occurred
6054 resolve_thread.detach(); // Let the thread finish in background
6055 return EAI_AGAIN; // Return timeout error
6056 }
6057#endif
6058#else
6059 (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
6060 return getaddrinfo(node, service, hints, res);
6061#endif
6062}
6063
6064template <typename BindOrConnect>
6065socket_t create_socket(const std::string &host, const std::string &ip, int port,
6066 int address_family, int socket_flags, bool tcp_nodelay,
6067 bool ipv6_v6only, SocketOptions socket_options,
6068 BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
6069 // Get address info
6070 const char *node = nullptr;
6071 struct addrinfo hints;
6072 struct addrinfo *result;
6073
6074 memset(&hints, 0, sizeof(struct addrinfo));
6075 hints.ai_socktype = SOCK_STREAM;
6076 hints.ai_protocol = IPPROTO_IP;
6077
6078 if (!ip.empty()) {
6079 node = ip.c_str();
6080 // Ask getaddrinfo to convert IP in c-string to address
6081 hints.ai_family = AF_UNSPEC;
6082 hints.ai_flags = AI_NUMERICHOST;
6083 } else {
6084 if (!host.empty()) { node = host.c_str(); }
6085 hints.ai_family = address_family;
6086 hints.ai_flags = socket_flags;
6087 }
6088
6089#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
6090 if (hints.ai_family == AF_UNIX) {
6091 const auto addrlen = host.length();
6092 if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
6093
6094#ifdef SOCK_CLOEXEC
6095 auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
6096 hints.ai_protocol);
6097#else
6098 auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
6099#endif
6100
6101 if (sock != INVALID_SOCKET) {
6102 sockaddr_un addr{};
6103 addr.sun_family = AF_UNIX;
6104
6105 auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
6106 std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
6107
6108 hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
6109 hints.ai_addrlen = static_cast<socklen_t>(
6110 sizeof(addr) - sizeof(addr.sun_path) + addrlen);
6111
6112#ifndef SOCK_CLOEXEC
6113#ifndef _WIN32
6114 fcntl(sock, F_SETFD, FD_CLOEXEC);
6115#endif
6116#endif
6117
6118 if (socket_options) { socket_options(sock); }
6119
6120#ifdef _WIN32
6121 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
6122 // remove the option.
6123 set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
6124#endif
6125
6126 bool dummy;
6127 if (!bind_or_connect(sock, hints, dummy)) {
6128 close_socket(sock);
6129 sock = INVALID_SOCKET;
6130 }
6131 }
6132 return sock;
6133 }
6134#endif
6135
6136 auto service = std::to_string(port);
6137
6138 if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
6139 timeout_sec)) {
6140#if defined __linux__ && !defined __ANDROID__
6141 res_init();
6142#endif
6143 return INVALID_SOCKET;
6144 }
6145 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6146
6147 for (auto rp = result; rp; rp = rp->ai_next) {
6148 // Create a socket
6149#ifdef _WIN32
6150 auto sock =
6151 WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
6152 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
6167 if (sock == INVALID_SOCKET) {
6168 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6169 }
6170#else
6171
6172#ifdef SOCK_CLOEXEC
6173 auto sock =
6174 socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
6175#else
6176 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6177#endif
6178
6179#endif
6180 if (sock == INVALID_SOCKET) { continue; }
6181
6182#if !defined _WIN32 && !defined SOCK_CLOEXEC
6183 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
6184 close_socket(sock);
6185 continue;
6186 }
6187#endif
6188
6189 if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
6190
6191 if (rp->ai_family == AF_INET6) {
6192 set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
6193 }
6194
6195 if (socket_options) { socket_options(sock); }
6196
6197 // bind or connect
6198 auto quit = false;
6199 if (bind_or_connect(sock, *rp, quit)) { return sock; }
6200
6201 close_socket(sock);
6202
6203 if (quit) { break; }
6204 }
6205
6206 return INVALID_SOCKET;
6207}
6208
6209inline void set_nonblocking(socket_t sock, bool nonblocking) {
6210#ifdef _WIN32
6211 auto flags = nonblocking ? 1UL : 0UL;
6212 ioctlsocket(sock, FIONBIO, &flags);
6213#else
6214 auto flags = fcntl(sock, F_GETFL, 0);
6215 fcntl(sock, F_SETFL,
6216 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
6217#endif
6218}
6219
6220inline bool is_connection_error() {
6221#ifdef _WIN32
6222 return WSAGetLastError() != WSAEWOULDBLOCK;
6223#else
6224 return errno != EINPROGRESS;
6225#endif
6226}
6227
6228inline bool bind_ip_address(socket_t sock, const std::string &host) {
6229 struct addrinfo hints;
6230 struct addrinfo *result;
6231
6232 memset(&hints, 0, sizeof(struct addrinfo));
6233 hints.ai_family = AF_UNSPEC;
6234 hints.ai_socktype = SOCK_STREAM;
6235 hints.ai_protocol = 0;
6236
6237 if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
6238 return false;
6239 }
6240
6241 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6242
6243 auto ret = false;
6244 for (auto rp = result; rp; rp = rp->ai_next) {
6245 const auto &ai = *rp;
6246 if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
6247 ret = true;
6248 break;
6249 }
6250 }
6251
6252 return ret;
6253}
6254
6255#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
6256#define USE_IF2IP
6257#endif
6258
6259#ifdef USE_IF2IP
6260inline std::string if2ip(int address_family, const std::string &ifn) {
6261 struct ifaddrs *ifap;
6262 getifaddrs(&ifap);
6263 auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
6264
6265 std::string addr_candidate;
6266 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
6267 if (ifa->ifa_addr && ifn == ifa->ifa_name &&
6268 (AF_UNSPEC == address_family ||
6269 ifa->ifa_addr->sa_family == address_family)) {
6270 if (ifa->ifa_addr->sa_family == AF_INET) {
6271 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
6272 char buf[INET_ADDRSTRLEN];
6273 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
6274 return std::string(buf, INET_ADDRSTRLEN);
6275 }
6276 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
6277 auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
6278 if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
6279 char buf[INET6_ADDRSTRLEN] = {};
6280 if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
6281 // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
6282 auto s6_addr_head = sa->sin6_addr.s6_addr[0];
6283 if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
6284 addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
6285 } else {
6286 return std::string(buf, INET6_ADDRSTRLEN);
6287 }
6288 }
6289 }
6290 }
6291 }
6292 }
6293 return addr_candidate;
6294}
6295#endif
6296
6298 const std::string &host, const std::string &ip, int port,
6299 int address_family, bool tcp_nodelay, bool ipv6_v6only,
6300 SocketOptions socket_options, time_t connection_timeout_sec,
6301 time_t connection_timeout_usec, time_t read_timeout_sec,
6302 time_t read_timeout_usec, time_t write_timeout_sec,
6303 time_t write_timeout_usec, const std::string &intf, Error &error) {
6304 auto sock = create_socket(
6305 host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
6306 std::move(socket_options),
6307 [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
6308 if (!intf.empty()) {
6309#ifdef USE_IF2IP
6310 auto ip_from_if = if2ip(address_family, intf);
6311 if (ip_from_if.empty()) { ip_from_if = intf; }
6312 if (!bind_ip_address(sock2, ip_from_if)) {
6314 return false;
6315 }
6316#endif
6317 }
6318
6319 set_nonblocking(sock2, true);
6320
6321 auto ret =
6322 ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
6323
6324 if (ret < 0) {
6325 if (is_connection_error()) {
6327 return false;
6328 }
6329 error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
6330 connection_timeout_usec);
6331 if (error != Error::Success) {
6332 if (error == Error::ConnectionTimeout) { quit = true; }
6333 return false;
6334 }
6335 }
6336
6337 set_nonblocking(sock2, false);
6338 set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
6339 read_timeout_usec);
6340 set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
6341 write_timeout_usec);
6342
6344 return true;
6345 },
6346 connection_timeout_sec); // Pass DNS timeout
6347
6348 if (sock != INVALID_SOCKET) {
6350 } else {
6352 }
6353
6354 return sock;
6355}
6356
6357inline bool get_ip_and_port(const struct sockaddr_storage &addr,
6358 socklen_t addr_len, std::string &ip, int &port) {
6359 if (addr.ss_family == AF_INET) {
6360 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
6361 } else if (addr.ss_family == AF_INET6) {
6362 port =
6363 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
6364 } else {
6365 return false;
6366 }
6367
6368 std::array<char, NI_MAXHOST> ipstr{};
6369 if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
6370 ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
6371 0, NI_NUMERICHOST)) {
6372 return false;
6373 }
6374
6375 ip = ipstr.data();
6376 return true;
6377}
6378
6379inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
6380 struct sockaddr_storage addr;
6381 socklen_t addr_len = sizeof(addr);
6382 if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
6383 &addr_len)) {
6384 get_ip_and_port(addr, addr_len, ip, port);
6385 }
6386}
6387
6388inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
6389 struct sockaddr_storage addr;
6390 socklen_t addr_len = sizeof(addr);
6391
6392 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
6393 &addr_len)) {
6394#ifndef _WIN32
6395 if (addr.ss_family == AF_UNIX) {
6396#if defined(__linux__)
6397 struct ucred ucred;
6398 socklen_t len = sizeof(ucred);
6399 if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
6400 port = ucred.pid;
6401 }
6402#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
6403 pid_t pid;
6404 socklen_t len = sizeof(pid);
6405 if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
6406 port = pid;
6407 }
6408#endif
6409 return;
6410 }
6411#endif
6412 get_ip_and_port(addr, addr_len, ip, port);
6413 }
6414}
6415
6416// Recursive form retained so operator""_t below can compute hashes for
6417// switch-case labels at compile time (C++11 constexpr forbids loops). Do not
6418// call from runtime paths with arbitrary-length inputs — use str2tag()
6419// instead, which is iterative and stack-safe.
6420inline constexpr unsigned int str2tag_core(const char *s, size_t l,
6421 unsigned int h) {
6422 return (l == 0)
6423 ? h
6424 : str2tag_core(
6425 s + 1, l - 1,
6426 // Unsets the 6 high bits of h, therefore no overflow happens
6427 (((std::numeric_limits<unsigned int>::max)() >> 6) &
6428 h * 33) ^
6429 static_cast<unsigned char>(*s));
6430}
6431
6432inline unsigned int str2tag(const std::string &s) {
6433 // Iterative form of str2tag_core: the recursive constexpr version is kept
6434 // for compile-time UDL evaluation of short string literals, but at runtime
6435 // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that
6436 // would blow the stack with one frame per character.
6437 unsigned int h = 0;
6438 for (auto c : s) {
6439 h = (((std::numeric_limits<unsigned int>::max)() >> 6) & h * 33) ^
6440 static_cast<unsigned char>(c);
6441 }
6442 return h;
6443}
6444
6445namespace udl {
6446
6447inline constexpr unsigned int operator""_t(const char *s, size_t l) {
6448 return str2tag_core(s, l, 0);
6449}
6450
6451} // namespace udl
6452
6453inline std::string
6454find_content_type(const std::string &path,
6455 const std::map<std::string, std::string> &user_data,
6456 const std::string &default_content_type) {
6457 auto ext = file_extension(path);
6458
6459 auto it = user_data.find(ext);
6460 if (it != user_data.end()) { return it->second; }
6461
6462 using udl::operator""_t;
6463
6464 switch (str2tag(ext)) {
6465 default: return default_content_type;
6466
6467 case "css"_t: return "text/css";
6468 case "csv"_t: return "text/csv";
6469 case "htm"_t:
6470 case "html"_t: return "text/html";
6471 case "js"_t:
6472 case "mjs"_t: return "text/javascript";
6473 case "txt"_t: return "text/plain";
6474 case "vtt"_t: return "text/vtt";
6475
6476 case "apng"_t: return "image/apng";
6477 case "avif"_t: return "image/avif";
6478 case "bmp"_t: return "image/bmp";
6479 case "gif"_t: return "image/gif";
6480 case "png"_t: return "image/png";
6481 case "svg"_t: return "image/svg+xml";
6482 case "webp"_t: return "image/webp";
6483 case "ico"_t: return "image/x-icon";
6484 case "tif"_t: return "image/tiff";
6485 case "tiff"_t: return "image/tiff";
6486 case "jpg"_t:
6487 case "jpeg"_t: return "image/jpeg";
6488
6489 case "mp4"_t: return "video/mp4";
6490 case "mpeg"_t: return "video/mpeg";
6491 case "webm"_t: return "video/webm";
6492
6493 case "mp3"_t: return "audio/mp3";
6494 case "mpga"_t: return "audio/mpeg";
6495 case "weba"_t: return "audio/webm";
6496 case "wav"_t: return "audio/wave";
6497
6498 case "otf"_t: return "font/otf";
6499 case "ttf"_t: return "font/ttf";
6500 case "woff"_t: return "font/woff";
6501 case "woff2"_t: return "font/woff2";
6502
6503 case "7z"_t: return "application/x-7z-compressed";
6504 case "atom"_t: return "application/atom+xml";
6505 case "pdf"_t: return "application/pdf";
6506 case "json"_t: return "application/json";
6507 case "rss"_t: return "application/rss+xml";
6508 case "tar"_t: return "application/x-tar";
6509 case "xht"_t:
6510 case "xhtml"_t: return "application/xhtml+xml";
6511 case "xslt"_t: return "application/xslt+xml";
6512 case "xml"_t: return "application/xml";
6513 case "gz"_t: return "application/gzip";
6514 case "zip"_t: return "application/zip";
6515 case "wasm"_t: return "application/wasm";
6516 }
6517}
6518
6519inline std::string
6520extract_media_type(const std::string &content_type,
6521 std::map<std::string, std::string> *params = nullptr) {
6522 // Extract type/subtype from Content-Type value (RFC 2045)
6523 // e.g. "application/json; charset=utf-8" -> "application/json"
6524 auto media_type = content_type;
6525 auto semicolon_pos = media_type.find(';');
6526 if (semicolon_pos != std::string::npos) {
6527 auto param_str = media_type.substr(semicolon_pos + 1);
6528 media_type = media_type.substr(0, semicolon_pos);
6529
6530 if (params) {
6531 // Parse parameters: key=value pairs separated by ';'
6532 split(param_str.data(), param_str.data() + param_str.size(), ';',
6533 [&](const char *b, const char *e) {
6534 std::string key;
6535 std::string val;
6536 split(b, e, '=', [&](const char *b2, const char *e2) {
6537 if (key.empty()) {
6538 key.assign(b2, e2);
6539 } else {
6540 val.assign(b2, e2);
6541 }
6542 });
6543 if (!key.empty()) {
6544 params->emplace(trim_copy(key), trim_double_quotes_copy(val));
6545 }
6546 });
6547 }
6548 }
6549
6550 // Trim whitespace from media type
6551 return trim_copy(media_type);
6552}
6553
6554inline bool can_compress_content_type(const std::string &content_type) {
6555 using udl::operator""_t;
6556
6557 auto mime_type = extract_media_type(content_type);
6558 auto tag = str2tag(mime_type);
6559
6560 switch (tag) {
6561 case "image/svg+xml"_t:
6562 case "application/javascript"_t:
6563 case "application/x-javascript"_t:
6564 case "application/json"_t:
6565 case "application/ld+json"_t:
6566 case "application/xml"_t:
6567 case "application/xhtml+xml"_t:
6568 case "application/rss+xml"_t:
6569 case "application/atom+xml"_t:
6570 case "application/xslt+xml"_t:
6571 case "application/protobuf"_t: return true;
6572
6573 case "text/event-stream"_t: return false;
6574
6575 default: return !mime_type.rfind("text/", 0);
6576 }
6577}
6578
6579inline bool parse_quality(const char *b, const char *e, std::string &token,
6580 double &quality) {
6581 quality = 1.0;
6582 token.clear();
6583
6584 // Split on first ';': left = token name, right = parameters
6585 const char *params_b = nullptr;
6586 std::size_t params_len = 0;
6587
6588 divide(
6589 b, static_cast<std::size_t>(e - b), ';',
6590 [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
6591 auto r = trim(lb, lb + llen, 0, llen);
6592 if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
6593 params_b = rb;
6594 params_len = rlen;
6595 });
6596
6597 if (token.empty()) { return false; }
6598 if (params_len == 0) { return true; }
6599
6600 // Scan parameters for q= (stops on first match)
6601 bool invalid = false;
6602 split_find(params_b, params_b + params_len, ';',
6603 (std::numeric_limits<size_t>::max)(),
6604 [&](const char *pb, const char *pe) -> bool {
6605 // Match exactly "q=" or "Q=" (not "query=" etc.)
6606 auto len = static_cast<size_t>(pe - pb);
6607 if (len < 2) { return false; }
6608 if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
6609 return false;
6610 }
6611
6612 // Trim the value portion
6613 auto r = trim(pb, pe, 2, len);
6614 if (r.first >= r.second) {
6615 invalid = true;
6616 return true;
6617 }
6618
6619 double v = 0.0;
6620 auto res = from_chars(pb + r.first, pb + r.second, v);
6621 if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
6622 invalid = true;
6623 return true;
6624 }
6625 quality = v;
6626 return true;
6627 });
6628
6629 return !invalid;
6630}
6631
6632inline EncodingType encoding_type(const Request &req, const Response &res) {
6633 if (!can_compress_content_type(res.get_header_value("Content-Type"))) {
6634 return EncodingType::None;
6635 }
6636
6637 const auto &s = req.get_header_value("Accept-Encoding");
6638 if (s.empty()) { return EncodingType::None; }
6639
6640 // Single-pass: iterate tokens and track the best supported encoding.
6641 // Server preference breaks ties (br > gzip > zstd).
6643 double best_q = 0.0; // q=0 means "not acceptable"
6644
6645 // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
6646 auto priority = [](EncodingType t) -> int {
6647 switch (t) {
6648 case EncodingType::Brotli: return 0;
6649 case EncodingType::Gzip: return 1;
6650 case EncodingType::Zstd: return 2;
6651 default: return 3;
6652 }
6653 };
6654
6655 std::string name;
6656 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
6657 double quality = 1.0;
6658 if (!parse_quality(b, e, name, quality)) { return; }
6659 if (quality <= 0.0) { return; }
6660
6662#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6663 if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
6664#endif
6665#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6666 if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
6667 type = EncodingType::Gzip;
6668 }
6669#endif
6670#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6671 if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
6672 type = EncodingType::Zstd;
6673 }
6674#endif
6675
6676 if (type == EncodingType::None) { return; }
6677
6678 // Higher q-value wins; for equal q, server preference breaks ties
6679 if (quality > best_q ||
6680 (quality == best_q && priority(type) < priority(best))) {
6681 best_q = quality;
6682 best = type;
6683 }
6684 });
6685
6686 return best;
6687}
6688
6689inline std::unique_ptr<compressor> make_compressor(EncodingType type) {
6690#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6691 if (type == EncodingType::Gzip) {
6693 }
6694#endif
6695#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6696 if (type == EncodingType::Brotli) {
6698 }
6699#endif
6700#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6701 if (type == EncodingType::Zstd) {
6703 }
6704#endif
6705 (void)type;
6706 return nullptr;
6707}
6708
6709inline const char *encoding_name(EncodingType type) {
6710 switch (type) {
6711 case EncodingType::Gzip: return "gzip";
6712 case EncodingType::Brotli: return "br";
6713 case EncodingType::Zstd: return "zstd";
6714 default: return "";
6715 }
6716}
6717
6718inline bool nocompressor::compress(const char *data, size_t data_length,
6719 bool /*last*/, Callback callback) {
6720 if (!data_length) { return true; }
6721 return callback(data, data_length);
6722}
6723
6724#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6725inline gzip_compressor::gzip_compressor() {
6726 std::memset(&strm_, 0, sizeof(strm_));
6727 strm_.zalloc = Z_NULL;
6728 strm_.zfree = Z_NULL;
6729 strm_.opaque = Z_NULL;
6730
6731 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
6733}
6734
6735inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
6736
6737inline bool gzip_compressor::compress(const char *data, size_t data_length,
6738 bool last, Callback callback) {
6739 assert(is_valid_);
6740
6741 do {
6742 constexpr size_t max_avail_in =
6743 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6744
6745 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6746 (std::min)(data_length, max_avail_in));
6747 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6748
6749 data_length -= strm_.avail_in;
6750 data += strm_.avail_in;
6751
6752 auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
6753 auto ret = Z_OK;
6754
6755 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6756 do {
6757 strm_.avail_out = static_cast<uInt>(buff.size());
6758 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6759
6760 ret = deflate(&strm_, flush);
6761 if (ret == Z_STREAM_ERROR) { return false; }
6762
6763 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6764 return false;
6765 }
6766 } while (strm_.avail_out == 0);
6767
6768 assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
6769 (flush == Z_NO_FLUSH && ret == Z_OK));
6770 assert(strm_.avail_in == 0);
6771 } while (data_length > 0);
6772
6773 return true;
6774}
6775
6776inline gzip_decompressor::gzip_decompressor() {
6777 std::memset(&strm_, 0, sizeof(strm_));
6778 strm_.zalloc = Z_NULL;
6779 strm_.zfree = Z_NULL;
6780 strm_.opaque = Z_NULL;
6781
6782 // 15 is the value of wbits, which should be at the maximum possible value
6783 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
6784 // that the stream type should be automatically detected either gzip or
6785 // deflate.
6786 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
6787}
6788
6789inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
6790
6791inline bool gzip_decompressor::is_valid() const { return is_valid_; }
6792
6793inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
6794 Callback callback) {
6795 assert(is_valid_);
6796
6797 auto ret = Z_OK;
6798
6799 do {
6800 constexpr size_t max_avail_in =
6801 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6802
6803 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6804 (std::min)(data_length, max_avail_in));
6805 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6806
6807 data_length -= strm_.avail_in;
6808 data += strm_.avail_in;
6809
6810 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6811 while (strm_.avail_in > 0 && ret == Z_OK) {
6812 strm_.avail_out = static_cast<uInt>(buff.size());
6813 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6814
6815 ret = inflate(&strm_, Z_NO_FLUSH);
6816
6817 assert(ret != Z_STREAM_ERROR);
6818 switch (ret) {
6819 case Z_NEED_DICT:
6820 case Z_DATA_ERROR:
6821 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
6822 }
6823
6824 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6825 return false;
6826 }
6827 }
6828
6829 if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
6830
6831 } while (data_length > 0);
6832
6833 return true;
6834}
6835#endif
6836
6837#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6838inline brotli_compressor::brotli_compressor() {
6839 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
6840}
6841
6842inline brotli_compressor::~brotli_compressor() {
6843 BrotliEncoderDestroyInstance(state_);
6844}
6845
6846inline bool brotli_compressor::compress(const char *data, size_t data_length,
6847 bool last, Callback callback) {
6848 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6849
6850 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
6851 auto available_in = data_length;
6852 auto next_in = reinterpret_cast<const uint8_t *>(data);
6853
6854 for (;;) {
6855 if (last) {
6856 if (BrotliEncoderIsFinished(state_)) { break; }
6857 } else {
6858 if (!available_in) { break; }
6859 }
6860
6861 auto available_out = buff.size();
6862 auto next_out = buff.data();
6863
6864 if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
6865 &available_out, &next_out, nullptr)) {
6866 return false;
6867 }
6868
6869 auto output_bytes = buff.size() - available_out;
6870 if (output_bytes) {
6871 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
6872 }
6873 }
6874
6875 return true;
6876}
6877
6878inline brotli_decompressor::brotli_decompressor() {
6879 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
6880 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
6881 : BROTLI_DECODER_RESULT_ERROR;
6882}
6883
6884inline brotli_decompressor::~brotli_decompressor() {
6885 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
6886}
6887
6888inline bool brotli_decompressor::is_valid() const { return decoder_s; }
6889
6890inline bool brotli_decompressor::decompress(const char *data,
6891 size_t data_length,
6892 Callback callback) {
6893 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6894 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
6895 return 0;
6896 }
6897
6898 auto next_in = reinterpret_cast<const uint8_t *>(data);
6899 size_t avail_in = data_length;
6900 size_t total_out;
6901
6902 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
6903
6904 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6905 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
6906 char *next_out = buff.data();
6907 size_t avail_out = buff.size();
6908
6909 decoder_r = BrotliDecoderDecompressStream(
6910 decoder_s, &avail_in, &next_in, &avail_out,
6911 reinterpret_cast<uint8_t **>(&next_out), &total_out);
6912
6913 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
6914
6915 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
6916 }
6917
6918 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
6919 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
6920}
6921#endif
6922
6923#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6924inline zstd_compressor::zstd_compressor() {
6925 ctx_ = ZSTD_createCCtx();
6926 ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
6927}
6928
6929inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
6930
6931inline bool zstd_compressor::compress(const char *data, size_t data_length,
6932 bool last, Callback callback) {
6933 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6934
6935 ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
6936 ZSTD_inBuffer input = {data, data_length, 0};
6937
6938 bool finished;
6939 do {
6940 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
6941 size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
6942
6943 if (ZSTD_isError(remaining)) { return false; }
6944
6945 if (!callback(buff.data(), output.pos)) { return false; }
6946
6947 finished = last ? (remaining == 0) : (input.pos == input.size);
6948
6949 } while (!finished);
6950
6951 return true;
6952}
6953
6954inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
6955
6956inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
6957
6958inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
6959
6960inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
6961 Callback callback) {
6962 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6963 ZSTD_inBuffer input = {data, data_length, 0};
6964
6965 while (input.pos < input.size) {
6966 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
6967 size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
6968
6969 if (ZSTD_isError(remaining)) { return false; }
6970
6971 if (!callback(buff.data(), output.pos)) { return false; }
6972 }
6973
6974 return true;
6975}
6976#endif
6977
6978inline std::unique_ptr<decompressor>
6979create_decompressor(const std::string &encoding) {
6980 std::unique_ptr<decompressor> decompressor;
6981
6982 if (encoding == "gzip" || encoding == "deflate") {
6983#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6985#endif
6986 } else if (encoding.find("br") != std::string::npos) {
6987#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6989#endif
6990 } else if (encoding == "zstd" || encoding.find("zstd") != std::string::npos) {
6991#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6993#endif
6994 }
6995
6996 return decompressor;
6997}
6998
6999// Returns the best available compressor and its Content-Encoding name.
7000// Priority: Brotli > Gzip > Zstd (matches server-side preference).
7001inline std::pair<std::unique_ptr<compressor>, const char *>
7003#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7005#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
7006 return {detail::make_unique<gzip_compressor>(), "gzip"};
7007#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
7008 return {detail::make_unique<zstd_compressor>(), "zstd"};
7009#else
7010 return {nullptr, nullptr};
7011#endif
7012}
7013
7014inline bool is_prohibited_header_name(const std::string &name) {
7015 using udl::operator""_t;
7016
7017 switch (str2tag(name)) {
7018 case "REMOTE_ADDR"_t:
7019 case "REMOTE_PORT"_t:
7020 case "LOCAL_ADDR"_t:
7021 case "LOCAL_PORT"_t: return true;
7022 default: return false;
7023 }
7024}
7025
7026inline bool has_header(const Headers &headers, const std::string &key) {
7027 if (is_prohibited_header_name(key)) { return false; }
7028 return headers.find(key) != headers.end();
7029}
7030
7031inline const char *get_header_value(const Headers &headers,
7032 const std::string &key, const char *def,
7033 size_t id) {
7034 if (is_prohibited_header_name(key)) {
7035#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7036 std::string msg = "Prohibited header name '" + key + "' is specified.";
7037 throw std::invalid_argument(msg);
7038#else
7039 return "";
7040#endif
7041 }
7042
7043 auto rng = headers.equal_range(key);
7044 auto it = rng.first;
7045 std::advance(it, static_cast<ssize_t>(id));
7046 if (it != rng.second) { return it->second.c_str(); }
7047 return def;
7048}
7049
7050inline size_t get_header_value_count(const Headers &headers,
7051 const std::string &key) {
7052 auto r = headers.equal_range(key);
7053 return static_cast<size_t>(std::distance(r.first, r.second));
7054}
7055
7056template <typename Map>
7057inline typename Map::mapped_type
7058get_multimap_value(const Map &m, const std::string &key, size_t id) {
7059 auto rng = m.equal_range(key);
7060 auto it = rng.first;
7061 std::advance(it, static_cast<ssize_t>(id));
7062 if (it != rng.second) { return it->second; }
7063 return typename Map::mapped_type();
7064}
7065
7066inline void set_header(Headers &headers, const std::string &key,
7067 const std::string &val) {
7069 headers.emplace(key, val);
7070 }
7071}
7072
7073inline bool read_headers(Stream &strm, Headers &headers) {
7074 const auto bufsiz = 2048;
7075 char buf[bufsiz];
7076 stream_line_reader line_reader(strm, buf, bufsiz);
7077
7078 size_t header_count = 0;
7079
7080 for (;;) {
7081 if (!line_reader.getline()) { return false; }
7082
7083 // Check if the line ends with CRLF.
7084 auto line_terminator_len = 2;
7085 if (line_reader.end_with_crlf()) {
7086 // Blank line indicates end of headers.
7087 if (line_reader.size() == 2) { break; }
7088 } else {
7089#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
7090 // Blank line indicates end of headers.
7091 if (line_reader.size() == 1) { break; }
7092 line_terminator_len = 1;
7093#else
7094 continue; // Skip invalid line.
7095#endif
7096 }
7097
7098 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7099
7100 // Check header count limit
7101 if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
7102
7103 // Exclude line terminator
7104 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
7105
7106 if (!parse_header(line_reader.ptr(), end,
7107 [&](const std::string &key, const std::string &val) {
7108 headers.emplace(key, val);
7109 })) {
7110 return false;
7111 }
7112
7113 header_count++;
7114 }
7115
7116 // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
7117 // headers that have different values to prevent request smuggling.
7118 auto cl_range = headers.equal_range("Content-Length");
7119 if (cl_range.first != cl_range.second) {
7120 const auto &first_val = cl_range.first->second;
7121 for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
7122 if (it->second != first_val) { return false; }
7123 }
7124 }
7125
7126 return true;
7127}
7128
7130 const std::string &expected_accept,
7131 std::string &selected_subprotocol) {
7132 // Read status line
7133 const auto bufsiz = 2048;
7134 char buf[bufsiz];
7135 stream_line_reader line_reader(strm, buf, bufsiz);
7136 if (!line_reader.getline()) { return false; }
7137
7138 // Check for "HTTP/1.1 101"
7139 auto line = std::string(line_reader.ptr(), line_reader.size());
7140 if (line.find("HTTP/1.1 101") == std::string::npos) { return false; }
7141
7142 // Parse headers using existing read_headers
7143 Headers headers;
7144 if (!read_headers(strm, headers)) { return false; }
7145
7146 // Verify Upgrade: websocket (case-insensitive)
7147 auto upgrade_it = headers.find("Upgrade");
7148 if (upgrade_it == headers.end()) { return false; }
7149 auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
7150 if (upgrade_val != "websocket") { return false; }
7151
7152 // Verify Connection header contains "Upgrade" (case-insensitive)
7153 auto connection_it = headers.find("Connection");
7154 if (connection_it == headers.end()) { return false; }
7155 auto connection_val = case_ignore::to_lower(connection_it->second);
7156 if (connection_val.find("upgrade") == std::string::npos) { return false; }
7157
7158 // Verify Sec-WebSocket-Accept header value
7159 auto it = headers.find("Sec-WebSocket-Accept");
7160 if (it == headers.end() || it->second != expected_accept) { return false; }
7161
7162 // Extract negotiated subprotocol
7163 auto proto_it = headers.find("Sec-WebSocket-Protocol");
7164 if (proto_it != headers.end()) { selected_subprotocol = proto_it->second; }
7165
7166 return true;
7167}
7168
7170 Success, // Successfully read the content
7171 PayloadTooLarge, // The content exceeds the specified payload limit
7172 Error // An error occurred while reading the content
7173};
7174
7176 Stream &strm, size_t len, DownloadProgress progress,
7178 size_t payload_max_length = (std::numeric_limits<size_t>::max)()) {
7179 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7180
7182 br.stream = &strm;
7183 br.has_content_length = true;
7184 br.content_length = len;
7185 br.payload_max_length = payload_max_length;
7186 br.chunked = false;
7187 br.bytes_read = 0;
7189
7190 size_t r = 0;
7191 while (r < len) {
7192 auto read_len = static_cast<size_t>(len - r);
7193 auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
7194 auto n = detail::read_body_content(&strm, br, buf, to_read);
7195 if (n <= 0) {
7196 // Check if it was a payload size error
7199 }
7201 }
7202
7203 if (!out(buf, static_cast<size_t>(n), r, len)) {
7205 }
7206 r += static_cast<size_t>(n);
7207
7208 if (progress) {
7209 if (!progress(r, len)) { return ReadContentResult::Error; }
7210 }
7211 }
7212
7214}
7215
7216inline ReadContentResult
7217read_content_without_length(Stream &strm, size_t payload_max_length,
7219 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7220 size_t r = 0;
7221 for (;;) {
7222 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
7223 if (n == 0) { return ReadContentResult::Success; }
7224 if (n < 0) { return ReadContentResult::Error; }
7225
7226 // Check if adding this data would exceed the payload limit
7227 if (r > payload_max_length ||
7228 payload_max_length - r < static_cast<size_t>(n)) {
7230 }
7231
7232 if (!out(buf, static_cast<size_t>(n), r, 0)) {
7234 }
7235 r += static_cast<size_t>(n);
7236 }
7237
7239}
7240
7241template <typename T>
7243 size_t payload_max_length,
7245 detail::ChunkedDecoder dec(strm);
7246
7247 char buf[CPPHTTPLIB_RECV_BUFSIZ];
7248 size_t total_len = 0;
7249
7250 for (;;) {
7251 size_t chunk_offset = 0;
7252 size_t chunk_total = 0;
7253 auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
7254 if (n < 0) { return ReadContentResult::Error; }
7255
7256 if (n == 0) {
7257 if (!dec.parse_trailers_into(x.trailers, x.headers)) {
7259 }
7261 }
7262
7263 if (total_len > payload_max_length ||
7264 payload_max_length - total_len < static_cast<size_t>(n)) {
7266 }
7267
7268 if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
7270 }
7271
7272 total_len += static_cast<size_t>(n);
7273 }
7274}
7275
7276inline bool is_chunked_transfer_encoding(const Headers &headers) {
7277 return case_ignore::equal(
7278 get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
7279}
7280
7281template <typename T, typename U>
7282bool prepare_content_receiver(T &x, int &status,
7284 bool decompress, size_t payload_max_length,
7285 bool &exceed_payload_max_length, U callback) {
7286 if (decompress) {
7287 std::string encoding = x.get_header_value("Content-Encoding");
7288 std::unique_ptr<decompressor> decompressor;
7289
7290 if (!encoding.empty()) {
7292 if (!decompressor) {
7293 // Unsupported encoding or no support compiled in
7295 return false;
7296 }
7297 }
7298
7299 if (decompressor) {
7300 if (decompressor->is_valid()) {
7301 size_t decompressed_size = 0;
7302 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
7303 size_t off, size_t len) {
7304 return decompressor->decompress(
7305 buf, n, [&](const char *buf2, size_t n2) {
7306 // Guard against zip-bomb: check
7307 // decompressed size against limit.
7308 if (payload_max_length > 0 &&
7309 (decompressed_size >= payload_max_length ||
7310 n2 > payload_max_length - decompressed_size)) {
7311 exceed_payload_max_length = true;
7312 return false;
7313 }
7314 decompressed_size += n2;
7315 return receiver(buf2, n2, off, len);
7316 });
7317 };
7318 return callback(std::move(out));
7319 } else {
7321 return false;
7322 }
7323 }
7324 }
7325
7326 ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
7327 size_t len) {
7328 return receiver(buf, n, off, len);
7329 };
7330 return callback(std::move(out));
7331}
7332
7333template <typename T>
7334bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
7335 DownloadProgress progress,
7336 ContentReceiverWithProgress receiver, bool decompress) {
7337 bool exceed_payload_max_length = false;
7339 x, status, std::move(receiver), decompress, payload_max_length,
7340 exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
7341 auto ret = true;
7342 // Note: exceed_payload_max_length may also be set by the decompressor
7343 // wrapper in prepare_content_receiver when the decompressed payload
7344 // size exceeds the limit.
7345
7346 if (is_chunked_transfer_encoding(x.headers)) {
7347 auto result = read_content_chunked(strm, x, payload_max_length, out);
7348 if (result == ReadContentResult::Success) {
7349 ret = true;
7350 } else if (result == ReadContentResult::PayloadTooLarge) {
7351 exceed_payload_max_length = true;
7352 ret = false;
7353 } else {
7354 ret = false;
7355 }
7356 } else if (!has_header(x.headers, "Content-Length")) {
7357 auto result =
7358 read_content_without_length(strm, payload_max_length, out);
7359 if (result == ReadContentResult::Success) {
7360 ret = true;
7361 } else if (result == ReadContentResult::PayloadTooLarge) {
7362 exceed_payload_max_length = true;
7363 ret = false;
7364 } else {
7365 ret = false;
7366 }
7367 } else {
7368 auto is_invalid_value = false;
7369 auto len = get_header_value_u64(x.headers, "Content-Length",
7370 (std::numeric_limits<size_t>::max)(),
7371 0, is_invalid_value);
7372
7373 if (is_invalid_value) {
7374 ret = false;
7375 } else if (len > 0) {
7376 auto result = read_content_with_length(
7377 strm, len, std::move(progress), out, payload_max_length);
7378 ret = (result == ReadContentResult::Success);
7379 if (result == ReadContentResult::PayloadTooLarge) {
7380 exceed_payload_max_length = true;
7381 }
7382 }
7383 }
7384
7385 if (!ret) {
7386 status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
7388 }
7389 return ret;
7390 });
7391}
7392
7393inline ssize_t write_request_line(Stream &strm, const std::string &method,
7394 const std::string &path) {
7395 std::string s = method;
7396 s += ' ';
7397 s += path;
7398 s += " HTTP/1.1\r\n";
7399 return strm.write(s.data(), s.size());
7400}
7401
7402inline ssize_t write_response_line(Stream &strm, int status) {
7403 std::string s = "HTTP/1.1 ";
7404 s += std::to_string(status);
7405 s += ' ';
7406 s += httplib::status_message(status);
7407 s += "\r\n";
7408 return strm.write(s.data(), s.size());
7409}
7410
7411inline ssize_t write_headers(Stream &strm, const Headers &headers) {
7412 ssize_t write_len = 0;
7413 for (const auto &x : headers) {
7414 std::string s;
7415 s = x.first;
7416 s += ": ";
7417 s += x.second;
7418 s += "\r\n";
7419
7420 auto len = strm.write(s.data(), s.size());
7421 if (len < 0) { return len; }
7422 write_len += len;
7423 }
7424 auto len = strm.write("\r\n");
7425 if (len < 0) { return len; }
7426 write_len += len;
7427 return write_len;
7428}
7429
7430inline bool write_data(Stream &strm, const char *d, size_t l) {
7431 size_t offset = 0;
7432 while (offset < l) {
7433 auto length = strm.write(d + offset, l - offset);
7434 if (length < 0) { return false; }
7435 offset += static_cast<size_t>(length);
7436 }
7437 return true;
7438}
7439
7440template <typename T>
7442 const ContentProvider &content_provider,
7443 size_t offset, size_t length,
7444 T is_shutting_down,
7445 const UploadProgress &upload_progress,
7446 Error &error) {
7447 size_t end_offset = offset + length;
7448 size_t start_offset = offset;
7449 auto ok = true;
7450 DataSink data_sink;
7451
7452 data_sink.write = [&](const char *d, size_t l) -> bool {
7453 if (ok) {
7454 if (write_data(strm, d, l)) {
7455 offset += l;
7456
7457 if (upload_progress && length > 0) {
7458 size_t current_written = offset - start_offset;
7459 if (!upload_progress(current_written, length)) {
7460 ok = false;
7461 return false;
7462 }
7463 }
7464 } else {
7465 ok = false;
7466 }
7467 }
7468 return ok;
7469 };
7470
7471 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7472
7473 while (offset < end_offset && !is_shutting_down()) {
7474 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7476 return false;
7477 } else if (!content_provider(offset, end_offset - offset, data_sink)) {
7479 return false;
7480 } else if (!ok) {
7482 return false;
7483 }
7484 }
7485
7486 if (offset < end_offset) { // exited due to is_shutting_down(), not completion
7488 return false;
7489 }
7490
7492 return true;
7493}
7494
7495template <typename T>
7496inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7497 size_t offset, size_t length, T is_shutting_down,
7498 Error &error) {
7499 return write_content_with_progress<T>(strm, content_provider, offset, length,
7500 is_shutting_down, nullptr, error);
7501}
7502
7503template <typename T>
7504inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7505 size_t offset, size_t length,
7506 const T &is_shutting_down) {
7507 auto error = Error::Success;
7508 return write_content(strm, content_provider, offset, length, is_shutting_down,
7509 error);
7510}
7511
7512template <typename T>
7513inline bool
7515 const ContentProvider &content_provider,
7516 const T &is_shutting_down) {
7517 size_t offset = 0;
7518 auto data_available = true;
7519 auto ok = true;
7520 DataSink data_sink;
7521
7522 data_sink.write = [&](const char *d, size_t l) -> bool {
7523 if (ok) {
7524 offset += l;
7525 if (!write_data(strm, d, l)) { ok = false; }
7526 }
7527 return ok;
7528 };
7529
7530 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7531
7532 data_sink.done = [&](void) { data_available = false; };
7533
7534 while (data_available && !is_shutting_down()) {
7535 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7536 return false;
7537 } else if (!content_provider(offset, 0, data_sink)) {
7538 return false;
7539 } else if (!ok) {
7540 return false;
7541 }
7542 }
7543 return !data_available; // true only if done() was called, false if shutting
7544 // down
7545}
7546
7547template <typename T, typename U>
7548inline bool
7549write_content_chunked(Stream &strm, const ContentProvider &content_provider,
7550 const T &is_shutting_down, U &compressor, Error &error) {
7551 size_t offset = 0;
7552 auto data_available = true;
7553 auto ok = true;
7554 DataSink data_sink;
7555
7556 data_sink.write = [&](const char *d, size_t l) -> bool {
7557 if (ok) {
7558 data_available = l > 0;
7559 offset += l;
7560
7561 std::string payload;
7562 if (compressor.compress(d, l, false,
7563 [&](const char *data, size_t data_len) {
7564 payload.append(data, data_len);
7565 return true;
7566 })) {
7567 if (!payload.empty()) {
7568 // Emit chunked response header and footer for each chunk
7569 auto chunk =
7570 from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7571 if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
7572 }
7573 } else {
7574 ok = false;
7575 }
7576 }
7577 return ok;
7578 };
7579
7580 data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7581
7582 auto done_with_trailer = [&](const Headers *trailer) {
7583 if (!ok) { return; }
7584
7585 data_available = false;
7586
7587 std::string payload;
7588 if (!compressor.compress(nullptr, 0, true,
7589 [&](const char *data, size_t data_len) {
7590 payload.append(data, data_len);
7591 return true;
7592 })) {
7593 ok = false;
7594 return;
7595 }
7596
7597 if (!payload.empty()) {
7598 // Emit chunked response header and footer for each chunk
7599 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7600 if (!write_data(strm, chunk.data(), chunk.size())) {
7601 ok = false;
7602 return;
7603 }
7604 }
7605
7606 constexpr const char done_marker[] = "0\r\n";
7607 if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
7608
7609 // Trailer
7610 if (trailer) {
7611 for (const auto &kv : *trailer) {
7612 std::string field_line = kv.first + ": " + kv.second + "\r\n";
7613 if (!write_data(strm, field_line.data(), field_line.size())) {
7614 ok = false;
7615 }
7616 }
7617 }
7618
7619 constexpr const char crlf[] = "\r\n";
7620 if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
7621 };
7622
7623 data_sink.done = [&](void) { done_with_trailer(nullptr); };
7624
7625 data_sink.done_with_trailer = [&](const Headers &trailer) {
7626 done_with_trailer(&trailer);
7627 };
7628
7629 while (data_available && !is_shutting_down()) {
7630 if (!strm.wait_writable() || !strm.is_peer_alive()) {
7632 return false;
7633 } else if (!content_provider(offset, 0, data_sink)) {
7635 return false;
7636 } else if (!ok) {
7638 return false;
7639 }
7640 }
7641
7642 if (data_available) { // exited due to is_shutting_down(), not done()
7644 return false;
7645 }
7646
7648 return true;
7649}
7650
7651template <typename T, typename U>
7653 const ContentProvider &content_provider,
7654 const T &is_shutting_down, U &compressor) {
7655 auto error = Error::Success;
7656 return write_content_chunked(strm, content_provider, is_shutting_down,
7657 compressor, error);
7658}
7659
7660template <typename T>
7661inline bool redirect(T &cli, Request &req, Response &res,
7662 const std::string &path, const std::string &location,
7663 Error &error) {
7664 Request new_req = req;
7665 new_req.path = path;
7666 new_req.redirect_count_ -= 1;
7667
7668 if (res.status == StatusCode::SeeOther_303 &&
7669 (req.method != "GET" && req.method != "HEAD")) {
7670 new_req.method = "GET";
7671 new_req.body.clear();
7672 new_req.headers.clear();
7673 }
7674
7675 Response new_res;
7676
7677 auto ret = cli.send(new_req, new_res, error);
7678 if (ret) {
7679 req = std::move(new_req);
7680 res = std::move(new_res);
7681
7682 if (res.location.empty()) { res.location = location; }
7683 }
7684 return ret;
7685}
7686
7687inline std::string params_to_query_str(const Params &params) {
7688 std::string query;
7689
7690 for (auto it = params.begin(); it != params.end(); ++it) {
7691 if (it != params.begin()) { query += '&'; }
7692 query += encode_query_component(it->first);
7693 query += '=';
7694 query += encode_query_component(it->second);
7695 }
7696 return query;
7697}
7698
7699inline void parse_query_text(const char *data, std::size_t size,
7700 Params &params) {
7701 std::set<std::string> cache;
7702 split(data, data + size, '&', [&](const char *b, const char *e) {
7703 std::string kv(b, e);
7704 if (cache.find(kv) != cache.end()) { return; }
7705 cache.insert(std::move(kv));
7706
7707 std::string key;
7708 std::string val;
7709 divide(b, static_cast<std::size_t>(e - b), '=',
7710 [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
7711 std::size_t rhs_size) {
7712 key.assign(lhs_data, lhs_size);
7713 val.assign(rhs_data, rhs_size);
7714 });
7715
7716 if (!key.empty()) {
7717 params.emplace(decode_query_component(key), decode_query_component(val));
7718 }
7719 });
7720}
7721
7722inline void parse_query_text(const std::string &s, Params &params) {
7723 parse_query_text(s.data(), s.size(), params);
7724}
7725
7726// Normalize a query string by decoding and re-encoding each key/value pair
7727// while preserving the original parameter order. This avoids double-encoding
7728// and ensures consistent encoding without reordering (unlike Params which
7729// uses std::multimap and sorts keys).
7730inline std::string normalize_query_string(const std::string &query) {
7731 std::string result;
7732 split(query.data(), query.data() + query.size(), '&',
7733 [&](const char *b, const char *e) {
7734 std::string key;
7735 std::string val;
7736 divide(b, static_cast<std::size_t>(e - b), '=',
7737 [&](const char *lhs_data, std::size_t lhs_size,
7738 const char *rhs_data, std::size_t rhs_size) {
7739 key.assign(lhs_data, lhs_size);
7740 val.assign(rhs_data, rhs_size);
7741 });
7742
7743 if (!key.empty()) {
7744 auto dec_key = decode_query_component(key);
7745 auto dec_val = decode_query_component(val);
7746
7747 if (!result.empty()) { result += '&'; }
7748 result += encode_query_component(dec_key);
7749 if (!val.empty() || std::find(b, e, '=') != e) {
7750 result += '=';
7751 result += encode_query_component(dec_val);
7752 }
7753 }
7754 });
7755 return result;
7756}
7757
7758inline bool parse_multipart_boundary(const std::string &content_type,
7759 std::string &boundary) {
7760 std::map<std::string, std::string> params;
7761 extract_media_type(content_type, &params);
7762 auto it = params.find("boundary");
7763 if (it == params.end()) { return false; }
7764 boundary = it->second;
7765 return !boundary.empty();
7766}
7767
7768inline void parse_disposition_params(const std::string &s, Params &params) {
7769 std::set<std::string> cache;
7770 split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
7771 std::string kv(b, e);
7772 if (cache.find(kv) != cache.end()) { return; }
7773 cache.insert(kv);
7774
7775 std::string key;
7776 std::string val;
7777 split(b, e, '=', [&](const char *b2, const char *e2) {
7778 if (key.empty()) {
7779 key.assign(b2, e2);
7780 } else {
7781 val.assign(b2, e2);
7782 }
7783 });
7784
7785 if (!key.empty()) {
7786 params.emplace(trim_double_quotes_copy((key)),
7787 trim_double_quotes_copy((val)));
7788 }
7789 });
7790}
7791
7792#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7793inline bool parse_range_header(const std::string &s, Ranges &ranges) {
7794#else
7795inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
7796#endif
7797 auto is_valid = [](const std::string &str) {
7798 return std::all_of(str.cbegin(), str.cend(),
7799 [](unsigned char c) { return std::isdigit(c); });
7800 };
7801
7802 if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
7803 const auto pos = static_cast<size_t>(6);
7804 const auto len = static_cast<size_t>(s.size() - 6);
7805 auto all_valid_ranges = true;
7806 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
7807 if (!all_valid_ranges) { return; }
7808
7809 const auto it = std::find(b, e, '-');
7810 if (it == e) {
7811 all_valid_ranges = false;
7812 return;
7813 }
7814
7815 const auto lhs = std::string(b, it);
7816 const auto rhs = std::string(it + 1, e);
7817 if (!is_valid(lhs) || !is_valid(rhs)) {
7818 all_valid_ranges = false;
7819 return;
7820 }
7821
7822 ssize_t first = -1;
7823 if (!lhs.empty()) {
7824 ssize_t v;
7825 auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
7826 if (res.ec == std::errc{}) { first = v; }
7827 }
7828
7829 ssize_t last = -1;
7830 if (!rhs.empty()) {
7831 ssize_t v;
7832 auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
7833 if (res.ec == std::errc{}) { last = v; }
7834 }
7835
7836 if ((first == -1 && last == -1) ||
7837 (first != -1 && last != -1 && first > last)) {
7838 all_valid_ranges = false;
7839 return;
7840 }
7841
7842 ranges.emplace_back(first, last);
7843 });
7844 return all_valid_ranges && !ranges.empty();
7845 }
7846 return false;
7847#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7848}
7849#else
7850} catch (...) { return false; }
7851#endif
7852
7853inline bool parse_accept_header(const std::string &s,
7854 std::vector<std::string> &content_types) {
7855 content_types.clear();
7856
7857 // Empty string is considered valid (no preference)
7858 if (s.empty()) { return true; }
7859
7860 // Check for invalid patterns: leading/trailing commas or consecutive commas
7861 if (s.front() == ',' || s.back() == ',' ||
7862 s.find(",,") != std::string::npos) {
7863 return false;
7864 }
7865
7866 struct AcceptEntry {
7867 std::string media_type;
7868 double quality;
7869 int order;
7870 };
7871
7872 std::vector<AcceptEntry> entries;
7873 int order = 0;
7874 bool has_invalid_entry = false;
7875
7876 // Split by comma and parse each entry
7877 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
7878 std::string entry(b, e);
7879 entry = trim_copy(entry);
7880
7881 if (entry.empty()) {
7882 has_invalid_entry = true;
7883 return;
7884 }
7885
7886 AcceptEntry accept_entry;
7887 accept_entry.order = order++;
7888
7889 if (!parse_quality(entry.data(), entry.data() + entry.size(),
7890 accept_entry.media_type, accept_entry.quality)) {
7891 has_invalid_entry = true;
7892 return;
7893 }
7894
7895 // Remove additional parameters from media type
7896 accept_entry.media_type = extract_media_type(accept_entry.media_type);
7897
7898 // Basic validation of media type format
7899 if (accept_entry.media_type.empty()) {
7900 has_invalid_entry = true;
7901 return;
7902 }
7903
7904 // Check for basic media type format (should contain '/' or be '*')
7905 if (accept_entry.media_type != "*" &&
7906 accept_entry.media_type.find('/') == std::string::npos) {
7907 has_invalid_entry = true;
7908 return;
7909 }
7910
7911 entries.push_back(std::move(accept_entry));
7912 });
7913
7914 // Return false if any invalid entry was found
7915 if (has_invalid_entry) { return false; }
7916
7917 // Sort by quality (descending), then by original order (ascending)
7918 std::sort(entries.begin(), entries.end(),
7919 [](const AcceptEntry &a, const AcceptEntry &b) {
7920 if (a.quality != b.quality) {
7921 return a.quality > b.quality; // Higher quality first
7922 }
7923 return a.order < b.order; // Earlier order first for same quality
7924 });
7925
7926 // Extract sorted media types
7927 content_types.reserve(entries.size());
7928 for (auto &entry : entries) {
7929 content_types.push_back(std::move(entry.media_type));
7930 }
7931
7932 return true;
7933}
7934
7936public:
7937 FormDataParser() = default;
7938
7939 void set_boundary(std::string &&boundary) {
7940 boundary_ = std::move(boundary);
7941 dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
7942 crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
7943 }
7944
7945 bool is_valid() const { return is_valid_; }
7946
7947 bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
7948 const ContentReceiver &content_callback) {
7949
7950 buf_append(buf, n);
7951
7952 while (buf_size() > 0) {
7953 switch (state_) {
7954 case 0: { // Initial boundary
7955 auto pos = buf_find(dash_boundary_crlf_);
7956 if (pos == buf_size()) { return true; }
7957 buf_erase(pos + dash_boundary_crlf_.size());
7958 state_ = 1;
7959 break;
7960 }
7961 case 1: { // New entry
7962 clear_file_info();
7963 state_ = 2;
7964 break;
7965 }
7966 case 2: { // Headers
7967 auto pos = buf_find(crlf_);
7968 if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7969 while (pos < buf_size()) {
7970 // Empty line
7971 if (pos == 0) {
7972 if (!header_callback(file_)) {
7973 is_valid_ = false;
7974 return false;
7975 }
7976 buf_erase(crlf_.size());
7977 state_ = 3;
7978 break;
7979 }
7980
7981 const auto header = buf_head(pos);
7982
7983 if (!parse_header(header.data(), header.data() + header.size(),
7984 [&](const std::string &, const std::string &) {})) {
7985 is_valid_ = false;
7986 return false;
7987 }
7988
7989 // Parse and emplace space trimmed headers into a map
7990 if (!parse_header(
7991 header.data(), header.data() + header.size(),
7992 [&](const std::string &key, const std::string &val) {
7993 file_.headers.emplace(key, val);
7994 })) {
7995 is_valid_ = false;
7996 return false;
7997 }
7998
7999 constexpr const char header_content_type[] = "Content-Type:";
8000
8001 if (start_with_case_ignore(header, header_content_type)) {
8002 file_.content_type =
8003 trim_copy(header.substr(str_len(header_content_type)));
8004 } else {
8005 std::string disposition_params;
8006 if (parse_content_disposition(header, disposition_params)) {
8007 Params params;
8008 parse_disposition_params(disposition_params, params);
8009
8010 auto it = params.find("name");
8011 if (it != params.end()) {
8012 file_.name = it->second;
8013 } else {
8014 is_valid_ = false;
8015 return false;
8016 }
8017
8018 it = params.find("filename");
8019 if (it != params.end()) { file_.filename = it->second; }
8020
8021 it = params.find("filename*");
8022 if (it != params.end()) {
8023 // RFC 5987: only UTF-8 encoding is allowed
8024 const auto &val = it->second;
8025 constexpr const char utf8_prefix[] = "UTF-8''";
8026 constexpr size_t prefix_len = str_len(utf8_prefix);
8027 if (val.size() > prefix_len &&
8028 start_with_case_ignore(val, utf8_prefix)) {
8029 file_.filename = decode_path_component(
8030 val.substr(prefix_len)); // override...
8031 } else {
8032 is_valid_ = false;
8033 return false;
8034 }
8035 }
8036 }
8037 }
8038 buf_erase(pos + crlf_.size());
8039 pos = buf_find(crlf_);
8040 }
8041 if (state_ != 3) { return true; }
8042 break;
8043 }
8044 case 3: { // Body
8045 if (crlf_dash_boundary_.size() > buf_size()) { return true; }
8046 auto pos = buf_find(crlf_dash_boundary_);
8047 if (pos < buf_size()) {
8048 if (!content_callback(buf_data(), pos)) {
8049 is_valid_ = false;
8050 return false;
8051 }
8052 buf_erase(pos + crlf_dash_boundary_.size());
8053 state_ = 4;
8054 } else {
8055 auto len = buf_size() - crlf_dash_boundary_.size();
8056 if (len > 0) {
8057 if (!content_callback(buf_data(), len)) {
8058 is_valid_ = false;
8059 return false;
8060 }
8061 buf_erase(len);
8062 }
8063 return true;
8064 }
8065 break;
8066 }
8067 case 4: { // Boundary
8068 if (crlf_.size() > buf_size()) { return true; }
8069 if (buf_start_with(crlf_)) {
8070 buf_erase(crlf_.size());
8071 state_ = 1;
8072 } else {
8073 if (dash_.size() > buf_size()) { return true; }
8074 if (buf_start_with(dash_)) {
8075 buf_erase(dash_.size());
8076 is_valid_ = true;
8077 buf_erase(buf_size()); // Remove epilogue
8078 } else {
8079 return true;
8080 }
8081 }
8082 break;
8083 }
8084 }
8085 }
8086
8087 return true;
8088 }
8089
8090private:
8091 void clear_file_info() {
8092 file_.name.clear();
8093 file_.filename.clear();
8094 file_.content_type.clear();
8095 file_.headers.clear();
8096 }
8097
8098 bool start_with_case_ignore(const std::string &a, const char *b,
8099 size_t offset = 0) const {
8100 const auto b_len = strlen(b);
8101 if (a.size() < offset + b_len) { return false; }
8102 for (size_t i = 0; i < b_len; i++) {
8103 if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) {
8104 return false;
8105 }
8106 }
8107 return true;
8108 }
8109
8110 // Parses "Content-Disposition: form-data; <params>" without std::regex.
8111 // Returns true if header matches, with the params portion in `params_out`.
8112 bool parse_content_disposition(const std::string &header,
8113 std::string &params_out) const {
8114 constexpr const char prefix[] = "Content-Disposition:";
8115 constexpr size_t prefix_len = str_len(prefix);
8116
8117 if (!start_with_case_ignore(header, prefix)) { return false; }
8118
8119 // Skip whitespace after "Content-Disposition:"
8120 auto pos = prefix_len;
8121 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8122 pos++;
8123 }
8124
8125 // Match "form-data;" (case-insensitive)
8126 constexpr const char form_data[] = "form-data;";
8127 constexpr size_t form_data_len = str_len(form_data);
8128 if (!start_with_case_ignore(header, form_data, pos)) { return false; }
8129 pos += form_data_len;
8130
8131 // Skip whitespace after "form-data;"
8132 while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8133 pos++;
8134 }
8135
8136 params_out = header.substr(pos);
8137 return true;
8138 }
8139
8140 const std::string dash_ = "--";
8141 const std::string crlf_ = "\r\n";
8142 std::string boundary_;
8143 std::string dash_boundary_crlf_;
8144 std::string crlf_dash_boundary_;
8145
8146 size_t state_ = 0;
8147 bool is_valid_ = false;
8148 FormData file_;
8149
8150 // Buffer
8151 bool start_with(const std::string &a, size_t spos, size_t epos,
8152 const std::string &b) const {
8153 if (epos - spos < b.size()) { return false; }
8154 for (size_t i = 0; i < b.size(); i++) {
8155 if (a[i + spos] != b[i]) { return false; }
8156 }
8157 return true;
8158 }
8159
8160 size_t buf_size() const { return buf_epos_ - buf_spos_; }
8161
8162 const char *buf_data() const { return &buf_[buf_spos_]; }
8163
8164 std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
8165
8166 bool buf_start_with(const std::string &s) const {
8167 return start_with(buf_, buf_spos_, buf_epos_, s);
8168 }
8169
8170 size_t buf_find(const std::string &s) const {
8171 auto c = s.front();
8172
8173 size_t off = buf_spos_;
8174 while (off < buf_epos_) {
8175 auto pos = off;
8176 while (true) {
8177 if (pos == buf_epos_) { return buf_size(); }
8178 if (buf_[pos] == c) { break; }
8179 pos++;
8180 }
8181
8182 auto remaining_size = buf_epos_ - pos;
8183 if (s.size() > remaining_size) { return buf_size(); }
8184
8185 if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
8186
8187 off = pos + 1;
8188 }
8189
8190 return buf_size();
8191 }
8192
8193 void buf_append(const char *data, size_t n) {
8194 auto remaining_size = buf_size();
8195 if (remaining_size > 0 && buf_spos_ > 0) {
8196 for (size_t i = 0; i < remaining_size; i++) {
8197 buf_[i] = buf_[buf_spos_ + i];
8198 }
8199 }
8200 buf_spos_ = 0;
8201 buf_epos_ = remaining_size;
8202
8203 if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
8204
8205 for (size_t i = 0; i < n; i++) {
8206 buf_[buf_epos_ + i] = data[i];
8207 }
8208 buf_epos_ += n;
8209 }
8210
8211 void buf_erase(size_t size) { buf_spos_ += size; }
8212
8213 std::string buf_;
8214 size_t buf_spos_ = 0;
8215 size_t buf_epos_ = 0;
8216};
8217
8218inline std::string random_string(size_t length) {
8219 constexpr const char data[] =
8220 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8221
8222 thread_local auto engine([]() {
8223 // std::random_device might actually be deterministic on some
8224 // platforms, but due to lack of support in the c++ standard library,
8225 // doing better requires either some ugly hacks or breaking portability.
8226 std::random_device seed_gen;
8227 // Request 128 bits of entropy for initialization
8228 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
8229 return std::mt19937(seed_sequence);
8230 }());
8231
8232 std::string result;
8233 for (size_t i = 0; i < length; i++) {
8234 result += data[engine() % (sizeof(data) - 1)];
8235 }
8236 return result;
8237}
8238
8239inline std::string make_multipart_data_boundary() {
8240 return "--cpp-httplib-multipart-data-" + detail::random_string(16);
8241}
8242
8243inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
8244 auto valid = true;
8245 for (size_t i = 0; i < boundary.size(); i++) {
8246 auto c = boundary[i];
8247 if (!std::isalnum(c) && c != '-' && c != '_') {
8248 valid = false;
8249 break;
8250 }
8251 }
8252 return valid;
8253}
8254
8255template <typename T>
8256inline std::string
8258 const std::string &boundary) {
8259 std::string body = "--" + boundary + "\r\n";
8260 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
8261 if (!item.filename.empty()) {
8262 body += "; filename=\"" + item.filename + "\"";
8263 }
8264 body += "\r\n";
8265 if (!item.content_type.empty()) {
8266 body += "Content-Type: " + item.content_type + "\r\n";
8267 }
8268 body += "\r\n";
8269
8270 return body;
8271}
8272
8273inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
8274
8275inline std::string
8276serialize_multipart_formdata_finish(const std::string &boundary) {
8277 return "--" + boundary + "--\r\n";
8278}
8279
8280inline std::string
8282 return "multipart/form-data; boundary=" + boundary;
8283}
8284
8285inline std::string
8287 const std::string &boundary, bool finish = true) {
8288 std::string body;
8289
8290 for (const auto &item : items) {
8291 body += serialize_multipart_formdata_item_begin(item, boundary);
8292 body += item.content + serialize_multipart_formdata_item_end();
8293 }
8294
8295 if (finish) { body += serialize_multipart_formdata_finish(boundary); }
8296
8297 return body;
8298}
8299
8301 const std::string &boundary) {
8302 size_t total = 0;
8303 for (const auto &item : items) {
8304 total += serialize_multipart_formdata_item_begin(item, boundary).size();
8305 total += item.content.size();
8306 total += serialize_multipart_formdata_item_end().size();
8307 }
8308 total += serialize_multipart_formdata_finish(boundary).size();
8309 return total;
8310}
8311
8313 const char *data;
8314 size_t size;
8315};
8316
8317// NOTE: items must outlive the returned ContentProvider
8318// (safe for synchronous use inside Post/Put/Patch)
8319inline ContentProvider
8321 const std::string &boundary) {
8322 // Own the per-item header strings and the finish string
8323 std::vector<std::string> owned;
8324 owned.reserve(items.size() + 1);
8325 for (const auto &item : items)
8326 owned.push_back(serialize_multipart_formdata_item_begin(item, boundary));
8327 owned.push_back(serialize_multipart_formdata_finish(boundary));
8328
8329 // Flat segment list: [header, content, "\r\n"] * N + [finish]
8330 std::vector<MultipartSegment> segs;
8331 segs.reserve(items.size() * 3 + 1);
8332 static const char crlf[] = "\r\n";
8333 for (size_t i = 0; i < items.size(); i++) {
8334 segs.push_back({owned[i].data(), owned[i].size()});
8335 segs.push_back({items[i].content.data(), items[i].content.size()});
8336 segs.push_back({crlf, 2});
8337 }
8338 segs.push_back({owned.back().data(), owned.back().size()});
8339
8340 struct MultipartState {
8341 std::vector<std::string> owned;
8342 std::vector<MultipartSegment> segs;
8343 std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
8344 };
8345 auto state = std::make_shared<MultipartState>();
8346 state->owned = std::move(owned);
8347 // `segs` holds raw pointers into owned strings; std::string move preserves
8348 // the data pointer, so these pointers remain valid after the move above.
8349 state->segs = std::move(segs);
8350
8351 return [state](size_t offset, size_t length, DataSink &sink) -> bool {
8352 // Buffer multiple small segments into fewer, larger writes to avoid
8353 // excessive TCP packets when there are many form data items (#2410)
8354 auto &buf = state->buf;
8355 auto buf_size = buf.size();
8356 size_t buf_len = 0;
8357 size_t remaining = length;
8358
8359 // Find the first segment containing 'offset'
8360 size_t pos = 0;
8361 size_t seg_idx = 0;
8362 for (; seg_idx < state->segs.size(); seg_idx++) {
8363 const auto &seg = state->segs[seg_idx];
8364 if (seg.size > 0 && offset - pos < seg.size) { break; }
8365 pos += seg.size;
8366 }
8367
8368 size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0;
8369
8370 for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) {
8371 const auto &seg = state->segs[seg_idx];
8372 size_t available = seg.size - seg_offset;
8373 size_t to_copy = (std::min)(available, remaining);
8374 const char *src = seg.data + seg_offset;
8375 seg_offset = 0; // only the first segment has a non-zero offset
8376
8377 while (to_copy > 0) {
8378 size_t space = buf_size - buf_len;
8379 size_t chunk = (std::min)(to_copy, space);
8380 std::memcpy(buf.data() + buf_len, src, chunk);
8381 buf_len += chunk;
8382 src += chunk;
8383 to_copy -= chunk;
8384 remaining -= chunk;
8385
8386 if (buf_len == buf_size) {
8387 if (!sink.write(buf.data(), buf_len)) { return false; }
8388 buf_len = 0;
8389 }
8390 }
8391 }
8392
8393 if (buf_len > 0) { return sink.write(buf.data(), buf_len); }
8394 return true;
8395 };
8396}
8397
8398inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
8399 if (ranges.size() <= 1) return;
8400
8401 // Sort ranges by start position
8402 std::sort(ranges.begin(), ranges.end(),
8403 [](const Range &a, const Range &b) { return a.first < b.first; });
8404
8405 Ranges coalesced;
8406 coalesced.reserve(ranges.size());
8407
8408 for (auto &r : ranges) {
8409 auto first_pos = r.first;
8410 auto last_pos = r.second;
8411
8412 // Handle special cases like in range_error
8413 if (first_pos == -1 && last_pos == -1) {
8414 first_pos = 0;
8415 last_pos = static_cast<ssize_t>(content_length);
8416 }
8417
8418 if (first_pos == -1) {
8419 first_pos = static_cast<ssize_t>(content_length) - last_pos;
8420 last_pos = static_cast<ssize_t>(content_length) - 1;
8421 }
8422
8423 if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
8424 last_pos = static_cast<ssize_t>(content_length) - 1;
8425 }
8426
8427 // Skip invalid ranges
8428 if (!(0 <= first_pos && first_pos <= last_pos &&
8429 last_pos < static_cast<ssize_t>(content_length))) {
8430 continue;
8431 }
8432
8433 // Coalesce with previous range if overlapping or adjacent (but not
8434 // identical)
8435 if (!coalesced.empty()) {
8436 auto &prev = coalesced.back();
8437 // Check if current range overlaps or is adjacent to previous range
8438 // but don't coalesce identical ranges (allow duplicates)
8439 if (first_pos <= prev.second + 1 &&
8440 !(first_pos == prev.first && last_pos == prev.second)) {
8441 // Extend the previous range
8442 prev.second = (std::max)(prev.second, last_pos);
8443 continue;
8444 }
8445 }
8446
8447 // Add new range
8448 coalesced.emplace_back(first_pos, last_pos);
8449 }
8450
8451 ranges = std::move(coalesced);
8452}
8453
8454inline bool range_error(Request &req, Response &res) {
8455 if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
8456 if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
8457 req.ranges.clear();
8460 }
8461 return false;
8462 }
8463
8464 ssize_t content_len = static_cast<ssize_t>(
8465 res.content_length_ ? res.content_length_ : res.body.size());
8466
8467 std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
8468 size_t overwrapping_count = 0;
8469
8470 // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
8471 // 'HTTP Semantics' to avoid potential denial-of-service attacks.
8472 // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
8473
8474 // Too many ranges
8475 if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
8476
8477 for (auto &r : req.ranges) {
8478 auto &first_pos = r.first;
8479 auto &last_pos = r.second;
8480
8481 if (first_pos == -1 && last_pos == -1) {
8482 first_pos = 0;
8483 last_pos = content_len;
8484 }
8485
8486 if (first_pos == -1) {
8487 first_pos = content_len - last_pos;
8488 last_pos = content_len - 1;
8489 }
8490
8491 // NOTE: RFC-9110 '14.1.2. Byte Ranges':
8492 // A client can limit the number of bytes requested without knowing the
8493 // size of the selected representation. If the last-pos value is absent,
8494 // or if the value is greater than or equal to the current length of the
8495 // representation data, the byte range is interpreted as the remainder of
8496 // the representation (i.e., the server replaces the value of last-pos
8497 // with a value that is one less than the current length of the selected
8498 // representation).
8499 // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
8500 if (last_pos == -1 || last_pos >= content_len) {
8501 last_pos = content_len - 1;
8502 }
8503
8504 // Range must be within content length
8505 if (!(0 <= first_pos && first_pos <= last_pos &&
8506 last_pos <= content_len - 1)) {
8507 return true;
8508 }
8509
8510 // Request must not have more than two overlapping ranges
8511 for (const auto &processed_range : processed_ranges) {
8512 if (!(last_pos < processed_range.first ||
8513 first_pos > processed_range.second)) {
8514 overwrapping_count++;
8515 if (overwrapping_count > 2) { return true; }
8516 break; // Only count once per range
8517 }
8518 }
8519
8520 processed_ranges.emplace_back(first_pos, last_pos);
8521 }
8522
8523 // After validation, coalesce overlapping ranges as per RFC 9110
8524 coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
8525 }
8526
8527 return false;
8528}
8529
8530inline std::pair<size_t, size_t>
8531get_range_offset_and_length(Range r, size_t content_length) {
8532 assert(r.first != -1 && r.second != -1);
8533 assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
8534 assert(r.first <= r.second &&
8535 r.second < static_cast<ssize_t>(content_length));
8536 (void)(content_length);
8537 return std::make_pair(static_cast<size_t>(r.first),
8538 static_cast<size_t>(r.second - r.first) + 1);
8539}
8540
8542 const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
8543 auto st = offset_and_length.first;
8544 auto ed = st + offset_and_length.second - 1;
8545
8546 std::string field = "bytes ";
8547 field += std::to_string(st);
8548 field += '-';
8549 field += std::to_string(ed);
8550 field += '/';
8551 field += std::to_string(content_length);
8552 return field;
8553}
8554
8555template <typename SToken, typename CToken, typename Content>
8557 const std::string &boundary,
8558 const std::string &content_type,
8559 size_t content_length, SToken stoken,
8560 CToken ctoken, Content content) {
8561 for (size_t i = 0; i < req.ranges.size(); i++) {
8562 ctoken("--");
8563 stoken(boundary);
8564 ctoken("\r\n");
8565 if (!content_type.empty()) {
8566 ctoken("Content-Type: ");
8567 stoken(content_type);
8568 ctoken("\r\n");
8569 }
8570
8571 auto offset_and_length =
8572 get_range_offset_and_length(req.ranges[i], content_length);
8573
8574 ctoken("Content-Range: ");
8575 stoken(make_content_range_header_field(offset_and_length, content_length));
8576 ctoken("\r\n");
8577 ctoken("\r\n");
8578
8579 if (!content(offset_and_length.first, offset_and_length.second)) {
8580 return false;
8581 }
8582 ctoken("\r\n");
8583 }
8584
8585 ctoken("--");
8586 stoken(boundary);
8587 ctoken("--");
8588
8589 return true;
8590}
8591
8592inline void make_multipart_ranges_data(const Request &req, Response &res,
8593 const std::string &boundary,
8594 const std::string &content_type,
8595 size_t content_length,
8596 std::string &data) {
8598 req, boundary, content_type, content_length,
8599 [&](const std::string &token) { data += token; },
8600 [&](const std::string &token) { data += token; },
8601 [&](size_t offset, size_t length) {
8602 assert(offset + length <= content_length);
8603 data += res.body.substr(offset, length);
8604 return true;
8605 });
8606}
8607
8609 const std::string &boundary,
8610 const std::string &content_type,
8611 size_t content_length) {
8612 size_t data_length = 0;
8613
8615 req, boundary, content_type, content_length,
8616 [&](const std::string &token) { data_length += token.size(); },
8617 [&](const std::string &token) { data_length += token.size(); },
8618 [&](size_t /*offset*/, size_t length) {
8619 data_length += length;
8620 return true;
8621 });
8622
8623 return data_length;
8624}
8625
8626template <typename T>
8627inline bool
8629 const std::string &boundary,
8630 const std::string &content_type,
8631 size_t content_length, const T &is_shutting_down) {
8633 req, boundary, content_type, content_length,
8634 [&](const std::string &token) { strm.write(token); },
8635 [&](const std::string &token) { strm.write(token); },
8636 [&](size_t offset, size_t length) {
8637 return write_content(strm, res.content_provider_, offset, length,
8638 is_shutting_down);
8639 });
8640}
8641
8642inline bool has_framed_body(const Request &req) {
8644 req.get_header_value_u64("Content-Length") > 0;
8645}
8646
8647inline bool is_connection_persistent(const Request &req) {
8648 auto conn = req.get_header_value("Connection");
8649 if (conn == "close") { return false; }
8650 if (req.version == "HTTP/1.0" && conn != "Keep-Alive") { return false; }
8651 return true;
8652}
8653
8654inline bool expect_content(const Request &req) {
8655 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
8656 req.method == "DELETE") {
8657 return true;
8658 }
8659 return has_framed_body(req);
8660}
8661
8662#ifdef _WIN32
8663class WSInit {
8664public:
8665 WSInit() {
8666 WSADATA wsaData;
8667 if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
8668 }
8669
8670 ~WSInit() {
8671 if (is_valid_) WSACleanup();
8672 }
8673
8674 bool is_valid_ = false;
8675};
8676
8677static WSInit wsinit_;
8678#endif
8679
8680inline bool parse_www_authenticate(const Response &res,
8681 std::map<std::string, std::string> &auth,
8682 bool is_proxy) {
8683 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
8684 if (res.has_header(auth_key)) {
8685 thread_local auto re =
8686 std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
8687 auto s = res.get_header_value(auth_key);
8688 auto pos = s.find(' ');
8689 if (pos != std::string::npos) {
8690 auto type = s.substr(0, pos);
8691 if (type == "Basic") {
8692 return false;
8693 } else if (type == "Digest") {
8694 s = s.substr(pos + 1);
8695 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
8696 for (auto i = beg; i != std::sregex_iterator(); ++i) {
8697 const auto &m = *i;
8698 auto key = s.substr(static_cast<size_t>(m.position(1)),
8699 static_cast<size_t>(m.length(1)));
8700 auto val = m.length(2) > 0
8701 ? s.substr(static_cast<size_t>(m.position(2)),
8702 static_cast<size_t>(m.length(2)))
8703 : s.substr(static_cast<size_t>(m.position(3)),
8704 static_cast<size_t>(m.length(3)));
8705 auth[std::move(key)] = std::move(val);
8706 }
8707 return true;
8708 }
8709 }
8710 }
8711 return false;
8712}
8713
8715public:
8717 ContentProviderWithoutLength &&content_provider)
8718 : content_provider_(std::move(content_provider)) {}
8719
8720 bool operator()(size_t offset, size_t, DataSink &sink) {
8721 return content_provider_(offset, sink);
8722 }
8723
8724private:
8725 ContentProviderWithoutLength content_provider_;
8726};
8727
8728// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
8729namespace fields {
8730
8731inline bool is_token_char(char c) {
8732 return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
8733 c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
8734 c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
8735}
8736
8737inline bool is_token(const std::string &s) {
8738 if (s.empty()) { return false; }
8739 for (auto c : s) {
8740 if (!is_token_char(c)) { return false; }
8741 }
8742 return true;
8743}
8744
8745inline bool is_field_name(const std::string &s) { return is_token(s); }
8746
8747inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
8748
8749inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
8750
8751inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
8752
8753inline bool is_field_content(const std::string &s) {
8754 if (s.empty()) { return true; }
8755
8756 if (s.size() == 1) {
8757 return is_field_vchar(s[0]);
8758 } else if (s.size() == 2) {
8759 return is_field_vchar(s[0]) && is_field_vchar(s[1]);
8760 } else {
8761 size_t i = 0;
8762
8763 if (!is_field_vchar(s[i])) { return false; }
8764 i++;
8765
8766 while (i < s.size() - 1) {
8767 auto c = s[i++];
8768 if (c == ' ' || c == '\t' || is_field_vchar(c)) {
8769 } else {
8770 return false;
8771 }
8772 }
8773
8774 return is_field_vchar(s[i]);
8775 }
8776}
8777
8778inline bool is_field_value(const std::string &s) { return is_field_content(s); }
8779
8780} // namespace fields
8781
8782inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
8783 int port, const std::string &path,
8784 const Headers &headers,
8785 std::string &selected_subprotocol) {
8786 // Validate path and host
8787 if (!fields::is_field_value(path) || !fields::is_field_value(host)) {
8788 return false;
8789 }
8790
8791 // Validate user-provided headers
8792 for (const auto &h : headers) {
8793 if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) {
8794 return false;
8795 }
8796 }
8797
8798 // Generate random Sec-WebSocket-Key
8799 thread_local std::mt19937 rng(std::random_device{}());
8800 std::string key_bytes(16, '\0');
8801 for (size_t i = 0; i < 16; i += 4) {
8802 auto r = rng();
8803 std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i)));
8804 }
8805 auto client_key = base64_encode(key_bytes);
8806
8807 // Build upgrade request
8808 std::string req_str = "GET " + path + " HTTP/1.1\r\n";
8809 req_str += "Host: " + host + ":" + std::to_string(port) + "\r\n";
8810 req_str += "Upgrade: websocket\r\n";
8811 req_str += "Connection: Upgrade\r\n";
8812 req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
8813 req_str += "Sec-WebSocket-Version: 13\r\n";
8814 for (const auto &h : headers) {
8815 req_str += h.first + ": " + h.second + "\r\n";
8816 }
8817 req_str += "\r\n";
8818
8819 if (strm.write(req_str.data(), req_str.size()) < 0) { return false; }
8820
8821 // Verify 101 response and Sec-WebSocket-Accept header
8822 auto expected_accept = websocket_accept_key(client_key);
8823 return read_websocket_upgrade_response(strm, expected_accept,
8824 selected_subprotocol);
8825}
8826
8827} // namespace detail
8828
8829/*
8830 * Group 2: detail namespace - SSL common utilities
8831 */
8832
8833#ifdef CPPHTTPLIB_SSL_ENABLED
8834namespace detail {
8835
8836class SSLSocketStream final : public Stream {
8837public:
8838 SSLSocketStream(
8839 socket_t sock, tls::session_t session, time_t read_timeout_sec,
8840 time_t read_timeout_usec, time_t write_timeout_sec,
8841 time_t write_timeout_usec, time_t max_timeout_msec = 0,
8842 std::chrono::time_point<std::chrono::steady_clock> start_time =
8843 (std::chrono::steady_clock::time_point::min)());
8844 ~SSLSocketStream() override;
8845
8846 bool is_readable() const override;
8847 bool wait_readable() const override;
8848 bool wait_writable() const override;
8849 bool is_peer_alive() const override;
8850 ssize_t read(char *ptr, size_t size) override;
8851 ssize_t write(const char *ptr, size_t size) override;
8852 void get_remote_ip_and_port(std::string &ip, int &port) const override;
8853 void get_local_ip_and_port(std::string &ip, int &port) const override;
8854 socket_t socket() const override;
8855 time_t duration() const override;
8856 void set_read_timeout(time_t sec, time_t usec = 0) override;
8857
8858private:
8859 socket_t sock_;
8860 tls::session_t session_;
8861 time_t read_timeout_sec_;
8862 time_t read_timeout_usec_;
8863 time_t write_timeout_sec_;
8864 time_t write_timeout_usec_;
8865 time_t max_timeout_msec_;
8866 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
8867};
8868
8869#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8870inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
8871 auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
8872 EVP_MD_CTX_new(), EVP_MD_CTX_free);
8873
8874 unsigned int hash_length = 0;
8875 unsigned char hash[EVP_MAX_MD_SIZE];
8876
8877 EVP_DigestInit_ex(context.get(), algo, nullptr);
8878 EVP_DigestUpdate(context.get(), s.c_str(), s.size());
8879 EVP_DigestFinal_ex(context.get(), hash, &hash_length);
8880
8881 std::stringstream ss;
8882 for (auto i = 0u; i < hash_length; ++i) {
8883 ss << std::hex << std::setw(2) << std::setfill('0')
8884 << static_cast<unsigned int>(hash[i]);
8885 }
8886
8887 return ss.str();
8888}
8889
8890inline std::string MD5(const std::string &s) {
8891 return message_digest(s, EVP_md5());
8892}
8893
8894inline std::string SHA_256(const std::string &s) {
8895 return message_digest(s, EVP_sha256());
8896}
8897
8898inline std::string SHA_512(const std::string &s) {
8899 return message_digest(s, EVP_sha512());
8900}
8901#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
8902namespace {
8903template <size_t N>
8904inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
8905 std::stringstream ss;
8906 for (size_t i = 0; i < N; ++i) {
8907 ss << std::hex << std::setw(2) << std::setfill('0')
8908 << static_cast<unsigned int>(hash[i]);
8909 }
8910 return ss.str();
8911}
8912} // namespace
8913
8914inline std::string MD5(const std::string &s) {
8915 unsigned char hash[16];
8916#ifdef CPPHTTPLIB_MBEDTLS_V3
8917 mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8918 hash);
8919#else
8920 mbedtls_md5_ret(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8921 hash);
8922#endif
8923 return hash_to_hex(hash);
8924}
8925
8926inline std::string SHA_256(const std::string &s) {
8927 unsigned char hash[32];
8928#ifdef CPPHTTPLIB_MBEDTLS_V3
8929 mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8930 hash, 0);
8931#else
8932 mbedtls_sha256_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
8933 s.size(), hash, 0);
8934#endif
8935 return hash_to_hex(hash);
8936}
8937
8938inline std::string SHA_512(const std::string &s) {
8939 unsigned char hash[64];
8940#ifdef CPPHTTPLIB_MBEDTLS_V3
8941 mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
8942 hash, 0);
8943#else
8944 mbedtls_sha512_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
8945 s.size(), hash, 0);
8946#endif
8947 return hash_to_hex(hash);
8948}
8949#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
8950namespace {
8951template <size_t N>
8952inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
8953 std::stringstream ss;
8954 for (size_t i = 0; i < N; ++i) {
8955 ss << std::hex << std::setw(2) << std::setfill('0')
8956 << static_cast<unsigned int>(hash[i]);
8957 }
8958 return ss.str();
8959}
8960} // namespace
8961
8962inline std::string MD5(const std::string &s) {
8963 unsigned char hash[WC_MD5_DIGEST_SIZE];
8964 wc_Md5Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8965 static_cast<word32>(s.size()), hash);
8966 return hash_to_hex(hash);
8967}
8968
8969inline std::string SHA_256(const std::string &s) {
8970 unsigned char hash[WC_SHA256_DIGEST_SIZE];
8971 wc_Sha256Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8972 static_cast<word32>(s.size()), hash);
8973 return hash_to_hex(hash);
8974}
8975
8976inline std::string SHA_512(const std::string &s) {
8977 unsigned char hash[WC_SHA512_DIGEST_SIZE];
8978 wc_Sha512Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
8979 static_cast<word32>(s.size()), hash);
8980 return hash_to_hex(hash);
8981}
8982#endif
8983
8984inline bool is_ip_address(const std::string &host) {
8985 struct in_addr addr4;
8986 struct in6_addr addr6;
8987 return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
8988 inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
8989}
8990
8991template <typename T>
8992inline bool process_server_socket_ssl(
8993 const std::atomic<socket_t> &svr_sock, tls::session_t session,
8994 socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
8995 time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
8996 time_t write_timeout_usec, T callback) {
8998 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
8999 [&](bool close_connection, bool &connection_closed) {
9000 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9001 write_timeout_sec, write_timeout_usec);
9002 return callback(strm, close_connection, connection_closed);
9003 });
9004}
9005
9006template <typename T>
9007inline bool process_client_socket_ssl(
9008 tls::session_t session, socket_t sock, time_t read_timeout_sec,
9009 time_t read_timeout_usec, time_t write_timeout_sec,
9010 time_t write_timeout_usec, time_t max_timeout_msec,
9011 std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
9012 SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9013 write_timeout_sec, write_timeout_usec, max_timeout_msec,
9014 start_time);
9015 return callback(strm);
9016}
9017
9018inline std::pair<std::string, std::string> make_digest_authentication_header(
9019 const Request &req, const std::map<std::string, std::string> &auth,
9020 size_t cnonce_count, const std::string &cnonce, const std::string &username,
9021 const std::string &password, bool is_proxy = false) {
9022 std::string nc;
9023 {
9024 std::stringstream ss;
9025 ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
9026 nc = ss.str();
9027 }
9028
9029 std::string qop;
9030 if (auth.find("qop") != auth.end()) {
9031 qop = auth.at("qop");
9032 if (qop.find("auth-int") != std::string::npos) {
9033 qop = "auth-int";
9034 } else if (qop.find("auth") != std::string::npos) {
9035 qop = "auth";
9036 } else {
9037 qop.clear();
9038 }
9039 }
9040
9041 std::string algo = "MD5";
9042 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
9043
9044 std::string response;
9045 {
9046 auto H = algo == "SHA-256" ? detail::SHA_256
9047 : algo == "SHA-512" ? detail::SHA_512
9048 : detail::MD5;
9049
9050 auto A1 = username + ":" + auth.at("realm") + ":" + password;
9051
9052 auto A2 = req.method + ":" + req.path;
9053 if (qop == "auth-int") { A2 += ":" + H(req.body); }
9054
9055 if (qop.empty()) {
9056 response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
9057 } else {
9058 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
9059 ":" + qop + ":" + H(A2));
9060 }
9061 }
9062
9063 auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
9064
9065 auto field = "Digest username=\"" + username + "\", realm=\"" +
9066 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
9067 "\", uri=\"" + req.path + "\", algorithm=" + algo +
9068 (qop.empty() ? ", response=\""
9069 : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
9070 cnonce + "\", response=\"") +
9071 response + "\"" +
9072 (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
9073
9074 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9075 return std::make_pair(key, field);
9076}
9077
9078inline bool match_hostname(const std::string &pattern,
9079 const std::string &hostname) {
9080 // Exact match (case-insensitive)
9081 if (detail::case_ignore::equal(hostname, pattern)) { return true; }
9082
9083 // Split both pattern and hostname into components by '.'
9084 std::vector<std::string> pattern_components;
9085 if (!pattern.empty()) {
9086 split(pattern.data(), pattern.data() + pattern.size(), '.',
9087 [&](const char *b, const char *e) {
9088 pattern_components.emplace_back(b, e);
9089 });
9090 }
9091
9092 std::vector<std::string> host_components;
9093 if (!hostname.empty()) {
9094 split(hostname.data(), hostname.data() + hostname.size(), '.',
9095 [&](const char *b, const char *e) {
9096 host_components.emplace_back(b, e);
9097 });
9098 }
9099
9100 // Component count must match
9101 if (host_components.size() != pattern_components.size()) { return false; }
9102
9103 // Compare each component with wildcard support
9104 // Supports: "*" (full wildcard), "prefix*" (partial wildcard)
9105 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
9106 auto itr = pattern_components.begin();
9107 for (const auto &h : host_components) {
9108 auto &p = *itr;
9109 if (!detail::case_ignore::equal(p, h) && p != "*") {
9110 bool partial_match = false;
9111 if (!p.empty() && p[p.size() - 1] == '*') {
9112 const auto prefix_length = p.size() - 1;
9113 if (prefix_length == 0) {
9114 partial_match = true;
9115 } else if (h.size() >= prefix_length) {
9116 partial_match =
9117 std::equal(p.begin(),
9118 p.begin() + static_cast<std::string::difference_type>(
9119 prefix_length),
9120 h.begin(), [](const char ca, const char cb) {
9121 return detail::case_ignore::to_lower(ca) ==
9122 detail::case_ignore::to_lower(cb);
9123 });
9124 }
9125 }
9126 if (!partial_match) { return false; }
9127 }
9128 ++itr;
9129 }
9130
9131 return true;
9132}
9133
9134#ifdef _WIN32
9135// Verify certificate using Windows CertGetCertificateChain API.
9136// This provides real-time certificate validation with Windows Update
9137// integration, independent of the TLS backend (OpenSSL or MbedTLS).
9138inline bool
9139verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
9140 const std::string &hostname,
9141 bool verify_hostname, uint64_t &out_error) {
9142 if (der_cert.empty()) { return false; }
9143
9144 out_error = 0;
9145
9146 // Create Windows certificate context from DER data
9147 auto cert_context = CertCreateCertificateContext(
9148 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
9149 static_cast<DWORD>(der_cert.size()));
9150
9151 if (!cert_context) {
9152 out_error = GetLastError();
9153 return false;
9154 }
9155
9156 auto cert_guard =
9157 scope_exit([&] { CertFreeCertificateContext(cert_context); });
9158
9159 // Setup chain parameters
9160 CERT_CHAIN_PARA chain_para = {};
9161 chain_para.cbSize = sizeof(chain_para);
9162
9163 // Build certificate chain with revocation checking
9164 PCCERT_CHAIN_CONTEXT chain_context = nullptr;
9165 auto chain_result = CertGetCertificateChain(
9166 nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
9167 CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
9168 CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
9169 nullptr, &chain_context);
9170
9171 if (!chain_result || !chain_context) {
9172 out_error = GetLastError();
9173 return false;
9174 }
9175
9176 auto chain_guard =
9177 scope_exit([&] { CertFreeCertificateChain(chain_context); });
9178
9179 // Check if chain has errors
9180 if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
9181 out_error = chain_context->TrustStatus.dwErrorStatus;
9182 return false;
9183 }
9184
9185 // Verify SSL policy
9186 SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
9187 extra_policy_para.cbSize = sizeof(extra_policy_para);
9188#ifdef AUTHTYPE_SERVER
9189 extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
9190#endif
9191
9192 std::wstring whost;
9193 if (verify_hostname) {
9194 whost = u8string_to_wstring(hostname.c_str());
9195 extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
9196 }
9197
9198 CERT_CHAIN_POLICY_PARA policy_para = {};
9199 policy_para.cbSize = sizeof(policy_para);
9200#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
9201 policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
9202#else
9203 policy_para.dwFlags = 0;
9204#endif
9205 policy_para.pvExtraPolicyPara = &extra_policy_para;
9206
9207 CERT_CHAIN_POLICY_STATUS policy_status = {};
9208 policy_status.cbSize = sizeof(policy_status);
9209
9210 if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
9211 &policy_para, &policy_status)) {
9212 out_error = GetLastError();
9213 return false;
9214 }
9215
9216 if (policy_status.dwError != 0) {
9217 out_error = policy_status.dwError;
9218 return false;
9219 }
9220
9221 return true;
9222}
9223#endif // _WIN32
9224
9225inline bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx,
9226 tls::session_t &session, socket_t sock,
9227 bool server_certificate_verification,
9228 const std::string &ca_cert_file_path,
9229 tls::ca_store_t ca_cert_store,
9230 time_t timeout_sec, time_t timeout_usec) {
9231 using namespace tls;
9232
9233 ctx = create_client_context();
9234 if (!ctx) { return false; }
9235
9236 if (server_certificate_verification) {
9237 if (!ca_cert_file_path.empty()) {
9238 load_ca_file(ctx, ca_cert_file_path.c_str());
9239 }
9240 if (ca_cert_store) { set_ca_store(ctx, ca_cert_store); }
9241 load_system_certs(ctx);
9242 }
9243
9244 bool is_ip = is_ip_address(host);
9245
9246#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
9247 if (is_ip && server_certificate_verification) {
9248 set_verify_client(ctx, false);
9249 } else {
9250 set_verify_client(ctx, server_certificate_verification);
9251 }
9252#endif
9253
9254 session = create_session(ctx, sock);
9255 if (!session) { return false; }
9256
9257 // RFC 6066: SNI must not be set for IP addresses
9258 if (!is_ip) { set_sni(session, host.c_str()); }
9259 if (server_certificate_verification) { set_hostname(session, host.c_str()); }
9260
9261 if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
9262 return false;
9263 }
9264
9265 if (server_certificate_verification) {
9266 if (get_verify_result(session) != 0) { return false; }
9267 }
9268
9269 return true;
9270}
9271
9272} // namespace detail
9273#endif // CPPHTTPLIB_SSL_ENABLED
9274
9275/*
9276 * Group 3: httplib namespace - Non-SSL public API implementations
9277 */
9278
9280 set_socket_opt(sock, SOL_SOCKET,
9281#ifdef SO_REUSEPORT
9282 SO_REUSEPORT,
9283#else
9284 SO_REUSEADDR,
9285#endif
9286 1);
9287}
9288
9289inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
9290 return detail::set_socket_opt_impl(sock, level, optname, &optval,
9291 sizeof(optval));
9292}
9293
9294inline std::string get_bearer_token_auth(const Request &req) {
9295 if (req.has_header("Authorization")) {
9296 constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
9297 return req.get_header_value("Authorization")
9298 .substr(bearer_header_prefix_len);
9299 }
9300 return "";
9301}
9302
9303inline const char *status_message(int status) {
9304 switch (status) {
9305 case StatusCode::Continue_100: return "Continue";
9306 case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
9307 case StatusCode::Processing_102: return "Processing";
9308 case StatusCode::EarlyHints_103: return "Early Hints";
9309 case StatusCode::OK_200: return "OK";
9310 case StatusCode::Created_201: return "Created";
9311 case StatusCode::Accepted_202: return "Accepted";
9313 return "Non-Authoritative Information";
9314 case StatusCode::NoContent_204: return "No Content";
9315 case StatusCode::ResetContent_205: return "Reset Content";
9316 case StatusCode::PartialContent_206: return "Partial Content";
9317 case StatusCode::MultiStatus_207: return "Multi-Status";
9318 case StatusCode::AlreadyReported_208: return "Already Reported";
9319 case StatusCode::IMUsed_226: return "IM Used";
9320 case StatusCode::MultipleChoices_300: return "Multiple Choices";
9321 case StatusCode::MovedPermanently_301: return "Moved Permanently";
9322 case StatusCode::Found_302: return "Found";
9323 case StatusCode::SeeOther_303: return "See Other";
9324 case StatusCode::NotModified_304: return "Not Modified";
9325 case StatusCode::UseProxy_305: return "Use Proxy";
9326 case StatusCode::unused_306: return "unused";
9327 case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
9328 case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
9329 case StatusCode::BadRequest_400: return "Bad Request";
9330 case StatusCode::Unauthorized_401: return "Unauthorized";
9331 case StatusCode::PaymentRequired_402: return "Payment Required";
9332 case StatusCode::Forbidden_403: return "Forbidden";
9333 case StatusCode::NotFound_404: return "Not Found";
9334 case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
9335 case StatusCode::NotAcceptable_406: return "Not Acceptable";
9337 return "Proxy Authentication Required";
9338 case StatusCode::RequestTimeout_408: return "Request Timeout";
9339 case StatusCode::Conflict_409: return "Conflict";
9340 case StatusCode::Gone_410: return "Gone";
9341 case StatusCode::LengthRequired_411: return "Length Required";
9342 case StatusCode::PreconditionFailed_412: return "Precondition Failed";
9343 case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
9344 case StatusCode::UriTooLong_414: return "URI Too Long";
9345 case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
9346 case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
9347 case StatusCode::ExpectationFailed_417: return "Expectation Failed";
9348 case StatusCode::ImATeapot_418: return "I'm a teapot";
9349 case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
9350 case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
9351 case StatusCode::Locked_423: return "Locked";
9352 case StatusCode::FailedDependency_424: return "Failed Dependency";
9353 case StatusCode::TooEarly_425: return "Too Early";
9354 case StatusCode::UpgradeRequired_426: return "Upgrade Required";
9355 case StatusCode::PreconditionRequired_428: return "Precondition Required";
9356 case StatusCode::TooManyRequests_429: return "Too Many Requests";
9358 return "Request Header Fields Too Large";
9360 return "Unavailable For Legal Reasons";
9361 case StatusCode::NotImplemented_501: return "Not Implemented";
9362 case StatusCode::BadGateway_502: return "Bad Gateway";
9363 case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
9364 case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
9366 return "HTTP Version Not Supported";
9367 case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
9368 case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
9369 case StatusCode::LoopDetected_508: return "Loop Detected";
9370 case StatusCode::NotExtended_510: return "Not Extended";
9372 return "Network Authentication Required";
9373
9374 default:
9375 case StatusCode::InternalServerError_500: return "Internal Server Error";
9376 }
9377}
9378
9379inline std::string to_string(const Error error) {
9380 switch (error) {
9381 case Error::Success: return "Success (no error)";
9382 case Error::Unknown: return "Unknown";
9383 case Error::Connection: return "Could not establish connection";
9384 case Error::BindIPAddress: return "Failed to bind IP address";
9385 case Error::Read: return "Failed to read connection";
9386 case Error::Write: return "Failed to write connection";
9387 case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
9388 case Error::Canceled: return "Connection handling canceled";
9389 case Error::SSLConnection: return "SSL connection failed";
9390 case Error::SSLLoadingCerts: return "SSL certificate loading failed";
9391 case Error::SSLServerVerification: return "SSL server verification failed";
9393 return "SSL server hostname verification failed";
9395 return "Unsupported HTTP multipart boundary characters";
9396 case Error::Compression: return "Compression failed";
9397 case Error::ConnectionTimeout: return "Connection timed out";
9398 case Error::ProxyConnection: return "Proxy connection failed";
9399 case Error::ConnectionClosed: return "Connection closed by server";
9400 case Error::Timeout: return "Read timeout";
9401 case Error::ResourceExhaustion: return "Resource exhaustion";
9402 case Error::TooManyFormDataFiles: return "Too many form data files";
9403 case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
9404 case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
9406 return "Exceeded maximum socket descriptor count";
9407 case Error::InvalidRequestLine: return "Invalid request line";
9408 case Error::InvalidHTTPMethod: return "Invalid HTTP method";
9409 case Error::InvalidHTTPVersion: return "Invalid HTTP version";
9410 case Error::InvalidHeaders: return "Invalid headers";
9411 case Error::MultipartParsing: return "Multipart parsing failed";
9412 case Error::OpenFile: return "Failed to open file";
9413 case Error::Listen: return "Failed to listen on socket";
9414 case Error::GetSockName: return "Failed to get socket name";
9415 case Error::UnsupportedAddressFamily: return "Unsupported address family";
9416 case Error::HTTPParsing: return "HTTP parsing failed";
9417 case Error::InvalidRangeHeader: return "Invalid Range header";
9418 default: break;
9419 }
9420
9421 return "Invalid";
9422}
9423
9424inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
9425 os << to_string(obj);
9426 os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
9427 return os;
9428}
9429
9430inline std::string hosted_at(const std::string &hostname) {
9431 std::vector<std::string> addrs;
9432 hosted_at(hostname, addrs);
9433 if (addrs.empty()) { return std::string(); }
9434 return addrs[0];
9435}
9436
9437inline void hosted_at(const std::string &hostname,
9438 std::vector<std::string> &addrs) {
9439 struct addrinfo hints;
9440 struct addrinfo *result;
9441
9442 memset(&hints, 0, sizeof(struct addrinfo));
9443 hints.ai_family = AF_UNSPEC;
9444 hints.ai_socktype = SOCK_STREAM;
9445 hints.ai_protocol = 0;
9446
9447 if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
9448 &result, 0)) {
9449#if defined __linux__ && !defined __ANDROID__
9450 res_init();
9451#endif
9452 return;
9453 }
9454 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
9455
9456 for (auto rp = result; rp; rp = rp->ai_next) {
9457 const auto &addr =
9458 *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
9459 std::string ip;
9460 auto dummy = -1;
9461 if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
9462 dummy)) {
9463 addrs.emplace_back(std::move(ip));
9464 }
9465 }
9466}
9467
9468inline std::string encode_uri_component(const std::string &value) {
9469 std::ostringstream escaped;
9470 escaped.fill('0');
9471 escaped << std::hex;
9472
9473 for (auto c : value) {
9474 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9475 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9476 c == ')') {
9477 escaped << c;
9478 } else {
9479 escaped << std::uppercase;
9480 escaped << '%' << std::setw(2)
9481 << static_cast<int>(static_cast<unsigned char>(c));
9482 escaped << std::nouppercase;
9483 }
9484 }
9485
9486 return escaped.str();
9487}
9488
9489inline std::string encode_uri(const std::string &value) {
9490 std::ostringstream escaped;
9491 escaped.fill('0');
9492 escaped << std::hex;
9493
9494 for (auto c : value) {
9495 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
9496 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
9497 c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
9498 c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
9499 escaped << c;
9500 } else {
9501 escaped << std::uppercase;
9502 escaped << '%' << std::setw(2)
9503 << static_cast<int>(static_cast<unsigned char>(c));
9504 escaped << std::nouppercase;
9505 }
9506 }
9507
9508 return escaped.str();
9509}
9510
9511inline std::string decode_uri_component(const std::string &value) {
9512 std::string result;
9513
9514 for (size_t i = 0; i < value.size(); i++) {
9515 if (value[i] == '%' && i + 2 < value.size()) {
9516 auto val = 0;
9517 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9518 result += static_cast<char>(val);
9519 i += 2;
9520 } else {
9521 result += value[i];
9522 }
9523 } else {
9524 result += value[i];
9525 }
9526 }
9527
9528 return result;
9529}
9530
9531inline std::string decode_uri(const std::string &value) {
9532 std::string result;
9533
9534 for (size_t i = 0; i < value.size(); i++) {
9535 if (value[i] == '%' && i + 2 < value.size()) {
9536 auto val = 0;
9537 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9538 result += static_cast<char>(val);
9539 i += 2;
9540 } else {
9541 result += value[i];
9542 }
9543 } else {
9544 result += value[i];
9545 }
9546 }
9547
9548 return result;
9549}
9550
9551inline std::string encode_path_component(const std::string &component) {
9552 std::string result;
9553 result.reserve(component.size() * 3);
9554
9555 for (size_t i = 0; i < component.size(); i++) {
9556 auto c = static_cast<unsigned char>(component[i]);
9557
9558 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
9559 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9560 result += static_cast<char>(c);
9561 }
9562 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
9563 // "," / ";" / "="
9564 else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
9565 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
9566 c == '=') {
9567 result += static_cast<char>(c);
9568 }
9569 // Colon is allowed in path segments except first segment
9570 else if (c == ':') {
9571 result += static_cast<char>(c);
9572 }
9573 // @ is allowed in path
9574 else if (c == '@') {
9575 result += static_cast<char>(c);
9576 } else {
9577 result += '%';
9578 char hex[3];
9579 snprintf(hex, sizeof(hex), "%02X", c);
9580 result.append(hex, 2);
9581 }
9582 }
9583 return result;
9584}
9585
9586inline std::string decode_path_component(const std::string &component) {
9587 std::string result;
9588 result.reserve(component.size());
9589
9590 for (size_t i = 0; i < component.size(); i++) {
9591 if (component[i] == '%' && i + 1 < component.size()) {
9592 if (component[i + 1] == 'u') {
9593 // Unicode %uXXXX encoding
9594 auto val = 0;
9595 if (detail::from_hex_to_i(component, i + 2, 4, val)) {
9596 // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits),
9597 // so to_utf8 writes at most 3 bytes. buff[4] is safe.
9598 char buff[4];
9599 size_t len = detail::to_utf8(val, buff);
9600 if (len > 0) { result.append(buff, len); }
9601 i += 5; // 'u0000'
9602 } else {
9603 result += component[i];
9604 }
9605 } else {
9606 // Standard %XX encoding
9607 auto val = 0;
9608 if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9609 // 2 digits hex codes
9610 result += static_cast<char>(val);
9611 i += 2; // 'XX'
9612 } else {
9613 result += component[i];
9614 }
9615 }
9616 } else {
9617 result += component[i];
9618 }
9619 }
9620 return result;
9621}
9622
9623inline std::string encode_query_component(const std::string &component,
9624 bool space_as_plus) {
9625 std::string result;
9626 result.reserve(component.size() * 3);
9627
9628 for (size_t i = 0; i < component.size(); i++) {
9629 auto c = static_cast<unsigned char>(component[i]);
9630
9631 // Unreserved characters per RFC 3986
9632 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
9633 result += static_cast<char>(c);
9634 }
9635 // Space handling
9636 else if (c == ' ') {
9637 if (space_as_plus) {
9638 result += '+';
9639 } else {
9640 result += "%20";
9641 }
9642 }
9643 // Plus sign handling
9644 else if (c == '+') {
9645 if (space_as_plus) {
9646 result += "%2B";
9647 } else {
9648 result += static_cast<char>(c);
9649 }
9650 }
9651 // Query-safe sub-delimiters (excluding & and = which are query delimiters)
9652 else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
9653 c == '*' || c == ',' || c == ';') {
9654 result += static_cast<char>(c);
9655 }
9656 // Colon and @ are allowed in query
9657 else if (c == ':' || c == '@') {
9658 result += static_cast<char>(c);
9659 }
9660 // Forward slash is allowed in query values
9661 else if (c == '/') {
9662 result += static_cast<char>(c);
9663 }
9664 // Question mark is allowed in query values (after first ?)
9665 else if (c == '?') {
9666 result += static_cast<char>(c);
9667 } else {
9668 result += '%';
9669 char hex[3];
9670 snprintf(hex, sizeof(hex), "%02X", c);
9671 result.append(hex, 2);
9672 }
9673 }
9674 return result;
9675}
9676
9677inline std::string decode_query_component(const std::string &component,
9678 bool plus_as_space) {
9679 std::string result;
9680 result.reserve(component.size());
9681
9682 for (size_t i = 0; i < component.size(); i++) {
9683 if (component[i] == '%' && i + 2 < component.size()) {
9684 std::string hex = component.substr(i + 1, 2);
9685 char *end;
9686 unsigned long value = std::strtoul(hex.c_str(), &end, 16);
9687 if (end == hex.c_str() + 2) {
9688 result += static_cast<char>(value);
9689 i += 2;
9690 } else {
9691 result += component[i];
9692 }
9693 } else if (component[i] == '+' && plus_as_space) {
9694 result += ' '; // + becomes space in form-urlencoded
9695 } else {
9696 result += component[i];
9697 }
9698 }
9699 return result;
9700}
9701
9702inline std::string sanitize_filename(const std::string &filename) {
9703 // Extract basename: find the last path separator (/ or \‍)
9704 auto pos = filename.find_last_of("/\\");
9705 auto result =
9706 (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
9707
9708 // Strip null bytes
9709 result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());
9710
9711 // Trim whitespace
9712 {
9713 auto start = result.find_first_not_of(" \t");
9714 auto end = result.find_last_not_of(" \t");
9715 result = (start == std::string::npos)
9716 ? ""
9717 : result.substr(start, end - start + 1);
9718 }
9719
9720 // Reject . and ..
9721 if (result == "." || result == "..") { return ""; }
9722
9723 return result;
9724}
9725
9726inline std::string append_query_params(const std::string &path,
9727 const Params &params) {
9728 std::string path_with_query = path;
9729 thread_local const std::regex re("[^?]+\\?.*");
9730 auto delm = std::regex_match(path, re) ? '&' : '?';
9731 path_with_query += delm + detail::params_to_query_str(params);
9732 return path_with_query;
9733}
9734
9735// Header utilities
9736inline std::pair<std::string, std::string>
9738 std::string field = "bytes=";
9739 auto i = 0;
9740 for (const auto &r : ranges) {
9741 if (i != 0) { field += ", "; }
9742 if (r.first != -1) { field += std::to_string(r.first); }
9743 field += '-';
9744 if (r.second != -1) { field += std::to_string(r.second); }
9745 i++;
9746 }
9747 return std::make_pair("Range", std::move(field));
9748}
9749
9750inline std::pair<std::string, std::string>
9751make_basic_authentication_header(const std::string &username,
9752 const std::string &password, bool is_proxy) {
9753 auto field = "Basic " + detail::base64_encode(username + ":" + password);
9754 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9755 return std::make_pair(key, std::move(field));
9756}
9757
9758inline std::pair<std::string, std::string>
9760 bool is_proxy = false) {
9761 auto field = "Bearer " + token;
9762 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9763 return std::make_pair(key, std::move(field));
9764}
9765
9766// Request implementation
9767inline size_t Request::get_header_value_u64(const std::string &key, size_t def,
9768 size_t id) const {
9769 return detail::get_header_value_u64(headers, key, def, id);
9770}
9771
9772inline bool Request::has_header(const std::string &key) const {
9773 return detail::has_header(headers, key);
9774}
9775
9776inline std::string Request::get_header_value(const std::string &key,
9777 const char *def, size_t id) const {
9778 return detail::get_header_value(headers, key, def, id);
9779}
9780
9781inline size_t Request::get_header_value_count(const std::string &key) const {
9783}
9784
9785inline void Request::set_header(const std::string &key,
9786 const std::string &val) {
9787 detail::set_header(headers, key, val);
9788}
9789
9790inline bool Request::has_trailer(const std::string &key) const {
9791 return trailers.find(key) != trailers.end();
9792}
9793
9794inline std::string Request::get_trailer_value(const std::string &key,
9795 size_t id) const {
9796 return detail::get_multimap_value(trailers, key, id);
9797}
9798
9799inline size_t Request::get_trailer_value_count(const std::string &key) const {
9800 auto r = trailers.equal_range(key);
9801 return static_cast<size_t>(std::distance(r.first, r.second));
9802}
9803
9804inline bool Request::has_param(const std::string &key) const {
9805 return params.find(key) != params.end();
9806}
9807
9808inline std::string Request::get_param_value(const std::string &key,
9809 size_t id) const {
9810 return detail::get_multimap_value(params, key, id);
9811}
9812
9813inline std::vector<std::string>
9814Request::get_param_values(const std::string &key) const {
9815 auto rng = params.equal_range(key);
9816 std::vector<std::string> values;
9817 values.reserve(static_cast<size_t>(std::distance(rng.first, rng.second)));
9818 for (auto it = rng.first; it != rng.second; ++it) {
9819 values.push_back(it->second);
9820 }
9821 return values;
9822}
9823
9824inline size_t Request::get_param_value_count(const std::string &key) const {
9825 auto r = params.equal_range(key);
9826 return static_cast<size_t>(std::distance(r.first, r.second));
9827}
9828
9830 const auto &content_type = get_header_value("Content-Type");
9831 return detail::extract_media_type(content_type) == "multipart/form-data";
9832}
9833
9834// Multipart FormData implementation
9835inline std::string MultipartFormData::get_field(const std::string &key,
9836 size_t id) const {
9837 auto rng = fields.equal_range(key);
9838 auto it = rng.first;
9839 std::advance(it, static_cast<ssize_t>(id));
9840 if (it != rng.second) { return it->second.content; }
9841 return std::string();
9842}
9843
9844inline std::vector<std::string>
9845MultipartFormData::get_fields(const std::string &key) const {
9846 std::vector<std::string> values;
9847 auto rng = fields.equal_range(key);
9848 for (auto it = rng.first; it != rng.second; it++) {
9849 values.push_back(it->second.content);
9850 }
9851 return values;
9852}
9853
9854inline bool MultipartFormData::has_field(const std::string &key) const {
9855 return fields.find(key) != fields.end();
9856}
9857
9858inline size_t MultipartFormData::get_field_count(const std::string &key) const {
9859 auto r = fields.equal_range(key);
9860 return static_cast<size_t>(std::distance(r.first, r.second));
9861}
9862
9863inline FormData MultipartFormData::get_file(const std::string &key,
9864 size_t id) const {
9865 return detail::get_multimap_value(files, key, id);
9866}
9867
9868inline std::vector<FormData>
9869MultipartFormData::get_files(const std::string &key) const {
9870 std::vector<FormData> values;
9871 auto rng = files.equal_range(key);
9872 for (auto it = rng.first; it != rng.second; it++) {
9873 values.push_back(it->second);
9874 }
9875 return values;
9876}
9877
9878inline bool MultipartFormData::has_file(const std::string &key) const {
9879 return files.find(key) != files.end();
9880}
9881
9882inline size_t MultipartFormData::get_file_count(const std::string &key) const {
9883 auto r = files.equal_range(key);
9884 return static_cast<size_t>(std::distance(r.first, r.second));
9885}
9886
9887// Response implementation
9888inline size_t Response::get_header_value_u64(const std::string &key, size_t def,
9889 size_t id) const {
9890 return detail::get_header_value_u64(headers, key, def, id);
9891}
9892
9893inline bool Response::has_header(const std::string &key) const {
9894 return headers.find(key) != headers.end();
9895}
9896
9897inline std::string Response::get_header_value(const std::string &key,
9898 const char *def,
9899 size_t id) const {
9900 return detail::get_header_value(headers, key, def, id);
9901}
9902
9903inline size_t Response::get_header_value_count(const std::string &key) const {
9905}
9906
9907inline void Response::set_header(const std::string &key,
9908 const std::string &val) {
9909 detail::set_header(headers, key, val);
9910}
9911inline bool Response::has_trailer(const std::string &key) const {
9912 return trailers.find(key) != trailers.end();
9913}
9914
9915inline std::string Response::get_trailer_value(const std::string &key,
9916 size_t id) const {
9917 return detail::get_multimap_value(trailers, key, id);
9918}
9919
9920inline size_t Response::get_trailer_value_count(const std::string &key) const {
9921 auto r = trailers.equal_range(key);
9922 return static_cast<size_t>(std::distance(r.first, r.second));
9923}
9924
9925inline void Response::set_redirect(const std::string &url, int stat) {
9927 set_header("Location", url);
9928 if (300 <= stat && stat < 400) {
9929 this->status = stat;
9930 } else {
9932 }
9933 }
9934}
9935
9936inline void Response::set_content(const char *s, size_t n,
9937 const std::string &content_type) {
9938 body.assign(s, n);
9939
9940 auto rng = headers.equal_range("Content-Type");
9941 headers.erase(rng.first, rng.second);
9942 set_header("Content-Type", content_type);
9943}
9944
9945inline void Response::set_content(const std::string &s,
9946 const std::string &content_type) {
9947 set_content(s.data(), s.size(), content_type);
9948}
9949
9950inline void Response::set_content(std::string &&s,
9951 const std::string &content_type) {
9952 body = std::move(s);
9953
9954 auto rng = headers.equal_range("Content-Type");
9955 headers.erase(rng.first, rng.second);
9956 set_header("Content-Type", content_type);
9957}
9958
9960 size_t in_length, const std::string &content_type, ContentProvider provider,
9961 ContentProviderResourceReleaser resource_releaser) {
9962 set_header("Content-Type", content_type);
9963 content_length_ = in_length;
9964 if (in_length > 0) { content_provider_ = std::move(provider); }
9965 content_provider_resource_releaser_ = std::move(resource_releaser);
9967}
9968
9970 const std::string &content_type, ContentProviderWithoutLength provider,
9971 ContentProviderResourceReleaser resource_releaser) {
9972 set_header("Content-Type", content_type);
9973 content_length_ = 0;
9975 content_provider_resource_releaser_ = std::move(resource_releaser);
9977}
9978
9980 const std::string &content_type, ContentProviderWithoutLength provider,
9981 ContentProviderResourceReleaser resource_releaser) {
9982 set_header("Content-Type", content_type);
9983 content_length_ = 0;
9985 content_provider_resource_releaser_ = std::move(resource_releaser);
9987}
9988
9989inline void Response::set_file_content(const std::string &path,
9990 const std::string &content_type) {
9991 file_content_path_ = path;
9992 file_content_content_type_ = content_type;
9993}
9994
9995inline void Response::set_file_content(const std::string &path) {
9996 file_content_path_ = path;
9997}
9998
9999// Result implementation
10000inline size_t Result::get_request_header_value_u64(const std::string &key,
10001 size_t def,
10002 size_t id) const {
10003 return detail::get_header_value_u64(request_headers_, key, def, id);
10004}
10005
10006inline bool Result::has_request_header(const std::string &key) const {
10007 return request_headers_.find(key) != request_headers_.end();
10008}
10009
10010inline std::string Result::get_request_header_value(const std::string &key,
10011 const char *def,
10012 size_t id) const {
10013 return detail::get_header_value(request_headers_, key, def, id);
10014}
10015
10016inline size_t
10017Result::get_request_header_value_count(const std::string &key) const {
10018 auto r = request_headers_.equal_range(key);
10019 return static_cast<size_t>(std::distance(r.first, r.second));
10020}
10021
10022// Stream implementation
10023inline ssize_t Stream::write(const char *ptr) {
10024 return write(ptr, strlen(ptr));
10025}
10026
10027inline ssize_t Stream::write(const std::string &s) {
10028 return write(s.data(), s.size());
10029}
10030
10031// BodyReader implementation
10032inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
10033 if (!stream) {
10035 return -1;
10036 }
10037 if (eof) { return 0; }
10038
10039 if (!chunked) {
10040 // Content-Length based reading
10042 eof = true;
10043 return 0;
10044 }
10045
10046 auto to_read = len;
10047 if (has_content_length) {
10048 auto remaining = content_length - bytes_read;
10049 to_read = (std::min)(len, remaining);
10050 }
10051 auto n = stream->read(buf, to_read);
10052
10053 if (n < 0) {
10054 last_error = stream->get_error();
10056 eof = true;
10057 return n;
10058 }
10059 if (n == 0) {
10060 // Unexpected EOF before content_length
10061 last_error = stream->get_error();
10063 eof = true;
10064 return 0;
10065 }
10066
10067 bytes_read += static_cast<size_t>(n);
10068 if (has_content_length && bytes_read >= content_length) { eof = true; }
10071 eof = true;
10072 return -1;
10073 }
10074 return n;
10075 }
10076
10077 // Chunked transfer encoding: delegate to shared decoder instance.
10079
10080 size_t chunk_offset = 0;
10081 size_t chunk_total = 0;
10082 auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
10083 if (n < 0) {
10084 last_error = stream->get_error();
10086 eof = true;
10087 return n;
10088 }
10089
10090 if (n == 0) {
10091 // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
10092 eof = true;
10093 return 0;
10094 }
10095
10096 bytes_read += static_cast<size_t>(n);
10099 eof = true;
10100 return -1;
10101 }
10102 return n;
10103}
10104
10105// ThreadPool implementation
10106inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr)
10107 : base_thread_count_(n), max_queued_requests_(mqr), idle_thread_count_(0),
10108 shutdown_(false) {
10109#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10110 if (max_n != 0 && max_n < n) {
10111 std::string msg = "max_threads must be >= base_threads";
10112 throw std::invalid_argument(msg);
10113 }
10114#endif
10115 max_thread_count_ = max_n == 0 ? n : max_n;
10116 threads_.reserve(base_thread_count_);
10117#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10118 try {
10119#endif
10120 for (size_t i = 0; i < base_thread_count_; i++) {
10121 threads_.emplace_back(std::thread([this]() { worker(false); }));
10122 }
10123#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10124 } catch (...) {
10125 // If thread creation fails partway (e.g., pthread_create returns EAGAIN),
10126 // signal the workers we already spawned to exit and join them so the
10127 // vector destructor does not see joinable threads (which would call
10128 // std::terminate). Then rethrow so the caller learns of the failure.
10129 {
10130 std::unique_lock<std::mutex> lock(mutex_);
10131 shutdown_ = true;
10132 }
10133 cond_.notify_all();
10134 for (auto &t : threads_) {
10135 if (t.joinable()) { t.join(); }
10136 }
10137 throw;
10138 }
10139#endif
10140}
10141
10142inline bool ThreadPool::enqueue(std::function<void()> fn) {
10143 {
10144 std::unique_lock<std::mutex> lock(mutex_);
10145 if (shutdown_) { return false; }
10146 if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
10147 return false;
10148 }
10149 jobs_.push_back(std::move(fn));
10150
10151 // Spawn a dynamic thread if no idle threads and under max
10152 if (idle_thread_count_ == 0 &&
10153 threads_.size() + dynamic_threads_.size() < max_thread_count_) {
10154 cleanup_finished_threads();
10155 dynamic_threads_.emplace_back(std::thread([this]() { worker(true); }));
10156 }
10157 }
10158
10159 cond_.notify_one();
10160 return true;
10161}
10162
10164 {
10165 std::unique_lock<std::mutex> lock(mutex_);
10166 shutdown_ = true;
10167 }
10168
10169 cond_.notify_all();
10170
10171 for (auto &t : threads_) {
10172 if (t.joinable()) { t.join(); }
10173 }
10174
10175 // Move dynamic_threads_ to a local list under the lock to avoid racing
10176 // with worker threads that call move_to_finished() concurrently.
10177 std::list<std::thread> remaining_dynamic;
10178 {
10179 std::unique_lock<std::mutex> lock(mutex_);
10180 remaining_dynamic = std::move(dynamic_threads_);
10181 }
10182 for (auto &t : remaining_dynamic) {
10183 if (t.joinable()) { t.join(); }
10184 }
10185
10186 std::unique_lock<std::mutex> lock(mutex_);
10187 cleanup_finished_threads();
10188}
10189
10190inline void ThreadPool::move_to_finished(std::thread::id id) {
10191 // Must be called with mutex_ held
10192 for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) {
10193 if (it->get_id() == id) {
10194 finished_threads_.push_back(std::move(*it));
10195 dynamic_threads_.erase(it);
10196 return;
10197 }
10198 }
10199}
10200
10201inline void ThreadPool::cleanup_finished_threads() {
10202 // Must be called with mutex_ held
10203 for (auto &t : finished_threads_) {
10204 if (t.joinable()) { t.join(); }
10205 }
10206 finished_threads_.clear();
10207}
10208
10209inline void ThreadPool::worker(bool is_dynamic) {
10210 for (;;) {
10211 std::function<void()> fn;
10212 {
10213 std::unique_lock<std::mutex> lock(mutex_);
10214 idle_thread_count_++;
10215
10216 if (is_dynamic) {
10217 auto has_work = cond_.wait_for(
10218 lock, std::chrono::seconds(CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT),
10219 [&] { return !jobs_.empty() || shutdown_; });
10220 if (!has_work) {
10221 // Timed out with no work - exit this dynamic thread
10222 idle_thread_count_--;
10223 move_to_finished(std::this_thread::get_id());
10224 break;
10225 }
10226 } else {
10227 cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; });
10228 }
10229
10230 idle_thread_count_--;
10231
10232 if (shutdown_ && jobs_.empty()) { break; }
10233
10234 fn = std::move(jobs_.front());
10235 jobs_.pop_front();
10236 }
10237
10238 assert(true == static_cast<bool>(fn));
10239 fn();
10240 }
10241
10242#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
10243 !defined(LIBRESSL_VERSION_NUMBER)
10244 OPENSSL_thread_stop();
10245#endif
10246}
10247
10248/*
10249 * Group 1 (continued): detail namespace - Stream implementations
10250 */
10251
10252namespace detail {
10253
10254inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
10255 time_t timeout_sec, time_t timeout_usec,
10256 time_t &actual_timeout_sec,
10257 time_t &actual_timeout_usec) {
10258 auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
10259
10260 auto actual_timeout_msec =
10261 (std::min)(max_timeout_msec - duration_msec, timeout_msec);
10262
10263 if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
10264
10265 actual_timeout_sec = actual_timeout_msec / 1000;
10266 actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
10267}
10268
10269// Socket stream implementation
10271 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
10272 time_t write_timeout_sec, time_t write_timeout_usec,
10273 time_t max_timeout_msec,
10274 std::chrono::time_point<std::chrono::steady_clock> start_time)
10275 : sock_(sock), read_timeout_sec_(read_timeout_sec),
10276 read_timeout_usec_(read_timeout_usec),
10277 write_timeout_sec_(write_timeout_sec),
10278 write_timeout_usec_(write_timeout_usec),
10279 max_timeout_msec_(max_timeout_msec), start_time_(start_time),
10280 read_buff_(read_buff_size_, 0) {}
10281
10282inline SocketStream::~SocketStream() = default;
10283
10284inline bool SocketStream::is_readable() const {
10285 return read_buff_off_ < read_buff_content_size_;
10286}
10287
10288inline bool SocketStream::wait_readable() const {
10289 if (max_timeout_msec_ <= 0) {
10290 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10291 }
10292
10293 time_t read_timeout_sec;
10294 time_t read_timeout_usec;
10295 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10296 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10297
10298 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10299}
10300
10301inline bool SocketStream::wait_writable() const {
10302 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
10303}
10304
10305inline bool SocketStream::is_peer_alive() const {
10306 return detail::is_socket_alive(sock_);
10307}
10308
10309inline ssize_t SocketStream::read(char *ptr, size_t size) {
10310#ifdef _WIN32
10311 size =
10312 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10313#else
10314 size = (std::min)(size,
10315 static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
10316#endif
10317
10318 if (read_buff_off_ < read_buff_content_size_) {
10319 auto remaining_size = read_buff_content_size_ - read_buff_off_;
10320 if (size <= remaining_size) {
10321 memcpy(ptr, read_buff_.data() + read_buff_off_, size);
10322 read_buff_off_ += size;
10323 return static_cast<ssize_t>(size);
10324 } else {
10325 memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
10326 read_buff_off_ += remaining_size;
10327 return static_cast<ssize_t>(remaining_size);
10328 }
10329 }
10330
10331 if (!wait_readable()) {
10333 return -1;
10334 }
10335
10336 read_buff_off_ = 0;
10337 read_buff_content_size_ = 0;
10338
10339 if (size < read_buff_size_) {
10340 auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
10342 if (n <= 0) {
10343 if (n == 0) {
10345 } else {
10347 }
10348 return n;
10349 } else if (n <= static_cast<ssize_t>(size)) {
10350 memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
10351 return n;
10352 } else {
10353 memcpy(ptr, read_buff_.data(), size);
10354 read_buff_off_ = size;
10355 read_buff_content_size_ = static_cast<size_t>(n);
10356 return static_cast<ssize_t>(size);
10357 }
10358 } else {
10359 auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
10360 if (n <= 0) {
10361 if (n == 0) {
10363 } else {
10365 }
10366 }
10367 return n;
10368 }
10369}
10370
10371inline ssize_t SocketStream::write(const char *ptr, size_t size) {
10372 if (!wait_writable()) { return -1; }
10373
10374#if defined(_WIN32) && !defined(_WIN64)
10375 size =
10376 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10377#endif
10378
10379 return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
10380}
10381
10382inline void SocketStream::get_remote_ip_and_port(std::string &ip,
10383 int &port) const {
10384 return detail::get_remote_ip_and_port(sock_, ip, port);
10385}
10386
10387inline void SocketStream::get_local_ip_and_port(std::string &ip,
10388 int &port) const {
10389 return detail::get_local_ip_and_port(sock_, ip, port);
10390}
10391
10392inline socket_t SocketStream::socket() const { return sock_; }
10393
10394inline time_t SocketStream::duration() const {
10395 return std::chrono::duration_cast<std::chrono::milliseconds>(
10396 std::chrono::steady_clock::now() - start_time_)
10397 .count();
10398}
10399
10400inline void SocketStream::set_read_timeout(time_t sec, time_t usec) {
10401 read_timeout_sec_ = sec;
10402 read_timeout_usec_ = usec;
10403}
10404
10405// Buffer stream implementation
10406inline bool BufferStream::is_readable() const { return true; }
10407
10408inline bool BufferStream::wait_readable() const { return true; }
10409
10410inline bool BufferStream::wait_writable() const { return true; }
10411
10412inline ssize_t BufferStream::read(char *ptr, size_t size) {
10413#if defined(_MSC_VER) && _MSC_VER < 1910
10414 auto len_read = buffer._Copy_s(ptr, size, size, position);
10415#else
10416 auto len_read = buffer.copy(ptr, size, position);
10417#endif
10418 position += static_cast<size_t>(len_read);
10419 return static_cast<ssize_t>(len_read);
10420}
10421
10422inline ssize_t BufferStream::write(const char *ptr, size_t size) {
10423 buffer.append(ptr, size);
10424 return static_cast<ssize_t>(size);
10425}
10426
10427inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
10428 int & /*port*/) const {}
10429
10430inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
10431 int & /*port*/) const {}
10432
10433inline socket_t BufferStream::socket() const { return 0; }
10434
10435inline time_t BufferStream::duration() const { return 0; }
10436
10437inline const std::string &BufferStream::get_buffer() const { return buffer; }
10438
10440 : MatcherBase(pattern) {
10441 constexpr const char marker[] = "/:";
10442
10443 // One past the last ending position of a path param substring
10444 std::size_t last_param_end = 0;
10445
10446#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10447 // Needed to ensure that parameter names are unique during matcher
10448 // construction
10449 // If exceptions are disabled, only last duplicate path
10450 // parameter will be set
10451 std::unordered_set<std::string> param_name_set;
10452#endif
10453
10454 while (true) {
10455 const auto marker_pos = pattern.find(
10456 marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
10457 if (marker_pos == std::string::npos) { break; }
10458
10459 static_fragments_.push_back(
10460 pattern.substr(last_param_end, marker_pos - last_param_end + 1));
10461
10462 const auto param_name_start = marker_pos + str_len(marker);
10463
10464 auto sep_pos = pattern.find(separator, param_name_start);
10465 if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
10466
10467 auto param_name =
10468 pattern.substr(param_name_start, sep_pos - param_name_start);
10469
10470#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10471 if (param_name_set.find(param_name) != param_name_set.cend()) {
10472 std::string msg = "Encountered path parameter '" + param_name +
10473 "' multiple times in route pattern '" + pattern + "'.";
10474 throw std::invalid_argument(msg);
10475 }
10476#endif
10477
10478 param_names_.push_back(std::move(param_name));
10479
10480 last_param_end = sep_pos + 1;
10481 }
10482
10483 if (last_param_end < pattern.length()) {
10484 static_fragments_.push_back(pattern.substr(last_param_end));
10485 }
10486}
10487
10488inline bool PathParamsMatcher::match(Request &request) const {
10489 request.matches = std::smatch();
10490 request.path_params.clear();
10491 request.path_params.reserve(param_names_.size());
10492
10493 // One past the position at which the path matched the pattern last time
10494 std::size_t starting_pos = 0;
10495 for (size_t i = 0; i < static_fragments_.size(); ++i) {
10496 const auto &fragment = static_fragments_[i];
10497
10498 if (starting_pos + fragment.length() > request.path.length()) {
10499 return false;
10500 }
10501
10502 // Avoid unnecessary allocation by using strncmp instead of substr +
10503 // comparison
10504 if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
10505 fragment.length()) != 0) {
10506 return false;
10507 }
10508
10509 starting_pos += fragment.length();
10510
10511 // Should only happen when we have a static fragment after a param
10512 // Example: '/users/:id/subscriptions'
10513 // The 'subscriptions' fragment here does not have a corresponding param
10514 if (i >= param_names_.size()) { continue; }
10515
10516 auto sep_pos = request.path.find(separator, starting_pos);
10517 if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
10518
10519 const auto &param_name = param_names_[i];
10520
10521 request.path_params.emplace(
10522 param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
10523
10524 // Mark everything up to '/' as matched
10525 starting_pos = sep_pos + 1;
10526 }
10527 // Returns false if the path is longer than the pattern
10528 return starting_pos >= request.path.length();
10529}
10530
10531inline bool RegexMatcher::match(Request &request) const {
10532 request.path_params.clear();
10533 return std::regex_match(request.path, request.matches, regex_);
10534}
10535
10536// Enclose IPv6 address in brackets if needed
10537inline std::string prepare_host_string(const std::string &host) {
10538 // Enclose IPv6 address in brackets (but not if already enclosed)
10539 if (host.find(':') == std::string::npos ||
10540 (!host.empty() && host[0] == '[')) {
10541 // IPv4, hostname, or already bracketed IPv6
10542 return host;
10543 } else {
10544 // IPv6 address without brackets
10545 return "[" + host + "]";
10546 }
10547}
10548
10549inline std::string make_host_and_port_string(const std::string &host, int port,
10550 bool is_ssl) {
10551 auto result = prepare_host_string(host);
10552
10553 // Append port if not default
10554 if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
10555 ; // do nothing
10556 } else {
10557 result += ":" + std::to_string(port);
10558 }
10559
10560 return result;
10561}
10562
10563// Create "host:port" string always including port number (for CONNECT method)
10564inline std::string
10565make_host_and_port_string_always_port(const std::string &host, int port) {
10566 return prepare_host_string(host) + ":" + std::to_string(port);
10567}
10568
10569bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out);
10570NormalizedTarget normalize_target(const std::string &host);
10571bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits);
10572bool host_matches_no_proxy(const NormalizedTarget &target,
10573 const std::vector<NoProxyEntry> &entries);
10574
10575inline bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) {
10576 if (prefix_bits < 0 || prefix_bits > 128) { return false; }
10577 if (prefix_bits == 0) { return true; }
10578 int full_bytes = prefix_bits / 8;
10579 int rem_bits = prefix_bits % 8;
10580 if (full_bytes > 0 && std::memcmp(ip.data(), net.data(),
10581 static_cast<size_t>(full_bytes)) != 0) {
10582 return false;
10583 }
10584 if (rem_bits == 0) { return true; }
10585 auto i = static_cast<size_t>(full_bytes);
10586 auto mask = static_cast<uint8_t>(0xFFu << (8 - rem_bits));
10587 return (ip[i] & mask) == (net[i] & mask);
10588}
10589
10590inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
10591 if (token.empty()) { return false; }
10592
10593 if (token == "*") {
10595 return true;
10596 }
10597
10598 auto slash = token.find('/');
10599 std::string addr_part =
10600 (slash == std::string::npos) ? token : token.substr(0, slash);
10601 std::string prefix_part =
10602 (slash == std::string::npos) ? std::string() : token.substr(slash + 1);
10603
10604 // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed;
10605 // don't silently treat it as a /32 (or /128).
10606 if (slash != std::string::npos && prefix_part.empty()) { return false; }
10607
10608 // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the
10609 // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt
10610 // when brackets are present.
10611 bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' &&
10612 addr_part.back() == ']';
10613 if (bracketed) { addr_part = addr_part.substr(1, addr_part.size() - 2); }
10614
10615 if (!bracketed) {
10616 struct in_addr v4;
10617 if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
10618 int prefix = 32;
10619 if (!prefix_part.empty()) {
10620 auto r = from_chars(prefix_part.data(),
10621 prefix_part.data() + prefix_part.size(), prefix);
10622 if (r.ec != std::errc{} ||
10623 r.ptr != prefix_part.data() + prefix_part.size()) {
10624 return false;
10625 }
10626 if (prefix < 0 || prefix > 32) { return false; }
10627 }
10629 std::memcpy(out.net.data(), &v4, sizeof(v4));
10630 out.prefix_bits = prefix;
10631 return true;
10632 }
10633 }
10634
10635 struct in6_addr v6;
10636 if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
10637 int prefix = 128;
10638 if (!prefix_part.empty()) {
10639 auto r = from_chars(prefix_part.data(),
10640 prefix_part.data() + prefix_part.size(), prefix);
10641 if (r.ec != std::errc{} ||
10642 r.ptr != prefix_part.data() + prefix_part.size()) {
10643 return false;
10644 }
10645 if (prefix < 0 || prefix > 128) { return false; }
10646 }
10648 std::memcpy(out.net.data(), &v6, sizeof(v6));
10649 out.prefix_bits = prefix;
10650 return true;
10651 }
10652
10653 // Bracketed entries can only be IPv6. If the IPv6 parse above failed,
10654 // the entry is malformed — don't fall through to the hostname branch.
10655 if (bracketed) { return false; }
10656
10657 // A '/' on a non-IP token means a CIDR prefix without an address. Reject.
10658 if (slash != std::string::npos) { return false; }
10659 // Port-specific entries (host:port) are not supported.
10660 if (token.find(':') != std::string::npos) { return false; }
10661
10662 std::string hostname = case_ignore::to_lower(token);
10663 while (!hostname.empty() && hostname.front() == '.') {
10664 hostname.erase(hostname.begin());
10665 }
10666 while (!hostname.empty() && hostname.back() == '.') {
10667 hostname.pop_back();
10668 }
10669 if (hostname.empty()) { return false; }
10670
10672 out.hostname_pattern = std::move(hostname);
10673 return true;
10674}
10675
10676inline NormalizedTarget normalize_target(const std::string &host) {
10678 std::string h = host;
10679
10680 if (h.size() >= 2 && h.front() == '[' && h.back() == ']') {
10681 h = h.substr(1, h.size() - 2);
10682 }
10683
10684 // Strip a single trailing dot so "example.com." canonicalizes to
10685 // "example.com".
10686 if (!h.empty() && h.back() == '.') { h.pop_back(); }
10687
10689
10690 if (!t.hostname.empty()) {
10691 struct in_addr v4;
10692 struct in6_addr v6;
10693 if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) {
10694 t.is_ipv4 = true;
10695 std::memcpy(t.ip.data(), &v4, sizeof(v4));
10696 } else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) {
10697 t.is_ipv6 = true;
10698 std::memcpy(t.ip.data(), &v6, sizeof(v6));
10699 }
10700 }
10701 return t;
10702}
10703
10704inline bool host_matches_no_proxy(const NormalizedTarget &target,
10705 const std::vector<NoProxyEntry> &entries) {
10706 if (target.hostname.empty()) { return false; }
10707 for (const auto &e : entries) {
10708 switch (e.kind) {
10709 case NoProxyKind::Wildcard: return true;
10711 if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10712 return true;
10713 }
10714 break;
10716 if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10717 return true;
10718 }
10719 break;
10721 if (target.is_ipv4 || target.is_ipv6) { break; }
10722 if (target.hostname == e.hostname_pattern) { return true; }
10723 // Dot-boundary suffix match: prevents "evilexample.com" from matching
10724 // an entry of "example.com".
10725 if (target.hostname.size() > e.hostname_pattern.size() + 1) {
10726 auto offset = target.hostname.size() - e.hostname_pattern.size();
10727 if (target.hostname[offset - 1] == '.' &&
10728 target.hostname.compare(offset, e.hostname_pattern.size(),
10729 e.hostname_pattern) == 0) {
10730 return true;
10731 }
10732 }
10733 break;
10734 }
10735 }
10736 return false;
10737}
10738
10739template <typename T>
10740inline bool check_and_write_headers(Stream &strm, Headers &headers,
10741 T header_writer, Error &error) {
10742 for (const auto &h : headers) {
10743 if (!detail::fields::is_field_name(h.first) ||
10744 !detail::fields::is_field_value(h.second)) {
10746 return false;
10747 }
10748 }
10749 if (header_writer(strm, headers) <= 0) {
10751 return false;
10752 }
10753 return true;
10754}
10755
10756} // namespace detail
10757
10758/*
10759 * Group 2 (continued): detail namespace - SSLSocketStream implementation
10760 */
10761
10762#ifdef CPPHTTPLIB_SSL_ENABLED
10763namespace detail {
10764
10765// SSL socket stream implementation
10766inline SSLSocketStream::SSLSocketStream(
10767 socket_t sock, tls::session_t session, time_t read_timeout_sec,
10768 time_t read_timeout_usec, time_t write_timeout_sec,
10769 time_t write_timeout_usec, time_t max_timeout_msec,
10770 std::chrono::time_point<std::chrono::steady_clock> start_time)
10771 : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
10772 read_timeout_usec_(read_timeout_usec),
10773 write_timeout_sec_(write_timeout_sec),
10774 write_timeout_usec_(write_timeout_usec),
10775 max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
10776#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10777 // Clear AUTO_RETRY for proper non-blocking I/O timeout handling
10778 // Note: create_session() also clears this, but SSLClient currently
10779 // uses ssl_new() which does not. Until full TLS API migration is complete,
10780 // we need to ensure AUTO_RETRY is cleared here regardless of how the
10781 // SSL session was created.
10782 SSL_clear_mode(static_cast<SSL *>(session), SSL_MODE_AUTO_RETRY);
10783#endif
10784}
10785
10786inline SSLSocketStream::~SSLSocketStream() = default;
10787
10788inline bool SSLSocketStream::is_readable() const {
10789 return tls::pending(session_) > 0;
10790}
10791
10792inline bool SSLSocketStream::wait_readable() const {
10793 if (max_timeout_msec_ <= 0) {
10794 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10795 }
10796
10797 time_t read_timeout_sec;
10798 time_t read_timeout_usec;
10799 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10800 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10801
10802 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10803}
10804
10805inline bool SSLSocketStream::wait_writable() const {
10806 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
10807 !tls::is_peer_closed(session_, sock_);
10808}
10809
10810inline bool SSLSocketStream::is_peer_alive() const {
10811 return !tls::is_peer_closed(session_, sock_);
10812}
10813
10814inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
10815 if (tls::pending(session_) > 0) {
10816 tls::TlsError err;
10817 auto ret = tls::read(session_, ptr, size, err);
10818 if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10819 error_ = Error::ConnectionClosed;
10820 }
10821 return ret;
10822 } else if (wait_readable()) {
10823 tls::TlsError err;
10824 auto ret = tls::read(session_, ptr, size, err);
10825 if (ret < 0) {
10826 auto n = 1000;
10827#ifdef _WIN32
10828 while (--n >= 0 && (err.code == tls::ErrorCode::WantRead ||
10829 (err.code == tls::ErrorCode::SyscallError &&
10830 WSAGetLastError() == WSAETIMEDOUT))) {
10831#else
10832 while (--n >= 0 && err.code == tls::ErrorCode::WantRead) {
10833#endif
10834 if (tls::pending(session_) > 0) {
10835 return tls::read(session_, ptr, size, err);
10836 } else if (wait_readable()) {
10837 std::this_thread::sleep_for(std::chrono::microseconds{10});
10838 ret = tls::read(session_, ptr, size, err);
10839 if (ret >= 0) { return ret; }
10840 } else {
10841 break;
10842 }
10843 }
10844 assert(ret < 0);
10845 } else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
10846 error_ = Error::ConnectionClosed;
10847 }
10848 return ret;
10849 } else {
10850 error_ = Error::Timeout;
10851 return -1;
10852 }
10853}
10854
10855inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
10856 if (wait_writable()) {
10857 auto handle_size =
10858 std::min<size_t>(size, (std::numeric_limits<int>::max)());
10859
10860 tls::TlsError err;
10861 auto ret = tls::write(session_, ptr, handle_size, err);
10862 if (ret < 0) {
10863 auto n = 1000;
10864#ifdef _WIN32
10865 while (--n >= 0 && (err.code == tls::ErrorCode::WantWrite ||
10866 (err.code == tls::ErrorCode::SyscallError &&
10867 WSAGetLastError() == WSAETIMEDOUT))) {
10868#else
10869 while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) {
10870#endif
10871 if (wait_writable()) {
10872 std::this_thread::sleep_for(std::chrono::microseconds{10});
10873 ret = tls::write(session_, ptr, handle_size, err);
10874 if (ret >= 0) { return ret; }
10875 } else {
10876 break;
10877 }
10878 }
10879 assert(ret < 0);
10880 }
10881 return ret;
10882 }
10883 return -1;
10884}
10885
10886inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
10887 int &port) const {
10888 detail::get_remote_ip_and_port(sock_, ip, port);
10889}
10890
10891inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
10892 int &port) const {
10893 detail::get_local_ip_and_port(sock_, ip, port);
10894}
10895
10896inline socket_t SSLSocketStream::socket() const { return sock_; }
10897
10898inline time_t SSLSocketStream::duration() const {
10899 return std::chrono::duration_cast<std::chrono::milliseconds>(
10900 std::chrono::steady_clock::now() - start_time_)
10901 .count();
10902}
10903
10904inline void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) {
10905 read_timeout_sec_ = sec;
10906 read_timeout_usec_ = usec;
10907}
10908
10909} // namespace detail
10910#endif // CPPHTTPLIB_SSL_ENABLED
10911
10912/*
10913 * Group 4: Server implementation
10914 */
10915
10916// HTTP server implementation
10918 : new_task_queue([] {
10921 }) {
10922#ifndef _WIN32
10923 signal(SIGPIPE, SIG_IGN);
10924#endif
10925}
10926
10927inline Server::~Server() = default;
10928
10929inline std::unique_ptr<detail::MatcherBase>
10930Server::make_matcher(const std::string &pattern) {
10931 if (pattern.find("/:") != std::string::npos) {
10933 } else {
10935 }
10936}
10937
10938inline Server &Server::Get(const std::string &pattern, Handler handler) {
10939 return add_handler(get_handlers_, pattern, std::move(handler));
10940}
10941
10942inline Server &Server::Post(const std::string &pattern, Handler handler) {
10943 return add_handler(post_handlers_, pattern, std::move(handler));
10944}
10945
10946inline Server &Server::Post(const std::string &pattern,
10947 HandlerWithContentReader handler) {
10948 return add_handler(post_handlers_for_content_reader_, pattern,
10949 std::move(handler));
10950}
10951
10952inline Server &Server::Put(const std::string &pattern, Handler handler) {
10953 return add_handler(put_handlers_, pattern, std::move(handler));
10954}
10955
10956inline Server &Server::Put(const std::string &pattern,
10957 HandlerWithContentReader handler) {
10958 return add_handler(put_handlers_for_content_reader_, pattern,
10959 std::move(handler));
10960}
10961
10962inline Server &Server::Patch(const std::string &pattern, Handler handler) {
10963 return add_handler(patch_handlers_, pattern, std::move(handler));
10964}
10965
10966inline Server &Server::Patch(const std::string &pattern,
10967 HandlerWithContentReader handler) {
10968 return add_handler(patch_handlers_for_content_reader_, pattern,
10969 std::move(handler));
10970}
10971
10972inline Server &Server::Delete(const std::string &pattern, Handler handler) {
10973 return add_handler(delete_handlers_, pattern, std::move(handler));
10974}
10975
10976inline Server &Server::Delete(const std::string &pattern,
10977 HandlerWithContentReader handler) {
10978 return add_handler(delete_handlers_for_content_reader_, pattern,
10979 std::move(handler));
10980}
10981
10982inline Server &Server::Options(const std::string &pattern, Handler handler) {
10983 return add_handler(options_handlers_, pattern, std::move(handler));
10984}
10985
10986inline Server &Server::WebSocket(const std::string &pattern,
10987 WebSocketHandler handler) {
10988 websocket_handlers_.push_back(
10989 {make_matcher(pattern), std::move(handler), nullptr});
10990 return *this;
10991}
10992
10993inline Server &Server::WebSocket(const std::string &pattern,
10994 WebSocketHandler handler,
10995 SubProtocolSelector sub_protocol_selector) {
10996 websocket_handlers_.push_back({make_matcher(pattern), std::move(handler),
10997 std::move(sub_protocol_selector)});
10998 return *this;
10999}
11000
11001inline bool Server::set_base_dir(const std::string &dir,
11002 const std::string &mount_point) {
11003 return set_mount_point(mount_point, dir);
11004}
11005
11006inline bool Server::set_mount_point(const std::string &mount_point,
11007 const std::string &dir, Headers headers) {
11008 detail::FileStat stat(dir);
11009 if (stat.is_dir()) {
11010 std::string mnt = !mount_point.empty() ? mount_point : "/";
11011 if (!mnt.empty() && mnt[0] == '/') {
11012 std::string resolved_base;
11013 if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
11014#if defined(_WIN32)
11015 if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
11016 resolved_base += '\\';
11017 }
11018#else
11019 if (resolved_base.back() != '/') { resolved_base += '/'; }
11020#endif
11021 }
11022 base_dirs_.push_back(
11023 {std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
11024 return true;
11025 }
11026 }
11027 return false;
11028}
11029
11030inline bool Server::remove_mount_point(const std::string &mount_point) {
11031 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
11032 if (it->mount_point == mount_point) {
11033 base_dirs_.erase(it);
11034 return true;
11035 }
11036 }
11037 return false;
11038}
11039
11040inline Server &
11042 const std::string &mime) {
11043 file_extension_and_mimetype_map_[ext] = mime;
11044 return *this;
11045}
11046
11047inline Server &Server::set_default_file_mimetype(const std::string &mime) {
11048 default_file_mimetype_ = mime;
11049 return *this;
11050}
11051
11053 file_request_handler_ = std::move(handler);
11054 return *this;
11055}
11056
11057inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
11058 std::true_type) {
11059 error_handler_ = std::move(handler);
11060 return *this;
11061}
11062
11063inline Server &Server::set_error_handler_core(Handler handler,
11064 std::false_type) {
11065 error_handler_ = [handler](const Request &req, Response &res) {
11066 handler(req, res);
11068 };
11069 return *this;
11070}
11071
11073 exception_handler_ = std::move(handler);
11074 return *this;
11075}
11076
11078 pre_routing_handler_ = std::move(handler);
11079 return *this;
11080}
11081
11083 post_routing_handler_ = std::move(handler);
11084 return *this;
11085}
11086
11088 pre_request_handler_ = std::move(handler);
11089 return *this;
11090}
11091
11093 logger_ = std::move(logger);
11094 return *this;
11095}
11096
11098 error_logger_ = std::move(error_logger);
11099 return *this;
11100}
11101
11103 pre_compression_logger_ = std::move(logger);
11104 return *this;
11105}
11106
11107inline Server &
11109 expect_100_continue_handler_ = std::move(handler);
11110 return *this;
11111}
11112
11114 start_handler_ = std::move(handler);
11115 return *this;
11116}
11117
11119 address_family_ = family;
11120 return *this;
11121}
11122
11124 tcp_nodelay_ = on;
11125 return *this;
11126}
11127
11129 ipv6_v6only_ = on;
11130 return *this;
11131}
11132
11134 socket_options_ = std::move(socket_options);
11135 return *this;
11136}
11137
11139 default_headers_ = std::move(headers);
11140 return *this;
11141}
11142
11144 std::function<ssize_t(Stream &, Headers &)> const &writer) {
11145 header_writer_ = writer;
11146 return *this;
11147}
11148
11149inline Server &
11150Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
11151 trusted_proxies_ = proxies;
11152 return *this;
11153}
11154
11156 keep_alive_max_count_ = count;
11157 return *this;
11158}
11159
11162 return *this;
11163}
11164
11165template <class Rep, class Period>
11167 const std::chrono::duration<Rep, Period> &duration) {
11168 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11170 });
11171 return *this;
11172}
11173
11174inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
11175 read_timeout_sec_ = sec;
11176 read_timeout_usec_ = usec;
11177 return *this;
11178}
11179
11180inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
11181 write_timeout_sec_ = sec;
11182 write_timeout_usec_ = usec;
11183 return *this;
11184}
11185
11186inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
11187 idle_interval_sec_ = sec;
11188 idle_interval_usec_ = usec;
11189 return *this;
11190}
11191
11193 payload_max_length_ = length;
11194 return *this;
11195}
11196
11199 return *this;
11200}
11201
11204 return *this;
11205}
11206
11207template <class Rep, class Period>
11209 const std::chrono::duration<Rep, Period> &duration) {
11210 detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11212 });
11213 return *this;
11214}
11215
11216inline bool Server::bind_to_port(const std::string &host, int port,
11217 int socket_flags) {
11218 auto ret = bind_internal(host, port, socket_flags);
11219 if (ret == -1) { is_decommissioned = true; }
11220 return ret >= 0;
11221}
11222inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
11223 auto ret = bind_internal(host, 0, socket_flags);
11224 if (ret == -1) { is_decommissioned = true; }
11225 return ret;
11226}
11227
11228inline bool Server::listen_after_bind() { return listen_internal(); }
11229
11230inline bool Server::listen(const std::string &host, int port,
11231 int socket_flags) {
11232 return bind_to_port(host, port, socket_flags) && listen_internal();
11233}
11234
11235inline bool Server::is_running() const { return is_running_; }
11236
11237inline void Server::wait_until_ready() const {
11238 while (!is_running_ && !is_decommissioned) {
11239 std::this_thread::sleep_for(std::chrono::milliseconds{1});
11240 }
11241}
11242
11243inline void Server::stop() noexcept {
11244 if (is_running_) {
11245 assert(svr_sock_ != INVALID_SOCKET);
11246 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
11249 }
11250 is_decommissioned = false;
11251}
11252
11253inline void Server::decommission() { is_decommissioned = true; }
11254
11255inline bool Server::parse_request_line(const char *s, Request &req) const {
11256 auto len = strlen(s);
11257 if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
11258 len -= 2;
11259
11260 {
11261 size_t count = 0;
11262
11263 detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
11264 switch (count) {
11265 case 0: req.method = std::string(b, e); break;
11266 case 1: req.target = std::string(b, e); break;
11267 case 2: req.version = std::string(b, e); break;
11268 default: break;
11269 }
11270 count++;
11271 });
11272
11273 if (count != 3) { return false; }
11274 }
11275
11276 thread_local const std::set<std::string> methods{
11277 "GET", "HEAD", "POST", "PUT", "DELETE",
11278 "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
11279
11280 if (methods.find(req.method) == methods.end()) {
11281 output_error_log(Error::InvalidHTTPMethod, &req);
11282 return false;
11283 }
11284
11285 if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
11286 output_error_log(Error::InvalidHTTPVersion, &req);
11287 return false;
11288 }
11289
11290 {
11291 // Skip URL fragment
11292 for (size_t i = 0; i < req.target.size(); i++) {
11293 if (req.target[i] == '#') {
11294 req.target.erase(i);
11295 break;
11296 }
11297 }
11298
11299 detail::divide(req.target, '?',
11300 [&](const char *lhs_data, std::size_t lhs_size,
11301 const char *rhs_data, std::size_t rhs_size) {
11302 req.path =
11303 decode_path_component(std::string(lhs_data, lhs_size));
11304 detail::parse_query_text(rhs_data, rhs_size, req.params);
11305 });
11306 }
11307
11308 return true;
11309}
11310
11311inline bool Server::write_response(Stream &strm, bool close_connection,
11312 Request &req, Response &res) {
11313 // NOTE: `req.ranges` should be empty, otherwise it will be applied
11314 // incorrectly to the error content.
11315 req.ranges.clear();
11316 return write_response_core(strm, close_connection, req, res, false);
11317}
11318
11319inline bool Server::write_response_with_content(Stream &strm,
11320 bool close_connection,
11321 const Request &req,
11322 Response &res) {
11323 return write_response_core(strm, close_connection, req, res, true);
11324}
11325
11326inline bool Server::write_response_core(Stream &strm, bool close_connection,
11327 const Request &req, Response &res,
11328 bool need_apply_ranges) {
11329 assert(res.status != -1);
11330
11331 if (400 <= res.status && error_handler_ &&
11332 error_handler_(req, res) == HandlerResponse::Handled) {
11333 need_apply_ranges = true;
11334 }
11335
11336 std::string content_type;
11337 std::string boundary;
11338 if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
11339
11340 // Prepare additional headers
11341 if (close_connection || req.get_header_value("Connection") == "close" ||
11342 400 <= res.status) { // Don't leave connections open after errors
11343 res.set_header("Connection", "close");
11344 } else {
11345 std::string s = "timeout=";
11346 s += std::to_string(keep_alive_timeout_sec_);
11347 s += ", max=";
11348 s += std::to_string(keep_alive_max_count_);
11349 res.set_header("Keep-Alive", s);
11350 }
11351
11352 if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
11353 !res.has_header("Content-Type")) {
11354 res.set_header("Content-Type", "text/plain");
11355 }
11356
11357 if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
11358 !res.has_header("Content-Length")) {
11359 res.set_header("Content-Length", "0");
11360 }
11361
11362 if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
11363 res.set_header("Accept-Ranges", "bytes");
11364 }
11365
11366 if (post_routing_handler_) { post_routing_handler_(req, res); }
11367
11368 // Response line and headers
11369 detail::BufferStream bstrm;
11370 if (!detail::write_response_line(bstrm, res.status)) { return false; }
11371 if (header_writer_(bstrm, res.headers) <= 0) { return false; }
11372
11373 // Combine small body with headers to reduce write syscalls
11374 if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
11375 bstrm.write(res.body.data(), res.body.size());
11376 }
11377
11378 // Log before writing to avoid race condition with client-side code that
11379 // accesses logger-captured data immediately after receiving the response.
11380 output_log(req, res);
11381
11382 // Flush buffer
11383 auto &data = bstrm.get_buffer();
11384 if (!detail::write_data(strm, data.data(), data.size())) { return false; }
11385
11386 // Streaming body
11387 auto ret = true;
11388 if (req.method != "HEAD" && res.content_provider_) {
11389 if (write_content_with_provider(strm, req, res, boundary, content_type)) {
11390 res.content_provider_success_ = true;
11391 } else {
11392 ret = false;
11393 }
11394 }
11395
11396 return ret;
11397}
11398
11399inline bool
11400Server::write_content_with_provider(Stream &strm, const Request &req,
11401 Response &res, const std::string &boundary,
11402 const std::string &content_type) {
11403 auto is_shutting_down = [this]() {
11404 return this->svr_sock_ == INVALID_SOCKET;
11405 };
11406
11407 if (res.content_length_ > 0) {
11408 if (req.ranges.empty()) {
11409 return detail::write_content(strm, res.content_provider_, 0,
11410 res.content_length_, is_shutting_down);
11411 } else if (req.ranges.size() == 1) {
11412 auto offset_and_length = detail::get_range_offset_and_length(
11413 req.ranges[0], res.content_length_);
11414
11415 return detail::write_content(strm, res.content_provider_,
11416 offset_and_length.first,
11417 offset_and_length.second, is_shutting_down);
11418 } else {
11420 strm, req, res, boundary, content_type, res.content_length_,
11421 is_shutting_down);
11422 }
11423 } else {
11424 if (res.is_chunked_content_provider_) {
11425 auto type = detail::encoding_type(req, res);
11426
11427 auto compressor = detail::make_compressor(type);
11428 if (!compressor) {
11430 }
11431
11432 return detail::write_content_chunked(strm, res.content_provider_,
11433 is_shutting_down, *compressor);
11434 } else {
11435 return detail::write_content_without_length(strm, res.content_provider_,
11436 is_shutting_down);
11437 }
11438 }
11439}
11440
11441inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
11442 FormFields::iterator cur_field;
11443 FormFiles::iterator cur_file;
11444 auto is_text_field = false;
11445 size_t count = 0;
11446 if (read_content_core(
11447 strm, req, res,
11448 // Regular
11449 [&](const char *buf, size_t n) {
11450 // Prevent arithmetic overflow when checking sizes.
11451 // Avoid computing (req.body.size() + n) directly because
11452 // adding two unsigned `size_t` values can wrap around and
11453 // produce a small result instead of indicating overflow.
11454 // Instead, check using subtraction: ensure `n` does not
11455 // exceed the remaining capacity `max_size() - size()`.
11456 if (req.body.size() >= req.body.max_size() ||
11457 n > req.body.max_size() - req.body.size()) {
11458 return false;
11459 }
11460
11461 // Limit decompressed body size to payload_max_length_ to protect
11462 // against "zip bomb" attacks where a small compressed payload
11463 // decompresses to a massive size.
11464 if (payload_max_length_ > 0 &&
11465 (req.body.size() >= payload_max_length_ ||
11466 n > payload_max_length_ - req.body.size())) {
11467 return false;
11468 }
11469
11470 req.body.append(buf, n);
11471 return true;
11472 },
11473 // Multipart FormData
11474 [&](const FormData &file) {
11476 output_error_log(Error::TooManyFormDataFiles, &req);
11477 return false;
11478 }
11479
11480 if (file.filename.empty()) {
11481 cur_field = req.form.fields.emplace(
11482 file.name, FormField{file.name, file.content, file.headers});
11483 is_text_field = true;
11484 } else {
11485 cur_file = req.form.files.emplace(file.name, file);
11486 is_text_field = false;
11487 }
11488 return true;
11489 },
11490 [&](const char *buf, size_t n) {
11491 if (is_text_field) {
11492 auto &content = cur_field->second.content;
11493 if (content.size() + n > content.max_size()) { return false; }
11494 content.append(buf, n);
11495 } else {
11496 auto &content = cur_file->second.content;
11497 if (content.size() + n > content.max_size()) { return false; }
11498 content.append(buf, n);
11499 }
11500 return true;
11501 })) {
11502 const auto &content_type = req.get_header_value("Content-Type");
11503 if (detail::extract_media_type(content_type) ==
11504 "application/x-www-form-urlencoded") {
11505 if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
11506 res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
11507 output_error_log(Error::ExceedMaxPayloadSize, &req);
11508 return false;
11509 }
11510 detail::parse_query_text(req.body, req.params);
11511 }
11512 return true;
11513 }
11514 return false;
11515}
11516
11517inline bool Server::read_content_with_content_receiver(
11518 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11519 FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
11520 return read_content_core(strm, req, res, std::move(receiver),
11521 std::move(multipart_header),
11522 std::move(multipart_receiver));
11523}
11524
11525inline bool Server::read_content_core(
11526 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11527 FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
11528 detail::FormDataParser multipart_form_data_parser;
11530
11531 if (req.is_multipart_form_data()) {
11532 const auto &content_type = req.get_header_value("Content-Type");
11533 std::string boundary;
11534 if (!detail::parse_multipart_boundary(content_type, boundary)) {
11535 res.status = StatusCode::BadRequest_400;
11536 output_error_log(Error::MultipartParsing, &req);
11537 return false;
11538 }
11539
11540 multipart_form_data_parser.set_boundary(std::move(boundary));
11541 out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
11542 return multipart_form_data_parser.parse(buf, n, multipart_header,
11543 multipart_receiver);
11544 };
11545 } else {
11546 out = [receiver](const char *buf, size_t n, size_t /*off*/,
11547 size_t /*len*/) { return receiver(buf, n); };
11548 }
11549
11550 // RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body.
11551 // For non-SSL builds we still scan non-persistent connections for stray
11552 // body bytes so the payload limit is enforced (413). On keep-alive,
11553 // pending bytes may be the next request (issue #2450), so skip.
11554#if !defined(CPPHTTPLIB_SSL_ENABLED)
11555 if (!req.has_header("Content-Length") &&
11558 payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
11559 auto has_data = strm.is_readable();
11560 if (!has_data) {
11561 auto s = strm.socket();
11562 if (s != INVALID_SOCKET) {
11563 has_data = detail::select_read(s, 0, 0) > 0;
11564 }
11565 }
11566 if (has_data) {
11567 auto result =
11571 return false;
11572 } else if (result != detail::ReadContentResult::Success) {
11573 return false;
11574 }
11575 return true;
11576 }
11577 }
11578 return true;
11579 }
11580#else
11581 if (!req.has_header("Content-Length") &&
11583 return true;
11584 }
11585#endif
11586
11587 if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
11588 out, true)) {
11589 return false;
11590 }
11591
11592 req.body_consumed_ = true;
11593
11594 if (req.is_multipart_form_data()) {
11595 if (!multipart_form_data_parser.is_valid()) {
11596 res.status = StatusCode::BadRequest_400;
11597 output_error_log(Error::MultipartParsing, &req);
11598 return false;
11599 }
11600 }
11601
11602 return true;
11603}
11604
11605inline bool Server::handle_file_request(Request &req, Response &res) {
11606 for (const auto &entry : base_dirs_) {
11607 // Prefix match
11608 if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
11609 std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
11610 if (detail::is_valid_path(sub_path)) {
11611 auto path = entry.base_dir + sub_path;
11612 if (path.back() == '/') { path += "index.html"; }
11613
11614 // Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
11615 // but symlinks/junctions can still escape the base directory.
11616 if (!entry.resolved_base_dir.empty()) {
11617 std::string resolved_path;
11618 if (detail::canonicalize_path(path.c_str(), resolved_path) &&
11619 !detail::is_path_within_base(resolved_path,
11620 entry.resolved_base_dir)) {
11621 res.status = StatusCode::Forbidden_403;
11622 return true;
11623 }
11624 }
11625
11626 detail::FileStat stat(path);
11627
11628 if (stat.is_dir()) {
11629 res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
11630 return true;
11631 }
11632
11633 if (stat.is_file()) {
11634 for (const auto &kv : entry.headers) {
11635 res.set_header(kv.first, kv.second);
11636 }
11637
11638 auto etag = detail::compute_etag(stat);
11639 if (!etag.empty()) { res.set_header("ETag", etag); }
11640
11641 auto mtime = stat.mtime();
11642
11643 auto last_modified = detail::file_mtime_to_http_date(mtime);
11644 if (!last_modified.empty()) {
11645 res.set_header("Last-Modified", last_modified);
11646 }
11647
11648 if (check_if_not_modified(req, res, etag, mtime)) { return true; }
11649
11650 check_if_range(req, etag, mtime);
11651
11652 auto mm = std::make_shared<detail::mmap>(path.c_str());
11653 if (!mm->is_open()) {
11654 output_error_log(Error::OpenFile, &req);
11655 return false;
11656 }
11657
11658 res.set_content_provider(
11659 mm->size(),
11660 detail::find_content_type(path, file_extension_and_mimetype_map_,
11661 default_file_mimetype_),
11662 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
11663 sink.write(mm->data() + offset, length);
11664 return true;
11665 });
11666
11667 if (req.method != "HEAD" && file_request_handler_) {
11668 file_request_handler_(req, res);
11669 }
11670
11671 return true;
11672 } else {
11673 output_error_log(Error::OpenFile, &req);
11674 }
11675 }
11676 }
11677 }
11678 return false;
11679}
11680
11681inline bool Server::check_if_not_modified(const Request &req, Response &res,
11682 const std::string &etag,
11683 time_t mtime) const {
11684 // Handle conditional GET:
11685 // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
11686 // 2. If-Modified-Since is checked only when If-None-Match is absent
11687 if (req.has_header("If-None-Match")) {
11688 if (!etag.empty()) {
11689 auto val = req.get_header_value("If-None-Match");
11690
11691 // NOTE: We use exact string matching here. This works correctly
11692 // because our server always generates weak ETags (W/"..."), and
11693 // clients typically send back the same ETag they received.
11694 // RFC 9110 Section 8.8.3.2 allows weak comparison for
11695 // If-None-Match, where W/"x" and "x" would match, but this
11696 // simplified implementation requires exact matches.
11697 auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
11698 [&](const char *b, const char *e) {
11699 auto seg_len = static_cast<size_t>(e - b);
11700 return (seg_len == 1 && *b == '*') ||
11701 (seg_len == etag.size() &&
11702 std::equal(b, e, etag.begin()));
11703 });
11704
11705 if (ret) {
11706 res.status = StatusCode::NotModified_304;
11707 return true;
11708 }
11709 }
11710 } else if (req.has_header("If-Modified-Since")) {
11711 auto val = req.get_header_value("If-Modified-Since");
11712 auto t = detail::parse_http_date(val);
11713
11714 if (t != static_cast<time_t>(-1) && mtime <= t) {
11715 res.status = StatusCode::NotModified_304;
11716 return true;
11717 }
11718 }
11719 return false;
11720}
11721
11722inline bool Server::check_if_range(Request &req, const std::string &etag,
11723 time_t mtime) const {
11724 // Handle If-Range for partial content requests (RFC 9110
11725 // Section 13.1.5). If-Range is only evaluated when Range header is
11726 // present. If the validator matches, serve partial content; otherwise
11727 // serve full content.
11728 if (!req.ranges.empty() && req.has_header("If-Range")) {
11729 auto val = req.get_header_value("If-Range");
11730
11731 auto is_valid_range = [&]() {
11732 if (detail::is_strong_etag(val)) {
11733 // RFC 9110 Section 13.1.5: If-Range requires strong ETag
11734 // comparison.
11735 return (!etag.empty() && val == etag);
11736 } else if (detail::is_weak_etag(val)) {
11737 // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
11738 return false;
11739 } else {
11740 // HTTP-date comparison
11741 auto t = detail::parse_http_date(val);
11742 return (t != static_cast<time_t>(-1) && mtime <= t);
11743 }
11744 };
11745
11746 if (!is_valid_range()) {
11747 // Validator doesn't match: ignore Range and serve full content
11748 req.ranges.clear();
11749 return false;
11750 }
11751 }
11752
11753 return true;
11754}
11755
11756inline socket_t
11757Server::create_server_socket(const std::string &host, int port,
11758 int socket_flags,
11759 SocketOptions socket_options) const {
11760 return detail::create_socket(
11761 host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
11762 ipv6_v6only_, std::move(socket_options),
11763 [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
11764 if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
11765 output_error_log(Error::BindIPAddress, nullptr);
11766 return false;
11767 }
11769 output_error_log(Error::Listen, nullptr);
11770 return false;
11771 }
11772 return true;
11773 });
11774}
11775
11776inline int Server::bind_internal(const std::string &host, int port,
11777 int socket_flags) {
11778 if (is_decommissioned) { return -1; }
11779
11780 if (!is_valid()) { return -1; }
11781
11782 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
11783 if (svr_sock_ == INVALID_SOCKET) { return -1; }
11784
11785 if (port == 0) {
11786 struct sockaddr_storage addr;
11787 socklen_t addr_len = sizeof(addr);
11788 if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
11789 &addr_len) == -1) {
11790 output_error_log(Error::GetSockName, nullptr);
11791 return -1;
11792 }
11793 if (addr.ss_family == AF_INET) {
11794 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
11795 } else if (addr.ss_family == AF_INET6) {
11796 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
11797 } else {
11798 output_error_log(Error::UnsupportedAddressFamily, nullptr);
11799 return -1;
11800 }
11801 } else {
11802 return port;
11803 }
11804}
11805
11806inline bool Server::listen_internal() {
11807 if (is_decommissioned) { return false; }
11808
11809 auto ret = true;
11810 is_running_ = true;
11811 auto se = detail::scope_exit([&]() { is_running_ = false; });
11812
11813 if (start_handler_) { start_handler_(); }
11814
11815 {
11816 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
11817
11818 while (svr_sock_ != INVALID_SOCKET) {
11819#ifndef _WIN32
11820 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
11821#endif
11824 if (val == 0) { // Timeout
11825 task_queue->on_idle();
11826 continue;
11827 }
11828#ifndef _WIN32
11829 }
11830#endif
11831
11832#if defined _WIN32
11833 // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
11834 // OVERLAPPED
11835 socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
11836#elif defined SOCK_CLOEXEC
11837 socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
11838#else
11839 socket_t sock = accept(svr_sock_, nullptr, nullptr);
11840#endif
11841
11842 if (sock == INVALID_SOCKET) {
11843 if (errno == EMFILE) {
11844 // The per-process limit of open file descriptors has been reached.
11845 // Try to accept new connections after a short sleep.
11846 std::this_thread::sleep_for(std::chrono::microseconds{1});
11847 continue;
11848 } else if (errno == EINTR || errno == EAGAIN) {
11849 continue;
11850 }
11851 if (svr_sock_ != INVALID_SOCKET) {
11853 ret = false;
11854 output_error_log(Error::Connection, nullptr);
11855 } else {
11856 ; // The server socket was closed by user.
11857 }
11858 break;
11859 }
11860
11861 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
11863 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
11865
11866 if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
11867
11868 if (!task_queue->enqueue(
11869 [this, sock]() { process_and_close_socket(sock); })) {
11870 output_error_log(Error::ResourceExhaustion, nullptr);
11873 }
11874 }
11875
11876 task_queue->shutdown();
11877 }
11878
11879 is_decommissioned = !ret;
11880 return ret;
11881}
11882
11883inline bool Server::routing(Request &req, Response &res, Stream &strm) {
11884 if (pre_routing_handler_ &&
11885 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
11886 return true;
11887 }
11888
11889 // File handler
11890 if ((req.method == "GET" || req.method == "HEAD") &&
11891 handle_file_request(req, res)) {
11892 return true;
11893 }
11894
11895 if (detail::expect_content(req)) {
11896 // Content reader handler
11897 {
11898 // Track whether the ContentReader was aborted due to the decompressed
11899 // payload exceeding `payload_max_length_`.
11900 // The user handler runs after the lambda returns, so we must restore the
11901 // 413 status if the handler overwrites it.
11902 bool content_reader_payload_too_large = false;
11903
11904 ContentReader reader(
11905 [&](ContentReceiver receiver) {
11906 auto result = read_content_with_content_receiver(
11907 strm, req, res, std::move(receiver), nullptr, nullptr);
11908 if (!result) {
11909 output_error_log(Error::Read, &req);
11910 if (res.status == StatusCode::PayloadTooLarge_413) {
11911 content_reader_payload_too_large = true;
11912 }
11913 }
11914 return result;
11915 },
11916 [&](FormDataHeader header, ContentReceiver receiver) {
11917 auto result = read_content_with_content_receiver(
11918 strm, req, res, nullptr, std::move(header),
11919 std::move(receiver));
11920 if (!result) {
11921 output_error_log(Error::Read, &req);
11922 if (res.status == StatusCode::PayloadTooLarge_413) {
11923 content_reader_payload_too_large = true;
11924 }
11925 }
11926 return result;
11927 });
11928
11929 bool dispatched = false;
11930 if (req.method == "POST") {
11931 dispatched = dispatch_request_for_content_reader(
11932 req, res, std::move(reader), post_handlers_for_content_reader_);
11933 } else if (req.method == "PUT") {
11934 dispatched = dispatch_request_for_content_reader(
11935 req, res, std::move(reader), put_handlers_for_content_reader_);
11936 } else if (req.method == "PATCH") {
11937 dispatched = dispatch_request_for_content_reader(
11938 req, res, std::move(reader), patch_handlers_for_content_reader_);
11939 } else if (req.method == "DELETE") {
11940 dispatched = dispatch_request_for_content_reader(
11941 req, res, std::move(reader), delete_handlers_for_content_reader_);
11942 }
11943
11944 if (dispatched) {
11945 if (content_reader_payload_too_large) {
11946 // Enforce the limit: override any status the handler may have set
11947 // and return false so the error path sends a plain 413 response.
11949 res.body.clear();
11950 res.content_length_ = 0;
11951 res.content_provider_ = nullptr;
11952 return false;
11953 }
11954 return true;
11955 }
11956 }
11957
11958 // Read content into `req.body`
11959 if (!read_content(strm, req, res)) {
11960 output_error_log(Error::Read, &req);
11961 return false;
11962 }
11963 }
11964
11965 // Regular handler
11966 if (req.method == "GET" || req.method == "HEAD") {
11967 return dispatch_request(req, res, get_handlers_);
11968 } else if (req.method == "POST") {
11969 return dispatch_request(req, res, post_handlers_);
11970 } else if (req.method == "PUT") {
11971 return dispatch_request(req, res, put_handlers_);
11972 } else if (req.method == "DELETE") {
11973 return dispatch_request(req, res, delete_handlers_);
11974 } else if (req.method == "OPTIONS") {
11975 return dispatch_request(req, res, options_handlers_);
11976 } else if (req.method == "PATCH") {
11977 return dispatch_request(req, res, patch_handlers_);
11978 }
11979
11980 res.status = StatusCode::BadRequest_400;
11981 return false;
11982}
11983
11984inline bool Server::dispatch_request(Request &req, Response &res,
11985 const Handlers &handlers) const {
11986 for (const auto &x : handlers) {
11987 const auto &matcher = x.first;
11988 const auto &handler = x.second;
11989
11990 if (matcher->match(req)) {
11991 req.matched_route = matcher->pattern();
11992 if (!pre_request_handler_ ||
11993 pre_request_handler_(req, res) != HandlerResponse::Handled) {
11994 handler(req, res);
11995 }
11996 return true;
11997 }
11998 }
11999 return false;
12000}
12001
12002inline void Server::apply_ranges(const Request &req, Response &res,
12003 std::string &content_type,
12004 std::string &boundary) const {
12005 if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
12006 auto it = res.headers.find("Content-Type");
12007 if (it != res.headers.end()) {
12008 content_type = it->second;
12009 res.headers.erase(it);
12010 }
12011
12013
12014 res.set_header("Content-Type",
12015 "multipart/byteranges; boundary=" + boundary);
12016 }
12017
12018 auto type = detail::encoding_type(req, res);
12019
12020 if (res.body.empty()) {
12021 if (res.content_length_ > 0) {
12022 size_t length = 0;
12023 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12024 length = res.content_length_;
12025 } else if (req.ranges.size() == 1) {
12026 auto offset_and_length = detail::get_range_offset_and_length(
12027 req.ranges[0], res.content_length_);
12028
12029 length = offset_and_length.second;
12030
12031 auto content_range = detail::make_content_range_header_field(
12032 offset_and_length, res.content_length_);
12033 res.set_header("Content-Range", content_range);
12034 } else {
12036 req, boundary, content_type, res.content_length_);
12037 }
12038 res.set_header("Content-Length", std::to_string(length));
12039 } else {
12040 if (res.content_provider_) {
12041 if (res.is_chunked_content_provider_) {
12042 res.set_header("Transfer-Encoding", "chunked");
12043 if (type != detail::EncodingType::None) {
12044 res.set_header("Content-Encoding", detail::encoding_name(type));
12045 res.set_header("Vary", "Accept-Encoding");
12046 }
12047 }
12048 }
12049 }
12050 } else {
12051 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12052 ;
12053 } else if (req.ranges.size() == 1) {
12054 auto offset_and_length =
12055 detail::get_range_offset_and_length(req.ranges[0], res.body.size());
12056 auto offset = offset_and_length.first;
12057 auto length = offset_and_length.second;
12058
12059 auto content_range = detail::make_content_range_header_field(
12060 offset_and_length, res.body.size());
12061 res.set_header("Content-Range", content_range);
12062
12063 assert(offset + length <= res.body.size());
12064 res.body = res.body.substr(offset, length);
12065 } else {
12066 std::string data;
12067 detail::make_multipart_ranges_data(req, res, boundary, content_type,
12068 res.body.size(), data);
12069 res.body.swap(data);
12070 }
12071
12072 if (type != detail::EncodingType::None) {
12073 output_pre_compression_log(req, res);
12074
12075 if (auto compressor = detail::make_compressor(type)) {
12076 std::string compressed;
12077 if (compressor->compress(res.body.data(), res.body.size(), true,
12078 [&](const char *data, size_t data_len) {
12079 compressed.append(data, data_len);
12080 return true;
12081 })) {
12082 res.body.swap(compressed);
12083 res.set_header("Content-Encoding", detail::encoding_name(type));
12084 res.set_header("Vary", "Accept-Encoding");
12085 }
12086 }
12087 }
12088
12089 auto length = std::to_string(res.body.size());
12090 res.set_header("Content-Length", length);
12091 }
12092}
12093
12094inline bool Server::dispatch_request_for_content_reader(
12095 Request &req, Response &res, ContentReader content_reader,
12096 const HandlersForContentReader &handlers) const {
12097 for (const auto &x : handlers) {
12098 const auto &matcher = x.first;
12099 const auto &handler = x.second;
12100
12101 if (matcher->match(req)) {
12102 req.matched_route = matcher->pattern();
12103 if (!pre_request_handler_ ||
12104 pre_request_handler_(req, res) != HandlerResponse::Handled) {
12105 handler(req, res, content_reader);
12106 }
12107 return true;
12108 }
12109 }
12110 return false;
12111}
12112
12113inline std::string
12114get_client_ip(const std::string &x_forwarded_for,
12115 const std::vector<std::string> &trusted_proxies) {
12116 // X-Forwarded-For is a comma-separated list per RFC 7239
12117 std::vector<std::string> ip_list;
12118 detail::split(x_forwarded_for.data(),
12119 x_forwarded_for.data() + x_forwarded_for.size(), ',',
12120 [&](const char *b, const char *e) {
12121 auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
12122 ip_list.emplace_back(std::string(b + r.first, b + r.second));
12123 });
12124
12125 // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields
12126 // no segments. Signal "no client IP derived" with an empty string so the
12127 // caller can fall back to the connection-level remote address.
12128 if (ip_list.empty()) { return std::string(); }
12129
12130 for (size_t i = 0; i < ip_list.size(); ++i) {
12131 auto ip = ip_list[i];
12132
12133 auto is_trusted_proxy =
12134 std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
12135 [&](const std::string &proxy) { return ip == proxy; });
12136
12137 if (is_trusted_proxy) {
12138 if (i == 0) {
12139 // If the trusted proxy is the first IP, there's no preceding client IP
12140 return ip;
12141 } else {
12142 // Return the IP immediately before the trusted proxy
12143 return ip_list[i - 1];
12144 }
12145 }
12146 }
12147
12148 // If no trusted proxy is found, return the first IP in the list
12149 return ip_list.front();
12150}
12151
12152inline bool
12153Server::process_request(Stream &strm, const std::string &remote_addr,
12154 int remote_port, const std::string &local_addr,
12155 int local_port, bool close_connection,
12156 bool &connection_closed,
12157 const std::function<void(Request &)> &setup_request,
12158 bool *websocket_upgraded) {
12159 std::array<char, 2048> buf{};
12160
12161 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12162
12163 // Connection has been closed on client
12164 if (!line_reader.getline()) { return false; }
12165
12166 Request req;
12167 req.start_time_ = std::chrono::steady_clock::now();
12168 req.remote_addr = remote_addr;
12169 req.remote_port = remote_port;
12170 req.local_addr = local_addr;
12171 req.local_port = local_port;
12172
12173 Response res;
12174 res.version = "HTTP/1.1";
12175 res.headers = default_headers_;
12176
12177 // Request line and headers
12178 if (!parse_request_line(line_reader.ptr(), req)) {
12180 output_error_log(Error::InvalidRequestLine, &req);
12181 return write_response(strm, close_connection, req, res);
12182 }
12183
12184 // Request headers
12185 if (!detail::read_headers(strm, req.headers)) {
12187 output_error_log(Error::InvalidHeaders, &req);
12188 return write_response(strm, close_connection, req, res);
12189 }
12190
12191 // RFC 9112 §6.3: Reject requests with both a non-zero Content-Length and
12192 // any Transfer-Encoding to prevent request smuggling. Content-Length: 0 is
12193 // tolerated for compatibility with existing clients.
12194 if (req.get_header_value_u64("Content-Length") > 0 &&
12195 req.has_header("Transfer-Encoding")) {
12196 connection_closed = true;
12198 return write_response(strm, close_connection, req, res);
12199 }
12200
12201 // Check if the request URI doesn't exceed the limit
12202 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
12203 connection_closed = true;
12205 output_error_log(Error::ExceedUriMaxLength, &req);
12206 return write_response(strm, close_connection, req, res);
12207 }
12208
12209 if (req.get_header_value("Connection") == "close") {
12210 connection_closed = true;
12211 }
12212
12213 if (req.version == "HTTP/1.0" &&
12214 req.get_header_value("Connection") != "Keep-Alive") {
12215 connection_closed = true;
12216 }
12217
12218 if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
12219 auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
12220 auto derived = get_client_ip(x_forwarded_for, trusted_proxies_);
12221 req.remote_addr = derived.empty() ? remote_addr : derived;
12222 } else {
12223 req.remote_addr = remote_addr;
12224 }
12225 req.remote_port = remote_port;
12226
12227 req.local_addr = local_addr;
12228 req.local_port = local_port;
12229
12230 if (req.has_header("Accept")) {
12231 const auto &accept_header = req.get_header_value("Accept");
12232 if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
12233 connection_closed = true;
12235 output_error_log(Error::HTTPParsing, &req);
12236 return write_response(strm, close_connection, req, res);
12237 }
12238 }
12239
12240 if (req.has_header("Range")) {
12241 const auto &range_header_value = req.get_header_value("Range");
12242 if (!detail::parse_range_header(range_header_value, req.ranges)) {
12243 connection_closed = true;
12245 output_error_log(Error::InvalidRangeHeader, &req);
12246 return write_response(strm, close_connection, req, res);
12247 }
12248 }
12249
12250 if (setup_request) { setup_request(req); }
12251
12252 if (req.get_header_value("Expect") == "100-continue") {
12253 int status = StatusCode::Continue_100;
12254 if (expect_100_continue_handler_) {
12255 status = expect_100_continue_handler_(req, res);
12256 }
12257 switch (status) {
12260 detail::write_response_line(strm, status);
12261 strm.write("\r\n");
12262 break;
12263 default:
12264 connection_closed = true;
12265 return write_response(strm, true, req, res);
12266 }
12267 }
12268
12269 // Setup `is_connection_closed` method
12270 auto sock = strm.socket();
12271 req.is_connection_closed = [sock]() {
12272 return !detail::is_socket_alive(sock);
12273 };
12274
12275 // WebSocket upgrade
12276 // Check pre_routing_handler_ before upgrading so that authentication
12277 // and other middleware can reject the request with an HTTP response
12278 // (e.g., 401) before the protocol switches.
12280 if (pre_routing_handler_ &&
12281 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
12282 if (res.status == -1) { res.status = StatusCode::OK_200; }
12283 return write_response(strm, close_connection, req, res);
12284 }
12285 // Find matching WebSocket handler
12286 for (const auto &entry : websocket_handlers_) {
12287 if (entry.matcher->match(req)) {
12288 // Compute accept key
12289 auto client_key = req.get_header_value("Sec-WebSocket-Key");
12290 auto accept_key = detail::websocket_accept_key(client_key);
12291
12292 // Negotiate subprotocol
12293 std::string selected_subprotocol;
12294 if (entry.sub_protocol_selector) {
12295 auto protocol_header = req.get_header_value("Sec-WebSocket-Protocol");
12296 if (!protocol_header.empty()) {
12297 std::vector<std::string> protocols;
12298 std::istringstream iss(protocol_header);
12299 std::string token;
12300 while (std::getline(iss, token, ',')) {
12301 // Trim whitespace
12302 auto start = token.find_first_not_of(' ');
12303 auto end = token.find_last_not_of(' ');
12304 if (start != std::string::npos) {
12305 protocols.push_back(token.substr(start, end - start + 1));
12306 }
12307 }
12308 selected_subprotocol = entry.sub_protocol_selector(protocols);
12309 }
12310 }
12311
12312 // Send 101 Switching Protocols
12313 std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
12314 "Upgrade: websocket\r\n"
12315 "Connection: Upgrade\r\n"
12316 "Sec-WebSocket-Accept: " +
12317 accept_key + "\r\n";
12318 if (!selected_subprotocol.empty()) {
12319 if (!detail::fields::is_field_value(selected_subprotocol)) {
12320 return false;
12321 }
12322 handshake_response +=
12323 "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
12324 }
12325 handshake_response += "\r\n";
12326 if (strm.write(handshake_response.data(), handshake_response.size()) <
12327 0) {
12328 return false;
12329 }
12330
12331 connection_closed = true;
12332 if (websocket_upgraded) { *websocket_upgraded = true; }
12333
12334 {
12335 // Use WebSocket-specific read timeout instead of HTTP timeout
12339 entry.handler(req, ws);
12340 }
12341 return true;
12342 }
12343 }
12344 // No matching handler - fall through to 404
12345 }
12346
12347 // Routing
12348 auto routed = false;
12349#ifdef CPPHTTPLIB_NO_EXCEPTIONS
12350 routed = routing(req, res, strm);
12351#else
12352 try {
12353 routed = routing(req, res, strm);
12354 } catch (std::exception &) {
12355 if (exception_handler_) {
12356 auto ep = std::current_exception();
12357 exception_handler_(req, res, ep);
12358 routed = true;
12359 } else {
12361 }
12362 } catch (...) {
12363 if (exception_handler_) {
12364 auto ep = std::current_exception();
12365 exception_handler_(req, res, ep);
12366 routed = true;
12367 } else {
12369 }
12370 }
12371#endif
12372 auto ret = false;
12373 if (routed) {
12374 if (res.status == -1) {
12375 res.status = req.ranges.empty() ? StatusCode::OK_200
12377 }
12378
12379 // Serve file content by using a content provider
12380 auto file_open_error = false;
12381 if (!res.file_content_path_.empty()) {
12382 const auto &path = res.file_content_path_;
12383 auto mm = std::make_shared<detail::mmap>(path.c_str());
12384 if (!mm->is_open()) {
12385 res.body.clear();
12386 res.content_length_ = 0;
12387 res.content_provider_ = nullptr;
12389 output_error_log(Error::OpenFile, &req);
12390 file_open_error = true;
12391 } else {
12392 auto content_type = res.file_content_content_type_;
12393 if (content_type.empty()) {
12394 content_type = detail::find_content_type(
12395 path, file_extension_and_mimetype_map_, default_file_mimetype_);
12396 }
12397
12399 mm->size(), content_type,
12400 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
12401 sink.write(mm->data() + offset, length);
12402 return true;
12403 });
12404 }
12405 }
12406
12407 if (file_open_error) {
12408 ret = write_response(strm, close_connection, req, res);
12409 } else if (detail::range_error(req, res)) {
12410 res.body.clear();
12411 res.content_length_ = 0;
12412 res.content_provider_ = nullptr;
12414 ret = write_response(strm, close_connection, req, res);
12415 } else {
12416 ret = write_response_with_content(strm, close_connection, req, res);
12417 }
12418 } else {
12419 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
12420 ret = write_response(strm, close_connection, req, res);
12421 }
12422
12423 // Drain any unconsumed framed body to prevent request smuggling on
12424 // keep-alive. Without framing there is no body to drain — reading would
12425 // consume the next request (issue #2450).
12426 if (!req.body_consumed_ && detail::has_framed_body(req)) {
12427 int dummy_status;
12429 strm, req, payload_max_length_, dummy_status, nullptr,
12430 [](const char *, size_t, size_t, size_t) { return true; }, false)) {
12431 connection_closed = true;
12432 }
12433 }
12434
12435 return ret;
12436}
12437
12438inline bool Server::is_valid() const { return true; }
12439
12440inline bool Server::process_and_close_socket(socket_t sock) {
12441 std::string remote_addr;
12442 int remote_port = 0;
12443 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
12444
12445 std::string local_addr;
12446 int local_port = 0;
12447 detail::get_local_ip_and_port(sock, local_addr, local_port);
12448
12449 bool websocket_upgraded = false;
12454 [&](Stream &strm, bool close_connection, bool &connection_closed) {
12455 return process_request(strm, remote_addr, remote_port, local_addr,
12456 local_port, close_connection, connection_closed,
12457 nullptr, &websocket_upgraded);
12458 });
12459
12462 return ret;
12463}
12464
12465inline void Server::output_log(const Request &req, const Response &res) const {
12466 if (logger_) {
12467 std::lock_guard<std::mutex> guard(logger_mutex_);
12468 logger_(req, res);
12469 }
12470}
12471
12472inline void Server::output_pre_compression_log(const Request &req,
12473 const Response &res) const {
12474 if (pre_compression_logger_) {
12475 std::lock_guard<std::mutex> guard(logger_mutex_);
12476 pre_compression_logger_(req, res);
12477 }
12478}
12479
12480inline void Server::output_error_log(const Error &err,
12481 const Request *req) const {
12482 if (error_logger_) {
12483 std::lock_guard<std::mutex> guard(logger_mutex_);
12484 error_logger_(err, req);
12485 }
12486}
12487
12488/*
12489 * Group 5: ClientImpl and Client (Universal) implementation
12490 */
12491// HTTP client implementation
12492inline ClientImpl::ClientImpl(const std::string &host)
12493 : ClientImpl(host, 80, std::string(), std::string()) {}
12494
12495inline ClientImpl::ClientImpl(const std::string &host, int port)
12496 : ClientImpl(host, port, std::string(), std::string()) {}
12497
12498inline ClientImpl::ClientImpl(const std::string &host, int port,
12499 const std::string &client_cert_path,
12500 const std::string &client_key_path)
12501 : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
12502 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
12503
12505 // Wait until all the requests in flight are handled.
12506 size_t retry_count = 10;
12507 while (retry_count-- > 0) {
12508 {
12509 std::lock_guard<std::mutex> guard(socket_mutex_);
12510 if (socket_requests_in_flight_ == 0) { break; }
12511 }
12512 std::this_thread::sleep_for(std::chrono::milliseconds{1});
12513 }
12514
12515 std::lock_guard<std::mutex> guard(socket_mutex_);
12518}
12519
12520inline bool ClientImpl::is_valid() const { return true; }
12521
12522inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
12541 compress_ = rhs.compress_;
12545 interface_ = rhs.interface_;
12552 logger_ = rhs.logger_;
12554
12555#ifdef CPPHTTPLIB_SSL_ENABLED
12556 digest_auth_username_ = rhs.digest_auth_username_;
12557 digest_auth_password_ = rhs.digest_auth_password_;
12558 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
12559 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
12560 ca_cert_file_path_ = rhs.ca_cert_file_path_;
12561 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
12562 server_certificate_verification_ = rhs.server_certificate_verification_;
12563 server_hostname_verification_ = rhs.server_hostname_verification_;
12564#endif
12565}
12566
12567inline bool
12569 if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
12570 if (no_proxy_entries_.empty()) { return true; }
12571 // host_ is const so its normalized form is invariant; cache it. The
12572 // cross-host path (setup_redirect_client passing next_host) re-normalizes.
12573 if (host == host_) {
12577 }
12579 }
12580 auto target = detail::normalize_target(host);
12582}
12583
12584inline socket_t ClientImpl::create_client_socket(Error &error) const {
12591 }
12592
12593 // Check is custom IP specified for host_
12594 std::string ip;
12595 auto it = addr_map_.find(host_);
12596 if (it != addr_map_.end()) { ip = it->second; }
12597
12603}
12604
12606 Error &error) {
12607 auto sock = create_client_socket(error);
12608 if (sock == INVALID_SOCKET) { return false; }
12609 socket.sock = sock;
12610 return true;
12611}
12612
12616
12618 Socket & /*socket*/,
12619 std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
12620 Response & /*res*/, bool & /*success*/, Error & /*error*/) {
12621 return true;
12622}
12623
12624inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
12625 bool /*shutdown_gracefully*/) {
12626 // If there are any requests in flight from threads other than us, then it's
12627 // a thread-unsafe race because individual ssl* objects are not thread-safe.
12628 assert(socket_requests_in_flight_ == 0 ||
12629 socket_requests_are_from_thread_ == std::this_thread::get_id());
12630}
12631
12633 if (socket.sock == INVALID_SOCKET) { return; }
12635}
12636
12638 // If there are requests in flight in another thread, usually closing
12639 // the socket will be fine and they will simply receive an error when
12640 // using the closed socket, but it is still a bug since rarely the OS
12641 // may reassign the socket id to be used for a new socket, and then
12642 // suddenly they will be operating on a live socket that is different
12643 // than the one they intended!
12644 assert(socket_requests_in_flight_ == 0 ||
12645 socket_requests_are_from_thread_ == std::this_thread::get_id());
12646
12647 // It is also a bug if this happens while SSL is still active
12648#ifdef CPPHTTPLIB_SSL_ENABLED
12649 assert(socket.ssl == nullptr);
12650#endif
12651
12652 if (socket.sock == INVALID_SOCKET) { return; }
12654 socket.sock = INVALID_SOCKET;
12655}
12656
12657inline void ClientImpl::disconnect(bool gracefully) {
12658 shutdown_ssl(socket_, gracefully);
12661}
12662
12663inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
12664 Response &res,
12665 bool skip_100_continue) const {
12666 std::array<char, 2048> buf{};
12667
12668 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12669
12670 if (!line_reader.getline()) { return false; }
12671
12672#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
12673 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
12674#else
12675 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
12676#endif
12677
12678 std::cmatch m;
12679 if (!std::regex_match(line_reader.ptr(), m, re)) {
12680 return req.method == "CONNECT";
12681 }
12682 res.version = std::string(m[1]);
12683 res.status = std::stoi(std::string(m[2]));
12684 res.reason = std::string(m[3]);
12685
12686 // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
12687 while (skip_100_continue && res.status == StatusCode::Continue_100) {
12688 if (!line_reader.getline()) { return false; } // CRLF
12689 if (!line_reader.getline()) { return false; } // next response line
12690
12691 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
12692 res.version = std::string(m[1]);
12693 res.status = std::stoi(std::string(m[2]));
12694 res.reason = std::string(m[3]);
12695 }
12696
12697 return true;
12698}
12699
12700inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
12701 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
12702 auto ret = send_(req, res, error);
12704 assert(!ret);
12705 ret = send_(req, res, error);
12706 // If still failing with SSLPeerCouldBeClosed_, convert to Read error
12708 }
12709 return ret;
12710}
12711
12712inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
12713 {
12714 std::lock_guard<std::mutex> guard(socket_mutex_);
12715
12716 // Set this to false immediately - if it ever gets set to true by the end
12717 // of the request, we know another thread instructed us to close the
12718 // socket.
12720
12721 auto is_alive = false;
12722 if (socket_.is_open()) {
12724
12725#ifdef CPPHTTPLIB_SSL_ENABLED
12726 if (is_alive && is_ssl()) {
12727 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12728 is_alive = false;
12729 }
12730 }
12731#endif
12732
12733 if (!is_alive) {
12734 // Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
12735 disconnect(/*gracefully=*/false);
12736 }
12737 }
12738
12739 if (!is_alive) {
12741 output_error_log(error, &req);
12742 return false;
12743 }
12744
12745 {
12746 auto success = true;
12747 if (!setup_proxy_connection(socket_, req.start_time_, res, success,
12748 error)) {
12749 if (!success) { output_error_log(error, &req); }
12750 return success;
12751 }
12752 }
12753 }
12754
12755 // Mark the current socket as being in use so that it cannot be closed by
12756 // anyone else while this request is ongoing, even though we will be
12757 // releasing the mutex.
12759 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
12760 }
12762 socket_requests_are_from_thread_ = std::this_thread::get_id();
12763 }
12764
12765 for (const auto &header : default_headers_) {
12766 if (req.headers.find(header.first) == req.headers.end()) {
12767 req.headers.insert(header);
12768 }
12769 }
12770
12771 auto ret = false;
12772 auto close_connection = !keep_alive_;
12773
12774 auto se = detail::scope_exit([&]() {
12775 // Briefly lock mutex in order to mark that a request is no longer ongoing
12776 std::lock_guard<std::mutex> guard(socket_mutex_);
12778 if (socket_requests_in_flight_ <= 0) {
12779 assert(socket_requests_in_flight_ == 0);
12780 socket_requests_are_from_thread_ = std::thread::id();
12781 }
12782
12783 if (socket_should_be_closed_when_request_is_done_ || close_connection ||
12784 !ret) {
12785 disconnect(/*gracefully=*/true);
12786 }
12787 });
12788
12789 ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
12790 return handle_request(strm, req, res, close_connection, error);
12791 });
12792
12793 if (!ret) {
12794 if (error == Error::Success) {
12796 output_error_log(error, &req);
12797 }
12798 }
12799
12800 return ret;
12801}
12802
12803inline Result ClientImpl::send(const Request &req) {
12804 auto req2 = req;
12805 return send_(std::move(req2));
12806}
12807
12808inline Result ClientImpl::send_(Request &&req) {
12809 auto res = detail::make_unique<Response>();
12810 auto error = Error::Success;
12811 auto ret = send(req, *res, error);
12812#ifdef CPPHTTPLIB_SSL_ENABLED
12813 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
12814 last_ssl_error_, last_backend_error_};
12815#else
12816 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
12817#endif
12818}
12819
12820inline void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
12821 const std::string &ct) {
12822 (void)for_stream;
12823 for (const auto &header : default_headers_) {
12824 if (!r.has_header(header.first)) { r.headers.insert(header); }
12825 }
12826
12827 if (!r.has_header("Host")) {
12828 if (address_family_ == AF_UNIX) {
12829 r.headers.emplace("Host", "localhost");
12830 } else {
12831 r.headers.emplace(
12832 "Host", detail::make_host_and_port_string(host_, port_, is_ssl()));
12833 }
12834 }
12835
12836 if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
12837
12838 if (!r.content_receiver) {
12839 if (!r.has_header("Accept-Encoding")) {
12840 std::string accept_encoding;
12841#ifdef CPPHTTPLIB_BROTLI_SUPPORT
12842 accept_encoding = "br";
12843#endif
12844#ifdef CPPHTTPLIB_ZLIB_SUPPORT
12845 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12846 accept_encoding += "gzip, deflate";
12847#endif
12848#ifdef CPPHTTPLIB_ZSTD_SUPPORT
12849 if (!accept_encoding.empty()) { accept_encoding += ", "; }
12850 accept_encoding += "zstd";
12851#endif
12852 r.set_header("Accept-Encoding", accept_encoding);
12853 }
12854
12855#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
12856 if (!r.has_header("User-Agent")) {
12857 auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
12858 r.set_header("User-Agent", agent);
12859 }
12860#endif
12861 }
12862
12863 if (!r.body.empty()) {
12864 if (!ct.empty() && !r.has_header("Content-Type")) {
12865 r.headers.emplace("Content-Type", ct);
12866 }
12867 if (!r.has_header("Content-Length")) {
12868 r.headers.emplace("Content-Length", std::to_string(r.body.size()));
12869 }
12870 }
12871}
12872
12873inline ClientImpl::StreamHandle
12874ClientImpl::open_stream(const std::string &method, const std::string &path,
12875 const Params &params, const Headers &headers,
12876 const std::string &body,
12877 const std::string &content_type) {
12878 StreamHandle handle;
12880 handle.error = Error::Success;
12881
12882 auto query_path = params.empty() ? path : append_query_params(path, params);
12883 handle.connection_ = detail::make_unique<ClientConnection>();
12884
12885 {
12886 std::lock_guard<std::mutex> guard(socket_mutex_);
12887
12888 auto is_alive = false;
12889 if (socket_.is_open()) {
12890 is_alive = detail::is_socket_alive(socket_.sock);
12891#ifdef CPPHTTPLIB_SSL_ENABLED
12892 if (is_alive && is_ssl()) {
12893 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12894 is_alive = false;
12895 }
12896 }
12897#endif
12898 if (!is_alive) { disconnect(/*gracefully=*/false); }
12899 }
12900
12901 if (!is_alive) {
12902 if (!ensure_socket_connection(socket_, handle.error)) {
12903 handle.response.reset();
12904 return handle;
12905 }
12906
12907 {
12908 auto success = true;
12909 auto start_time = std::chrono::steady_clock::now();
12910 if (!setup_proxy_connection(socket_, start_time, *handle.response,
12911 success, handle.error)) {
12912 if (!success) { handle.response.reset(); }
12913 return handle;
12914 }
12915 }
12916 }
12917
12918 transfer_socket_ownership_to_handle(handle);
12919 }
12920
12921#ifdef CPPHTTPLIB_SSL_ENABLED
12922 if (is_ssl() && handle.connection_->session) {
12923 handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
12924 handle.connection_->sock, handle.connection_->session,
12927 } else {
12928 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
12929 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
12931 }
12932#else
12933 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
12934 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
12936#endif
12937 handle.stream_ = handle.socket_stream_.get();
12938
12939 Request req;
12940 req.method = method;
12941 req.path = query_path;
12942 req.headers = headers;
12943 req.body = body;
12944
12945 prepare_default_headers(req, true, content_type);
12946
12947 auto &strm = *handle.stream_;
12948 if (detail::write_request_line(strm, req.method, req.path) < 0) {
12949 handle.error = Error::Write;
12950 handle.response.reset();
12951 return handle;
12952 }
12953
12955 handle.error)) {
12956 handle.response.reset();
12957 return handle;
12958 }
12959
12960 if (!body.empty()) {
12961 if (strm.write(body.data(), body.size()) < 0) {
12962 handle.error = Error::Write;
12963 handle.response.reset();
12964 return handle;
12965 }
12966 }
12967
12968 if (!read_response_line(strm, req, *handle.response) ||
12969 !detail::read_headers(strm, handle.response->headers)) {
12970 handle.error = Error::Read;
12971 handle.response.reset();
12972 return handle;
12973 }
12974
12975 handle.body_reader_.stream = handle.stream_;
12976 handle.body_reader_.payload_max_length = payload_max_length_;
12977
12978 if (handle.response->has_header("Content-Length")) {
12979 bool is_invalid = false;
12980 auto content_length = detail::get_header_value_u64(
12981 handle.response->headers, "Content-Length", 0, 0, is_invalid);
12982 if (is_invalid) {
12983 handle.error = Error::Read;
12984 handle.response.reset();
12985 return handle;
12986 }
12987 handle.body_reader_.has_content_length = true;
12988 handle.body_reader_.content_length = content_length;
12989 }
12990
12991 auto transfer_encoding =
12992 handle.response->get_header_value("Transfer-Encoding");
12993 handle.body_reader_.chunked = (transfer_encoding == "chunked");
12994
12995 auto content_encoding = handle.response->get_header_value("Content-Encoding");
12996 if (!content_encoding.empty()) {
12997 handle.decompressor_ = detail::create_decompressor(content_encoding);
12998 }
12999
13000 return handle;
13001}
13002
13003inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
13004 if (!is_valid() || !response) { return -1; }
13005
13006 if (decompressor_) { return read_with_decompression(buf, len); }
13007 auto n = detail::read_body_content(stream_, body_reader_, buf, len);
13008
13009 if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
13010 trailers_parsed_ = true;
13011 if (body_reader_.chunked_decoder) {
13012 if (!body_reader_.chunked_decoder->parse_trailers_into(
13013 response->trailers, response->headers)) {
13014 return n;
13015 }
13016 } else {
13017 detail::ChunkedDecoder dec(*stream_);
13018 if (!dec.parse_trailers_into(response->trailers, response->headers)) {
13019 return n;
13020 }
13021 }
13022 }
13023
13024 return n;
13025}
13026
13027inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
13028 size_t len) {
13029 if (decompress_offset_ < decompress_buffer_.size()) {
13030 auto available = decompress_buffer_.size() - decompress_offset_;
13031 auto to_copy = (std::min)(len, available);
13032 std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
13033 decompress_offset_ += to_copy;
13034 decompressed_bytes_read_ += to_copy;
13035 return static_cast<ssize_t>(to_copy);
13036 }
13037
13038 decompress_buffer_.clear();
13039 decompress_offset_ = 0;
13040
13041 constexpr size_t kDecompressionBufferSize = 8192;
13042 char compressed_buf[kDecompressionBufferSize];
13043
13044 while (true) {
13045 auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
13046 sizeof(compressed_buf));
13047
13048 if (n <= 0) { return n; }
13049
13050 bool decompress_ok = decompressor_->decompress(
13051 compressed_buf, static_cast<size_t>(n),
13052 [this](const char *data, size_t data_len) {
13053 decompress_buffer_.append(data, data_len);
13054 auto limit = body_reader_.payload_max_length;
13055 if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) {
13056 return false;
13057 }
13058 return true;
13059 });
13060
13061 if (!decompress_ok) {
13062 body_reader_.last_error = Error::Read;
13063 return -1;
13064 }
13065
13066 if (!decompress_buffer_.empty()) { break; }
13067 }
13068
13069 auto to_copy = (std::min)(len, decompress_buffer_.size());
13070 std::memcpy(buf, decompress_buffer_.data(), to_copy);
13071 decompress_offset_ = to_copy;
13072 decompressed_bytes_read_ += to_copy;
13073 return static_cast<ssize_t>(to_copy);
13074}
13075
13077 if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
13078 return;
13079 }
13080
13081 trailers_parsed_ = true;
13082
13083 const auto bufsiz = 128;
13084 char line_buf[bufsiz];
13085 detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
13086
13087 if (!line_reader.getline()) { return; }
13088
13089 if (!detail::parse_trailers(line_reader, response->trailers,
13090 response->headers)) {
13091 return;
13092 }
13093}
13094
13095namespace detail {
13096
13098
13099inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
13100 size_t &out_chunk_offset,
13101 size_t &out_chunk_total) {
13102 if (finished) { return 0; }
13103
13104 if (chunk_remaining == 0) {
13106 if (!lr.getline()) { return -1; }
13107
13108 // RFC 9112 §7.1: chunk-size = 1*HEXDIG
13109 const char *p = lr.ptr();
13110 int v = 0;
13111 if (!is_hex(*p, v)) { return -1; }
13112
13113 size_t chunk_len = 0;
13114 constexpr size_t chunk_len_max = (std::numeric_limits<size_t>::max)();
13115 for (; is_hex(*p, v); ++p) {
13116 if (chunk_len > (chunk_len_max >> 4)) { return -1; }
13117 chunk_len = (chunk_len << 4) | static_cast<size_t>(v);
13118 }
13119
13120 while (is_space_or_tab(*p)) {
13121 ++p;
13122 }
13123 if (*p != '\0' && *p != ';' && *p != '\r' && *p != '\n') { return -1; }
13124
13125 if (chunk_len == 0) {
13126 chunk_remaining = 0;
13127 finished = true;
13128 out_chunk_offset = 0;
13129 out_chunk_total = 0;
13130 return 0;
13131 }
13132
13133 chunk_remaining = chunk_len;
13136 }
13137
13138 auto to_read = (std::min)(chunk_remaining, len);
13139 auto n = strm.read(buf, to_read);
13140 if (n <= 0) { return -1; }
13141
13142 auto offset_before = last_chunk_offset;
13143 last_chunk_offset += static_cast<size_t>(n);
13144 chunk_remaining -= static_cast<size_t>(n);
13145
13146 out_chunk_offset = offset_before;
13147 out_chunk_total = last_chunk_total;
13148
13149 if (chunk_remaining == 0) {
13151 if (!lr.getline()) { return -1; }
13152 if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
13153 }
13154
13155 return n;
13156}
13157
13159 const Headers &src_headers) {
13161 if (!lr.getline()) { return false; }
13162 return parse_trailers(lr, dest, src_headers);
13163}
13164
13165} // namespace detail
13166
13167inline void
13168ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
13169 handle.connection_->sock = socket_.sock;
13170#ifdef CPPHTTPLIB_SSL_ENABLED
13171 handle.connection_->session = socket_.ssl;
13172 socket_.ssl = nullptr;
13173#endif
13175}
13176
13177inline bool ClientImpl::handle_request(Stream &strm, Request &req,
13178 Response &res, bool close_connection,
13179 Error &error) {
13180 if (req.path.empty()) {
13182 output_error_log(error, &req);
13183 return false;
13184 }
13185
13186 auto req_save = req;
13187
13188 bool ret;
13189
13190 if (!is_ssl() && is_proxy_enabled_for_host(host_)) {
13191 auto req2 = req;
13192 req2.path = "http://" +
13194 req.path;
13195 ret = process_request(strm, req2, res, close_connection, error);
13196 req = std::move(req2);
13197 req.path = req_save.path;
13198 } else {
13199 ret = process_request(strm, req, res, close_connection, error);
13200 }
13201
13202 if (!ret) { return false; }
13203
13204 if (res.get_header_value("Connection") == "close" ||
13205 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
13206 // NOTE: this requires a not-entirely-obvious chain of calls to be correct
13207 // for this to be safe.
13208
13209 // This is safe to call because handle_request is only called by send_
13210 // which locks the request mutex during the process. It would be a bug
13211 // to call it from a different thread since it's a thread-safety issue
13212 // to do these things to the socket if another thread is using the socket.
13213 std::lock_guard<std::mutex> guard(socket_mutex_);
13214 disconnect(/*gracefully=*/true);
13215 }
13216
13217 if (300 < res.status && res.status < 400 && follow_location_) {
13218 req = std::move(req_save);
13219 ret = redirect(req, res, error);
13220 }
13221
13222#ifdef CPPHTTPLIB_SSL_ENABLED
13223 if ((res.status == StatusCode::Unauthorized_401 ||
13225 req.authorization_count_ < 5) {
13226 auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
13227
13228 // Only retry when the 407 actually came from a proxy hop: plain HTTP
13229 // through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the
13230 // origin (#2457); direct/bypassed origins have no proxy hop at all.
13231 if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) {
13232 return ret;
13233 }
13234
13235 const auto &username =
13236 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
13237 const auto &password =
13238 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
13239
13240 if (!username.empty() && !password.empty()) {
13241 std::map<std::string, std::string> auth;
13242 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
13243 Request new_req = req;
13244 new_req.authorization_count_ += 1;
13245 new_req.headers.erase(is_proxy ? "Proxy-Authorization"
13246 : "Authorization");
13247 new_req.headers.insert(detail::make_digest_authentication_header(
13248 req, auth, new_req.authorization_count_, detail::random_string(10),
13249 username, password, is_proxy));
13250
13251 Response new_res;
13252
13253 ret = send(new_req, new_res, error);
13254 if (ret) { res = std::move(new_res); }
13255 }
13256 }
13257 }
13258#endif
13259
13260 return ret;
13261}
13262
13263inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
13264 if (req.redirect_count_ == 0) {
13266 output_error_log(error, &req);
13267 return false;
13268 }
13269
13270 auto location = res.get_header_value("location");
13271 if (location.empty()) { return false; }
13272
13273 detail::UrlComponents uc;
13274 if (!detail::parse_url(location, uc)) { return false; }
13275
13276 // Only follow http/https redirects
13277 if (!uc.scheme.empty() && uc.scheme != "http" && uc.scheme != "https") {
13278 return false;
13279 }
13280
13281 auto scheme = is_ssl() ? "https" : "http";
13282
13283 auto next_scheme = std::move(uc.scheme);
13284 auto next_host = std::move(uc.host);
13285 auto port_str = std::move(uc.port);
13286 auto next_path = std::move(uc.path);
13287 auto next_query = std::move(uc.query);
13288
13289 auto next_port = port_;
13290 if (!port_str.empty()) {
13291 if (!detail::parse_port(port_str, next_port)) { return false; }
13292 } else if (!next_scheme.empty()) {
13293 next_port = next_scheme == "https" ? 443 : 80;
13294 }
13295
13296 if (next_scheme.empty()) { next_scheme = scheme; }
13297 if (next_host.empty()) { next_host = host_; }
13298 if (next_path.empty()) { next_path = "/"; }
13299
13300 auto path = decode_path_component(next_path) + next_query;
13301
13302 // Same host redirect - use current client
13303 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
13304 return detail::redirect(*this, req, res, path, location, error);
13305 }
13306
13307 // Cross-host/scheme redirect - create new client with robust setup
13308 return create_redirect_client(next_scheme, next_host, next_port, req, res,
13309 path, location, error);
13310}
13311
13312// New method for robust redirect client creation
13313inline bool ClientImpl::create_redirect_client(
13314 const std::string &scheme, const std::string &host, int port, Request &req,
13315 Response &res, const std::string &path, const std::string &location,
13316 Error &error) {
13317 // Determine if we need SSL
13318 auto need_ssl = (scheme == "https");
13319
13320 // Clean up request headers that are host/client specific
13321 // Remove headers that should not be carried over to new host
13322 auto headers_to_remove =
13323 std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
13324
13325 for (const auto &header_name : headers_to_remove) {
13326 auto it = req.headers.find(header_name);
13327 while (it != req.headers.end()) {
13328 it = req.headers.erase(it);
13329 it = req.headers.find(header_name);
13330 }
13331 }
13332
13333 // Create appropriate client type and handle redirect
13334 if (need_ssl) {
13335#ifdef CPPHTTPLIB_SSL_ENABLED
13336 // Create SSL client for HTTPS redirect
13337 SSLClient redirect_client(host, port);
13338
13339 // Setup basic client configuration first
13340 setup_redirect_client(redirect_client);
13341
13342 redirect_client.enable_server_certificate_verification(
13343 server_certificate_verification_);
13344 redirect_client.enable_server_hostname_verification(
13345 server_hostname_verification_);
13346
13347 // Transfer CA certificate to redirect client
13348 if (!ca_cert_pem_.empty()) {
13349 redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
13350 ca_cert_pem_.size());
13351 }
13352 if (!ca_cert_file_path_.empty()) {
13353 redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
13354 }
13355
13356 // Client certificates are set through constructor for SSLClient
13357 // NOTE: SSLClient constructor already takes client_cert_path and
13358 // client_key_path so we need to create it properly if client certs are
13359 // needed
13360
13361 // Execute the redirect
13362 return detail::redirect(redirect_client, req, res, path, location, error);
13363#else
13364 // SSL not supported - set appropriate error
13366 output_error_log(error, &req);
13367 return false;
13368#endif
13369 } else {
13370 // HTTP redirect
13371 ClientImpl redirect_client(host, port);
13372
13373 // Setup client with robust configuration
13374 setup_redirect_client(redirect_client);
13375
13376 // Execute the redirect
13377 return detail::redirect(redirect_client, req, res, path, location, error);
13378 }
13379}
13380
13381// New method for robust client setup (based on basic_manual_redirect.cpp
13382// logic)
13383template <typename ClientType>
13384inline void ClientImpl::setup_redirect_client(ClientType &client) {
13385 // Copy basic settings first
13386 client.set_connection_timeout(connection_timeout_sec_);
13387 client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
13388 client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
13389 client.set_keep_alive(keep_alive_);
13390 client.set_follow_location(
13391 true); // Enable redirects to handle multi-step redirects
13392 client.set_path_encode(path_encode_);
13393 client.set_compress(compress_);
13394 client.set_decompress(decompress_);
13395
13396 // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
13397 // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
13398 // 15.4, credentials must not be forwarded when redirecting to a different
13399 // host. This function is only called for cross-host redirects; same-host
13400 // redirects are handled directly in ClientImpl::redirect().
13401
13402 // Copy the proxy configuration unconditionally; the per-target bypass is
13403 // re-evaluated at send time, so a later hop to a non-bypassed host can
13404 // still use the proxy.
13405 client.no_proxy_entries_ = no_proxy_entries_;
13406 if (!proxy_host_.empty() && proxy_port_ != -1) {
13407 client.set_proxy(proxy_host_, proxy_port_);
13408
13409 if (!proxy_basic_auth_username_.empty()) {
13410 client.set_proxy_basic_auth(proxy_basic_auth_username_,
13412 }
13413 if (!proxy_bearer_token_auth_token_.empty()) {
13414 client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
13415 }
13416#ifdef CPPHTTPLIB_SSL_ENABLED
13417 if (!proxy_digest_auth_username_.empty()) {
13418 client.set_proxy_digest_auth(proxy_digest_auth_username_,
13419 proxy_digest_auth_password_);
13420 }
13421#endif
13422 }
13423
13424 // Copy network and socket settings
13425 client.set_address_family(address_family_);
13426 client.set_tcp_nodelay(tcp_nodelay_);
13427 client.set_ipv6_v6only(ipv6_v6only_);
13428 if (socket_options_) { client.set_socket_options(socket_options_); }
13429 if (!interface_.empty()) { client.set_interface(interface_); }
13430
13431 // Copy logging and headers
13432 if (logger_) { client.set_logger(logger_); }
13433 if (error_logger_) { client.set_error_logger(error_logger_); }
13434
13435 // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
13436 // Each new client should generate its own headers based on its target host
13437}
13438
13440 const Request &req,
13441 Error &error) const {
13442 auto is_shutting_down = []() { return false; };
13443
13445 auto compressor = compress_ ? detail::create_compressor().first
13446 : std::unique_ptr<detail::compressor>();
13447 if (!compressor) {
13449 }
13450
13452 is_shutting_down, *compressor, error);
13453 } else {
13455 strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
13456 req.upload_progress, error);
13457 }
13458}
13459
13460inline bool ClientImpl::write_request(Stream &strm, Request &req,
13461 bool close_connection, Error &error,
13462 bool skip_body) {
13463 // Prepare additional headers
13464 if (close_connection) {
13465 if (!req.has_header("Connection")) {
13466 req.set_header("Connection", "close");
13467 }
13468 }
13469
13470 std::string ct_for_defaults;
13471 if (!req.has_header("Content-Type") && !req.body.empty()) {
13472 ct_for_defaults = "text/plain";
13473 }
13474 prepare_default_headers(req, false, ct_for_defaults);
13475
13476 if (req.body.empty()) {
13477 if (req.content_provider_) {
13479 if (!req.has_header("Content-Length")) {
13480 auto length = std::to_string(req.content_length_);
13481 req.set_header("Content-Length", length);
13482 }
13483 }
13484 } else {
13485 if (req.method == "POST" || req.method == "PUT" ||
13486 req.method == "PATCH") {
13487 req.set_header("Content-Length", "0");
13488 }
13489 }
13490 }
13491
13492 if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
13493 if (!req.has_header("Authorization")) {
13496 }
13497 }
13498
13499 if (!bearer_token_auth_token_.empty()) {
13500 if (!req.has_header("Authorization")) {
13502 bearer_token_auth_token_, false));
13503 }
13504 }
13505
13506 // Proxy-Authorization is only sent when the proxy is actually used for
13507 // this target — otherwise NO_PROXY-matched requests would leak proxy
13508 // credentials directly to the destination server.
13510 if (!proxy_basic_auth_username_.empty() &&
13511 !proxy_basic_auth_password_.empty() &&
13512 !req.has_header("Proxy-Authorization")) {
13515 }
13516 if (!proxy_bearer_token_auth_token_.empty() &&
13517 !req.has_header("Proxy-Authorization")) {
13520 }
13521 }
13522
13523 // Request line and headers
13524 {
13525 detail::BufferStream bstrm;
13526
13527 // Extract path and query from req.path
13528 std::string path_part, query_part;
13529 auto query_pos = req.path.find('?');
13530 if (query_pos != std::string::npos) {
13531 path_part = req.path.substr(0, query_pos);
13532 query_part = req.path.substr(query_pos + 1);
13533 } else {
13534 path_part = req.path;
13535 query_part = "";
13536 }
13537
13538 // Encode path part. If the original `req.path` already contained a
13539 // query component, preserve its raw query string (including parameter
13540 // order) instead of reparsing and reassembling it which may reorder
13541 // parameters due to container ordering (e.g. `Params` uses
13542 // `std::multimap`). When there is no query in `req.path`, fall back to
13543 // building a query from `req.params` so existing callers that pass
13544 // `Params` continue to work.
13545 auto path_with_query =
13546 path_encode_ ? detail::encode_path(path_part) : path_part;
13547
13548 if (!query_part.empty()) {
13549 // Normalize the query string (decode then re-encode) while preserving
13550 // the original parameter order.
13551 auto normalized = detail::normalize_query_string(query_part);
13552 if (!normalized.empty()) { path_with_query += '?' + normalized; }
13553
13554 // Still populate req.params for handlers/users who read them.
13555 detail::parse_query_text(query_part, req.params);
13556 } else {
13557 // No query in path; parse any query_part (empty) and append params
13558 // from `req.params` when present (preserves prior behavior for
13559 // callers who provide Params separately).
13560 detail::parse_query_text(query_part, req.params);
13561 if (!req.params.empty()) {
13562 path_with_query = append_query_params(path_with_query, req.params);
13563 }
13564 }
13565
13566 // Write request line and headers
13567 detail::write_request_line(bstrm, req.method, path_with_query);
13569 error)) {
13570 output_error_log(error, &req);
13571 return false;
13572 }
13573
13574 // Flush buffer
13575 auto &data = bstrm.get_buffer();
13576 if (!detail::write_data(strm, data.data(), data.size())) {
13578 output_error_log(error, &req);
13579 return false;
13580 }
13581 }
13582
13583 // After sending request line and headers, wait briefly for an early server
13584 // response (e.g. 4xx) and avoid sending a potentially large request body
13585 // unnecessarily. This workaround is only enabled on Windows because Unix
13586 // platforms surface write errors (EPIPE) earlier; on Windows kernel send
13587 // buffering can accept large writes even when the peer already responded.
13588 // Check the stream first (which covers SSL via `is_readable()`), then
13589 // fall back to select on the socket. Only perform the wait for very large
13590 // request bodies to avoid interfering with normal small requests and
13591 // reduce side-effects. Poll briefly (up to 50ms as default) for an early
13592 // response. Skip this check when using Expect: 100-continue, as the protocol
13593 // handles early responses properly.
13594#if defined(_WIN32)
13595 if (!skip_body &&
13598 auto start = std::chrono::high_resolution_clock::now();
13599
13600 for (;;) {
13601 // Prefer socket-level readiness to avoid SSL_pending() false-positives
13602 // from SSL internals. If the underlying socket is readable, assume an
13603 // early response may be present.
13604 auto sock = strm.socket();
13605 if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
13606 return false;
13607 }
13608
13609 // Fallback to stream-level check for non-socket streams or when the
13610 // socket isn't reporting readable. Avoid using `is_readable()` for
13611 // SSL, since `SSL_pending()` may report buffered records that do not
13612 // indicate a complete application-level response yet.
13613 if (!is_ssl() && strm.is_readable()) { return false; }
13614
13615 auto now = std::chrono::high_resolution_clock::now();
13616 auto elapsed =
13617 std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
13618 .count();
13620 break;
13621 }
13622
13623 std::this_thread::sleep_for(std::chrono::milliseconds(1));
13624 }
13625 }
13626#endif
13627
13628 // Body
13629 if (skip_body) { return true; }
13630
13631 return write_request_body(strm, req, error);
13632}
13633
13634inline bool ClientImpl::write_request_body(Stream &strm, Request &req,
13635 Error &error) {
13636 if (req.body.empty()) {
13637 return write_content_with_provider(strm, req, error);
13638 }
13639
13640 if (req.upload_progress) {
13641 auto body_size = req.body.size();
13642 size_t written = 0;
13643 auto data = req.body.data();
13644
13645 while (written < body_size) {
13646 size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
13647 if (!detail::write_data(strm, data + written, to_write)) {
13649 output_error_log(error, &req);
13650 return false;
13651 }
13652 written += to_write;
13653
13654 if (!req.upload_progress(written, body_size)) {
13656 output_error_log(error, &req);
13657 return false;
13658 }
13659 }
13660 } else {
13661 if (!detail::write_data(strm, req.body.data(), req.body.size())) {
13663 output_error_log(error, &req);
13664 return false;
13665 }
13666 }
13667
13668 return true;
13669}
13670
13671inline std::unique_ptr<Response>
13672ClientImpl::send_with_content_provider_and_receiver(
13673 Request &req, const char *body, size_t content_length,
13674 ContentProvider content_provider,
13675 ContentProviderWithoutLength content_provider_without_length,
13676 const std::string &content_type, ContentReceiver content_receiver,
13677 Error &error) {
13678 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
13679
13680 auto enc = compress_
13682 : std::pair<std::unique_ptr<detail::compressor>, const char *>(
13683 nullptr, nullptr);
13684
13685 if (enc.second) { req.set_header("Content-Encoding", enc.second); }
13686
13687 if (enc.first && !content_provider_without_length) {
13688 auto &compressor = enc.first;
13689
13690 if (content_provider) {
13691 auto ok = true;
13692 size_t offset = 0;
13693 DataSink data_sink;
13694
13695 data_sink.write = [&](const char *data, size_t data_len) -> bool {
13696 if (ok) {
13697 auto last = offset + data_len == content_length;
13698
13699 auto ret = compressor->compress(
13700 data, data_len, last,
13701 [&](const char *compressed_data, size_t compressed_data_len) {
13702 req.body.append(compressed_data, compressed_data_len);
13703 return true;
13704 });
13705
13706 if (ret) {
13707 offset += data_len;
13708 } else {
13709 ok = false;
13710 }
13711 }
13712 return ok;
13713 };
13714
13715 while (ok && offset < content_length) {
13716 if (!content_provider(offset, content_length - offset, data_sink)) {
13717 error = Error::Canceled;
13718 output_error_log(error, &req);
13719 return nullptr;
13720 }
13721 }
13722 } else {
13723 if (!compressor->compress(body, content_length, true,
13724 [&](const char *data, size_t data_len) {
13725 req.body.append(data, data_len);
13726 return true;
13727 })) {
13729 output_error_log(error, &req);
13730 return nullptr;
13731 }
13732 }
13733 } else {
13734 if (content_provider) {
13735 req.content_length_ = content_length;
13736 req.content_provider_ = std::move(content_provider);
13737 req.is_chunked_content_provider_ = false;
13738 } else if (content_provider_without_length) {
13739 req.content_length_ = 0;
13740 req.content_provider_ = detail::ContentProviderAdapter(
13741 std::move(content_provider_without_length));
13742 req.is_chunked_content_provider_ = true;
13743 req.set_header("Transfer-Encoding", "chunked");
13744 } else {
13745 req.body.assign(body, content_length);
13746 }
13747 }
13748
13749 if (content_receiver) {
13750 req.content_receiver =
13751 [content_receiver](const char *data, size_t data_length,
13752 size_t /*offset*/, size_t /*total_length*/) {
13753 return content_receiver(data, data_length);
13754 };
13755 }
13756
13757 auto res = detail::make_unique<Response>();
13758 return send(req, *res, error) ? std::move(res) : nullptr;
13759}
13760
13761inline Result ClientImpl::send_with_content_provider_and_receiver(
13762 const std::string &method, const std::string &path, const Headers &headers,
13763 const char *body, size_t content_length, ContentProvider content_provider,
13764 ContentProviderWithoutLength content_provider_without_length,
13765 const std::string &content_type, ContentReceiver content_receiver,
13766 UploadProgress progress) {
13767 Request req;
13768 req.method = method;
13769 req.headers = headers;
13770 req.path = path;
13771 req.upload_progress = std::move(progress);
13772 if (max_timeout_msec_ > 0) {
13773 req.start_time_ = std::chrono::steady_clock::now();
13774 }
13775
13776 auto error = Error::Success;
13777
13778 auto res = send_with_content_provider_and_receiver(
13779 req, body, content_length, std::move(content_provider),
13780 std::move(content_provider_without_length), content_type,
13781 std::move(content_receiver), error);
13782
13783#ifdef CPPHTTPLIB_SSL_ENABLED
13784 return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
13785 last_backend_error_};
13786#else
13787 return Result{std::move(res), error, std::move(req.headers)};
13788#endif
13789}
13790
13791inline void ClientImpl::output_log(const Request &req,
13792 const Response &res) const {
13793 if (logger_) {
13794 std::lock_guard<std::mutex> guard(logger_mutex_);
13795 logger_(req, res);
13796 }
13797}
13798
13800 const Request *req) const {
13801 if (error_logger_) {
13802 std::lock_guard<std::mutex> guard(logger_mutex_);
13803 error_logger_(err, req);
13804 }
13805}
13806
13808 Response &res, bool close_connection,
13809 Error &error) {
13810 // Auto-add Expect: 100-continue for large bodies
13811 if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
13812 auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
13813 if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
13814 req.set_header("Expect", "100-continue");
13815 }
13816 }
13817
13818 // Check for Expect: 100-continue
13819 auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
13820
13821 // Send request (skip body if using Expect: 100-continue)
13822 auto write_request_success =
13823 write_request(strm, req, close_connection, error, expect_100_continue);
13824
13825#ifdef CPPHTTPLIB_SSL_ENABLED
13826 if (is_ssl() && !expect_100_continue) {
13827 auto is_proxy_enabled = is_proxy_enabled_for_host(host_);
13828 if (!is_proxy_enabled) {
13829 if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
13831 output_error_log(error, &req);
13832 return false;
13833 }
13834 }
13835 }
13836#endif
13837
13838 // Handle Expect: 100-continue.
13839 //
13840 // Wait for an interim/early response by attempting to read the status line
13841 // under a short timeout, instead of trusting raw socket readability. Over
13842 // TLS, post-handshake records (e.g. session tickets) make the socket
13843 // readable without any HTTP response being available; relying on
13844 // `select_read` there caused the body to be withheld forever and the
13845 // request to fail with `Read` (#2458). If no status line arrives within the
13846 // timeout, send the body anyway (matching curl's behavior).
13847 auto status_line_read = false;
13848 if (expect_100_continue && write_request_success) {
13850 time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
13851 time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
13852 strm.set_read_timeout(sec, usec);
13853 status_line_read = read_response_line(strm, req, res, false);
13855 }
13856
13857 if (!status_line_read) {
13858 // No interim response within the timeout: send the body and handle the
13859 // response as usual.
13860 if (!write_request_body(strm, req, error)) { return false; }
13861 expect_100_continue = false; // Switch to normal response handling
13862 }
13863 }
13864
13865 // Receive response and headers
13866 // When using Expect: 100-continue, don't auto-skip `100 Continue` response
13867 if ((!status_line_read &&
13868 !read_response_line(strm, req, res, !expect_100_continue)) ||
13869 !detail::read_headers(strm, res.headers)) {
13870 if (write_request_success) { error = Error::Read; }
13871 output_error_log(error, &req);
13872 return false;
13873 }
13874
13875 if (!write_request_success) { return false; }
13876
13877 // Handle Expect: 100-continue response
13878 if (expect_100_continue) {
13879 if (res.status == StatusCode::Continue_100) {
13880 // Server accepted, send the body
13881 if (!write_request_body(strm, req, error)) { return false; }
13882
13883 // Read the actual response
13884 res.headers.clear();
13885 res.body.clear();
13886 if (!read_response_line(strm, req, res) ||
13887 !detail::read_headers(strm, res.headers)) {
13889 output_error_log(error, &req);
13890 return false;
13891 }
13892 }
13893 // If not 100 Continue, server returned an error; proceed with that response
13894 }
13895
13896 // Body
13897 if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
13898 req.method != "CONNECT") {
13899 auto redirect = 300 < res.status && res.status < 400 &&
13902
13903 if (req.response_handler && !redirect) {
13904 if (!req.response_handler(res)) {
13906 output_error_log(error, &req);
13907 return false;
13908 }
13909 }
13910
13911 auto out =
13913 ? static_cast<ContentReceiverWithProgress>(
13914 [&](const char *buf, size_t n, size_t off, size_t len) {
13915 if (redirect) { return true; }
13916 auto ret = req.content_receiver(buf, n, off, len);
13917 if (!ret) {
13919 output_error_log(error, &req);
13920 }
13921 return ret;
13922 })
13923 : static_cast<ContentReceiverWithProgress>(
13924 [&](const char *buf, size_t n, size_t /*off*/,
13925 size_t /*len*/) {
13926 assert(res.body.size() + n <= res.body.max_size());
13927 if (payload_max_length_ > 0 &&
13928 (res.body.size() >= payload_max_length_ ||
13929 n > payload_max_length_ - res.body.size())) {
13930 return false;
13931 }
13932 res.body.append(buf, n);
13933 return true;
13934 });
13935
13936 auto progress = [&](size_t current, size_t total) {
13937 if (!req.download_progress || redirect) { return true; }
13938 auto ret = req.download_progress(current, total);
13939 if (!ret) {
13941 output_error_log(error, &req);
13942 }
13943 return ret;
13944 };
13945
13946 if (res.has_header("Content-Length")) {
13947 if (!req.content_receiver) {
13948 auto len = res.get_header_value_u64("Content-Length");
13949 if (len > res.body.max_size()) {
13951 output_error_log(error, &req);
13952 return false;
13953 }
13954 // Cap the reservation by payload_max_length_ to avoid OOM when a
13955 // hostile or malformed server sends an enormous Content-Length.
13956 // The actual body read below is bounded by payload_max_length_,
13957 // so reserving more than that is never useful.
13958 auto reserve_len = static_cast<size_t>(len);
13959 if (payload_max_length_ > 0 && reserve_len > payload_max_length_) {
13960 reserve_len = payload_max_length_;
13961 }
13962 res.body.reserve(reserve_len);
13963 }
13964 }
13965
13967 int dummy_status;
13968 auto max_length = (!has_payload_max_length_ && req.content_receiver)
13969 ? (std::numeric_limits<size_t>::max)()
13971 if (!detail::read_content(strm, res, max_length, dummy_status,
13972 std::move(progress), std::move(out),
13973 decompress_)) {
13974 if (error != Error::Canceled) { error = Error::Read; }
13975 output_error_log(error, &req);
13976 return false;
13977 }
13978 }
13979 }
13980
13981 // Log
13982 output_log(req, res);
13983
13984 return true;
13985}
13986
13987inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
13988 const std::string &boundary, const UploadFormDataItems &items,
13989 const FormDataProviderItems &provider_items) const {
13990 size_t cur_item = 0;
13991 size_t cur_start = 0;
13992 // cur_item and cur_start are copied to within the std::function and
13993 // maintain state between successive calls
13994 return [&, cur_item, cur_start](size_t offset,
13995 DataSink &sink) mutable -> bool {
13996 if (!offset && !items.empty()) {
13997 sink.os << detail::serialize_multipart_formdata(items, boundary, false);
13998 return true;
13999 } else if (cur_item < provider_items.size()) {
14000 if (!cur_start) {
14001 const auto &begin = detail::serialize_multipart_formdata_item_begin(
14002 provider_items[cur_item], boundary);
14003 offset += begin.size();
14004 cur_start = offset;
14005 sink.os << begin;
14006 }
14007
14008 DataSink cur_sink;
14009 auto has_data = true;
14010 cur_sink.write = sink.write;
14011 cur_sink.done = [&]() { has_data = false; };
14012
14013 if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
14014 return false;
14015 }
14016
14017 if (!has_data) {
14018 sink.os << detail::serialize_multipart_formdata_item_end();
14019 cur_item++;
14020 cur_start = 0;
14021 }
14022 return true;
14023 } else {
14024 sink.os << detail::serialize_multipart_formdata_finish(boundary);
14025 sink.done();
14026 return true;
14027 }
14028 };
14029}
14030
14031inline bool ClientImpl::process_socket(
14032 const Socket &socket,
14033 std::chrono::time_point<std::chrono::steady_clock> start_time,
14034 std::function<bool(Stream &strm)> callback) {
14035 return detail::process_client_socket(
14036 socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
14037 write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
14038}
14039
14040inline bool ClientImpl::is_ssl() const { return false; }
14041
14042inline Result ClientImpl::Get(const std::string &path,
14043 DownloadProgress progress) {
14044 return Get(path, Headers(), std::move(progress));
14045}
14046
14047inline Result ClientImpl::Get(const std::string &path, const Params &params,
14048 const Headers &headers,
14049 DownloadProgress progress) {
14050 if (params.empty()) { return Get(path, headers); }
14051
14052 std::string path_with_query = append_query_params(path, params);
14053 return Get(path_with_query, headers, std::move(progress));
14054}
14055
14056inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14057 DownloadProgress progress) {
14058 Request req;
14059 req.method = "GET";
14060 req.path = path;
14061 req.headers = headers;
14062 req.download_progress = std::move(progress);
14063 if (max_timeout_msec_ > 0) {
14064 req.start_time_ = std::chrono::steady_clock::now();
14065 }
14066
14067 return send_(std::move(req));
14068}
14069
14070inline Result ClientImpl::Get(const std::string &path,
14071 ContentReceiver content_receiver,
14072 DownloadProgress progress) {
14073 return Get(path, Headers(), nullptr, std::move(content_receiver),
14074 std::move(progress));
14075}
14076
14077inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14078 ContentReceiver content_receiver,
14079 DownloadProgress progress) {
14080 return Get(path, headers, nullptr, std::move(content_receiver),
14081 std::move(progress));
14082}
14083
14084inline Result ClientImpl::Get(const std::string &path,
14085 ResponseHandler response_handler,
14086 ContentReceiver content_receiver,
14087 DownloadProgress progress) {
14088 return Get(path, Headers(), std::move(response_handler),
14089 std::move(content_receiver), std::move(progress));
14090}
14091
14092inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14093 ResponseHandler response_handler,
14094 ContentReceiver content_receiver,
14095 DownloadProgress progress) {
14096 Request req;
14097 req.method = "GET";
14098 req.path = path;
14099 req.headers = headers;
14100 req.response_handler = std::move(response_handler);
14101 req.content_receiver =
14102 [content_receiver](const char *data, size_t data_length,
14103 size_t /*offset*/, size_t /*total_length*/) {
14104 return content_receiver(data, data_length);
14105 };
14106 req.download_progress = std::move(progress);
14107 if (max_timeout_msec_ > 0) {
14108 req.start_time_ = std::chrono::steady_clock::now();
14109 }
14110
14111 return send_(std::move(req));
14112}
14113
14114inline Result ClientImpl::Get(const std::string &path, const Params &params,
14115 const Headers &headers,
14116 ContentReceiver content_receiver,
14117 DownloadProgress progress) {
14118 return Get(path, params, headers, nullptr, std::move(content_receiver),
14119 std::move(progress));
14120}
14121
14122inline Result ClientImpl::Get(const std::string &path, const Params &params,
14123 const Headers &headers,
14124 ResponseHandler response_handler,
14125 ContentReceiver content_receiver,
14126 DownloadProgress progress) {
14127 if (params.empty()) {
14128 return Get(path, headers, std::move(response_handler),
14129 std::move(content_receiver), std::move(progress));
14130 }
14131
14132 std::string path_with_query = append_query_params(path, params);
14133 return Get(path_with_query, headers, std::move(response_handler),
14134 std::move(content_receiver), std::move(progress));
14135}
14136
14137inline Result ClientImpl::Head(const std::string &path) {
14138 return Head(path, Headers());
14139}
14140
14141inline Result ClientImpl::Head(const std::string &path,
14142 const Headers &headers) {
14143 Request req;
14144 req.method = "HEAD";
14145 req.headers = headers;
14146 req.path = path;
14147 if (max_timeout_msec_ > 0) {
14148 req.start_time_ = std::chrono::steady_clock::now();
14149 }
14150
14151 return send_(std::move(req));
14152}
14153
14154inline Result ClientImpl::Post(const std::string &path) {
14155 return Post(path, std::string(), std::string());
14156}
14157
14158inline Result ClientImpl::Post(const std::string &path,
14159 const Headers &headers) {
14160 return Post(path, headers, nullptr, 0, std::string());
14161}
14162
14163inline Result ClientImpl::Post(const std::string &path, const char *body,
14164 size_t content_length,
14165 const std::string &content_type,
14166 UploadProgress progress) {
14167 return Post(path, Headers(), body, content_length, content_type, progress);
14168}
14169
14170inline Result ClientImpl::Post(const std::string &path, const std::string &body,
14171 const std::string &content_type,
14172 UploadProgress progress) {
14173 return Post(path, Headers(), body, content_type, progress);
14174}
14175
14176inline Result ClientImpl::Post(const std::string &path, const Params &params) {
14177 return Post(path, Headers(), params);
14178}
14179
14180inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14181 ContentProvider content_provider,
14182 const std::string &content_type,
14183 UploadProgress progress) {
14184 return Post(path, Headers(), content_length, std::move(content_provider),
14185 content_type, progress);
14186}
14187
14188inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14189 ContentProvider content_provider,
14190 const std::string &content_type,
14191 ContentReceiver content_receiver,
14192 UploadProgress progress) {
14193 return Post(path, Headers(), content_length, std::move(content_provider),
14194 content_type, std::move(content_receiver), progress);
14195}
14196
14197inline Result ClientImpl::Post(const std::string &path,
14198 ContentProviderWithoutLength content_provider,
14199 const std::string &content_type,
14200 UploadProgress progress) {
14201 return Post(path, Headers(), std::move(content_provider), content_type,
14202 progress);
14203}
14204
14205inline Result ClientImpl::Post(const std::string &path,
14206 ContentProviderWithoutLength content_provider,
14207 const std::string &content_type,
14208 ContentReceiver content_receiver,
14209 UploadProgress progress) {
14210 return Post(path, Headers(), std::move(content_provider), content_type,
14211 std::move(content_receiver), progress);
14212}
14213
14214inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14215 const Params &params) {
14216 auto query = detail::params_to_query_str(params);
14217 return Post(path, headers, query, "application/x-www-form-urlencoded");
14218}
14219
14220inline Result ClientImpl::Post(const std::string &path,
14221 const UploadFormDataItems &items,
14222 UploadProgress progress) {
14223 return Post(path, Headers(), items, progress);
14224}
14225
14226inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14227 const UploadFormDataItems &items,
14228 UploadProgress progress) {
14229 const auto &boundary = detail::make_multipart_data_boundary();
14230 const auto &content_type =
14232 auto content_length = detail::get_multipart_content_length(items, boundary);
14233 return Post(path, headers, content_length,
14235 content_type, progress);
14236}
14237
14238inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14239 const UploadFormDataItems &items,
14240 const std::string &boundary,
14241 UploadProgress progress) {
14244 }
14245
14246 const auto &content_type =
14248 auto content_length = detail::get_multipart_content_length(items, boundary);
14249 return Post(path, headers, content_length,
14251 content_type, progress);
14252}
14253
14254inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14255 const char *body, size_t content_length,
14256 const std::string &content_type,
14257 UploadProgress progress) {
14258 return send_with_content_provider_and_receiver(
14259 "POST", path, headers, body, content_length, nullptr, nullptr,
14260 content_type, nullptr, progress);
14261}
14262
14263inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14264 const std::string &body,
14265 const std::string &content_type,
14266 UploadProgress progress) {
14267 return send_with_content_provider_and_receiver(
14268 "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
14269 content_type, nullptr, progress);
14270}
14271
14272inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14273 size_t content_length,
14274 ContentProvider content_provider,
14275 const std::string &content_type,
14276 UploadProgress progress) {
14277 return send_with_content_provider_and_receiver(
14278 "POST", path, headers, nullptr, content_length,
14279 std::move(content_provider), nullptr, content_type, nullptr, progress);
14280}
14281
14282inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14283 size_t content_length,
14284 ContentProvider content_provider,
14285 const std::string &content_type,
14286 ContentReceiver content_receiver,
14287 DownloadProgress progress) {
14288 return send_with_content_provider_and_receiver(
14289 "POST", path, headers, nullptr, content_length,
14290 std::move(content_provider), nullptr, content_type,
14291 std::move(content_receiver), std::move(progress));
14292}
14293
14294inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14295 ContentProviderWithoutLength content_provider,
14296 const std::string &content_type,
14297 UploadProgress progress) {
14298 return send_with_content_provider_and_receiver(
14299 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14300 content_type, nullptr, progress);
14301}
14302
14303inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14304 ContentProviderWithoutLength content_provider,
14305 const std::string &content_type,
14306 ContentReceiver content_receiver,
14307 DownloadProgress progress) {
14308 return send_with_content_provider_and_receiver(
14309 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14310 content_type, std::move(content_receiver), std::move(progress));
14311}
14312
14313inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14314 const UploadFormDataItems &items,
14315 const FormDataProviderItems &provider_items,
14316 UploadProgress progress) {
14317 const auto &boundary = detail::make_multipart_data_boundary();
14318 const auto &content_type =
14320 return send_with_content_provider_and_receiver(
14321 "POST", path, headers, nullptr, 0, nullptr,
14322 get_multipart_content_provider(boundary, items, provider_items),
14323 content_type, nullptr, progress);
14324}
14325
14326inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14327 const std::string &body,
14328 const std::string &content_type,
14329 ContentReceiver content_receiver,
14330 DownloadProgress progress) {
14331 Request req;
14332 req.method = "POST";
14333 req.path = path;
14334 req.headers = headers;
14335 req.body = body;
14336 req.content_receiver =
14337 [content_receiver](const char *data, size_t data_length,
14338 size_t /*offset*/, size_t /*total_length*/) {
14339 return content_receiver(data, data_length);
14340 };
14341 req.download_progress = std::move(progress);
14342
14343 if (max_timeout_msec_ > 0) {
14344 req.start_time_ = std::chrono::steady_clock::now();
14345 }
14346
14347 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14348
14349 return send_(std::move(req));
14350}
14351
14352inline Result ClientImpl::Put(const std::string &path) {
14353 return Put(path, std::string(), std::string());
14354}
14355
14356inline Result ClientImpl::Put(const std::string &path, const Headers &headers) {
14357 return Put(path, headers, nullptr, 0, std::string());
14358}
14359
14360inline Result ClientImpl::Put(const std::string &path, const char *body,
14361 size_t content_length,
14362 const std::string &content_type,
14363 UploadProgress progress) {
14364 return Put(path, Headers(), body, content_length, content_type, progress);
14365}
14366
14367inline Result ClientImpl::Put(const std::string &path, const std::string &body,
14368 const std::string &content_type,
14369 UploadProgress progress) {
14370 return Put(path, Headers(), body, content_type, progress);
14371}
14372
14373inline Result ClientImpl::Put(const std::string &path, const Params &params) {
14374 return Put(path, Headers(), params);
14375}
14376
14377inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14378 ContentProvider content_provider,
14379 const std::string &content_type,
14380 UploadProgress progress) {
14381 return Put(path, Headers(), content_length, std::move(content_provider),
14382 content_type, progress);
14383}
14384
14385inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14386 ContentProvider content_provider,
14387 const std::string &content_type,
14388 ContentReceiver content_receiver,
14389 UploadProgress progress) {
14390 return Put(path, Headers(), content_length, std::move(content_provider),
14391 content_type, std::move(content_receiver), progress);
14392}
14393
14394inline Result ClientImpl::Put(const std::string &path,
14395 ContentProviderWithoutLength content_provider,
14396 const std::string &content_type,
14397 UploadProgress progress) {
14398 return Put(path, Headers(), std::move(content_provider), content_type,
14399 progress);
14400}
14401
14402inline Result ClientImpl::Put(const std::string &path,
14403 ContentProviderWithoutLength content_provider,
14404 const std::string &content_type,
14405 ContentReceiver content_receiver,
14406 UploadProgress progress) {
14407 return Put(path, Headers(), std::move(content_provider), content_type,
14408 std::move(content_receiver), progress);
14409}
14410
14411inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14412 const Params &params) {
14413 auto query = detail::params_to_query_str(params);
14414 return Put(path, headers, query, "application/x-www-form-urlencoded");
14415}
14416
14417inline Result ClientImpl::Put(const std::string &path,
14418 const UploadFormDataItems &items,
14419 UploadProgress progress) {
14420 return Put(path, Headers(), items, progress);
14421}
14422
14423inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14424 const UploadFormDataItems &items,
14425 UploadProgress progress) {
14426 const auto &boundary = detail::make_multipart_data_boundary();
14427 const auto &content_type =
14429 auto content_length = detail::get_multipart_content_length(items, boundary);
14430 return Put(path, headers, content_length,
14432 content_type, progress);
14433}
14434
14435inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14436 const UploadFormDataItems &items,
14437 const std::string &boundary,
14438 UploadProgress progress) {
14441 }
14442
14443 const auto &content_type =
14445 auto content_length = detail::get_multipart_content_length(items, boundary);
14446 return Put(path, headers, content_length,
14448 content_type, progress);
14449}
14450
14451inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14452 const char *body, size_t content_length,
14453 const std::string &content_type,
14454 UploadProgress progress) {
14455 return send_with_content_provider_and_receiver(
14456 "PUT", path, headers, body, content_length, nullptr, nullptr,
14457 content_type, nullptr, progress);
14458}
14459
14460inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14461 const std::string &body,
14462 const std::string &content_type,
14463 UploadProgress progress) {
14464 return send_with_content_provider_and_receiver(
14465 "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
14466 content_type, nullptr, progress);
14467}
14468
14469inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14470 size_t content_length,
14471 ContentProvider content_provider,
14472 const std::string &content_type,
14473 UploadProgress progress) {
14474 return send_with_content_provider_and_receiver(
14475 "PUT", path, headers, nullptr, content_length,
14476 std::move(content_provider), nullptr, content_type, nullptr, progress);
14477}
14478
14479inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14480 size_t content_length,
14481 ContentProvider content_provider,
14482 const std::string &content_type,
14483 ContentReceiver content_receiver,
14484 UploadProgress progress) {
14485 return send_with_content_provider_and_receiver(
14486 "PUT", path, headers, nullptr, content_length,
14487 std::move(content_provider), nullptr, content_type,
14488 std::move(content_receiver), progress);
14489}
14490
14491inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14492 ContentProviderWithoutLength content_provider,
14493 const std::string &content_type,
14494 UploadProgress progress) {
14495 return send_with_content_provider_and_receiver(
14496 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14497 content_type, nullptr, progress);
14498}
14499
14500inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14501 ContentProviderWithoutLength content_provider,
14502 const std::string &content_type,
14503 ContentReceiver content_receiver,
14504 UploadProgress progress) {
14505 return send_with_content_provider_and_receiver(
14506 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14507 content_type, std::move(content_receiver), progress);
14508}
14509
14510inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14511 const UploadFormDataItems &items,
14512 const FormDataProviderItems &provider_items,
14513 UploadProgress progress) {
14514 const auto &boundary = detail::make_multipart_data_boundary();
14515 const auto &content_type =
14517 return send_with_content_provider_and_receiver(
14518 "PUT", path, headers, nullptr, 0, nullptr,
14519 get_multipart_content_provider(boundary, items, provider_items),
14520 content_type, nullptr, progress);
14521}
14522
14523inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14524 const std::string &body,
14525 const std::string &content_type,
14526 ContentReceiver content_receiver,
14527 DownloadProgress progress) {
14528 Request req;
14529 req.method = "PUT";
14530 req.path = path;
14531 req.headers = headers;
14532 req.body = body;
14533 req.content_receiver =
14534 [content_receiver](const char *data, size_t data_length,
14535 size_t /*offset*/, size_t /*total_length*/) {
14536 return content_receiver(data, data_length);
14537 };
14538 req.download_progress = std::move(progress);
14539
14540 if (max_timeout_msec_ > 0) {
14541 req.start_time_ = std::chrono::steady_clock::now();
14542 }
14543
14544 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14545
14546 return send_(std::move(req));
14547}
14548
14549inline Result ClientImpl::Patch(const std::string &path) {
14550 return Patch(path, std::string(), std::string());
14551}
14552
14553inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14554 UploadProgress progress) {
14555 return Patch(path, headers, nullptr, 0, std::string(), progress);
14556}
14557
14558inline Result ClientImpl::Patch(const std::string &path, const char *body,
14559 size_t content_length,
14560 const std::string &content_type,
14561 UploadProgress progress) {
14562 return Patch(path, Headers(), body, content_length, content_type, progress);
14563}
14564
14565inline Result ClientImpl::Patch(const std::string &path,
14566 const std::string &body,
14567 const std::string &content_type,
14568 UploadProgress progress) {
14569 return Patch(path, Headers(), body, content_type, progress);
14570}
14571
14572inline Result ClientImpl::Patch(const std::string &path, const Params &params) {
14573 return Patch(path, Headers(), params);
14574}
14575
14576inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14577 ContentProvider content_provider,
14578 const std::string &content_type,
14579 UploadProgress progress) {
14580 return Patch(path, Headers(), content_length, std::move(content_provider),
14581 content_type, progress);
14582}
14583
14584inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14585 ContentProvider content_provider,
14586 const std::string &content_type,
14587 ContentReceiver content_receiver,
14588 UploadProgress progress) {
14589 return Patch(path, Headers(), content_length, std::move(content_provider),
14590 content_type, std::move(content_receiver), progress);
14591}
14592
14593inline Result ClientImpl::Patch(const std::string &path,
14594 ContentProviderWithoutLength content_provider,
14595 const std::string &content_type,
14596 UploadProgress progress) {
14597 return Patch(path, Headers(), std::move(content_provider), content_type,
14598 progress);
14599}
14600
14601inline Result ClientImpl::Patch(const std::string &path,
14602 ContentProviderWithoutLength content_provider,
14603 const std::string &content_type,
14604 ContentReceiver content_receiver,
14605 UploadProgress progress) {
14606 return Patch(path, Headers(), std::move(content_provider), content_type,
14607 std::move(content_receiver), progress);
14608}
14609
14610inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14611 const Params &params) {
14612 auto query = detail::params_to_query_str(params);
14613 return Patch(path, headers, query, "application/x-www-form-urlencoded");
14614}
14615
14616inline Result ClientImpl::Patch(const std::string &path,
14617 const UploadFormDataItems &items,
14618 UploadProgress progress) {
14619 return Patch(path, Headers(), items, progress);
14620}
14621
14622inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14623 const UploadFormDataItems &items,
14624 UploadProgress progress) {
14625 const auto &boundary = detail::make_multipart_data_boundary();
14626 const auto &content_type =
14628 auto content_length = detail::get_multipart_content_length(items, boundary);
14629 return Patch(path, headers, content_length,
14631 content_type, progress);
14632}
14633
14634inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14635 const UploadFormDataItems &items,
14636 const std::string &boundary,
14637 UploadProgress progress) {
14640 }
14641
14642 const auto &content_type =
14644 auto content_length = detail::get_multipart_content_length(items, boundary);
14645 return Patch(path, headers, content_length,
14647 content_type, progress);
14648}
14649
14650inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14651 const char *body, size_t content_length,
14652 const std::string &content_type,
14653 UploadProgress progress) {
14654 return send_with_content_provider_and_receiver(
14655 "PATCH", path, headers, body, content_length, nullptr, nullptr,
14656 content_type, nullptr, progress);
14657}
14658
14659inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14660 const std::string &body,
14661 const std::string &content_type,
14662 UploadProgress progress) {
14663 return send_with_content_provider_and_receiver(
14664 "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
14665 content_type, nullptr, progress);
14666}
14667
14668inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14669 size_t content_length,
14670 ContentProvider content_provider,
14671 const std::string &content_type,
14672 UploadProgress progress) {
14673 return send_with_content_provider_and_receiver(
14674 "PATCH", path, headers, nullptr, content_length,
14675 std::move(content_provider), nullptr, content_type, nullptr, progress);
14676}
14677
14678inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14679 size_t content_length,
14680 ContentProvider content_provider,
14681 const std::string &content_type,
14682 ContentReceiver content_receiver,
14683 UploadProgress progress) {
14684 return send_with_content_provider_and_receiver(
14685 "PATCH", path, headers, nullptr, content_length,
14686 std::move(content_provider), nullptr, content_type,
14687 std::move(content_receiver), progress);
14688}
14689
14690inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14691 ContentProviderWithoutLength content_provider,
14692 const std::string &content_type,
14693 UploadProgress progress) {
14694 return send_with_content_provider_and_receiver(
14695 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14696 content_type, nullptr, progress);
14697}
14698
14699inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14700 ContentProviderWithoutLength content_provider,
14701 const std::string &content_type,
14702 ContentReceiver content_receiver,
14703 UploadProgress progress) {
14704 return send_with_content_provider_and_receiver(
14705 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14706 content_type, std::move(content_receiver), progress);
14707}
14708
14709inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14710 const UploadFormDataItems &items,
14711 const FormDataProviderItems &provider_items,
14712 UploadProgress progress) {
14713 const auto &boundary = detail::make_multipart_data_boundary();
14714 const auto &content_type =
14716 return send_with_content_provider_and_receiver(
14717 "PATCH", path, headers, nullptr, 0, nullptr,
14718 get_multipart_content_provider(boundary, items, provider_items),
14719 content_type, nullptr, progress);
14720}
14721
14722inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14723 const std::string &body,
14724 const std::string &content_type,
14725 ContentReceiver content_receiver,
14726 DownloadProgress progress) {
14727 Request req;
14728 req.method = "PATCH";
14729 req.path = path;
14730 req.headers = headers;
14731 req.body = body;
14732 req.content_receiver =
14733 [content_receiver](const char *data, size_t data_length,
14734 size_t /*offset*/, size_t /*total_length*/) {
14735 return content_receiver(data, data_length);
14736 };
14737 req.download_progress = std::move(progress);
14738
14739 if (max_timeout_msec_ > 0) {
14740 req.start_time_ = std::chrono::steady_clock::now();
14741 }
14742
14743 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14744
14745 return send_(std::move(req));
14746}
14747
14748inline Result ClientImpl::Delete(const std::string &path,
14749 DownloadProgress progress) {
14750 return Delete(path, Headers(), std::string(), std::string(), progress);
14751}
14752
14753inline Result ClientImpl::Delete(const std::string &path,
14754 const Headers &headers,
14755 DownloadProgress progress) {
14756 return Delete(path, headers, std::string(), std::string(), progress);
14757}
14758
14759inline Result ClientImpl::Delete(const std::string &path, const char *body,
14760 size_t content_length,
14761 const std::string &content_type,
14762 DownloadProgress progress) {
14763 return Delete(path, Headers(), body, content_length, content_type, progress);
14764}
14765
14766inline Result ClientImpl::Delete(const std::string &path,
14767 const std::string &body,
14768 const std::string &content_type,
14769 DownloadProgress progress) {
14770 return Delete(path, Headers(), body.data(), body.size(), content_type,
14771 progress);
14772}
14773
14774inline Result ClientImpl::Delete(const std::string &path,
14775 const Headers &headers,
14776 const std::string &body,
14777 const std::string &content_type,
14778 DownloadProgress progress) {
14779 return Delete(path, headers, body.data(), body.size(), content_type,
14780 progress);
14781}
14782
14783inline Result ClientImpl::Delete(const std::string &path, const Params &params,
14784 DownloadProgress progress) {
14785 return Delete(path, Headers(), params, progress);
14786}
14787
14788inline Result ClientImpl::Delete(const std::string &path,
14789 const Headers &headers, const Params &params,
14790 DownloadProgress progress) {
14791 auto query = detail::params_to_query_str(params);
14792 return Delete(path, headers, query, "application/x-www-form-urlencoded",
14793 progress);
14794}
14795
14796inline Result ClientImpl::Delete(const std::string &path,
14797 const Headers &headers, const char *body,
14798 size_t content_length,
14799 const std::string &content_type,
14800 DownloadProgress progress) {
14801 Request req;
14802 req.method = "DELETE";
14803 req.headers = headers;
14804 req.path = path;
14805 req.download_progress = std::move(progress);
14806 if (max_timeout_msec_ > 0) {
14807 req.start_time_ = std::chrono::steady_clock::now();
14808 }
14809
14810 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14811 req.body.assign(body, content_length);
14812
14813 return send_(std::move(req));
14814}
14815
14816inline Result ClientImpl::Options(const std::string &path) {
14817 return Options(path, Headers());
14818}
14819
14820inline Result ClientImpl::Options(const std::string &path,
14821 const Headers &headers) {
14822 Request req;
14823 req.method = "OPTIONS";
14824 req.headers = headers;
14825 req.path = path;
14826 if (max_timeout_msec_ > 0) {
14827 req.start_time_ = std::chrono::steady_clock::now();
14828 }
14829
14830 return send_(std::move(req));
14831}
14832
14833inline void ClientImpl::stop() {
14834 std::lock_guard<std::mutex> guard(socket_mutex_);
14835
14836 // If there is anything ongoing right now, the ONLY thread-safe thing we can
14837 // do is to shutdown_socket, so that threads using this socket suddenly
14838 // discover they can't read/write any more and error out. Everything else
14839 // (closing the socket, shutting ssl down) is unsafe because these actions
14840 // are not thread-safe.
14843
14844 // Aside from that, we set a flag for the socket to be closed when we're
14845 // done.
14847 return;
14848 }
14849
14850 disconnect(/*gracefully=*/true);
14851}
14852
14853inline std::string ClientImpl::host() const { return host_; }
14854
14855inline int ClientImpl::port() const { return port_; }
14856
14857inline size_t ClientImpl::is_socket_open() const {
14858 std::lock_guard<std::mutex> guard(socket_mutex_);
14859 return socket_.is_open();
14860}
14861
14862inline socket_t ClientImpl::socket() const { return socket_.sock; }
14863
14864inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
14867}
14868
14869inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
14870 read_timeout_sec_ = sec;
14871 read_timeout_usec_ = usec;
14872}
14873
14874inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
14875 write_timeout_sec_ = sec;
14876 write_timeout_usec_ = usec;
14877}
14878
14879inline void ClientImpl::set_max_timeout(time_t msec) {
14880 max_timeout_msec_ = msec;
14881}
14882
14883inline void ClientImpl::set_basic_auth(const std::string &username,
14884 const std::string &password) {
14885 basic_auth_username_ = username;
14886 basic_auth_password_ = password;
14887}
14888
14889inline void ClientImpl::set_bearer_token_auth(const std::string &token) {
14891}
14892
14893inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
14894
14896
14897inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
14898
14899inline void
14900ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
14901 addr_map_ = std::move(addr_map);
14902}
14903
14905 default_headers_ = std::move(headers);
14906}
14907
14909 std::function<ssize_t(Stream &, Headers &)> const &writer) {
14910 header_writer_ = writer;
14911}
14912
14913inline void ClientImpl::set_address_family(int family) {
14914 address_family_ = family;
14915}
14916
14917inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
14918
14919inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
14920
14922 socket_options_ = std::move(socket_options);
14923}
14924
14925inline void ClientImpl::set_compress(bool on) { compress_ = on; }
14926
14927inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
14928
14929inline void ClientImpl::set_payload_max_length(size_t length) {
14930 payload_max_length_ = length;
14932}
14933
14934inline void ClientImpl::set_interface(const std::string &intf) {
14935 interface_ = intf;
14936}
14937
14938inline void ClientImpl::set_proxy(const std::string &host, int port) {
14939 proxy_host_ = host;
14940 proxy_port_ = port;
14941 std::lock_guard<std::mutex> guard(socket_mutex_);
14942 disconnect(/*gracefully=*/true);
14943}
14944
14945inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
14946 const std::string &password) {
14947 proxy_basic_auth_username_ = username;
14948 proxy_basic_auth_password_ = password;
14949}
14950
14951inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
14953}
14954
14955inline void ClientImpl::set_no_proxy(const std::vector<std::string> &patterns) {
14956 std::vector<detail::NoProxyEntry> parsed;
14957 parsed.reserve(patterns.size());
14958 for (const auto &p : patterns) {
14959 auto trimmed = detail::trim_copy(p);
14960 if (trimmed.empty()) { continue; }
14962 if (detail::parse_no_proxy_entry(trimmed, entry)) {
14963 parsed.push_back(std::move(entry));
14964 }
14965 }
14966 no_proxy_entries_ = std::move(parsed);
14967 std::lock_guard<std::mutex> guard(socket_mutex_);
14968 disconnect(/*gracefully=*/true);
14969}
14970
14971#ifdef CPPHTTPLIB_SSL_ENABLED
14972inline void ClientImpl::set_digest_auth(const std::string &username,
14973 const std::string &password) {
14974 digest_auth_username_ = username;
14975 digest_auth_password_ = password;
14976}
14977
14978inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
14979 const std::string &ca_cert_dir_path) {
14980 ca_cert_file_path_ = ca_cert_file_path;
14981 ca_cert_dir_path_ = ca_cert_dir_path;
14982}
14983
14984inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
14985 const std::string &password) {
14986 proxy_digest_auth_username_ = username;
14987 proxy_digest_auth_password_ = password;
14988}
14989
14990inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
14991 server_certificate_verification_ = enabled;
14992}
14993
14994inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
14995 server_hostname_verification_ = enabled;
14996}
14997#endif
14998
14999inline void ClientImpl::set_logger(Logger logger) {
15000 logger_ = std::move(logger);
15001}
15002
15004 error_logger_ = std::move(error_logger);
15005}
15006
15007/*
15008 * SSL/TLS Common Implementation
15009 */
15010
15012#ifdef CPPHTTPLIB_SSL_ENABLED
15013 if (session) {
15014 tls::shutdown(session, true);
15015 tls::free_session(session);
15016 session = nullptr;
15017 }
15018#endif
15019
15020 if (sock != INVALID_SOCKET) {
15023 }
15024}
15025
15026// Universal client implementation
15027inline Client::Client(const std::string &scheme_host_port)
15028 : Client(scheme_host_port, std::string(), std::string()) {}
15029
15030inline Client::Client(const std::string &scheme_host_port,
15031 const std::string &client_cert_path,
15032 const std::string &client_key_path) {
15034 if (detail::parse_url(scheme_host_port, uc) && !uc.host.empty()) {
15035 auto &scheme = uc.scheme;
15036
15037#ifdef CPPHTTPLIB_SSL_ENABLED
15038 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
15039#else
15040 if (!scheme.empty() && scheme != "http") {
15041#endif
15042#ifndef CPPHTTPLIB_NO_EXCEPTIONS
15043 std::string msg = "'" + scheme + "' scheme is not supported.";
15044 throw std::invalid_argument(msg);
15045#endif
15046 return;
15047 }
15048
15049 auto is_ssl = scheme == "https";
15050
15051 auto host = std::move(uc.host);
15052
15053 auto port = is_ssl ? 443 : 80;
15054 if (!uc.port.empty() && !detail::parse_port(uc.port, port)) { return; }
15055
15056 if (is_ssl) {
15057#ifdef CPPHTTPLIB_SSL_ENABLED
15058 cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
15059 client_key_path);
15060 is_ssl_ = is_ssl;
15061#endif
15062 } else {
15063 cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
15064 client_key_path);
15065 }
15066 } else {
15067 // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
15068 // if port param below changes.
15069 cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
15070 client_cert_path, client_key_path);
15071 }
15072}
15073
15074inline Client::Client(const std::string &host, int port)
15075 : Client(host, port, std::string(), std::string()) {}
15076
15077inline Client::Client(const std::string &host, int port,
15078 const std::string &client_cert_path,
15079 const std::string &client_key_path)
15080 : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
15081 client_key_path)) {}
15082
15083inline Client::~Client() = default;
15084
15085inline bool Client::is_valid() const {
15086 return cli_ != nullptr && cli_->is_valid();
15087}
15088
15089inline Result Client::Get(const std::string &path, DownloadProgress progress) {
15090 return cli_->Get(path, std::move(progress));
15091}
15092inline Result Client::Get(const std::string &path, const Headers &headers,
15093 DownloadProgress progress) {
15094 return cli_->Get(path, headers, std::move(progress));
15095}
15096inline Result Client::Get(const std::string &path,
15097 ContentReceiver content_receiver,
15098 DownloadProgress progress) {
15099 return cli_->Get(path, std::move(content_receiver), std::move(progress));
15100}
15101inline Result Client::Get(const std::string &path, const Headers &headers,
15102 ContentReceiver content_receiver,
15103 DownloadProgress progress) {
15104 return cli_->Get(path, headers, std::move(content_receiver),
15105 std::move(progress));
15106}
15107inline Result Client::Get(const std::string &path,
15108 ResponseHandler response_handler,
15109 ContentReceiver content_receiver,
15110 DownloadProgress progress) {
15111 return cli_->Get(path, std::move(response_handler),
15112 std::move(content_receiver), std::move(progress));
15113}
15114inline Result Client::Get(const std::string &path, const Headers &headers,
15115 ResponseHandler response_handler,
15116 ContentReceiver content_receiver,
15117 DownloadProgress progress) {
15118 return cli_->Get(path, headers, std::move(response_handler),
15119 std::move(content_receiver), std::move(progress));
15120}
15121inline Result Client::Get(const std::string &path, const Params &params,
15122 const Headers &headers, DownloadProgress progress) {
15123 return cli_->Get(path, params, headers, std::move(progress));
15124}
15125inline Result Client::Get(const std::string &path, const Params &params,
15126 const Headers &headers,
15127 ContentReceiver content_receiver,
15128 DownloadProgress progress) {
15129 return cli_->Get(path, params, headers, std::move(content_receiver),
15130 std::move(progress));
15131}
15132inline Result Client::Get(const std::string &path, const Params &params,
15133 const Headers &headers,
15134 ResponseHandler response_handler,
15135 ContentReceiver content_receiver,
15136 DownloadProgress progress) {
15137 return cli_->Get(path, params, headers, std::move(response_handler),
15138 std::move(content_receiver), std::move(progress));
15139}
15140
15141inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
15142inline Result Client::Head(const std::string &path, const Headers &headers) {
15143 return cli_->Head(path, headers);
15144}
15145
15146inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
15147inline Result Client::Post(const std::string &path, const Headers &headers) {
15148 return cli_->Post(path, headers);
15149}
15150inline Result Client::Post(const std::string &path, const char *body,
15151 size_t content_length,
15152 const std::string &content_type,
15153 UploadProgress progress) {
15154 return cli_->Post(path, body, content_length, content_type, progress);
15155}
15156inline Result Client::Post(const std::string &path, const Headers &headers,
15157 const char *body, size_t content_length,
15158 const std::string &content_type,
15159 UploadProgress progress) {
15160 return cli_->Post(path, headers, body, content_length, content_type,
15161 progress);
15162}
15163inline Result Client::Post(const std::string &path, const std::string &body,
15164 const std::string &content_type,
15165 UploadProgress progress) {
15166 return cli_->Post(path, body, content_type, progress);
15167}
15168inline Result Client::Post(const std::string &path, const Headers &headers,
15169 const std::string &body,
15170 const std::string &content_type,
15171 UploadProgress progress) {
15172 return cli_->Post(path, headers, body, content_type, progress);
15173}
15174inline Result Client::Post(const std::string &path, size_t content_length,
15175 ContentProvider content_provider,
15176 const std::string &content_type,
15177 UploadProgress progress) {
15178 return cli_->Post(path, content_length, std::move(content_provider),
15179 content_type, progress);
15180}
15181inline Result Client::Post(const std::string &path, size_t content_length,
15182 ContentProvider content_provider,
15183 const std::string &content_type,
15184 ContentReceiver content_receiver,
15185 UploadProgress progress) {
15186 return cli_->Post(path, content_length, std::move(content_provider),
15187 content_type, std::move(content_receiver), progress);
15188}
15189inline Result Client::Post(const std::string &path,
15190 ContentProviderWithoutLength content_provider,
15191 const std::string &content_type,
15192 UploadProgress progress) {
15193 return cli_->Post(path, std::move(content_provider), content_type, progress);
15194}
15195inline Result Client::Post(const std::string &path,
15196 ContentProviderWithoutLength content_provider,
15197 const std::string &content_type,
15198 ContentReceiver content_receiver,
15199 UploadProgress progress) {
15200 return cli_->Post(path, std::move(content_provider), content_type,
15201 std::move(content_receiver), progress);
15202}
15203inline Result Client::Post(const std::string &path, const Headers &headers,
15204 size_t content_length,
15205 ContentProvider content_provider,
15206 const std::string &content_type,
15207 UploadProgress progress) {
15208 return cli_->Post(path, headers, content_length, std::move(content_provider),
15209 content_type, progress);
15210}
15211inline Result Client::Post(const std::string &path, const Headers &headers,
15212 size_t content_length,
15213 ContentProvider content_provider,
15214 const std::string &content_type,
15215 ContentReceiver content_receiver,
15216 DownloadProgress progress) {
15217 return cli_->Post(path, headers, content_length, std::move(content_provider),
15218 content_type, std::move(content_receiver), progress);
15219}
15220inline Result Client::Post(const std::string &path, const Headers &headers,
15221 ContentProviderWithoutLength content_provider,
15222 const std::string &content_type,
15223 UploadProgress progress) {
15224 return cli_->Post(path, headers, std::move(content_provider), content_type,
15225 progress);
15226}
15227inline Result Client::Post(const std::string &path, const Headers &headers,
15228 ContentProviderWithoutLength content_provider,
15229 const std::string &content_type,
15230 ContentReceiver content_receiver,
15231 DownloadProgress progress) {
15232 return cli_->Post(path, headers, std::move(content_provider), content_type,
15233 std::move(content_receiver), progress);
15234}
15235inline Result Client::Post(const std::string &path, const Params &params) {
15236 return cli_->Post(path, params);
15237}
15238inline Result Client::Post(const std::string &path, const Headers &headers,
15239 const Params &params) {
15240 return cli_->Post(path, headers, params);
15241}
15242inline Result Client::Post(const std::string &path,
15243 const UploadFormDataItems &items,
15244 UploadProgress progress) {
15245 return cli_->Post(path, items, progress);
15246}
15247inline Result Client::Post(const std::string &path, const Headers &headers,
15248 const UploadFormDataItems &items,
15249 UploadProgress progress) {
15250 return cli_->Post(path, headers, items, progress);
15251}
15252inline Result Client::Post(const std::string &path, const Headers &headers,
15253 const UploadFormDataItems &items,
15254 const std::string &boundary,
15255 UploadProgress progress) {
15256 return cli_->Post(path, headers, items, boundary, progress);
15257}
15258inline Result Client::Post(const std::string &path, const Headers &headers,
15259 const UploadFormDataItems &items,
15260 const FormDataProviderItems &provider_items,
15261 UploadProgress progress) {
15262 return cli_->Post(path, headers, items, provider_items, progress);
15263}
15264inline Result Client::Post(const std::string &path, const Headers &headers,
15265 const std::string &body,
15266 const std::string &content_type,
15267 ContentReceiver content_receiver,
15268 DownloadProgress progress) {
15269 return cli_->Post(path, headers, body, content_type,
15270 std::move(content_receiver), progress);
15271}
15272
15273inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
15274inline Result Client::Put(const std::string &path, const Headers &headers) {
15275 return cli_->Put(path, headers);
15276}
15277inline Result Client::Put(const std::string &path, const char *body,
15278 size_t content_length,
15279 const std::string &content_type,
15280 UploadProgress progress) {
15281 return cli_->Put(path, body, content_length, content_type, progress);
15282}
15283inline Result Client::Put(const std::string &path, const Headers &headers,
15284 const char *body, size_t content_length,
15285 const std::string &content_type,
15286 UploadProgress progress) {
15287 return cli_->Put(path, headers, body, content_length, content_type, progress);
15288}
15289inline Result Client::Put(const std::string &path, const std::string &body,
15290 const std::string &content_type,
15291 UploadProgress progress) {
15292 return cli_->Put(path, body, content_type, progress);
15293}
15294inline Result Client::Put(const std::string &path, const Headers &headers,
15295 const std::string &body,
15296 const std::string &content_type,
15297 UploadProgress progress) {
15298 return cli_->Put(path, headers, body, content_type, progress);
15299}
15300inline Result Client::Put(const std::string &path, size_t content_length,
15301 ContentProvider content_provider,
15302 const std::string &content_type,
15303 UploadProgress progress) {
15304 return cli_->Put(path, content_length, std::move(content_provider),
15305 content_type, progress);
15306}
15307inline Result Client::Put(const std::string &path, size_t content_length,
15308 ContentProvider content_provider,
15309 const std::string &content_type,
15310 ContentReceiver content_receiver,
15311 UploadProgress progress) {
15312 return cli_->Put(path, content_length, std::move(content_provider),
15313 content_type, std::move(content_receiver), progress);
15314}
15315inline Result Client::Put(const std::string &path,
15316 ContentProviderWithoutLength content_provider,
15317 const std::string &content_type,
15318 UploadProgress progress) {
15319 return cli_->Put(path, std::move(content_provider), content_type, progress);
15320}
15321inline Result Client::Put(const std::string &path,
15322 ContentProviderWithoutLength content_provider,
15323 const std::string &content_type,
15324 ContentReceiver content_receiver,
15325 UploadProgress progress) {
15326 return cli_->Put(path, std::move(content_provider), content_type,
15327 std::move(content_receiver), progress);
15328}
15329inline Result Client::Put(const std::string &path, const Headers &headers,
15330 size_t content_length,
15331 ContentProvider content_provider,
15332 const std::string &content_type,
15333 UploadProgress progress) {
15334 return cli_->Put(path, headers, content_length, std::move(content_provider),
15335 content_type, progress);
15336}
15337inline Result Client::Put(const std::string &path, const Headers &headers,
15338 size_t content_length,
15339 ContentProvider content_provider,
15340 const std::string &content_type,
15341 ContentReceiver content_receiver,
15342 UploadProgress progress) {
15343 return cli_->Put(path, headers, content_length, std::move(content_provider),
15344 content_type, std::move(content_receiver), progress);
15345}
15346inline Result Client::Put(const std::string &path, const Headers &headers,
15347 ContentProviderWithoutLength content_provider,
15348 const std::string &content_type,
15349 UploadProgress progress) {
15350 return cli_->Put(path, headers, std::move(content_provider), content_type,
15351 progress);
15352}
15353inline Result Client::Put(const std::string &path, const Headers &headers,
15354 ContentProviderWithoutLength content_provider,
15355 const std::string &content_type,
15356 ContentReceiver content_receiver,
15357 UploadProgress progress) {
15358 return cli_->Put(path, headers, std::move(content_provider), content_type,
15359 std::move(content_receiver), progress);
15360}
15361inline Result Client::Put(const std::string &path, const Params &params) {
15362 return cli_->Put(path, params);
15363}
15364inline Result Client::Put(const std::string &path, const Headers &headers,
15365 const Params &params) {
15366 return cli_->Put(path, headers, params);
15367}
15368inline Result Client::Put(const std::string &path,
15369 const UploadFormDataItems &items,
15370 UploadProgress progress) {
15371 return cli_->Put(path, items, progress);
15372}
15373inline Result Client::Put(const std::string &path, const Headers &headers,
15374 const UploadFormDataItems &items,
15375 UploadProgress progress) {
15376 return cli_->Put(path, headers, items, progress);
15377}
15378inline Result Client::Put(const std::string &path, const Headers &headers,
15379 const UploadFormDataItems &items,
15380 const std::string &boundary,
15381 UploadProgress progress) {
15382 return cli_->Put(path, headers, items, boundary, progress);
15383}
15384inline Result Client::Put(const std::string &path, const Headers &headers,
15385 const UploadFormDataItems &items,
15386 const FormDataProviderItems &provider_items,
15387 UploadProgress progress) {
15388 return cli_->Put(path, headers, items, provider_items, progress);
15389}
15390inline Result Client::Put(const std::string &path, const Headers &headers,
15391 const std::string &body,
15392 const std::string &content_type,
15393 ContentReceiver content_receiver,
15394 DownloadProgress progress) {
15395 return cli_->Put(path, headers, body, content_type, content_receiver,
15396 progress);
15397}
15398
15399inline Result Client::Patch(const std::string &path) {
15400 return cli_->Patch(path);
15401}
15402inline Result Client::Patch(const std::string &path, const Headers &headers) {
15403 return cli_->Patch(path, headers);
15404}
15405inline Result Client::Patch(const std::string &path, const char *body,
15406 size_t content_length,
15407 const std::string &content_type,
15408 UploadProgress progress) {
15409 return cli_->Patch(path, body, content_length, content_type, progress);
15410}
15411inline Result Client::Patch(const std::string &path, const Headers &headers,
15412 const char *body, size_t content_length,
15413 const std::string &content_type,
15414 UploadProgress progress) {
15415 return cli_->Patch(path, headers, body, content_length, content_type,
15416 progress);
15417}
15418inline Result Client::Patch(const std::string &path, const std::string &body,
15419 const std::string &content_type,
15420 UploadProgress progress) {
15421 return cli_->Patch(path, body, content_type, progress);
15422}
15423inline Result Client::Patch(const std::string &path, const Headers &headers,
15424 const std::string &body,
15425 const std::string &content_type,
15426 UploadProgress progress) {
15427 return cli_->Patch(path, headers, body, content_type, progress);
15428}
15429inline Result Client::Patch(const std::string &path, size_t content_length,
15430 ContentProvider content_provider,
15431 const std::string &content_type,
15432 UploadProgress progress) {
15433 return cli_->Patch(path, content_length, std::move(content_provider),
15434 content_type, progress);
15435}
15436inline Result Client::Patch(const std::string &path, size_t content_length,
15437 ContentProvider content_provider,
15438 const std::string &content_type,
15439 ContentReceiver content_receiver,
15440 UploadProgress progress) {
15441 return cli_->Patch(path, content_length, std::move(content_provider),
15442 content_type, std::move(content_receiver), progress);
15443}
15444inline Result Client::Patch(const std::string &path,
15445 ContentProviderWithoutLength content_provider,
15446 const std::string &content_type,
15447 UploadProgress progress) {
15448 return cli_->Patch(path, std::move(content_provider), content_type, progress);
15449}
15450inline Result Client::Patch(const std::string &path,
15451 ContentProviderWithoutLength content_provider,
15452 const std::string &content_type,
15453 ContentReceiver content_receiver,
15454 UploadProgress progress) {
15455 return cli_->Patch(path, std::move(content_provider), content_type,
15456 std::move(content_receiver), progress);
15457}
15458inline Result Client::Patch(const std::string &path, const Headers &headers,
15459 size_t content_length,
15460 ContentProvider content_provider,
15461 const std::string &content_type,
15462 UploadProgress progress) {
15463 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15464 content_type, progress);
15465}
15466inline Result Client::Patch(const std::string &path, const Headers &headers,
15467 size_t content_length,
15468 ContentProvider content_provider,
15469 const std::string &content_type,
15470 ContentReceiver content_receiver,
15471 UploadProgress progress) {
15472 return cli_->Patch(path, headers, content_length, std::move(content_provider),
15473 content_type, std::move(content_receiver), progress);
15474}
15475inline Result Client::Patch(const std::string &path, const Headers &headers,
15476 ContentProviderWithoutLength content_provider,
15477 const std::string &content_type,
15478 UploadProgress progress) {
15479 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15480 progress);
15481}
15482inline Result Client::Patch(const std::string &path, const Headers &headers,
15483 ContentProviderWithoutLength content_provider,
15484 const std::string &content_type,
15485 ContentReceiver content_receiver,
15486 UploadProgress progress) {
15487 return cli_->Patch(path, headers, std::move(content_provider), content_type,
15488 std::move(content_receiver), progress);
15489}
15490inline Result Client::Patch(const std::string &path, const Params &params) {
15491 return cli_->Patch(path, params);
15492}
15493inline Result Client::Patch(const std::string &path, const Headers &headers,
15494 const Params &params) {
15495 return cli_->Patch(path, headers, params);
15496}
15497inline Result Client::Patch(const std::string &path,
15498 const UploadFormDataItems &items,
15499 UploadProgress progress) {
15500 return cli_->Patch(path, items, progress);
15501}
15502inline Result Client::Patch(const std::string &path, const Headers &headers,
15503 const UploadFormDataItems &items,
15504 UploadProgress progress) {
15505 return cli_->Patch(path, headers, items, progress);
15506}
15507inline Result Client::Patch(const std::string &path, const Headers &headers,
15508 const UploadFormDataItems &items,
15509 const std::string &boundary,
15510 UploadProgress progress) {
15511 return cli_->Patch(path, headers, items, boundary, progress);
15512}
15513inline Result Client::Patch(const std::string &path, const Headers &headers,
15514 const UploadFormDataItems &items,
15515 const FormDataProviderItems &provider_items,
15516 UploadProgress progress) {
15517 return cli_->Patch(path, headers, items, provider_items, progress);
15518}
15519inline Result Client::Patch(const std::string &path, const Headers &headers,
15520 const std::string &body,
15521 const std::string &content_type,
15522 ContentReceiver content_receiver,
15523 DownloadProgress progress) {
15524 return cli_->Patch(path, headers, body, content_type, content_receiver,
15525 progress);
15526}
15527
15528inline Result Client::Delete(const std::string &path,
15529 DownloadProgress progress) {
15530 return cli_->Delete(path, progress);
15531}
15532inline Result Client::Delete(const std::string &path, const Headers &headers,
15533 DownloadProgress progress) {
15534 return cli_->Delete(path, headers, progress);
15535}
15536inline Result Client::Delete(const std::string &path, const char *body,
15537 size_t content_length,
15538 const std::string &content_type,
15539 DownloadProgress progress) {
15540 return cli_->Delete(path, body, content_length, content_type, progress);
15541}
15542inline Result Client::Delete(const std::string &path, const Headers &headers,
15543 const char *body, size_t content_length,
15544 const std::string &content_type,
15545 DownloadProgress progress) {
15546 return cli_->Delete(path, headers, body, content_length, content_type,
15547 progress);
15548}
15549inline Result Client::Delete(const std::string &path, const std::string &body,
15550 const std::string &content_type,
15551 DownloadProgress progress) {
15552 return cli_->Delete(path, body, content_type, progress);
15553}
15554inline Result Client::Delete(const std::string &path, const Headers &headers,
15555 const std::string &body,
15556 const std::string &content_type,
15557 DownloadProgress progress) {
15558 return cli_->Delete(path, headers, body, content_type, progress);
15559}
15560inline Result Client::Delete(const std::string &path, const Params &params,
15561 DownloadProgress progress) {
15562 return cli_->Delete(path, params, progress);
15563}
15564inline Result Client::Delete(const std::string &path, const Headers &headers,
15565 const Params &params, DownloadProgress progress) {
15566 return cli_->Delete(path, headers, params, progress);
15567}
15568
15569inline Result Client::Options(const std::string &path) {
15570 return cli_->Options(path);
15571}
15572inline Result Client::Options(const std::string &path, const Headers &headers) {
15573 return cli_->Options(path, headers);
15574}
15575
15577Client::open_stream(const std::string &method, const std::string &path,
15578 const Params &params, const Headers &headers,
15579 const std::string &body, const std::string &content_type) {
15580 return cli_->open_stream(method, path, params, headers, body, content_type);
15581}
15582
15583inline bool Client::send(Request &req, Response &res, Error &error) {
15584 return cli_->send(req, res, error);
15585}
15586
15587inline Result Client::send(const Request &req) { return cli_->send(req); }
15588
15589inline void Client::stop() { cli_->stop(); }
15590
15591inline std::string Client::host() const { return cli_->host(); }
15592
15593inline int Client::port() const { return cli_->port(); }
15594
15595inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
15596
15597inline socket_t Client::socket() const { return cli_->socket(); }
15598
15599inline void
15600Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15601 cli_->set_hostname_addr_map(std::move(addr_map));
15602}
15603
15605 cli_->set_default_headers(std::move(headers));
15606}
15607
15609 std::function<ssize_t(Stream &, Headers &)> const &writer) {
15610 cli_->set_header_writer(writer);
15611}
15612
15613inline void Client::set_address_family(int family) {
15614 cli_->set_address_family(family);
15615}
15616
15617inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
15618
15619inline void Client::set_socket_options(SocketOptions socket_options) {
15620 cli_->set_socket_options(std::move(socket_options));
15621}
15622
15623inline void Client::set_connection_timeout(time_t sec, time_t usec) {
15624 cli_->set_connection_timeout(sec, usec);
15625}
15626
15627inline void Client::set_read_timeout(time_t sec, time_t usec) {
15628 cli_->set_read_timeout(sec, usec);
15629}
15630
15631inline void Client::set_write_timeout(time_t sec, time_t usec) {
15632 cli_->set_write_timeout(sec, usec);
15633}
15634
15635inline void Client::set_basic_auth(const std::string &username,
15636 const std::string &password) {
15637 cli_->set_basic_auth(username, password);
15638}
15639inline void Client::set_bearer_token_auth(const std::string &token) {
15640 cli_->set_bearer_token_auth(token);
15641}
15642
15643inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
15644inline void Client::set_follow_location(bool on) {
15645 cli_->set_follow_location(on);
15646}
15647
15648inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
15649
15650inline void Client::set_compress(bool on) { cli_->set_compress(on); }
15651
15652inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
15653
15654inline void Client::set_payload_max_length(size_t length) {
15655 cli_->set_payload_max_length(length);
15656}
15657
15658inline void Client::set_interface(const std::string &intf) {
15659 cli_->set_interface(intf);
15660}
15661
15662inline void Client::set_proxy(const std::string &host, int port) {
15663 cli_->set_proxy(host, port);
15664}
15665inline void Client::set_proxy_basic_auth(const std::string &username,
15666 const std::string &password) {
15667 cli_->set_proxy_basic_auth(username, password);
15668}
15669inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
15670 cli_->set_proxy_bearer_token_auth(token);
15671}
15672inline void Client::set_no_proxy(const std::vector<std::string> &patterns) {
15673 cli_->set_no_proxy(patterns);
15674}
15675
15676inline void Client::set_logger(Logger logger) {
15677 cli_->set_logger(std::move(logger));
15678}
15679
15680inline void Client::set_error_logger(ErrorLogger error_logger) {
15681 cli_->set_error_logger(std::move(error_logger));
15682}
15683
15684/*
15685 * Group 6: SSL Server and Client implementation
15686 */
15687
15688#ifdef CPPHTTPLIB_SSL_ENABLED
15689
15690// SSL HTTP server implementation
15691inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
15692 const char *client_ca_cert_file_path,
15693 const char *client_ca_cert_dir_path,
15694 const char *private_key_password) {
15695 using namespace tls;
15696
15697 ctx_ = create_server_context();
15698 if (!ctx_) { return; }
15699
15700 // Load server certificate and private key
15701 if (!set_server_cert_file(ctx_, cert_path, private_key_path,
15702 private_key_password)) {
15703 last_ssl_error_ = static_cast<int>(get_error());
15704 free_context(ctx_);
15705 ctx_ = nullptr;
15706 return;
15707 }
15708
15709 // Load client CA certificates for client authentication
15710 if (client_ca_cert_file_path || client_ca_cert_dir_path) {
15711 if (!set_client_ca_file(ctx_, client_ca_cert_file_path,
15712 client_ca_cert_dir_path)) {
15713 last_ssl_error_ = static_cast<int>(get_error());
15714 free_context(ctx_);
15715 ctx_ = nullptr;
15716 return;
15717 }
15718 // Enable client certificate verification
15719 set_verify_client(ctx_, true);
15720 }
15721}
15722
15723inline SSLServer::SSLServer(const PemMemory &pem) {
15724 using namespace tls;
15725 ctx_ = create_server_context();
15726 if (ctx_) {
15727 if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
15728 pem.private_key_password)) {
15729 last_ssl_error_ = static_cast<int>(get_error());
15730 free_context(ctx_);
15731 ctx_ = nullptr;
15732 } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) {
15733 if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) {
15734 last_ssl_error_ = static_cast<int>(get_error());
15735 free_context(ctx_);
15736 ctx_ = nullptr;
15737 } else {
15738 set_verify_client(ctx_, true);
15739 }
15740 }
15741 }
15742}
15743
15744inline SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) {
15745 using namespace tls;
15746 ctx_ = create_server_context();
15747 if (ctx_) {
15748 if (!setup_callback(ctx_)) {
15749 free_context(ctx_);
15750 ctx_ = nullptr;
15751 }
15752 }
15753}
15754
15755inline SSLServer::~SSLServer() {
15756 if (ctx_) { tls::free_context(ctx_); }
15757}
15758
15759inline bool SSLServer::is_valid() const { return ctx_ != nullptr; }
15760
15761inline bool SSLServer::process_and_close_socket(socket_t sock) {
15762 using namespace tls;
15763
15764 // Create TLS session with mutex protection
15765 session_t session = nullptr;
15766 {
15767 std::lock_guard<std::mutex> guard(ctx_mutex_);
15768 session = create_session(static_cast<ctx_t>(ctx_), sock);
15769 }
15770
15771 if (!session) {
15772 last_ssl_error_ = static_cast<int>(get_error());
15775 return false;
15776 }
15777
15778 // Use scope_exit to ensure cleanup on all paths (including exceptions)
15779 bool handshake_done = false;
15780 bool ret = false;
15781 bool websocket_upgraded = false;
15782 auto cleanup = detail::scope_exit([&] {
15783 if (handshake_done) { shutdown(session, !websocket_upgraded && ret); }
15784 free_session(session);
15787 });
15788
15789 // Perform TLS accept handshake with timeout
15790 TlsError tls_err;
15791 if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_,
15792 &tls_err)) {
15793#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
15794 // Map TlsError to legacy ssl_error for backward compatibility
15795 if (tls_err.code == ErrorCode::WantRead) {
15796 last_ssl_error_ = SSL_ERROR_WANT_READ;
15797 } else if (tls_err.code == ErrorCode::WantWrite) {
15798 last_ssl_error_ = SSL_ERROR_WANT_WRITE;
15799 } else {
15800 last_ssl_error_ = SSL_ERROR_SSL;
15801 }
15802#else
15803 last_ssl_error_ = static_cast<int>(get_error());
15804#endif
15805 return false;
15806 }
15807
15808 handshake_done = true;
15809
15810 std::string remote_addr;
15811 int remote_port = 0;
15812 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
15813
15814 std::string local_addr;
15815 int local_port = 0;
15816 detail::get_local_ip_and_port(sock, local_addr, local_port);
15817
15818 ret = detail::process_server_socket_ssl(
15819 svr_sock_, session, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
15820 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
15821 write_timeout_usec_,
15822 [&](Stream &strm, bool close_connection, bool &connection_closed) {
15823 return process_request(
15824 strm, remote_addr, remote_port, local_addr, local_port,
15825 close_connection, connection_closed,
15826 [&](Request &req) { req.ssl = session; }, &websocket_upgraded);
15827 });
15828
15829 return ret;
15830}
15831
15832inline bool SSLServer::update_certs_pem(const char *cert_pem,
15833 const char *key_pem,
15834 const char *client_ca_pem,
15835 const char *password) {
15836 if (!ctx_) { return false; }
15837 std::lock_guard<std::mutex> guard(ctx_mutex_);
15838 if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) {
15839 return false;
15840 }
15841 if (client_ca_pem) {
15842 return tls::update_server_client_ca(ctx_, client_ca_pem);
15843 }
15844 return true;
15845}
15846
15847// SSL HTTP client implementation
15848inline SSLClient::~SSLClient() {
15849 if (ctx_) { tls::free_context(ctx_); }
15850 // Make sure to shut down SSL since shutdown_ssl will resolve to the
15851 // base function rather than the derived function once we get to the
15852 // base class destructor, and won't free the SSL (causing a leak).
15853 shutdown_ssl_impl(socket_, true);
15854}
15855
15856inline bool SSLClient::is_valid() const { return ctx_ != nullptr; }
15857
15858inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
15859 shutdown_ssl_impl(socket, shutdown_gracefully);
15860}
15861
15862inline void SSLClient::shutdown_ssl_impl(Socket &socket,
15863 bool shutdown_gracefully) {
15864 if (socket.sock == INVALID_SOCKET) {
15865 assert(socket.ssl == nullptr);
15866 return;
15867 }
15868 if (socket.ssl) {
15869 tls::shutdown(socket.ssl, shutdown_gracefully);
15870 {
15871 std::lock_guard<std::mutex> guard(ctx_mutex_);
15872 tls::free_session(socket.ssl);
15873 }
15874 socket.ssl = nullptr;
15875 }
15876 assert(socket.ssl == nullptr);
15877}
15878
15879inline bool SSLClient::process_socket(
15880 const Socket &socket,
15881 std::chrono::time_point<std::chrono::steady_clock> start_time,
15882 std::function<bool(Stream &strm)> callback) {
15883 assert(socket.ssl);
15884 return detail::process_client_socket_ssl(
15885 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
15886 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
15887 std::move(callback));
15888}
15889
15890inline bool SSLClient::is_ssl() const { return true; }
15891
15892inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
15893 if (!is_valid()) {
15895 return false;
15896 }
15898}
15899
15900inline bool SSLClient::setup_proxy_connection(
15901 Socket &socket,
15902 std::chrono::time_point<std::chrono::steady_clock> start_time,
15903 Response &res, bool &success, Error &error) {
15904 if (!is_proxy_enabled_for_host(host_)) { return true; }
15905
15906 if (!connect_with_proxy(socket, start_time, res, success, error)) {
15907 return false;
15908 }
15909
15910 if (!initialize_ssl(socket, error)) {
15911 success = false;
15912 return false;
15913 }
15914
15915 return true;
15916}
15917
15918// Assumes that socket_mutex_ is locked and that there are no requests in
15919// flight
15920inline bool SSLClient::connect_with_proxy(
15921 Socket &socket,
15922 std::chrono::time_point<std::chrono::steady_clock> start_time,
15923 Response &res, bool &success, Error &error) {
15924 success = true;
15925 Response proxy_res;
15927 socket.sock, read_timeout_sec_, read_timeout_usec_,
15928 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
15929 start_time, [&](Stream &strm) {
15930 Request req2;
15931 req2.method = "CONNECT";
15932 req2.path =
15933 detail::make_host_and_port_string_always_port(host_, port_);
15934 if (max_timeout_msec_ > 0) {
15935 req2.start_time_ = std::chrono::steady_clock::now();
15936 }
15937 return process_request(strm, req2, proxy_res, false, error);
15938 })) {
15939 // Thread-safe to close everything because we are assuming there are no
15940 // requests in flight
15941 shutdown_ssl(socket, true);
15942 shutdown_socket(socket);
15943 close_socket(socket);
15944 success = false;
15945 return false;
15946 }
15947
15948 if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
15949 if (!proxy_digest_auth_username_.empty() &&
15950 !proxy_digest_auth_password_.empty()) {
15951 std::map<std::string, std::string> auth;
15952 if (detail::parse_www_authenticate(proxy_res, auth, true)) {
15953 // Close the current socket and create a new one for the authenticated
15954 // request
15955 shutdown_ssl(socket, true);
15956 shutdown_socket(socket);
15957 close_socket(socket);
15958
15959 // Create a new socket for the authenticated CONNECT request
15960 if (!ensure_socket_connection(socket, error)) {
15961 success = false;
15962 output_error_log(error, nullptr);
15963 return false;
15964 }
15965
15966 proxy_res = Response();
15968 socket.sock, read_timeout_sec_, read_timeout_usec_,
15969 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
15970 start_time, [&](Stream &strm) {
15971 Request req3;
15972 req3.method = "CONNECT";
15973 req3.path = detail::make_host_and_port_string_always_port(
15974 host_, port_);
15975 req3.headers.insert(detail::make_digest_authentication_header(
15976 req3, auth, 1, detail::random_string(10),
15977 proxy_digest_auth_username_, proxy_digest_auth_password_,
15978 true));
15979 if (max_timeout_msec_ > 0) {
15980 req3.start_time_ = std::chrono::steady_clock::now();
15981 }
15982 return process_request(strm, req3, proxy_res, false, error);
15983 })) {
15984 // Thread-safe to close everything because we are assuming there are
15985 // no requests in flight
15986 shutdown_ssl(socket, true);
15987 shutdown_socket(socket);
15988 close_socket(socket);
15989 success = false;
15990 return false;
15991 }
15992 }
15993 }
15994 }
15995
15996 // If status code is not 200, proxy request is failed.
15997 // Set error to ProxyConnection and return proxy response
15998 // as the response of the request
15999 if (proxy_res.status != StatusCode::OK_200) {
16000 error = Error::ProxyConnection;
16001 output_error_log(error, nullptr);
16002 res = std::move(proxy_res);
16003 // Thread-safe to close everything because we are assuming there are
16004 // no requests in flight
16005 shutdown_ssl(socket, true);
16006 shutdown_socket(socket);
16007 close_socket(socket);
16008 return false;
16009 }
16010
16011 return true;
16012}
16013
16014inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
16015 if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
16016
16017 if (is_proxy_enabled_for_host(host_)) { return true; }
16018
16019 if (!initialize_ssl(socket, error)) {
16020 shutdown_socket(socket);
16021 close_socket(socket);
16022 return false;
16023 }
16024
16025 return true;
16026}
16027
16028// SSL HTTP client implementation
16029inline SSLClient::SSLClient(const std::string &host)
16030 : SSLClient(host, 443, std::string(), std::string()) {}
16031
16032inline SSLClient::SSLClient(const std::string &host, int port)
16033 : SSLClient(host, port, std::string(), std::string()) {}
16034
16035inline void SSLClient::init_ctx() {
16036 ctx_ = tls::create_client_context();
16037 if (ctx_) { tls::set_min_version(ctx_, tls::Version::TLS1_2); }
16038}
16039
16040inline void SSLClient::reset_ctx_on_error() {
16041 last_backend_error_ = tls::get_error();
16042 tls::free_context(ctx_);
16043 ctx_ = nullptr;
16044}
16045
16046inline SSLClient::SSLClient(const std::string &host, int port,
16047 const std::string &client_cert_path,
16048 const std::string &client_key_path,
16049 const std::string &private_key_password)
16050 : ClientImpl(host, port, client_cert_path, client_key_path) {
16051 init_ctx();
16052 if (!ctx_) { return; }
16053
16054 if (!client_cert_path.empty() && !client_key_path.empty()) {
16055 const char *password =
16056 private_key_password.empty() ? nullptr : private_key_password.c_str();
16057 if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(),
16058 client_key_path.c_str(), password)) {
16059 reset_ctx_on_error();
16060 }
16061 }
16062}
16063
16064inline SSLClient::SSLClient(const std::string &host, int port,
16065 const PemMemory &pem)
16066 : ClientImpl(host, port) {
16067 init_ctx();
16068 if (!ctx_) { return; }
16069
16070 if (pem.cert_pem && pem.key_pem) {
16071 if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
16072 pem.private_key_password)) {
16073 reset_ctx_on_error();
16074 }
16075 }
16076}
16077
16078inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16079 if (ca_cert_store && ctx_) {
16080 // set_ca_store takes ownership of ca_cert_store
16081 tls::set_ca_store(ctx_, ca_cert_store);
16082 } else if (ca_cert_store) {
16083 tls::free_ca_store(ca_cert_store);
16084 }
16085}
16086
16087inline void
16088SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16089 if (!ctx_) { return; }
16090 tls::set_verify_callback(ctx_, verifier);
16091}
16092
16093inline void SSLClient::set_session_verifier(
16094 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16095 session_verifier_ = std::move(verifier);
16096}
16097
16098#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16099inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
16100 enable_windows_cert_verification_ = enabled;
16101}
16102#endif
16103
16104inline void SSLClient::load_ca_cert_store(const char *ca_cert,
16105 std::size_t size) {
16106 if (ctx_ && ca_cert && size > 0) {
16107 ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer
16108 tls::load_ca_pem(ctx_, ca_cert, size);
16109 }
16110}
16111
16112inline bool SSLClient::load_certs() {
16113 auto ret = true;
16114
16115 std::call_once(initialize_cert_, [&]() {
16116 std::lock_guard<std::mutex> guard(ctx_mutex_);
16117
16118 if (!ca_cert_file_path_.empty()) {
16119 if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) {
16120 last_backend_error_ = tls::get_error();
16121 ret = false;
16122 }
16123 } else if (!ca_cert_dir_path_.empty()) {
16124 if (!tls::load_ca_dir(ctx_, ca_cert_dir_path_.c_str())) {
16125 last_backend_error_ = tls::get_error();
16126 ret = false;
16127 }
16128 } else if (ca_cert_pem_.empty()) {
16129 if (!tls::load_system_certs(ctx_)) {
16130 last_backend_error_ = tls::get_error();
16131 }
16132 }
16133 });
16134
16135 return ret;
16136}
16137
16138inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
16139 using namespace tls;
16140
16141 // Load CA certificates if server verification is enabled
16142 if (server_certificate_verification_) {
16143 if (!load_certs()) {
16145 output_error_log(error, nullptr);
16146 return false;
16147 }
16148 }
16149
16150 bool is_ip = detail::is_ip_address(host_);
16151
16152#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
16153 // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
16154 // SSL_VERIFY_NONE by default and performs all verification post-handshake).
16155 // For IP addresses with verification enabled, use OPTIONAL mode since
16156 // these backends require hostname for strict verification.
16157 if (is_ip && server_certificate_verification_) {
16158 set_verify_client(ctx_, false);
16159 } else {
16160 set_verify_client(ctx_, server_certificate_verification_);
16161 }
16162#endif
16163
16164 // Create TLS session
16165 session_t session = nullptr;
16166 {
16167 std::lock_guard<std::mutex> guard(ctx_mutex_);
16168 session = create_session(ctx_, socket.sock);
16169 }
16170
16171 if (!session) {
16173 last_backend_error_ = get_error();
16174 return false;
16175 }
16176
16177 // Use scope_exit to ensure session is freed on error paths
16178 bool success = false;
16179 auto session_guard = detail::scope_exit([&] {
16180 if (!success) { free_session(session); }
16181 });
16182
16183 // Set SNI extension (skip for IP addresses per RFC 6066).
16184 // On MbedTLS, set_sni also enables hostname verification internally.
16185 // On OpenSSL, set_sni only sets SNI; verification is done post-handshake.
16186 if (!is_ip) {
16187 if (!set_sni(session, host_.c_str())) {
16189 last_backend_error_ = get_error();
16190 return false;
16191 }
16192 }
16193
16194 // Perform non-blocking TLS handshake with timeout
16195 TlsError tls_err;
16196 if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_,
16197 connection_timeout_usec_, &tls_err)) {
16198 last_ssl_error_ = static_cast<int>(tls_err.code);
16199 last_backend_error_ = tls_err.backend_code;
16200 if (tls_err.code == ErrorCode::CertVerifyFailed) {
16202 } else if (tls_err.code == ErrorCode::HostnameMismatch) {
16204 } else {
16206 }
16207 output_error_log(error, nullptr);
16208 return false;
16209 }
16210
16211 // Post-handshake session verifier callback
16212 auto verification_status = SSLVerifierResponse::NoDecisionMade;
16213 if (session_verifier_) { verification_status = session_verifier_(session); }
16214
16215 if (verification_status == SSLVerifierResponse::CertificateRejected) {
16216 last_backend_error_ = get_error();
16218 output_error_log(error, nullptr);
16219 return false;
16220 }
16221
16222 // Default server certificate verification
16223 if (verification_status == SSLVerifierResponse::NoDecisionMade &&
16224 server_certificate_verification_) {
16225 verify_result_ = tls::get_verify_result(session);
16226 if (verify_result_ != 0) {
16227 last_backend_error_ = static_cast<uint64_t>(verify_result_);
16229 output_error_log(error, nullptr);
16230 return false;
16231 }
16232
16233 auto server_cert = get_peer_cert(session);
16234 if (!server_cert) {
16235 last_backend_error_ = get_error();
16237 output_error_log(error, nullptr);
16238 return false;
16239 }
16240 auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
16241
16242 // Hostname verification (post-handshake for all cases).
16243 // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE).
16244 // On MbedTLS, set_sni already enabled hostname verification during
16245 // handshake for non-IP hosts, but this check is still needed for IP
16246 // addresses where SNI is not set.
16247 if (server_hostname_verification_) {
16248 if (!verify_hostname(server_cert, host_.c_str())) {
16249 last_backend_error_ = hostname_mismatch_code();
16251 output_error_log(error, nullptr);
16252 return false;
16253 }
16254 }
16255
16256#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16257 // Additional Windows Schannel verification.
16258 // This provides real-time certificate validation with Windows Update
16259 // integration, working with both OpenSSL and MbedTLS backends.
16260 // Skip when a custom CA cert is specified, as the Windows certificate
16261 // store would not know about user-provided CA certificates.
16262 if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
16263 ca_cert_dir_path_.empty() && ca_cert_pem_.empty()) {
16264 std::vector<unsigned char> der;
16265 if (get_cert_der(server_cert, der)) {
16266 uint64_t wincrypt_error = 0;
16267 if (!detail::verify_cert_with_windows_schannel(
16268 der, host_, server_hostname_verification_, wincrypt_error)) {
16269 last_backend_error_ = wincrypt_error;
16271 output_error_log(error, nullptr);
16272 return false;
16273 }
16274 }
16275 }
16276#endif
16277 }
16278
16279 success = true;
16280 socket.ssl = session;
16281 return true;
16282}
16283
16284inline void Client::set_digest_auth(const std::string &username,
16285 const std::string &password) {
16286 cli_->set_digest_auth(username, password);
16287}
16288
16289inline void Client::set_proxy_digest_auth(const std::string &username,
16290 const std::string &password) {
16291 cli_->set_proxy_digest_auth(username, password);
16292}
16293
16294inline void Client::enable_server_certificate_verification(bool enabled) {
16295 cli_->enable_server_certificate_verification(enabled);
16296}
16297
16298inline void Client::enable_server_hostname_verification(bool enabled) {
16299 cli_->enable_server_hostname_verification(enabled);
16300}
16301
16302#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16303inline void Client::enable_windows_certificate_verification(bool enabled) {
16304 if (is_ssl_) {
16305 static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
16306 enabled);
16307 }
16308}
16309#endif
16310
16311inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
16312 const std::string &ca_cert_dir_path) {
16313 cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
16314}
16315
16316inline void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16317 if (is_ssl_) {
16318 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
16319 } else if (ca_cert_store) {
16320 tls::free_ca_store(ca_cert_store);
16321 }
16322}
16323
16324inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
16325 set_ca_cert_store(tls::create_ca_store(ca_cert, size));
16326}
16327
16328inline void
16329Client::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16330 if (is_ssl_) {
16331 static_cast<SSLClient &>(*cli_).set_server_certificate_verifier(
16332 std::move(verifier));
16333 }
16334}
16335
16336inline void Client::set_session_verifier(
16337 std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16338 if (is_ssl_) {
16339 static_cast<SSLClient &>(*cli_).set_session_verifier(std::move(verifier));
16340 }
16341}
16342
16343inline tls::ctx_t Client::tls_context() const {
16344 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).tls_context(); }
16345 return nullptr;
16346}
16347
16348#endif // CPPHTTPLIB_SSL_ENABLED
16349
16350/*
16351 * Group 7: TLS abstraction layer - Common API
16352 */
16353
16354#ifdef CPPHTTPLIB_SSL_ENABLED
16355
16356namespace tls {
16357
16358// Helper for PeerCert construction
16359inline PeerCert get_peer_cert_from_session(const_session_t session) {
16360 return PeerCert(get_peer_cert(session));
16361}
16362
16363namespace impl {
16364
16365inline VerifyCallback &get_verify_callback() {
16366 static thread_local VerifyCallback callback;
16367 return callback;
16368}
16369
16370inline VerifyCallback &get_mbedtls_verify_callback() {
16371 static thread_local VerifyCallback callback;
16372 return callback;
16373}
16374
16375// Check if a string is an IPv4 address
16376inline bool is_ipv4_address(const std::string &str) {
16377 int dots = 0;
16378 for (char c : str) {
16379 if (c == '.') {
16380 dots++;
16381 } else if (!isdigit(static_cast<unsigned char>(c))) {
16382 return false;
16383 }
16384 }
16385 return dots == 3;
16386}
16387
16388// Parse IPv4 address string to bytes
16389inline bool parse_ipv4(const std::string &str, unsigned char *out) {
16390 const char *p = str.c_str();
16391 for (int i = 0; i < 4; i++) {
16392 if (i > 0) {
16393 if (*p != '.') { return false; }
16394 p++;
16395 }
16396 int val = 0;
16397 int digits = 0;
16398 while (*p >= '0' && *p <= '9') {
16399 val = val * 10 + (*p - '0');
16400 if (val > 255) { return false; }
16401 p++;
16402 digits++;
16403 }
16404 if (digits == 0) { return false; }
16405 // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity
16406 if (digits > 1 && *(p - digits) == '0') { return false; }
16407 out[i] = static_cast<unsigned char>(val);
16408 }
16409 return *p == '\0';
16410}
16411
16412#ifdef _WIN32
16413// Enumerate Windows system certificates and call callback with DER data
16414template <typename Callback>
16415inline bool enumerate_windows_system_certs(Callback cb) {
16416 bool loaded = false;
16417 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16418 for (auto store_name : store_names) {
16419 HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
16420 if (hStore) {
16421 PCCERT_CONTEXT pContext = nullptr;
16422 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16423 nullptr) {
16424 if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) {
16425 loaded = true;
16426 }
16427 }
16428 CertCloseStore(hStore, 0);
16429 }
16430 }
16431 return loaded;
16432}
16433#endif
16434
16435#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16436// Enumerate macOS Keychain certificates and call callback with DER data
16437template <typename Callback>
16438inline bool enumerate_macos_keychain_certs(Callback cb) {
16439 bool loaded = false;
16440 const SecTrustSettingsDomain domains[] = {
16441 kSecTrustSettingsDomainSystem,
16442 kSecTrustSettingsDomainAdmin,
16443 kSecTrustSettingsDomainUser,
16444 };
16445 for (auto domain : domains) {
16446 CFArrayRef certs = nullptr;
16447 OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
16448 if (status != errSecSuccess || !certs) {
16449 if (certs) CFRelease(certs);
16450 continue;
16451 }
16452 CFIndex count = CFArrayGetCount(certs);
16453 for (CFIndex i = 0; i < count; i++) {
16454 SecCertificateRef cert =
16455 (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
16456 CFDataRef data = SecCertificateCopyData(cert);
16457 if (data) {
16458 if (cb(CFDataGetBytePtr(data),
16459 static_cast<size_t>(CFDataGetLength(data)))) {
16460 loaded = true;
16461 }
16462 CFRelease(data);
16463 }
16464 }
16465 CFRelease(certs);
16466 }
16467 return loaded;
16468}
16469#endif
16470
16471#if !defined(_WIN32) && !(defined(__APPLE__) && \
16472 defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN))
16473// Common CA certificate file paths on Linux/Unix
16474inline const char **system_ca_paths() {
16475 static const char *paths[] = {
16476 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
16477 "/etc/pki/tls/certs/ca-bundle.crt", // RHEL/CentOS
16478 "/etc/ssl/ca-bundle.pem", // OpenSUSE
16479 "/etc/pki/tls/cacert.pem", // OpenELEC
16480 "/etc/ssl/cert.pem", // Alpine, FreeBSD
16481 nullptr};
16482 return paths;
16483}
16484
16485// Common CA certificate directory paths on Linux/Unix
16486inline const char **system_ca_dirs() {
16487 static const char *dirs[] = {"/etc/ssl/certs", // Debian/Ubuntu
16488 "/etc/pki/tls/certs", // RHEL/CentOS
16489 "/usr/share/ca-certificates", // Other
16490 nullptr};
16491 return dirs;
16492}
16493#endif
16494
16495} // namespace impl
16496
16497inline bool set_client_ca_file(ctx_t ctx, const char *ca_file,
16498 const char *ca_dir) {
16499 if (!ctx) { return false; }
16500
16501 bool success = true;
16502 if (ca_file && *ca_file) {
16503 if (!load_ca_file(ctx, ca_file)) { success = false; }
16504 }
16505 if (ca_dir && *ca_dir) {
16506 if (!load_ca_dir(ctx, ca_dir)) { success = false; }
16507 }
16508
16509#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16510 // Set CA list for client certificate request (CertificateRequest message)
16511 if (ca_file && *ca_file) {
16512 auto list = SSL_load_client_CA_file(ca_file);
16513 if (list) { SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), list); }
16514 }
16515#endif
16516
16517 return success;
16518}
16519
16520inline bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
16521 const char *password) {
16522 return set_client_cert_pem(ctx, cert, key, password);
16523}
16524
16525inline bool set_server_cert_file(ctx_t ctx, const char *cert_path,
16526 const char *key_path, const char *password) {
16527 return set_client_cert_file(ctx, cert_path, key_path, password);
16528}
16529
16530// PeerCert implementation
16531inline PeerCert::PeerCert() = default;
16532
16533inline PeerCert::PeerCert(cert_t cert) : cert_(cert) {}
16534
16535inline PeerCert::PeerCert(PeerCert &&other) noexcept : cert_(other.cert_) {
16536 other.cert_ = nullptr;
16537}
16538
16539inline PeerCert &PeerCert::operator=(PeerCert &&other) noexcept {
16540 if (this != &other) {
16541 if (cert_) { free_cert(cert_); }
16542 cert_ = other.cert_;
16543 other.cert_ = nullptr;
16544 }
16545 return *this;
16546}
16547
16548inline PeerCert::~PeerCert() {
16549 if (cert_) { free_cert(cert_); }
16550}
16551
16552inline PeerCert::operator bool() const { return cert_ != nullptr; }
16553
16554inline std::string PeerCert::subject_cn() const {
16555 return cert_ ? get_cert_subject_cn(cert_) : std::string();
16556}
16557
16558inline std::string PeerCert::issuer_name() const {
16559 return cert_ ? get_cert_issuer_name(cert_) : std::string();
16560}
16561
16562inline bool PeerCert::check_hostname(const char *hostname) const {
16563 return cert_ ? verify_hostname(cert_, hostname) : false;
16564}
16565
16566inline std::vector<SanEntry> PeerCert::sans() const {
16567 std::vector<SanEntry> result;
16568 if (cert_) { get_cert_sans(cert_, result); }
16569 return result;
16570}
16571
16572inline bool PeerCert::validity(time_t &not_before, time_t &not_after) const {
16573 return cert_ ? get_cert_validity(cert_, not_before, not_after) : false;
16574}
16575
16576inline std::string PeerCert::serial() const {
16577 return cert_ ? get_cert_serial(cert_) : std::string();
16578}
16579
16580// VerifyContext method implementations
16581inline std::string VerifyContext::subject_cn() const {
16582 return cert ? get_cert_subject_cn(cert) : std::string();
16583}
16584
16585inline std::string VerifyContext::issuer_name() const {
16586 return cert ? get_cert_issuer_name(cert) : std::string();
16587}
16588
16589inline bool VerifyContext::check_hostname(const char *hostname) const {
16590 return cert ? verify_hostname(cert, hostname) : false;
16591}
16592
16593inline std::vector<SanEntry> VerifyContext::sans() const {
16594 std::vector<SanEntry> result;
16595 if (cert) { get_cert_sans(cert, result); }
16596 return result;
16597}
16598
16599inline bool VerifyContext::validity(time_t &not_before,
16600 time_t &not_after) const {
16601 return cert ? get_cert_validity(cert, not_before, not_after) : false;
16602}
16603
16604inline std::string VerifyContext::serial() const {
16605 return cert ? get_cert_serial(cert) : std::string();
16606}
16607
16608// TlsError static method implementation
16609inline std::string TlsError::verify_error_to_string(long error_code) {
16610 return verify_error_string(error_code);
16611}
16612
16613} // namespace tls
16614
16615// Request::peer_cert() implementation
16616inline tls::PeerCert Request::peer_cert() const {
16617 return tls::get_peer_cert_from_session(ssl);
16618}
16619
16620// Request::sni() implementation
16621inline std::string Request::sni() const {
16622 if (!ssl) { return std::string(); }
16623 const char *s = tls::get_sni(ssl);
16624 return s ? std::string(s) : std::string();
16625}
16626
16627#endif // CPPHTTPLIB_SSL_ENABLED
16628
16629/*
16630 * Group 8: TLS abstraction layer - OpenSSL backend
16631 */
16632
16633/*
16634 * OpenSSL Backend Implementation
16635 */
16636
16637#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16638namespace tls {
16639
16640namespace impl {
16641
16642// Helper to map OpenSSL SSL_get_error to ErrorCode
16643inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
16644 switch (ssl_error) {
16645 case SSL_ERROR_NONE: return ErrorCode::Success;
16646 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
16647 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
16648 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
16649 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
16650 case SSL_ERROR_SSL:
16651 default: return ErrorCode::Fatal;
16652 }
16653}
16654
16655// Helper: Create client CA list from PEM string
16656// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
16657// Caller takes ownership of returned list
16658inline STACK_OF(X509_NAME) *
16659 create_client_ca_list_from_pem(const char *ca_pem) {
16660 if (!ca_pem) { return nullptr; }
16661
16662 auto ca_list = sk_X509_NAME_new_null();
16663 if (!ca_list) { return nullptr; }
16664
16665 BIO *bio = BIO_new_mem_buf(ca_pem, -1);
16666 if (!bio) {
16667 sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
16668 return nullptr;
16669 }
16670
16671 X509 *cert = nullptr;
16672 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16673 nullptr) {
16674 const X509_NAME *name = X509_get_subject_name(cert);
16675 if (name) {
16676 sk_X509_NAME_push(ca_list, X509_NAME_dup(const_cast<X509_NAME *>(name)));
16677 }
16678 X509_free(cert);
16679 }
16680 BIO_free(bio);
16681
16682 return ca_list;
16683}
16684
16685// OpenSSL verify callback wrapper
16686inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
16687 auto &callback = get_verify_callback();
16688 if (!callback) { return preverify_ok; }
16689
16690 // Get SSL object from X509_STORE_CTX
16691 auto ssl = static_cast<SSL *>(
16692 X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
16693 if (!ssl) { return preverify_ok; }
16694
16695 // Get current certificate and depth
16696 auto cert = X509_STORE_CTX_get_current_cert(ctx);
16697 int depth = X509_STORE_CTX_get_error_depth(ctx);
16698 int error = X509_STORE_CTX_get_error(ctx);
16699
16700 // Build context
16701 VerifyContext verify_ctx;
16702 verify_ctx.session = static_cast<session_t>(ssl);
16703 verify_ctx.cert = static_cast<cert_t>(cert);
16704 verify_ctx.depth = depth;
16705 verify_ctx.preverify_ok = (preverify_ok != 0);
16706 verify_ctx.error_code = error;
16707 verify_ctx.error_string =
16708 (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
16709
16710 return callback(verify_ctx) ? 1 : 0;
16711}
16712
16713} // namespace impl
16714
16715inline ctx_t create_client_context() {
16716 SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
16717 if (ctx) {
16718 // Disable auto-retry to properly handle non-blocking I/O
16719 SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
16720 // Set minimum TLS version
16721 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
16722 }
16723 return static_cast<ctx_t>(ctx);
16724}
16725
16726inline void free_context(ctx_t ctx) {
16727 if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
16728}
16729
16730inline bool set_min_version(ctx_t ctx, Version version) {
16731 if (!ctx) return false;
16732 return SSL_CTX_set_min_proto_version(static_cast<SSL_CTX *>(ctx),
16733 static_cast<int>(version)) == 1;
16734}
16735
16736inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
16737 if (!ctx || !pem || len == 0) return false;
16738
16739 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16740 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16741 if (!store) return false;
16742
16743 auto bio = BIO_new_mem_buf(pem, static_cast<int>(len));
16744 if (!bio) return false;
16745
16746 bool ok = true;
16747 X509 *cert = nullptr;
16748 while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16749 nullptr) {
16750 if (X509_STORE_add_cert(store, cert) != 1) {
16751 // Ignore duplicate errors
16752 auto err = ERR_peek_last_error();
16753 if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
16754 ok = false;
16755 }
16756 }
16757 X509_free(cert);
16758 if (!ok) break;
16759 }
16760 BIO_free(bio);
16761
16762 // Clear any "no more certificates" errors
16763 ERR_clear_error();
16764 return ok;
16765}
16766
16767inline bool load_ca_file(ctx_t ctx, const char *file_path) {
16768 if (!ctx || !file_path) return false;
16769 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), file_path,
16770 nullptr) == 1;
16771}
16772
16773inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
16774 if (!ctx || !dir_path) return false;
16775 return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), nullptr,
16776 dir_path) == 1;
16777}
16778
16779inline bool load_system_certs(ctx_t ctx) {
16780 if (!ctx) return false;
16781 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16782
16783#ifdef _WIN32
16784 // Windows: Load from system certificate store (ROOT and CA)
16785 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16786 if (!store) return false;
16787
16788 bool loaded_any = false;
16789 static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16790 for (auto store_name : store_names) {
16791 auto hStore = CertOpenSystemStoreW(NULL, store_name);
16792 if (!hStore) continue;
16793
16794 PCCERT_CONTEXT pContext = nullptr;
16795 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16796 nullptr) {
16797 const unsigned char *data = pContext->pbCertEncoded;
16798 auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
16799 if (x509) {
16800 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16801 X509_free(x509);
16802 }
16803 }
16804 CertCloseStore(hStore, 0);
16805 }
16806 return loaded_any;
16807
16808#elif defined(__APPLE__)
16809#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16810 // macOS: Load from Keychain
16811 auto store = SSL_CTX_get_cert_store(ssl_ctx);
16812 if (!store) return false;
16813
16814 bool loaded_any = false;
16815 const SecTrustSettingsDomain domains[] = {
16816 kSecTrustSettingsDomainSystem,
16817 kSecTrustSettingsDomainAdmin,
16818 kSecTrustSettingsDomainUser,
16819 };
16820 for (auto domain : domains) {
16821 CFArrayRef certs = nullptr;
16822 if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
16823 !certs) {
16824 if (certs) CFRelease(certs);
16825 continue;
16826 }
16827 auto count = CFArrayGetCount(certs);
16828 for (CFIndex i = 0; i < count; i++) {
16829 auto cert = reinterpret_cast<SecCertificateRef>(
16830 const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
16831 CFDataRef der = SecCertificateCopyData(cert);
16832 if (der) {
16833 const unsigned char *data = CFDataGetBytePtr(der);
16834 auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
16835 if (x509) {
16836 if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
16837 X509_free(x509);
16838 }
16839 CFRelease(der);
16840 }
16841 }
16842 CFRelease(certs);
16843 }
16844 return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16845#else
16846 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16847#endif
16848
16849#else
16850 // Other Unix: use default verify paths
16851 return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
16852#endif
16853}
16854
16855inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
16856 const char *password) {
16857 if (!ctx || !cert || !key) return false;
16858
16859 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16860
16861 // Load certificate
16862 auto cert_bio = BIO_new_mem_buf(cert, -1);
16863 if (!cert_bio) return false;
16864
16865 auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
16866 BIO_free(cert_bio);
16867 if (!x509) return false;
16868
16869 auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1;
16870 X509_free(x509);
16871 if (!cert_ok) return false;
16872
16873 // Load private key
16874 auto key_bio = BIO_new_mem_buf(key, -1);
16875 if (!key_bio) return false;
16876
16877 auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
16878 password ? const_cast<char *>(password)
16879 : nullptr);
16880 BIO_free(key_bio);
16881 if (!pkey) return false;
16882
16883 auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1;
16884 EVP_PKEY_free(pkey);
16885
16886 return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1;
16887}
16888
16889inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
16890 const char *key_path, const char *password) {
16891 if (!ctx || !cert_path || !key_path) return false;
16892
16893 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16894
16895 if (password && password[0] != '\0') {
16896 SSL_CTX_set_default_passwd_cb_userdata(
16897 ssl_ctx, reinterpret_cast<void *>(const_cast<char *>(password)));
16898 }
16899
16900 return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 &&
16901 SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1;
16902}
16903
16904inline ctx_t create_server_context() {
16905 SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
16906 if (ctx) {
16907 SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION |
16908 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
16909 SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
16910 }
16911 return static_cast<ctx_t>(ctx);
16912}
16913
16914inline void set_verify_client(ctx_t ctx, bool require) {
16915 if (!ctx) return;
16916 SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
16917 require
16918 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
16919 : SSL_VERIFY_NONE,
16920 nullptr);
16921}
16922
16923inline session_t create_session(ctx_t ctx, socket_t sock) {
16924 if (!ctx || sock == INVALID_SOCKET) return nullptr;
16925
16926 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
16927 SSL *ssl = SSL_new(ssl_ctx);
16928 if (!ssl) return nullptr;
16929
16930 // Disable auto-retry for proper non-blocking I/O handling
16931 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
16932
16933 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
16934 if (!bio) {
16935 SSL_free(ssl);
16936 return nullptr;
16937 }
16938
16939 SSL_set_bio(ssl, bio, bio);
16940 return static_cast<session_t>(ssl);
16941}
16942
16943inline void free_session(session_t session) {
16944 if (session) { SSL_free(static_cast<SSL *>(session)); }
16945}
16946
16947inline bool set_sni(session_t session, const char *hostname) {
16948 if (!session || !hostname) return false;
16949
16950 auto ssl = static_cast<SSL *>(session);
16951
16952 // Set SNI (Server Name Indication) only - does not enable verification
16953#if defined(OPENSSL_IS_BORINGSSL)
16954 return SSL_set_tlsext_host_name(ssl, hostname) == 1;
16955#else
16956 // Direct call instead of macro to suppress -Wold-style-cast warning
16957 return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
16958 static_cast<void *>(const_cast<char *>(hostname))) == 1;
16959#endif
16960}
16961
16962inline bool set_hostname(session_t session, const char *hostname) {
16963 if (!session || !hostname) return false;
16964
16965 auto ssl = static_cast<SSL *>(session);
16966
16967 // Set SNI (Server Name Indication)
16968 if (!set_sni(session, hostname)) { return false; }
16969
16970 // Enable hostname verification
16971 auto param = SSL_get0_param(ssl);
16972 if (!param) return false;
16973
16974 X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
16975 if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
16976
16977 SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
16978 return true;
16979}
16980
16981inline TlsError connect(session_t session) {
16982 if (!session) { return TlsError(); }
16983
16984 auto ssl = static_cast<SSL *>(session);
16985 auto ret = SSL_connect(ssl);
16986
16987 TlsError err;
16988 if (ret == 1) {
16989 err.code = ErrorCode::Success;
16990 } else {
16991 auto ssl_err = SSL_get_error(ssl, ret);
16992 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
16993 err.backend_code = ERR_get_error();
16994 }
16995 return err;
16996}
16997
16998inline TlsError accept(session_t session) {
16999 if (!session) { return TlsError(); }
17000
17001 auto ssl = static_cast<SSL *>(session);
17002 auto ret = SSL_accept(ssl);
17003
17004 TlsError err;
17005 if (ret == 1) {
17006 err.code = ErrorCode::Success;
17007 } else {
17008 auto ssl_err = SSL_get_error(ssl, ret);
17009 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17010 err.backend_code = ERR_get_error();
17011 }
17012 return err;
17013}
17014
17015inline bool connect_nonblocking(session_t session, socket_t sock,
17016 time_t timeout_sec, time_t timeout_usec,
17017 TlsError *err) {
17018 if (!session) {
17019 if (err) { err->code = ErrorCode::Fatal; }
17020 return false;
17021 }
17022
17023 auto ssl = static_cast<SSL *>(session);
17024 auto bio = SSL_get_rbio(ssl);
17025
17026 // Set non-blocking mode for handshake
17027 detail::set_nonblocking(sock, true);
17028 if (bio) { BIO_set_nbio(bio, 1); }
17029
17030 auto cleanup = detail::scope_exit([&]() {
17031 // Restore blocking mode after handshake
17032 if (bio) { BIO_set_nbio(bio, 0); }
17033 detail::set_nonblocking(sock, false);
17034 });
17035
17036 auto res = 0;
17037 while ((res = SSL_connect(ssl)) != 1) {
17038 auto ssl_err = SSL_get_error(ssl, res);
17039 switch (ssl_err) {
17040 case SSL_ERROR_WANT_READ:
17041 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17042 continue;
17043 }
17044 break;
17045 case SSL_ERROR_WANT_WRITE:
17046 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17047 continue;
17048 }
17049 break;
17050 default: break;
17051 }
17052 if (err) {
17053 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17054 err->backend_code = ERR_get_error();
17055 }
17056 return false;
17057 }
17058 if (err) { err->code = ErrorCode::Success; }
17059 return true;
17060}
17061
17062inline bool accept_nonblocking(session_t session, socket_t sock,
17063 time_t timeout_sec, time_t timeout_usec,
17064 TlsError *err) {
17065 if (!session) {
17066 if (err) { err->code = ErrorCode::Fatal; }
17067 return false;
17068 }
17069
17070 auto ssl = static_cast<SSL *>(session);
17071 auto bio = SSL_get_rbio(ssl);
17072
17073 // Set non-blocking mode for handshake
17074 detail::set_nonblocking(sock, true);
17075 if (bio) { BIO_set_nbio(bio, 1); }
17076
17077 auto cleanup = detail::scope_exit([&]() {
17078 // Restore blocking mode after handshake
17079 if (bio) { BIO_set_nbio(bio, 0); }
17080 detail::set_nonblocking(sock, false);
17081 });
17082
17083 auto res = 0;
17084 while ((res = SSL_accept(ssl)) != 1) {
17085 auto ssl_err = SSL_get_error(ssl, res);
17086 switch (ssl_err) {
17087 case SSL_ERROR_WANT_READ:
17088 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17089 continue;
17090 }
17091 break;
17092 case SSL_ERROR_WANT_WRITE:
17093 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17094 continue;
17095 }
17096 break;
17097 default: break;
17098 }
17099 if (err) {
17100 err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17101 err->backend_code = ERR_get_error();
17102 }
17103 return false;
17104 }
17105 if (err) { err->code = ErrorCode::Success; }
17106 return true;
17107}
17108
17109inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
17110 if (!session || !buf) {
17111 err.code = ErrorCode::Fatal;
17112 return -1;
17113 }
17114
17115 auto ssl = static_cast<SSL *>(session);
17116 constexpr auto max_len =
17117 static_cast<size_t>((std::numeric_limits<int>::max)());
17118 if (len > max_len) { len = max_len; }
17119 auto ret = SSL_read(ssl, buf, static_cast<int>(len));
17120
17121 if (ret > 0) {
17122 err.code = ErrorCode::Success;
17123 return ret;
17124 }
17125
17126 auto ssl_err = SSL_get_error(ssl, ret);
17127 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17128 if (err.code == ErrorCode::PeerClosed) {
17129 return 0;
17130 } // Gracefully handle the peer closed state.
17131 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17132 return -1;
17133}
17134
17135inline ssize_t write(session_t session, const void *buf, size_t len,
17136 TlsError &err) {
17137 if (!session || !buf) {
17138 err.code = ErrorCode::Fatal;
17139 return -1;
17140 }
17141
17142 auto ssl = static_cast<SSL *>(session);
17143 auto ret = SSL_write(ssl, buf, static_cast<int>(len));
17144
17145 if (ret > 0) {
17146 err.code = ErrorCode::Success;
17147 return ret;
17148 }
17149
17150 auto ssl_err = SSL_get_error(ssl, ret);
17151 err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17152 if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17153 return -1;
17154}
17155
17156inline int pending(const_session_t session) {
17157 if (!session) return 0;
17158 return SSL_pending(static_cast<SSL *>(const_cast<void *>(session)));
17159}
17160
17161inline void shutdown(session_t session, bool graceful) {
17162 if (!session) return;
17163
17164 auto ssl = static_cast<SSL *>(session);
17165 if (graceful) {
17166 // First call sends close_notify
17167 if (SSL_shutdown(ssl) == 0) {
17168 // Second call waits for peer's close_notify
17169 SSL_shutdown(ssl);
17170 }
17171 }
17172}
17173
17174inline bool is_peer_closed(session_t session, socket_t sock) {
17175 if (!session) return true;
17176
17177 // Temporarily set socket to non-blocking to avoid blocking on SSL_peek
17178 detail::set_nonblocking(sock, true);
17179 auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
17180
17181 auto ssl = static_cast<SSL *>(session);
17182 char buf;
17183 auto ret = SSL_peek(ssl, &buf, 1);
17184 if (ret > 0) return false;
17185
17186 auto err = SSL_get_error(ssl, ret);
17187 return err == SSL_ERROR_ZERO_RETURN;
17188}
17189
17190inline cert_t get_peer_cert(const_session_t session) {
17191 if (!session) return nullptr;
17192 return static_cast<cert_t>(SSL_get1_peer_certificate(
17193 static_cast<SSL *>(const_cast<void *>(session))));
17194}
17195
17196inline void free_cert(cert_t cert) {
17197 if (cert) { X509_free(static_cast<X509 *>(cert)); }
17198}
17199
17200inline bool verify_hostname(cert_t cert, const char *hostname) {
17201 if (!cert || !hostname) return false;
17202
17203 auto x509 = static_cast<X509 *>(cert);
17204
17205 // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names
17206 if (detail::is_ip_address(hostname)) {
17207 return X509_check_ip_asc(x509, hostname, 0) == 1;
17208 }
17209 return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
17210}
17211
17212inline uint64_t hostname_mismatch_code() {
17213 return static_cast<uint64_t>(X509_V_ERR_HOSTNAME_MISMATCH);
17214}
17215
17216inline long get_verify_result(const_session_t session) {
17217 if (!session) return X509_V_ERR_UNSPECIFIED;
17218 return SSL_get_verify_result(static_cast<SSL *>(const_cast<void *>(session)));
17219}
17220
17221inline std::string get_cert_subject_cn(cert_t cert) {
17222 if (!cert) return "";
17223 auto x509 = static_cast<X509 *>(cert);
17224 auto subject_name = X509_get_subject_name(x509);
17225 if (!subject_name) return "";
17226
17227 char buf[256];
17228 auto len =
17229 X509_NAME_get_text_by_NID(subject_name, NID_commonName, buf, sizeof(buf));
17230 if (len < 0) return "";
17231 return std::string(buf, static_cast<size_t>(len));
17232}
17233
17234inline std::string get_cert_issuer_name(cert_t cert) {
17235 if (!cert) return "";
17236 auto x509 = static_cast<X509 *>(cert);
17237 auto issuer_name = X509_get_issuer_name(x509);
17238 if (!issuer_name) return "";
17239
17240 char buf[256];
17241 X509_NAME_oneline(issuer_name, buf, sizeof(buf));
17242 return std::string(buf);
17243}
17244
17245inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
17246 sans.clear();
17247 if (!cert) return false;
17248 auto x509 = static_cast<X509 *>(cert);
17249
17250 auto names = static_cast<GENERAL_NAMES *>(
17251 X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
17252 if (!names) return true; // No SANs is valid
17253
17254 auto count = sk_GENERAL_NAME_num(names);
17255 for (decltype(count) i = 0; i < count; i++) {
17256 auto gen = sk_GENERAL_NAME_value(names, i);
17257 if (!gen) continue;
17258
17259 SanEntry entry;
17260 switch (gen->type) {
17261 case GEN_DNS:
17262 entry.type = SanType::DNS;
17263 if (gen->d.dNSName) {
17264 entry.value = std::string(
17265 reinterpret_cast<const char *>(
17266 ASN1_STRING_get0_data(gen->d.dNSName)),
17267 static_cast<size_t>(ASN1_STRING_length(gen->d.dNSName)));
17268 }
17269 break;
17270 case GEN_IPADD:
17271 entry.type = SanType::IP;
17272 if (gen->d.iPAddress) {
17273 auto data = ASN1_STRING_get0_data(gen->d.iPAddress);
17274 auto len = ASN1_STRING_length(gen->d.iPAddress);
17275 if (len == 4) {
17276 // IPv4
17277 char buf[INET_ADDRSTRLEN];
17278 inet_ntop(AF_INET, data, buf, sizeof(buf));
17279 entry.value = buf;
17280 } else if (len == 16) {
17281 // IPv6
17282 char buf[INET6_ADDRSTRLEN];
17283 inet_ntop(AF_INET6, data, buf, sizeof(buf));
17284 entry.value = buf;
17285 }
17286 }
17287 break;
17288 case GEN_EMAIL:
17289 entry.type = SanType::EMAIL;
17290 if (gen->d.rfc822Name) {
17291 entry.value = std::string(
17292 reinterpret_cast<const char *>(
17293 ASN1_STRING_get0_data(gen->d.rfc822Name)),
17294 static_cast<size_t>(ASN1_STRING_length(gen->d.rfc822Name)));
17295 }
17296 break;
17297 case GEN_URI:
17298 entry.type = SanType::URI;
17299 if (gen->d.uniformResourceIdentifier) {
17300 entry.value = std::string(
17301 reinterpret_cast<const char *>(
17302 ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
17303 static_cast<size_t>(
17304 ASN1_STRING_length(gen->d.uniformResourceIdentifier)));
17305 }
17306 break;
17307 default: entry.type = SanType::OTHER; break;
17308 }
17309
17310 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
17311 }
17312
17313 GENERAL_NAMES_free(names);
17314 return true;
17315}
17316
17317inline bool get_cert_validity(cert_t cert, time_t &not_before,
17318 time_t &not_after) {
17319 if (!cert) return false;
17320 auto x509 = static_cast<X509 *>(cert);
17321
17322 auto nb = X509_get0_notBefore(x509);
17323 auto na = X509_get0_notAfter(x509);
17324 if (!nb || !na) return false;
17325
17326 ASN1_TIME *epoch = ASN1_TIME_new();
17327 if (!epoch) return false;
17328 auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); });
17329
17330 if (!ASN1_TIME_set(epoch, 0)) return false;
17331
17332 int pday, psec;
17333
17334 if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) return false;
17335 not_before = 86400 * (time_t)pday + psec;
17336
17337 if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) return false;
17338 not_after = 86400 * (time_t)pday + psec;
17339
17340 return true;
17341}
17342
17343inline std::string get_cert_serial(cert_t cert) {
17344 if (!cert) return "";
17345 auto x509 = static_cast<X509 *>(cert);
17346
17347 auto serial = X509_get_serialNumber(x509);
17348 if (!serial) return "";
17349
17350 auto bn = ASN1_INTEGER_to_BN(serial, nullptr);
17351 if (!bn) return "";
17352
17353 auto hex = BN_bn2hex(bn);
17354 BN_free(bn);
17355 if (!hex) return "";
17356
17357 std::string result(hex);
17358 OPENSSL_free(hex);
17359 return result;
17360}
17361
17362inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
17363 if (!cert) return false;
17364 auto x509 = static_cast<X509 *>(cert);
17365 auto len = i2d_X509(x509, nullptr);
17366 if (len < 0) return false;
17367 der.resize(static_cast<size_t>(len));
17368 auto p = der.data();
17369 i2d_X509(x509, &p);
17370 return true;
17371}
17372
17373inline const char *get_sni(const_session_t session) {
17374 if (!session) return nullptr;
17375 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17376 return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
17377}
17378
17379inline uint64_t peek_error() { return ERR_peek_last_error(); }
17380
17381inline uint64_t get_error() { return ERR_get_error(); }
17382
17383inline std::string error_string(uint64_t code) {
17384 char buf[256];
17385 ERR_error_string_n(static_cast<unsigned long>(code), buf, sizeof(buf));
17386 return std::string(buf);
17387}
17388
17389inline ca_store_t create_ca_store(const char *pem, size_t len) {
17390 auto mem = BIO_new_mem_buf(pem, static_cast<int>(len));
17391 if (!mem) { return nullptr; }
17392 auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); });
17393
17394 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
17395 if (!inf) { return nullptr; }
17396
17397 auto store = X509_STORE_new();
17398 if (store) {
17399 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
17400 auto itmp = sk_X509_INFO_value(inf, i);
17401 if (!itmp) { continue; }
17402 if (itmp->x509) { X509_STORE_add_cert(store, itmp->x509); }
17403 if (itmp->crl) { X509_STORE_add_crl(store, itmp->crl); }
17404 }
17405 }
17406
17407 sk_X509_INFO_pop_free(inf, X509_INFO_free);
17408 return static_cast<ca_store_t>(store);
17409}
17410
17411inline void free_ca_store(ca_store_t store) {
17412 if (store) { X509_STORE_free(static_cast<X509_STORE *>(store)); }
17413}
17414
17415inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
17416 if (!ctx || !store) { return false; }
17417 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17418 auto x509_store = static_cast<X509_STORE *>(store);
17419
17420 // Check if same store is already set
17421 if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { return true; }
17422
17423 // SSL_CTX_set_cert_store takes ownership and frees the old store
17424 SSL_CTX_set_cert_store(ssl_ctx, x509_store);
17425 return true;
17426}
17427
17428inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
17429 certs.clear();
17430 if (!ctx) { return 0; }
17431 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17432
17433 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17434 if (!store) { return 0; }
17435
17436 auto objs = X509_STORE_get0_objects(store);
17437 if (!objs) { return 0; }
17438
17439 auto count = sk_X509_OBJECT_num(objs);
17440 for (decltype(count) i = 0; i < count; i++) {
17441 auto obj = sk_X509_OBJECT_value(objs, i);
17442 if (!obj) { continue; }
17443 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17444 auto x509 = X509_OBJECT_get0_X509(obj);
17445 if (x509) {
17446 // Increment reference count so caller can free it
17447 X509_up_ref(x509);
17448 certs.push_back(static_cast<cert_t>(x509));
17449 }
17450 }
17451 }
17452 return certs.size();
17453}
17454
17455inline std::vector<std::string> get_ca_names(ctx_t ctx) {
17456 std::vector<std::string> names;
17457 if (!ctx) { return names; }
17458 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17459
17460 auto store = SSL_CTX_get_cert_store(ssl_ctx);
17461 if (!store) { return names; }
17462
17463 auto objs = X509_STORE_get0_objects(store);
17464 if (!objs) { return names; }
17465
17466 auto count = sk_X509_OBJECT_num(objs);
17467 for (decltype(count) i = 0; i < count; i++) {
17468 auto obj = sk_X509_OBJECT_value(objs, i);
17469 if (!obj) { continue; }
17470 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17471 auto x509 = X509_OBJECT_get0_X509(obj);
17472 if (x509) {
17473 auto subject = X509_get_subject_name(x509);
17474 if (subject) {
17475 char buf[512];
17476 X509_NAME_oneline(subject, buf, sizeof(buf));
17477 names.push_back(buf);
17478 }
17479 }
17480 }
17481 }
17482 return names;
17483}
17484
17485inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
17486 const char *key_pem, const char *password) {
17487 if (!ctx || !cert_pem || !key_pem) { return false; }
17488 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17489
17490 // Load certificate from PEM
17491 auto cert_bio = BIO_new_mem_buf(cert_pem, -1);
17492 if (!cert_bio) { return false; }
17493 auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17494 BIO_free(cert_bio);
17495 if (!cert) { return false; }
17496
17497 // Load private key from PEM
17498 auto key_bio = BIO_new_mem_buf(key_pem, -1);
17499 if (!key_bio) {
17500 X509_free(cert);
17501 return false;
17502 }
17503 auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17504 password ? const_cast<char *>(password)
17505 : nullptr);
17506 BIO_free(key_bio);
17507 if (!key) {
17508 X509_free(cert);
17509 return false;
17510 }
17511
17512 // Update certificate and key
17513 auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 &&
17514 SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1;
17515
17516 X509_free(cert);
17517 EVP_PKEY_free(key);
17518 return ret;
17519}
17520
17521inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
17522 if (!ctx || !ca_pem) { return false; }
17523 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17524
17525 // Create new X509_STORE from PEM
17526 auto store = create_ca_store(ca_pem, strlen(ca_pem));
17527 if (!store) { return false; }
17528
17529 // SSL_CTX_set_cert_store takes ownership
17530 SSL_CTX_set_cert_store(ssl_ctx, static_cast<X509_STORE *>(store));
17531
17532 // Set client CA list for client certificate request
17533 auto ca_list = impl::create_client_ca_list_from_pem(ca_pem);
17534 if (ca_list) {
17535 // SSL_CTX_set_client_CA_list takes ownership of ca_list
17536 SSL_CTX_set_client_CA_list(ssl_ctx, ca_list);
17537 }
17538
17539 return true;
17540}
17541
17542inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
17543 if (!ctx) { return false; }
17544 auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17545
17546 impl::get_verify_callback() = std::move(callback);
17547
17548 if (impl::get_verify_callback()) {
17549 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
17550 } else {
17551 SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
17552 }
17553 return true;
17554}
17555
17556inline long get_verify_error(const_session_t session) {
17557 if (!session) { return -1; }
17558 auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17559 return SSL_get_verify_result(ssl);
17560}
17561
17562inline std::string verify_error_string(long error_code) {
17563 if (error_code == X509_V_OK) { return ""; }
17564 const char *str = X509_verify_cert_error_string(static_cast<int>(error_code));
17565 return str ? str : "unknown error";
17566}
17567
17568} // namespace tls
17569
17570inline bool SSLClient::verify_host(X509 *server_cert) const {
17571 /* Quote from RFC2818 section 3.1 "Server Identity"
17572
17573 If a subjectAltName extension of type dNSName is present, that MUST
17574 be used as the identity. Otherwise, the (most specific) Common Name
17575 field in the Subject field of the certificate MUST be used. Although
17576 the use of the Common Name is existing practice, it is deprecated and
17577 Certification Authorities are encouraged to use the dNSName instead.
17578
17579 Matching is performed using the matching rules specified by
17580 [RFC2459]. If more than one identity of a given type is present in
17581 the certificate (e.g., more than one dNSName name, a match in any one
17582 of the set is considered acceptable.) Names may contain the wildcard
17583 character * which is considered to match any single domain name
17584 component or component fragment. E.g., *.a.com matches foo.a.com but
17585 not bar.foo.a.com. f*.com matches foo.com but not bar.com.
17586
17587 In some cases, the URI is specified as an IP address rather than a
17588 hostname. In this case, the iPAddress subjectAltName must be present
17589 in the certificate and must exactly match the IP in the URI.
17590
17591 */
17592 return verify_host_with_subject_alt_name(server_cert) ||
17593 verify_host_with_common_name(server_cert);
17594}
17595
17596inline bool
17597SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
17598 auto ret = false;
17599
17600 auto type = GEN_DNS;
17601
17602 struct in6_addr addr6 = {};
17603 struct in_addr addr = {};
17604 size_t addr_len = 0;
17605
17606#ifndef __MINGW32__
17607 if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
17608 type = GEN_IPADD;
17609 addr_len = sizeof(struct in6_addr);
17610 } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
17611 type = GEN_IPADD;
17612 addr_len = sizeof(struct in_addr);
17613 }
17614#endif
17615
17616 auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
17617 X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
17618
17619 if (alt_names) {
17620 auto dsn_matched = false;
17621 auto ip_matched = false;
17622
17623 auto count = sk_GENERAL_NAME_num(alt_names);
17624
17625 for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
17626 auto val = sk_GENERAL_NAME_value(alt_names, i);
17627 if (!val || val->type != type) { continue; }
17628
17629 auto name =
17630 reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
17631 if (name == nullptr) { continue; }
17632
17633 auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
17634
17635 switch (type) {
17636 case GEN_DNS:
17637 dsn_matched =
17638 detail::match_hostname(std::string(name, name_len), host_);
17639 break;
17640
17641 case GEN_IPADD:
17642 if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
17643 ip_matched = true;
17644 }
17645 break;
17646 }
17647 }
17648
17649 if (dsn_matched || ip_matched) { ret = true; }
17650 }
17651
17652 GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
17653 reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
17654 return ret;
17655}
17656
17657inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
17658 const auto subject_name = X509_get_subject_name(server_cert);
17659
17660 if (subject_name != nullptr) {
17661 char name[BUFSIZ];
17662 auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
17663 name, sizeof(name));
17664
17665 if (name_len != -1) {
17666 return detail::match_hostname(
17667 std::string(name, static_cast<size_t>(name_len)), host_);
17668 }
17669 }
17670
17671 return false;
17672}
17673
17674#endif // CPPHTTPLIB_OPENSSL_SUPPORT
17675
17676/*
17677 * Group 9: TLS abstraction layer - Mbed TLS backend
17678 */
17679
17680/*
17681 * Mbed TLS Backend Implementation
17682 */
17683
17684#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
17685namespace tls {
17686
17687namespace impl {
17688
17689// Mbed TLS session wrapper
17690struct MbedTlsSession {
17691 mbedtls_ssl_context ssl;
17692 socket_t sock = INVALID_SOCKET;
17693 std::string hostname; // For client: set via set_sni
17694 std::string sni_hostname; // For server: received from client via SNI callback
17695
17696 MbedTlsSession() { mbedtls_ssl_init(&ssl); }
17697
17698 ~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
17699
17700 MbedTlsSession(const MbedTlsSession &) = delete;
17701 MbedTlsSession &operator=(const MbedTlsSession &) = delete;
17702};
17703
17704// Thread-local error code accessor for Mbed TLS (since it doesn't have an error
17705// queue)
17706inline int &mbedtls_last_error() {
17707 static thread_local int err = 0;
17708 return err;
17709}
17710
17711// Helper to map Mbed TLS error to ErrorCode
17712inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
17713 if (ret == 0) { return ErrorCode::Success; }
17714 if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
17715 if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
17716 if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
17717 return ErrorCode::PeerClosed;
17718 }
17719 if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED ||
17720 ret == MBEDTLS_ERR_NET_RECV_FAILED) {
17721 out_errno = errno;
17722 return ErrorCode::SyscallError;
17723 }
17724 if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
17725 return ErrorCode::CertVerifyFailed;
17726 }
17727 return ErrorCode::Fatal;
17728}
17729
17730// BIO-like send callback for Mbed TLS
17731inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
17732 size_t len) {
17733 auto sock = *static_cast<socket_t *>(ctx);
17734#ifdef _WIN32
17735 auto ret =
17736 send(sock, reinterpret_cast<const char *>(buf), static_cast<int>(len), 0);
17737 if (ret == SOCKET_ERROR) {
17738 int err = WSAGetLastError();
17739 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
17740 return MBEDTLS_ERR_NET_SEND_FAILED;
17741 }
17742#else
17743 auto ret = send(sock, buf, len, 0);
17744 if (ret < 0) {
17745 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17746 return MBEDTLS_ERR_SSL_WANT_WRITE;
17747 }
17748 return MBEDTLS_ERR_NET_SEND_FAILED;
17749 }
17750#endif
17751 return static_cast<int>(ret);
17752}
17753
17754// BIO-like recv callback for Mbed TLS
17755inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
17756 auto sock = *static_cast<socket_t *>(ctx);
17757#ifdef _WIN32
17758 auto ret =
17759 recv(sock, reinterpret_cast<char *>(buf), static_cast<int>(len), 0);
17760 if (ret == SOCKET_ERROR) {
17761 int err = WSAGetLastError();
17762 if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_READ; }
17763 return MBEDTLS_ERR_NET_RECV_FAILED;
17764 }
17765#else
17766 auto ret = recv(sock, buf, len, 0);
17767 if (ret < 0) {
17768 if (errno == EAGAIN || errno == EWOULDBLOCK) {
17769 return MBEDTLS_ERR_SSL_WANT_READ;
17770 }
17771 return MBEDTLS_ERR_NET_RECV_FAILED;
17772 }
17773#endif
17774 if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
17775 return static_cast<int>(ret);
17776}
17777
17778// MbedTlsContext constructor/destructor implementations
17779inline MbedTlsContext::MbedTlsContext() {
17780 mbedtls_ssl_config_init(&conf);
17781 mbedtls_entropy_init(&entropy);
17782 mbedtls_ctr_drbg_init(&ctr_drbg);
17783 mbedtls_x509_crt_init(&ca_chain);
17784 mbedtls_x509_crt_init(&own_cert);
17785 mbedtls_pk_init(&own_key);
17786}
17787
17788inline MbedTlsContext::~MbedTlsContext() {
17789 mbedtls_pk_free(&own_key);
17790 mbedtls_x509_crt_free(&own_cert);
17791 mbedtls_x509_crt_free(&ca_chain);
17792 mbedtls_ctr_drbg_free(&ctr_drbg);
17793 mbedtls_entropy_free(&entropy);
17794 mbedtls_ssl_config_free(&conf);
17795}
17796
17797// Thread-local storage for SNI captured during handshake
17798// This is needed because the SNI callback doesn't have a way to pass
17799// session-specific data before the session is fully set up
17800inline std::string &mbedpending_sni() {
17801 static thread_local std::string sni;
17802 return sni;
17803}
17804
17805// SNI callback for Mbed TLS server to capture client's SNI hostname
17806inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
17807 const unsigned char *name, size_t name_len) {
17808 (void)p_ctx;
17809 (void)ssl;
17810
17811 // Store SNI name in thread-local storage
17812 // It will be retrieved and stored in the session after handshake
17813 if (name && name_len > 0) {
17814 mbedpending_sni().assign(reinterpret_cast<const char *>(name), name_len);
17815 } else {
17816 mbedpending_sni().clear();
17817 }
17818 return 0; // Accept any SNI
17819}
17820
17821inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17822 int cert_depth, uint32_t *flags);
17823
17824// MbedTLS verify callback wrapper
17825inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
17826 int cert_depth, uint32_t *flags) {
17827 auto &callback = get_verify_callback();
17828 if (!callback) { return 0; } // Continue with default verification
17829
17830 // data points to the MbedTlsSession
17831 auto *session = static_cast<MbedTlsSession *>(data);
17832
17833 // Build context
17834 VerifyContext verify_ctx;
17835 verify_ctx.session = static_cast<session_t>(session);
17836 verify_ctx.cert = static_cast<cert_t>(crt);
17837 verify_ctx.depth = cert_depth;
17838 verify_ctx.preverify_ok = (*flags == 0);
17839 verify_ctx.error_code = static_cast<long>(*flags);
17840
17841 // Convert Mbed TLS flags to error string
17842 static thread_local char error_buf[256];
17843 if (*flags != 0) {
17844 mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags);
17845 verify_ctx.error_string = error_buf;
17846 } else {
17847 verify_ctx.error_string = nullptr;
17848 }
17849
17850 bool accepted = callback(verify_ctx);
17851
17852 if (accepted) {
17853 *flags = 0; // Clear all error flags
17854 return 0;
17855 }
17856 return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
17857}
17858
17859} // namespace impl
17860
17861inline ctx_t create_client_context() {
17862 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17863 if (!ctx) { return nullptr; }
17864
17865 ctx->is_server = false;
17866
17867 // Seed the random number generator
17868 const char *pers = "httplib_client";
17869 int ret = mbedtls_ctr_drbg_seed(
17870 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17871 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17872 if (ret != 0) {
17873 impl::mbedtls_last_error() = ret;
17874 delete ctx;
17875 return nullptr;
17876 }
17877
17878 // Set up SSL config for client
17879 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
17880 MBEDTLS_SSL_TRANSPORT_STREAM,
17881 MBEDTLS_SSL_PRESET_DEFAULT);
17882 if (ret != 0) {
17883 impl::mbedtls_last_error() = ret;
17884 delete ctx;
17885 return nullptr;
17886 }
17887
17888 // Set random number generator
17889 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17890
17891 // Default: verify peer certificate
17892 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
17893
17894 // Set minimum TLS version to 1.2
17895#ifdef CPPHTTPLIB_MBEDTLS_V3
17896 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
17897#else
17898 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
17899 MBEDTLS_SSL_MINOR_VERSION_3);
17900#endif
17901
17902 return static_cast<ctx_t>(ctx);
17903}
17904
17905inline ctx_t create_server_context() {
17906 auto ctx = new (std::nothrow) impl::MbedTlsContext();
17907 if (!ctx) { return nullptr; }
17908
17909 ctx->is_server = true;
17910
17911 // Seed the random number generator
17912 const char *pers = "httplib_server";
17913 int ret = mbedtls_ctr_drbg_seed(
17914 &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
17915 reinterpret_cast<const unsigned char *>(pers), strlen(pers));
17916 if (ret != 0) {
17917 impl::mbedtls_last_error() = ret;
17918 delete ctx;
17919 return nullptr;
17920 }
17921
17922 // Set up SSL config for server
17923 ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
17924 MBEDTLS_SSL_TRANSPORT_STREAM,
17925 MBEDTLS_SSL_PRESET_DEFAULT);
17926 if (ret != 0) {
17927 impl::mbedtls_last_error() = ret;
17928 delete ctx;
17929 return nullptr;
17930 }
17931
17932 // Set random number generator
17933 mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
17934
17935 // Default: don't verify client
17936 mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
17937
17938 // Set minimum TLS version to 1.2
17939#ifdef CPPHTTPLIB_MBEDTLS_V3
17940 mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
17941#else
17942 mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
17943 MBEDTLS_SSL_MINOR_VERSION_3);
17944#endif
17945
17946 // Set SNI callback to capture client's SNI hostname
17947 mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr);
17948
17949 return static_cast<ctx_t>(ctx);
17950}
17951
17952inline void free_context(ctx_t ctx) {
17953 if (ctx) { delete static_cast<impl::MbedTlsContext *>(ctx); }
17954}
17955
17956inline bool set_min_version(ctx_t ctx, Version version) {
17957 if (!ctx) { return false; }
17958 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17959
17960#ifdef CPPHTTPLIB_MBEDTLS_V3
17961 // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum
17962 mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2;
17963 if (version >= Version::TLS1_3) {
17964#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
17965 min_ver = MBEDTLS_SSL_VERSION_TLS1_3;
17966#endif
17967 }
17968 mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver);
17969#else
17970 // Mbed TLS 2.x uses major/minor version numbers
17971 int major = MBEDTLS_SSL_MAJOR_VERSION_3;
17972 int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2
17973 if (version >= Version::TLS1_3) {
17974#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
17975 minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3
17976#else
17977 minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2
17978#endif
17979 }
17980 mbedtls_ssl_conf_min_version(&mctx->conf, major, minor);
17981#endif
17982 return true;
17983}
17984
17985inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
17986 if (!ctx || !pem) { return false; }
17987 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
17988
17989 // mbedtls_x509_crt_parse expects null-terminated string for PEM
17990 // Add null terminator if not present
17991 std::string pem_str(pem, len);
17992 int ret = mbedtls_x509_crt_parse(
17993 &mctx->ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
17994 pem_str.size() + 1);
17995 if (ret != 0) {
17996 impl::mbedtls_last_error() = ret;
17997 return false;
17998 }
17999
18000 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18001 return true;
18002}
18003
18004inline bool load_ca_file(ctx_t ctx, const char *file_path) {
18005 if (!ctx || !file_path) { return false; }
18006 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18007
18008 int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path);
18009 if (ret != 0) {
18010 impl::mbedtls_last_error() = ret;
18011 return false;
18012 }
18013
18014 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18015 return true;
18016}
18017
18018inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
18019 if (!ctx || !dir_path) { return false; }
18020 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18021
18022 int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path);
18023 if (ret < 0) { // Returns number of certs on success, negative on error
18024 impl::mbedtls_last_error() = ret;
18025 return false;
18026 }
18027
18028 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18029 return true;
18030}
18031
18032inline bool load_system_certs(ctx_t ctx) {
18033 if (!ctx) { return false; }
18034 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18035 bool loaded = false;
18036
18037#ifdef _WIN32
18038 loaded = impl::enumerate_windows_system_certs(
18039 [&](const unsigned char *data, size_t len) {
18040 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18041 });
18042#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
18043 loaded = impl::enumerate_macos_keychain_certs(
18044 [&](const unsigned char *data, size_t len) {
18045 return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18046 });
18047#else
18048 for (auto path = impl::system_ca_paths(); *path; ++path) {
18049 if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) {
18050 loaded = true;
18051 break;
18052 }
18053 }
18054
18055 if (!loaded) {
18056 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
18057 if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) {
18058 loaded = true;
18059 break;
18060 }
18061 }
18062 }
18063#endif
18064
18065 if (loaded) {
18066 mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18067 }
18068 return loaded;
18069}
18070
18071inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
18072 const char *password) {
18073 if (!ctx || !cert || !key) { return false; }
18074 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18075
18076 // Parse certificate
18077 std::string cert_str(cert);
18078 int ret = mbedtls_x509_crt_parse(
18079 &mctx->own_cert,
18080 reinterpret_cast<const unsigned char *>(cert_str.c_str()),
18081 cert_str.size() + 1);
18082 if (ret != 0) {
18083 impl::mbedtls_last_error() = ret;
18084 return false;
18085 }
18086
18087 // Parse private key
18088 std::string key_str(key);
18089 const unsigned char *pwd =
18090 password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
18091 size_t pwd_len = password ? strlen(password) : 0;
18092
18093#ifdef CPPHTTPLIB_MBEDTLS_V3
18094 ret = mbedtls_pk_parse_key(
18095 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18096 key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
18097 &mctx->ctr_drbg);
18098#else
18099 ret = mbedtls_pk_parse_key(
18100 &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18101 key_str.size() + 1, pwd, pwd_len);
18102#endif
18103 if (ret != 0) {
18104 impl::mbedtls_last_error() = ret;
18105 return false;
18106 }
18107
18108 // Verify that the certificate and private key match
18109#ifdef CPPHTTPLIB_MBEDTLS_V3
18110 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18111 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18112#else
18113 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18114#endif
18115 if (ret != 0) {
18116 impl::mbedtls_last_error() = ret;
18117 return false;
18118 }
18119
18120 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18121 if (ret != 0) {
18122 impl::mbedtls_last_error() = ret;
18123 return false;
18124 }
18125
18126 return true;
18127}
18128
18129inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
18130 const char *key_path, const char *password) {
18131 if (!ctx || !cert_path || !key_path) { return false; }
18132 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18133
18134 // Parse certificate file
18135 int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path);
18136 if (ret != 0) {
18137 impl::mbedtls_last_error() = ret;
18138 return false;
18139 }
18140
18141 // Parse private key file
18142#ifdef CPPHTTPLIB_MBEDTLS_V3
18143 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
18144 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18145#else
18146 ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password);
18147#endif
18148 if (ret != 0) {
18149 impl::mbedtls_last_error() = ret;
18150 return false;
18151 }
18152
18153 // Verify that the certificate and private key match
18154#ifdef CPPHTTPLIB_MBEDTLS_V3
18155 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18156 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18157#else
18158 ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18159#endif
18160 if (ret != 0) {
18161 impl::mbedtls_last_error() = ret;
18162 return false;
18163 }
18164
18165 ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18166 if (ret != 0) {
18167 impl::mbedtls_last_error() = ret;
18168 return false;
18169 }
18170
18171 return true;
18172}
18173
18174inline void set_verify_client(ctx_t ctx, bool require) {
18175 if (!ctx) { return; }
18176 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18177 mctx->verify_client = require;
18178 if (require) {
18179 mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
18180 } else {
18181 // If a verify callback is set, use OPTIONAL mode to ensure the callback
18182 // is called (matching OpenSSL behavior). Otherwise use NONE.
18183 mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback
18184 ? MBEDTLS_SSL_VERIFY_OPTIONAL
18185 : MBEDTLS_SSL_VERIFY_NONE);
18186 }
18187}
18188
18189inline session_t create_session(ctx_t ctx, socket_t sock) {
18190 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
18191 auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18192
18193 auto session = new (std::nothrow) impl::MbedTlsSession();
18194 if (!session) { return nullptr; }
18195
18196 session->sock = sock;
18197
18198 int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf);
18199 if (ret != 0) {
18200 impl::mbedtls_last_error() = ret;
18201 delete session;
18202 return nullptr;
18203 }
18204
18205 // Set BIO callbacks
18206 mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
18207 impl::mbedtls_net_recv_cb, nullptr);
18208
18209 // Set per-session verify callback with session pointer if callback is
18210 // registered
18211 if (mctx->has_verify_callback) {
18212 mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
18213 session);
18214 }
18215
18216 return static_cast<session_t>(session);
18217}
18218
18219inline void free_session(session_t session) {
18220 if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
18221}
18222
18223inline bool set_sni(session_t session, const char *hostname) {
18224 if (!session || !hostname) { return false; }
18225 auto msession = static_cast<impl::MbedTlsSession *>(session);
18226
18227 int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
18228 if (ret != 0) {
18229 impl::mbedtls_last_error() = ret;
18230 return false;
18231 }
18232
18233 msession->hostname = hostname;
18234 return true;
18235}
18236
18237inline bool set_hostname(session_t session, const char *hostname) {
18238 // In Mbed TLS, set_hostname also sets up hostname verification
18239 return set_sni(session, hostname);
18240}
18241
18242inline TlsError connect(session_t session) {
18243 TlsError err;
18244 if (!session) {
18245 err.code = ErrorCode::Fatal;
18246 return err;
18247 }
18248
18249 auto msession = static_cast<impl::MbedTlsSession *>(session);
18250 int ret = mbedtls_ssl_handshake(&msession->ssl);
18251
18252 if (ret == 0) {
18253 err.code = ErrorCode::Success;
18254 } else {
18255 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18256 err.backend_code = static_cast<uint64_t>(-ret);
18257 impl::mbedtls_last_error() = ret;
18258 }
18259
18260 return err;
18261}
18262
18263inline TlsError accept(session_t session) {
18264 // Same as connect for Mbed TLS - handshake works for both client and server
18265 auto result = connect(session);
18266
18267 // After successful handshake, capture SNI from thread-local storage
18268 if (result.code == ErrorCode::Success && session) {
18269 auto msession = static_cast<impl::MbedTlsSession *>(session);
18270 msession->sni_hostname = std::move(impl::mbedpending_sni());
18271 impl::mbedpending_sni().clear();
18272 }
18273
18274 return result;
18275}
18276
18277inline bool connect_nonblocking(session_t session, socket_t sock,
18278 time_t timeout_sec, time_t timeout_usec,
18279 TlsError *err) {
18280 if (!session) {
18281 if (err) { err->code = ErrorCode::Fatal; }
18282 return false;
18283 }
18284
18285 auto msession = static_cast<impl::MbedTlsSession *>(session);
18286
18287 // Set socket to non-blocking mode
18288 detail::set_nonblocking(sock, true);
18289 auto cleanup =
18290 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18291
18292 int ret;
18293 while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
18294 if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
18295 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
18296 continue;
18297 }
18298 } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
18299 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
18300 continue;
18301 }
18302 }
18303
18304 // TlsError or timeout
18305 if (err) {
18306 err->code = impl::map_mbedtls_error(ret, err->sys_errno);
18307 err->backend_code = static_cast<uint64_t>(-ret);
18308 }
18309 impl::mbedtls_last_error() = ret;
18310 return false;
18311 }
18312
18313 if (err) { err->code = ErrorCode::Success; }
18314 return true;
18315}
18316
18317inline bool accept_nonblocking(session_t session, socket_t sock,
18318 time_t timeout_sec, time_t timeout_usec,
18319 TlsError *err) {
18320 // Same implementation as connect for Mbed TLS
18321 bool result =
18322 connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
18323
18324 // After successful handshake, capture SNI from thread-local storage
18325 if (result && session) {
18326 auto msession = static_cast<impl::MbedTlsSession *>(session);
18327 msession->sni_hostname = std::move(impl::mbedpending_sni());
18328 impl::mbedpending_sni().clear();
18329 }
18330
18331 return result;
18332}
18333
18334inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
18335 if (!session || !buf) {
18336 err.code = ErrorCode::Fatal;
18337 return -1;
18338 }
18339
18340 auto msession = static_cast<impl::MbedTlsSession *>(session);
18341 int ret =
18342 mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len);
18343
18344 if (ret > 0) {
18345 err.code = ErrorCode::Success;
18346 return static_cast<ssize_t>(ret);
18347 }
18348
18349 if (ret == 0) {
18350 err.code = ErrorCode::PeerClosed;
18351 return 0;
18352 }
18353
18354 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18355 err.backend_code = static_cast<uint64_t>(-ret);
18356 impl::mbedtls_last_error() = ret;
18357 // mbedTLS signals a clean close_notify via a negative error code rather
18358 // than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do.
18359 if (err.code == ErrorCode::PeerClosed) { return 0; }
18360 return -1;
18361}
18362
18363inline ssize_t write(session_t session, const void *buf, size_t len,
18364 TlsError &err) {
18365 if (!session || !buf) {
18366 err.code = ErrorCode::Fatal;
18367 return -1;
18368 }
18369
18370 auto msession = static_cast<impl::MbedTlsSession *>(session);
18371 int ret = mbedtls_ssl_write(&msession->ssl,
18372 static_cast<const unsigned char *>(buf), len);
18373
18374 if (ret > 0) {
18375 err.code = ErrorCode::Success;
18376 return static_cast<ssize_t>(ret);
18377 }
18378
18379 if (ret == 0) {
18380 err.code = ErrorCode::PeerClosed;
18381 return 0;
18382 }
18383
18384 err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18385 err.backend_code = static_cast<uint64_t>(-ret);
18386 impl::mbedtls_last_error() = ret;
18387 return -1;
18388}
18389
18390inline int pending(const_session_t session) {
18391 if (!session) { return 0; }
18392 auto msession =
18393 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18394 return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl));
18395}
18396
18397inline void shutdown(session_t session, bool graceful) {
18398 if (!session) { return; }
18399 auto msession = static_cast<impl::MbedTlsSession *>(session);
18400
18401 if (graceful) {
18402 // Try to send close_notify, but don't block forever
18403 int ret;
18404 int attempts = 0;
18405 while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 &&
18406 attempts < 3) {
18407 if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
18408 ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
18409 break;
18410 }
18411 attempts++;
18412 }
18413 }
18414}
18415
18416inline bool is_peer_closed(session_t session, socket_t sock) {
18417 if (!session || sock == INVALID_SOCKET) { return true; }
18418 auto msession = static_cast<impl::MbedTlsSession *>(session);
18419
18420 // Check if there's already decrypted data available in the TLS buffer
18421 // If so, the connection is definitely alive
18422 if (mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { return false; }
18423
18424 // Set socket to non-blocking to avoid blocking on read
18425 detail::set_nonblocking(sock, true);
18426 auto cleanup =
18427 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18428
18429 // Try a 1-byte read to check connection status
18430 // Note: This will consume the byte if data is available, but for the
18431 // purpose of checking if peer is closed, this should be acceptable
18432 // since we're only called when we expect the connection might be closing
18433 unsigned char buf;
18434 int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
18435
18436 // If we got data or WANT_READ (would block), connection is alive
18437 if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
18438
18439 // If we get a peer close notify or a connection reset, the peer is closed
18440 return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
18441 ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0;
18442}
18443
18444inline cert_t get_peer_cert(const_session_t session) {
18445 if (!session) { return nullptr; }
18446 auto msession =
18447 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18448
18449 // Mbed TLS returns a pointer to the internal peer cert chain.
18450 // WARNING: This pointer is only valid while the session is active.
18451 // Do not use the certificate after calling free_session().
18452 const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl);
18453 return const_cast<mbedtls_x509_crt *>(cert);
18454}
18455
18456inline void free_cert(cert_t cert) {
18457 // Mbed TLS: peer certificate is owned by the SSL context.
18458 // No-op here, but callers should still call this for cross-backend
18459 // portability.
18460 (void)cert;
18461}
18462
18463inline bool verify_hostname(cert_t cert, const char *hostname) {
18464 if (!cert || !hostname) { return false; }
18465 auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
18466 std::string host_str(hostname);
18467
18468 // Check if hostname is an IP address
18469 bool is_ip = impl::is_ipv4_address(host_str);
18470 unsigned char ip_bytes[4];
18471 if (is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
18472
18473 // Check Subject Alternative Names (SAN)
18474 // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
18475 // - DNS names: raw string bytes
18476 // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
18477 const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
18478 while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
18479 const unsigned char *p = san->buf.p;
18480 size_t len = san->buf.len;
18481
18482 if (is_ip) {
18483 // Check if this SAN is an IPv4 address (4 bytes)
18484 if (len == 4 && memcmp(p, ip_bytes, 4) == 0) { return true; }
18485 // Check if this SAN is an IPv6 address (16 bytes) - skip for now
18486 } else {
18487 // Check if this SAN is a DNS name (printable ASCII string)
18488 bool is_dns = len > 0;
18489 for (size_t i = 0; i < len && is_dns; i++) {
18490 if (p[i] < 32 || p[i] > 126) { is_dns = false; }
18491 }
18492 if (is_dns) {
18493 std::string san_name(reinterpret_cast<const char *>(p), len);
18494 if (detail::match_hostname(san_name, host_str)) { return true; }
18495 }
18496 }
18497 san = san->next;
18498 }
18499
18500 // Fallback: Check Common Name (CN) in subject
18501 char cn[256];
18502 int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18503 if (ret > 0) {
18504 std::string cn_str(cn);
18505
18506 // Look for "CN=" in the DN string
18507 size_t cn_pos = cn_str.find("CN=");
18508 if (cn_pos != std::string::npos) {
18509 size_t start = cn_pos + 3;
18510 size_t end = cn_str.find(',', start);
18511 std::string cn_value =
18512 cn_str.substr(start, end == std::string::npos ? end : end - start);
18513
18514 if (detail::match_hostname(cn_value, host_str)) { return true; }
18515 }
18516 }
18517
18518 return false;
18519}
18520
18521inline uint64_t hostname_mismatch_code() {
18522 return static_cast<uint64_t>(MBEDTLS_X509_BADCERT_CN_MISMATCH);
18523}
18524
18525inline long get_verify_result(const_session_t session) {
18526 if (!session) { return -1; }
18527 auto msession =
18528 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18529 uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl);
18530 // Return 0 (X509_V_OK equivalent) if verification passed
18531 return flags == 0 ? 0 : static_cast<long>(flags);
18532}
18533
18534inline std::string get_cert_subject_cn(cert_t cert) {
18535 if (!cert) return "";
18536 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18537
18538 // Find the CN in the subject
18539 const mbedtls_x509_name *name = &x509->subject;
18540 while (name != nullptr) {
18541 if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
18542 return std::string(reinterpret_cast<const char *>(name->val.p),
18543 name->val.len);
18544 }
18545 name = name->next;
18546 }
18547 return "";
18548}
18549
18550inline std::string get_cert_issuer_name(cert_t cert) {
18551 if (!cert) return "";
18552 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18553
18554 // Build a human-readable issuer name string
18555 char buf[512];
18556 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer);
18557 if (ret < 0) return "";
18558 return std::string(buf);
18559}
18560
18561inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
18562 sans.clear();
18563 if (!cert) return false;
18564 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18565
18566 // Parse the Subject Alternative Name extension
18567 const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
18568 while (cur != nullptr) {
18569 if (cur->buf.len > 0) {
18570 // Mbed TLS stores SAN as ASN.1 sequences
18571 // The tag byte indicates the type
18572 const unsigned char *p = cur->buf.p;
18573 size_t len = cur->buf.len;
18574
18575 // First byte is the tag
18576 unsigned char tag = *p;
18577 p++;
18578 len--;
18579
18580 // Parse length (simple single-byte length assumed)
18581 if (len > 0 && *p < 0x80) {
18582 size_t value_len = *p;
18583 p++;
18584 len--;
18585
18586 if (value_len <= len) {
18587 SanEntry entry;
18588 // ASN.1 context tags for GeneralName
18589 switch (tag & 0x1F) {
18590 case 2: // dNSName
18591 entry.type = SanType::DNS;
18592 entry.value =
18593 std::string(reinterpret_cast<const char *>(p), value_len);
18594 break;
18595 case 7: // iPAddress
18596 entry.type = SanType::IP;
18597 if (value_len == 4) {
18598 // IPv4
18599 char buf[16];
18600 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
18601 entry.value = buf;
18602 } else if (value_len == 16) {
18603 // IPv6
18604 char buf[64];
18605 snprintf(buf, sizeof(buf),
18606 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
18607 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
18608 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
18609 p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
18610 entry.value = buf;
18611 }
18612 break;
18613 case 1: // rfc822Name (email)
18614 entry.type = SanType::EMAIL;
18615 entry.value =
18616 std::string(reinterpret_cast<const char *>(p), value_len);
18617 break;
18618 case 6: // uniformResourceIdentifier
18619 entry.type = SanType::URI;
18620 entry.value =
18621 std::string(reinterpret_cast<const char *>(p), value_len);
18622 break;
18623 default: entry.type = SanType::OTHER; break;
18624 }
18625
18626 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
18627 }
18628 }
18629 }
18630 cur = cur->next;
18631 }
18632 return true;
18633}
18634
18635inline bool get_cert_validity(cert_t cert, time_t &not_before,
18636 time_t &not_after) {
18637 if (!cert) return false;
18638 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18639
18640 // Convert mbedtls_x509_time to time_t
18641 auto to_time_t = [](const mbedtls_x509_time &t) -> time_t {
18642 struct tm tm_time = {};
18643 tm_time.tm_year = t.year - 1900;
18644 tm_time.tm_mon = t.mon - 1;
18645 tm_time.tm_mday = t.day;
18646 tm_time.tm_hour = t.hour;
18647 tm_time.tm_min = t.min;
18648 tm_time.tm_sec = t.sec;
18649#ifdef _WIN32
18650 return _mkgmtime(&tm_time);
18651#else
18652 return timegm(&tm_time);
18653#endif
18654 };
18655
18656 not_before = to_time_t(x509->valid_from);
18657 not_after = to_time_t(x509->valid_to);
18658 return true;
18659}
18660
18661inline std::string get_cert_serial(cert_t cert) {
18662 if (!cert) return "";
18663 auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18664
18665 // Convert serial number to hex string
18666 std::string result;
18667 result.reserve(x509->serial.len * 2);
18668 for (size_t i = 0; i < x509->serial.len; i++) {
18669 char hex[3];
18670 snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]);
18671 result += hex;
18672 }
18673 return result;
18674}
18675
18676inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
18677 if (!cert) return false;
18678 auto crt = static_cast<mbedtls_x509_crt *>(cert);
18679 if (!crt->raw.p || crt->raw.len == 0) return false;
18680 der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
18681 return true;
18682}
18683
18684inline const char *get_sni(const_session_t session) {
18685 if (!session) return nullptr;
18686 auto msession = static_cast<const impl::MbedTlsSession *>(session);
18687
18688 // For server: return SNI received from client during handshake
18689 if (!msession->sni_hostname.empty()) {
18690 return msession->sni_hostname.c_str();
18691 }
18692
18693 // For client: return the hostname set via set_sni
18694 if (!msession->hostname.empty()) { return msession->hostname.c_str(); }
18695
18696 return nullptr;
18697}
18698
18699inline uint64_t peek_error() {
18700 // Mbed TLS doesn't have an error queue, return the last error
18701 return static_cast<uint64_t>(-impl::mbedtls_last_error());
18702}
18703
18704inline uint64_t get_error() {
18705 // Mbed TLS doesn't have an error queue, return and clear the last error
18706 uint64_t err = static_cast<uint64_t>(-impl::mbedtls_last_error());
18707 impl::mbedtls_last_error() = 0;
18708 return err;
18709}
18710
18711inline std::string error_string(uint64_t code) {
18712 char buf[256];
18713 mbedtls_strerror(-static_cast<int>(code), buf, sizeof(buf));
18714 return std::string(buf);
18715}
18716
18717inline ca_store_t create_ca_store(const char *pem, size_t len) {
18718 auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
18719 if (!ca_chain) { return nullptr; }
18720
18721 mbedtls_x509_crt_init(ca_chain);
18722
18723 // mbedtls_x509_crt_parse expects null-terminated PEM
18724 int ret = mbedtls_x509_crt_parse(ca_chain,
18725 reinterpret_cast<const unsigned char *>(pem),
18726 len + 1); // +1 for null terminator
18727 if (ret != 0) {
18728 // Try without +1 in case PEM is already null-terminated
18729 ret = mbedtls_x509_crt_parse(
18730 ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
18731 if (ret != 0) {
18732 mbedtls_x509_crt_free(ca_chain);
18733 delete ca_chain;
18734 return nullptr;
18735 }
18736 }
18737
18738 return static_cast<ca_store_t>(ca_chain);
18739}
18740
18741inline void free_ca_store(ca_store_t store) {
18742 if (store) {
18743 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18744 mbedtls_x509_crt_free(ca_chain);
18745 delete ca_chain;
18746 }
18747}
18748
18749inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
18750 if (!ctx || !store) { return false; }
18751 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18752 auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18753
18754 // Free existing CA chain
18755 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18756 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18757
18758 // Copy the CA chain (deep copy)
18759 // Parse from the raw data of the source cert
18760 mbedtls_x509_crt *src = ca_chain;
18761 while (src != nullptr) {
18762 int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
18763 src->raw.len);
18764 if (ret != 0) { return false; }
18765 src = src->next;
18766 }
18767
18768 // Update the SSL config to use the new CA chain
18769 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18770 return true;
18771}
18772
18773inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
18774 certs.clear();
18775 if (!ctx) { return 0; }
18776 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18777
18778 // Iterate through the CA chain
18779 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18780 while (cert != nullptr && cert->raw.len > 0) {
18781 // Create a copy of the certificate for the caller
18782 auto *copy = new mbedtls_x509_crt;
18783 mbedtls_x509_crt_init(copy);
18784 int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
18785 if (ret == 0) {
18786 certs.push_back(static_cast<cert_t>(copy));
18787 } else {
18788 mbedtls_x509_crt_free(copy);
18789 delete copy;
18790 }
18791 cert = cert->next;
18792 }
18793 return certs.size();
18794}
18795
18796inline std::vector<std::string> get_ca_names(ctx_t ctx) {
18797 std::vector<std::string> names;
18798 if (!ctx) { return names; }
18799 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18800
18801 // Iterate through the CA chain
18802 mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
18803 while (cert != nullptr && cert->raw.len > 0) {
18804 char buf[512];
18805 int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject);
18806 if (ret > 0) { names.push_back(buf); }
18807 cert = cert->next;
18808 }
18809 return names;
18810}
18811
18812inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
18813 const char *key_pem, const char *password) {
18814 if (!ctx || !cert_pem || !key_pem) { return false; }
18815 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18816
18817 // Free existing certificate and key
18818 mbedtls_x509_crt_free(&mbed_ctx->own_cert);
18819 mbedtls_pk_free(&mbed_ctx->own_key);
18820 mbedtls_x509_crt_init(&mbed_ctx->own_cert);
18821 mbedtls_pk_init(&mbed_ctx->own_key);
18822
18823 // Parse certificate PEM
18824 int ret = mbedtls_x509_crt_parse(
18825 &mbed_ctx->own_cert, reinterpret_cast<const unsigned char *>(cert_pem),
18826 strlen(cert_pem) + 1);
18827 if (ret != 0) {
18828 impl::mbedtls_last_error() = ret;
18829 return false;
18830 }
18831
18832 // Parse private key PEM
18833#ifdef CPPHTTPLIB_MBEDTLS_V3
18834 ret = mbedtls_pk_parse_key(
18835 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18836 strlen(key_pem) + 1,
18837 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18838 password ? strlen(password) : 0, mbedtls_ctr_drbg_random,
18839 &mbed_ctx->ctr_drbg);
18840#else
18841 ret = mbedtls_pk_parse_key(
18842 &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
18843 strlen(key_pem) + 1,
18844 password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
18845 password ? strlen(password) : 0);
18846#endif
18847 if (ret != 0) {
18848 impl::mbedtls_last_error() = ret;
18849 return false;
18850 }
18851
18852 // Configure SSL to use the new certificate and key
18853 ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert,
18854 &mbed_ctx->own_key);
18855 if (ret != 0) {
18856 impl::mbedtls_last_error() = ret;
18857 return false;
18858 }
18859
18860 return true;
18861}
18862
18863inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
18864 if (!ctx || !ca_pem) { return false; }
18865 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18866
18867 // Free existing CA chain
18868 mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18869 mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18870
18871 // Parse CA PEM
18872 int ret = mbedtls_x509_crt_parse(
18873 &mbed_ctx->ca_chain, reinterpret_cast<const unsigned char *>(ca_pem),
18874 strlen(ca_pem) + 1);
18875 if (ret != 0) {
18876 impl::mbedtls_last_error() = ret;
18877 return false;
18878 }
18879
18880 // Update SSL config to use new CA chain
18881 mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
18882 return true;
18883}
18884
18885inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
18886 if (!ctx) { return false; }
18887 auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18888
18889 impl::get_verify_callback() = std::move(callback);
18890 mbed_ctx->has_verify_callback =
18891 static_cast<bool>(impl::get_verify_callback());
18892
18893 if (mbed_ctx->has_verify_callback) {
18894 // Set OPTIONAL mode to ensure callback is called even when verification
18895 // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
18896 mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
18897 mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
18898 nullptr);
18899 } else {
18900 mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
18901 }
18902 return true;
18903}
18904
18905inline long get_verify_error(const_session_t session) {
18906 if (!session) { return -1; }
18907 auto *msession =
18908 static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18909 return static_cast<long>(mbedtls_ssl_get_verify_result(&msession->ssl));
18910}
18911
18912inline std::string verify_error_string(long error_code) {
18913 if (error_code == 0) { return ""; }
18914 char buf[256];
18915 mbedtls_x509_crt_verify_info(buf, sizeof(buf), "",
18916 static_cast<uint32_t>(error_code));
18917 // Remove trailing newline if present
18918 std::string result(buf);
18919 while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) {
18920 result.pop_back();
18921 }
18922 return result;
18923}
18924
18925} // namespace tls
18926
18927#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
18928
18929/*
18930 * Group 10: TLS abstraction layer - wolfSSL backend
18931 */
18932
18933/*
18934 * wolfSSL Backend Implementation
18935 */
18936
18937#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
18938namespace tls {
18939
18940namespace impl {
18941
18942// wolfSSL session wrapper
18943struct WolfSSLSession {
18944 WOLFSSL *ssl = nullptr;
18945 socket_t sock = INVALID_SOCKET;
18946 std::string hostname; // For client: set via set_sni
18947 std::string sni_hostname; // For server: received from client via SNI callback
18948
18949 WolfSSLSession() = default;
18950
18951 ~WolfSSLSession() {
18952 if (ssl) { wolfSSL_free(ssl); }
18953 }
18954
18955 WolfSSLSession(const WolfSSLSession &) = delete;
18956 WolfSSLSession &operator=(const WolfSSLSession &) = delete;
18957};
18958
18959// Thread-local error code accessor for wolfSSL
18960inline uint64_t &wolfssl_last_error() {
18961 static thread_local uint64_t err = 0;
18962 return err;
18963}
18964
18965// Helper to map wolfSSL error to ErrorCode.
18966// ssl_error is the value from wolfSSL_get_error().
18967// raw_ret is the raw return value from the wolfSSL call (for low-level error).
18968inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
18969 int &out_errno) {
18970 switch (ssl_error) {
18971 case SSL_ERROR_NONE: return ErrorCode::Success;
18972 case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
18973 case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
18974 case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
18975 case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
18976 default:
18977 if (ssl) {
18978 // wolfSSL stores the low-level error code as a negative value.
18979 // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure.
18980 int low_err = ssl_error; // wolfSSL_get_error returns the low-level code
18981 if (low_err == DOMAIN_NAME_MISMATCH) {
18982 return ErrorCode::HostnameMismatch;
18983 }
18984 // Check verify result to distinguish cert verification from generic SSL
18985 // errors.
18986 long vr = wolfSSL_get_verify_result(ssl);
18987 if (vr != 0) { return ErrorCode::CertVerifyFailed; }
18988 }
18989 return ErrorCode::Fatal;
18990 }
18991}
18992
18993// WolfSSLContext constructor/destructor implementations
18994inline WolfSSLContext::WolfSSLContext() { wolfSSL_Init(); }
18995
18996inline WolfSSLContext::~WolfSSLContext() {
18997 if (ctx) { wolfSSL_CTX_free(ctx); }
18998}
18999
19000// Thread-local storage for SNI captured during handshake
19001inline std::string &wolfssl_pending_sni() {
19002 static thread_local std::string sni;
19003 return sni;
19004}
19005
19006// SNI callback for wolfSSL server to capture client's SNI hostname
19007inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
19008 (void)ret;
19009 (void)exArg;
19010
19011 void *name_data = nullptr;
19012 unsigned short name_len =
19013 wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data);
19014
19015 if (name_data && name_len > 0) {
19016 wolfssl_pending_sni().assign(static_cast<const char *>(name_data),
19017 name_len);
19018 } else {
19019 wolfssl_pending_sni().clear();
19020 }
19021 return 0; // Continue regardless
19022}
19023
19024// wolfSSL verify callback wrapper
19025inline int wolfssl_verify_callback(int preverify_ok,
19026 WOLFSSL_X509_STORE_CTX *x509_ctx) {
19027 auto &callback = get_verify_callback();
19028 if (!callback) { return preverify_ok; }
19029
19030 WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
19031 int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
19032 int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx);
19033
19034 // Get the WOLFSSL object from the X509_STORE_CTX
19035 WOLFSSL *ssl = static_cast<WOLFSSL *>(wolfSSL_X509_STORE_CTX_get_ex_data(
19036 x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()));
19037
19038 VerifyContext verify_ctx;
19039 verify_ctx.session = static_cast<session_t>(ssl);
19040 verify_ctx.cert = static_cast<cert_t>(cert);
19041 verify_ctx.depth = depth;
19042 verify_ctx.preverify_ok = (preverify_ok != 0);
19043 verify_ctx.error_code = static_cast<long>(err);
19044
19045 if (err != 0) {
19046 verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err);
19047 } else {
19048 verify_ctx.error_string = nullptr;
19049 }
19050
19051 bool accepted = callback(verify_ctx);
19052 return accepted ? 1 : 0;
19053}
19054
19055inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
19056 wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
19057 wolfSSL_CTX_set_default_passwd_cb(
19058 ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int {
19059 auto *pwd = static_cast<const char *>(userdata);
19060 if (!pwd) return 0;
19061 auto len = static_cast<int>(strlen(pwd));
19062 if (len > size) len = size;
19063 memcpy(buf, pwd, static_cast<size_t>(len));
19064 return len;
19065 });
19066}
19067
19068} // namespace impl
19069
19070inline ctx_t create_client_context() {
19071 auto ctx = new (std::nothrow) impl::WolfSSLContext();
19072 if (!ctx) { return nullptr; }
19073
19074 ctx->is_server = false;
19075
19076 WOLFSSL_METHOD *method = wolfTLSv1_2_client_method();
19077 if (!method) {
19078 delete ctx;
19079 return nullptr;
19080 }
19081
19082 ctx->ctx = wolfSSL_CTX_new(method);
19083 if (!ctx->ctx) {
19084 delete ctx;
19085 return nullptr;
19086 }
19087
19088 // Default: verify peer certificate
19089 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
19090
19091 return static_cast<ctx_t>(ctx);
19092}
19093
19094inline ctx_t create_server_context() {
19095 auto ctx = new (std::nothrow) impl::WolfSSLContext();
19096 if (!ctx) { return nullptr; }
19097
19098 ctx->is_server = true;
19099
19100 WOLFSSL_METHOD *method = wolfTLSv1_2_server_method();
19101 if (!method) {
19102 delete ctx;
19103 return nullptr;
19104 }
19105
19106 ctx->ctx = wolfSSL_CTX_new(method);
19107 if (!ctx->ctx) {
19108 delete ctx;
19109 return nullptr;
19110 }
19111
19112 // Default: don't verify client
19113 wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
19114
19115 // Enable SNI on server
19116 wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
19117 WOLFSSL_SNI_CONTINUE_ON_MISMATCH);
19118 wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback);
19119
19120 return static_cast<ctx_t>(ctx);
19121}
19122
19123inline void free_context(ctx_t ctx) {
19124 if (ctx) { delete static_cast<impl::WolfSSLContext *>(ctx); }
19125}
19126
19127inline bool set_min_version(ctx_t ctx, Version version) {
19128 if (!ctx) { return false; }
19129 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19130
19131 int min_ver = WOLFSSL_TLSV1_2;
19132 if (version >= Version::TLS1_3) { min_ver = WOLFSSL_TLSV1_3; }
19133
19134 return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS;
19135}
19136
19137inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
19138 if (!ctx || !pem) { return false; }
19139 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19140
19141 int ret = wolfSSL_CTX_load_verify_buffer(
19142 wctx->ctx, reinterpret_cast<const unsigned char *>(pem),
19143 static_cast<long>(len), SSL_FILETYPE_PEM);
19144 if (ret != SSL_SUCCESS) {
19145 impl::wolfssl_last_error() =
19146 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19147 return false;
19148 }
19149 wctx->ca_pem_data_.append(pem, len);
19150 return true;
19151}
19152
19153inline bool load_ca_file(ctx_t ctx, const char *file_path) {
19154 if (!ctx || !file_path) { return false; }
19155 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19156
19157 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr);
19158 if (ret != SSL_SUCCESS) {
19159 impl::wolfssl_last_error() =
19160 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19161 return false;
19162 }
19163 return true;
19164}
19165
19166inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
19167 if (!ctx || !dir_path) { return false; }
19168 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19169
19170 int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path);
19171 // wolfSSL may fail if the directory doesn't contain properly hashed certs.
19172 // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans
19173 // immediately. Return true even on failure since the CA file may have
19174 // already been loaded, matching OpenSSL's lenient behavior.
19175 (void)ret;
19176 return true;
19177}
19178
19179inline bool load_system_certs(ctx_t ctx) {
19180 if (!ctx) { return false; }
19181 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19182 bool loaded = false;
19183
19184#ifdef _WIN32
19185 loaded = impl::enumerate_windows_system_certs(
19186 [&](const unsigned char *data, size_t len) {
19187 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19188 static_cast<long>(len),
19189 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19190 });
19191#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
19192 loaded = impl::enumerate_macos_keychain_certs(
19193 [&](const unsigned char *data, size_t len) {
19194 return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19195 static_cast<long>(len),
19196 SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19197 });
19198#else
19199 for (auto path = impl::system_ca_paths(); *path; ++path) {
19200 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) ==
19201 SSL_SUCCESS) {
19202 loaded = true;
19203 break;
19204 }
19205 }
19206
19207 if (!loaded) {
19208 for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
19209 if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) ==
19210 SSL_SUCCESS) {
19211 loaded = true;
19212 break;
19213 }
19214 }
19215 }
19216#endif
19217
19218 return loaded;
19219}
19220
19221inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
19222 const char *password) {
19223 if (!ctx || !cert || !key) { return false; }
19224 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19225
19226 // Load certificate
19227 int ret = wolfSSL_CTX_use_certificate_buffer(
19228 wctx->ctx, reinterpret_cast<const unsigned char *>(cert),
19229 static_cast<long>(strlen(cert)), SSL_FILETYPE_PEM);
19230 if (ret != SSL_SUCCESS) {
19231 impl::wolfssl_last_error() =
19232 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19233 return false;
19234 }
19235
19236 // Set password callback if password is provided
19237 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19238
19239 // Load private key
19240 ret = wolfSSL_CTX_use_PrivateKey_buffer(
19241 wctx->ctx, reinterpret_cast<const unsigned char *>(key),
19242 static_cast<long>(strlen(key)), SSL_FILETYPE_PEM);
19243 if (ret != SSL_SUCCESS) {
19244 impl::wolfssl_last_error() =
19245 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19246 return false;
19247 }
19248
19249 // Verify that the certificate and private key match
19250 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19251}
19252
19253inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
19254 const char *key_path, const char *password) {
19255 if (!ctx || !cert_path || !key_path) { return false; }
19256 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19257
19258 // Load certificate file
19259 int ret =
19260 wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM);
19261 if (ret != SSL_SUCCESS) {
19262 impl::wolfssl_last_error() =
19263 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19264 return false;
19265 }
19266
19267 // Set password callback if password is provided
19268 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19269
19270 // Load private key file
19271 ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM);
19272 if (ret != SSL_SUCCESS) {
19273 impl::wolfssl_last_error() =
19274 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19275 return false;
19276 }
19277
19278 // Verify that the certificate and private key match
19279 return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19280}
19281
19282inline void set_verify_client(ctx_t ctx, bool require) {
19283 if (!ctx) { return; }
19284 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19285 wctx->verify_client = require;
19286 if (require) {
19287 wolfSSL_CTX_set_verify(
19288 wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
19289 wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr);
19290 } else {
19291 if (wctx->has_verify_callback) {
19292 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
19293 impl::wolfssl_verify_callback);
19294 } else {
19295 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
19296 }
19297 }
19298}
19299
19300inline session_t create_session(ctx_t ctx, socket_t sock) {
19301 if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
19302 auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19303
19304 auto session = new (std::nothrow) impl::WolfSSLSession();
19305 if (!session) { return nullptr; }
19306
19307 session->sock = sock;
19308 session->ssl = wolfSSL_new(wctx->ctx);
19309 if (!session->ssl) {
19310 impl::wolfssl_last_error() =
19311 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19312 delete session;
19313 return nullptr;
19314 }
19315
19316 wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
19317
19318 return static_cast<session_t>(session);
19319}
19320
19321inline void free_session(session_t session) {
19322 if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
19323}
19324
19325inline bool set_sni(session_t session, const char *hostname) {
19326 if (!session || !hostname) { return false; }
19327 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19328
19329 int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname,
19330 static_cast<word16>(strlen(hostname)));
19331 if (ret != WOLFSSL_SUCCESS) {
19332 impl::wolfssl_last_error() =
19333 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19334 return false;
19335 }
19336
19337 // Also set hostname for verification
19338 wolfSSL_check_domain_name(wsession->ssl, hostname);
19339
19340 wsession->hostname = hostname;
19341 return true;
19342}
19343
19344inline bool set_hostname(session_t session, const char *hostname) {
19345 // In wolfSSL, set_hostname also sets up hostname verification
19346 return set_sni(session, hostname);
19347}
19348
19349inline TlsError connect(session_t session) {
19350 TlsError err;
19351 if (!session) {
19352 err.code = ErrorCode::Fatal;
19353 return err;
19354 }
19355
19356 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19357 int ret = wolfSSL_connect(wsession->ssl);
19358
19359 if (ret == SSL_SUCCESS) {
19360 err.code = ErrorCode::Success;
19361 } else {
19362 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19363 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19364 err.backend_code = static_cast<uint64_t>(ssl_error);
19365 impl::wolfssl_last_error() = err.backend_code;
19366 }
19367
19368 return err;
19369}
19370
19371inline TlsError accept(session_t session) {
19372 TlsError err;
19373 if (!session) {
19374 err.code = ErrorCode::Fatal;
19375 return err;
19376 }
19377
19378 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19379 int ret = wolfSSL_accept(wsession->ssl);
19380
19381 if (ret == SSL_SUCCESS) {
19382 err.code = ErrorCode::Success;
19383 // Capture SNI from thread-local storage after successful handshake
19384 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19385 impl::wolfssl_pending_sni().clear();
19386 } else {
19387 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19388 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19389 err.backend_code = static_cast<uint64_t>(ssl_error);
19390 impl::wolfssl_last_error() = err.backend_code;
19391 }
19392
19393 return err;
19394}
19395
19396inline bool connect_nonblocking(session_t session, socket_t sock,
19397 time_t timeout_sec, time_t timeout_usec,
19398 TlsError *err) {
19399 if (!session) {
19400 if (err) { err->code = ErrorCode::Fatal; }
19401 return false;
19402 }
19403
19404 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19405
19406 // Set socket to non-blocking mode
19407 detail::set_nonblocking(sock, true);
19408 auto cleanup =
19409 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19410
19411 int ret;
19412 while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) {
19413 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19414 if (ssl_error == SSL_ERROR_WANT_READ) {
19415 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19416 continue;
19417 }
19418 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19419 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19420 continue;
19421 }
19422 }
19423
19424 // Error or timeout
19425 if (err) {
19426 err->code =
19427 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19428 err->backend_code = static_cast<uint64_t>(ssl_error);
19429 }
19430 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19431 return false;
19432 }
19433
19434 if (err) { err->code = ErrorCode::Success; }
19435 return true;
19436}
19437
19438inline bool accept_nonblocking(session_t session, socket_t sock,
19439 time_t timeout_sec, time_t timeout_usec,
19440 TlsError *err) {
19441 if (!session) {
19442 if (err) { err->code = ErrorCode::Fatal; }
19443 return false;
19444 }
19445
19446 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19447
19448 // Set socket to non-blocking mode
19449 detail::set_nonblocking(sock, true);
19450 auto cleanup =
19451 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19452
19453 int ret;
19454 while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) {
19455 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19456 if (ssl_error == SSL_ERROR_WANT_READ) {
19457 if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19458 continue;
19459 }
19460 } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19461 if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19462 continue;
19463 }
19464 }
19465
19466 // Error or timeout
19467 if (err) {
19468 err->code =
19469 impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19470 err->backend_code = static_cast<uint64_t>(ssl_error);
19471 }
19472 impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19473 return false;
19474 }
19475
19476 if (err) { err->code = ErrorCode::Success; }
19477
19478 // Capture SNI from thread-local storage after successful handshake
19479 wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19480 impl::wolfssl_pending_sni().clear();
19481
19482 return true;
19483}
19484
19485inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
19486 if (!session || !buf) {
19487 err.code = ErrorCode::Fatal;
19488 return -1;
19489 }
19490
19491 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19492 int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
19493
19494 if (ret > 0) {
19495 err.code = ErrorCode::Success;
19496 return static_cast<ssize_t>(ret);
19497 }
19498
19499 if (ret == 0) {
19500 err.code = ErrorCode::PeerClosed;
19501 return 0;
19502 }
19503
19504 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19505 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19506 err.backend_code = static_cast<uint64_t>(ssl_error);
19507 impl::wolfssl_last_error() = err.backend_code;
19508 return -1;
19509}
19510
19511inline ssize_t write(session_t session, const void *buf, size_t len,
19512 TlsError &err) {
19513 if (!session || !buf) {
19514 err.code = ErrorCode::Fatal;
19515 return -1;
19516 }
19517
19518 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19519 int ret = wolfSSL_write(wsession->ssl, buf, static_cast<int>(len));
19520
19521 if (ret > 0) {
19522 err.code = ErrorCode::Success;
19523 return static_cast<ssize_t>(ret);
19524 }
19525
19526 // wolfSSL_write returns 0 when the peer has sent a close_notify.
19527 // Treat this as an error (return -1) so callers don't spin in a
19528 // write loop adding zero to the offset.
19529 if (ret == 0) {
19530 err.code = ErrorCode::PeerClosed;
19531 return -1;
19532 }
19533
19534 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19535 err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19536 err.backend_code = static_cast<uint64_t>(ssl_error);
19537 impl::wolfssl_last_error() = err.backend_code;
19538 return -1;
19539}
19540
19541inline int pending(const_session_t session) {
19542 if (!session) { return 0; }
19543 auto wsession =
19544 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19545 return wolfSSL_pending(wsession->ssl);
19546}
19547
19548inline void shutdown(session_t session, bool graceful) {
19549 if (!session) { return; }
19550 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19551
19552 if (graceful) {
19553 int ret;
19554 int attempts = 0;
19555 while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS &&
19556 attempts < 3) {
19557 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19558 if (ssl_error != SSL_ERROR_WANT_READ &&
19559 ssl_error != SSL_ERROR_WANT_WRITE) {
19560 break;
19561 }
19562 attempts++;
19563 }
19564 } else {
19565 wolfSSL_shutdown(wsession->ssl);
19566 }
19567}
19568
19569inline bool is_peer_closed(session_t session, socket_t sock) {
19570 if (!session || sock == INVALID_SOCKET) { return true; }
19571 auto wsession = static_cast<impl::WolfSSLSession *>(session);
19572
19573 // Check if there's already decrypted data available
19574 if (wolfSSL_pending(wsession->ssl) > 0) { return false; }
19575
19576 // Set socket to non-blocking to avoid blocking on read
19577 detail::set_nonblocking(sock, true);
19578 auto cleanup =
19579 detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19580
19581 // Peek 1 byte to check connection status without consuming data
19582 unsigned char buf;
19583 int ret = wolfSSL_peek(wsession->ssl, &buf, 1);
19584
19585 // If we got data or WANT_READ (would block), connection is alive
19586 if (ret > 0) { return false; }
19587
19588 int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19589 if (ssl_error == SSL_ERROR_WANT_READ) { return false; }
19590
19591 return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL ||
19592 ret == 0;
19593}
19594
19595inline cert_t get_peer_cert(const_session_t session) {
19596 if (!session) { return nullptr; }
19597 auto wsession =
19598 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19599
19600 WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl);
19601 return static_cast<cert_t>(cert);
19602}
19603
19604inline void free_cert(cert_t cert) {
19605 if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
19606}
19607
19608inline bool verify_hostname(cert_t cert, const char *hostname) {
19609 if (!cert || !hostname) { return false; }
19610 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19611 std::string host_str(hostname);
19612
19613 // Check if hostname is an IP address
19614 bool is_ip = impl::is_ipv4_address(host_str);
19615 unsigned char ip_bytes[4];
19616 if (is_ip) { impl::parse_ipv4(host_str, ip_bytes); }
19617
19618 // Check Subject Alternative Names
19619 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19620 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19621
19622 if (san_names) {
19623 int san_count = wolfSSL_sk_num(san_names);
19624 for (int i = 0; i < san_count; i++) {
19625 auto *names =
19626 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19627 if (!names) continue;
19628
19629 if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
19630 // DNS name
19631 unsigned char *dns_name = nullptr;
19632 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
19633 if (dns_name && dns_len > 0) {
19634 std::string san_name(reinterpret_cast<char *>(dns_name),
19635 static_cast<size_t>(dns_len));
19636 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19637 if (detail::match_hostname(san_name, host_str)) {
19638 wolfSSL_sk_free(san_names);
19639 return true;
19640 }
19641 }
19642 } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
19643 // IP address
19644 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
19645 int ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19646 if (ip_data && ip_len == 4 && memcmp(ip_data, ip_bytes, 4) == 0) {
19647 wolfSSL_sk_free(san_names);
19648 return true;
19649 }
19650 }
19651 }
19652 wolfSSL_sk_free(san_names);
19653 }
19654
19655 // Fallback: Check Common Name (CN) in subject
19656 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19657 if (subject) {
19658 char cn[256] = {};
19659 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19660 sizeof(cn));
19661 if (cn_len > 0) {
19662 std::string cn_str(cn, static_cast<size_t>(cn_len));
19663 if (detail::match_hostname(cn_str, host_str)) { return true; }
19664 }
19665 }
19666
19667 return false;
19668}
19669
19670inline uint64_t hostname_mismatch_code() {
19671 return static_cast<uint64_t>(DOMAIN_NAME_MISMATCH);
19672}
19673
19674inline long get_verify_result(const_session_t session) {
19675 if (!session) { return -1; }
19676 auto wsession =
19677 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19678 long result = wolfSSL_get_verify_result(wsession->ssl);
19679 return result;
19680}
19681
19682inline std::string get_cert_subject_cn(cert_t cert) {
19683 if (!cert) return "";
19684 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19685
19686 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19687 if (!subject) return "";
19688
19689 char cn[256] = {};
19690 int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19691 sizeof(cn));
19692 if (cn_len <= 0) return "";
19693 return std::string(cn, static_cast<size_t>(cn_len));
19694}
19695
19696inline std::string get_cert_issuer_name(cert_t cert) {
19697 if (!cert) return "";
19698 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19699
19700 WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509);
19701 if (!issuer) return "";
19702
19703 char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0);
19704 if (!name_str) return "";
19705
19706 std::string result(name_str);
19707 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19708 return result;
19709}
19710
19711inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
19712 sans.clear();
19713 if (!cert) return false;
19714 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19715
19716 auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19717 wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19718 if (!san_names) return true; // No SANs is not an error
19719
19720 int count = wolfSSL_sk_num(san_names);
19721 for (int i = 0; i < count; i++) {
19722 auto *name =
19723 static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19724 if (!name) continue;
19725
19726 SanEntry entry;
19727 switch (name->type) {
19728 case WOLFSSL_GEN_DNS: {
19729 entry.type = SanType::DNS;
19730 unsigned char *dns_name = nullptr;
19731 int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName);
19732 if (dns_name && dns_len > 0) {
19733 entry.value = std::string(reinterpret_cast<char *>(dns_name),
19734 static_cast<size_t>(dns_len));
19735 XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19736 }
19737 break;
19738 }
19739 case WOLFSSL_GEN_IPADD: {
19740 entry.type = SanType::IP;
19741 unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress);
19742 int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress);
19743 if (ip_data && ip_len == 4) {
19744 char buf[16];
19745 snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1],
19746 ip_data[2], ip_data[3]);
19747 entry.value = buf;
19748 } else if (ip_data && ip_len == 16) {
19749 char buf[64];
19750 snprintf(buf, sizeof(buf),
19751 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
19752 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
19753 ip_data[0], ip_data[1], ip_data[2], ip_data[3], ip_data[4],
19754 ip_data[5], ip_data[6], ip_data[7], ip_data[8], ip_data[9],
19755 ip_data[10], ip_data[11], ip_data[12], ip_data[13],
19756 ip_data[14], ip_data[15]);
19757 entry.value = buf;
19758 }
19759 break;
19760 }
19761 case WOLFSSL_GEN_EMAIL:
19762 entry.type = SanType::EMAIL;
19763 {
19764 unsigned char *email = nullptr;
19765 int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name);
19766 if (email && email_len > 0) {
19767 entry.value = std::string(reinterpret_cast<char *>(email),
19768 static_cast<size_t>(email_len));
19769 XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL);
19770 }
19771 }
19772 break;
19773 case WOLFSSL_GEN_URI:
19774 entry.type = SanType::URI;
19775 {
19776 unsigned char *uri = nullptr;
19777 int uri_len = wolfSSL_ASN1_STRING_to_UTF8(
19778 &uri, name->d.uniformResourceIdentifier);
19779 if (uri && uri_len > 0) {
19780 entry.value = std::string(reinterpret_cast<char *>(uri),
19781 static_cast<size_t>(uri_len));
19782 XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL);
19783 }
19784 }
19785 break;
19786 default: entry.type = SanType::OTHER; break;
19787 }
19788
19789 if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
19790 }
19791 wolfSSL_sk_free(san_names);
19792 return true;
19793}
19794
19795inline bool get_cert_validity(cert_t cert, time_t &not_before,
19796 time_t &not_after) {
19797 if (!cert) return false;
19798 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19799
19800 const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509);
19801 const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509);
19802
19803 if (!nb || !na) return false;
19804
19805 // wolfSSL_ASN1_TIME_to_tm is available
19806 struct tm tm_nb = {}, tm_na = {};
19807 if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) return false;
19808 if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) return false;
19809
19810#ifdef _WIN32
19811 not_before = _mkgmtime(&tm_nb);
19812 not_after = _mkgmtime(&tm_na);
19813#else
19814 not_before = timegm(&tm_nb);
19815 not_after = timegm(&tm_na);
19816#endif
19817 return true;
19818}
19819
19820inline std::string get_cert_serial(cert_t cert) {
19821 if (!cert) return "";
19822 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19823
19824 WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509);
19825 if (!serial_asn1) return "";
19826
19827 // Get the serial number data
19828 int len = serial_asn1->length;
19829 unsigned char *data = serial_asn1->data;
19830 if (!data || len <= 0) return "";
19831
19832 std::string result;
19833 result.reserve(static_cast<size_t>(len) * 2);
19834 for (int i = 0; i < len; i++) {
19835 char hex[3];
19836 snprintf(hex, sizeof(hex), "%02X", data[i]);
19837 result += hex;
19838 }
19839 return result;
19840}
19841
19842inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
19843 if (!cert) return false;
19844 auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19845
19846 int der_len = 0;
19847 const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len);
19848 if (!der_data || der_len <= 0) return false;
19849
19850 der.assign(der_data, der_data + der_len);
19851 return true;
19852}
19853
19854inline const char *get_sni(const_session_t session) {
19855 if (!session) return nullptr;
19856 auto wsession = static_cast<const impl::WolfSSLSession *>(session);
19857
19858 // For server: return SNI received from client during handshake
19859 if (!wsession->sni_hostname.empty()) {
19860 return wsession->sni_hostname.c_str();
19861 }
19862
19863 // For client: return the hostname set via set_sni
19864 if (!wsession->hostname.empty()) { return wsession->hostname.c_str(); }
19865
19866 return nullptr;
19867}
19868
19869inline uint64_t peek_error() {
19870 return static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19871}
19872
19873inline uint64_t get_error() {
19874 uint64_t err = impl::wolfssl_last_error();
19875 impl::wolfssl_last_error() = 0;
19876 return err;
19877}
19878
19879inline std::string error_string(uint64_t code) {
19880 char buf[256];
19881 wolfSSL_ERR_error_string(static_cast<unsigned long>(code), buf);
19882 return std::string(buf);
19883}
19884
19885inline ca_store_t create_ca_store(const char *pem, size_t len) {
19886 if (!pem || len == 0) { return nullptr; }
19887 // Validate by attempting to load into a temporary ctx
19888 WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
19889 if (!tmp_ctx) { return nullptr; }
19890 int ret = wolfSSL_CTX_load_verify_buffer(
19891 tmp_ctx, reinterpret_cast<const unsigned char *>(pem),
19892 static_cast<long>(len), SSL_FILETYPE_PEM);
19893 wolfSSL_CTX_free(tmp_ctx);
19894 if (ret != SSL_SUCCESS) { return nullptr; }
19895 return static_cast<ca_store_t>(
19896 new impl::WolfSSLCAStore{std::string(pem, len)});
19897}
19898
19899inline void free_ca_store(ca_store_t store) {
19900 delete static_cast<impl::WolfSSLCAStore *>(store);
19901}
19902
19903inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
19904 if (!ctx || !store) { return false; }
19905 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19906 auto *ca = static_cast<impl::WolfSSLCAStore *>(store);
19907 int ret = wolfSSL_CTX_load_verify_buffer(
19908 wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
19909 static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
19910 if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
19911 return ret == SSL_SUCCESS;
19912}
19913
19914inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
19915 certs.clear();
19916 if (!ctx) { return 0; }
19917 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19918 if (wctx->ca_pem_data_.empty()) { return 0; }
19919
19920 const std::string &pem = wctx->ca_pem_data_;
19921 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
19922 const std::string end_marker = "-----END CERTIFICATE-----";
19923 size_t pos = 0;
19924 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
19925 size_t end_pos = pem.find(end_marker, pos);
19926 if (end_pos == std::string::npos) { break; }
19927 end_pos += end_marker.size();
19928 std::string cert_pem = pem.substr(pos, end_pos - pos);
19929 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
19930 reinterpret_cast<const unsigned char *>(cert_pem.data()),
19931 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
19932 if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
19933 pos = end_pos;
19934 }
19935 return certs.size();
19936}
19937
19938inline std::vector<std::string> get_ca_names(ctx_t ctx) {
19939 std::vector<std::string> names;
19940 if (!ctx) { return names; }
19941 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19942 if (wctx->ca_pem_data_.empty()) { return names; }
19943
19944 const std::string &pem = wctx->ca_pem_data_;
19945 const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
19946 const std::string end_marker = "-----END CERTIFICATE-----";
19947 size_t pos = 0;
19948 while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
19949 size_t end_pos = pem.find(end_marker, pos);
19950 if (end_pos == std::string::npos) { break; }
19951 end_pos += end_marker.size();
19952 std::string cert_pem = pem.substr(pos, end_pos - pos);
19953 WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
19954 reinterpret_cast<const unsigned char *>(cert_pem.data()),
19955 static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
19956 if (x509) {
19957 WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19958 if (subject) {
19959 char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0);
19960 if (name_str) {
19961 names.push_back(name_str);
19962 XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19963 }
19964 }
19965 wolfSSL_X509_free(x509);
19966 }
19967 pos = end_pos;
19968 }
19969 return names;
19970}
19971
19972inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
19973 const char *key_pem, const char *password) {
19974 if (!ctx || !cert_pem || !key_pem) { return false; }
19975 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
19976
19977 // Load new certificate
19978 int ret = wolfSSL_CTX_use_certificate_buffer(
19979 wctx->ctx, reinterpret_cast<const unsigned char *>(cert_pem),
19980 static_cast<long>(strlen(cert_pem)), SSL_FILETYPE_PEM);
19981 if (ret != SSL_SUCCESS) {
19982 impl::wolfssl_last_error() =
19983 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19984 return false;
19985 }
19986
19987 // Set password if provided
19988 if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19989
19990 // Load new private key
19991 ret = wolfSSL_CTX_use_PrivateKey_buffer(
19992 wctx->ctx, reinterpret_cast<const unsigned char *>(key_pem),
19993 static_cast<long>(strlen(key_pem)), SSL_FILETYPE_PEM);
19994 if (ret != SSL_SUCCESS) {
19995 impl::wolfssl_last_error() =
19996 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19997 return false;
19998 }
19999
20000 return true;
20001}
20002
20003inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
20004 if (!ctx || !ca_pem) { return false; }
20005 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20006
20007 int ret = wolfSSL_CTX_load_verify_buffer(
20008 wctx->ctx, reinterpret_cast<const unsigned char *>(ca_pem),
20009 static_cast<long>(strlen(ca_pem)), SSL_FILETYPE_PEM);
20010 if (ret != SSL_SUCCESS) {
20011 impl::wolfssl_last_error() =
20012 static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20013 return false;
20014 }
20015 return true;
20016}
20017
20018inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
20019 if (!ctx) { return false; }
20020 auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20021
20022 impl::get_verify_callback() = std::move(callback);
20023 wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
20024
20025 if (wctx->has_verify_callback) {
20026 wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
20027 impl::wolfssl_verify_callback);
20028 } else {
20029 wolfSSL_CTX_set_verify(
20030 wctx->ctx,
20031 wctx->verify_client
20032 ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
20033 : SSL_VERIFY_NONE,
20034 nullptr);
20035 }
20036 return true;
20037}
20038
20039inline long get_verify_error(const_session_t session) {
20040 if (!session) { return -1; }
20041 auto *wsession =
20042 static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
20043 return wolfSSL_get_verify_result(wsession->ssl);
20044}
20045
20046inline std::string verify_error_string(long error_code) {
20047 if (error_code == 0) { return ""; }
20048 const char *str =
20049 wolfSSL_X509_verify_cert_error_string(static_cast<int>(error_code));
20050 return str ? std::string(str) : std::string();
20051}
20052
20053} // namespace tls
20054
20055#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
20056
20057// WebSocket implementation
20058namespace ws {
20059
20060inline bool WebSocket::send_frame(Opcode op, const char *data, size_t len,
20061 bool fin) {
20062 std::lock_guard<std::mutex> lock(write_mutex_);
20063 if (closed_) { return false; }
20064 return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_);
20065}
20066
20067inline ReadResult WebSocket::read(std::string &msg) {
20068 while (!closed_) {
20069 Opcode opcode;
20070 std::string payload;
20071 bool fin;
20072
20073 if (!impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_,
20075 closed_ = true;
20076 return Fail;
20077 }
20078
20079 switch (opcode) {
20080 case Opcode::Ping: {
20081 std::lock_guard<std::mutex> lock(write_mutex_);
20082 detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(),
20083 payload.size(), true, !is_server_);
20084 continue;
20085 }
20086 case Opcode::Pong: {
20087 std::lock_guard<std::mutex> lock(ping_mutex_);
20088 unacked_pings_ = 0;
20089 continue;
20090 }
20091 case Opcode::Close: {
20092 if (!closed_.exchange(true)) {
20093 // Echo close frame back
20094 std::lock_guard<std::mutex> lock(write_mutex_);
20095 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20096 payload.size(), true, !is_server_);
20097 }
20098 return Fail;
20099 }
20100 case Opcode::Text:
20101 case Opcode::Binary: {
20102 auto result = opcode == Opcode::Text ? Text : Binary;
20103 msg = std::move(payload);
20104
20105 // Handle fragmentation
20106 if (!fin) {
20107 while (true) {
20108 Opcode cont_opcode;
20109 std::string cont_payload;
20110 bool cont_fin;
20112 strm_, cont_opcode, cont_payload, cont_fin, is_server_,
20114 closed_ = true;
20115 return Fail;
20116 }
20117 if (cont_opcode == Opcode::Ping) {
20118 std::lock_guard<std::mutex> lock(write_mutex_);
20120 strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(),
20121 true, !is_server_);
20122 continue;
20123 }
20124 if (cont_opcode == Opcode::Pong) {
20125 std::lock_guard<std::mutex> lock(ping_mutex_);
20126 unacked_pings_ = 0;
20127 continue;
20128 }
20129 if (cont_opcode == Opcode::Close) {
20130 if (!closed_.exchange(true)) {
20131 std::lock_guard<std::mutex> lock(write_mutex_);
20133 strm_, Opcode::Close, cont_payload.data(),
20134 cont_payload.size(), true, !is_server_);
20135 }
20136 return Fail;
20137 }
20138 // RFC 6455: continuation frames must use opcode 0x0
20139 if (cont_opcode != Opcode::Continuation) {
20140 closed_ = true;
20141 return Fail;
20142 }
20143 msg += cont_payload;
20144 if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) {
20145 closed_ = true;
20146 return Fail;
20147 }
20148 if (cont_fin) { break; }
20149 }
20150 }
20151 // RFC 6455 Section 5.6: text frames must contain valid UTF-8
20152 if (result == Text && !impl::is_valid_utf8(msg)) {
20153 close(CloseStatus::InvalidPayload, "invalid UTF-8");
20154 return Fail;
20155 }
20156 return result;
20157 }
20158 default: closed_ = true; return Fail;
20159 }
20160 }
20161 return Fail;
20162}
20163
20164inline bool WebSocket::send(const std::string &data) {
20165 return send_frame(Opcode::Text, data.data(), data.size());
20166}
20167
20168inline bool WebSocket::send(const char *data, size_t len) {
20169 return send_frame(Opcode::Binary, data, len);
20170}
20171
20172inline void WebSocket::close(CloseStatus status, const std::string &reason) {
20173 if (closed_.exchange(true)) { return; }
20174 ping_cv_.notify_all();
20175 std::string payload;
20176 auto code = static_cast<uint16_t>(status);
20177 payload.push_back(static_cast<char>((code >> 8) & 0xFF));
20178 payload.push_back(static_cast<char>(code & 0xFF));
20179 // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes
20180 // Close frame has 2-byte status code, so reason is limited to 123 bytes
20181 payload += reason.substr(0, 123);
20182 {
20183 std::lock_guard<std::mutex> lock(write_mutex_);
20184 detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20185 payload.size(), true, !is_server_);
20186 }
20187
20188 // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's
20189 // Close response before closing the TCP connection. Use a short timeout to
20190 // avoid hanging if the peer doesn't respond.
20191 strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0);
20192 Opcode op;
20193 std::string resp;
20194 bool fin;
20195 while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125)) {
20196 if (op == Opcode::Close) { break; }
20197 }
20198}
20199
20201 {
20202 std::lock_guard<std::mutex> lock(ping_mutex_);
20203 closed_ = true;
20204 }
20205 ping_cv_.notify_all();
20206 if (ping_thread_.joinable()) { ping_thread_.join(); }
20207}
20208
20209inline void WebSocket::start_heartbeat() {
20210 if (ping_interval_sec_ == 0) { return; }
20211 ping_thread_ = std::thread([this]() {
20212 std::unique_lock<std::mutex> lock(ping_mutex_);
20213 while (!closed_) {
20214 ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
20215 if (closed_) { break; }
20216 // If the peer has failed to respond to the previous pings, give up.
20217 // RFC 6455 does not define a pong-timeout mechanism; this is an
20218 // opt-in liveness check controlled by max_missed_pongs_.
20219 if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) {
20220 lock.unlock();
20221 close(CloseStatus::GoingAway, "pong timeout");
20222 return;
20223 }
20224 lock.unlock();
20225 if (!send_frame(Opcode::Ping, nullptr, 0)) {
20226 lock.lock();
20227 closed_ = true;
20228 break;
20229 }
20230 lock.lock();
20231 unacked_pings_++;
20232 }
20233 });
20234}
20235
20236inline const Request &WebSocket::request() const { return req_; }
20237
20238inline bool WebSocket::is_open() const { return !closed_; }
20239
20240// WebSocketClient implementation
20242 const std::string &scheme_host_port_path, const Headers &headers)
20243 : headers_(headers) {
20245 if (detail::parse_url(scheme_host_port_path, uc) && !uc.scheme.empty() &&
20246 !uc.host.empty() && !uc.path.empty()) {
20247 auto &scheme = uc.scheme;
20248
20249#ifdef CPPHTTPLIB_SSL_ENABLED
20250 if (scheme != "ws" && scheme != "wss") {
20251#else
20252 if (scheme != "ws") {
20253#endif
20254#ifndef CPPHTTPLIB_NO_EXCEPTIONS
20255 std::string msg = "'" + scheme + "' scheme is not supported.";
20256 throw std::invalid_argument(msg);
20257#endif
20258 return;
20259 }
20260
20261 auto is_ssl = scheme == "wss";
20262
20263 host_ = std::move(uc.host);
20264
20265 port_ = is_ssl ? 443 : 80;
20266 if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
20267
20268 path_ = std::move(uc.path);
20269 if (!uc.query.empty()) { path_ += uc.query; }
20270
20271#ifdef CPPHTTPLIB_SSL_ENABLED
20272 is_ssl_ = is_ssl;
20273#else
20274 if (is_ssl) { return; }
20275#endif
20276
20277 is_valid_ = true;
20278 }
20279}
20280
20281inline WebSocketClient::~WebSocketClient() { shutdown_and_close(); }
20282
20283inline bool WebSocketClient::is_valid() const { return is_valid_; }
20284
20285inline void WebSocketClient::shutdown_and_close() {
20286#ifdef CPPHTTPLIB_SSL_ENABLED
20287 if (is_ssl_) {
20288 if (tls_session_) {
20289 tls::shutdown(tls_session_, true);
20290 tls::free_session(tls_session_);
20291 tls_session_ = nullptr;
20292 }
20293 if (tls_ctx_) {
20294 tls::free_context(tls_ctx_);
20295 tls_ctx_ = nullptr;
20296 }
20297 }
20298#endif
20299 if (ws_ && ws_->is_open()) { ws_->close(); }
20300 ws_.reset();
20301 if (sock_ != INVALID_SOCKET) {
20303 detail::close_socket(sock_);
20304 sock_ = INVALID_SOCKET;
20305 }
20306}
20307
20308inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
20309#ifdef CPPHTTPLIB_SSL_ENABLED
20310 if (is_ssl_) {
20311 if (!detail::setup_client_tls_session(
20312 host_, tls_ctx_, tls_session_, sock_,
20313 server_certificate_verification_, ca_cert_file_path_,
20314 ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) {
20315 return false;
20316 }
20317
20318 strm = std::unique_ptr<Stream>(new detail::SSLSocketStream(
20319 sock_, tls_session_, read_timeout_sec_, read_timeout_usec_,
20320 write_timeout_sec_, write_timeout_usec_));
20321 return true;
20322 }
20323#endif
20324 strm = std::unique_ptr<Stream>(
20325 new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_,
20326 write_timeout_sec_, write_timeout_usec_));
20327 return true;
20328}
20329
20331 if (!is_valid_) { return false; }
20332 shutdown_and_close();
20333
20334 // Check is custom IP specified for host_
20335 std::string ip;
20336 auto it = addr_map_.find(host_);
20337 if (it != addr_map_.end()) { ip = it->second; }
20338
20339 Error error;
20341 host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
20342 socket_options_, connection_timeout_sec_, connection_timeout_usec_,
20343 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
20344 write_timeout_usec_, interface_, error);
20345
20346 if (sock_ == INVALID_SOCKET) { return false; }
20347
20348 std::unique_ptr<Stream> strm;
20349 if (!create_stream(strm)) {
20350 shutdown_and_close();
20351 return false;
20352 }
20353
20354 std::string selected_subprotocol;
20355 if (!detail::perform_websocket_handshake(*strm, host_, port_, path_, headers_,
20356 selected_subprotocol)) {
20357 shutdown_and_close();
20358 return false;
20359 }
20360 subprotocol_ = std::move(selected_subprotocol);
20361
20362 Request req;
20363 req.method = "GET";
20364 req.path = path_;
20365 ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false,
20366 websocket_ping_interval_sec_,
20367 websocket_max_missed_pongs_));
20368 return true;
20369}
20370
20371inline ReadResult WebSocketClient::read(std::string &msg) {
20372 if (!ws_) { return Fail; }
20373 return ws_->read(msg);
20374}
20375
20376inline bool WebSocketClient::send(const std::string &data) {
20377 if (!ws_) { return false; }
20378 return ws_->send(data);
20379}
20380
20381inline bool WebSocketClient::send(const char *data, size_t len) {
20382 if (!ws_) { return false; }
20383 return ws_->send(data, len);
20384}
20385
20387 const std::string &reason) {
20388 if (ws_) { ws_->close(status, reason); }
20389}
20390
20391inline bool WebSocketClient::is_open() const { return ws_ && ws_->is_open(); }
20392
20393inline const std::string &WebSocketClient::subprotocol() const {
20394 return subprotocol_;
20395}
20396
20397inline void WebSocketClient::set_read_timeout(time_t sec, time_t usec) {
20398 read_timeout_sec_ = sec;
20399 read_timeout_usec_ = usec;
20400}
20401
20402inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
20403 write_timeout_sec_ = sec;
20404 write_timeout_usec_ = usec;
20405}
20406
20408 websocket_ping_interval_sec_ = sec;
20409}
20410
20412 websocket_max_missed_pongs_ = count;
20413}
20414
20415inline void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
20416
20418 address_family_ = family;
20419}
20420
20421inline void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
20422
20424 socket_options_ = std::move(socket_options);
20425}
20426
20427inline void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) {
20428 connection_timeout_sec_ = sec;
20429 connection_timeout_usec_ = usec;
20430}
20431
20432inline void WebSocketClient::set_interface(const std::string &intf) {
20433 interface_ = intf;
20434}
20435
20437 std::map<std::string, std::string> addr_map) {
20438 addr_map_ = std::move(addr_map);
20439}
20440
20441#ifdef CPPHTTPLIB_SSL_ENABLED
20442
20443inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
20444 ca_cert_file_path_ = path;
20445}
20446
20447inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
20448 ca_cert_store_ = store;
20449}
20450
20451inline void
20452WebSocketClient::enable_server_certificate_verification(bool enabled) {
20453 server_certificate_verification_ = enabled;
20454}
20455
20456#endif // CPPHTTPLIB_SSL_ENABLED
20457
20458} // namespace ws
20459
20460// ----------------------------------------------------------------------------
20461
20462} // namespace httplib
20463
20464#endif // CPPHTTPLIB_HTTPLIB_H
Env e
Definition Common.cc:2
void split(std::vector< FontInterfaceImpl::Bezier2d > &vec, FontInterfaceImpl::Bezier2d orig, float t, bool emit_left, bool emit_right)
Definition FreeType2.cc:134
void Write(Parser2 *p, char *c, int size, int type, std::string s, bool &success)
Definition Graph.cc:1523
std::string trim(std::string s)
Definition Shader.cc:6754
std::ostream & operator<<(std::ostream &, Catch_global_namespace_dummy)
header('
Definition cert.php:38
void info(std::string info)
Definition check.cc:24
void error(std::string err, std::string fail_reason)
Definition check.cc:18
Definition Map.hh:26
Definition httplib.h:2060
void set_proxy_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:14945
void disconnect(bool gracefully)
Definition httplib.h:12657
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:14921
time_t write_timeout_sec_
Definition httplib.h:2357
std::string host() const
Definition httplib.h:14853
virtual bool ensure_socket_connection(Socket &socket, Error &error)
Definition httplib.h:12613
bool decompress_
Definition httplib.h:2376
void set_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:14883
time_t read_timeout_sec_
Definition httplib.h:2355
void set_error_logger(ErrorLogger error_logger)
Definition httplib.h:15003
size_t socket_requests_in_flight_
Definition httplib.h:2335
void close_socket(Socket &socket)
Definition httplib.h:12637
std::string proxy_bearer_token_auth_token_
Definition httplib.h:2388
bool write_content_with_provider(Stream &strm, const Request &req, Error &error) const
Definition httplib.h:13439
void set_decompress(bool on)
Definition httplib.h:14927
socket_t socket() const
Definition httplib.h:14862
void set_proxy(const std::string &host, int port)
Definition httplib.h:14938
Socket socket_
Definition httplib.h:2330
void set_interface(const std::string &intf)
Definition httplib.h:14934
time_t connection_timeout_sec_
Definition httplib.h:2353
bool follow_location_
Definition httplib.h:2366
Result Patch(const std::string &path)
Definition httplib.h:14549
void stop()
Definition httplib.h:14833
bool tcp_nodelay_
Definition httplib.h:2371
const std::string host_
Definition httplib.h:2326
time_t max_timeout_msec_
Definition httplib.h:2359
void set_no_proxy(const std::vector< std::string > &patterns)
Definition httplib.h:14955
time_t read_timeout_usec_
Definition httplib.h:2356
std::string proxy_host_
Definition httplib.h:2383
Result Delete(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:14748
void set_tcp_nodelay(bool on)
Definition httplib.h:14917
void set_proxy_bearer_token_auth(const std::string &token)
Definition httplib.h:14951
std::string basic_auth_password_
Definition httplib.h:2362
bool send(Request &req, Response &res, Error &error)
Definition httplib.h:12700
size_t payload_max_length_
Definition httplib.h:2378
virtual bool setup_proxy_connection(Socket &socket, std::chrono::time_point< std::chrono::steady_clock > start_time, Response &res, bool &success, Error &error)
Definition httplib.h:12617
virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully)
Definition httplib.h:12624
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14864
std::string bearer_token_auth_token_
Definition httplib.h:2363
void set_hostname_addr_map(std::map< std::string, std::string > addr_map)
Definition httplib.h:14900
int proxy_port_
Definition httplib.h:2384
void set_logger(Logger logger)
Definition httplib.h:14999
ClientImpl(const std::string &host)
Definition httplib.h:12492
void set_compress(bool on)
Definition httplib.h:14925
void output_log(const Request &req, const Response &res) const
Definition httplib.h:13791
bool path_encode_
Definition httplib.h:2368
int port() const
Definition httplib.h:14855
int address_family_
Definition httplib.h:2370
void set_address_family(int family)
Definition httplib.h:14913
std::string client_key_path_
Definition httplib.h:2351
std::string interface_
Definition httplib.h:2381
Result Put(const std::string &path)
Definition httplib.h:14352
Headers default_headers_
Definition httplib.h:2343
std::string client_cert_path_
Definition httplib.h:2350
void set_ipv6_v6only(bool on)
Definition httplib.h:14919
bool ipv6_v6only_
Definition httplib.h:2372
Result Post(const std::string &path)
Definition httplib.h:14154
time_t connection_timeout_usec_
Definition httplib.h:2354
std::string proxy_basic_auth_username_
Definition httplib.h:2386
bool has_payload_max_length_
Definition httplib.h:2379
void shutdown_socket(Socket &socket) const
Definition httplib.h:12632
Logger logger_
Definition httplib.h:2396
std::function< ssize_t(Stream &, Headers &)> header_writer_
Definition httplib.h:2346
bool process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error)
Definition httplib.h:13807
std::thread::id socket_requests_are_from_thread_
Definition httplib.h:2336
bool compress_
Definition httplib.h:2375
std::mutex logger_mutex_
Definition httplib.h:2395
std::map< std::string, std::string > addr_map_
Definition httplib.h:2340
ErrorLogger error_logger_
Definition httplib.h:2397
time_t write_timeout_usec_
Definition httplib.h:2358
std::string proxy_basic_auth_password_
Definition httplib.h:2387
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14874
void copy_settings(const ClientImpl &rhs)
Definition httplib.h:12522
void output_error_log(const Error &err, const Request *req) const
Definition httplib.h:13799
void set_max_timeout(time_t msec)
Definition httplib.h:14879
void set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:14908
std::recursive_mutex request_mutex_
Definition httplib.h:2332
const int port_
Definition httplib.h:2327
detail::NormalizedTarget host_normalized_
Definition httplib.h:2392
Result Options(const std::string &path)
Definition httplib.h:14816
size_t is_socket_open() const
Definition httplib.h:14857
void set_default_headers(Headers headers)
Definition httplib.h:14904
void set_payload_max_length(size_t length)
Definition httplib.h:14929
bool host_normalized_valid_
Definition httplib.h:2393
void set_follow_location(bool on)
Definition httplib.h:14895
SocketOptions socket_options_
Definition httplib.h:2373
void set_keep_alive(bool on)
Definition httplib.h:14893
void set_path_encode(bool on)
Definition httplib.h:14897
bool socket_should_be_closed_when_request_is_done_
Definition httplib.h:2337
bool keep_alive_
Definition httplib.h:2365
bool is_proxy_enabled_for_host(const std::string &host) const
Definition httplib.h:12568
Result Head(const std::string &path)
Definition httplib.h:14137
std::vector< detail::NoProxyEntry > no_proxy_entries_
Definition httplib.h:2390
std::mutex socket_mutex_
Definition httplib.h:2331
virtual ~ClientImpl()
Definition httplib.h:12504
StreamHandle open_stream(const std::string &method, const std::string &path, const Params &params={}, const Headers &headers={}, const std::string &body={}, const std::string &content_type={})
Definition httplib.h:12874
std::string basic_auth_username_
Definition httplib.h:2361
virtual bool create_and_connect_socket(Socket &socket, Error &error)
Definition httplib.h:12605
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:14042
virtual bool is_valid() const
Definition httplib.h:12520
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:14869
void set_bearer_token_auth(const std::string &token)
Definition httplib.h:14889
Definition httplib.h:2470
ClientImpl::StreamHandle open_stream(const std::string &method, const std::string &path, const Params &params={}, const Headers &headers={}, const std::string &body={}, const std::string &content_type={})
Definition httplib.h:15577
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15627
Result Post(const std::string &path)
Definition httplib.h:15146
Client(Client &&)=default
Result Put(const std::string &path)
Definition httplib.h:15273
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15623
bool send(Request &req, Response &res, Error &error)
Definition httplib.h:15583
Result Options(const std::string &path)
Definition httplib.h:15569
void set_default_headers(Headers headers)
Definition httplib.h:15604
void set_proxy_bearer_token_auth(const std::string &token)
Definition httplib.h:15669
void set_hostname_addr_map(std::map< std::string, std::string > addr_map)
Definition httplib.h:15600
Client & operator=(Client &&)=default
void set_proxy_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:15665
void set_tcp_nodelay(bool on)
Definition httplib.h:15617
void set_keep_alive(bool on)
Definition httplib.h:15643
void stop()
Definition httplib.h:15589
void set_max_timeout(time_t msec)
Definition httplib.h:2945
void set_interface(const std::string &intf)
Definition httplib.h:15658
socket_t socket() const
Definition httplib.h:15597
Result Get(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:15089
void set_decompress(bool on)
Definition httplib.h:15652
size_t is_socket_open() const
Definition httplib.h:15595
bool is_valid() const
Definition httplib.h:15085
void set_follow_location(bool on)
Definition httplib.h:15644
void set_path_encode(bool on)
Definition httplib.h:15648
void set_proxy(const std::string &host, int port)
Definition httplib.h:15662
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:15631
Client(const std::string &scheme_host_port)
Definition httplib.h:15027
void set_address_family(int family)
Definition httplib.h:15613
Result Head(const std::string &path)
Definition httplib.h:15141
std::string host() const
Definition httplib.h:15591
void set_error_logger(ErrorLogger error_logger)
Definition httplib.h:15680
void set_bearer_token_auth(const std::string &token)
Definition httplib.h:15639
void set_payload_max_length(size_t length)
Definition httplib.h:15654
void set_logger(Logger logger)
Definition httplib.h:15676
void set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:15608
Result Patch(const std::string &path)
Definition httplib.h:15399
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:15619
void set_no_proxy(const std::vector< std::string > &patterns)
Definition httplib.h:15672
void set_compress(bool on)
Definition httplib.h:15650
Result Delete(const std::string &path, DownloadProgress progress=nullptr)
Definition httplib.h:15528
void set_basic_auth(const std::string &username, const std::string &password)
Definition httplib.h:15635
int port() const
Definition httplib.h:15593
Definition httplib.h:1142
Reader reader_
Definition httplib.h:1160
ContentReader(Reader reader, FormDataReader multipart_reader)
Definition httplib.h:1148
bool operator()(FormDataHeader header, ContentReceiver receiver) const
Definition httplib.h:1152
bool operator()(ContentReceiver receiver) const
Definition httplib.h:1156
std::function< bool(ContentReceiver receiver)> Reader
Definition httplib.h:1144
FormDataReader formdata_reader_
Definition httplib.h:1161
std::function< bool(FormDataHeader header, ContentReceiver receiver)> FormDataReader
Definition httplib.h:1145
Definition httplib.h:1028
std::function< bool(const char *data, size_t data_len)> write
Definition httplib.h:1037
DataSink(DataSink &&)=delete
DataSink(const DataSink &)=delete
DataSink & operator=(DataSink &&)=delete
DataSink & operator=(const DataSink &)=delete
std::function< void()> done
Definition httplib.h:1039
std::function< bool()> is_writable
Definition httplib.h:1038
DataSink()
Definition httplib.h:1030
std::ostream os
Definition httplib.h:1041
std::function< void(const Headers &trailer)> done_with_trailer
Definition httplib.h:1040
Definition httplib.h:1908
bool has_request_header(const std::string &key) const
Definition httplib.h:10006
Response & value()
Definition httplib.h:1920
Result()=default
const Response & value() const
Definition httplib.h:1919
size_t get_request_header_value_count(const std::string &key) const
Definition httplib.h:10017
const Response & operator*() const
Definition httplib.h:1921
bool operator==(std::nullptr_t) const
Definition httplib.h:1917
Result(std::unique_ptr< Response > &&res, Error err, Headers &&request_headers=Headers{})
Definition httplib.h:1911
bool operator!=(std::nullptr_t) const
Definition httplib.h:1918
const Response * operator->() const
Definition httplib.h:1923
Response * operator->()
Definition httplib.h:1924
size_t get_request_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:10000
Error error() const
Definition httplib.h:1927
Response & operator*()
Definition httplib.h:1922
std::string get_request_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:10010
Definition httplib.h:1626
time_t read_timeout_usec_
Definition httplib.h:1772
Server & set_default_file_mimetype(const std::string &mime)
Definition httplib.h:11047
void stop() noexcept
Definition httplib.h:11243
std::function< int(const Request &, Response &)> Expect100ContinueHandler
Definition httplib.h:1643
Server & Put(const std::string &pattern, Handler handler)
Definition httplib.h:10952
Server & Delete(const std::string &pattern, Handler handler)
Definition httplib.h:10972
Server & set_pre_routing_handler(HandlerWithResponse handler)
Definition httplib.h:11077
Server & set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:11180
time_t idle_interval_usec_
Definition httplib.h:1776
size_t keep_alive_max_count_
Definition httplib.h:1769
time_t write_timeout_sec_
Definition httplib.h:1773
Server & set_error_handler(ErrorHandlerFunc &&handler)
Definition httplib.h:1685
bool listen(const std::string &host, int port, int socket_flags=0)
Definition httplib.h:11230
Server & WebSocket(const std::string &pattern, WebSocketHandler handler)
Definition httplib.h:10986
bool set_base_dir(const std::string &dir, const std::string &mount_point=std::string())
Definition httplib.h:11001
void decommission()
Definition httplib.h:11253
bool listen_after_bind()
Definition httplib.h:11228
std::vector< std::string > trusted_proxies_
Definition httplib.h:1767
Server & set_tcp_nodelay(bool on)
Definition httplib.h:11123
Server & set_header_writer(std::function< ssize_t(Stream &, Headers &)> const &writer)
Definition httplib.h:11143
Server & set_payload_max_length(size_t length)
Definition httplib.h:11192
Server & Options(const std::string &pattern, Handler handler)
Definition httplib.h:10982
virtual bool is_valid() const
Definition httplib.h:12438
int bind_to_any_port(const std::string &host, int socket_flags=0)
Definition httplib.h:11222
Server()
Definition httplib.h:10917
time_t idle_interval_sec_
Definition httplib.h:1775
bool bind_to_port(const std::string &host, int port, int socket_flags=0)
Definition httplib.h:11216
time_t write_timeout_usec_
Definition httplib.h:1774
time_t keep_alive_timeout_sec_
Definition httplib.h:1770
HandlerResponse
Definition httplib.h:1633
@ Unhandled
Definition httplib.h:1635
@ Handled
Definition httplib.h:1634
std::function< void()> StartHandler
Definition httplib.h:1646
Server & Patch(const std::string &pattern, Handler handler)
Definition httplib.h:10962
Server & set_default_headers(Headers headers)
Definition httplib.h:11138
time_t websocket_ping_interval_sec_
Definition httplib.h:1778
Server & Get(const std::string &pattern, Handler handler)
Definition httplib.h:10938
Server & set_trusted_proxies(const std::vector< std::string > &proxies)
Definition httplib.h:11150
Server & set_file_request_handler(Handler handler)
Definition httplib.h:11052
std::function< void(const Request &, Response &)> Handler
Definition httplib.h:1628
std::function< std::string(const std::vector< std::string > &protocols)> SubProtocolSelector
Definition httplib.h:1650
std::function< void(const Request &, Response &, std::exception_ptr ep)> ExceptionHandler
Definition httplib.h:1630
std::atomic< socket_t > svr_sock_
Definition httplib.h:1765
void wait_until_ready() const
Definition httplib.h:11237
Server & set_post_routing_handler(Handler handler)
Definition httplib.h:11082
bool is_running() const
Definition httplib.h:11235
Server & Post(const std::string &pattern, Handler handler)
Definition httplib.h:10942
Server & set_idle_interval(time_t sec, time_t usec=0)
Definition httplib.h:11186
Server & set_address_family(int family)
Definition httplib.h:11118
Server & set_websocket_max_missed_pongs(int count)
Definition httplib.h:11197
bool process_request(Stream &strm, const std::string &remote_addr, int remote_port, const std::string &local_addr, int local_port, bool close_connection, bool &connection_closed, const std::function< void(Request &)> &setup_request, bool *websocket_upgraded=nullptr)
Definition httplib.h:12153
Server & set_start_handler(StartHandler handler)
Definition httplib.h:11113
bool remove_mount_point(const std::string &mount_point)
Definition httplib.h:11030
Server & set_keep_alive_timeout(time_t sec)
Definition httplib.h:11160
std::function< TaskQueue *(void)> new_task_queue
Definition httplib.h:1755
Server & set_logger(Logger logger)
Definition httplib.h:11092
std::function< HandlerResponse(const Request &, Response &)> HandlerWithResponse
Definition httplib.h:1637
std::function< void(const Request &, ws::WebSocket &)> WebSocketHandler
Definition httplib.h:1648
bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers=Headers())
Definition httplib.h:11006
Server & set_socket_options(SocketOptions socket_options)
Definition httplib.h:11133
Server & set_pre_compression_logger(Logger logger)
Definition httplib.h:11102
time_t read_timeout_sec_
Definition httplib.h:1771
int websocket_max_missed_pongs_
Definition httplib.h:1780
Server & set_pre_request_handler(HandlerWithResponse handler)
Definition httplib.h:11087
Server & set_exception_handler(ExceptionHandler handler)
Definition httplib.h:11072
Server & set_error_logger(ErrorLogger error_logger)
Definition httplib.h:11097
Server & set_expect_100_continue_handler(Expect100ContinueHandler handler)
Definition httplib.h:11108
Server & set_websocket_ping_interval(time_t sec)
Definition httplib.h:11202
Server & set_ipv6_v6only(bool on)
Definition httplib.h:11128
size_t payload_max_length_
Definition httplib.h:1777
virtual ~Server()
Server & set_file_extension_and_mimetype_mapping(const std::string &ext, const std::string &mime)
Definition httplib.h:11041
std::function< void( const Request &, Response &, const ContentReader &content_reader)> HandlerWithContentReader
Definition httplib.h:1640
Server & set_keep_alive_max_count(size_t count)
Definition httplib.h:11155
Server & set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:11174
Definition httplib.h:1443
Error get_error() const
Definition httplib.h:1468
virtual bool wait_writable() const =0
Error error_
Definition httplib.h:1471
virtual bool wait_readable() const =0
virtual ssize_t write(const char *ptr, size_t size)=0
virtual ssize_t read(char *ptr, size_t size)=0
virtual void get_remote_ip_and_port(std::string &ip, int &port) const =0
virtual void get_local_ip_and_port(std::string &ip, int &port) const =0
virtual bool is_peer_alive() const
Definition httplib.h:1450
virtual time_t duration() const =0
virtual void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:1460
virtual ~Stream()=default
virtual socket_t socket() const =0
virtual bool is_readable() const =0
Definition httplib.h:1474
virtual bool enqueue(std::function< void()> fn)=0
virtual void on_idle()
Definition httplib.h:1482
virtual ~TaskQueue()=default
virtual void shutdown()=0
Definition httplib.h:1485
ThreadPool(size_t n, size_t max_n=0, size_t mqr=0)
Definition httplib.h:10106
void shutdown() override
Definition httplib.h:10163
ThreadPool(const ThreadPool &)=delete
~ThreadPool() override=default
bool enqueue(std::function< void()> fn) override
Definition httplib.h:10142
Definition httplib.h:929
T * get(const std::string &key) noexcept
Definition httplib.h:956
void erase(const std::string &key)
Definition httplib.h:974
void set(const std::string &key, T &&value)
Definition httplib.h:951
UserData & operator=(const UserData &o)
Definition httplib.h:941
const T * get(const std::string &key) const noexcept
Definition httplib.h:963
bool has(const std::string &key) const noexcept
Definition httplib.h:970
UserData()=default
UserData(UserData &&) noexcept=default
void clear() noexcept
Definition httplib.h:976
bool wait_writable() const override
Definition httplib.h:10410
void get_remote_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10427
void get_local_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10430
ssize_t write(const char *ptr, size_t size) override
Definition httplib.h:10422
~BufferStream() override=default
time_t duration() const override
Definition httplib.h:10435
bool wait_readable() const override
Definition httplib.h:10408
const std::string & get_buffer() const
Definition httplib.h:10437
socket_t socket() const override
Definition httplib.h:10433
bool is_readable() const override
Definition httplib.h:10406
ssize_t read(char *ptr, size_t size) override
Definition httplib.h:10412
bool operator()(size_t offset, size_t, DataSink &sink)
Definition httplib.h:8720
ContentProviderAdapter(ContentProviderWithoutLength &&content_provider)
Definition httplib.h:8716
void set_boundary(std::string &&boundary)
Definition httplib.h:7939
bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, const ContentReceiver &content_callback)
Definition httplib.h:7947
bool is_valid() const
Definition httplib.h:7945
virtual bool match(Request &request) const =0
virtual ~MatcherBase()=default
MatcherBase(std::string pattern)
Definition httplib.h:1540
const std::string & pattern() const
Definition httplib.h:1543
PathParamsMatcher(const std::string &pattern)
Definition httplib.h:10439
bool match(Request &request) const override
Definition httplib.h:10488
bool match(Request &request) const override
Definition httplib.h:10531
RegexMatcher(const std::string &pattern)
Definition httplib.h:1601
Definition httplib.h:5589
bool wait_readable() const override
Definition httplib.h:10288
bool wait_writable() const override
Definition httplib.h:10301
void get_remote_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10382
socket_t socket() const override
Definition httplib.h:10392
bool is_peer_alive() const override
Definition httplib.h:10305
time_t duration() const override
Definition httplib.h:10394
bool is_readable() const override
Definition httplib.h:10284
ssize_t write(const char *ptr, size_t size) override
Definition httplib.h:10371
void get_local_ip_and_port(std::string &ip, int &port) const override
Definition httplib.h:10387
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, time_t max_timeout_msec=0, std::chrono::time_point< std::chrono::steady_clock > start_time=(std::chrono::steady_clock::time_point::min)())
Definition httplib.h:10270
ssize_t read(char *ptr, size_t size) override
Definition httplib.h:10309
void set_read_timeout(time_t sec, time_t usec=0) override
Definition httplib.h:10400
Definition httplib.h:3112
virtual bool compress(const char *data, size_t data_length, bool last, Callback callback)=0
std::function< bool(const char *data, size_t data_len)> Callback
Definition httplib.h:3116
virtual ~compressor()=default
Definition httplib.h:3121
virtual ~decompressor()=default
virtual bool decompress(const char *data, size_t data_length, Callback callback)=0
virtual bool is_valid() const =0
std::function< bool(const char *data, size_t data_len)> Callback
Definition httplib.h:3127
const char * data() const
Definition httplib.h:5443
bool open(const char *path)
Definition httplib.h:5367
~mmap()
Definition httplib.h:5365
size_t size() const
Definition httplib.h:5441
mmap(const char *path)
Definition httplib.h:5363
bool is_open() const
Definition httplib.h:5437
void close()
Definition httplib.h:5447
Definition httplib.h:3132
~nocompressor() override=default
bool compress(const char *data, size_t data_length, bool, Callback callback) override
Definition httplib.h:6718
Definition httplib.h:3229
stream_line_reader(Stream &strm, char *fixed_buffer, size_t fixed_buffer_size)
Definition httplib.h:5284
bool getline()
Definition httplib.h:5310
bool end_with_crlf() const
Definition httplib.h:5305
size_t size() const
Definition httplib.h:5297
const char * ptr() const
Definition httplib.h:5289
void start_async()
Definition httplib.h:4068
const std::string & last_event_id() const
Definition httplib.h:4059
bool is_connected() const
Definition httplib.h:4057
SSEClient & on_event(const std::string &type, MessageHandler handler)
Definition httplib.h:4025
SSEClient & set_reconnect_interval(int ms)
Definition httplib.h:4041
SSEClient & on_message(MessageHandler handler)
Definition httplib.h:4020
SSEClient & set_max_reconnect_attempts(int n)
Definition httplib.h:4046
SSEClient(const SSEClient &)=delete
SSEClient & on_open(OpenHandler handler)
Definition httplib.h:4031
SSEClient & operator=(const SSEClient &)=delete
~SSEClient()
Definition httplib.h:4018
SSEClient & set_headers(const Headers &headers)
Definition httplib.h:4051
void stop()
Definition httplib.h:4073
std::function< void(Error)> ErrorHandler
Definition httplib.h:3672
SSEClient & on_error(ErrorHandler handler)
Definition httplib.h:4036
SSEClient(Client &client, const std::string &path)
Definition httplib.h:4011
std::function< void(const SSEMessage &)> MessageHandler
Definition httplib.h:3671
std::function< void()> OpenHandler
Definition httplib.h:3673
void start()
Definition httplib.h:4063
Definition httplib.h:3375
Result & operator=(const Result &)=delete
Result & operator=(Result &&other) noexcept
Definition httplib.h:3932
const char * data() const
Definition httplib.h:3986
Result()
Definition httplib.h:3919
size_t size() const
Definition httplib.h:3987
Error error() const
Definition httplib.h:3966
bool has_read_error() const
Definition httplib.h:3968
bool is_valid() const
Definition httplib.h:3945
const Headers & headers() const
Definition httplib.h:3952
int status() const
Definition httplib.h:3948
std::string get_header_value(const std::string &key, const char *def="") const
Definition httplib.h:3957
bool has_header(const std::string &key) const
Definition httplib.h:3962
std::string read_all()
Definition httplib.h:3989
bool next()
Definition httplib.h:3970
Error read_error() const
Definition httplib.h:3967
Result(const Result &)=delete
bool send(const std::string &data)
Definition httplib.h:20376
WebSocketClient(const std::string &scheme_host_port_path, const Headers &headers={})
Definition httplib.h:20241
bool connect()
Definition httplib.h:20330
WebSocketClient & operator=(const WebSocketClient &)=delete
ReadResult read(std::string &msg)
Definition httplib.h:20371
void set_address_family(int family)
Definition httplib.h:20417
~WebSocketClient()
Definition httplib.h:20281
void set_read_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20397
void close(CloseStatus status=CloseStatus::Normal, const std::string &reason="")
Definition httplib.h:20386
void set_ipv6_v6only(bool on)
Definition httplib.h:20421
WebSocketClient(const WebSocketClient &)=delete
void set_connection_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20427
bool is_valid() const
Definition httplib.h:20283
const std::string & subprotocol() const
Definition httplib.h:20393
void set_socket_options(SocketOptions socket_options)
Definition httplib.h:20423
void set_websocket_ping_interval(time_t sec)
Definition httplib.h:20407
void set_write_timeout(time_t sec, time_t usec=0)
Definition httplib.h:20402
void set_tcp_nodelay(bool on)
Definition httplib.h:20415
bool is_open() const
Definition httplib.h:20391
void set_websocket_max_missed_pongs(int count)
Definition httplib.h:20411
void set_hostname_addr_map(std::map< std::string, std::string > addr_map)
Definition httplib.h:20436
void set_interface(const std::string &intf)
Definition httplib.h:20432
Definition httplib.h:3767
bool send(const std::string &data)
Definition httplib.h:20164
const Request & request() const
Definition httplib.h:20236
void close(CloseStatus status=CloseStatus::Normal, const std::string &reason="")
Definition httplib.h:20172
bool is_open() const
Definition httplib.h:20238
ReadResult read(std::string &msg)
Definition httplib.h:20067
friend class WebSocketClient
Definition httplib.h:3783
WebSocket(const WebSocket &)=delete
~WebSocket()
Definition httplib.h:20200
WebSocket & operator=(const WebSocket &)=delete
std::string code
Definition test.cpp:145
if( $mode==true)
Definition get_file_size.php:77
#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
double now()
Definition libfuzzer_client.cpp:262
void function(void)
Definition memory_leak_test.cpp:26
ret($return)
Definition mesh_login_result.php:5
d
Definition meshpage.php:3895
iVBORw0KGgoAAAANSUhEUgAAANcAAAB5CAMAAACdtUQZAAAA51BMVEX</div ></div >< div style="padding: 0px 0px; width: 1324px;">< div style="height: 400px; width: 1324px;"></div >< div itemscope itemtype="https: </div><div itemscope itemtype="https:</div >< div itemscope itemtype="https: </div></div><p><div style="padding:0px 0px;width:1324px;"><div></div><div itemscope itemtype="https:< ul >< li >< b > Application name
8AkrcAgqgAs9wqQ0ey2tkAo8gAuuQfeI4hPUH09fXHy8z4+fng4uNKXF/Lz9BzfoAcgpkQMTUAKjAbOT4Qr9AXhJ8Rl7ktMCwTosA...
Definition meshpage.php:1610
lazy data
Definition meshpage.php:1583
str
Definition meshpage.php:1778
tag src
Definition meshpage.php:1786
tag
Definition meshpage.php:1785
n< br > font size
Definition meshpage.php:3536
url
Definition meshpage.php:1781
store
Definition meshpage_bootstrap.php:1758
not_this_one end(...)
size_t count(const T &t_target, const typename T::key_type &t_key)
Definition bootstrap_stl.hpp:104
Definition httplib.h:543
std::unordered_set< T, detail::case_ignore::hash, detail::case_ignore::equal_to > unordered_set
Definition httplib.h:607
bool equal(const std::string &a, const std::string &b)
Definition httplib.h:577
unsigned char to_lower(int c)
Definition httplib.h:545
Definition httplib.h:3292
bool is_obs_text(char c)
Definition httplib.h:8749
bool is_vchar(char c)
Definition httplib.h:8747
bool is_field_value(const std::string &s)
Definition httplib.h:8778
bool is_token(const std::string &s)
Definition httplib.h:8737
bool is_token_char(char c)
Definition httplib.h:8731
bool is_field_name(const std::string &s)
Definition httplib.h:8745
bool is_field_content(const std::string &s)
Definition httplib.h:8753
bool is_field_vchar(char c)
Definition httplib.h:8751
Definition httplib.h:6445
Definition httplib.h:520
size_t to_utf8(int code, char *buff)
Definition httplib.h:4515
void divide(const char *data, std::size_t size, char d, std::function< void(const char *, std::size_t, const char *, std::size_t)> fn)
Definition httplib.h:5201
size_t get_header_value_count(const Headers &headers, const std::string &key)
Definition httplib.h:7050
std::string make_host_and_port_string_always_port(const std::string &host, int port)
Definition httplib.h:10565
std::string trim_double_quotes_copy(const std::string &s)
Definition httplib.h:5193
ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags)
Definition httplib.h:5511
bool has_framed_body(const Request &req)
Definition httplib.h:8642
std::string sha1(const std::string &input)
Definition httplib.h:4627
std::string make_host_and_port_string(const std::string &host, int port, bool is_ssl)
Definition httplib.h:10549
void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port)
Definition httplib.h:6388
bool parse_www_authenticate(const Response &res, std::map< std::string, std::string > &auth, bool is_proxy)
Definition httplib.h:8680
bool parse_url(const std::string &url, UrlComponents &uc)
Definition httplib.h:723
bool parse_quality(const char *b, const char *e, std::string &token, double &quality)
Definition httplib.h:6579
std::string serialize_multipart_formdata_get_content_type(const std::string &boundary)
Definition httplib.h:8281
bool is_socket_alive(socket_t sock)
Definition httplib.h:5578
void coalesce_ranges(Ranges &ranges, size_t content_length)
Definition httplib.h:8398
bool has_header(const Headers &headers, const std::string &key)
Definition httplib.h:7026
size_t get_multipart_content_length(const UploadFormDataItems &items, const std::string &boundary)
Definition httplib.h:8300
int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout)
Definition httplib.h:5524
EncodingType
Definition httplib.h:3086
@ Zstd
Definition httplib.h:3086
@ Gzip
Definition httplib.h:3086
@ Brotli
Definition httplib.h:3086
@ None
Definition httplib.h:3086
std::string compute_etag(const FileStat &fs)
Definition httplib.h:4429
size_t get_header_value_u64(const Headers &headers, const std::string &key, size_t def, size_t id, bool &is_invalid_value)
Definition httplib.h:2847
time_t parse_http_date(const std::string &date_str)
Definition httplib.h:4467
void split(const char *b, const char *e, char d, std::function< void(const char *, const char *)> fn)
Definition httplib.h:5221
bool write_websocket_frame(Stream &strm, ws::Opcode opcode, const char *data, size_t len, bool fin, bool mask)
Definition httplib.h:4756
std::string normalize_query_string(const std::string &query)
Definition httplib.h:7730
bool host_matches_no_proxy(const NormalizedTarget &target, const std::vector< NoProxyEntry > &entries)
Definition httplib.h:10704
const void * any_type_id
Definition httplib.h:905
std::array< uint8_t, 16 > IPBytes
Definition httplib.h:2042
int shutdown_socket(socket_t sock) noexcept
Definition httplib.h:5705
ReadContentResult read_content_with_length(Stream &strm, size_t len, DownloadProgress progress, ContentReceiverWithProgress out, size_t payload_max_length=(std::numeric_limits< size_t >::max)())
Definition httplib.h:7175
const char * encoding_name(EncodingType type)
Definition httplib.h:6709
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, DownloadProgress progress, ContentReceiverWithProgress receiver, bool decompress)
Definition httplib.h:7334
std::pair< std::unique_ptr< compressor >, const char * > create_compressor()
Definition httplib.h:7002
std::unique_ptr< decompressor > create_decompressor(const std::string &encoding)
Definition httplib.h:6979
std::string trim_copy(const std::string &s)
Definition httplib.h:5188
std::string base64_encode(const std::string &in)
Definition httplib.h:4599
Map::mapped_type get_multimap_value(const Map &m, const std::string &key, size_t id)
Definition httplib.h:7058
std::string file_mtime_to_http_date(time_t mtime)
Definition httplib.h:4449
bool perform_websocket_handshake(Stream &strm, const std::string &host, int port, const std::string &path, const Headers &headers, std::string &selected_subprotocol)
Definition httplib.h:8782
std::string serialize_multipart_formdata_item_end()
Definition httplib.h:8273
bool is_hex(char c, int &v)
Definition httplib.h:4388
bool write_content_without_length(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down)
Definition httplib.h:7514
std::string if2ip(int address_family, const std::string &ifn)
Definition httplib.h:6260
std::string extract_media_type(const std::string &content_type, std::map< std::string, std::string > *params=nullptr)
Definition httplib.h:6520
bool is_space_or_tab(char c)
Definition httplib.h:5033
ssize_t write_response_line(Stream &strm, int status)
Definition httplib.h:7402
ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf, size_t len)
Definition httplib.h:2025
bool redirect(T &cli, Request &req, Response &res, const std::string &path, const std::string &location, Error &error)
Definition httplib.h:7661
bool parse_trailers(stream_line_reader &line_reader, Headers &dest, const Headers &src_headers)
Definition httplib.h:5084
std::enable_if<!std::is_array< T >::value, std::unique_ptr< T > >::type make_unique(Args &&...args)
Definition httplib.h:532
bool is_weak_etag(const std::string &s)
Definition httplib.h:4504
ssize_t select_impl(socket_t sock, short events, time_t sec, time_t usec)
Definition httplib.h:5532
bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, int &val)
Definition httplib.h:4402
void parse_query_text(const char *data, std::size_t size, Params &params)
Definition httplib.h:7699
bool process_client_socket(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, time_t max_timeout_msec, std::chrono::time_point< std::chrono::steady_clock > start_time, std::function< bool(Stream &)> callback)
Definition httplib.h:5693
void set_nonblocking(socket_t sock, bool nonblocking)
Definition httplib.h:6209
socket_t create_socket(const std::string &host, const std::string &ip, int port, int address_family, int socket_flags, bool tcp_nodelay, bool ipv6_v6only, SocketOptions socket_options, BindOrConnect bind_or_connect, time_t timeout_sec=0)
Definition httplib.h:6065
bool keep_alive(const std::atomic< socket_t > &svr_sock, socket_t sock, time_t keep_alive_timeout_sec)
Definition httplib.h:5626
std::string serialize_multipart_formdata_item_begin(const T &item, const std::string &boundary)
Definition httplib.h:8257
bool is_connection_persistent(const Request &req)
Definition httplib.h:8647
bool set_socket_opt_impl(socket_t sock, int level, int optname, const void *optval, socklen_t optlen)
Definition httplib.h:4365
bool is_strong_etag(const std::string &s)
Definition httplib.h:4509
std::pair< size_t, size_t > get_range_offset_and_length(Range r, size_t content_length)
Definition httplib.h:8531
bool write_content_with_progress(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, T is_shutting_down, const UploadProgress &upload_progress, Error &error)
Definition httplib.h:7441
std::string file_extension(const std::string &path)
Definition httplib.h:5026
void parse_disposition_params(const std::string &s, Params &params)
Definition httplib.h:7768
unsigned int str2tag(const std::string &s)
Definition httplib.h:6432
bool read_websocket_upgrade_response(Stream &strm, const std::string &expected_accept, std::string &selected_subprotocol)
Definition httplib.h:7129
bool split_find(const char *b, const char *e, char d, size_t m, std::function< bool(const char *, const char *)> fn)
Definition httplib.h:5248
bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out)
Definition httplib.h:10590
void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, time_t timeout_sec, time_t timeout_usec, time_t &actual_timeout_sec, time_t &actual_timeout_usec)
Definition httplib.h:10254
bool expect_content(const Request &req)
Definition httplib.h:8654
int close_socket(socket_t sock) noexcept
Definition httplib.h:5478
ssize_t handle_EINTR(T fn)
Definition httplib.h:5486
ssize_t select_write(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5548
bool get_ip_and_port(const struct sockaddr_storage &addr, socklen_t addr_len, std::string &ip, int &port)
Definition httplib.h:6357
void make_multipart_ranges_data(const Request &req, Response &res, const std::string &boundary, const std::string &content_type, size_t content_length, std::string &data)
Definition httplib.h:8592
constexpr size_t str_len(const char(&)[N])
Definition httplib.h:2837
bool is_valid_path(const std::string &path)
Definition httplib.h:4899
NoProxyKind
Definition httplib.h:2033
@ HostnameSuffix
Definition httplib.h:2035
@ IPv4Cidr
Definition httplib.h:2036
@ Wildcard
Definition httplib.h:2034
@ IPv6Cidr
Definition httplib.h:2037
std::string websocket_accept_key(const std::string &client_key)
Definition httplib.h:4717
bool parse_header(const char *beg, const char *end, T fn)
Definition httplib.h:5039
std::string random_string(size_t length)
Definition httplib.h:8218
bool process_multipart_ranges_data(const Request &req, const std::string &boundary, const std::string &content_type, size_t content_length, SToken stoken, CToken ctoken, Content content)
Definition httplib.h:8556
std::pair< size_t, size_t > trim(const char *b, const char *e, size_t left, size_t right)
Definition httplib.h:5177
bool range_error(Request &req, Response &res)
Definition httplib.h:8454
EncodingType encoding_type(const Request &req, const Response &res)
Definition httplib.h:6632
bool bind_ip_address(socket_t sock, const std::string &host)
Definition httplib.h:6228
bool parse_multipart_boundary(const std::string &content_type, std::string &boundary)
Definition httplib.h:7758
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags)
Definition httplib.h:5499
bool parse_port(const char *s, size_t len, int &port)
Definition httplib.h:703
std::string serialize_multipart_formdata_finish(const std::string &boundary)
Definition httplib.h:8276
std::string params_to_query_str(const Params &params)
Definition httplib.h:7687
ReadContentResult read_content_without_length(Stream &strm, size_t payload_max_length, ContentReceiverWithProgress out)
Definition httplib.h:7217
std::unique_ptr< compressor > make_compressor(EncodingType type)
Definition httplib.h:6689
bool prepare_content_receiver(T &x, int &status, ContentReceiverWithProgress receiver, bool decompress, size_t payload_max_length, bool &exceed_payload_max_length, U callback)
Definition httplib.h:7282
ssize_t write_headers(Stream &strm, const Headers &headers)
Definition httplib.h:7411
bool is_chunked_transfer_encoding(const Headers &headers)
Definition httplib.h:7276
bool can_compress_content_type(const std::string &content_type)
Definition httplib.h:6554
std::string from_i_to_hex(size_t n)
Definition httplib.h:4419
bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, T is_shutting_down, Error &error)
Definition httplib.h:7496
bool is_path_within_base(const std::string &resolved_path, const std::string &resolved_base)
Definition httplib.h:4959
bool parse_accept_header(const std::string &s, std::vector< std::string > &content_types)
Definition httplib.h:7853
socket_t create_client_socket(const std::string &host, const std::string &ip, int port, int address_family, bool tcp_nodelay, bool ipv6_v6only, SocketOptions socket_options, time_t connection_timeout_sec, time_t connection_timeout_usec, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, const std::string &intf, Error &error)
Definition httplib.h:6297
bool is_websocket_upgrade(const Request &req)
Definition httplib.h:4722
bool parse_range_header(const std::string &s, Ranges &ranges)
Definition httplib.h:7795
from_chars_result< T > from_chars(const char *first, const char *last, T &value, int base=10)
Definition httplib.h:648
bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type, size_t content_length, const T &is_shutting_down)
Definition httplib.h:8628
void duration_to_sec_and_usec(const T &duration, U callback)
Definition httplib.h:2829
const char * get_header_value(const Headers &headers, const std::string &key, const char *def, size_t id)
Definition httplib.h:7031
std::string find_content_type(const std::string &path, const std::map< std::string, std::string > &user_data, const std::string &default_content_type)
Definition httplib.h:6454
any_type_id any_typeid() noexcept
Definition httplib.h:907
bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits)
Definition httplib.h:10575
bool canonicalize_path(const char *path, std::string &resolved)
Definition httplib.h:4941
ssize_t select_read(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5544
std::string serialize_multipart_formdata(const UploadFormDataItems &items, const std::string &boundary, bool finish=true)
Definition httplib.h:8286
bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, time_t usec)
Definition httplib.h:4376
std::string make_multipart_data_boundary()
Definition httplib.h:8239
void get_local_ip_and_port(socket_t sock, std::string &ip, int &port)
Definition httplib.h:6379
ReadContentResult read_content_chunked(Stream &strm, T &x, size_t payload_max_length, ContentReceiverWithProgress out)
Definition httplib.h:7242
bool is_multipart_boundary_chars_valid(const std::string &boundary)
Definition httplib.h:8243
size_t get_multipart_ranges_data_length(const Request &req, const std::string &boundary, const std::string &content_type, size_t content_length)
Definition httplib.h:8608
void set_header(Headers &headers, const std::string &key, const std::string &val)
Definition httplib.h:7066
ContentProvider make_multipart_content_provider(const UploadFormDataItems &items, const std::string &boundary)
Definition httplib.h:8320
ssize_t write_request_line(Stream &strm, const std::string &method, const std::string &path)
Definition httplib.h:7393
std::string make_content_range_header_field(const std::pair< size_t, size_t > &offset_and_length, size_t content_length)
Definition httplib.h:8541
std::string escape_abstract_namespace_unix_domain(const std::string &s)
Definition httplib.h:5713
bool read_headers(Stream &strm, Headers &headers)
Definition httplib.h:7073
std::string unescape_abstract_namespace_unix_domain(const std::string &s)
Definition httplib.h:5723
bool is_connection_error()
Definition httplib.h:6220
bool write_data(Stream &strm, const char *d, size_t l)
Definition httplib.h:7430
bool check_and_write_headers(Stream &strm, Headers &headers, T header_writer, Error &error)
Definition httplib.h:10740
Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec)
Definition httplib.h:5552
bool write_content_chunked(Stream &strm, const ContentProvider &content_provider, const T &is_shutting_down, U &compressor, Error &error)
Definition httplib.h:7549
ReadContentResult
Definition httplib.h:7169
@ PayloadTooLarge
Definition httplib.h:7171
@ Success
Definition httplib.h:7170
@ Error
Definition httplib.h:7172
bool is_prohibited_header_name(const std::string &name)
Definition httplib.h:7014
bool process_server_socket(const std::atomic< socket_t > &svr_sock, socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, T callback)
Definition httplib.h:5679
std::string prepare_host_string(const std::string &host)
Definition httplib.h:10537
bool is_numeric(const std::string &str)
Definition httplib.h:2841
int getaddrinfo_with_timeout(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, time_t timeout_sec)
Definition httplib.h:5732
constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h)
Definition httplib.h:6420
std::string encode_path(const std::string &s)
Definition httplib.h:4994
NormalizedTarget normalize_target(const std::string &host)
Definition httplib.h:10676
bool process_server_socket_core(const std::atomic< socket_t > &svr_sock, socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec, T callback)
Definition httplib.h:5661
Definition httplib.h:3658
Definition httplib.h:3373
Result Options(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3632
Result Post(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size=8192)
Definition httplib.h:3438
Result Delete(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3543
Result Put(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size=8192)
Definition httplib.h:3473
Result Head(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3606
Result Get(ClientType &cli, const std::string &path, size_t chunk_size=8192)
Definition httplib.h:3412
Result Patch(ClientType &cli, const std::string &path, const std::string &body, const std::string &content_type, size_t chunk_size=8192)
Definition httplib.h:3508
Definition httplib.h:3899
bool is_valid_utf8(const std::string &s)
Definition httplib.h:4552
bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload, bool &fin, bool expect_masked, size_t max_len)
Definition httplib.h:4822
Definition httplib.h:516
CloseStatus
Definition httplib.h:3751
@ Abnormal
Definition httplib.h:3757
@ MessageTooBig
Definition httplib.h:3760
@ NoStatus
Definition httplib.h:3756
@ InvalidPayload
Definition httplib.h:3758
@ InternalError
Definition httplib.h:3762
@ Normal
Definition httplib.h:3752
@ UnsupportedData
Definition httplib.h:3755
@ PolicyViolation
Definition httplib.h:3759
@ MandatoryExtension
Definition httplib.h:3761
@ GoingAway
Definition httplib.h:3753
@ ProtocolError
Definition httplib.h:3754
ReadResult
Definition httplib.h:3765
@ Binary
Definition httplib.h:3765
@ Text
Definition httplib.h:3765
@ Fail
Definition httplib.h:3765
Opcode
Definition httplib.h:3742
@ Pong
Definition httplib.h:3748
@ Binary
Definition httplib.h:3745
@ Text
Definition httplib.h:3744
@ Ping
Definition httplib.h:3747
@ Close
Definition httplib.h:3746
@ Continuation
Definition httplib.h:3743
Definition httplib.h:514
std::function< bool(size_t offset, DataSink &sink)> ContentProviderWithoutLength
Definition httplib.h:1064
std::ostream & operator<<(std::ostream &os, const Error &obj)
Definition httplib.h:9424
FormDataProvider make_file_provider(const std::string &name, const std::string &filepath, const std::string &filename=std::string(), const std::string &content_type=std::string())
Definition httplib.h:1078
std::pair< std::string, std::string > make_range_header(const Ranges &ranges)
Definition httplib.h:9737
std::string encode_path_component(const std::string &component)
Definition httplib.h:9551
std::function< bool(const Response &response)> ResponseHandler
Definition httplib.h:984
std::function< bool( const char *data, size_t data_length, size_t offset, size_t total_length)> ContentReceiverWithProgress
Definition httplib.h:1134
std::function< void(socket_t sock)> SocketOptions
Definition httplib.h:1522
std::function< bool(const FormData &file)> FormDataHeader
Definition httplib.h:1140
std::string hosted_at(const std::string &hostname)
Definition httplib.h:9430
std::string decode_uri_component(const std::string &value)
Definition httplib.h:9511
std::function< void(const Request &, const Response &)> Logger
Definition httplib.h:1516
std::vector< UploadFormData > UploadFormDataItems
Definition httplib.h:1026
Error
Definition httplib.h:1399
@ Canceled
Definition httplib.h:1407
@ ConnectionTimeout
Definition httplib.h:1414
@ Write
Definition httplib.h:1405
@ ExceedUriMaxLength
Definition httplib.h:1421
@ TooManyFormDataFiles
Definition httplib.h:1419
@ SSLServerVerification
Definition httplib.h:1410
@ ConnectionClosed
Definition httplib.h:1416
@ SSLServerHostnameVerification
Definition httplib.h:1411
@ ExceedMaxSocketDescriptorCount
Definition httplib.h:1422
@ InvalidHTTPMethod
Definition httplib.h:1424
@ OpenFile
Definition httplib.h:1428
@ ResourceExhaustion
Definition httplib.h:1418
@ Success
Definition httplib.h:1400
@ BindIPAddress
Definition httplib.h:1403
@ Read
Definition httplib.h:1404
@ InvalidRequestLine
Definition httplib.h:1423
@ Compression
Definition httplib.h:1413
@ GetSockName
Definition httplib.h:1430
@ Unknown
Definition httplib.h:1401
@ InvalidHeaders
Definition httplib.h:1426
@ SSLConnection
Definition httplib.h:1408
@ UnsupportedAddressFamily
Definition httplib.h:1431
@ ProxyConnection
Definition httplib.h:1415
@ InvalidRangeHeader
Definition httplib.h:1433
@ ExceedMaxPayloadSize
Definition httplib.h:1420
@ MultipartParsing
Definition httplib.h:1427
@ Connection
Definition httplib.h:1402
@ Timeout
Definition httplib.h:1417
@ Listen
Definition httplib.h:1429
@ HTTPParsing
Definition httplib.h:1432
@ ExceedRedirectCount
Definition httplib.h:1406
@ InvalidHTTPVersion
Definition httplib.h:1425
@ SSLLoadingCerts
Definition httplib.h:1409
@ UnsupportedMultipartBoundaryChars
Definition httplib.h:1412
@ SSLPeerCouldBeClosed_
Definition httplib.h:1436
std::string to_string(Error error)
Definition httplib.h:9379
std::pair< size_t, ContentProvider > make_file_body(const std::string &filepath)
Definition httplib.h:1106
std::string encode_uri_component(const std::string &value)
Definition httplib.h:9468
std::pair< std::string, std::string > make_basic_authentication_header(const std::string &username, const std::string &password, bool is_proxy=false)
Definition httplib.h:9751
std::function< bool(const char *data, size_t data_length)> ContentReceiver
Definition httplib.h:1137
std::function< bool(size_t offset, size_t length, DataSink &sink)> ContentProvider
Definition httplib.h:1061
std::string sanitize_filename(const std::string &filename)
Definition httplib.h:9702
const char * status_message(int status)
Definition httplib.h:9303
std::multimap< std::string, std::string > Params
Definition httplib.h:892
std::smatch Match
Definition httplib.h:893
SSLVerifierResponse
Definition httplib.h:804
@ NoDecisionMade
Definition httplib.h:806
@ CertificateRejected
Definition httplib.h:810
@ CertificateAccepted
Definition httplib.h:808
std::string get_bearer_token_auth(const Request &req)
Definition httplib.h:9294
std::pair< std::string, std::string > make_bearer_token_authentication_header(const std::string &token, bool is_proxy=false)
Definition httplib.h:9759
std::function< bool(size_t current, size_t total)> DownloadProgress
Definition httplib.h:895
std::unordered_multimap< std::string, std::string, detail::case_ignore::hash, detail::case_ignore::equal_to > Headers
Definition httplib.h:888
std::function< void(bool success)> ContentProviderResourceReleaser
Definition httplib.h:1067
std::vector< Range > Ranges
Definition httplib.h:1165
std::string decode_path_component(const std::string &component)
Definition httplib.h:9586
std::string decode_uri(const std::string &value)
Definition httplib.h:9531
std::string encode_query_component(const std::string &component, bool space_as_plus=true)
Definition httplib.h:9623
std::vector< FormDataProvider > FormDataProviderItems
Definition httplib.h:1075
std::multimap< std::string, FormField > FormFields
Definition httplib.h:999
bool set_socket_opt(socket_t sock, int level, int optname, int optval)
Definition httplib.h:9289
std::function< void(const Error &, const Request *)> ErrorLogger
Definition httplib.h:1520
std::string decode_query_component(const std::string &component, bool plus_as_space=true)
Definition httplib.h:9677
StatusCode
Definition httplib.h:813
@ Conflict_409
Definition httplib.h:853
@ IMUsed_226
Definition httplib.h:830
@ SwitchingProtocol_101
Definition httplib.h:816
@ MisdirectedRequest_421
Definition httplib.h:863
@ FailedDependency_424
Definition httplib.h:866
@ Found_302
Definition httplib.h:835
@ MultipleChoices_300
Definition httplib.h:833
@ UnavailableForLegalReasons_451
Definition httplib.h:872
@ LoopDetected_508
Definition httplib.h:883
@ ServiceUnavailable_503
Definition httplib.h:878
@ EarlyHints_103
Definition httplib.h:818
@ ProxyAuthenticationRequired_407
Definition httplib.h:851
@ Processing_102
Definition httplib.h:817
@ Locked_423
Definition httplib.h:865
@ PaymentRequired_402
Definition httplib.h:846
@ NonAuthoritativeInformation_203
Definition httplib.h:824
@ UriTooLong_414
Definition httplib.h:858
@ Accepted_202
Definition httplib.h:823
@ ResetContent_205
Definition httplib.h:826
@ Forbidden_403
Definition httplib.h:847
@ Gone_410
Definition httplib.h:854
@ UnsupportedMediaType_415
Definition httplib.h:859
@ NotExtended_510
Definition httplib.h:884
@ NotModified_304
Definition httplib.h:837
@ VariantAlsoNegotiates_506
Definition httplib.h:881
@ unused_306
Definition httplib.h:839
@ TooManyRequests_429
Definition httplib.h:870
@ RequestTimeout_408
Definition httplib.h:852
@ OK_200
Definition httplib.h:821
@ NetworkAuthenticationRequired_511
Definition httplib.h:885
@ AlreadyReported_208
Definition httplib.h:829
@ MultiStatus_207
Definition httplib.h:828
@ ImATeapot_418
Definition httplib.h:862
@ NotAcceptable_406
Definition httplib.h:850
@ InternalServerError_500
Definition httplib.h:875
@ ExpectationFailed_417
Definition httplib.h:861
@ MethodNotAllowed_405
Definition httplib.h:849
@ NotFound_404
Definition httplib.h:848
@ TemporaryRedirect_307
Definition httplib.h:840
@ PermanentRedirect_308
Definition httplib.h:841
@ SeeOther_303
Definition httplib.h:836
@ Unauthorized_401
Definition httplib.h:845
@ PreconditionFailed_412
Definition httplib.h:856
@ Created_201
Definition httplib.h:822
@ NotImplemented_501
Definition httplib.h:876
@ UseProxy_305
Definition httplib.h:838
@ NoContent_204
Definition httplib.h:825
@ InsufficientStorage_507
Definition httplib.h:882
@ TooEarly_425
Definition httplib.h:867
@ BadGateway_502
Definition httplib.h:877
@ MovedPermanently_301
Definition httplib.h:834
@ PreconditionRequired_428
Definition httplib.h:869
@ PartialContent_206
Definition httplib.h:827
@ LengthRequired_411
Definition httplib.h:855
@ PayloadTooLarge_413
Definition httplib.h:857
@ RangeNotSatisfiable_416
Definition httplib.h:860
@ UnprocessableContent_422
Definition httplib.h:864
@ RequestHeaderFieldsTooLarge_431
Definition httplib.h:871
@ Continue_100
Definition httplib.h:815
@ BadRequest_400
Definition httplib.h:844
@ UpgradeRequired_426
Definition httplib.h:868
@ GatewayTimeout_504
Definition httplib.h:879
@ HttpVersionNotSupported_505
Definition httplib.h:880
std::string encode_uri(const std::string &value)
Definition httplib.h:9489
std::function< bool(size_t current, size_t total)> UploadProgress
Definition httplib.h:896
void default_socket_options(socket_t sock)
Definition httplib.h:9279
std::string append_query_params(const std::string &path, const Params &params)
Definition httplib.h:9726
std::pair< ssize_t, ssize_t > Range
Definition httplib.h:1164
std::multimap< std::string, FormData > FormFiles
Definition httplib.h:1001
@ string
string value
Definition json.hpp:1223
STL namespace.
*specialization for std::less< value_t > *see nlohmann::detail::value_t rhs const noexcept
Definition json.hpp:20328
double f(const std::string &, double, bool) noexcept
Definition profile_cpp_calls_2.cpp:4
std::string to_string(int val)
Definition Parser.cc:71
Definition GameApi.h:57
Definition GameApi.h:42
ClientConnection & operator=(ClientConnection &&other) noexcept
Definition httplib.h:1989
ClientConnection(ClientConnection &&other) noexcept
Definition httplib.h:1976
~ClientConnection()
Definition httplib.h:15011
ClientConnection & operator=(const ClientConnection &)=delete
socket_t sock
Definition httplib.h:1965
ClientConnection(const ClientConnection &)=delete
bool is_open() const
Definition httplib.h:1967
Definition httplib.h:2280
bool is_open() const
Definition httplib.h:2286
socket_t sock
Definition httplib.h:2281
std::chrono::time_point< std::chrono::steady_clock > start_time_
Definition httplib.h:2284
Definition httplib.h:2074
bool trailers_parsed_
Definition httplib.h:2094
friend class ClientImpl
Definition httplib.h:2097
bool has_read_error() const
Definition httplib.h:2092
Error error
Definition httplib.h:2076
StreamHandle & operator=(StreamHandle &&)=default
ssize_t read(char *buf, size_t len)
Definition httplib.h:13003
StreamHandle(const StreamHandle &)=delete
bool is_valid() const
Definition httplib.h:2085
StreamHandle & operator=(const StreamHandle &)=delete
Error get_read_error() const
Definition httplib.h:2091
StreamHandle(StreamHandle &&)=default
void parse_trailers_if_needed()
Definition httplib.h:13076
std::unique_ptr< Response > response
Definition httplib.h:2075
Definition httplib.h:1069
std::string name
Definition httplib.h:1070
std::string filename
Definition httplib.h:1072
std::string content_type
Definition httplib.h:1073
ContentProviderWithoutLength provider
Definition httplib.h:1071
Definition httplib.h:986
std::string content
Definition httplib.h:988
std::string filename
Definition httplib.h:989
std::string content_type
Definition httplib.h:990
Headers headers
Definition httplib.h:991
std::string name
Definition httplib.h:987
Definition httplib.h:994
std::string content
Definition httplib.h:996
Headers headers
Definition httplib.h:997
std::string name
Definition httplib.h:995
Definition httplib.h:1003
std::string get_field(const std::string &key, size_t id=0) const
Definition httplib.h:9835
bool has_file(const std::string &key) const
Definition httplib.h:9878
FormData get_file(const std::string &key, size_t id=0) const
Definition httplib.h:9863
bool has_field(const std::string &key) const
Definition httplib.h:9854
size_t get_file_count(const std::string &key) const
Definition httplib.h:9882
std::vector< FormData > get_files(const std::string &key) const
Definition httplib.h:9869
FormFiles files
Definition httplib.h:1005
FormFields fields
Definition httplib.h:1004
std::vector< std::string > get_fields(const std::string &key) const
Definition httplib.h:9845
size_t get_field_count(const std::string &key) const
Definition httplib.h:9858
Definition httplib.h:1266
MultipartFormData form
Definition httplib.h:1283
bool has_param(const std::string &key) const
Definition httplib.h:9804
bool has_header(const std::string &key) const
Definition httplib.h:9772
DownloadProgress download_progress
Definition httplib.h:1293
size_t get_param_value_count(const std::string &key) const
Definition httplib.h:9824
std::vector< std::string > accept_content_types
Definition httplib.h:1290
size_t get_trailer_value_count(const std::string &key) const
Definition httplib.h:9799
ContentReceiverWithProgress content_receiver
Definition httplib.h:1292
std::string local_addr
Definition httplib.h:1277
std::unordered_map< std::string, std::string > path_params
Definition httplib.h:1286
UploadProgress upload_progress
Definition httplib.h:1294
Headers trailers
Definition httplib.h:1272
std::string remote_addr
Definition httplib.h:1275
std::string get_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:9776
int remote_port
Definition httplib.h:1276
Params params
Definition httplib.h:1270
std::string target
Definition httplib.h:1282
std::vector< std::string > get_param_values(const std::string &key) const
Definition httplib.h:9814
size_t get_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:9767
std::string get_param_value(const std::string &key, size_t id=0) const
Definition httplib.h:9808
size_t authorization_count_
Definition httplib.h:1321
bool is_multipart_form_data() const
Definition httplib.h:9829
Match matches
Definition httplib.h:1285
std::string version
Definition httplib.h:1281
bool has_trailer(const std::string &key) const
Definition httplib.h:9790
bool body_consumed_
Definition httplib.h:1316
std::chrono::time_point< std::chrono::steady_clock > start_time_
Definition httplib.h:1322
ContentProvider content_provider_
Definition httplib.h:1319
std::string get_trailer_value(const std::string &key, size_t id=0) const
Definition httplib.h:9794
size_t content_length_
Definition httplib.h:1318
Ranges ranges
Definition httplib.h:1284
std::string path
Definition httplib.h:1268
bool is_chunked_content_provider_
Definition httplib.h:1320
std::function< bool()> is_connection_closed
Definition httplib.h:1287
std::string body
Definition httplib.h:1273
size_t redirect_count_
Definition httplib.h:1317
std::string matched_route
Definition httplib.h:1269
size_t get_header_value_count(const std::string &key) const
Definition httplib.h:9781
ResponseHandler response_handler
Definition httplib.h:1291
int local_port
Definition httplib.h:1278
std::string method
Definition httplib.h:1267
void set_header(const std::string &key, const std::string &val)
Definition httplib.h:9785
Headers headers
Definition httplib.h:1271
Definition httplib.h:1332
ContentProvider content_provider_
Definition httplib.h:1391
bool is_chunked_content_provider_
Definition httplib.h:1393
Headers trailers
Definition httplib.h:1337
void set_redirect(const std::string &url, int status=StatusCode::Found_302)
Definition httplib.h:9925
Response & operator=(Response &&)=default
std::string file_content_path_
Definition httplib.h:1395
size_t get_header_value_u64(const std::string &key, size_t def=0, size_t id=0) const
Definition httplib.h:9888
Response()=default
size_t content_length_
Definition httplib.h:1390
void set_content_provider(size_t length, const std::string &content_type, ContentProvider provider, ContentProviderResourceReleaser resource_releaser=nullptr)
Definition httplib.h:9959
std::string version
Definition httplib.h:1333
Headers headers
Definition httplib.h:1336
void set_content(const char *s, size_t n, const std::string &content_type)
Definition httplib.h:9936
bool has_header(const std::string &key) const
Definition httplib.h:9893
bool content_provider_success_
Definition httplib.h:1394
ContentProviderResourceReleaser content_provider_resource_releaser_
Definition httplib.h:1392
std::string get_header_value(const std::string &key, const char *def="", size_t id=0) const
Definition httplib.h:9897
void set_chunked_content_provider(const std::string &content_type, ContentProviderWithoutLength provider, ContentProviderResourceReleaser resource_releaser=nullptr)
Definition httplib.h:9979
int status
Definition httplib.h:1334
std::string get_trailer_value(const std::string &key, size_t id=0) const
Definition httplib.h:9915
UserData user_data
Definition httplib.h:1343
std::string reason
Definition httplib.h:1335
size_t get_trailer_value_count(const std::string &key) const
Definition httplib.h:9920
std::string body
Definition httplib.h:1338
void set_file_content(const std::string &path, const std::string &content_type)
Definition httplib.h:9989
bool has_trailer(const std::string &key) const
Definition httplib.h:9911
Response & operator=(const Response &)=default
Response(const Response &)=default
~Response()
Definition httplib.h:1383
std::string file_content_content_type_
Definition httplib.h:1396
Response(Response &&)=default
void set_header(const std::string &key, const std::string &val)
Definition httplib.h:9907
size_t get_header_value_count(const std::string &key) const
Definition httplib.h:9903
std::string location
Definition httplib.h:1339
Definition httplib.h:1020
std::string filename
Definition httplib.h:1023
std::string content_type
Definition httplib.h:1024
std::string content
Definition httplib.h:1022
std::string name
Definition httplib.h:1021
Definition httplib.h:2010
size_t bytes_read
Definition httplib.h:2015
bool has_content_length
Definition httplib.h:2012
std::unique_ptr< ChunkedDecoder > chunked_decoder
Definition httplib.h:2018
bool has_error() const
Definition httplib.h:2022
size_t content_length
Definition httplib.h:2013
Stream * stream
Definition httplib.h:2011
bool chunked
Definition httplib.h:2016
size_t payload_max_length
Definition httplib.h:2014
ssize_t read(char *buf, size_t len)
Definition httplib.h:10032
Error last_error
Definition httplib.h:2019
bool eof
Definition httplib.h:2017
Definition httplib.h:3251
char line_buf[64]
Definition httplib.h:3255
ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset, size_t &out_chunk_total)
Definition httplib.h:13099
size_t last_chunk_offset
Definition httplib.h:3257
size_t chunk_remaining
Definition httplib.h:3253
bool parse_trailers_into(Headers &dest, const Headers &src_headers)
Definition httplib.h:13158
bool finished
Definition httplib.h:3254
size_t last_chunk_total
Definition httplib.h:3256
Stream & strm
Definition httplib.h:3252
ChunkedDecoder(Stream &s)
Definition httplib.h:13097
Definition httplib.h:3011
time_t mtime() const
Definition httplib.h:4985
FileStat(const std::string &path)
Definition httplib.h:4970
size_t size() const
Definition httplib.h:4990
bool is_file() const
Definition httplib.h:4978
bool is_dir() const
Definition httplib.h:4981
Definition httplib.h:8312
size_t size
Definition httplib.h:8314
const char * data
Definition httplib.h:8313
Definition httplib.h:2044
NoProxyKind kind
Definition httplib.h:2045
std::string hostname_pattern
Definition httplib.h:2046
int prefix_bits
Definition httplib.h:2048
IPBytes net
Definition httplib.h:2047
Definition httplib.h:2051
bool is_ipv6
Definition httplib.h:2054
std::string hostname
Definition httplib.h:2052
bool is_ipv4
Definition httplib.h:2053
IPBytes ip
Definition httplib.h:2055
Definition httplib.h:715
std::string scheme
Definition httplib.h:716
std::string host
Definition httplib.h:717
std::string path
Definition httplib.h:719
std::string port
Definition httplib.h:718
std::string query
Definition httplib.h:720
Definition httplib.h:912
virtual std::unique_ptr< any_storage > clone() const =0
virtual ~any_storage()=default
virtual any_type_id type_id() const noexcept=0
Definition httplib.h:918
any_type_id type_id() const noexcept override
Definition httplib.h:924
any_value(U &&v)
Definition httplib.h:920
std::unique_ptr< any_storage > clone() const override
Definition httplib.h:921
T value
Definition httplib.h:919
bool operator()(const std::string &a, const std::string &b) const
Definition httplib.h:585
Definition httplib.h:590
size_t operator()(const std::string &key) const
Definition httplib.h:591
size_t hash_core(const char *s, size_t l, size_t h) const
Definition httplib.h:595
Definition httplib.h:642
std::errc ec
Definition httplib.h:644
const char * ptr
Definition httplib.h:643
Definition httplib.h:615
scope_exit(std::function< void(void)> &&f)
Definition httplib.h:616
scope_exit(scope_exit &&rhs) noexcept
Definition httplib.h:619
void release()
Definition httplib.h:629
~scope_exit()
Definition httplib.h:625
Definition httplib.h:3660
std::string event
Definition httplib.h:3661
void clear()
Definition httplib.h:4004
SSEMessage()
Definition httplib.h:4002
std::string data
Definition httplib.h:3662
std::string id
Definition httplib.h:3663
get_client_ip()
Definition user.php:146
#define deflate
Definition zip_file.hpp:482
#define inflateEnd
Definition zip_file.hpp:491
#define deflateEnd
Definition zip_file.hpp:483
#define Z_DEFLATED
Definition zip_file.hpp:473
#define Z_DEFAULT_STRATEGY
Definition zip_file.hpp:468
#define deflateInit2
Definition zip_file.hpp:480
int intf
Definition zip_file.hpp:442
#define Z_STREAM_END
Definition zip_file.hpp:455
#define Z_FINISH
Definition zip_file.hpp:452
unsigned int uInt
Definition zip_file.hpp:437
#define Z_OK
Definition zip_file.hpp:454
#define inflateInit2
Definition zip_file.hpp:489
#define Z_STREAM_ERROR
Definition zip_file.hpp:458
#define Z_NO_FLUSH
Definition zip_file.hpp:448
Byte Bytef
Definition zip_file.hpp:439
#define Z_NULL
Definition zip_file.hpp:447
#define compress
Definition zip_file.hpp:485
#define z_stream
Definition zip_file.hpp:478
#define Z_DEFAULT_COMPRESSION
Definition zip_file.hpp:467